Skip to content

Commit

Permalink
NEOS-1696, NEOS-1682: Adds bulk apply subsets, Adds mssql subset vali…
Browse files Browse the repository at this point in the history
…dation (#3101)
  • Loading branch information
nickzelei authored Jan 7, 2025
1 parent a107a3b commit 15a3d04
Show file tree
Hide file tree
Showing 17 changed files with 797 additions and 268 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ReactElement, useMemo } from 'react';
import { ReactElement } from 'react';

import ConnectionSelectContent from '@/app/(mgmt)/[account]/new/job/connect/ConnectionSelectContent';
import FormErrorMessage from '@/components/FormErrorMessage';
Expand All @@ -9,13 +9,12 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import useMonacoResizer from '@/libs/hooks/monaco/useMonacoResizer';
import useMonacoTheme from '@/libs/hooks/monaco/useMonacoTheme';
import { splitConnections } from '@/libs/utils';
import { Editor } from '@monaco-editor/react';
import { Connection } from '@neosync/sdk';
import { editor } from 'monaco-editor';
import { useTheme } from 'next-themes';
import { useResizeDetector } from 'react-resize-detector';
import { OnRefChangeType } from 'react-resize-detector/build/types/types';
import FormHeader from './FormHeader';
import { JobHookSqlFormValues, SqlTimingFormValue } from './validation';

