From a107a3b40f288e5fa02a4d360c18cedfdc792cb1 Mon Sep 17 00:00:00 2001 From: Nick Zelei <2420177+nickzelei@users.noreply.github.com> Date: Tue, 7 Jan 2025 11:02:08 -0800 Subject: [PATCH] NEOS-1630: Updates Subset Table to be more performant (#3099) --- .../[id]/subsets/components/SubsetCard.tsx | 205 ++++++++--------- .../[account]/new/job/job-form-validations.ts | 5 +- .../(mgmt)/[account]/new/job/subset/page.tsx | 216 ++++++++---------- .../web/components/FastTable/FastTable.tsx | 117 ++++++++++ .../web/components/FastTable/MemoizedCell.tsx | 35 +++ .../web/components/FastTable/MemoizedRow.tsx | 92 ++++++++ .../jobs/JobMappingTable/JobMappingTable.tsx | 182 +-------------- .../web/components/jobs/subsets/EditItem.tsx | 108 ++++----- .../jobs/subsets/EditItemDialog.tsx | 50 ++++ .../jobs/subsets/SubsetTable/ActionsCell.tsx | 74 ++++++ .../jobs/subsets/SubsetTable/Columns.tsx | 118 ++++++++++ .../subsets/SubsetTable/RootTableCell.tsx | 35 +++ .../jobs/subsets/SubsetTable/SubsetTable.tsx | 77 +++++++ .../jobs/subsets/subset-table/SubsetTable.tsx | 19 -- .../jobs/subsets/subset-table/column.tsx | 189 --------------- .../subset-table/data-table-column-header.tsx | 74 ------ .../jobs/subsets/subset-table/data-table.tsx | 163 ------------- .../apps/web/components/jobs/subsets/utils.ts | 28 +-- 18 files changed, 866 insertions(+), 921 deletions(-) create mode 100644 frontend/apps/web/components/FastTable/FastTable.tsx create mode 100644 frontend/apps/web/components/FastTable/MemoizedCell.tsx create mode 100644 frontend/apps/web/components/FastTable/MemoizedRow.tsx create mode 100644 frontend/apps/web/components/jobs/subsets/EditItemDialog.tsx create mode 100644 frontend/apps/web/components/jobs/subsets/SubsetTable/ActionsCell.tsx create mode 100644 frontend/apps/web/components/jobs/subsets/SubsetTable/Columns.tsx create mode 100644 frontend/apps/web/components/jobs/subsets/SubsetTable/RootTableCell.tsx create mode 100644 frontend/apps/web/components/jobs/subsets/SubsetTable/SubsetTable.tsx delete mode 100644 frontend/apps/web/components/jobs/subsets/subset-table/SubsetTable.tsx delete mode 100644 frontend/apps/web/components/jobs/subsets/subset-table/column.tsx delete mode 100644 frontend/apps/web/components/jobs/subsets/subset-table/data-table-column-header.tsx delete mode 100644 frontend/apps/web/components/jobs/subsets/subset-table/data-table.tsx diff --git a/frontend/apps/web/app/(mgmt)/[account]/jobs/[id]/subsets/components/SubsetCard.tsx b/frontend/apps/web/app/(mgmt)/[account]/jobs/[id]/subsets/components/SubsetCard.tsx index 2fc74cd66b..f5994573ba 100644 --- a/frontend/apps/web/app/(mgmt)/[account]/jobs/[id]/subsets/components/SubsetCard.tsx +++ b/frontend/apps/web/app/(mgmt)/[account]/jobs/[id]/subsets/components/SubsetCard.tsx @@ -5,26 +5,21 @@ import { import { SubsetFormValues } from '@/app/(mgmt)/[account]/new/job/job-form-validations'; import SubsetOptionsForm from '@/components/jobs/Form/SubsetOptionsForm'; import EditItem from '@/components/jobs/subsets/EditItem'; -import SubsetTable from '@/components/jobs/subsets/subset-table/SubsetTable'; -import { TableRow } from '@/components/jobs/subsets/subset-table/column'; +import EditItemDialog from '@/components/jobs/subsets/EditItemDialog'; +import { + SUBSET_TABLE_COLUMNS, + SubsetTableRow, +} from '@/components/jobs/subsets/SubsetTable/Columns'; +import SubsetTable from '@/components/jobs/subsets/SubsetTable/SubsetTable'; import { - GetColumnsForSqlAutocomplete, buildRowKey, buildTableRowData, + getColumnsForSqlAutocomplete, isValidSubsetType, } from '@/components/jobs/subsets/utils'; -import LearnMoreLink from '@/components/labels/LearnMoreLink'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; -import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from '@/components/ui/dialog'; import { Form } from '@/components/ui/form'; -import { Separator } from '@/components/ui/separator'; import { getErrorMessage } from '@/util/util'; import { create } from '@bufbuild/protobuf'; import { @@ -43,8 +38,8 @@ import { } from '@neosync/sdk'; import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; import { useQueryClient } from '@tanstack/react-query'; -import { ReactElement, useEffect, useState } from 'react'; -import { useForm } from 'react-hook-form'; +import { ReactElement, useEffect, useMemo, useState } from 'react'; +import { useFieldArray, useForm } from 'react-hook-form'; import { toast } from 'sonner'; import { toJobSourceSqlSubsetSchemas } from '../../../util'; import { getConnectionIdFromSource } from '../../source/components/util'; @@ -86,13 +81,14 @@ export default function SubsetCard(props: Props): ReactElement { useEffect(() => { if (!isTableConstraintsValidating && fkConstraints) { + const newRootTables = new Set(rootTables); data?.job?.mappings.forEach((m) => { const tn = `${m.schema}.${m.table}`; if (!fkConstraints[tn]) { - rootTables.add(tn); - setRootTables(rootTables); + newRootTables.add(tn); } }); + setRootTables(newRootTables); } }, [fkConstraints, isTableConstraintsValidating]); @@ -103,17 +99,34 @@ export default function SubsetCard(props: Props): ReactElement { values: formValues, }); - const tableRowData = buildTableRowData( - data?.job?.mappings ?? [], - rootTables, - form.watch().subsets // ensures that all form changes cause a re-render since stuff happens outside of the form that depends on the form values - ); - const [itemToEdit, setItemToEdit] = useState(); + const formSubsets = form.watch().subsets; // ensures that all form changes cause a re-render since stuff happens outside of the form that depends on the form values + const { update: updateSubsetsFormValues, append: addSubsetsFormValues } = + useFieldArray({ + control: form.control, + name: 'subsets', + }); + + const tableRowData = useMemo(() => { + return buildTableRowData( + data?.job?.mappings ?? [], + rootTables, + formSubsets + ); + }, [data?.job?.mappings, rootTables, formSubsets]); + const [itemToEdit, setItemToEdit] = useState(); const formValuesMap = new Map( formValues.subsets.map((ss) => [buildRowKey(ss.schema, ss.table), ss]) ); + const sqlAutocompleteColumns = useMemo(() => { + return getColumnsForSqlAutocomplete( + data?.job?.mappings ?? [], + itemToEdit?.schema ?? '', + itemToEdit?.table ?? '' + ); + }, [data?.job?.mappings, itemToEdit?.schema, itemToEdit?.table]); + if (isJobLoading) { return (
@@ -169,7 +182,11 @@ export default function SubsetCard(props: Props): ReactElement { } } - function hasLocalChange(schema: string, table: string): boolean { + function hasLocalChange( + _rowIdx: number, + schema: string, + table: string + ): boolean { const key = buildRowKey(schema, table); const trData = tableRowData[key]; @@ -182,7 +199,11 @@ export default function SubsetCard(props: Props): ReactElement { return trData.where !== svrData?.whereClause; } - function onLocalRowReset(schema: string, table: string): void { + function onLocalRowReset( + _rowIdx: number, + schema: string, + table: string + ): void { const key = buildRowKey(schema, table); const idx = form .getValues() @@ -191,7 +212,7 @@ export default function SubsetCard(props: Props): ReactElement { ); if (idx >= 0) { const svrData = formValuesMap.get(key); - form.setValue(`subsets.${idx}`, { + updateSubsetsFormValues(idx, { schema: schema, table: table, whereClause: svrData?.whereClause ?? undefined, @@ -210,7 +231,8 @@ export default function SubsetCard(props: Props): ReactElement {
{ + columns={SUBSET_TABLE_COLUMNS} + onEdit={(_rowIdx, schema, table) => { setIsDialogOpen(true); const key = buildRowKey(schema, table); if (tableRowData[key]) { @@ -223,90 +245,53 @@ export default function SubsetCard(props: Props): ReactElement { onReset={onLocalRowReset} />
-
{ - e.stopPropagation(); - }} - > - - e.preventDefault()} - onEscapeKeyDown={(e) => e.preventDefault()} - > - -
-
-
-
- - Subset Query - -
-
-
- - Subset your data using SQL expressions.{' '} - - -
-
-
- -
-
- { - setItemToEdit(undefined); - setIsDialogOpen(false); - }} - columns={GetColumnsForSqlAutocomplete( - data?.job?.mappings ?? [], - itemToEdit - )} - onSave={() => { - if (!itemToEdit) { - return; - } - const key = buildRowKey( - itemToEdit.schema, - itemToEdit.table - ); - const idx = form - .getValues() - .subsets.findIndex( - (item) => - buildRowKey(item.schema, item.table) === key - ); - if (idx >= 0) { - form.setValue(`subsets.${idx}`, { - schema: itemToEdit.schema, - table: itemToEdit.table, - whereClause: itemToEdit.where, - }); - } else { - form.setValue( - `subsets`, - form.getValues().subsets.concat({ - schema: itemToEdit.schema, - table: itemToEdit.table, - whereClause: itemToEdit.where, - }) - ); - } - setItemToEdit(undefined); - setIsDialogOpen(false); - }} - connectionType={connectionType} - /> -
-
-
-
+ + { + setItemToEdit(undefined); + setIsDialogOpen(false); + }} + columns={sqlAutocompleteColumns} + onSave={() => { + if (!itemToEdit) { + return; + } + const key = buildRowKey( + itemToEdit.schema, + itemToEdit.table + ); + const idx = form + .getValues() + .subsets.findIndex( + (item) => buildRowKey(item.schema, item.table) === key + ); + if (idx >= 0) { + updateSubsetsFormValues(idx, { + schema: itemToEdit.schema, + table: itemToEdit.table, + whereClause: itemToEdit.where, + }); + } else { + addSubsetsFormValues({ + schema: itemToEdit.schema, + table: itemToEdit.table, + whereClause: itemToEdit.where, + }); + } + setItemToEdit(undefined); + setIsDialogOpen(false); + }} + connectionType={connectionType} + /> + } + />
+
{showRowCountButton && ( <> @@ -303,56 +332,31 @@ export default function EditItem(props: Props): ReactElement { )} + + + + + + +

+ Applies changes to table only, click Save below to fully + submit changes +

+
+
+
-
- onWhereChange(e?.replace('WHERE ', '') ?? '')} - options={editorOptions} - /> -
- -
- - - - - - - -

- Applies changes to table only, click Save below to fully submit - changes -

-
-
-
-
); } diff --git a/frontend/apps/web/components/jobs/subsets/EditItemDialog.tsx b/frontend/apps/web/components/jobs/subsets/EditItemDialog.tsx new file mode 100644 index 0000000000..3732a0330f --- /dev/null +++ b/frontend/apps/web/components/jobs/subsets/EditItemDialog.tsx @@ -0,0 +1,50 @@ +import LearnMoreLink from '@/components/labels/LearnMoreLink'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Separator } from '@/components/ui/separator'; +import { ReactElement } from 'react'; + +interface Props { + open: boolean; + onOpenChange(open: boolean): void; + + body: ReactElement; +} + +export default function EditItemDialog(props: Props): ReactElement { + const { open, onOpenChange, body } = props; + return ( + + e.preventDefault()} + onEscapeKeyDown={(e) => e.preventDefault()} + > + +
+
+
+
+ Subset Query +
+
+
+ + Subset your data using SQL expressions.{' '} + + +
+
+
+ +
+
{body}
+
+
+ ); +} diff --git a/frontend/apps/web/components/jobs/subsets/SubsetTable/ActionsCell.tsx b/frontend/apps/web/components/jobs/subsets/SubsetTable/ActionsCell.tsx new file mode 100644 index 0000000000..45c3b4e44b --- /dev/null +++ b/frontend/apps/web/components/jobs/subsets/SubsetTable/ActionsCell.tsx @@ -0,0 +1,74 @@ +import { Button } from '@/components/ui/button'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { Pencil1Icon, ReloadIcon } from '@radix-ui/react-icons'; +import { ReactElement } from 'react'; + +interface Props { + onEdit(): void; + onReset(): void; + isResetDisabled: boolean; +} + +export default function ActionsCell(props: Props): ReactElement { + const { onEdit, onReset, isResetDisabled } = props; + return ( +
+ onEdit()} /> + onReset()} isDisabled={isResetDisabled} /> +
+ ); +} + +interface EditActionProps { + onClick(): void; +} + +function EditAction(props: EditActionProps): ReactElement { + const { onClick } = props; + return ( + + ); +} + +interface ResetActionProps { + onClick(): void; + isDisabled: boolean; +} + +function ResetAction(props: ResetActionProps): ReactElement { + const { onClick, isDisabled } = props; + return ( + + + + + + +

Reset changes made locally to this row

+
+
+
+ ); +} diff --git a/frontend/apps/web/components/jobs/subsets/SubsetTable/Columns.tsx b/frontend/apps/web/components/jobs/subsets/SubsetTable/Columns.tsx new file mode 100644 index 0000000000..b6373bda15 --- /dev/null +++ b/frontend/apps/web/components/jobs/subsets/SubsetTable/Columns.tsx @@ -0,0 +1,118 @@ +import TruncatedText from '@/components/TruncatedText'; +import { ColumnDef, createColumnHelper } from '@tanstack/react-table'; +import { SchemaColumnHeader } from '../../SchemaTable/SchemaColumnHeader'; +import ActionsCell from './ActionsCell'; +import RootTableCell from './RootTableCell'; + +export interface SubsetTableRow { + schema: string; + table: string; + where?: string; + isRootTable: boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type ColumnTValue = any; + +function getColumns(): ColumnDef[] { + const columnHelper = createColumnHelper(); + + const schemaColumn = columnHelper.accessor('schema', { + header({ column }) { + return ; + }, + cell({ getValue }) { + return ; + }, + }); + + const tableColumn = columnHelper.accessor('table', { + header({ column }) { + return ; + }, + cell({ getValue }) { + return ; + }, + }); + + const isRootTableColumn = columnHelper.accessor( + (row) => (row.isRootTable ? 'Root' : ''), + { + id: 'isRootTable', + header({ column }) { + return ; + }, + cell({ getValue }) { + return ; + }, + } + ); + + const whereColumn = columnHelper.accessor((row) => row.where ?? '', { + id: 'where', + header({ column }) { + return ; + }, + cell({ getValue }) { + const whereValue = getValue(); + return ( +
+ + {!!whereValue && ( +
+                {whereValue}
+              
+ )} +
+
+ ); + }, + size: 250, + }); + + const actionsColumn = columnHelper.display({ + id: 'actions', + enableSorting: false, + enableColumnFilter: false, + header: ({ column }) => ( + + ), + cell: ({ row, table }) => { + const isResetDisabled = !table.options.meta?.subsetTable?.hasLocalChange( + row.index, + row.original.schema, + row.original.table + ); + + return ( + + table.options.meta?.subsetTable?.onEdit( + row.index, + row.original.schema, + row.original.table + ) + } + onReset={() => + table.options.meta?.subsetTable?.onReset( + row.index, + row.original.schema, + row.original.table + ) + } + isResetDisabled={isResetDisabled} + /> + ); + }, + }); + + return [ + schemaColumn, + tableColumn, + isRootTableColumn, + whereColumn, + actionsColumn, + ]; +} + +export const SUBSET_TABLE_COLUMNS = getColumns(); diff --git a/frontend/apps/web/components/jobs/subsets/SubsetTable/RootTableCell.tsx b/frontend/apps/web/components/jobs/subsets/SubsetTable/RootTableCell.tsx new file mode 100644 index 0000000000..ea85eff0c4 --- /dev/null +++ b/frontend/apps/web/components/jobs/subsets/SubsetTable/RootTableCell.tsx @@ -0,0 +1,35 @@ +import { Badge } from '@/components/ui/badge'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +import { ReactElement } from 'react'; + +interface Props { + isRootTable: boolean; +} +export default function RootTableCell(props: Props): ReactElement { + const { isRootTable } = props; + return ( +
+ {isRootTable && ( + + + +
+ Root +
+
+ + This is a Root table that only has foreign key references to + children tables. Subsetting this table will subset all of + it's children tables. + +
+
+ )} +
+ ); +} diff --git a/frontend/apps/web/components/jobs/subsets/SubsetTable/SubsetTable.tsx b/frontend/apps/web/components/jobs/subsets/SubsetTable/SubsetTable.tsx new file mode 100644 index 0000000000..8b1417f222 --- /dev/null +++ b/frontend/apps/web/components/jobs/subsets/SubsetTable/SubsetTable.tsx @@ -0,0 +1,77 @@ +import ButtonText from '@/components/ButtonText'; +import FastTable from '@/components/FastTable/FastTable'; +import { Button } from '@/components/ui/button'; +import { Cross2Icon } from '@radix-ui/react-icons'; +import { + ColumnDef, + getCoreRowModel, + getFilteredRowModel, + getSortedRowModel, + RowData, + useReactTable, +} from '@tanstack/react-table'; +import { ReactElement } from 'react'; + +declare module '@tanstack/react-table' { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + interface TableMeta { + subsetTable?: { + onEdit(rowIndex: number, schema: string, table: string): void; + onReset(rowIndex: number, schema: string, table: string): void; + hasLocalChange(rowIndex: number, schema: string, table: string): boolean; + }; + } +} + +interface Props { + data: TData[]; + columns: ColumnDef[]; + onEdit(rowIndex: number, schema: string, table: string): void; + onReset(rowIndex: number, schema: string, table: string): void; + hasLocalChange(rowIndex: number, schema: string, table: string): boolean; +} + +export default function SubsetTable( + props: Props +): ReactElement { + const { data, columns, onEdit, onReset, hasLocalChange } = props; + + const table = useReactTable({ + data, + columns, + enableRowSelection: false, + initialState: {}, + getCoreRowModel: getCoreRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + meta: { + subsetTable: { + onEdit, + onReset, + hasLocalChange, + }, + }, + }); + + return ( +
+
+
+ +
+
+ 33} rowOverscan={50} /> +
+ ); +} diff --git a/frontend/apps/web/components/jobs/subsets/subset-table/SubsetTable.tsx b/frontend/apps/web/components/jobs/subsets/subset-table/SubsetTable.tsx deleted file mode 100644 index b6ac7707f5..0000000000 --- a/frontend/apps/web/components/jobs/subsets/subset-table/SubsetTable.tsx +++ /dev/null @@ -1,19 +0,0 @@ -'use client'; -import { ReactElement } from 'react'; -import { TableRow, getColumns } from './column'; -import { DataTable } from './data-table'; - -interface Props { - data: TableRow[]; - onEdit(schema: string, table: string): void; - hasLocalChange(schema: string, table: string): boolean; - onReset(schema: string, table: string): void; -} - -export default function SubsetTable(props: Props): ReactElement { - const { data, onEdit, hasLocalChange, onReset } = props; - - const columns = getColumns({ onEdit, hasLocalChange, onReset }); - - return ; -} diff --git a/frontend/apps/web/components/jobs/subsets/subset-table/column.tsx b/frontend/apps/web/components/jobs/subsets/subset-table/column.tsx deleted file mode 100644 index f5a5bf0076..0000000000 --- a/frontend/apps/web/components/jobs/subsets/subset-table/column.tsx +++ /dev/null @@ -1,189 +0,0 @@ -'use client'; - -import TruncatedText from '@/components/TruncatedText'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from '@/components/ui/tooltip'; -import { Pencil1Icon, ReloadIcon } from '@radix-ui/react-icons'; -import { ColumnDef } from '@tanstack/react-table'; -import { ReactElement } from 'react'; -import { SchemaColumnHeader } from '../../SchemaTable/SchemaColumnHeader'; -import { DataTableColumnHeader } from './data-table-column-header'; - -export interface TableRow { - schema: string; - table: string; - where?: string; - isRootTable?: boolean; -} - -interface GetColumnsProps { - onEdit(schema: string, table: string): void; - hasLocalChange(schema: string, table: string): boolean; - onReset(schema: string, table: string): void; -} - -export function getColumns(props: GetColumnsProps): ColumnDef[] { - const { onEdit, hasLocalChange, onReset } = props; - return [ - { - accessorKey: 'schema', - header: ({ column }) => ( - - ), - cell: ({ getValue }) => getValue(), - }, - { - accessorKey: 'table', - header: ({ column }) => ( - - ), - cell: ({ getValue }) => { - return ( -
- {getValue()} -
- ); - }, - }, - { - accessorFn: (row) => `${row.schema}.${row.table}`, - id: 'schemaTable', - footer: (props) => props.column.id, - header: ({ column }) => ( - - ), - cell: ({ row }) => { - return ( - - ); - }, - size: 250, - }, - { - accessorKey: 'isRootTable', - header: ({ column }) => ( - - ), - cell: ({ getValue }) => { - return ( -
- {getValue() && ( - - - -
- Root -
-
- - This is a Root table that only has foreign key references to - children tables. Subsetting this table will subset all of - it's children tables. - -
-
- )} -
- ); - }, - }, - { - accessorKey: 'where', - header: ({ column }) => ( - - ), - cell: ({ getValue }) => { - return ( -
- - {getValue() && ( -
-                  {getValue()}
-                
- )} -
-
- ); - }, - size: 250, - }, - { - accessorKey: 'edit', - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const schema = row.getValue('schema'); - const table = row.getValue('table'); - return ( -
- onEdit(schema, table)} /> - onReset(schema, table)} - isDisabled={!hasLocalChange(schema, table)} - /> -
- ); - }, - enableSorting: false, - enableColumnFilter: false, - }, - ]; -} - -interface EditActionProps { - onClick(): void; -} - -function EditAction(props: EditActionProps): ReactElement { - const { onClick } = props; - return ( - - ); -} - -interface ResetActionProps { - onClick(): void; - isDisabled: boolean; -} - -function ResetAction(props: ResetActionProps): ReactElement { - const { onClick, isDisabled } = props; - return ( - - - - - - -

Reset changes made locally to this row

-
-
-
- ); -} diff --git a/frontend/apps/web/components/jobs/subsets/subset-table/data-table-column-header.tsx b/frontend/apps/web/components/jobs/subsets/subset-table/data-table-column-header.tsx deleted file mode 100644 index f1a9a535a7..0000000000 --- a/frontend/apps/web/components/jobs/subsets/subset-table/data-table-column-header.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Button } from '@/components/ui/button'; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu'; -import { Input } from '@/components/ui/input'; -import { cn } from '@/libs/utils'; -import { - ArrowDownIcon, - ArrowUpIcon, - MagnifyingGlassIcon, -} from '@radix-ui/react-icons'; -import { Column } from '@tanstack/react-table'; - -interface DataTableColumnHeaderProps - extends React.HTMLAttributes { - column: Column; - title: string; -} - -export function DataTableColumnHeader({ - column, - title, - className, -}: DataTableColumnHeaderProps) { - return ( -
-
- - - - - - column.setFilterValue(e.target.value)} - placeholder={`Search...`} - className="w-36 border rounded" - /> - {column.getCanSort() && ( - <> - column.toggleSorting(false)}> - - Asc - - column.toggleSorting(true)}> - - Desc - - - )} - - -
-
- ); -} diff --git a/frontend/apps/web/components/jobs/subsets/subset-table/data-table.tsx b/frontend/apps/web/components/jobs/subsets/subset-table/data-table.tsx deleted file mode 100644 index 858252de4d..0000000000 --- a/frontend/apps/web/components/jobs/subsets/subset-table/data-table.tsx +++ /dev/null @@ -1,163 +0,0 @@ -'use client'; - -import { - ColumnDef, - flexRender, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getSortedRowModel, - useReactTable, -} from '@tanstack/react-table'; - -import ButtonText from '@/components/ButtonText'; -import { Button } from '@/components/ui/button'; -import { - StickyHeaderTable, - TableBody, - TableHead, - TableHeader, - TableRow, -} from '@/components/ui/table'; -import { cn } from '@/libs/utils'; -import { Cross2Icon } from '@radix-ui/react-icons'; -import { useVirtualizer } from '@tanstack/react-virtual'; -import { useRef } from 'react'; -interface DataTableProps { - columns: ColumnDef[]; - data: TData[]; -} - -export function DataTable({ - columns, - data, -}: DataTableProps) { - const table = useReactTable({ - data, - columns, - enableRowSelection: true, - initialState: { - columnVisibility: { - schema: false, - table: false, - }, - }, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - }); - - const { rows } = table.getRowModel(); - - const tableContainerRef = useRef(null); - - const rowVirtualizer = useVirtualizer({ - count: rows.length, - estimateSize: () => 33, - getScrollElement: () => tableContainerRef.current, - overscan: 5, - }); - - return ( -
-
-
- -
-
-
0 && 'overflow-auto' - )} - ref={tableContainerRef} - > - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() - )} - - ); - })} - - ))} - - - {rows.length === 0 && ( - - No schema(s) or table(s) found. - - )} - {rowVirtualizer.getVirtualItems().map((virtualRow) => { - const row = rows[virtualRow.index]; - return ( - rowVirtualizer.measureElement(node)} //measure dynamic row height - key={row.id} - style={{ - transform: `translateY(${virtualRow.start}px)`, - }} - className="items-center flex absolute w-full justify-between px-2" - > - {row.getVisibleCells().map((cell) => { - return ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} - - ); - })} - - ); - })} - - -
-
- ); -} diff --git a/frontend/apps/web/components/jobs/subsets/utils.ts b/frontend/apps/web/components/jobs/subsets/utils.ts index b364ed4664..a51433a9d3 100644 --- a/frontend/apps/web/components/jobs/subsets/utils.ts +++ b/frontend/apps/web/components/jobs/subsets/utils.ts @@ -1,7 +1,7 @@ import { ConnectionConfigCase } from '@/app/(mgmt)/[account]/connections/util'; import { SubsetFormValues } from '@/app/(mgmt)/[account]/new/job/job-form-validations'; import { Job, JobMapping } from '@neosync/sdk'; -import { TableRow } from './subset-table/column'; +import { SubsetTableRow } from './SubsetTable/Columns'; // Valid ConnectionConfigCase types. Using Extract here to ensure they stay consistent with what is available in ConnectionConfigCase export type ValidSubsetConnectionType = Extract< @@ -18,8 +18,8 @@ export function buildTableRowData( dbCols: DbCol[], rootTables: Set, existingSubsets: SubsetFormValues['subsets'] -): Record { - const tableMap: Record = {}; +): Record { + const tableMap: Record = {}; dbCols.forEach((mapping) => { const key = buildRowKey(mapping.schema, mapping.table); @@ -27,6 +27,7 @@ export function buildTableRowData( schema: mapping.schema, table: mapping.table, isRootTable: rootTables.has(key), + where: undefined, }; }); existingSubsets.forEach((subset) => { @@ -43,18 +44,17 @@ export function buildRowKey(schema: string, table: string): string { return `${schema}.${table}`; } -export function GetColumnsForSqlAutocomplete( - mappings: JobMapping[], - itemToEdit: TableRow | undefined +export function getColumnsForSqlAutocomplete( + mappings: Pick[], + schema: string, + table: string ): string[] { - let cols: string[] = []; - mappings.map((row) => { - if (row.schema == itemToEdit?.schema && row.table == itemToEdit.table) { - cols.push(row.column); - } - }); - - return cols; + if (!mappings) { + return []; + } + return mappings + .filter((row) => row.schema === schema && row.table === table) + .map((row) => row.column); } export function isJobSubsettable(job: Job): boolean {