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

Connection Form Refactor - Connection Form Service #15893

Merged
merged 64 commits into from
Sep 19, 2022
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
10b4ff1
Work started
krishnaglick Aug 17, 2022
2f0e83f
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Aug 18, 2022
183087a
Minor cleanup
krishnaglick Aug 18, 2022
b3bc5ff
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Aug 18, 2022
85fe161
Some cleanup
krishnaglick Aug 18, 2022
0e063b1
Lots moved into context
krishnaglick Aug 18, 2022
d7ad611
WIP, stepping in the right direction
krishnaglick Aug 18, 2022
468d840
WIP testing
krishnaglick Aug 18, 2022
8834c04
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Aug 23, 2022
b99d1b3
Post-merge fix
krishnaglick Aug 23, 2022
25f28ba
Observables!
krishnaglick Aug 23, 2022
8272377
WIP tests
krishnaglick Aug 24, 2022
e85d301
Tests!
krishnaglick Aug 24, 2022
bf6a647
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Aug 24, 2022
33caf95
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Aug 25, 2022
68a6ad8
CI test
krishnaglick Aug 25, 2022
9bf2c73
CI?
krishnaglick Aug 25, 2022
8af1a41
perhaps
krishnaglick Aug 25, 2022
4ed21ec
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Aug 26, 2022
eb24031
Only show name field during create
krishnaglick Aug 26, 2022
1d4b3a7
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Aug 26, 2022
e41a42f
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Aug 29, 2022
dfcd4cc
Fix Build
krishnaglick Aug 29, 2022
eb54ec2
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Aug 29, 2022
34434d2
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Aug 30, 2022
be4001b
Fix build
krishnaglick Aug 30, 2022
9d95504
Fixing a bug
krishnaglick Aug 31, 2022
b53abb8
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Aug 31, 2022
50bbb3f
Fix failing test
krishnaglick Aug 31, 2022
65584ca
Fixes e2e
krishnaglick Aug 31, 2022
3da331e
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Sep 1, 2022
e841140
Type consolidation
krishnaglick Sep 1, 2022
92b7c0c
useCallback, improvements to connection create onAfter, and removing …
krishnaglick Sep 1, 2022
8c5558b
cleanup
krishnaglick Sep 1, 2022
d390833
Removing an unused prop
krishnaglick Sep 1, 2022
3d094fb
errorStatusMessage and mapFormPropsToOperation tests
krishnaglick Sep 2, 2022
3ec2cf2
useUniqueFormId and useInitialValues tests
krishnaglick Sep 2, 2022
0452458
Cleanup, onFrequencySelect is moved to its use location, better test …
krishnaglick Sep 2, 2022
df444fe
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Sep 2, 2022
aec7b10
Better formSubmit handling for new connection
krishnaglick Sep 2, 2022
6aabc3b
Commenting and some cleanup
krishnaglick Sep 2, 2022
8af9bd2
Comments!
krishnaglick Sep 2, 2022
c9ca5b5
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Sep 2, 2022
f809c68
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Sep 6, 2022
61251a0
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Sep 7, 2022
208cf78
Fixing errors from the merge
krishnaglick Sep 7, 2022
c7f9afa
mock data cleanup
krishnaglick Sep 7, 2022
cc91839
Better TODO
krishnaglick Sep 7, 2022
e89f762
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Sep 7, 2022
96b12a5
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Sep 8, 2022
3395828
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Sep 8, 2022
4c17657
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Sep 12, 2022
498743a
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Sep 13, 2022
ed26655
Merge branch 'master' into kc/connection-form-refactor
krishnaglick Sep 13, 2022
955848c
onFrequencySelect is now always called
krishnaglick Sep 13, 2022
a0beff7
Edmundo CR
krishnaglick Sep 13, 2022
e823d60
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Sep 13, 2022
e2dc6e1
Merge branch 'master' into kc/connection-form-refactor
lmossman Sep 13, 2022
7f4916a
Remove whitespace
krishnaglick Sep 14, 2022
cec5034
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Sep 15, 2022
5ac6baa
Bridging changes to bring things inline
krishnaglick Sep 15, 2022
184732f
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Sep 16, 2022
50df6de
Builds and tests run
krishnaglick Sep 16, 2022
a45d98a
Merge branch 'master' of github.com:airbytehq/airbyte into kc/connect…
krishnaglick Sep 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { faRedoAlt } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React, { Suspense, useMemo } from "react";
import React, { Suspense, useCallback } from "react";
import { FormattedMessage } from "react-intl";

import { Button, ContentCard } from "components";
import { IDataItem } from "components/base/DropDown/components/Option";
import { JobItem } from "components/JobItem/JobItem";
import LoadingSchema from "components/LoadingSchema";

