Skip to content

Commit

Permalink
NEOS-1630: Updates Subset Table to be more performant (#3099)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickzelei authored Jan 7, 2025
1 parent 0ef11ea commit a107a3b
Show file tree
Hide file tree
Showing 18 changed files with 866 additions and 921 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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';
Expand Down Expand Up @@ -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]);

Expand All @@ -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<TableRow | undefined>();
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<SubsetTableRow | undefined>();

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 (
<div className="space-y-10">
Expand Down Expand Up @@ -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];

Expand All @@ -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()
Expand All @@ -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,
Expand All @@ -210,7 +231,8 @@ export default function SubsetCard(props: Props): ReactElement {
<div>
<SubsetTable
data={Object.values(tableRowData)}
onEdit={(schema, table) => {
columns={SUBSET_TABLE_COLUMNS}
onEdit={(_rowIdx, schema, table) => {
setIsDialogOpen(true);
const key = buildRowKey(schema, table);
if (tableRowData[key]) {
Expand All @@ -223,90 +245,53 @@ export default function SubsetCard(props: Props): ReactElement {
onReset={onLocalRowReset}
/>
</div>
<div
// this prevents the tooltips inside of the dialog from automatically opening when the dialog opens since it stops the focus from being set in the dialog component
onFocusCapture={(e) => {
e.stopPropagation();
}}
>
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent
className="max-w-5xl"
onPointerDownOutside={(e) => e.preventDefault()}
onEscapeKeyDown={(e) => e.preventDefault()}
>
<DialogHeader>
<div className="flex flex-row w-full">
<div className="flex flex-col space-y-2 w-full">
<div className="flex flex-row justify-between items-center">
<div className="flex flex-row gap-4">
<DialogTitle className="text-xl">
Subset Query
</DialogTitle>
</div>
</div>
<div className="flex flex-row items-center gap-2">
<DialogDescription>
Subset your data using SQL expressions.{' '}
<LearnMoreLink href="https://docs.neosync.dev/table-constraints/subsetting" />
</DialogDescription>
</div>
</div>
</div>
<Separator />
</DialogHeader>
<div className="pt-4">
<EditItem
connectionId={sourceConnectionId ?? ''}
item={itemToEdit}
onItem={setItemToEdit}
onCancel={() => {
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}
/>
</div>
</DialogContent>
</Dialog>
</div>

<EditItemDialog
open={isDialogOpen}
onOpenChange={setIsDialogOpen}
body={
<EditItem
connectionId={sourceConnectionId ?? ''}
item={itemToEdit}
onItem={setItemToEdit}
onCancel={() => {
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}
/>
}
/>
<div className="flex flex-row pt-10 justify-end">
<Button key="submit" type="submit">
Save
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,11 +283,12 @@ function getErrorConnectionTypes(
return '';
}

const SINGLE_SUBSET_FORM_SCHEMA = Yup.object({
const SingleSubsetFormValue = Yup.object({
schema: Yup.string().trim().required('A schema is required'),
table: Yup.string().trim().required('A table is required'),
whereClause: Yup.string().trim().optional(),
});
type SingleSubsetFormValue = Yup.InferType<typeof SingleSubsetFormValue>;

export const SingleTableConnectFormValues = Yup.object({
fkSourceConnectionId: Yup.string().required('Connection is required').uuid(),
Expand Down Expand Up @@ -411,7 +412,7 @@ export type SingleTableEditSourceFormValues = Yup.InferType<
>;

export const SubsetFormValues = Yup.object({
subsets: Yup.array(SINGLE_SUBSET_FORM_SCHEMA).required('Subset is required.'),
subsets: Yup.array(SingleSubsetFormValue).required('Subset is required.'),
subsetOptions: Yup.object({
subsetByForeignKeyConstraints: Yup.boolean().default(true),
}),
Expand Down
Loading

0 comments on commit a107a3b

Please sign in to comment.