Skip to content

Commit

Permalink
🪟 🧹 Remove connection and mode prop drilling on connection view/edit …
Browse files Browse the repository at this point in the history
…pages (airbytehq#17808)

* normalization field to scss and remove mode

* remove instances of mode and connection being passed

* connectionsettingstab tests pass

* fix validation on blur/validation on change

* redo broken snapshot

* add validateOnBlur back

* cleanup, migrate transformation tab to scss modules

* update snapshot after rebase

* update snapshot
  • Loading branch information
teallarson authored and jhammarstedt committed Oct 31, 2022
1 parent c39b972 commit 16f7815
Show file tree
Hide file tree
Showing 12 changed files with 75 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ const CreateConnectionFormInner: React.FC<CreateConnectionPropsInner> = ({ schem
initialValues={initialValues}
validationSchema={connectionValidationSchema(mode)}
onSubmit={onFormSubmit}
validateOnChange={false}
>
{({ values, isSubmitting, isValid, dirty }) => (
<Form>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,12 @@ export const ConnectionItemPageInner: React.FC = () => {
>
<Suspense fallback={<LoadingPage />}>
<Routes>
<Route path={ConnectionSettingsRoutes.STATUS} element={<ConnectionStatusTab connection={connection} />} />
<Route path={ConnectionSettingsRoutes.STATUS} element={<ConnectionStatusTab />} />
<Route path={ConnectionSettingsRoutes.REPLICATION} element={<ConnectionReplicationTab />} />
<Route
path={ConnectionSettingsRoutes.TRANSFORMATION}
element={<ConnectionTransformationTab connection={connection} />}
/>
<Route path={ConnectionSettingsRoutes.TRANSFORMATION} element={<ConnectionTransformationTab />} />
<Route
path={ConnectionSettingsRoutes.SETTINGS}
element={
isConnectionDeleted ? <Navigate replace to=".." /> : <ConnectionSettingsTab connection={connection} />
}
element={isConnectionDeleted ? <Navigate replace to=".." /> : <ConnectionSettingsTab />}
/>
<Route index element={<Navigate to={ConnectionSettingsRoutes.STATUS} replace />} />
</Routes>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export const ConnectionReplicationTab: React.FC = () => {
validationSchema={connectionValidationSchema(mode)}
onSubmit={onFormSubmit}
enableReinitialize
validateOnChange={false}
>
{({ values, isSubmitting, isValid, dirty, resetForm }) => (
<Form>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, mockConnection } from "test-utils/testutils";
import { mockConnection, render } from "test-utils/testutils";

import { ConnectionSettingsTab } from "./ConnectionSettingsTab";

Expand All @@ -23,13 +23,10 @@ jest.mock("hooks/services/Analytics/useAnalyticsService", () => {
return analyticsService;
});

// Mocking the DeleteBlock component is a bit ugly, but it's simpler and less
// brittle than mocking the providers it depends on; at least it's a direct,
// visible dependency of the component under test here.
//
// This mock is intentionally trivial; if anything to do with this component is
// to be tested, we'll have to bite the bullet and render it properly, within
// the necessary providers.
jest.mock("hooks/services/ConnectionEdit/ConnectionEditService", () => ({
useConnectionEditService: () => ({ connection: mockConnection }),
}));

jest.mock("components/DeleteBlock", () => () => {
const MockDeleteBlock = () => <div>Does not actually delete anything</div>;
return <MockDeleteBlock />;
Expand All @@ -40,11 +37,11 @@ describe("<SettingsView />", () => {
let container: HTMLElement;

setMockIsAdvancedMode(false);
({ container } = await render(<ConnectionSettingsTab connection={mockConnection} />));
({ container } = await render(<ConnectionSettingsTab />));
expect(container.textContent).not.toContain("Connection State");

setMockIsAdvancedMode(true);
({ container } = await render(<ConnectionSettingsTab connection={mockConnection} />));
({ container } = await render(<ConnectionSettingsTab />));
expect(container.textContent).toContain("Connection State");
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,16 @@ import React from "react";

import DeleteBlock from "components/DeleteBlock";

import { WebBackendConnectionRead } from "core/request/AirbyteClient";
import { PageTrackingCodes, useTrackPage } from "hooks/services/Analytics";
import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService";
import { useAdvancedModeSetting } from "hooks/services/useAdvancedModeSetting";
import { useDeleteConnection } from "hooks/services/useConnectionHook";

import styles from "./ConnectionSettingsTab.module.scss";
import { StateBlock } from "./StateBlock";

interface ConnectionSettingsTabProps {
connection: WebBackendConnectionRead;
}

export const ConnectionSettingsTab: React.FC<ConnectionSettingsTabProps> = ({ connection }) => {
export const ConnectionSettingsTab: React.FC = () => {
const { connection } = useConnectionEditService();
const { mutateAsync: deleteConnection } = useDeleteConnection();

const [isAdvancedMode] = useAdvancedModeSetting();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ import { Tooltip } from "components/ui/Tooltip";

import { Action, Namespace } from "core/analytics";
import { getFrequencyFromScheduleData } from "core/analytics/utils";
import { ConnectionStatus, JobWithAttemptsRead, WebBackendConnectionRead } from "core/request/AirbyteClient";
import { ConnectionStatus, JobWithAttemptsRead } from "core/request/AirbyteClient";
import Status from "core/statuses";
import { useTrackPage, PageTrackingCodes, useAnalyticsService } from "hooks/services/Analytics";
import { useConfirmationModalService } from "hooks/services/ConfirmationModal";
import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService";
import { FeatureItem, useFeature } from "hooks/services/Feature";
import { useResetConnection, useSyncConnection } from "hooks/services/useConnectionHook";
import { useCancelJob, useListJobs } from "services/job/JobService";
Expand All @@ -37,18 +38,15 @@ interface ActiveJob {
isCanceling: boolean;
}

interface ConnectionStatusTabProps {
connection: WebBackendConnectionRead;
}

const getJobRunningOrPending = (jobs: JobWithAttemptsRead[]) => {
return jobs.find((jobWithAttempts) => {
const jobStatus = jobWithAttempts?.job?.status;
return jobStatus === Status.PENDING || jobStatus === Status.RUNNING || jobStatus === Status.INCOMPLETE;
});
};

export const ConnectionStatusTab: React.FC<ConnectionStatusTabProps> = ({ connection }) => {
export const ConnectionStatusTab: React.FC = () => {
const { connection } = useConnectionEditService();
useTrackPage(PageTrackingCodes.CONNECTIONS_ITEM_STATUS);
const [activeJob, setActiveJob] = useState<ActiveJob>();
const [jobPageSize, setJobPageSize] = useState(JOB_PAGE_SIZE_INCREMENT);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.content {
max-width: 1073px;
margin: 0 auto;
padding-bottom: 10px;
}

.customCard {
max-width: 500px;
margin: 0 auto;
min-height: 100px;
display: flex;
justify-content: center;
align-items: center;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,16 @@ import { Field, FieldArray } from "formik";
import React, { useMemo } from "react";
import { FormattedMessage } from "react-intl";
import { useToggle } from "react-use";
import styled from "styled-components";

import { Card } from "components/ui/Card";
import { Text } from "components/ui/Text";

import { buildConnectionUpdate, NormalizationType } from "core/domain/connection";
import {
ConnectionStatus,
OperationCreate,
OperationRead,
OperatorType,
WebBackendConnectionRead,
} from "core/request/AirbyteClient";
import { NormalizationType } from "core/domain/connection";
import { OperationCreate, OperationRead, OperatorType } from "core/request/AirbyteClient";
import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics";
import { ConnectionFormMode } from "hooks/services/ConnectionForm/ConnectionFormService";
import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService";
import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService";
import { FeatureItem, useFeature } from "hooks/services/Feature";
import { useUpdateConnection } from "hooks/services/useConnectionHook";
import { useCurrentWorkspace } from "hooks/services/useWorkspace";
import { useGetDestinationDefinitionSpecification } from "services/connector/DestinationDefinitionSpecificationService";
import { FormikOnSubmit } from "types/formik";
Expand All @@ -31,32 +24,14 @@ import {
} from "views/Connection/ConnectionForm/formConfig";
import { FormCard } from "views/Connection/FormCard";

interface ConnectionTransformationTabProps {
connection: WebBackendConnectionRead;
}

const Content = styled.div`
max-width: 1073px;
margin: 0 auto;
padding-bottom: 10px;
`;

const NoSupportedTransformationCard = styled(Card)`
max-width: 500px;
margin: 0 auto;
min-height: 100px;
display: flex;
justify-content: center;
align-items: center;
`;
import styles from "./ConnectionTransformationTab.module.scss";

const CustomTransformationsCard: React.FC<{
operations?: OperationCreate[];
onSubmit: FormikOnSubmit<{ transformations?: OperationRead[] }>;
mode: ConnectionFormMode;
}> = ({ operations, onSubmit, mode }) => {
}> = ({ operations, onSubmit }) => {
const [editingTransformation, toggleEditingTransformation] = useToggle(false);

const { mode } = useConnectionFormService();
const initialValues = useMemo(
() => ({
transformations: getInitialTransformations(operations || []),
Expand All @@ -75,7 +50,6 @@ const CustomTransformationsCard: React.FC<{
onSubmit,
}}
submitDisabled={editingTransformation}
mode={mode}
>
<FieldArray name="transformations">
{(formProps) => (
Expand All @@ -94,8 +68,8 @@ const CustomTransformationsCard: React.FC<{
const NormalizationCard: React.FC<{
operations?: OperationRead[];
onSubmit: FormikOnSubmit<{ normalization?: NormalizationType }>;
mode: ConnectionFormMode;
}> = ({ operations, onSubmit, mode }) => {
}> = ({ operations, onSubmit }) => {
const { mode } = useConnectionFormService();
const initialValues = useMemo(
() => ({
normalization: getInitialNormalization(operations, true),
Expand All @@ -111,24 +85,22 @@ const NormalizationCard: React.FC<{
}}
title={<FormattedMessage id="connection.normalization" />}
collapsible
mode={mode}
>
<Field name="normalization" component={NormalizationField} mode={mode} />
</FormCard>
);
};

export const ConnectionTransformationTab: React.FC<ConnectionTransformationTabProps> = ({ connection }) => {
export const ConnectionTransformationTab: React.FC = () => {
const { connection, updateConnection } = useConnectionEditService();
const { mode } = useConnectionFormService();
const definition = useGetDestinationDefinitionSpecification(connection.destination.destinationDefinitionId);
const { mutateAsync: updateConnection } = useUpdateConnection();
const workspace = useCurrentWorkspace();

useTrackPage(PageTrackingCodes.CONNECTIONS_ITEM_TRANSFORMATION);
const { supportsNormalization } = definition;
const supportsDbt = useFeature(FeatureItem.AllowCustomDBT) && definition.supportsDbt;

const mode = connection.status === ConnectionStatus.deprecated ? "readonly" : "edit";

const onSubmit: FormikOnSubmit<{ transformations?: OperationRead[]; normalization?: NormalizationType }> = async (
values,
{ resetForm }
Expand All @@ -143,11 +115,7 @@ export const ConnectionTransformationTab: React.FC<ConnectionTransformationTabPr
(connection.operations ?? [])?.filter((op) => op.operatorConfiguration.operatorType === OperatorType.dbt)
);

await updateConnection(
buildConnectionUpdate(connection, {
operations,
})
);
await updateConnection({ connectionId: connection.connectionId, operations });

const nextFormValues: typeof values = {};
if (values.transformations) {
Expand All @@ -159,25 +127,21 @@ export const ConnectionTransformationTab: React.FC<ConnectionTransformationTabPr
};

return (
<Content>
<div className={styles.content}>
<fieldset
disabled={mode === "readonly"}
style={{ border: "0", pointerEvents: `${mode === "readonly" ? "none" : "auto"}` }}
>
{supportsNormalization && (
<NormalizationCard operations={connection.operations} onSubmit={onSubmit} mode={mode} />
)}
{supportsDbt && (
<CustomTransformationsCard operations={connection.operations} onSubmit={onSubmit} mode={mode} />
)}
{supportsNormalization && <NormalizationCard operations={connection.operations} onSubmit={onSubmit} />}
{supportsDbt && <CustomTransformationsCard operations={connection.operations} onSubmit={onSubmit} />}
{!supportsNormalization && !supportsDbt && (
<NoSupportedTransformationCard>
<Card className={styles.customCard}>
<Text as="p" size="lg" centered>
<FormattedMessage id="connectionForm.operations.notSupported" />
</Text>
</NoSupportedTransformationCard>
</Card>
)}
</fieldset>
</Content>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@use "../../../../scss/variables";

.normalizationField {
margin: variables.$spacing-lg 0;
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import { FieldProps } from "formik";
import React from "react";
import { FormattedMessage } from "react-intl";
import styled from "styled-components";

import { LabeledRadioButton, Link } from "components";

import { NormalizationType } from "core/domain/connection/operation";
import { ConnectionFormMode } from "hooks/services/ConnectionForm/ConnectionFormService";
import { useConnectionFormService } from "hooks/services/ConnectionForm/ConnectionFormService";
import { links } from "utils/links";

const Normalization = styled.div`
margin: 16px 0;
`;
import styles from "./NormalizationField.module.scss";

type NormalizationBlockProps = FieldProps<string> & {
mode: ConnectionFormMode;
};
type NormalizationBlockProps = FieldProps<string>;

export const NormalizationField: React.FC<NormalizationBlockProps> = ({ form, field }) => {
const { mode } = useConnectionFormService();

const NormalizationField: React.FC<NormalizationBlockProps> = ({ form, field, mode }) => {
return (
<Normalization>
<div className={styles.normalizationField}>
<LabeledRadioButton
{...form.getFieldProps(field.name)}
id="normalization.raw"
Expand Down Expand Up @@ -50,8 +47,6 @@ const NormalizationField: React.FC<NormalizationBlockProps> = ({ form, field, mo
)
}
/>
</Normalization>
</div>
);
};

export { NormalizationField };
5 changes: 5 additions & 0 deletions airbyte-webapp/src/views/Connection/FormCard.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@use "../../scss/variables";

.formCard {
padding: 22px 27px variables.$spacing-xl 24px;
}
Loading

0 comments on commit 16f7815

Please sign in to comment.