Skip to content

Commit

Permalink
馃獰 馃帀 Disable deselection of cursor field/primary key in the UI (#20844)
Browse files Browse the repository at this point in the history
  • Loading branch information
josephkmh committed Jan 10, 2023
1 parent cb99eab commit 5c020fc
Show file tree
Hide file tree
Showing 7 changed files with 535 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,17 @@ import {
} from "core/request/AirbyteClient";
import { useDestinationNamespace } from "hooks/connection/useDestinationNamespace";
import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService";
import { equal, naturalComparatorBy } from "utils/objects";
import { naturalComparatorBy } from "utils/objects";
import { ConnectionFormValues, SUPPORTED_MODES } from "views/Connection/ConnectionForm/formConfig";

import styles from "./CatalogSection.module.scss";
import { CatalogTreeTableRow } from "./next/CatalogTreeTableRow";
import { StreamDetailsPanel } from "./next/StreamDetailsPanel/StreamDetailsPanel";
import {
updatePrimaryKey,
toggleFieldInPrimaryKey,
updateCursorField,
} from "./streamConfigHelpers/streamConfigHelpers";
import { StreamFieldTable } from "./StreamFieldTable";
import { StreamHeader } from "./StreamHeader";
import { flatten, getPathType } from "./utils";
Expand All @@ -47,6 +52,8 @@ const CatalogSectionInner: React.FC<CatalogSectionInnerProps> = ({
}) => {
const isNewStreamsTableEnabled = process.env.REACT_APP_NEW_STREAMS_TABLE ?? false;

const numberOfFieldsInStream = Object.keys(streamNode?.stream?.jsonSchema?.properties).length ?? 0;

const {
destDefinitionSpecification: { supportedDestinationSyncModes },
} = useConnectionFormService();
Expand Down Expand Up @@ -75,32 +82,44 @@ const CatalogSectionInner: React.FC<CatalogSectionInnerProps> = ({

const onPkSelect = useCallback(
(pkPath: string[]) => {
let newPrimaryKey: string[][];

if (config?.primaryKey?.find((pk) => equal(pk, pkPath))) {
newPrimaryKey = config.primaryKey.filter((key) => !equal(key, pkPath));
} else {
newPrimaryKey = [...(config?.primaryKey ?? []), pkPath];
if (!config) {
return;
}

updateStreamWithConfig({ primaryKey: newPrimaryKey });
const updatedConfig = toggleFieldInPrimaryKey(config, pkPath, numberOfFieldsInStream);

updateStreamWithConfig(updatedConfig);
},
[config?.primaryKey, updateStreamWithConfig]
[config, updateStreamWithConfig, numberOfFieldsInStream]
);

const onCursorSelect = useCallback(
(cursorField: string[]) => updateStreamWithConfig({ cursorField }),
[updateStreamWithConfig]
(cursorField: string[]) => {
if (!config) {
return;
}

const updatedConfig = updateCursorField(config, cursorField, numberOfFieldsInStream);

updateStreamWithConfig(updatedConfig);
},
[config, numberOfFieldsInStream, updateStreamWithConfig]
);

const onPkUpdate = useCallback(
(newPrimaryKey: string[][]) => updateStreamWithConfig({ primaryKey: newPrimaryKey }),
[updateStreamWithConfig]
);
(newPrimaryKey: string[][]) => {
if (!config) {
return;
}

const numberOfFieldsInStream = Object.keys(streamNode?.stream?.jsonSchema?.properties).length ?? 0;
const updatedConfig = updatePrimaryKey(config, newPrimaryKey, numberOfFieldsInStream);

updateStreamWithConfig(updatedConfig);
},
[config, updateStreamWithConfig, numberOfFieldsInStream]
);

const onSelectedFieldsUpdate = (fieldPath: string[], isSelected: boolean) => {
const onToggleFieldSelected = (fieldPath: string[], isSelected: boolean) => {
const previouslySelectedFields = config?.selectedFields || [];

if (!config?.fieldSelectionEnabled && !isSelected) {
Expand Down Expand Up @@ -226,8 +245,10 @@ const CatalogSectionInner: React.FC<CatalogSectionInnerProps> = ({
syncSchemaFields={flattenedFields}
onCursorSelect={onCursorSelect}
onPkSelect={onPkSelect}
handleFieldToggle={onSelectedFieldsUpdate}
shouldDefinePk={shouldDefinePk}
handleFieldToggle={onToggleFieldSelected}
primaryKeyIndexerType={pkType}
cursorIndexerType={cursorType}
shouldDefinePrimaryKey={shouldDefinePk}
shouldDefineCursor={shouldDefineCursor}
/>
</div>
Expand Down
59 changes: 43 additions & 16 deletions airbyte-webapp/src/components/connection/CatalogTree/FieldRow.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { memo } from "react";
import React, { memo, useCallback } from "react";
import { FormattedMessage } from "react-intl";
import styled from "styled-components";

Expand All @@ -15,18 +15,20 @@ import { equal } from "utils/objects";
import { useTranslateDataType } from "utils/useTranslateDataType";

import DataTypeCell from "./DataTypeCell";
import { pathDisplayName } from "./PathPopout";
import { IndexerType, pathDisplayName } from "./PathPopout";
import { NameContainer, SyncCheckboxContainer } from "./styles";

interface FieldRowProps {
isPrimaryKeyEnabled: boolean;
isCursorEnabled: boolean;
cursorIndexerType: IndexerType;
primaryKeyIndexerType: IndexerType;
isSelected: boolean;
onPrimaryKeyChange: (pk: string[]) => void;
onCursorChange: (cs: string[]) => void;
onToggleFieldSelected: (fieldPath: string[], isSelected: boolean) => void;
field: SyncSchemaField;
config: AirbyteStreamConfiguration | undefined;
shouldDefinePrimaryKey: boolean;
shouldDefineCursor: boolean;
}

const FirstCell = styled(Cell)`
Expand All @@ -43,9 +45,11 @@ const FieldRowInner: React.FC<FieldRowProps> = ({
onToggleFieldSelected,
field,
config,
isCursorEnabled,
isPrimaryKeyEnabled,
cursorIndexerType,
primaryKeyIndexerType,
isSelected,
shouldDefinePrimaryKey,
shouldDefineCursor,
}) => {
const isColumnSelectionEnabled = useExperiment("connection.columnSelection", false);
const dataType = useTranslateDataType(field);
Expand All @@ -54,20 +58,41 @@ const FieldRowInner: React.FC<FieldRowProps> = ({
const isCursor = equal(config?.cursorField, field.path);
const isPrimaryKey = !!config?.primaryKey?.some((p) => equal(p, field.path));
const isNestedField = SyncSchemaFieldObject.isNestedField(field);
// The indexer type tells us whether a cursor or pk is user-defined, source-defined or not required (null)
const fieldSelectionDisabled =
(cursorIndexerType !== null && isCursor) || (primaryKeyIndexerType !== null && isPrimaryKey) || isNestedField;

const renderDisabledReasonMessage = useCallback(() => {
if (isNestedField) {
return <FormattedMessage id="form.field.sync.nestedFieldTooltip" values={{ fieldName: field.path[0] }} />;
}
if (primaryKeyIndexerType !== null && isPrimaryKey) {
return <FormattedMessage id="form.field.sync.primaryKeyTooltip" />;
}
if (cursorIndexerType !== null && isCursor) {
return <FormattedMessage id="form.field.sync.cursorFieldTooltip" />;
}
return null;
}, [isCursor, isPrimaryKey, isNestedField, field.path, cursorIndexerType, primaryKeyIndexerType]);

return (
<>
{isColumnSelectionEnabled && (
<Cell flex={0}>
<SyncCheckboxContainer>
{!isNestedField && (
<Switch size="sm" checked={isSelected} onChange={() => onToggleFieldSelected(field.path, !isSelected)} />
)}
{isNestedField && (
<Tooltip control={<Switch size="sm" disabled checked={isSelected} />}>
<FormattedMessage id="form.field.sync.nestedFieldTooltip" values={{ fieldName: field.path[0] }} />
</Tooltip>
)}
<Tooltip
disabled={!fieldSelectionDisabled}
control={
<Switch
size="sm"
disabled={fieldSelectionDisabled}
checked={isSelected}
onChange={() => onToggleFieldSelected(field.path, !isSelected)}
/>
}
>
{renderDisabledReasonMessage()}
</Tooltip>
</SyncCheckboxContainer>
</Cell>
)}
Expand All @@ -82,9 +107,11 @@ const FieldRowInner: React.FC<FieldRowProps> = ({
</FirstCell>
)}
<DataTypeCell>{dataType}</DataTypeCell>
<Cell>{isCursorEnabled && <RadioButton checked={isCursor} onChange={() => onCursorChange(field.path)} />}</Cell>
<Cell>
{isPrimaryKeyEnabled && <CheckBox checked={isPrimaryKey} onChange={() => onPrimaryKeyChange(field.path)} />}
{shouldDefineCursor && <RadioButton checked={isCursor} onChange={() => onCursorChange(field.path)} />}
</Cell>
<Cell>
{shouldDefinePrimaryKey && <CheckBox checked={isPrimaryKey} onChange={() => onPrimaryKeyChange(field.path)} />}
</Cell>
<LastCell ellipsis title={field.cleanedName} flex={1.5}>
{field.cleanedName}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import isEqual from "lodash/isEqual";
import React, { useCallback } from "react";

import { SyncSchemaField, SyncSchemaFieldObject } from "core/domain/catalog";
import { SyncSchemaField } from "core/domain/catalog";
import { AirbyteStreamConfiguration } from "core/request/AirbyteClient";

import { FieldHeader } from "./FieldHeader";
import { FieldRow } from "./FieldRow";
import { pathDisplayName } from "./PathPopout";
import { IndexerType, pathDisplayName } from "./PathPopout";
import styles from "./StreamFieldTable.module.scss";
import { TreeRowWrapper } from "./TreeRowWrapper";

Expand All @@ -15,19 +15,23 @@ interface StreamFieldTableProps {
onCursorSelect: (cursorPath: string[]) => void;
onPkSelect: (pkPath: string[]) => void;
handleFieldToggle: (fieldPath: string[], isSelected: boolean) => void;
shouldDefineCursor: boolean;
shouldDefinePk: boolean;
cursorIndexerType: IndexerType;
primaryKeyIndexerType: IndexerType;
syncSchemaFields: SyncSchemaField[];
shouldDefinePrimaryKey: boolean;
shouldDefineCursor: boolean;
}

export const StreamFieldTable: React.FC<StreamFieldTableProps> = ({
config,
onCursorSelect,
onPkSelect,
handleFieldToggle,
shouldDefineCursor,
shouldDefinePk,
cursorIndexerType,
primaryKeyIndexerType,
syncSchemaFields,
shouldDefinePrimaryKey,
shouldDefineCursor,
}) => {
const isFieldSelected = useCallback(
(field: SyncSchemaField): boolean => {
Expand All @@ -36,7 +40,7 @@ export const StreamFieldTable: React.FC<StreamFieldTableProps> = ({
return true;
}

// path[0] is the top-level field name for all nested fields
// Nested fields cannot currently be individually deselected, so we can just check whether the top-level field has been selected
return !!config?.selectedFields?.find((f) => isEqual(f.fieldPath, [field.path[0]]));
},
[config]
Expand All @@ -53,12 +57,14 @@ export const StreamFieldTable: React.FC<StreamFieldTableProps> = ({
<FieldRow
field={field}
config={config}
isPrimaryKeyEnabled={shouldDefinePk && SyncSchemaFieldObject.isPrimitive(field)}
isCursorEnabled={shouldDefineCursor && SyncSchemaFieldObject.isPrimitive(field)}
cursorIndexerType={cursorIndexerType}
primaryKeyIndexerType={primaryKeyIndexerType}
onPrimaryKeyChange={onPkSelect}
onCursorChange={onCursorSelect}
onToggleFieldSelected={handleFieldToggle}
isSelected={isFieldSelected(field)}
shouldDefinePrimaryKey={shouldDefinePrimaryKey}
shouldDefineCursor={shouldDefineCursor}
/>
</TreeRowWrapper>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./streamConfigHelpers";

0 comments on commit 5c020fc

Please sign in to comment.