Expand Down Expand Up @@ -97,11 +96,7 @@ const editorOptions: editor.IStandaloneEditorConstructionOptions = {
function EditSqlQuery(props: EditSqlQueryProps): ReactElement {
const { query, setQuery } = props;

const { resolvedTheme } = useTheme();
const theme = useMemo(
() => (resolvedTheme === 'dark' ? 'vs-dark' : 'cobalt'),
[resolvedTheme]
);
const theme = useMonacoTheme();
const { ref, width: editorWidth } = useMonacoResizer();

return (
Expand All @@ -126,36 +121,6 @@ function EditSqlQuery(props: EditSqlQueryProps): ReactElement {
);
}

// Offset is important here as without it, things get pretty strange I believe due to the container
// Lower offsets cause the resize to happen at a glacial pace, and without one, not at all.
const WIDTH_OFFSET = 10;

function useMonacoResizer(): {
ref: OnRefChangeType<HTMLDivElement>;
width: string;
} {
const { ref, width } = useResizeDetector<HTMLDivElement>({
handleHeight: false,
handleWidth: true,
refreshMode: 'debounce',
refreshRate: 10,
skipOnMount: false,
});

const editorWidth = useMemo(
() =>
width != null && width > WIDTH_OFFSET
? `${width - WIDTH_OFFSET}px`
: '100%',
[width]
);

return {
ref,
width: editorWidth,
};
}

interface SelectConnectionsProps {
jobConnections: Connection[];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ import {
} from '@/app/(mgmt)/[account]/connections/util';
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 EditItemDialog from '@/components/jobs/subsets/EditItemDialog';
import EditItem from '@/components/jobs/subsets/edit/EditItem';
import EditItemDialog from '@/components/jobs/subsets/edit/EditItemDialog';
import EditItems from '@/components/jobs/subsets/edit/EditItems';
import useOnBulkEditItemSave, {
BulkEditItem,
} from '@/components/jobs/subsets/edit/useOnBulkEditItemSave';
import useOnEditItemSave from '@/components/jobs/subsets/edit/useOnEditItemSave';
import {
SUBSET_TABLE_COLUMNS,
SubsetTableRow,
Expand All @@ -14,6 +19,7 @@ import SubsetTable from '@/components/jobs/subsets/SubsetTable/SubsetTable';
import {
buildRowKey,
buildTableRowData,
getBulkColumnsForSqlAutocomplete,
getColumnsForSqlAutocomplete,
isValidSubsetType,
} from '@/components/jobs/subsets/utils';
Expand Down Expand Up @@ -52,6 +58,7 @@ interface Props {
export default function SubsetCard(props: Props): ReactElement {
const { jobId } = props;
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isBulkEditDialogOpen, setIsBulkEditDialogOpen] = useState(false);

const { data, isLoading: isJobLoading } = useQuery(
JobService.method.getJob,
Expand Down Expand Up @@ -105,6 +112,8 @@ export default function SubsetCard(props: Props): ReactElement {
control: form.control,
name: 'subsets',
});
const [itemToEdit, setItemToEdit] = useState<SubsetTableRow | undefined>();
const [bulkItemEdit, setBulkItemEdit] = useState<BulkEditItem | undefined>();

const tableRowData = useMemo(() => {
return buildTableRowData(
Expand All @@ -113,7 +122,39 @@ export default function SubsetCard(props: Props): ReactElement {
formSubsets
);
}, [data?.job?.mappings, rootTables, formSubsets]);
const [itemToEdit, setItemToEdit] = useState<SubsetTableRow | undefined>();

const { onClick: onEditItemSave } = useOnEditItemSave({
item: itemToEdit,
getSubsets: () => formSubsets,
appendSubsets: addSubsetsFormValues,
triggerUpdate: () => {
form.trigger();
setIsDialogOpen(false);
setItemToEdit(undefined);
},
updateSubset: (idx, subset) => {
updateSubsetsFormValues(idx, subset);
},
});

const { onClick: onBulkEditItemSave } = useOnBulkEditItemSave({
bulkEditItem: bulkItemEdit,
getSubsets: () => formSubsets,
setSubsets: () => {
form.setValue('subsets', formSubsets, {
shouldDirty: true,
shouldTouch: true,
shouldValidate: false,
});
},
triggerUpdate: () => {
form.trigger();
setIsBulkEditDialogOpen(false);
setBulkItemEdit(undefined);
},
getTableRowData: (key) => tableRowData[key],
appendSubsets: addSubsetsFormValues,
});

const formValuesMap = new Map(
formValues.subsets.map((ss) => [buildRowKey(ss.schema, ss.table), ss])
Expand All @@ -127,6 +168,25 @@ export default function SubsetCard(props: Props): ReactElement {
);
}, [data?.job?.mappings, itemToEdit?.schema, itemToEdit?.table]);

const bulkSqlAutocompleteColumns = useMemo(() => {
if (!bulkItemEdit) {
return [];
}
return getBulkColumnsForSqlAutocomplete(
data?.job?.mappings ?? [],
bulkItemEdit.rowKeys.map((key) => {
const td = tableRowData[key];
if (!td) {
return { schema: '', table: '' };
}
return {
schema: td.schema,
table: td.table,
};
}) ?? []
);
}, [data?.job?.mappings, bulkItemEdit?.rowKeys, tableRowData]);

if (isJobLoading) {
return (
<div className="space-y-10">
Expand Down Expand Up @@ -220,6 +280,39 @@ export default function SubsetCard(props: Props): ReactElement {
}
}

function onEdit(_rowIdx: number, schema: string, table: string): void {
setIsDialogOpen(true);
const key = buildRowKey(schema, table);
if (tableRowData[key]) {
setItemToEdit({
...tableRowData[key],
});
}
}

function onBulkEdit(
data: SubsetTableRow[],
onClearSelection: () => void
): void {
// todo: if only one item is selected, just go through the single item flow
if (data.length === 0) {
return;
}
if (data.length === 1) {
onEdit(0, data[0].schema, data[0].table);
return;
}
const firstWhereClauseIdx = data.findIndex((item) => !!item.where);
setBulkItemEdit({
rowKeys: data.map((item) => buildRowKey(item.schema, item.table)),
item: {
where: firstWhereClauseIdx >= 0 ? data[firstWhereClauseIdx].where : '',
},
onClearSelection,
});
setIsBulkEditDialogOpen(true);
}

return (
<div>
<Form {...form}>
Expand All @@ -232,15 +325,8 @@ export default function SubsetCard(props: Props): ReactElement {
<SubsetTable
data={Object.values(tableRowData)}
columns={SUBSET_TABLE_COLUMNS}
onEdit={(_rowIdx, schema, table) => {
setIsDialogOpen(true);
const key = buildRowKey(schema, table);
if (tableRowData[key]) {
setItemToEdit({
...tableRowData[key],
});
}
}}
onEdit={onEdit}
onBulkEdit={onBulkEdit}
hasLocalChange={hasLocalChange}
onReset={onLocalRowReset}
/>
Expand All @@ -259,39 +345,37 @@ export default function SubsetCard(props: Props): ReactElement {
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);
}}
onSave={onEditItemSave}
connectionType={connectionType}
/>
}
/>
<EditItemDialog
open={isBulkEditDialogOpen}
onOpenChange={setIsBulkEditDialogOpen}
body={
<EditItems
item={bulkItemEdit?.item ?? { where: '' }}
onItem={(item) => {
setBulkItemEdit((prev) => {
if (!prev) {
return undefined;
}
return {
...prev,
item,
};
});
}}
onCancel={() => {
setBulkItemEdit(undefined);
setIsBulkEditDialogOpen(false);
}}
columns={bulkSqlAutocompleteColumns}
onSave={onBulkEditItemSave}
/>
}
/>
<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 @@ -288,7 +288,7 @@ const SingleSubsetFormValue = Yup.object({
table: Yup.string().trim().required('A table is required'),
whereClause: Yup.string().trim().optional(),
});
type SingleSubsetFormValue = Yup.InferType<typeof SingleSubsetFormValue>;
export type SingleSubsetFormValue = Yup.InferType<typeof SingleSubsetFormValue>;

export const SingleTableConnectFormValues = Yup.object({
fkSourceConnectionId: Yup.string().required('Connection is required').uuid(),
Expand Down
Loading

0 comments on commit 15a3d04

Please sign in to comment.