diff --git a/airbyte-webapp/src/packages/cloud/locales/en.json b/airbyte-webapp/src/packages/cloud/locales/en.json index bee11a4fa0b4..ee807fe06147 100644 --- a/airbyte-webapp/src/packages/cloud/locales/en.json +++ b/airbyte-webapp/src/packages/cloud/locales/en.json @@ -104,6 +104,7 @@ "settings.integrationSettings.dbtCloudSettings.form.singleTenantUrl": "Single-tenant URL", "settings.integrationSettings.dbtCloudSettings.form.testConnection": "Test connection", "settings.integrationSettings.dbtCloudSettings.form.submit": "Save changes", + "settings.integrationSettings.dbtCloudSettings.form.success": "Service Token saved successfully", "settings.generalSettings": "General Settings", "settings.generalSettings.changeWorkspace": "Change Workspace", "settings.generalSettings.form.name.label": "Workspace name", diff --git a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts index 3802478e4666..9a816e0793e8 100644 --- a/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts +++ b/airbyte-webapp/src/packages/cloud/services/dbtCloud.ts @@ -17,6 +17,7 @@ import { OperationRead, OperatorWebhookWebhookType, WebhookConfigRead, + WorkspaceRead, } from "core/request/AirbyteClient"; import { useWebConnectionService } from "hooks/services/useConnectionHook"; import { useCurrentWorkspace } from "hooks/services/useWorkspace"; @@ -61,21 +62,26 @@ const isDbtCloudJob = (operation: OperationRead): boolean => export const isSameJob = (remoteJob: DbtCloudJobInfo, savedJob: DbtCloudJob): boolean => savedJob.accountId === remoteJob.accountId && savedJob.jobId === remoteJob.jobId; +type ServiceToken = string; + export const useSubmitDbtCloudIntegrationConfig = () => { const { workspaceId } = useCurrentWorkspace(); const { mutateAsync: updateWorkspace } = useUpdateWorkspace(); - return useMutation(async (authToken: string) => { - await updateWorkspace({ - workspaceId, - webhookConfigs: [ - { - name: webhookConfigName, - authToken, - }, - ], - }); - }); + return useMutation( + ["submitWorkspaceDbtCloudToken", workspaceId], + async (authToken: string) => { + return await updateWorkspace({ + workspaceId, + webhookConfigs: [ + { + name: webhookConfigName, + authToken, + }, + ], + }); + } + ); }; export const useDbtIntegration = (connection: WebBackendConnectionRead) => { 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 dd2ddd269a35..e8a34d176459 100644 --- a/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx +++ b/airbyte-webapp/src/packages/cloud/views/settings/integrations/DbtCloudSettingsView.tsx @@ -1,6 +1,6 @@ import { Field, FieldProps, Form, Formik } from "formik"; -import React from "react"; -import { FormattedMessage } from "react-intl"; +import React, { useState } from "react"; +import { FormattedMessage, useIntl } from "react-intl"; import { LabeledInput } from "components/LabeledInput"; import { Button } from "components/ui/Button"; @@ -11,7 +11,10 @@ import { Content, SettingsCard } from "pages/SettingsPage/pages/SettingsComponen import styles from "./DbtCloudSettingsView.module.scss"; export const DbtCloudSettingsView: React.FC = () => { + const { formatMessage } = useIntl(); const { mutate: submitDbtCloudIntegrationConfig, isLoading } = useSubmitDbtCloudIntegrationConfig(); + const [hasValidationError, setHasValidationError] = useState(false); + const [validationMessage, setValidationMessage] = useState(""); return ( }> @@ -19,7 +22,23 @@ export const DbtCloudSettingsView: React.FC = () => { initialValues={{ serviceToken: "", }} - onSubmit={({ serviceToken }) => submitDbtCloudIntegrationConfig(serviceToken)} + onSubmit={({ serviceToken }, { resetForm }) => { + setHasValidationError(false); + setValidationMessage(""); + return submitDbtCloudIntegrationConfig(serviceToken, { + onError: (e) => { + setHasValidationError(true); + + setValidationMessage(e.message.replace("Internal Server Error: ", "")); + }, + onSuccess: () => { + setValidationMessage( + formatMessage({ id: "settings.integrationSettings.dbtCloudSettings.form.success" }) + ); + resetForm(); + }, + }); + }} >
@@ -27,6 +46,8 @@ export const DbtCloudSettingsView: React.FC = () => { } + error={hasValidationError} + message={validationMessage} type="text" /> )} 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 4956ae6d1bda..af7a6048dbf9 100644 --- a/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx +++ b/airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx @@ -154,7 +154,7 @@ const DbtJobsList = ({ jobs, remove, dirty, isLoading }: DbtJobsListProps) => { {jobs.map((job, i) => ( - remove(i)} /> + remove(i)} isLoading={isLoading} /> ))} ) : ( @@ -184,8 +184,9 @@ const DbtJobsList = ({ jobs, remove, dirty, isLoading }: DbtJobsListProps) => { interface JobsListItemProps { job: DbtCloudJob; removeJob: () => void; + isLoading: boolean; } -const JobsListItem = ({ job, removeJob }: JobsListItemProps) => { +const JobsListItem = ({ job, removeJob, isLoading }: JobsListItemProps) => { const { formatMessage } = useIntl(); // TODO if `job.jobName` is undefined, that means we failed to match any of the // dbt-Cloud-supplied jobs with the saved job. This means one of two things has @@ -218,6 +219,7 @@ const JobsListItem = ({ job, removeJob }: JobsListItemProps) => { size="lg" className={styles.jobListItemDelete} onClick={removeJob} + disabled={isLoading} aria-label={formatMessage({ id: "connection.dbtCloudJobs.job.deleteButton" })} >