import { Action, Namespace } from "core/analytics";
import { LogsRequestError } from "core/request/LogsRequestError";
import { useAnalyticsService } from "hooks/services/Analytics";
import { ConnectionFormServiceProvider } from "hooks/services/Connection/ConnectionFormService";
import { useFormChangeTrackerService, useUniqueFormId } from "hooks/services/FormChangeTracker";
import { useCreateConnection, ValuesProps } from "hooks/services/useConnectionHook";
import { ConnectionForm, ConnectionFormProps } from "views/Connection/ConnectionForm";
import useRouter from "hooks/useRouter";
import { ConnectionForm } from "views/Connection/ConnectionForm";

import { DestinationRead, SourceRead, WebBackendConnectionRead } from "../../core/request/AirbyteClient";
import { DestinationRead, SourceRead } from "../../core/request/AirbyteClient";
import { useDiscoverSchema } from "../../hooks/services/useSourceHook";
import TryAfterErrorBlock from "./components/TryAfterErrorBlock";
import styles from "./CreateConnectionContent.module.scss";

interface CreateConnectionContentProps {
source: SourceRead;
destination: DestinationRead;
afterSubmitConnection?: (connection: WebBackendConnectionRead) => void;
afterSubmitConnection?: () => void;
}

const CreateConnectionContent: React.FC<CreateConnectionContentProps> = ({
Expand All @@ -31,61 +31,49 @@ const CreateConnectionContent: React.FC<CreateConnectionContentProps> = ({
afterSubmitConnection,
}) => {
const { mutateAsync: createConnection } = useCreateConnection();
const analyticsService = useAnalyticsService();
const { push } = useRouter();

const formId = useUniqueFormId();
const { clearFormChange } = useFormChangeTrackerService();

const { schema, isLoading, schemaErrorStatus, catalogId, onDiscoverSchema } = useDiscoverSchema(
source.sourceId,
true
);

const connection = useMemo<ConnectionFormProps["connection"]>(
() => ({
syncCatalog: schema,
destination,
source,
catalogId,
}),
[schema, destination, source, catalogId]
);

const onSubmitConnectionStep = async (values: ValuesProps) => {
const connection = await createConnection({
values,
source,
destination,
sourceDefinition: {
sourceDefinitionId: source?.sourceDefinitionId ?? "",
},
destinationDefinition: {
name: destination?.name ?? "",
destinationDefinitionId: destination?.destinationDefinitionId ?? "",
},
sourceCatalogId: catalogId,
});

return {
onSubmitComplete: () => {
afterSubmitConnection?.(connection);
},
};
const connection = {
syncCatalog: schema,
destination,
source,
catalogId,
};

const onSelectFrequency = (item: IDataItem | null) => {
const enabledStreams = connection.syncCatalog.streams.filter((stream) => stream.config?.selected).length;

if (item) {
analyticsService.track(Namespace.CONNECTION, Action.FREQUENCY, {
actionDescription: "Frequency selected",
frequency: item.label,
connector_source_definition: source?.sourceName,
connector_source_definition_id: source?.sourceDefinitionId,
connector_destination_definition: destination?.destinationName,
connector_destination_definition_id: destination?.destinationDefinitionId,
available_streams: connection.syncCatalog.streams.length,
enabled_streams: enabledStreams,
const onSubmitConnectionStep = useCallback(
async (values: ValuesProps) => {
const createdConnection = await createConnection({
values,
source,
destination,
sourceDefinition: {
sourceDefinitionId: source?.sourceDefinitionId ?? "",
},
destinationDefinition: {
name: destination?.name ?? "",
destinationDefinitionId: destination?.destinationDefinitionId ?? "",
},
sourceCatalogId: catalogId,
});
}
};

// We only want to go to the new connection if we _do not_ have an after submit action.
if (!afterSubmitConnection) {
// We have to clear the form change to prevent the dirty-form tracking modal from appearing.
clearFormChange(formId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks too high in the component tree and should be called from the Connection form. The reason is that the ConnectionForm sets up the ConnectionChangeTracker, and should be responsible for clearing it after submission. In this case, this component, which has no idea what the form does, assumes that the tracker is being used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next PR brings the FormikContext "out" a layer at which point this would be the right place for this call. Does it seem reasonable to leave this for now knowing it will be much more readily adressable in the next PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be changed in part 2.

// This is the "default behavior", go to the created connection.
push(`../../connections/${createdConnection.connectionId}`);
}
},
[afterSubmitConnection, catalogId, clearFormChange, createConnection, destination, formId, push, source]
);

if (schemaErrorStatus) {
const job = LogsRequestError.extractJobInfo(schemaErrorStatus);
Expand All @@ -101,18 +89,22 @@ const CreateConnectionContent: React.FC<CreateConnectionContentProps> = ({
<LoadingSchema />
) : (
<Suspense fallback={<LoadingSchema />}>
<ConnectionForm
mode="create"
<ConnectionFormServiceProvider
connection={connection}
onDropDownSelect={onSelectFrequency}
mode="create"
formId={formId}
onSubmit={onSubmitConnectionStep}
additionalSchemaControl={
<Button onClick={onDiscoverSchema} type="button">
<FontAwesomeIcon className={styles.tryArrowIcon} icon={faRedoAlt} />
<FormattedMessage id="connection.refreshSchema" />
</Button>
}
/>
onAfterSubmit={afterSubmitConnection}
>
<ConnectionForm
additionalSchemaControl={
<Button onClick={onDiscoverSchema} type="button">
<FontAwesomeIcon className={styles.tryArrowIcon} icon={faRedoAlt} />
<FormattedMessage id="connection.refreshSchema" />
</Button>
}
/>
</ConnectionFormServiceProvider>
</Suspense>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ const AgainButton = styled(Button)`
interface TryAfterErrorBlockProps {
message?: React.ReactNode;
onClick: () => void;
additionControl?: React.ReactNode;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I forgot to remove this as part of the side-PR's. This was unused.

}

const TryAfterErrorBlock: React.FC<TryAfterErrorBlockProps> = ({ message, onClick }) => (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
teallarson marked this conversation as resolved.
Show resolved Hide resolved
import { act } from "@testing-library/react";
import { renderHook } from "@testing-library/react-hooks";
import React from "react";
import { MemoryRouter } from "react-router-dom";
import mockConnection from "test-utils/mock-data/mockConnection.json";
import mockDest from "test-utils/mock-data/mockDestinationDefinition.json";
import mockWorkspace from "test-utils/mock-data/mockWorkspace.json";
import { TestWrapper } from "test-utils/testutils";

import { WebBackendConnectionRead } from "core/request/AirbyteClient";

import { ModalCancel } from "../Modal";
import {
ConnectionFormServiceProvider,
ConnectionServiceProps,
useConnectionFormService,
} from "./ConnectionFormService";

["packages/cloud/services/workspaces/WorkspacesService", "services/workspaces/WorkspacesService"].forEach((s) =>
jest.mock(s, () => ({
useCurrentWorkspaceId: () => mockWorkspace.workspaceId,
useCurrentWorkspace: () => mockWorkspace,
}))
);

jest.mock("../FormChangeTracker", () => ({
useFormChangeTrackerService: () => ({ clearFormChange: () => null }),
useUniqueFormId: () => "blah",
}));
jest.mock("services/connector/DestinationDefinitionSpecificationService", () => ({
useGetDestinationDefinitionSpecification: () => mockDest,
}));

describe("ConnectionFormService", () => {
krishnaglick marked this conversation as resolved.
Show resolved Hide resolved
const Wrapper: React.FC<ConnectionServiceProps> = ({ children, ...props }) => (
<TestWrapper>
<MemoryRouter>
<ConnectionFormServiceProvider {...props}>{children}</ConnectionFormServiceProvider>
</MemoryRouter>
</TestWrapper>
);

const onSubmit = jest.fn();
const onAfterSubmit = jest.fn();
const onCancel = jest.fn();

beforeEach(() => {
onSubmit.mockReset();
onAfterSubmit.mockReset();
onCancel.mockReset();
});

it("should call onSubmit when submitted", async () => {
const { result } = renderHook(useConnectionFormService, {
wrapper: Wrapper,
initialProps: {
connection: mockConnection as WebBackendConnectionRead,
mode: "create",
formId: Math.random().toString(),
onSubmit,
onAfterSubmit,
onCancel,
},
});

const resetForm = jest.fn();
const testValues: any = {};
await act(async () => {
await result.current.onFormSubmit(testValues, { resetForm } as any);
});

expect(resetForm).toBeCalledWith({ values: testValues });
expect(onSubmit).toBeCalledWith({
operations: [],
scheduleData: {
basicSchedule: {
timeUnit: undefined,
units: undefined,
},
},
scheduleType: "manual",
syncCatalog: {
streams: undefined,
},
});
expect(onAfterSubmit).toBeCalledWith();
});

it("should catch if onSubmit throws and generate an error message", async () => {
const errorMessage = "asdf";
onSubmit.mockImplementation(async () => {
throw new Error(errorMessage);
});

const { result } = renderHook(useConnectionFormService, {
wrapper: Wrapper,
initialProps: {
connection: mockConnection as WebBackendConnectionRead,
mode: "create",
formId: Math.random().toString(),
onSubmit,
onAfterSubmit,
onCancel,
},
});

const resetForm = jest.fn();
const testValues: any = {};
await act(async () => {
await result.current.onFormSubmit(testValues, { resetForm } as any);
});

expect(result.current.errorMessage).toBe(errorMessage);
expect(resetForm).not.toHaveBeenCalled();
});

it("should catch if onSubmit throws but not generate an error if it's a ModalCancel error", async () => {
onSubmit.mockImplementation(async () => {
throw new ModalCancel();
});

const { result } = renderHook(useConnectionFormService, {
wrapper: Wrapper,
initialProps: {
connection: mockConnection as WebBackendConnectionRead,
mode: "create",
formId: Math.random().toString(),
onSubmit,
onAfterSubmit,
onCancel,
},
});

const resetForm = jest.fn();
const testValues: any = {};
await act(async () => {
await result.current.onFormSubmit(testValues, { resetForm } as any);
});

expect(result.current.errorMessage).toBe(null);
expect(resetForm).not.toHaveBeenCalled();
});
});
Loading