From 353a64ce188b546ccbdc7c9b9c3a658e154875d9 Mon Sep 17 00:00:00 2001 From: Mark Berger Date: Wed, 21 Dec 2022 01:47:05 +0200 Subject: [PATCH 1/7] Highlight invalid cursor or primary key in new connection streams table - Added error handling for Cursor and Primary Key --- .../connection/CatalogTree/CatalogSection.tsx | 1 + .../connection/CatalogTree/StreamHeader.tsx | 1 + .../CatalogTree/next/CatalogTreeTableRow.module.scss | 4 ---- .../CatalogTree/next/CatalogTreeTableRow.tsx | 4 +++- .../connection/CatalogTree/next/StreamPathSelect.tsx | 2 ++ .../src/components/ui/PillSelect/PillButton.tsx | 12 ++++++++++-- .../src/components/ui/PillSelect/PillSelect.tsx | 2 ++ 7 files changed, 19 insertions(+), 7 deletions(-) diff --git a/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx index fd49a7b6f82a..791720aecb33 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx @@ -194,6 +194,7 @@ const CatalogSectionInner: React.FC = ({ onExpand={onExpand} changedSelected={changedSelected} hasError={hasError} + configErrors={configErrors} disabled={disabled} /> {isRowExpanded && diff --git a/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx index 9a1690c118b2..c30497c7f087 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx @@ -43,6 +43,7 @@ export interface StreamHeaderProps { onExpand: () => void; changedSelected: boolean; hasError: boolean; + configErrors?: Record; disabled?: boolean; } diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.module.scss b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.module.scss index a7eacc813342..ab30731b2e7e 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.module.scss +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.module.scss @@ -35,10 +35,6 @@ @include header-background(colors.$blue-30, colors.$blue-40); } - &.error { - border: 1px solid colors.$red; - } - &.disabled { background-color: colors.$grey-50; } diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx index 7da5ec35ec24..cf1f0a2933b0 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx @@ -35,9 +35,9 @@ export const CatalogTreeTableRow: React.FC = ({ fields, onExpand, disabled, + configErrors, }) => { const { primaryKey, cursorField, syncMode, destinationSyncMode } = stream.config ?? {}; - const { defaultCursorField } = stream.stream ?? {}; const syncSchema = useMemo( () => ({ @@ -104,6 +104,7 @@ export const CatalogTreeTableRow: React.FC = ({ path={cursorType === "sourceDefined" ? defaultCursorField : cursorField} onPathChange={onCursorChange} variant={pillButtonVariant} + hasError={!!configErrors?.cursorField} /> )} @@ -116,6 +117,7 @@ export const CatalogTreeTableRow: React.FC = ({ isMulti onPathChange={onPrimaryKeyChange} variant={pillButtonVariant} + hasError={!!configErrors?.primaryKey} /> )} diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPathSelect.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPathSelect.tsx index c4e7df8e7768..c89e68cf6458 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPathSelect.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/StreamPathSelect.tsx @@ -19,6 +19,7 @@ interface StreamPathSelectBaseProps { placeholder?: React.ReactNode; variant?: PillButtonVariant; disabled?: boolean; + hasError?: boolean; } interface StreamPathSelectMultiProps { @@ -69,6 +70,7 @@ export const StreamPathSelect: React.FC = (props) => { props.onPathChange(finalValues); }} className={styles.streamPathSelect} + hasError={props?.hasError} /> ); }; diff --git a/airbyte-webapp/src/components/ui/PillSelect/PillButton.tsx b/airbyte-webapp/src/components/ui/PillSelect/PillButton.tsx index 39c52db1e182..81a8c6c1af25 100644 --- a/airbyte-webapp/src/components/ui/PillSelect/PillButton.tsx +++ b/airbyte-webapp/src/components/ui/PillSelect/PillButton.tsx @@ -20,19 +20,27 @@ const STYLES_BY_VARIANT: Readonly> = { interface PillButtonProps extends React.ButtonHTMLAttributes { active?: boolean; variant?: PillButtonVariant; + hasError?: boolean; } -export const PillButton: React.FC = ({ children, active, variant = "grey", ...buttonProps }) => { +export const PillButton: React.FC = ({ + children, + active, + variant = "grey", + hasError = false, + ...buttonProps +}) => { const buttonClassName = classNames( styles.button, { [styles.active]: active, [styles.disabled]: buttonProps.disabled, }, - STYLES_BY_VARIANT[variant], + STYLES_BY_VARIANT[hasError ? "strong-red" : variant], buttonProps.className ); const arrayChildren = Children.toArray(children); + return ( - + +
+ Save changes +
+ + From 0ea9457d04ae2bdaf343bd9bb30d894f5a5356b7 Mon Sep 17 00:00:00 2001 From: Mark Berger Date: Tue, 3 Jan 2023 18:34:18 +0200 Subject: [PATCH 4/7] Highlight invalid cursor or primary key in new connection streams table - Added individual error handling for Cursor and Primary Key - Added feature variable to encapsulate new changes with new streams table --- .../components/connection/CatalogTree/CatalogSection.tsx | 8 +++++++- .../components/connection/CatalogTree/StreamHeader.tsx | 1 + .../connection/CatalogTree/next/CatalogTreeTableRow.tsx | 3 ++- .../CatalogTree/next/useCatalogTreeTableRowProps.test.tsx | 7 ------- .../CatalogTree/next/useCatalogTreeTableRowProps.tsx | 5 ----- 5 files changed, 10 insertions(+), 14 deletions(-) diff --git a/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx index fd49a7b6f82a..7d75f818ea61 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx @@ -166,7 +166,12 @@ const CatalogSectionInner: React.FC = ({ ); const destName = prefix + (streamNode.stream?.name ?? ""); - const configErrors = getIn(errors, `schema.streams[${streamNode.id}].config`); + const configErrors = getIn( + errors, + isNewStreamsTableEnabled + ? `syncCatalog.streams[${streamNode.id}].config` + : `schema.streams[${streamNode.id}].config` + ); const hasError = configErrors && Object.keys(configErrors).length > 0; const pkType = getPathType(pkRequired, shouldDefinePk); const cursorType = getPathType(cursorRequired, shouldDefineCursor); @@ -194,6 +199,7 @@ const CatalogSectionInner: React.FC = ({ onExpand={onExpand} changedSelected={changedSelected} hasError={hasError} + configErrors={configErrors} disabled={disabled} /> {isRowExpanded && diff --git a/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx index 9a1690c118b2..c30497c7f087 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/StreamHeader.tsx @@ -43,6 +43,7 @@ export interface StreamHeaderProps { onExpand: () => void; changedSelected: boolean; hasError: boolean; + configErrors?: Record; disabled?: boolean; } diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx index 8f86a94e939e..cf1f0a2933b0 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableRow.tsx @@ -35,6 +35,7 @@ export const CatalogTreeTableRow: React.FC = ({ fields, onExpand, disabled, + configErrors, }) => { const { primaryKey, cursorField, syncMode, destinationSyncMode } = stream.config ?? {}; const { defaultCursorField } = stream.stream ?? {}; @@ -52,7 +53,7 @@ export const CatalogTreeTableRow: React.FC = ({ const fieldCount = fields?.length ?? 0; const onRowClick = fieldCount > 0 ? () => onExpand() : undefined; - const { streamHeaderContentStyle, pillButtonVariant, configErrors } = useCatalogTreeTableRowProps(stream); + const { streamHeaderContentStyle, pillButtonVariant } = useCatalogTreeTableRowProps(stream); const checkboxCellCustomStyle = classnames(styles.checkboxCell, styles.streamRowCheckboxCell); diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/useCatalogTreeTableRowProps.test.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/useCatalogTreeTableRowProps.test.tsx index e1a5c4710ac4..191eefe32402 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/next/useCatalogTreeTableRowProps.test.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/useCatalogTreeTableRowProps.test.tsx @@ -143,11 +143,4 @@ describe("useCatalogTreeTableRowProps", () => { expect(result.current.streamHeaderContentStyle).toEqual("streamHeaderContent changed"); expect(result.current.pillButtonVariant).toEqual("blue"); }); - it("should return error styles for a row that has an error", () => { - testSetup(mockInitialValues, false, "error"); - - const { result } = renderHook(() => useCatalogTreeTableRowProps(mockStream)); - - expect(result.current.streamHeaderContentStyle).toEqual("streamHeaderContent error"); - }); }); diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/useCatalogTreeTableRowProps.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/useCatalogTreeTableRowProps.tsx index 8d9b9665e68e..4f9e07c7a688 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/next/useCatalogTreeTableRowProps.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/useCatalogTreeTableRowProps.tsx @@ -1,6 +1,5 @@ /* eslint-disable css-modules/no-unused-class */ import classNames from "classnames"; -import { getIn, useFormikContext } from "formik"; import isEqual from "lodash/isEqual"; import { useMemo } from "react"; @@ -17,9 +16,6 @@ type StatusToDisplay = "disabled" | "added" | "removed" | "changed" | "unchanged export const useCatalogTreeTableRowProps = (stream: SyncSchemaStream) => { const [isSelected] = useBulkEditSelect(stream.id); const { initialValues } = useConnectionFormService(); - const { errors } = useFormikContext(); - - const configErrors = getIn(errors, `syncCatalog.streams[${stream.id}].config`); const isStreamEnabled = stream.config?.selected; @@ -73,6 +69,5 @@ export const useCatalogTreeTableRowProps = (stream: SyncSchemaStream) => { isSelected, statusToDisplay, pillButtonVariant, - configErrors, }; }; From 0276f7c0825b2ae53c34c9638b009809242350e5 Mon Sep 17 00:00:00 2001 From: Mark Berger Date: Wed, 4 Jan 2023 01:41:51 +0200 Subject: [PATCH 5/7] Highlight invalid cursor or primary key in new connection streams table - Added individual error handling for Cursor and Primary Key - Added feature variable to encapsulate new changes with new streams table --- .../ConnectionReplicationTab.tsx | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx index 75de41ca6543..54b3c4a803df 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx @@ -203,20 +203,18 @@ export const ConnectionReplicationTab: React.FC = () => { dirty={dirty || schemaHasBeenRefreshed} /> {status.editControlsVisible && ( -
- { - resetForm(); - discardRefreshedSchema(); - }} - successMessage={saved && !dirty && } - errorMessage={getErrorMessage(isValid, dirty)} - enableControls={schemaHasBeenRefreshed || dirty} - /> -
+ { + resetForm(); + discardRefreshedSchema(); + }} + successMessage={saved && !dirty && } + errorMessage={getErrorMessage(isValid, dirty)} + enableControls={schemaHasBeenRefreshed || dirty} + /> )} From 5d49992e0c19650f486aabe1f3c9018707f7045a Mon Sep 17 00:00:00 2001 From: Mark Berger Date: Wed, 4 Jan 2023 13:44:25 +0200 Subject: [PATCH 6/7] Highlight invalid cursor or primary key in new connection streams table - Added individual error handling for Cursor and Primary Key - Added feature variable to encapsulate new changes with new streams table --- .../ConnectionReplicationTab.test.tsx.snap | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap index 1dd5d2ba1803..ef733dd7f281 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/__snapshots__/ConnectionReplicationTab.test.tsx.snap @@ -760,36 +760,34 @@ exports[`ConnectionReplicationTab should render 1`] = ` -
+
-
- - + -
+ Save changes +
+
From fdf169f8fa4ecba20483b207beb0e2bbb2c996d7 Mon Sep 17 00:00:00 2001 From: Mark Berger Date: Wed, 4 Jan 2023 15:52:35 +0200 Subject: [PATCH 7/7] Highlight invalid cursor or primary key in new connection streams table - Added individual error handling for Cursor and Primary Key - Added feature variable to encapsulate new changes with new streams table --- .../Connection/ConnectionForm/formConfig.tsx | 249 +++++++----------- 1 file changed, 94 insertions(+), 155 deletions(-) diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx index 9a0422fa678b..8bc5d5d9db0c 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx @@ -93,105 +93,6 @@ export const createConnectionValidationSchema = ({ }: CreateConnectionValidationSchemaArgs) => { const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; - if (isNewStreamsTableEnabled) { - return yup - .object({ - // The connection name during Editing is handled separately from the form - name: mode === "create" ? yup.string().required("form.empty.error") : yup.string().notRequired(), - geography: yup.mixed().oneOf(Object.values(Geography)), - scheduleType: yup - .string() - .oneOf([ConnectionScheduleType.manual, ConnectionScheduleType.basic, ConnectionScheduleType.cron]), - scheduleData: yup.mixed().when("scheduleType", (scheduleType) => { - if (scheduleType === ConnectionScheduleType.basic) { - return yup.object({ - basicSchedule: yup - .object({ - units: yup.number().required("form.empty.error"), - timeUnit: yup.string().required("form.empty.error"), - }) - .defined("form.empty.error"), - }); - } else if (scheduleType === ConnectionScheduleType.manual) { - return yup.mixed().notRequired(); - } - return yup.object({ - cron: yup - .object({ - cronExpression: yup - .string() - .trim() - .required("form.empty.error") - .test("validCron", "form.cronExpression.error", validateCronExpression) - .test( - "validCronFrequency", - "form.cronExpression.underOneHourNotAllowed", - (expression) => allowSubOneHourCronExpressions || validateCronFrequencyOneHourOrMore(expression) - ), - cronTimeZone: yup.string().required("form.empty.error"), - }) - .defined("form.empty.error"), - }); - }), - nonBreakingChangesPreference: allowAutoDetectSchema - ? yup.mixed().oneOf(Object.values(NonBreakingChangesPreference)).required("form.empty.error") - : yup.mixed().notRequired(), - namespaceDefinition: yup - .string() - .oneOf([ - NamespaceDefinitionType.source, - NamespaceDefinitionType.destination, - NamespaceDefinitionType.customformat, - ]) - .required("form.empty.error"), - namespaceFormat: yup.string().when("namespaceDefinition", { - is: NamespaceDefinitionType.customformat, - then: yup.string().trim().required("form.empty.error"), - }), - prefix: yup.string(), - syncCatalog: yup.object({ - streams: yup.array().of( - yup.object({ - id: yup - .string() - // This is required to get rid of id fields we are using to detect stream for edition - .when("$isRequest", (isRequest: boolean, schema: yup.StringSchema) => - isRequest ? schema.strip(true) : schema - ), - stream: yup.object(), - config: yup.object({ - selected: yup.boolean(), - syncMode: yup.string(), - destinationSyncMode: yup.string(), - primaryKey: yup - .array() - .of(yup.array().of(yup.string())) - .when(["syncMode", "destinationSyncMode", "selected"], { - is: (syncMode: SyncMode, destinationSyncMode: DestinationSyncMode, selected: boolean) => - syncMode === SyncMode.incremental && - destinationSyncMode === DestinationSyncMode.append_dedup && - selected, - then: yup.array().of(yup.array().of(yup.string())).min(1, "form.empty.error"), - }), - cursorField: yup - .array() - .of(yup.string()) - .when(["syncMode", "destinationSyncMode", "selected"], { - is: (syncMode: SyncMode, destinationSyncMode: DestinationSyncMode, selected: boolean) => - (destinationSyncMode === DestinationSyncMode.append || - destinationSyncMode === DestinationSyncMode.append_dedup) && - syncMode === SyncMode.incremental && - selected, - then: yup.array().of(yup.string()).min(1, "form.empty.error"), - }), - }), - }) - ), - }), - }) - .noUnknown(); - } - return yup .object({ // The connection name during Editing is handled separately from the form @@ -213,7 +114,6 @@ export const createConnectionValidationSchema = ({ } else if (scheduleType === ConnectionScheduleType.manual) { return yup.mixed().notRequired(); } - return yup.object({ cron: yup .object({ @@ -235,7 +135,6 @@ export const createConnectionValidationSchema = ({ nonBreakingChangesPreference: allowAutoDetectSchema ? yup.mixed().oneOf(Object.values(NonBreakingChangesPreference)).required("form.empty.error") : yup.mixed().notRequired(), - namespaceDefinition: yup .string() .oneOf([ @@ -249,62 +148,102 @@ export const createConnectionValidationSchema = ({ then: yup.string().trim().required("form.empty.error"), }), prefix: yup.string(), - syncCatalog: yup.object({ - streams: yup.array().of( - yup.object({ - id: yup - .string() - // This is required to get rid of id fields we are using to detect stream for edition - .when("$isRequest", (isRequest: boolean, schema: yup.StringSchema) => - isRequest ? schema.strip(true) : schema - ), - stream: yup.object(), - config: yup - .object({ - selected: yup.boolean(), - syncMode: yup.string(), - destinationSyncMode: yup.string(), - primaryKey: yup.array().of(yup.array().of(yup.string())), - cursorField: yup.array().of(yup.string()).defined(), + syncCatalog: isNewStreamsTableEnabled + ? yup.object({ + streams: yup.array().of( + yup.object({ + id: yup + .string() + // This is required to get rid of id fields we are using to detect stream for edition + .when("$isRequest", (isRequest: boolean, schema: yup.StringSchema) => + isRequest ? schema.strip(true) : schema + ), + stream: yup.object(), + config: yup.object({ + selected: yup.boolean(), + syncMode: yup.string(), + destinationSyncMode: yup.string(), + primaryKey: yup + .array() + .of(yup.array().of(yup.string())) + .when(["syncMode", "destinationSyncMode", "selected"], { + is: (syncMode: SyncMode, destinationSyncMode: DestinationSyncMode, selected: boolean) => + syncMode === SyncMode.incremental && + destinationSyncMode === DestinationSyncMode.append_dedup && + selected, + then: yup.array().of(yup.array().of(yup.string())).min(1, "form.empty.error"), + }), + cursorField: yup + .array() + .of(yup.string()) + .when(["syncMode", "destinationSyncMode", "selected"], { + is: (syncMode: SyncMode, destinationSyncMode: DestinationSyncMode, selected: boolean) => + (destinationSyncMode === DestinationSyncMode.append || + destinationSyncMode === DestinationSyncMode.append_dedup) && + syncMode === SyncMode.incremental && + selected, + then: yup.array().of(yup.string()).min(1, "form.empty.error"), + }), + }), }) - .test({ - name: "connectionSchema.config.validator", - // eslint-disable-next-line no-template-curly-in-string - message: "${path} is wrong", - test(value) { - if (!value.selected) { - return true; - } - if (DestinationSyncMode.append_dedup === value.destinationSyncMode) { - // it's possible that primaryKey array is always present - // however yup couldn't determine type correctly even with .required() call - if (value.primaryKey?.length === 0) { - return this.createError({ - message: "connectionForm.primaryKey.required", - path: `schema.streams[${this.parent.id}].config.primaryKey`, - }); - } - } - - if (SyncMode.incremental === value.syncMode) { - if ( - !this.parent.stream.sourceDefinedCursor && - // it's possible that cursorField array is always present - // however yup couldn't determine type correctly even with .required() call - value.cursorField?.length === 0 - ) { - return this.createError({ - message: "connectionForm.cursorField.required", - path: `schema.streams[${this.parent.id}].config.cursorField`, - }); - } - } - return true; - }, - }), + ), }) - ), - }), + : yup.object({ + streams: yup.array().of( + yup.object({ + id: yup + .string() + // This is required to get rid of id fields we are using to detect stream for edition + .when("$isRequest", (isRequest: boolean, schema: yup.StringSchema) => + isRequest ? schema.strip(true) : schema + ), + stream: yup.object(), + config: yup + .object({ + selected: yup.boolean(), + syncMode: yup.string(), + destinationSyncMode: yup.string(), + primaryKey: yup.array().of(yup.array().of(yup.string())), + cursorField: yup.array().of(yup.string()).defined(), + }) + .test({ + name: "connectionSchema.config.validator", + // eslint-disable-next-line no-template-curly-in-string + message: "${path} is wrong", + test(value) { + if (!value.selected) { + return true; + } + if (DestinationSyncMode.append_dedup === value.destinationSyncMode) { + // it's possible that primaryKey array is always present + // however yup couldn't determine type correctly even with .required() call + if (value.primaryKey?.length === 0) { + return this.createError({ + message: "connectionForm.primaryKey.required", + path: `schema.streams[${this.parent.id}].config.primaryKey`, + }); + } + } + + if (SyncMode.incremental === value.syncMode) { + if ( + !this.parent.stream.sourceDefinedCursor && + // it's possible that cursorField array is always present + // however yup couldn't determine type correctly even with .required() call + value.cursorField?.length === 0 + ) { + return this.createError({ + message: "connectionForm.cursorField.required", + path: `schema.streams[${this.parent.id}].config.cursorField`, + }); + } + } + return true; + }, + }), + }) + ), + }), }) .noUnknown(); };