Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🪟 🧹 Remove connection and mode prop drilling on connection view/edit pages #17808

Merged
merged 9 commits into from
Oct 14, 2022
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