Skip to content

Commit

Permalink
🪟 🐛 Add confirmation modal for dbt token deletion, allow users to rem…
Browse files Browse the repository at this point in the history
…ove relevant transformations from connections (#6429)
  • Loading branch information
teallarson committed May 11, 2023
1 parent 3743a4f commit ca2b685
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 31 deletions.
2 changes: 2 additions & 0 deletions airbyte-webapp/src/packages/cloud/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@
"settings.integrationSettings.dbtCloudSettings": "dbt Cloud Integration",
"settings.integrationSettings.dbtCloudSettings.actions.cancel": "Cancel",
"settings.integrationSettings.dbtCloudSettings.actions.delete": "Delete service token",
"settings.integrationSettings.dbtCloudSettings.actions.delete.confirm": "Confirm dbt Service Token deletion",
"settings.integrationSettings.dbtCloudSettings.action.delete.modal": "Are you sure you want to remove your dbt Service Token? \n \nThis will stop all dbt Cloud transformations from running and may cause sync failures. You will need to manually remove these transformations from your connections to keep syncing.",
"settings.integrationSettings.dbtCloudSettings.actions.delete.success": "Service token deleted successfully",
"settings.integrationSettings.dbtCloudSettings.actions.submit": "Save changes",
"settings.integrationSettings.dbtCloudSettings.actions.submit.success": "Service Token saved successfully",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@ import { SettingsCard } from "pages/SettingsPage/pages/SettingsComponents";
import { links } from "utils/links";

import styles from "./DbtCloudSettingsView.module.scss";
import { useDbtTokenRemovalModal } from "./useDbtTokenRemovalModal";
interface ServiceTokenFormValues {
serviceToken: string;
}

const cleanedErrorMessage = (e: Error): string => e.message.replace("Internal Server Error: ", "");
export const cleanedErrorMessage = (e: Error): string => e.message.replace("Internal Server Error: ", "");
// a centrally-defined key for accessing the token value within formik objects

export const DbtCloudSettingsView: React.FC = () => {
const { formatMessage } = useIntl();
const { hasExistingToken, saveToken, isSavingToken, deleteToken, isDeletingToken } = useDbtCloudServiceToken();
const { hasExistingToken, saveToken, isSavingToken, isDeletingToken } = useDbtCloudServiceToken();
const [hasValidationError, setHasValidationError] = useState(false);
const { registerNotification } = useNotificationService();

const onDeleteClick = useDbtTokenRemovalModal();

const ButtonGroup = () => {
const { resetForm, values } = useFormikContext<ServiceTokenFormValues>();

Expand All @@ -34,25 +37,9 @@ export const DbtCloudSettingsView: React.FC = () => {
{hasExistingToken && (
<Button
variant="danger"
type="button"
className={classNames(styles.button, styles.deleteButton)}
onClick={() => {
deleteToken(void 0, {
onError: (e) => {
registerNotification({
id: "dbtCloud/delete-token-failure",
text: cleanedErrorMessage(e),
type: "error",
});
},
onSuccess: () => {
registerNotification({
id: "dbtCloud/delete-token-success",
text: formatMessage({ id: "settings.integrationSettings.dbtCloudSettings.actions.delete.success" }),
type: "success",
});
},
});
}}
onClick={onDeleteClick}
isLoading={isDeletingToken}
>
<FormattedMessage id="settings.integrationSettings.dbtCloudSettings.actions.delete" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useCallback } from "react";
import { useIntl } from "react-intl";

import { useConfirmationModalService } from "hooks/services/ConfirmationModal";
import { useNotificationService } from "hooks/services/Notification";
import { useDbtCloudServiceToken } from "packages/cloud/services/dbtCloud";

import { cleanedErrorMessage } from "./DbtCloudSettingsView";

export const useDbtTokenRemovalModal = () => {
const { openConfirmationModal, closeConfirmationModal } = useConfirmationModalService();
const { deleteToken } = useDbtCloudServiceToken();
const { registerNotification } = useNotificationService();
const { formatMessage } = useIntl();

return useCallback(() => {
openConfirmationModal({
text: "settings.integrationSettings.dbtCloudSettings.action.delete.modal",
title: "settings.integrationSettings.dbtCloudSettings.actions.delete.confirm",
submitButtonText: "settings.integrationSettings.dbtCloudSettings.actions.delete",
onSubmit: async () => {
deleteToken(void 0, {
onError: (e) => {
registerNotification({
id: "dbtCloud/delete-token-failure",
text: cleanedErrorMessage(e),
type: "error",
});
},
onSuccess: () => {
registerNotification({
id: "dbtCloud/delete-token-success",
text: formatMessage({ id: "settings.integrationSettings.dbtCloudSettings.actions.delete.success" }),
type: "success",
});
},
});
closeConfirmationModal();
},
submitButtonDataId: "delete",
});
}, [openConfirmationModal, deleteToken, closeConfirmationModal, registerNotification, formatMessage]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { links } from "utils/links";
import styles from "./DbtJobsForm.module.scss";
import { JobsList } from "./JobsList";

interface DbtJobListValues {
export interface DbtJobListValues {
jobs: DbtCloudJob[];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
import classNames from "classnames";
import { FieldArray, Form, Formik, FormikHelpers } from "formik";
import { ReactNode } from "react";
import { FormattedMessage } from "react-intl";

import { FormChangeTracker } from "components/common/FormChangeTracker";
import { Card } from "components/ui/Card";
import { Link } from "components/ui/Link";
import { Text } from "components/ui/Text";

import { useConnectionEditService } from "hooks/services/ConnectionEdit/ConnectionEditService";
import { useDbtIntegration } from "packages/cloud/services/dbtCloud";
import { RoutePaths } from "pages/routePaths";
import { useCurrentWorkspaceId } from "services/workspaces/WorkspacesService";

import { DbtJobListValues } from "./DbtJobsForm";
import { JobsList } from "./JobsList";
import styles from "./NoDbtIntegration.module.scss";

export const NoDbtIntegration = () => {
const workspaceId = useCurrentWorkspaceId();
const { connection } = useConnectionEditService();
const dbtSettingsPath = `/${RoutePaths.Workspaces}/${workspaceId}/${RoutePaths.Settings}/dbt-cloud`;
const { saveJobs } = useDbtIntegration(connection);

const onSubmit = (values: DbtJobListValues, { resetForm }: FormikHelpers<DbtJobListValues>) => {
saveJobs(values.jobs).then(() => resetForm({ values }));
};

const jobs = connection.operations
?.filter((operation) => operation.operatorConfiguration.webhook?.webhookType === "dbtCloud")
.map((operation) => {
return { accountId: 0, jobId: 0, operationId: operation.operationId };
});

return (
<Card
title={
Expand All @@ -22,16 +41,36 @@ export const NoDbtIntegration = () => {
</span>
}
>
<div className={classNames(styles.cardBodyContainer)}>
<Text className={styles.contextExplanation}>
<FormattedMessage
id="connection.dbtCloudJobs.noIntegration"
values={{
settingsLink: (linkText: ReactNode) => <Link to={dbtSettingsPath}>{linkText}</Link>,
}}
/>
</Text>
</div>
{!jobs?.length ? (
<div className={classNames(styles.cardBodyContainer)}>
<Text className={styles.contextExplanation}>
<FormattedMessage
id="connection.dbtCloudJobs.noIntegration"
values={{
settingsLink: (linkText: ReactNode) => <Link to={dbtSettingsPath}>{linkText}</Link>,
}}
/>
</Text>
</div>
) : (
<Formik
onSubmit={onSubmit}
initialValues={{ jobs }}
render={({ values, dirty }) => {
return (
<Form className={styles.jobListForm}>
<FormChangeTracker changed={dirty} />
<FieldArray
name="jobs"
render={({ remove }) => {
return <JobsList jobs={values.jobs} remove={remove} dirty={dirty} isLoading={false} />;
}}
/>
</Form>
);
}}
/>
)}
</Card>
);
};

0 comments on commit ca2b685

Please sign in to comment.