From 534ed7151e8e56c1c589a8236b5da6e0a6c051e9 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Mon, 3 Oct 2022 14:15:07 -0700 Subject: [PATCH 01/30] Add lightly-styled ui for dbt cloud settings --- .../src/packages/cloud/locales/en.json | 6 +++ .../views/settings/CloudSettingsPage.tsx | 12 +++++ .../integrations/DbtCloudSettingsView.tsx | 49 +++++++++++++++++++ .../cloud/views/settings/routePaths.ts | 1 + 4 files changed, 68 insertions(+) create mode 100644 airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx diff --git a/airbyte-webapp/src/packages/cloud/locales/en.json b/airbyte-webapp/src/packages/cloud/locales/en.json index 2e762fb5820e..406c7335cc6e 100644 --- a/airbyte-webapp/src/packages/cloud/locales/en.json +++ b/airbyte-webapp/src/packages/cloud/locales/en.json @@ -86,6 +86,12 @@ "settings.accountSettings.updateNameSuccess": "Your name has been updated!", "settings.userSettings": "User settings", "settings.workspaceSettings": "Workspace settings", + "settings.integrationSettings": "Integration settings", + "settings.integrationSettings.dbtCloudSettings": "dbt Cloud Integration", + "settings.integrationSettings.dbtCloudSettings.form.serviceToken": "Service Token", + "settings.integrationSettings.dbtCloudSettings.form.singleTenantUrl": "Single-tenant URL", + "settings.integrationSettings.dbtCloudSettings.form.testConnection": "Test connection", + "settings.integrationSettings.dbtCloudSettings.form.submit": "Save changes", "settings.generalSettings": "General Settings", "settings.generalSettings.changeWorkspace": "Change Workspace", "settings.generalSettings.form.name.label": "Workspace name", diff --git a/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx b/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx index 86b89add3811..344ea898ea1f 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx @@ -2,6 +2,7 @@ import React, { useMemo } from "react"; import { FormattedMessage } from "react-intl"; // import useConnector from "hooks/services/useConnector"; +import { DbtCloudSettingsView } from "packages/cloud/views/settings/integrations/DbtCloudSettingsView"; import { AccountSettingsView } from "packages/cloud/views/users/AccountSettingsView"; import { UsersSettingsView } from "packages/cloud/views/users/UsersSettingsView"; import { WorkspaceSettingsView } from "packages/cloud/views/workspaces/WorkspaceSettingsView"; @@ -82,6 +83,17 @@ export const CloudSettingsPage: React.FC = () => { }, ], }, + { + category: , + routes: [ + { + path: CloudSettingsRoutes.DbtCloud, + name: , + component: DbtCloudSettingsView, + id: "integrationSettings.dbtCloudSettings", + }, + ], + }, ], }), [] diff --git a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx new file mode 100644 index 000000000000..d18ee3eac1dd --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx @@ -0,0 +1,49 @@ +import { Field, FieldProps, Form, Formik } from "formik"; +import React from "react"; +import { FormattedMessage } from "react-intl"; + +import { LabeledInput } from "components/LabeledInput"; +import { Button } from "components/ui/Button"; + +import { Content, SettingsCard } from "pages/SettingsPage/pages/SettingsComponents"; + +export const DbtCloudSettingsView: React.FC = () => ( + }> + + console.log(values)} + > +
+ + {({ field }: FieldProps) => ( + } + type="text" + /> + )} + + + {({ field }: FieldProps) => ( + } + type="text" + /> + )} + + + +
+
+
+
+); diff --git a/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts b/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts index c82011c13398..5926f6aeaf7e 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts +++ b/airbyte-webapp/src/packages/cloud/views/settings/routePaths.ts @@ -9,4 +9,5 @@ export const CloudSettingsRoutes = { Workspace: "workspaces", AccessManagement: "access-management", + DbtCloud: "dbt-cloud", } as const; From a46c42caaa94a3edf4df67bd3cee702ca47f2266 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Tue, 4 Oct 2022 14:38:23 -0700 Subject: [PATCH 02/30] Add CollapsablePanel component --- .../CollapsablePanel.module.scss | 6 +++ .../ui/CollapsablePanel/CollapsablePanel.tsx | 49 +++++++++++++++++++ .../components/ui/CollapsablePanel/index.ts | 1 + 3 files changed, 56 insertions(+) create mode 100644 airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.module.scss create mode 100644 airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.tsx create mode 100644 airbyte-webapp/src/components/ui/CollapsablePanel/index.ts diff --git a/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.module.scss b/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.module.scss new file mode 100644 index 000000000000..7dab8b22a3a5 --- /dev/null +++ b/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.module.scss @@ -0,0 +1,6 @@ +.arrow { + border: none; + background-color: inherit; + color: inherit; + cursor: pointer; +} diff --git a/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.tsx b/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.tsx new file mode 100644 index 000000000000..613a414cc63d --- /dev/null +++ b/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.tsx @@ -0,0 +1,49 @@ +import { faChevronRight, faChevronDown } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import classNames from "classnames"; +import React, { ReactElement } from "react"; +import { useToggle } from "react-use"; + +import styles from "./CollapsablePanel.module.scss"; + +const Arrow: React.FC<{ isOpen: boolean }> = ({ isOpen }) => ( + +); + +/** + * CollapsablePanel defines a reusable "click to expand" UI element. It has two + * props for content slots, each containing an arbitrary component tree: + * - children: the "above the fold" or "title" content which is always shown + * - drawer: the "below the fold" content which is expanded or hidden on user interaction + * + * The rationale for putting the "above the fold" content in `children` is that + react's syntactic sugar for `children` makes the source code better reflect + the rendered UI, with the always-present content defined inline next to + its "peer" components in the surrounding context. + */ +export const CollapsablePanel: React.FC<{ + drawer: ReactElement; + initiallyOpen?: boolean; + className?: string; + openClassName?: string; + closedClassName?: string; +}> = ({ drawer, initiallyOpen = false, children, className, openClassName, closedClassName }) => { + const [isOpen, toggleOpen] = useToggle(initiallyOpen); + return ( +
+
+ + {children} +
+ {isOpen ? drawer : null} +
+ ); +}; diff --git a/airbyte-webapp/src/components/ui/CollapsablePanel/index.ts b/airbyte-webapp/src/components/ui/CollapsablePanel/index.ts new file mode 100644 index 000000000000..78eff7373b36 --- /dev/null +++ b/airbyte-webapp/src/components/ui/CollapsablePanel/index.ts @@ -0,0 +1 @@ +export * from "./CollapsablePanel"; From 578113b320456eddebd3d225e9bc5713232f5412 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Wed, 5 Oct 2022 13:02:50 -0700 Subject: [PATCH 03/30] Add CollapsablePanel around url input, MVP styling To get the styling to work, I needed to edit `LabeledInput` to accept a `className` prop, so I could give it contextually-specific styling. --- .../components/LabeledInput/LabeledInput.tsx | 5 ++- .../src/packages/cloud/locales/en.json | 1 + .../DbtCloudSettingsView.module.scss | 39 ++++++++++++++++++ .../integrations/DbtCloudSettingsView.tsx | 41 +++++++++++++------ 4 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss diff --git a/airbyte-webapp/src/components/LabeledInput/LabeledInput.tsx b/airbyte-webapp/src/components/LabeledInput/LabeledInput.tsx index 74d93305ab1f..8f072745946b 100644 --- a/airbyte-webapp/src/components/LabeledInput/LabeledInput.tsx +++ b/airbyte-webapp/src/components/LabeledInput/LabeledInput.tsx @@ -3,7 +3,8 @@ import React from "react"; import { ControlLabels, ControlLabelsProps } from "components/LabeledControl"; import { Input, InputProps } from "components/ui/Input"; -type LabeledInputProps = Pick & InputProps; +type LabeledInputProps = Pick & + InputProps & { className?: string }; const LabeledInput: React.FC = ({ error, @@ -11,6 +12,7 @@ const LabeledInput: React.FC = ({ message, label, labelAdditionLength, + className, ...inputProps }) => ( = ({ success={success} message={message} label={label} + className={className} labelAdditionLength={labelAdditionLength} > diff --git a/airbyte-webapp/src/packages/cloud/locales/en.json b/airbyte-webapp/src/packages/cloud/locales/en.json index 406c7335cc6e..a1770af77576 100644 --- a/airbyte-webapp/src/packages/cloud/locales/en.json +++ b/airbyte-webapp/src/packages/cloud/locales/en.json @@ -89,6 +89,7 @@ "settings.integrationSettings": "Integration settings", "settings.integrationSettings.dbtCloudSettings": "dbt Cloud Integration", "settings.integrationSettings.dbtCloudSettings.form.serviceToken": "Service Token", + "settings.integrationSettings.dbtCloudSettings.form.advancedOptions": "Advanced options", "settings.integrationSettings.dbtCloudSettings.form.singleTenantUrl": "Single-tenant URL", "settings.integrationSettings.dbtCloudSettings.form.testConnection": "Test connection", "settings.integrationSettings.dbtCloudSettings.form.submit": "Save changes", diff --git a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss new file mode 100644 index 000000000000..649a0aea1efa --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss @@ -0,0 +1,39 @@ +@use "scss/colors"; +@use "scss/variables" as vars; + +$item-spacing: 25px; + +.advancedOptions { + margin-top: $item-spacing; +} + +.advancedOptionsClosed { + color: colors.$grey; +} + +.advancedOptionsOpen { + color: colors.$blue; +} + +.singleTenantUrlInput { + margin-top: 1em; + + // HACK: this left margin approximately matches the width of the containing + // CollapsablePanel component's caret button + margin-left: 1.6em; + + // this overrides the LabeledInput's width: 100%, which causes the input to + // overflow into the parent div's padding (by exactly the left margin above). + // With width: auto, the right-hand edges of the two inputs line up nicely. + width: auto; +} + +.controlGroup { + display: flex; + justify-content: flex-end; + margin-top: $item-spacing; + + .button { + margin-left: 1em; + } +} diff --git a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx index d18ee3eac1dd..1af7224a4336 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx +++ b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx @@ -1,12 +1,24 @@ -import { Field, FieldProps, Form, Formik } from "formik"; +import { Field, FieldInputProps, FieldProps, Form, Formik } from "formik"; import React from "react"; import { FormattedMessage } from "react-intl"; import { LabeledInput } from "components/LabeledInput"; import { Button } from "components/ui/Button"; +import { CollapsablePanel } from "components/ui/CollapsablePanel"; import { Content, SettingsCard } from "pages/SettingsPage/pages/SettingsComponents"; +import styles from "./DbtCloudSettingsView.module.scss"; + +const singleTenantUrlInput = (fieldProps: FieldInputProps) => ( + } + type="text" + /> +); + export const DbtCloudSettingsView: React.FC = () => ( }> @@ -29,19 +41,24 @@ export const DbtCloudSettingsView: React.FC = () => ( {({ field }: FieldProps) => ( - } - type="text" - /> + + + )} - - +
+ + +
From e3f8fbb64f4844ac37dd855bac54f01412037824 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Tue, 11 Oct 2022 21:39:40 -0700 Subject: [PATCH 04/30] Add new feature flag for dbt cloud integration This feature isn't added to either OSS or cloud builds; it will be dynamically toggled for specific targeted accounts via LaunchDarkly's `featureService.overwrites` key. --- airbyte-webapp/src/hooks/services/Feature/types.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/airbyte-webapp/src/hooks/services/Feature/types.tsx b/airbyte-webapp/src/hooks/services/Feature/types.tsx index f847aa37d2e2..2a7c9d8274a6 100644 --- a/airbyte-webapp/src/hooks/services/Feature/types.tsx +++ b/airbyte-webapp/src/hooks/services/Feature/types.tsx @@ -1,6 +1,7 @@ export enum FeatureItem { AllowUploadCustomImage = "ALLOW_UPLOAD_CUSTOM_IMAGE", AllowCustomDBT = "ALLOW_CUSTOM_DBT", + AllowDBTCloudIntegration = "ALLOW_DBT_CLOUD_INTEGRATION", AllowUpdateConnectors = "ALLOW_UPDATE_CONNECTORS", AllowOAuthConnector = "ALLOW_OAUTH_CONNECTOR", AllowCreateConnection = "ALLOW_CREATE_CONNECTION", From 54693cf9babfeff5f63e90f491e7036509f58276 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Wed, 12 Oct 2022 00:17:47 -0700 Subject: [PATCH 05/30] Put settings page dbt cloud ui behind feature flag --- .../views/settings/CloudSettingsPage.tsx | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx b/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx index 344ea898ea1f..8dd379a615c7 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/settings/CloudSettingsPage.tsx @@ -1,6 +1,7 @@ import React, { useMemo } from "react"; import { FormattedMessage } from "react-intl"; +import { FeatureItem, useFeature } from "hooks/services/Feature"; // import useConnector from "hooks/services/useConnector"; import { DbtCloudSettingsView } from "packages/cloud/views/settings/integrations/DbtCloudSettingsView"; import { AccountSettingsView } from "packages/cloud/views/users/AccountSettingsView"; @@ -21,6 +22,7 @@ import { CloudSettingsRoutes } from "./routePaths"; export const CloudSettingsPage: React.FC = () => { // TODO: uncomment when supported in cloud // const { countNewSourceVersion, countNewDestinationVersion } = useConnector(); + const supportsCloudDbtIntegration = useFeature(FeatureItem.AllowDBTCloudIntegration); const pageConfig = useMemo( () => ({ @@ -83,20 +85,24 @@ export const CloudSettingsPage: React.FC = () => { }, ], }, - { - category: , - routes: [ - { - path: CloudSettingsRoutes.DbtCloud, - name: , - component: DbtCloudSettingsView, - id: "integrationSettings.dbtCloudSettings", - }, - ], - }, + ...(supportsCloudDbtIntegration + ? [ + { + category: , + routes: [ + { + path: CloudSettingsRoutes.DbtCloud, + name: , + component: DbtCloudSettingsView, + id: "integrationSettings.dbtCloudSettings", + }, + ], + }, + ] + : []), ], }), - [] + [supportsCloudDbtIntegration] ); return ; From 39836dbfbf827b2e86bad9436bedaa762fdda65e Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Wed, 12 Oct 2022 01:26:42 -0700 Subject: [PATCH 06/30] Add feature-flagged CloudTransformationsCard --- .../public/images/octavia/worker.png | Bin 0 -> 14408 bytes .../ConnectionTransformationTab.module.scss | 30 ++++++++++++ .../ConnectionTransformationTab.tsx | 43 +++++++++++++++++- 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 airbyte-webapp/public/images/octavia/worker.png diff --git a/airbyte-webapp/public/images/octavia/worker.png b/airbyte-webapp/public/images/octavia/worker.png new file mode 100644 index 0000000000000000000000000000000000000000..1e0ddcff71025ca581031972ba47cb7f1aca1908 GIT binary patch literal 14408 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 O0000 img { + width: 111px; + height: 111px; + margin: 20px 0; + } +} + +.contextExplanation { + color: colors.$grey-300; + width: 100%; + + & a { + color: colors.$grey-300; + } } diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx index 41752ba8ffe9..b2141f78d635 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx @@ -1,8 +1,12 @@ +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import classNames from "classnames"; import { Field, FieldArray } from "formik"; import React, { useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useToggle } from "react-use"; +import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; import { Text } from "components/ui/Text"; @@ -65,6 +69,40 @@ const CustomTransformationsCard: React.FC<{ ); }; +const CloudTransformationsCard = () => { + // TODO fetch list of transformations for real + const transformations = []; + /* const transformations = [1, 2, 3]; */ + + const TransformationList = ({ className }: { className: string }) => + transformations.length ? ( +
this is a list
+ ) : ( +
+
+ After an Airbyte sync job has completed, the following jobs will run. +
+ An octopus wearing a hard hat, tools at the ready + No transformations +
+ ); + + return ( + + Transformations + + + } + > + + + ); +}; + const NormalizationCard: React.FC<{ operations?: OperationRead[]; onSubmit: FormikOnSubmit<{ normalization?: NormalizationType }>; @@ -100,6 +138,8 @@ export const ConnectionTransformationTab: React.FC = () => { useTrackPage(PageTrackingCodes.CONNECTIONS_ITEM_TRANSFORMATION); const { supportsNormalization } = definition; const supportsDbt = useFeature(FeatureItem.AllowCustomDBT) && definition.supportsDbt; + const supportsCloudDbtIntegration = useFeature(FeatureItem.AllowDBTCloudIntegration) && definition.supportsDbt; + const noSupportedTransformations = !supportsNormalization && !supportsDbt && !supportsCloudDbtIntegration; const onSubmit: FormikOnSubmit<{ transformations?: OperationRead[]; normalization?: NormalizationType }> = async ( values, @@ -134,7 +174,8 @@ export const ConnectionTransformationTab: React.FC = () => { > {supportsNormalization && } {supportsDbt && } - {!supportsNormalization && !supportsDbt && ( + {supportsCloudDbtIntegration && } + {noSupportedTransformations && ( From e9bb481b48c39e8d78164b715964b443499cfb59 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Wed, 12 Oct 2022 10:17:08 -0700 Subject: [PATCH 07/30] Extract (and rename) DbtCloudTransformationsCard --- .../ConnectionTransformationTab.module.scss | 30 ------------ .../ConnectionTransformationTab.tsx | 42 +---------------- .../DbtCloudTransformationsCard.module.scss | 46 +++++++++++++++++++ .../DbtCloudTransformationsCard.tsx | 42 +++++++++++++++++ 4 files changed, 90 insertions(+), 70 deletions(-) create mode 100644 airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss create mode 100644 airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.module.scss index ca280dbe2ae1..6903e64a247b 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.module.scss +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.module.scss @@ -11,34 +11,4 @@ display: flex; justify-content: center; align-items: center; - -.cloudTransformationsListContainer { - padding: 25px 25px 22px; - background-color: colors.$grey-50; -} - -.cloudTransformationsListTitle { - display: flex; - justify-content: space-between; -} - -.emptyListContent { - display: flex; - flex-direction: column; - align-items: center; - - > img { - width: 111px; - height: 111px; - margin: 20px 0; - } -} - -.contextExplanation { - color: colors.$grey-300; - width: 100%; - - & a { - color: colors.$grey-300; - } } diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx index b2141f78d635..b0dbdb884398 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx @@ -1,12 +1,8 @@ -import { faPlus } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import classNames from "classnames"; import { Field, FieldArray } from "formik"; import React, { useMemo } from "react"; import { FormattedMessage } from "react-intl"; import { useToggle } from "react-use"; -import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; import { Text } from "components/ui/Text"; @@ -28,7 +24,7 @@ import { } from "views/Connection/ConnectionForm/formConfig"; import { FormCard } from "views/Connection/FormCard"; -import styles from "./ConnectionTransformationTab.module.scss"; +import { DbtCloudTransformationsCard } from "./ConnectionTransformationTab/DbtCloudTransformationsCard"; const CustomTransformationsCard: React.FC<{ operations?: OperationCreate[]; @@ -69,40 +65,6 @@ const CustomTransformationsCard: React.FC<{ ); }; -const CloudTransformationsCard = () => { - // TODO fetch list of transformations for real - const transformations = []; - /* const transformations = [1, 2, 3]; */ - - const TransformationList = ({ className }: { className: string }) => - transformations.length ? ( -
this is a list
- ) : ( -
-
- After an Airbyte sync job has completed, the following jobs will run. -
- An octopus wearing a hard hat, tools at the ready - No transformations -
- ); - - return ( - - Transformations - - - } - > - - - ); -}; - const NormalizationCard: React.FC<{ operations?: OperationRead[]; onSubmit: FormikOnSubmit<{ normalization?: NormalizationType }>; @@ -174,7 +136,7 @@ export const ConnectionTransformationTab: React.FC = () => { > {supportsNormalization && } {supportsDbt && } - {supportsCloudDbtIntegration && } + {supportsCloudDbtIntegration && } {noSupportedTransformations && ( diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss new file mode 100644 index 000000000000..f86a7a3bcd50 --- /dev/null +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss @@ -0,0 +1,46 @@ +@use "scss/colors"; + +.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; + +.cloudTransformationsListContainer { + padding: 25px 25px 22px; + background-color: colors.$grey-50; +} + +.cloudTransformationsListTitle { + display: flex; + justify-content: space-between; +} + +.emptyListContent { + display: flex; + flex-direction: column; + align-items: center; + + > img { + width: 111px; + height: 111px; + margin: 20px 0; + } +} + +.contextExplanation { + color: colors.$grey-300; + width: 100%; + + & a { + color: colors.$grey-300; + } +} diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx new file mode 100644 index 000000000000..7d910b394964 --- /dev/null +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -0,0 +1,42 @@ +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import classNames from "classnames"; + +import { Button } from "components/ui/Button"; +import { Card } from "components/ui/Card"; + +import styles from "./DbtCloudTransformationsCard.module.scss"; + +export const DbtCloudTransformationsCard = () => { + // TODO fetch list of transformations for real + const transformations = []; + /* const transformations = [1, 2, 3]; */ + + const TransformationList = ({ className }: { className: string }) => + transformations.length ? ( +
this is a list
+ ) : ( +
+
+ After an Airbyte sync job has completed, the following jobs will run. +
+ An octopus wearing a hard hat, tools at the ready + No transformations +
+ ); + + return ( + + Transformations + + + } + > + + + ); +}; From 94598fe7b4dc6fe966afa01a1d193a762a10909f Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Wed, 12 Oct 2022 16:43:41 -0700 Subject: [PATCH 08/30] Extract EmptyTransformationList component --- .../DbtCloudTransformationsCard.tsx | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 7d910b394964..5a0c1ae3debb 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -7,8 +7,32 @@ import { Card } from "components/ui/Card"; import styles from "./DbtCloudTransformationsCard.module.scss"; +// This won't be used by the first prototype, but it is the UI specced in +// follow-up designs which can support multiple integrations; it's also a small, +// self-contained set of components and scss which will only trivially affect +// bundle size. +// eslint-disable-next-line: @typescript-eslint/no-usused-vars @typescript-eslint/ban-ts-comment +// @ts-ignore: no unused locals +const EmptyTransformationsList = ({ className }: { className: string }) => ( +
+
+ After an Airbyte sync job has completed, the following jobs will run. +
+ An octopus wearing a hard hat, tools at the ready + No transformations +
+); + export const DbtCloudTransformationsCard = () => { - // TODO fetch list of transformations for real + // Possible render paths: + // 1) IF the workspace has no dbt cloud account linked + // THEN show "go to your settings to connect your dbt Cloud Account" text + // and the "Don't have a dbt account?" hero/media element + // 2) IF the workspace has a dbt cloud account linked... + // 2.1) AND the connection has no saved dbt jobs (cf: operations) + // THEN show empty jobs list and the "+ Add transformation" button + // 2.2) AND the connection has saved dbt jobs + // THEN show the "no jobs" card body and the "+ Add transformation" button const transformations = []; /* const transformations = [1, 2, 3]; */ @@ -16,13 +40,7 @@ export const DbtCloudTransformationsCard = () => { transformations.length ? (
this is a list
) : ( -
-
- After an Airbyte sync job has completed, the following jobs will run. -
- An octopus wearing a hard hat, tools at the ready - No transformations -
+ ); return ( From 8377b6ac27d44fa763d61f56b827aeca794bc185 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Wed, 12 Oct 2022 18:13:25 -0700 Subject: [PATCH 09/30] List transformations if any, "no integration" UI This still uses some hardcoded conditions instead of anything resembling actual data --- .../DbtCloudTransformationsCard.module.scss | 6 ++ .../DbtCloudTransformationsCard.tsx | 80 +++++++++++++++---- 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss index f86a7a3bcd50..bb16967ef25b 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss @@ -44,3 +44,9 @@ color: colors.$grey-300; } } + +.transformationListItem { + margin-top: 10px; + padding: 18px; + width: 100%; +} diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 5a0c1ae3debb..a75b5a49cce3 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -1,27 +1,68 @@ import { faPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; +import { Link } from "react-router-dom"; import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; +import { useCurrentWorkspace } from "hooks/services/useWorkspace"; +import { RoutePaths } from "pages/routePaths"; + import styles from "./DbtCloudTransformationsCard.module.scss"; +type Transformation = { project: string; job: string }; + // This won't be used by the first prototype, but it is the UI specced in // follow-up designs which can support multiple integrations; it's also a small, // self-contained set of components and scss which will only trivially affect // bundle size. // eslint-disable-next-line: @typescript-eslint/no-usused-vars @typescript-eslint/ban-ts-comment // @ts-ignore: no unused locals -const EmptyTransformationsList = ({ className }: { className: string }) => ( -
-
- After an Airbyte sync job has completed, the following jobs will run. +const TransformationsList = ({ + className, + transformations, +}: { + className?: string; + transformations: Transformation[]; +}) => { + return ( +
+

After an Airbyte sync job has completed, the following jobs will run

+ {transformations.length ? ( + <> + {transformations.map((t, i) => ( + + ))} + + ) : ( + <> + An octopus wearing a hard hat, tools at the ready + No transformations + + )}
- An octopus wearing a hard hat, tools at the ready - No transformations -
-); + ); +}; + +const TransformationListItem = (transformation: Transformation) => { + return {JSON.stringify(transformation)}; +}; + +const NoDbtIntegration = ({ className, workspaceId }: { className: string; workspaceId: string }) => { + const dbtSettingsPath = `/${RoutePaths.Workspaces}/${workspaceId}/${RoutePaths.Settings}/dbt-cloud`; + return ( +
+

After an Airbyte sync job has completed, the following jobs will run

+

+ Go to your settings to connect your dbt Cloud account +

+ +
+ ); +}; + +const DbtCloudSignupBanner = () =>
; export const DbtCloudTransformationsCard = () => { // Possible render paths: @@ -33,15 +74,16 @@ export const DbtCloudTransformationsCard = () => { // THEN show empty jobs list and the "+ Add transformation" button // 2.2) AND the connection has saved dbt jobs // THEN show the "no jobs" card body and the "+ Add transformation" button - const transformations = []; - /* const transformations = [1, 2, 3]; */ + /* const transformations: Transformation[] = []; */ + const transformations: Transformation[] = [ + { project: "Project #1", job: "Job #1" }, + { project: "Project #1", job: "Job #2" }, + { project: "Project #1", job: "Job #3" }, + ]; - const TransformationList = ({ className }: { className: string }) => - transformations.length ? ( -
this is a list
- ) : ( - - ); + const { workspaceId } = useCurrentWorkspace(); + const hasDbtIntegration = true; + /* const hasDbtIntegration = false; */ return ( { } > - + {hasDbtIntegration ? ( + + ) : ( + + )} ); }; From c4a2f9ea4a02b3c41a5a1b2aeb3dea4f7419d5fb Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Thu, 13 Oct 2022 02:13:15 -0700 Subject: [PATCH 10/30] Initial UI for cloud transform jobs --- .../public/images/external/dbt-bit_tm.png | Bin 0 -> 4022 bytes .../DbtCloudTransformationsCard.module.scss | 33 ++++++++++++++++++ .../DbtCloudTransformationsCard.tsx | 33 +++++++++++++++--- 3 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 airbyte-webapp/public/images/external/dbt-bit_tm.png diff --git a/airbyte-webapp/public/images/external/dbt-bit_tm.png b/airbyte-webapp/public/images/external/dbt-bit_tm.png new file mode 100644 index 0000000000000000000000000000000000000000..b20774dced5ac5e7b3e7d5e502afab2472702648 GIT binary patch literal 4022 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 { - return {JSON.stringify(transformation)}; +const TransformationListItem = ({ transformation }: { transformation: Transformation }) => { + return ( + +
+ dbt logo + dbt Cloud transform +
+
+
+ +
+
+ +
+ +
+
+ ); }; const NoDbtIntegration = ({ className, workspaceId }: { className: string; workspaceId: string }) => { From 2286e36938962750e40ce123de087212f945d9b6 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Thu, 13 Oct 2022 10:14:00 -0700 Subject: [PATCH 11/30] Use formik-backed inputs for job list data fields --- .../DbtCloudTransformationsCard.module.scss | 14 +++++ .../DbtCloudTransformationsCard.tsx | 51 ++++++++++++++----- 2 files changed, 53 insertions(+), 12 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss index 9403bd880869..edd902d6c833 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss @@ -24,6 +24,10 @@ justify-content: space-between; } +.transformationListForm { + width: 100%; +} + .emptyListContent { display: flex; flex-direction: column; @@ -45,6 +49,16 @@ } } +.transformationListButtonGroup { + display: flex; + justify-content: flex-end; + margin-top: 20px; +} + +.transformationListButton { + margin-left: 10px; +} + .transformationListItem { margin-top: 10px; padding: 18px; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 13aa0edbb311..83ee98007662 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -1,6 +1,7 @@ import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; +import { Field, Form, Formik, FieldProps } from "formik"; import { Link } from "react-router-dom"; import { Button } from "components/ui/Button"; @@ -30,15 +31,34 @@ const TransformationsList = ({ className?: string; transformations: Transformation[]; }) => { + const onSubmit = (fields: { transformations: Transformation[] }) => { + console.info(`Saving with this job list: ${JSON.stringify(fields)}`); + }; return (

After an Airbyte sync job has completed, the following jobs will run

{transformations.length ? ( - <> - {transformations.map((t, i) => ( - - ))} - + +
+ {transformations.map((_t, i) => ( + { + console.info(`deleting transformation #${i}`); + }} + /> + ))} +
+ + +
+ +
) : ( <> An octopus wearing a hard hat, tools at the ready @@ -49,7 +69,13 @@ const TransformationsList = ({ ); }; -const TransformationListItem = ({ transformation }: { transformation: Transformation }) => { +const TransformationListItem = ({ + transformationIndex, + deleteTransformation, +}: { + transformationIndex: number; + deleteTransformation: () => void; +}) => { return (
@@ -58,15 +84,16 @@ const TransformationListItem = ({ transformation }: { transformation: Transforma
- + + {({ field }: FieldProps) => } +
- + + {({ field }: FieldProps) => } +
-
From 29ca6abb1bf8a0752fb390af12ac645f02715cda Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Fri, 14 Oct 2022 09:14:39 -0700 Subject: [PATCH 12/30] Improve job list management with FieldArray et al --- .../DbtCloudTransformationsCard.tsx | 54 +++++++++++-------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 83ee98007662..e59df7eab09a 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -1,7 +1,7 @@ import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; -import { Field, Form, Formik, FieldProps } from "formik"; +import { Field, Form, Formik, FieldArray, FieldProps } from "formik"; import { Link } from "react-router-dom"; import { Button } from "components/ui/Button"; @@ -34,31 +34,41 @@ const TransformationsList = ({ const onSubmit = (fields: { transformations: Transformation[] }) => { console.info(`Saving with this job list: ${JSON.stringify(fields)}`); }; + + const transformationKey = (t: Transformation, i: number) => `${i}:${t.project}/${t.job}`; + return (

After an Airbyte sync job has completed, the following jobs will run

{transformations.length ? ( - -
- {transformations.map((_t, i) => ( - { - console.info(`deleting transformation #${i}`); - }} + ( + + + values.transformations.map((t, i) => ( + arrayHelpers.remove(i)} + /> + )) + } /> - ))} -
- - -
- -
+
+ + +
+ + )} + /> ) : ( <> An octopus wearing a hard hat, tools at the ready @@ -93,7 +103,7 @@ const TransformationListItem = ({ {({ field }: FieldProps) => }
-
From 7c5fa8516523dc1edd5da657a1fce354777b575a Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Sun, 16 Oct 2022 17:00:33 -0700 Subject: [PATCH 13/30] WIP: build payload to save job data as operations There's some key data missing and it's not currently wired up --- .../DbtCloudTransformationsCard.tsx | 137 ++++++++++++------ 1 file changed, 90 insertions(+), 47 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index e59df7eab09a..6a38ec6316e1 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -2,28 +2,113 @@ import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; import { Field, Form, Formik, FieldArray, FieldProps } from "formik"; +import isEmpty from "lodash/isEmpty"; import { Link } from "react-router-dom"; import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; import { Input } from "components/ui/Input"; +import { + /* webBackendUpdateConnection, */ + WebBackendConnectionUpdate, + OperatorType, + /* OperatorWebhook, */ +} from "core/request/AirbyteClient"; +// eslint-disable-next-line import { useCurrentWorkspace } from "hooks/services/useWorkspace"; + import { RoutePaths } from "pages/routePaths"; import styles from "./DbtCloudTransformationsCard.module.scss"; +// TODO rename project->account interface Transformation { project: string; job: string; } +const _transformations: Transformation[] = [ + { project: "1", job: "1234" }, + { project: "2", job: "2134" }, + { project: "3", job: "3214" }, +]; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const updateConnection = (obj: WebBackendConnectionUpdate) => console.info(`updating with`, obj); +// without including the index, duplicate data causes annoying render bugs for the list +const transformationKey = (t: Transformation, i: number) => `${i}:${t.project}/${t.job}`; + +const useSaveJobsFn = () => { + const { workspaceId } = useCurrentWorkspace(); + + const dbtCloudDomain = "https://cloud.getdbt.com"; + const urlForJob = (job: Transformation) => `${dbtCloudDomain}/api/v2/accounts/${job.project}/jobs/${job.job}`; + const executionBody = `{"cause": "airbyte"}`; + // TODO dynamically use the workspace's configured dbt cloud domain + const webhookConfigName = "dbt cloud"; + + return (jobs: Transformation[]) => + // TODO query and add the actual connectionId and operationId values + updateConnection({ + connectionId: workspaceId, // lmao I know, right? + operations: [ + // TODO include all non-dbt-cloud operations in the payload + ...jobs.map((job, index) => ({ + workspaceId, + name: transformationKey(job, index), + // TODO add `operationId` if present + operatorConfiguration: { + operatorType: OperatorType.webhook, + webhook: { + executionUrl: urlForJob(job), + webhookConfigName, + executionBody, + }, + }, + })), + ], + }); +}; + +export const DbtCloudTransformationsCard = () => { + // Possible render paths: + // 1) IF the workspace has no dbt cloud account linked + // THEN show "go to your settings to connect your dbt Cloud Account" text + // and the "Don't have a dbt account?" hero/media element + // 2) IF the workspace has a dbt cloud account linked... + // 2.1) AND the connection has no saved dbt jobs (cf: operations) + // THEN show empty jobs list and the "+ Add transformation" button + // 2.2) AND the connection has saved dbt jobs + // THEN show the "no jobs" card body and the "+ Add transformation" button + /* const transformations: Transformation[] = []; */ + + const workspace = useCurrentWorkspace(); + const hasDbtIntegration = !isEmpty(workspace.webhookConfigs); + + return ( + + Transformations + + + } + > + {hasDbtIntegration ? ( + + ) : ( + + )} + + ); +}; // This won't be used by the first prototype, but it is the UI specced in // follow-up designs which can support multiple integrations; it's also a small, // self-contained set of components and scss which will only trivially affect // bundle size. -// eslint-disable-next-line: @typescript-eslint/no-usused-vars @typescript-eslint/ban-ts-comment -// @ts-ignore: no unused locals const TransformationsList = ({ className, transformations, @@ -31,12 +116,11 @@ const TransformationsList = ({ className?: string; transformations: Transformation[]; }) => { - const onSubmit = (fields: { transformations: Transformation[] }) => { - console.info(`Saving with this job list: ${JSON.stringify(fields)}`); + const saveJobs = useSaveJobsFn(); + const onSubmit = ({ transformations }: { transformations: Transformation[] }) => { + saveJobs(transformations); }; - const transformationKey = (t: Transformation, i: number) => `${i}:${t.project}/${t.job}`; - return (

After an Airbyte sync job has completed, the following jobs will run

@@ -125,44 +209,3 @@ const NoDbtIntegration = ({ className, workspaceId }: { className: string; works }; const DbtCloudSignupBanner = () =>
; - -export const DbtCloudTransformationsCard = () => { - // Possible render paths: - // 1) IF the workspace has no dbt cloud account linked - // THEN show "go to your settings to connect your dbt Cloud Account" text - // and the "Don't have a dbt account?" hero/media element - // 2) IF the workspace has a dbt cloud account linked... - // 2.1) AND the connection has no saved dbt jobs (cf: operations) - // THEN show empty jobs list and the "+ Add transformation" button - // 2.2) AND the connection has saved dbt jobs - // THEN show the "no jobs" card body and the "+ Add transformation" button - /* const transformations: Transformation[] = []; */ - const transformations: Transformation[] = [ - { project: "Project #1", job: "Job #1" }, - { project: "Project #1", job: "Job #2" }, - { project: "Project #1", job: "Job #3" }, - ]; - - const { workspaceId } = useCurrentWorkspace(); - const hasDbtIntegration = true; - /* const hasDbtIntegration = false; */ - - return ( - - Transformations - - - } - > - {hasDbtIntegration ? ( - - ) : ( - - )} - - ); -}; From 5f946f6360e5381ede14620b42bdae98c32f30d0 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Sun, 16 Oct 2022 22:14:49 -0700 Subject: [PATCH 14/30] Start pulling dbt cloud business logic to its own module --- airbyte-webapp/src/packages/cloud/services/dbtCloud.ts | 8 ++++++++ .../DbtCloudTransformationsCard.tsx | 7 ++----- 2 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 airbyte-webapp/src/packages/cloud/services/dbtCloud.ts diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts new file mode 100644 index 000000000000..d04d5d1ebf23 --- /dev/null +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -0,0 +1,8 @@ +// This module is for the business logic of working with dbt Cloud webhooks. +// Static config data, urls, functions which wrangle the APIs to manipulate +// records in ways suited to the UI user workflows--all the implementation +// details of working with dbtCloud jobs as webhook operations, all goes here. +// The presentation logic and orchestration in the UI all goes elsewhere. + +export const webhookConfigName = "dbt cloud"; +export const executionBody = `{"cause": "airbyte"}`; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 6a38ec6316e1..391678c42a30 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -15,9 +15,8 @@ import { OperatorType, /* OperatorWebhook, */ } from "core/request/AirbyteClient"; -// eslint-disable-next-line import { useCurrentWorkspace } from "hooks/services/useWorkspace"; - +import { webhookConfigName, executionBody } from "packages/cloud/services/dbtCloud"; import { RoutePaths } from "pages/routePaths"; import styles from "./DbtCloudTransformationsCard.module.scss"; @@ -41,11 +40,9 @@ const transformationKey = (t: Transformation, i: number) => `${i}:${t.project}/$ const useSaveJobsFn = () => { const { workspaceId } = useCurrentWorkspace(); + // TODO dynamically use the workspace's configured dbt cloud domain const dbtCloudDomain = "https://cloud.getdbt.com"; const urlForJob = (job: Transformation) => `${dbtCloudDomain}/api/v2/accounts/${job.project}/jobs/${job.job}`; - const executionBody = `{"cause": "airbyte"}`; - // TODO dynamically use the workspace's configured dbt cloud domain - const webhookConfigName = "dbt cloud"; return (jobs: Transformation[]) => // TODO query and add the actual connectionId and operationId values From a9484092bd73522d4c9f28395e2fc3a5a072318b Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Sun, 16 Oct 2022 22:49:25 -0700 Subject: [PATCH 15/30] Renaming pass (s/transformation/job/g) --- .../DbtCloudTransformationsCard.module.scss | 20 ++--- .../DbtCloudTransformationsCard.tsx | 88 +++++++------------ 2 files changed, 44 insertions(+), 64 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss index edd902d6c833..fa166032aa0f 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss @@ -14,17 +14,17 @@ justify-content: center; align-items: center; -.cloudTransformationsListContainer { +.jobListContainer { padding: 25px 25px 22px; background-color: colors.$grey-50; } -.cloudTransformationsListTitle { +.jobListTitle { display: flex; justify-content: space-between; } -.transformationListForm { +.jobListForm { width: 100%; } @@ -49,17 +49,17 @@ } } -.transformationListButtonGroup { +.jobListButtonGroup { display: flex; justify-content: flex-end; margin-top: 20px; } -.transformationListButton { +.jobListButton { margin-left: 10px; } -.transformationListItem { +.jobListItem { margin-top: 10px; padding: 18px; width: 100%; @@ -73,23 +73,23 @@ } } -.transformationListItemIntegrationName { +.jobListItemIntegrationName { display: flex; align-items: center; } -.transformationListItemInputGroup { +.jobListItemInputGroup { display: flex; justify-content: space-between; align-items: center; } -.transformationListItemInput { +.jobListItemInput { height: fit-content; margin-left: 1em; } -.transformationListItemDelete { +.jobListItemDelete { color: colors.$grey-200; font-size: large; margin: 0 1em; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 391678c42a30..01e3e7041442 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -21,30 +21,30 @@ import { RoutePaths } from "pages/routePaths"; import styles from "./DbtCloudTransformationsCard.module.scss"; -// TODO rename project->account -interface Transformation { +interface DbtCloudJob { + // TODO rename project->account project: string; job: string; } -const _transformations: Transformation[] = [ +const _jobs: DbtCloudJob[] = [ { project: "1", job: "1234" }, { project: "2", job: "2134" }, { project: "3", job: "3214" }, ]; +/* const _jobs: DbtCloudJob[] = []; */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any const updateConnection = (obj: WebBackendConnectionUpdate) => console.info(`updating with`, obj); // without including the index, duplicate data causes annoying render bugs for the list -const transformationKey = (t: Transformation, i: number) => `${i}:${t.project}/${t.job}`; +const jobKey = (t: DbtCloudJob, i: number) => `${i}:${t.project}/${t.job}`; const useSaveJobsFn = () => { const { workspaceId } = useCurrentWorkspace(); // TODO dynamically use the workspace's configured dbt cloud domain const dbtCloudDomain = "https://cloud.getdbt.com"; - const urlForJob = (job: Transformation) => `${dbtCloudDomain}/api/v2/accounts/${job.project}/jobs/${job.job}`; + const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.project}/jobs/${job.job}`; - return (jobs: Transformation[]) => + return (jobs: DbtCloudJob[]) => // TODO query and add the actual connectionId and operationId values updateConnection({ connectionId: workspaceId, // lmao I know, right? @@ -52,7 +52,7 @@ const useSaveJobsFn = () => { // TODO include all non-dbt-cloud operations in the payload ...jobs.map((job, index) => ({ workspaceId, - name: transformationKey(job, index), + name: jobKey(job, index), // TODO add `operationId` if present operatorConfiguration: { operatorType: OperatorType.webhook, @@ -77,7 +77,6 @@ export const DbtCloudTransformationsCard = () => { // THEN show empty jobs list and the "+ Add transformation" button // 2.2) AND the connection has saved dbt jobs // THEN show the "no jobs" card body and the "+ Add transformation" button - /* const transformations: Transformation[] = []; */ const workspace = useCurrentWorkspace(); const hasDbtIntegration = !isEmpty(workspace.webhookConfigs); @@ -85,7 +84,7 @@ export const DbtCloudTransformationsCard = () => { return ( + Transformations -
@@ -160,31 +145,25 @@ const TransformationsList = ({ ); }; -const TransformationListItem = ({ - transformationIndex, - deleteTransformation, -}: { - transformationIndex: number; - deleteTransformation: () => void; -}) => { +const JobsListItem = ({ jobIndex, removeJob }: { jobIndex: number; removeJob: () => void }) => { return ( - -
+ +
dbt logo dbt Cloud transform
-
-
- +
+
+ {({ field }: FieldProps) => }
-
- +
+ {({ field }: FieldProps) => }
-
@@ -192,7 +171,8 @@ const TransformationListItem = ({ ); }; -const NoDbtIntegration = ({ className, workspaceId }: { className: string; workspaceId: string }) => { +const NoDbtIntegration = ({ className }: { className: string }) => { + const { workspaceId } = useCurrentWorkspace(); const dbtSettingsPath = `/${RoutePaths.Workspaces}/${workspaceId}/${RoutePaths.Settings}/dbt-cloud`; return (
From fcfc4d4c5a649841bbe9c8e1daf2c4bead2ca377 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Mon, 17 Oct 2022 00:14:08 -0700 Subject: [PATCH 16/30] Move more logic into dbt service module --- .../src/packages/cloud/services/dbtCloud.ts | 59 ++++++++++++++++++- .../DbtCloudTransformationsCard.tsx | 47 +-------------- 2 files changed, 59 insertions(+), 47 deletions(-) diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index d04d5d1ebf23..88ac57609113 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -4,5 +4,60 @@ // details of working with dbtCloud jobs as webhook operations, all goes here. // The presentation logic and orchestration in the UI all goes elsewhere. -export const webhookConfigName = "dbt cloud"; -export const executionBody = `{"cause": "airbyte"}`; +import isEmpty from "lodash/isEmpty"; + +import { + /* webBackendUpdateConnection, */ + WebBackendConnectionUpdate, + OperatorType, +} from "core/request/AirbyteClient"; +import { useCurrentWorkspace } from "hooks/services/useWorkspace"; + +export interface DbtCloudJob { + // TODO rename project->account + project: string; + job: string; +} + +const webhookConfigName = "dbt cloud"; +const executionBody = `{"cause": "airbyte"}`; +const jobName = (t: DbtCloudJob) => `${t.project}/${t.job}`; + +const updateConnection = (obj: WebBackendConnectionUpdate) => console.info(`updating with`, obj); + +// saves jobs for the current connection +export const useSaveJobsFn = () => { + const { workspaceId } = useCurrentWorkspace(); + + // TODO dynamically use the workspace's configured dbt cloud domain + const dbtCloudDomain = "https://cloud.getdbt.com"; + const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.project}/jobs/${job.job}`; + + return (jobs: DbtCloudJob[]) => + // TODO query and add the actual connectionId and operationId values + updateConnection({ + connectionId: workspaceId, // lmao I know, right? + operations: [ + // TODO include all non-dbt-cloud operations in the payload + ...jobs.map((job) => ({ + workspaceId, + name: jobName(job), + // TODO add `operationId` if present + operatorConfiguration: { + operatorType: OperatorType.webhook, + webhook: { + executionUrl: urlForJob(job), + webhookConfigName, + executionBody, + }, + }, + })), + ], + }); +}; + +export const useDbtIntegration = () => { + const workspace = useCurrentWorkspace(); + + return { hasDbtIntegration: !isEmpty(workspace.webhookConfigs) }; +}; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 01e3e7041442..b324fab3f8aa 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -2,30 +2,18 @@ import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; import { Field, Form, Formik, FieldArray, FieldProps } from "formik"; -import isEmpty from "lodash/isEmpty"; import { Link } from "react-router-dom"; import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; import { Input } from "components/ui/Input"; -import { - /* webBackendUpdateConnection, */ - WebBackendConnectionUpdate, - OperatorType, - /* OperatorWebhook, */ -} from "core/request/AirbyteClient"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; -import { webhookConfigName, executionBody } from "packages/cloud/services/dbtCloud"; +import { useSaveJobsFn, DbtCloudJob, useDbtIntegration } from "packages/cloud/services/dbtCloud"; import { RoutePaths } from "pages/routePaths"; import styles from "./DbtCloudTransformationsCard.module.scss"; -interface DbtCloudJob { - // TODO rename project->account - project: string; - job: string; -} const _jobs: DbtCloudJob[] = [ { project: "1", job: "1234" }, { project: "2", job: "2134" }, @@ -33,40 +21,9 @@ const _jobs: DbtCloudJob[] = [ ]; /* const _jobs: DbtCloudJob[] = []; */ -const updateConnection = (obj: WebBackendConnectionUpdate) => console.info(`updating with`, obj); // without including the index, duplicate data causes annoying render bugs for the list const jobKey = (t: DbtCloudJob, i: number) => `${i}:${t.project}/${t.job}`; -const useSaveJobsFn = () => { - const { workspaceId } = useCurrentWorkspace(); - - // TODO dynamically use the workspace's configured dbt cloud domain - const dbtCloudDomain = "https://cloud.getdbt.com"; - const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.project}/jobs/${job.job}`; - - return (jobs: DbtCloudJob[]) => - // TODO query and add the actual connectionId and operationId values - updateConnection({ - connectionId: workspaceId, // lmao I know, right? - operations: [ - // TODO include all non-dbt-cloud operations in the payload - ...jobs.map((job, index) => ({ - workspaceId, - name: jobKey(job, index), - // TODO add `operationId` if present - operatorConfiguration: { - operatorType: OperatorType.webhook, - webhook: { - executionUrl: urlForJob(job), - webhookConfigName, - executionBody, - }, - }, - })), - ], - }); -}; - export const DbtCloudTransformationsCard = () => { // Possible render paths: // 1) IF the workspace has no dbt cloud account linked @@ -79,7 +36,7 @@ export const DbtCloudTransformationsCard = () => { // THEN show the "no jobs" card body and the "+ Add transformation" button const workspace = useCurrentWorkspace(); - const hasDbtIntegration = !isEmpty(workspace.webhookConfigs); + const { hasDbtIntegration } = useDbtIntegration(); return ( Date: Mon, 17 Oct 2022 00:32:40 -0700 Subject: [PATCH 17/30] Renaming pass (s/project/account/) --- .../src/packages/cloud/services/dbtCloud.ts | 6 +++--- .../DbtCloudTransformationsCard.tsx | 13 ++++++------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index 88ac57609113..53332beea0f8 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -15,13 +15,13 @@ import { useCurrentWorkspace } from "hooks/services/useWorkspace"; export interface DbtCloudJob { // TODO rename project->account - project: string; + account: string; job: string; } const webhookConfigName = "dbt cloud"; const executionBody = `{"cause": "airbyte"}`; -const jobName = (t: DbtCloudJob) => `${t.project}/${t.job}`; +const jobName = (t: DbtCloudJob) => `${t.account}/${t.job}`; const updateConnection = (obj: WebBackendConnectionUpdate) => console.info(`updating with`, obj); @@ -31,7 +31,7 @@ export const useSaveJobsFn = () => { // TODO dynamically use the workspace's configured dbt cloud domain const dbtCloudDomain = "https://cloud.getdbt.com"; - const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.project}/jobs/${job.job}`; + const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}`; return (jobs: DbtCloudJob[]) => // TODO query and add the actual connectionId and operationId values diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index b324fab3f8aa..e1aeeb5d146c 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -15,14 +15,14 @@ import { RoutePaths } from "pages/routePaths"; import styles from "./DbtCloudTransformationsCard.module.scss"; const _jobs: DbtCloudJob[] = [ - { project: "1", job: "1234" }, - { project: "2", job: "2134" }, - { project: "3", job: "3214" }, + { account: "1", job: "1234" }, + { account: "2", job: "2134" }, + { account: "3", job: "3214" }, ]; /* const _jobs: DbtCloudJob[] = []; */ // without including the index, duplicate data causes annoying render bugs for the list -const jobKey = (t: DbtCloudJob, i: number) => `${i}:${t.project}/${t.job}`; +const jobKey = (t: DbtCloudJob, i: number) => `${i}:${t.account}/${t.job}`; export const DbtCloudTransformationsCard = () => { // Possible render paths: @@ -35,7 +35,6 @@ export const DbtCloudTransformationsCard = () => { // 2.2) AND the connection has saved dbt jobs // THEN show the "no jobs" card body and the "+ Add transformation" button - const workspace = useCurrentWorkspace(); const { hasDbtIntegration } = useDbtIntegration(); return ( @@ -111,8 +110,8 @@ const JobsListItem = ({ jobIndex, removeJob }: { jobIndex: number; removeJob: ()
- - {({ field }: FieldProps) => } + + {({ field }: FieldProps) => }
From d2b2d9a81e7f281806977ee45dff0469e103c939 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Mon, 17 Oct 2022 02:17:05 -0700 Subject: [PATCH 18/30] Improve useDbtIntegration hook --- .../src/packages/cloud/services/dbtCloud.ts | 68 ++++++++++++++----- .../ConnectionTransformationTab.tsx | 3 +- .../DbtCloudTransformationsCard.module.scss | 14 ---- .../DbtCloudTransformationsCard.tsx | 20 ++++-- 4 files changed, 67 insertions(+), 38 deletions(-) diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index 53332beea0f8..4450316f5b2e 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -3,6 +3,10 @@ // records in ways suited to the UI user workflows--all the implementation // details of working with dbtCloud jobs as webhook operations, all goes here. // The presentation logic and orchestration in the UI all goes elsewhere. +// +// About that business logic: +// - for now, the code treats "webhook operations" and "dbt Cloud job" as synonymous. +// - custom domains aren't yet supported import isEmpty from "lodash/isEmpty"; @@ -10,6 +14,9 @@ import { /* webBackendUpdateConnection, */ WebBackendConnectionUpdate, OperatorType, + WebBackendConnectionRead, + OperationRead, + WorkspaceUpdate, } from "core/request/AirbyteClient"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; @@ -17,32 +24,58 @@ export interface DbtCloudJob { // TODO rename project->account account: string; job: string; + operationId?: string; } const webhookConfigName = "dbt cloud"; const executionBody = `{"cause": "airbyte"}`; const jobName = (t: DbtCloudJob) => `${t.account}/${t.job}`; -const updateConnection = (obj: WebBackendConnectionUpdate) => console.info(`updating with`, obj); +const updateConnection = (obj: WebBackendConnectionUpdate) => console.info(`updating connection with`, obj); +const updateWorkspace = (obj: WorkspaceUpdate) => console.info(`updating workspace with`, obj); +const toDbtCloudJob = (operation: OperationRead): DbtCloudJob => { + const { operationId } = operation; + const { executionUrl } = operation.operatorConfiguration.webhook || {}; -// saves jobs for the current connection -export const useSaveJobsFn = () => { - const { workspaceId } = useCurrentWorkspace(); + const matches = executionUrl?.match(/\/accounts\/([^/]+)\/jobs\/([^]+)\/run/); - // TODO dynamically use the workspace's configured dbt cloud domain - const dbtCloudDomain = "https://cloud.getdbt.com"; - const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}`; + if (!matches) { + throw new Error(`Cannot extract dbt cloud job params from executionUrl ${executionUrl}`); + } else { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_fullUrl, account, job] = matches; + + return { + account, + job, + operationId, + }; + } +}; +const isDbtCloudJob = (operation: OperationRead): boolean => + operation.operatorConfiguration.operatorType === OperatorType.webhook; + +export const useDbtIntegration = (connection: WebBackendConnectionRead) => { + const workspace = useCurrentWorkspace(); + const { workspaceId } = workspace; + const hasDbtIntegration = !isEmpty(workspace.webhookConfigs); + const dbtCloudJobs = [...(connection.operations?.filter((operation) => isDbtCloudJob(operation)) || [])].map( + toDbtCloudJob + ); + const otherOperations = [...(connection.operations?.filter((operation) => !isDbtCloudJob(operation)) || [])]; + const saveJobs = (jobs: DbtCloudJob[]) => { + // TODO dynamically use the workspace's configured dbt cloud domain + const dbtCloudDomain = "https://cloud.getdbt.com"; + const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}`; - return (jobs: DbtCloudJob[]) => - // TODO query and add the actual connectionId and operationId values updateConnection({ - connectionId: workspaceId, // lmao I know, right? + connectionId: connection.connectionId, operations: [ - // TODO include all non-dbt-cloud operations in the payload + ...otherOperations, ...jobs.map((job) => ({ workspaceId, + operationId: job.operationId, name: jobName(job), - // TODO add `operationId` if present operatorConfiguration: { operatorType: OperatorType.webhook, webhook: { @@ -54,10 +87,11 @@ export const useSaveJobsFn = () => { })), ], }); -}; - -export const useDbtIntegration = () => { - const workspace = useCurrentWorkspace(); + }; - return { hasDbtIntegration: !isEmpty(workspace.webhookConfigs) }; + return { + hasDbtIntegration, + dbtCloudJobs, + saveJobs, + }; }; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx index b0dbdb884398..bbb6b9983d81 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab.tsx @@ -24,6 +24,7 @@ import { } from "views/Connection/ConnectionForm/formConfig"; import { FormCard } from "views/Connection/FormCard"; +import styles from "./ConnectionTransformationTab.module.scss"; import { DbtCloudTransformationsCard } from "./ConnectionTransformationTab/DbtCloudTransformationsCard"; const CustomTransformationsCard: React.FC<{ @@ -136,7 +137,7 @@ export const ConnectionTransformationTab: React.FC = () => { > {supportsNormalization && } {supportsDbt && } - {supportsCloudDbtIntegration && } + {supportsCloudDbtIntegration && } {noSupportedTransformations && ( diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss index fa166032aa0f..a4f0d6b94dca 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss @@ -1,19 +1,5 @@ @use "scss/colors"; -.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; - .jobListContainer { padding: 25px 25px 22px; background-color: colors.$grey-50; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index e1aeeb5d146c..7555fcb1f8b2 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -8,8 +8,9 @@ import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; import { Input } from "components/ui/Input"; +import { WebBackendConnectionRead } from "core/request/AirbyteClient"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; -import { useSaveJobsFn, DbtCloudJob, useDbtIntegration } from "packages/cloud/services/dbtCloud"; +import { DbtCloudJob, useDbtIntegration } from "packages/cloud/services/dbtCloud"; import { RoutePaths } from "pages/routePaths"; import styles from "./DbtCloudTransformationsCard.module.scss"; @@ -24,7 +25,7 @@ const _jobs: DbtCloudJob[] = [ // without including the index, duplicate data causes annoying render bugs for the list const jobKey = (t: DbtCloudJob, i: number) => `${i}:${t.account}/${t.job}`; -export const DbtCloudTransformationsCard = () => { +export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBackendConnectionRead }) => { // Possible render paths: // 1) IF the workspace has no dbt cloud account linked // THEN show "go to your settings to connect your dbt Cloud Account" text @@ -35,7 +36,7 @@ export const DbtCloudTransformationsCard = () => { // 2.2) AND the connection has saved dbt jobs // THEN show the "no jobs" card body and the "+ Add transformation" button - const { hasDbtIntegration } = useDbtIntegration(); + const { hasDbtIntegration, saveJobs } = useDbtIntegration(connection); return ( { } > {hasDbtIntegration ? ( - + ) : ( )} @@ -57,8 +58,15 @@ export const DbtCloudTransformationsCard = () => { ); }; -const DbtJobsList = ({ className, jobs }: { className?: string; jobs: DbtCloudJob[] }) => { - const saveJobs = useSaveJobsFn(); +const DbtJobsList = ({ + className, + jobs, + saveJobs, +}: { + className?: string; + jobs: DbtCloudJob[]; + saveJobs: (jobs: DbtCloudJob[]) => void; +}) => { const onSubmit = ({ jobs }: { jobs: DbtCloudJob[] }) => { saveJobs(jobs); }; From 8c05d7298f6117864f15305f967e0e56ccbe5ace Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Mon, 17 Oct 2022 11:42:02 -0700 Subject: [PATCH 19/30] Add skeleton of updateWorkspace fn --- airbyte-webapp/src/packages/cloud/services/dbtCloud.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index 4450316f5b2e..795145a50f76 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -16,7 +16,7 @@ import { OperatorType, WebBackendConnectionRead, OperationRead, - WorkspaceUpdate, + // WorkspaceUpdate, } from "core/request/AirbyteClient"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; @@ -32,7 +32,7 @@ const executionBody = `{"cause": "airbyte"}`; const jobName = (t: DbtCloudJob) => `${t.account}/${t.job}`; const updateConnection = (obj: WebBackendConnectionUpdate) => console.info(`updating connection with`, obj); -const updateWorkspace = (obj: WorkspaceUpdate) => console.info(`updating workspace with`, obj); +// const updateWorkspace = (obj: WorkspaceUpdate) => console.info(`updating workspace with`, obj); const toDbtCloudJob = (operation: OperationRead): DbtCloudJob => { const { operationId } = operation; const { executionUrl } = operation.operatorConfiguration.webhook || {}; @@ -55,6 +55,10 @@ const toDbtCloudJob = (operation: OperationRead): DbtCloudJob => { const isDbtCloudJob = (operation: OperationRead): boolean => operation.operatorConfiguration.operatorType === OperatorType.webhook; +// export const configureDbtIntegration = (authToken: string, singleTenantUrl?: string) => { +// updateWorkspace(...) +// } + export const useDbtIntegration = (connection: WebBackendConnectionRead) => { const workspace = useCurrentWorkspace(); const { workspaceId } = workspace; From 10ee7658f302d05662e9442b3b5c17b41ae67832 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Mon, 17 Oct 2022 18:13:15 -0700 Subject: [PATCH 20/30] Connect pages to actual backend (no new jobs tho) --- .../src/packages/cloud/services/dbtCloud.ts | 57 ++++++----- .../integrations/DbtCloudSettingsView.tsx | 96 ++++++++++--------- .../DbtCloudTransformationsCard.tsx | 14 +-- 3 files changed, 93 insertions(+), 74 deletions(-) diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index 795145a50f76..00a7f4223227 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -10,34 +10,27 @@ import isEmpty from "lodash/isEmpty"; -import { - /* webBackendUpdateConnection, */ - WebBackendConnectionUpdate, - OperatorType, - WebBackendConnectionRead, - OperationRead, - // WorkspaceUpdate, -} from "core/request/AirbyteClient"; +import { OperatorType, WebBackendConnectionRead, OperationRead } from "core/request/AirbyteClient"; +import { useWebConnectionService } from "hooks/services/useConnectionHook"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; +import { useUpdateWorkspace } from "services/workspaces/WorkspacesService"; export interface DbtCloudJob { - // TODO rename project->account account: string; job: string; operationId?: string; } +const dbtCloudDomain = "https://cloud.getdbt.com"; const webhookConfigName = "dbt cloud"; const executionBody = `{"cause": "airbyte"}`; const jobName = (t: DbtCloudJob) => `${t.account}/${t.job}`; -const updateConnection = (obj: WebBackendConnectionUpdate) => console.info(`updating connection with`, obj); -// const updateWorkspace = (obj: WorkspaceUpdate) => console.info(`updating workspace with`, obj); const toDbtCloudJob = (operation: OperationRead): DbtCloudJob => { const { operationId } = operation; const { executionUrl } = operation.operatorConfiguration.webhook || {}; - const matches = executionUrl?.match(/\/accounts\/([^/]+)\/jobs\/([^]+)\/run/); + const matches = (executionUrl || "").match(/\/accounts\/([^/]+)\/jobs\/([^]+)\//); if (!matches) { throw new Error(`Cannot extract dbt cloud job params from executionUrl ${executionUrl}`); @@ -55,36 +48,56 @@ const toDbtCloudJob = (operation: OperationRead): DbtCloudJob => { const isDbtCloudJob = (operation: OperationRead): boolean => operation.operatorConfiguration.operatorType === OperatorType.webhook; -// export const configureDbtIntegration = (authToken: string, singleTenantUrl?: string) => { -// updateWorkspace(...) -// } +export const useSubmitDbtCloudIntegrationConfig = () => { + const { workspaceId } = useCurrentWorkspace(); + const { mutate: updateWorkspace } = useUpdateWorkspace(); + + return (authToken: string, singleTenantUrl?: string) => { + const domain = singleTenantUrl || dbtCloudDomain; + const validationUrl = `${domain}/api/v2/accounts`; + + updateWorkspace({ + workspaceId, + webhookConfigs: [ + { + name: webhookConfigName, + authToken, + validationUrl, + }, + ], + }); + }; +}; export const useDbtIntegration = (connection: WebBackendConnectionRead) => { const workspace = useCurrentWorkspace(); const { workspaceId } = workspace; - const hasDbtIntegration = !isEmpty(workspace.webhookConfigs); + const connectionService = useWebConnectionService(); + // TODO extract isDbtWebhookConfig predicate + const hasDbtIntegration = !isEmpty(workspace.webhookConfigs?.filter((config) => /dbt/.test(config.name || ""))); + const webhookConfigId = workspace.webhookConfigs?.find((config) => /dbt/.test(config.name || ""))?.id; const dbtCloudJobs = [...(connection.operations?.filter((operation) => isDbtCloudJob(operation)) || [])].map( toDbtCloudJob ); const otherOperations = [...(connection.operations?.filter((operation) => !isDbtCloudJob(operation)) || [])]; const saveJobs = (jobs: DbtCloudJob[]) => { - // TODO dynamically use the workspace's configured dbt cloud domain - const dbtCloudDomain = "https://cloud.getdbt.com"; - const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}`; + // TODO dynamically use the workspace's configured dbt cloud domain when it gets returned by backend + const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}/run`; - updateConnection({ + connectionService.update({ connectionId: connection.connectionId, operations: [ ...otherOperations, ...jobs.map((job) => ({ workspaceId, - operationId: job.operationId, + ...(job.operationId ? { operationId: job.operationId } : {}), name: jobName(job), operatorConfiguration: { operatorType: OperatorType.webhook, webhook: { executionUrl: urlForJob(job), - webhookConfigName, + // if `hasDbtIntegration` is true, webhookConfigId is guaranteed to exist + ...(webhookConfigId ? { webhookConfigId } : {}), executionBody, }, }, diff --git a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx index 1af7224a4336..2ca17268effb 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx +++ b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx @@ -6,6 +6,7 @@ import { LabeledInput } from "components/LabeledInput"; import { Button } from "components/ui/Button"; import { CollapsablePanel } from "components/ui/CollapsablePanel"; +import { useSubmitDbtCloudIntegrationConfig } from "packages/cloud/services/dbtCloud"; import { Content, SettingsCard } from "pages/SettingsPage/pages/SettingsComponents"; import styles from "./DbtCloudSettingsView.module.scss"; @@ -19,48 +20,53 @@ const singleTenantUrlInput = (fieldProps: FieldInputProps) => ( /> ); -export const DbtCloudSettingsView: React.FC = () => ( - }> - - console.log(values)} - > - - - {({ field }: FieldProps) => ( - } - type="text" - /> - )} - - - {({ field }: FieldProps) => ( - - - - )} - -
- - -
- -
-
-
-); +export const DbtCloudSettingsView: React.FC = () => { + const submitDbtCloudIntegrationConfig = useSubmitDbtCloudIntegrationConfig(); + return ( + }> + + + submitDbtCloudIntegrationConfig(serviceToken, singleTenantUrl) + } + > +
+ + {({ field }: FieldProps) => ( + } + type="text" + /> + )} + + + {({ field }: FieldProps) => ( + + + + )} + +
+ + +
+
+
+
+
+ ); +}; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 7555fcb1f8b2..fe9253594196 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -15,11 +15,11 @@ import { RoutePaths } from "pages/routePaths"; import styles from "./DbtCloudTransformationsCard.module.scss"; -const _jobs: DbtCloudJob[] = [ - { account: "1", job: "1234" }, - { account: "2", job: "2134" }, - { account: "3", job: "3214" }, -]; +/* const _jobs: DbtCloudJob[] = [ + * { account: "1", job: "1234" }, + * { account: "2", job: "2134" }, + * { account: "3", job: "3214" }, + * ]; */ /* const _jobs: DbtCloudJob[] = []; */ // without including the index, duplicate data causes annoying render bugs for the list @@ -36,7 +36,7 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac // 2.2) AND the connection has saved dbt jobs // THEN show the "no jobs" card body and the "+ Add transformation" button - const { hasDbtIntegration, saveJobs } = useDbtIntegration(connection); + const { hasDbtIntegration, saveJobs, dbtCloudJobs } = useDbtIntegration(connection); return ( {hasDbtIntegration ? ( - + ) : ( )} From 04cf38c81e240d1ac9b3aa0713d6222b34c5f968 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Mon, 17 Oct 2022 19:27:11 -0700 Subject: [PATCH 21/30] Add hacky initial add new job implementation --- .../src/packages/cloud/services/dbtCloud.ts | 23 ++++++++++++-- .../DbtCloudTransformationsCard.tsx | 31 +++++++++++++------ 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index 00a7f4223227..865349f486cf 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -20,7 +20,7 @@ export interface DbtCloudJob { job: string; operationId?: string; } - +const emptyDbtCloudJob = { account: "", job: "" }; const dbtCloudDomain = "https://cloud.getdbt.com"; const webhookConfigName = "dbt cloud"; const executionBody = `{"cause": "airbyte"}`; @@ -73,13 +73,30 @@ export const useDbtIntegration = (connection: WebBackendConnectionRead) => { const workspace = useCurrentWorkspace(); const { workspaceId } = workspace; const connectionService = useWebConnectionService(); - // TODO extract isDbtWebhookConfig predicate + + // TODO extract shared isDbtWebhookConfig predicate const hasDbtIntegration = !isEmpty(workspace.webhookConfigs?.filter((config) => /dbt/.test(config.name || ""))); const webhookConfigId = workspace.webhookConfigs?.find((config) => /dbt/.test(config.name || ""))?.id; + const dbtCloudJobs = [...(connection.operations?.filter((operation) => isDbtCloudJob(operation)) || [])].map( toDbtCloudJob ); const otherOperations = [...(connection.operations?.filter((operation) => !isDbtCloudJob(operation)) || [])]; + + // TODO this is a godawful hack: the "add new jobs" button should be defined + // inside the Formik FieldArray so it can directly use the array utilities + // from the render function. + let addNewJobFn: () => void = () => { + console.log("original fn implementation ha ha ho ho hee hee"); + }; + const addNewJob = () => addNewJobFn(); + const setAddNewJobFn = (addJobFn: (job: DbtCloudJob) => void) => { + addNewJobFn = () => { + console.log("the new job! the homie"); + addJobFn(emptyDbtCloudJob); + }; + }; + const saveJobs = (jobs: DbtCloudJob[]) => { // TODO dynamically use the workspace's configured dbt cloud domain when it gets returned by backend const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}/run`; @@ -110,5 +127,7 @@ export const useDbtIntegration = (connection: WebBackendConnectionRead) => { hasDbtIntegration, dbtCloudJobs, saveJobs, + addNewJob, + setAddNewJobFn, }; }; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index fe9253594196..a303fea1a944 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -36,21 +36,31 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac // 2.2) AND the connection has saved dbt jobs // THEN show the "no jobs" card body and the "+ Add transformation" button - const { hasDbtIntegration, saveJobs, dbtCloudJobs } = useDbtIntegration(connection); + const { hasDbtIntegration, saveJobs, dbtCloudJobs, addNewJob, setAddNewJobFn } = useDbtIntegration(connection); return ( Transformations - } > {hasDbtIntegration ? ( - + ) : ( )} @@ -62,10 +72,12 @@ const DbtJobsList = ({ className, jobs, saveJobs, + setAddNewJobFn, }: { className?: string; jobs: DbtCloudJob[]; saveJobs: (jobs: DbtCloudJob[]) => void; + setAddNewJobFn: (addNewJobFn: (job: DbtCloudJob) => void) => void; }) => { const onSubmit = ({ jobs }: { jobs: DbtCloudJob[] }) => { saveJobs(jobs); @@ -75,18 +87,19 @@ const DbtJobsList = ({

After an Airbyte sync job has completed, the following jobs will run

{jobs.length ? ( - (
- values.jobs.map((t, i) => ( - arrayHelpers.remove(i)} /> - )) - } + render={({ remove, push }) => { + setAddNewJobFn(push); + return values.jobs.map((t, i) => ( + remove(i)} /> + )); + }} />
- - } - > - {hasDbtIntegration ? ( - - ) : ( - - )} - - ); -}; - -const DbtJobsList = ({ - className, - jobs, - saveJobs, - setAddNewJobFn, -}: { - className?: string; - jobs: DbtCloudJob[]; - saveJobs: (jobs: DbtCloudJob[]) => void; - setAddNewJobFn: (addNewJobFn: (job: DbtCloudJob) => void) => void; -}) => { + const { hasDbtIntegration, saveJobs, dbtCloudJobs } = useDbtIntegration(connection); const onSubmit = ({ jobs }: { jobs: DbtCloudJob[] }) => { saveJobs(jobs); }; return ( -
-

After an Airbyte sync job has completed, the following jobs will run

- {jobs.length ? ( - ( - - { - setAddNewJobFn(push); - return values.jobs.map((t, i) => ( - remove(i)} /> - )); - }} - /> -
- - -
- - )} - /> - ) : ( - <> - An octopus wearing a hard hat, tools at the ready - No transformations - - )} -
+ { + return ( +
+ { + return ( + + Transformations + + + } + > + {hasDbtIntegration ? ( +
+

+ After an Airbyte sync job has completed, the following jobs will run +

+ {values.jobs.length ? ( + values.jobs.map((t, i) => ( + remove(i)} /> + )) + ) : ( + <> + An octopus wearing a hard hat, tools at the ready + No transformations + + )} +
+ + +
+
+ ) : ( + + )} +
+ ); + }} + /> + + ); + }} + /> ); }; From ffc1b2fbdd8d9e16f9ff4af47c24fcfe1fd9e58e Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Tue, 18 Oct 2022 01:08:24 -0700 Subject: [PATCH 23/30] Fix button placement, loss of focus on input Never use the input prop in your component key, kids. --- .../DbtCloudTransformationsCard.module.scss | 1 + .../DbtCloudTransformationsCard.tsx | 7 +------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss index a4f0d6b94dca..d0044e2deb58 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss @@ -39,6 +39,7 @@ display: flex; justify-content: flex-end; margin-top: 20px; + width: 100%; } .jobListButton { diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 2ab3fa8a0797..e5a03832d7f2 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -22,9 +22,6 @@ import styles from "./DbtCloudTransformationsCard.module.scss"; * ]; */ /* const _jobs: DbtCloudJob[] = []; */ -// without including the index, duplicate data causes annoying render bugs for the list -const jobKey = (t: DbtCloudJob, i: number) => `${i}:${t.account}/${t.job}`; - export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBackendConnectionRead }) => { // Possible render paths: // 1) IF the workspace has no dbt cloud account linked @@ -73,9 +70,7 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac After an Airbyte sync job has completed, the following jobs will run

{values.jobs.length ? ( - values.jobs.map((t, i) => ( - remove(i)} /> - )) + values.jobs.map((_j, i) => remove(i)} />) ) : ( <> Date: Tue, 18 Oct 2022 01:18:44 -0700 Subject: [PATCH 24/30] re-extract DbtJobsList component --- .../DbtCloudTransformationsCard.tsx | 47 +++++++++---------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index e5a03832d7f2..ee84ad57340e 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -65,30 +65,7 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac } > {hasDbtIntegration ? ( -
-

- After an Airbyte sync job has completed, the following jobs will run -

- {values.jobs.length ? ( - values.jobs.map((_j, i) => remove(i)} />) - ) : ( - <> - An octopus wearing a hard hat, tools at the ready - No transformations - - )} -
- - -
-
+ ) : ( )} @@ -103,6 +80,28 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac ); }; +const DbtJobsList = ({ jobs, remove }: { jobs: DbtCloudJob[]; remove: (i: number) => void }) => ( +
+

After an Airbyte sync job has completed, the following jobs will run

+ {jobs.length ? ( + jobs.map((_j, i) => remove(i)} />) + ) : ( + <> + An octopus wearing a hard hat, tools at the ready + No transformations + + )} +
+ + +
+
+); + const JobsListItem = ({ jobIndex, removeJob }: { jobIndex: number; removeJob: () => void }) => { return ( From 2cee9f569c96975e186a6867c0f4bcc184d85b64 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Tue, 18 Oct 2022 01:39:20 -0700 Subject: [PATCH 25/30] Add input labels for dbt cloud job list --- .../DbtCloudTransformationsCard.module.scss | 5 +++++ .../DbtCloudTransformationsCard.tsx | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss index d0044e2deb58..95de6155ca5a 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss @@ -76,6 +76,11 @@ margin-left: 1em; } +.jobListItemInputLabel { + font-size: 11px; + font-weight: 500; +} + .jobListItemDelete { color: colors.$grey-200; font-size: large; diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index ee84ad57340e..85a74a8448c1 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -112,12 +112,26 @@ const JobsListItem = ({ jobIndex, removeJob }: { jobIndex: number; removeJob: ()
- {({ field }: FieldProps) => } + {({ field }: FieldProps) => ( + <> + + + + )}
- {({ field }: FieldProps) => } + {({ field }: FieldProps) => ( + <> + + + + )}
-
); +// TODO give feedback on validation errors (red outline and validation message) const JobsListItem = ({ jobIndex, removeJob }: { jobIndex: number; removeJob: () => void }) => { return ( From b0e90792e8a851a4b65c3a9e69714f4471c75b35 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Tue, 18 Oct 2022 02:15:50 -0700 Subject: [PATCH 27/30] Fix typo --- .../ConnectionTransformationTab/DbtCloudTransformationsCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 1534735b9924..0a00204eb25e 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -34,7 +34,7 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac // 2.1) AND the connection has no saved dbt jobs (cf: operations) // THEN show empty jobs list and the "+ Add transformation" button // 2.2) AND the connection has saved dbt jobs - // THEN show the "no jobs" card body and the "+ Add transformation" button + // THEN show the jobs list and the "+ Add transformation" button const { hasDbtIntegration, saveJobs, dbtCloudJobs } = useDbtIntegration(connection); const onSubmit = ({ jobs }: { jobs: DbtCloudJob[] }) => { From 923f3320dc1f676e4f4b0df0fe3a5c7f3857ac2f Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Tue, 18 Oct 2022 12:31:59 -0700 Subject: [PATCH 28/30] Improve dirty form tracking for dbt jobs list --- .../DbtCloudTransformationsCard.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 0a00204eb25e..0e5e6d28ecd1 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -5,6 +5,7 @@ import { Field, Form, Formik, FieldArray, FieldProps } from "formik"; import { Link } from "react-router-dom"; import { array, object, number } from "yup"; +import { FormChangeTracker } from "components/FormChangeTracker"; import { Button } from "components/ui/Button"; import { Card } from "components/ui/Card"; import { Input } from "components/ui/Input"; @@ -46,9 +47,10 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac onSubmit={onSubmit} initialValues={{ jobs: dbtCloudJobs }} validationSchema={dbtCloudJobListSchema} - render={({ values, isValid }) => { + render={({ values, isValid, dirty }) => { return ( + { @@ -69,7 +71,7 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac } > {hasDbtIntegration ? ( - + ) : ( )} @@ -88,10 +90,12 @@ const DbtJobsList = ({ jobs, remove, isValid, + dirty, }: { jobs: DbtCloudJob[]; remove: (i: number) => void; isValid: boolean; + dirty: boolean; }) => (

After an Airbyte sync job has completed, the following jobs will run

@@ -107,7 +111,7 @@ const DbtJobsList = ({ -
From ea8d8bcbe42ac7062e433a91ebe0bd2419b1fda5 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Tue, 18 Oct 2022 12:40:30 -0700 Subject: [PATCH 29/30] Remove unused input, add loading state to dbt cloud settings view --- .../CollapsablePanel.module.scss | 6 --- .../ui/CollapsablePanel/CollapsablePanel.tsx | 49 ------------------- .../components/ui/CollapsablePanel/index.ts | 1 - .../src/packages/cloud/services/dbtCloud.ts | 13 ++--- .../DbtCloudSettingsView.module.scss | 25 ---------- .../integrations/DbtCloudSettingsView.tsx | 36 ++------------ 6 files changed, 9 insertions(+), 121 deletions(-) delete mode 100644 airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.module.scss delete mode 100644 airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.tsx delete mode 100644 airbyte-webapp/src/components/ui/CollapsablePanel/index.ts diff --git a/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.module.scss b/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.module.scss deleted file mode 100644 index 7dab8b22a3a5..000000000000 --- a/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.module.scss +++ /dev/null @@ -1,6 +0,0 @@ -.arrow { - border: none; - background-color: inherit; - color: inherit; - cursor: pointer; -} diff --git a/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.tsx b/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.tsx deleted file mode 100644 index 613a414cc63d..000000000000 --- a/airbyte-webapp/src/components/ui/CollapsablePanel/CollapsablePanel.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { faChevronRight, faChevronDown } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import classNames from "classnames"; -import React, { ReactElement } from "react"; -import { useToggle } from "react-use"; - -import styles from "./CollapsablePanel.module.scss"; - -const Arrow: React.FC<{ isOpen: boolean }> = ({ isOpen }) => ( - -); - -/** - * CollapsablePanel defines a reusable "click to expand" UI element. It has two - * props for content slots, each containing an arbitrary component tree: - * - children: the "above the fold" or "title" content which is always shown - * - drawer: the "below the fold" content which is expanded or hidden on user interaction - * - * The rationale for putting the "above the fold" content in `children` is that - react's syntactic sugar for `children` makes the source code better reflect - the rendered UI, with the always-present content defined inline next to - its "peer" components in the surrounding context. - */ -export const CollapsablePanel: React.FC<{ - drawer: ReactElement; - initiallyOpen?: boolean; - className?: string; - openClassName?: string; - closedClassName?: string; -}> = ({ drawer, initiallyOpen = false, children, className, openClassName, closedClassName }) => { - const [isOpen, toggleOpen] = useToggle(initiallyOpen); - return ( -
-
- - {children} -
- {isOpen ? drawer : null} -
- ); -}; diff --git a/airbyte-webapp/src/components/ui/CollapsablePanel/index.ts b/airbyte-webapp/src/components/ui/CollapsablePanel/index.ts deleted file mode 100644 index 78eff7373b36..000000000000 --- a/airbyte-webapp/src/components/ui/CollapsablePanel/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./CollapsablePanel"; diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index 0508acb553f9..a0d121c7255e 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -9,6 +9,7 @@ // - custom domains aren't yet supported import isEmpty from "lodash/isEmpty"; +import { useMutation } from "react-query"; import { OperatorType, WebBackendConnectionRead, OperationRead } from "core/request/AirbyteClient"; import { useWebConnectionService } from "hooks/services/useConnectionHook"; @@ -49,23 +50,19 @@ const isDbtCloudJob = (operation: OperationRead): boolean => export const useSubmitDbtCloudIntegrationConfig = () => { const { workspaceId } = useCurrentWorkspace(); - const { mutate: updateWorkspace } = useUpdateWorkspace(); + const { mutateAsync: updateWorkspace } = useUpdateWorkspace(); - return (authToken: string, singleTenantUrl?: string) => { - const domain = singleTenantUrl || dbtCloudDomain; - const validationUrl = `${domain}/api/v2/accounts`; - - updateWorkspace({ + return useMutation(async (authToken: string) => { + await updateWorkspace({ workspaceId, webhookConfigs: [ { name: webhookConfigName, authToken, - validationUrl, }, ], }); - }; + }); }; export const useDbtIntegration = (connection: WebBackendConnectionRead) => { diff --git a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss index 649a0aea1efa..fa65ebca2f51 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss +++ b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.module.scss @@ -3,31 +3,6 @@ $item-spacing: 25px; -.advancedOptions { - margin-top: $item-spacing; -} - -.advancedOptionsClosed { - color: colors.$grey; -} - -.advancedOptionsOpen { - color: colors.$blue; -} - -.singleTenantUrlInput { - margin-top: 1em; - - // HACK: this left margin approximately matches the width of the containing - // CollapsablePanel component's caret button - margin-left: 1.6em; - - // this overrides the LabeledInput's width: 100%, which causes the input to - // overflow into the parent div's padding (by exactly the left margin above). - // With width: auto, the right-hand edges of the two inputs line up nicely. - width: auto; -} - .controlGroup { display: flex; justify-content: flex-end; diff --git a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx index 2ca17268effb..dd2ddd269a35 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx +++ b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx @@ -1,38 +1,25 @@ -import { Field, FieldInputProps, FieldProps, Form, Formik } from "formik"; +import { Field, FieldProps, Form, Formik } from "formik"; import React from "react"; import { FormattedMessage } from "react-intl"; import { LabeledInput } from "components/LabeledInput"; import { Button } from "components/ui/Button"; -import { CollapsablePanel } from "components/ui/CollapsablePanel"; import { useSubmitDbtCloudIntegrationConfig } from "packages/cloud/services/dbtCloud"; import { Content, SettingsCard } from "pages/SettingsPage/pages/SettingsComponents"; import styles from "./DbtCloudSettingsView.module.scss"; -const singleTenantUrlInput = (fieldProps: FieldInputProps) => ( - } - type="text" - /> -); - export const DbtCloudSettingsView: React.FC = () => { - const submitDbtCloudIntegrationConfig = useSubmitDbtCloudIntegrationConfig(); + const { mutate: submitDbtCloudIntegrationConfig, isLoading } = useSubmitDbtCloudIntegrationConfig(); return ( }> - submitDbtCloudIntegrationConfig(serviceToken, singleTenantUrl) - } + onSubmit={({ serviceToken }) => submitDbtCloudIntegrationConfig(serviceToken)} > @@ -44,23 +31,8 @@ export const DbtCloudSettingsView: React.FC = () => { /> )} - - {({ field }: FieldProps) => ( - - - - )} -
- -
From 76c69ec27fdc516ed9887c46287abc4b2db287e8 Mon Sep 17 00:00:00 2001 From: Alex Birdsall Date: Tue, 18 Oct 2022 13:04:02 -0700 Subject: [PATCH 30/30] Handle no integration, dirty states in dbt jobs list --- .../src/packages/cloud/services/dbtCloud.ts | 2 +- .../DbtCloudTransformationsCard.tsx | 27 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index a0d121c7255e..9220cdb418f5 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -83,7 +83,7 @@ export const useDbtIntegration = (connection: WebBackendConnectionRead) => { // TODO dynamically use the workspace's configured dbt cloud domain when it gets returned by backend const urlForJob = (job: DbtCloudJob) => `${dbtCloudDomain}/api/v2/accounts/${job.account}/jobs/${job.job}/run`; - connectionService.update({ + return connectionService.update({ connectionId: connection.connectionId, operations: [ ...otherOperations, diff --git a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx index 0e5e6d28ecd1..18b04c2ba153 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -1,7 +1,7 @@ import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import classNames from "classnames"; -import { Field, Form, Formik, FieldArray, FieldProps } from "formik"; +import { Field, Form, Formik, FieldArray, FieldProps, FormikHelpers } from "formik"; import { Link } from "react-router-dom"; import { array, object, number } from "yup"; @@ -17,6 +17,10 @@ import { RoutePaths } from "pages/routePaths"; import styles from "./DbtCloudTransformationsCard.module.scss"; +interface DbtJobListValues { + jobs: DbtCloudJob[]; +} + const dbtCloudJobListSchema = object({ jobs: array().of( object({ @@ -38,8 +42,8 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac // THEN show the jobs list and the "+ Add transformation" button const { hasDbtIntegration, saveJobs, dbtCloudJobs } = useDbtIntegration(connection); - const onSubmit = ({ jobs }: { jobs: DbtCloudJob[] }) => { - saveJobs(jobs); + const onSubmit = (values: DbtJobListValues, { resetForm }: FormikHelpers) => { + saveJobs(values.jobs).then(() => resetForm({ values })); }; return ( @@ -59,14 +63,15 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac title={ Transformations - + {hasDbtIntegration && ( + + )} } >