diff --git a/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.tsx b/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.tsx index ea905a5e2c9ef..3f958ed947d21 100644 --- a/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.tsx +++ b/airbyte-webapp/src/components/CreateConnection/CreateConnectionForm.tsx @@ -18,10 +18,7 @@ import { useCurrentWorkspaceId } from "services/workspaces/WorkspacesService"; import CreateControls from "views/Connection/ConnectionForm/components/CreateControls"; import { OperationsSection } from "views/Connection/ConnectionForm/components/OperationsSection"; import { ConnectionFormFields } from "views/Connection/ConnectionForm/ConnectionFormFields"; -import { - createConnectionValidationSchema, - FormikConnectionFormValues, -} from "views/Connection/ConnectionForm/formConfig"; +import { useConnectionValidationSchema, FormikConnectionFormValues } from "views/Connection/ConnectionForm/formConfig"; import styles from "./CreateConnectionForm.module.scss"; import { CreateConnectionNameField } from "./CreateConnectionNameField"; @@ -42,24 +39,17 @@ const CreateConnectionFormInner: React.FC = ({ schem const navigate = useNavigate(); const canEditDataGeographies = useFeature(FeatureItem.AllowChangeDataGeographies); const { mutateAsync: createConnection } = useCreateConnection(); - const allowAutoDetectSchema = useFeature(FeatureItem.AllowAutoDetectSchema); const { clearFormChange } = useFormChangeTrackerService(); const workspaceId = useCurrentWorkspaceId(); const { connection, initialValues, mode, formId, getErrorMessage, setSubmitError } = useConnectionFormService(); const [editingTransformation, setEditingTransformation] = useState(false); - const allowSubOneHourCronExpressions = useFeature(FeatureItem.AllowSyncSubOneHourCronExpressions); + const validationSchema = useConnectionValidationSchema({ mode }); const onFormSubmit = useCallback( async (formValues: FormikConnectionFormValues, formikHelpers: FormikHelpers) => { - const values = tidyConnectionFormValues( - formValues, - workspaceId, - mode, - allowSubOneHourCronExpressions, - allowAutoDetectSchema - ); + const values = tidyConnectionFormValues(formValues, workspaceId, validationSchema); try { const createdConnection = await createConnection({ @@ -91,9 +81,7 @@ const CreateConnectionFormInner: React.FC = ({ schem }, [ workspaceId, - mode, - allowSubOneHourCronExpressions, - allowAutoDetectSchema, + validationSchema, createConnection, connection.source, connection.destination, @@ -113,15 +101,7 @@ const CreateConnectionFormInner: React.FC = ({ schem return ( }>
- + {({ values, isSubmitting, isValid, dirty }) => (
diff --git a/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx index 955b52e0a8826..3e57e6895d3a8 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/CatalogSection.tsx @@ -15,6 +15,7 @@ import { SelectedFieldInfo, } from "core/request/AirbyteClient"; import { useDestinationNamespace } from "hooks/connection/useDestinationNamespace"; +import { useNewTableDesignExperiment } from "hooks/connection/useNewTableDesignExperiment"; import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { naturalComparatorBy } from "utils/objects"; import { ConnectionFormValues, SUPPORTED_MODES } from "views/Connection/ConnectionForm/formConfig"; @@ -50,7 +51,7 @@ const CatalogSectionInner: React.FC = ({ errors, changedSelected, }) => { - const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; + const isNewTableDesignEnabled = useNewTableDesignExperiment(); const numberOfFieldsInStream = Object.keys(streamNode?.stream?.jsonSchema?.properties).length ?? 0; @@ -187,9 +188,7 @@ const CatalogSectionInner: React.FC = ({ const destName = prefix + (streamNode.stream?.name ?? ""); const configErrors = getIn( errors, - isNewStreamsTableEnabled - ? `syncCatalog.streams[${streamNode.id}].config` - : `schema.streams[${streamNode.id}].config` + isNewTableDesignEnabled ? `syncCatalog.streams[${streamNode.id}].config` : `schema.streams[${streamNode.id}].config` ); const hasError = configErrors && Object.keys(configErrors).length > 0; const pkType = getPathType(pkRequired, shouldDefinePk); @@ -197,7 +196,7 @@ const CatalogSectionInner: React.FC = ({ const hasFields = fields?.length > 0; const disabled = mode === "readonly"; - const StreamComponent = isNewStreamsTableEnabled ? CatalogTreeTableRow : StreamHeader; + const StreamComponent = isNewTableDesignEnabled ? CatalogTreeTableRow : StreamHeader; return (
@@ -223,7 +222,7 @@ const CatalogSectionInner: React.FC = ({ /> {isRowExpanded && hasFields && - (isNewStreamsTableEnabled ? ( + (isNewTableDesignEnabled ? ( > onStreamsChanged, isLoading, }) => { - const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; + const isNewTableDesignEnabled = useNewTableDesignExperiment(); const { initialValues, mode } = useConnectionFormService(); const [searchString, setSearchString] = useState(""); @@ -72,9 +73,7 @@ const CatalogTreeComponent: React.FC> {mode !== "readonly" && } -
+
> />
- {isNewStreamsTableEnabled && } + {isNewTableDesignEnabled && } ); }; diff --git a/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeBody.tsx b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeBody.tsx index 4746cd3c5c05c..95996bb2c6c39 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeBody.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/CatalogTreeBody.tsx @@ -3,6 +3,7 @@ import React, { useCallback } from "react"; import { SyncSchemaStream } from "core/domain/catalog"; import { AirbyteStreamConfiguration } from "core/request/AirbyteClient"; +import { useNewTableDesignExperiment } from "hooks/connection/useNewTableDesignExperiment"; import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { FormikConnectionFormValues } from "views/Connection/ConnectionForm/formConfig"; @@ -20,10 +21,9 @@ interface CatalogTreeBodyProps { onStreamChanged: (stream: SyncSchemaStream) => void; } -const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; - export const CatalogTreeBody: React.FC = ({ streams, changedStreams, onStreamChanged }) => { const { mode } = useConnectionFormService(); + const isNewTableDesignEnabled = useNewTableDesignExperiment(); const onUpdateStream = useCallback( (id: string | undefined, newConfig: Partial) => { @@ -40,7 +40,7 @@ export const CatalogTreeBody: React.FC = ({ streams, chang return (
- {isNewStreamsTableEnabled ? ( + {isNewTableDesignEnabled ? ( <> diff --git a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableHeader.tsx b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableHeader.tsx index 086f8f0259798..ee01a8e7b54cf 100644 --- a/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableHeader.tsx +++ b/airbyte-webapp/src/components/connection/CatalogTree/next/CatalogTreeTableHeader.tsx @@ -12,6 +12,7 @@ import { Text } from "components/ui/Text"; import { InfoTooltip, TooltipLearnMoreLink } from "components/ui/Tooltip"; import { NamespaceDefinitionType } from "core/request/AirbyteClient"; +import { useNewTableDesignExperiment } from "hooks/connection/useNewTableDesignExperiment"; import { useBulkEditService } from "hooks/services/BulkEdit/BulkEditService"; import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { useModalService } from "hooks/services/Modal"; @@ -43,13 +44,12 @@ const HeaderCell: React.FC { const { mode } = useConnectionFormService(); const { openModal, closeModal } = useModalService(); const { onCheckAll, selectedBatchNodeIds, allChecked } = useBulkEditService(); const formikProps = useFormikContext(); + const isNewTableDesignEnabled = useNewTableDesignExperiment(); const destinationNamespaceChange = (value: DestinationNamespaceFormValueType) => { formikProps.setFieldValue("namespaceDefinition", value.namespaceDefinition); @@ -67,7 +67,7 @@ export const CatalogTreeTableHeader: React.FC = () => { }; return ( -
+
{mode !== "readonly" && (
{getIcon(icon)}
; -const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; export const StreamConnectionHeader: React.FC = () => { const { @@ -20,11 +20,12 @@ export const StreamConnectionHeader: React.FC = () => { } = useConnectionFormService(); const sourceDefinition = useSourceDefinition(source.sourceDefinitionId); const destinationDefinition = useDestinationDefinition(destination.destinationDefinitionId); + const isNewTableDesignEnabled = useNewTableDesignExperiment(); const sourceStyles = classnames(styles.connector, styles.source); const destinationStyles = classnames(styles.connector, styles.destination); return ( -
+
{renderIcon(sourceDefinition.icon)}{" "} diff --git a/airbyte-webapp/src/hooks/connection/useNewTableDesignExperiment.ts b/airbyte-webapp/src/hooks/connection/useNewTableDesignExperiment.ts new file mode 100644 index 0000000000000..350c148107cb3 --- /dev/null +++ b/airbyte-webapp/src/hooks/connection/useNewTableDesignExperiment.ts @@ -0,0 +1,4 @@ +import { useExperiment } from "hooks/services/Experiment"; + +export const useNewTableDesignExperiment = () => + useExperiment("connection.newTableDesign", process.env.REACT_APP_NEW_STREAMS_TABLE === "true"); diff --git a/airbyte-webapp/src/hooks/services/ConnectionForm/ConnectionFormService.tsx b/airbyte-webapp/src/hooks/services/ConnectionForm/ConnectionFormService.tsx index f5d030f462152..03e9c25cfa460 100644 --- a/airbyte-webapp/src/hooks/services/ConnectionForm/ConnectionFormService.tsx +++ b/airbyte-webapp/src/hooks/services/ConnectionForm/ConnectionFormService.tsx @@ -8,15 +8,16 @@ import { OperationRead, WebBackendConnectionRead, } from "core/request/AirbyteClient"; +import { useNewTableDesignExperiment } from "hooks/connection/useNewTableDesignExperiment"; import { useDestinationDefinition } from "services/connector/DestinationDefinitionService"; import { useGetDestinationDefinitionSpecification } from "services/connector/DestinationDefinitionSpecificationService"; import { FormError, generateMessageFromError } from "utils/errorStatusMessage"; import { ConnectionFormValues, - createConnectionValidationSchema, FormikConnectionFormValues, mapFormPropsToOperation, useInitialValues, + ConnectionValidationSchema, } from "views/Connection/ConnectionForm/formConfig"; import { useUniqueFormId } from "../FormChangeTracker"; @@ -39,17 +40,11 @@ interface ConnectionServiceProps { export const tidyConnectionFormValues = ( values: FormikConnectionFormValues, workspaceId: string, - mode: ConnectionFormMode, - allowSubOneHourCronExpressions: boolean, - allowAutoDetectSchema: boolean, + validationSchema: ConnectionValidationSchema, operations?: OperationRead[] ): ValuesProps => { // TODO (https://github.com/airbytehq/airbyte/issues/17279): We should try to fix the types so we don't need the casting. - const formValues: ConnectionFormValues = createConnectionValidationSchema({ - mode, - allowSubOneHourCronExpressions, - allowAutoDetectSchema, - }).cast(values, { + const formValues: ConnectionFormValues = validationSchema.cast(values, { context: { isRequest: true }, }) as unknown as ConnectionFormValues; @@ -89,12 +84,11 @@ const useConnectionForm = ({ const { formatMessage } = useIntl(); const [submitError, setSubmitError] = useState(null); const formId = useUniqueFormId(); + const isNewTableDesignEnabled = useNewTableDesignExperiment(); const getErrorMessage = useCallback( (formValid: boolean, connectionDirty: boolean) => { - const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; - - if (isNewStreamsTableEnabled) { + if (isNewTableDesignEnabled) { // There is a case when some fields could be dropped in the database. We need to validate the form without property dirty return submitError ? generateMessageFromError(submitError) @@ -108,7 +102,7 @@ const useConnectionForm = ({ ? formatMessage({ id: "connectionForm.validation.error" }) : null; }, - [formatMessage, submitError] + [formatMessage, isNewTableDesignEnabled, submitError] ); return { diff --git a/airbyte-webapp/src/hooks/services/Experiment/experiments.ts b/airbyte-webapp/src/hooks/services/Experiment/experiments.ts index 0ff0aa80a7d28..8fdc7c54e01d1 100644 --- a/airbyte-webapp/src/hooks/services/Experiment/experiments.ts +++ b/airbyte-webapp/src/hooks/services/Experiment/experiments.ts @@ -27,4 +27,5 @@ export interface Experiments { "connection.onboarding.destinations": string; "connection.autoDetectSchemaChanges": boolean; "connection.columnSelection": boolean; + "connection.newTableDesign": boolean; } diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx index 54b3c4a803dfa..0541bfa5372b3 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionReplicationTab.tsx @@ -16,7 +16,6 @@ import { tidyConnectionFormValues, useConnectionFormService, } from "hooks/services/ConnectionForm/ConnectionFormService"; -import { FeatureItem, useFeature } from "hooks/services/Feature"; import { useModalService } from "hooks/services/Modal"; import { useConnectionService, ValuesProps } from "hooks/services/useConnectionHook"; import { useCurrentWorkspaceId } from "services/workspaces/WorkspacesService"; @@ -24,10 +23,7 @@ import { equal } from "utils/objects"; import EditControls from "views/Connection/ConnectionForm/components/EditControls"; import { SchemaChangeBackdrop } from "views/Connection/ConnectionForm/components/SchemaChangeBackdrop"; import { ConnectionFormFields } from "views/Connection/ConnectionForm/ConnectionFormFields"; -import { - createConnectionValidationSchema, - FormikConnectionFormValues, -} from "views/Connection/ConnectionForm/formConfig"; +import { useConnectionValidationSchema, FormikConnectionFormValues } from "views/Connection/ConnectionForm/formConfig"; import styles from "./ConnectionReplicationTab.module.scss"; import { ResetWarningModal } from "./ResetWarningModal"; @@ -46,7 +42,6 @@ const ValidateFormOnSchemaRefresh: React.FC = () => { }; export const ConnectionReplicationTab: React.FC = () => { - const allowAutoDetectSchema = useFeature(FeatureItem.AllowAutoDetectSchema); const analyticsService = useAnalyticsService(); const connectionService = useConnectionService(); const workspaceId = useCurrentWorkspaceId(); @@ -59,7 +54,7 @@ export const ConnectionReplicationTab: React.FC = () => { const { connection, schemaRefreshing, schemaHasBeenRefreshed, updateConnection, discardRefreshedSchema } = useConnectionEditService(); const { initialValues, mode, schemaError, getErrorMessage, setSubmitError } = useConnectionFormService(); - const allowSubOneHourCronExpressions = useFeature(FeatureItem.AllowSyncSubOneHourCronExpressions); + const validationSchema = useConnectionValidationSchema({ mode }); useTrackPage(PageTrackingCodes.CONNECTIONS_ITEM_REPLICATION); @@ -94,14 +89,7 @@ export const ConnectionReplicationTab: React.FC = () => { const onFormSubmit = useCallback( async (values: FormikConnectionFormValues, _: FormikHelpers) => { - const formValues = tidyConnectionFormValues( - values, - workspaceId, - mode, - allowSubOneHourCronExpressions, - allowAutoDetectSchema, - connection.operations - ); + const formValues = tidyConnectionFormValues(values, workspaceId, validationSchema, connection.operations); // Check if the user refreshed the catalog and there was any change in a currently enabled stream const hasDiffInEnabledStream = connection.catalogDiff?.transforms.some(({ streamDescriptor }) => { @@ -156,9 +144,7 @@ export const ConnectionReplicationTab: React.FC = () => { }, [ workspaceId, - mode, - allowSubOneHourCronExpressions, - allowAutoDetectSchema, + validationSchema, connection.operations, connection.catalogDiff?.transforms, connection.syncCatalog.streams, @@ -185,11 +171,7 @@ export const ConnectionReplicationTab: React.FC = () => { diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx index edd8f05d5812a..4f1e923127dab 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/ConnectionFormFields.tsx @@ -13,6 +13,7 @@ import { FlexContainer } from "components/ui/Flex"; import { Input } from "components/ui/Input"; import { NamespaceDefinitionType } from "core/request/AirbyteClient"; +import { useNewTableDesignExperiment } from "hooks/connection/useNewTableDesignExperiment"; import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService"; import { FeatureItem, useFeature } from "hooks/services/Feature"; import { useFormChangeTrackerService } from "hooks/services/FormChangeTracker"; @@ -50,8 +51,8 @@ export const ConnectionFormFields: React.FC = ({ valu clearFormChange(formId); }); - const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; - const firstSectionTitle = isNewStreamsTableEnabled ? undefined : ; + const isNewTableDesignEnabled = useNewTableDesignExperiment(); + const firstSectionTitle = isNewTableDesignEnabled ? undefined : ; return ( <> @@ -64,7 +65,7 @@ export const ConnectionFormFields: React.FC = ({ valu )} - {!isNewStreamsTableEnabled && ( + {!isNewTableDesignEnabled && (
}> diff --git a/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx b/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx index 33f8dc0d2efea..9606b700a6ee7 100644 --- a/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx +++ b/airbyte-webapp/src/views/Connection/ConnectionForm/formConfig.tsx @@ -28,7 +28,9 @@ import { SyncMode, WebBackendConnectionRead, } from "core/request/AirbyteClient"; +import { useNewTableDesignExperiment } from "hooks/connection/useNewTableDesignExperiment"; import { ConnectionFormMode, ConnectionOrPartialConnection } from "hooks/services/ConnectionForm/ConnectionFormService"; +import { FeatureItem, useFeature } from "hooks/services/Feature"; import { ValuesProps } from "hooks/services/useConnectionHook"; import { useCurrentWorkspace } from "services/workspaces/WorkspacesService"; import { validateCronExpression, validateCronFrequencyOneHourOrMore } from "utils/cron"; @@ -81,19 +83,12 @@ export function useDefaultTransformation(): OperationCreate { }; } -interface CreateConnectionValidationSchemaArgs { - allowSubOneHourCronExpressions: boolean; - mode: ConnectionFormMode; - allowAutoDetectSchema: boolean; -} - -export const createConnectionValidationSchema = ({ - mode, - allowSubOneHourCronExpressions, - allowAutoDetectSchema, -}: CreateConnectionValidationSchemaArgs) => { - const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false; - +const createConnectionValidationSchema = ( + mode: ConnectionFormMode, + allowSubOneHourCronExpressions: boolean, + allowAutoDetectSchema: boolean, + isNewTableDesignEnabled: boolean +) => { return yup .object({ // The connection name during Editing is handled separately from the form @@ -149,7 +144,7 @@ export const createConnectionValidationSchema = ({ then: yup.string().trim().required("form.empty.error"), }), prefix: yup.string(), - syncCatalog: isNewStreamsTableEnabled + syncCatalog: isNewTableDesignEnabled ? yup.object({ streams: yup.array().of( yup.object({ @@ -249,6 +244,29 @@ export const createConnectionValidationSchema = ({ .noUnknown(); }; +interface CreateConnectionValidationSchemaArgs { + mode: ConnectionFormMode; +} + +export const useConnectionValidationSchema = ({ mode }: CreateConnectionValidationSchemaArgs) => { + const allowSubOneHourCronExpressions = useFeature(FeatureItem.AllowSyncSubOneHourCronExpressions); + const allowAutoDetectSchema = useFeature(FeatureItem.AllowAutoDetectSchema); + const isNewTableDesignEnabled = useNewTableDesignExperiment(); + + return useMemo( + () => + createConnectionValidationSchema( + mode, + allowSubOneHourCronExpressions, + allowAutoDetectSchema, + isNewTableDesignEnabled + ), + [allowAutoDetectSchema, allowSubOneHourCronExpressions, isNewTableDesignEnabled, mode] + ); +}; + +export type ConnectionValidationSchema = ReturnType; + /** * Returns {@link Operation}[] *