Skip to content

Commit b879079

Browse files
ambirdsallakashkulk
authored andcommitted
Clean up of dbt cloud prototype UI code (#18894)
* Clean up of dbt cloud prototype UI code * Remove variables->vars alias from scss files * Remove obsolete comments * Improvements to delete job button - i18nize the aria-label - replace most custom scss by using `<Button variant="clear" size="lg">` * Use `_` for unused function arg
1 parent 7f53458 commit b879079

File tree

8 files changed

+107
-80
lines changed

8 files changed

+107
-80
lines changed
Binary file not shown.

airbyte-webapp/src/packages/cloud/locales/en.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@
6060
"workspaces.createNew": "Create new workspace",
6161
"workspaces.loading": "Loading…",
6262

63+
"connection.dbtCloudJobs.cardTitle": "Transformations",
64+
"connection.dbtCloudJobs.addJob": "Add transformation",
65+
"connection.dbtCloudJobs.explanation": "After an Airbyte sync job has completed, the following jobs will run",
66+
"connection.dbtCloudJobs.noJobs": "No transformations",
67+
"connection.dbtCloudJobs.job.title": "dbt Cloud transform",
68+
"connection.dbtCloudJobs.job.accountId": "Account ID",
69+
"connection.dbtCloudJobs.job.jobId": "Job ID",
70+
"connection.dbtCloudJobs.job.deleteButton": "Delete job",
71+
"connection.dbtCloudJobs.noIntegration": "Go to your <settingsLink>settings</settingsLink> to connect a dbt Cloud account",
72+
6373
"settings.accountSettings.logoutLabel": "Sign out",
6474
"settings.accountSettings.logoutText": "Sign out",
6575
"settings.accountSettings.firstName": "First name",

airbyte-webapp/src/packages/cloud/services/dbtCloud.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import isEmpty from "lodash/isEmpty";
1212
import { useMutation } from "react-query";
1313

14-
import { OperatorType, WebBackendConnectionRead, OperationRead } from "core/request/AirbyteClient";
14+
import { OperatorType, WebBackendConnectionRead, OperationRead, WebhookConfigRead } from "core/request/AirbyteClient";
1515
import { useWebConnectionService } from "hooks/services/useConnectionHook";
1616
import { useCurrentWorkspace } from "hooks/services/useWorkspace";
1717
import { useUpdateWorkspace } from "services/workspaces/WorkspacesService";
@@ -26,17 +26,17 @@ const webhookConfigName = "dbt cloud";
2626
const executionBody = `{"cause": "airbyte"}`;
2727
const jobName = (t: DbtCloudJob) => `${t.account}/${t.job}`;
2828

29+
const isDbtWebhookConfig = (webhookConfig: WebhookConfigRead) => !!webhookConfig.name?.includes("dbt");
30+
2931
const toDbtCloudJob = (operation: OperationRead): DbtCloudJob => {
3032
const { operationId } = operation;
3133
const { executionUrl } = operation.operatorConfiguration.webhook || {};
3234

33-
const matches = (executionUrl || "").match(/\/accounts\/([^/]+)\/jobs\/([^]+)\/run\//);
34-
35+
const matches = (executionUrl || "").match(/\/accounts\/([^/]+)\/jobs\/([^]+)\/run/);
3536
if (!matches) {
3637
throw new Error(`Cannot extract dbt cloud job params from executionUrl ${executionUrl}`);
3738
} else {
38-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
39-
const [_fullUrl, account, job] = matches;
39+
const [, account, job] = matches;
4040

4141
return {
4242
account,
@@ -70,9 +70,8 @@ export const useDbtIntegration = (connection: WebBackendConnectionRead) => {
7070
const { workspaceId } = workspace;
7171
const connectionService = useWebConnectionService();
7272

73-
// TODO extract shared isDbtWebhookConfig predicate
74-
const hasDbtIntegration = !isEmpty(workspace.webhookConfigs?.filter((config) => /dbt/.test(config.name || "")));
75-
const webhookConfigId = workspace.webhookConfigs?.find((config) => /dbt/.test(config.name || ""))?.id;
73+
const hasDbtIntegration = !isEmpty(workspace.webhookConfigs?.filter(isDbtWebhookConfig));
74+
const webhookConfigId = workspace.webhookConfigs?.find((config) => isDbtWebhookConfig(config))?.id;
7675

7776
const dbtCloudJobs = [...(connection.operations?.filter((operation) => isDbtCloudJob(operation)) || [])].map(
7877
toDbtCloudJob
Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
@use "scss/colors";
2-
@use "scss/variables" as vars;
3-
4-
$item-spacing: 25px;
1+
@use "scss/variables";
52

63
.controlGroup {
74
display: flex;
85
justify-content: flex-end;
9-
margin-top: $item-spacing;
6+
margin-top: variables.$spacing-xl;
107

118
.button {
12-
margin-left: 1em;
9+
margin-left: variables.$spacing-md;
1310
}
1411
}

airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.module.scss

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
@use "scss/colors";
2+
@use "scss/variables";
23

34
.jobListContainer {
5+
display: flex;
6+
flex-direction: column;
7+
align-items: center;
48
padding: 25px 25px 22px;
59
background-color: colors.$grey-50;
610
}
@@ -14,16 +18,10 @@
1418
width: 100%;
1519
}
1620

17-
.emptyListContent {
18-
display: flex;
19-
flex-direction: column;
20-
align-items: center;
21-
22-
> img {
23-
width: 111px;
24-
height: 111px;
25-
margin: 20px 0;
26-
}
21+
.emptyListImage {
22+
width: 111px;
23+
height: 111px;
24+
margin: variables.$spacing-xl 0;
2725
}
2826

2927
.contextExplanation {
@@ -38,26 +36,26 @@
3836
.jobListButtonGroup {
3937
display: flex;
4038
justify-content: flex-end;
41-
margin-top: 20px;
39+
margin-top: variables.$spacing-xl;
4240
width: 100%;
4341
}
4442

4543
.jobListButton {
46-
margin-left: 10px;
44+
margin-left: variables.$spacing-md;
4745
}
4846

4947
.jobListItem {
50-
margin-top: 10px;
48+
margin-top: variables.$spacing-md;
5149
padding: 18px;
5250
width: 100%;
5351
display: flex;
5452
justify-content: space-between;
5553
align-items: center;
54+
}
5655

57-
& img {
58-
height: 32px;
59-
width: 32px;
60-
}
56+
.dbtLogo {
57+
height: 32px;
58+
width: 32px;
6159
}
6260

6361
.jobListItemIntegrationName {
@@ -82,10 +80,5 @@
8280
}
8381

8482
.jobListItemDelete {
85-
color: colors.$grey-200;
86-
font-size: large;
87-
margin: 0 1em;
88-
cursor: pointer;
89-
border: none;
90-
background-color: inherit;
83+
margin-left: variables.$spacing-lg;
9184
}

airbyte-webapp/src/pages/ConnectionPage/pages/ConnectionItemPage/ConnectionTransformationTab/DbtCloudTransformationsCard.tsx

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,35 @@ import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
33
import classNames from "classnames";
44
import { Field, Form, Formik, FieldArray, FieldProps, FormikHelpers } from "formik";
5+
import { ReactNode } from "react";
6+
import { FormattedMessage, useIntl } from "react-intl";
57
import { Link } from "react-router-dom";
6-
import { array, object, number } from "yup";
8+
import * as yup from "yup";
79

810
import { FormChangeTracker } from "components/common/FormChangeTracker";
911
import { Button } from "components/ui/Button";
1012
import { Card } from "components/ui/Card";
1113
import { Input } from "components/ui/Input";
14+
import { Text } from "components/ui/Text";
1215

1316
import { WebBackendConnectionRead } from "core/request/AirbyteClient";
1417
import { useCurrentWorkspace } from "hooks/services/useWorkspace";
1518
import { DbtCloudJob, useDbtIntegration } from "packages/cloud/services/dbtCloud";
1619
import { RoutePaths } from "pages/routePaths";
1720

21+
import dbtLogo from "./dbt-bit_tm.svg";
1822
import styles from "./DbtCloudTransformationsCard.module.scss";
23+
import octaviaWorker from "./octavia-worker.png";
1924

2025
interface DbtJobListValues {
2126
jobs: DbtCloudJob[];
2227
}
2328

24-
const dbtCloudJobListSchema = object({
25-
jobs: array().of(
26-
object({
27-
account: number().required().positive().integer(),
28-
job: number().required().positive().integer(),
29+
const dbtCloudJobListSchema = yup.object({
30+
jobs: yup.array().of(
31+
yup.object({
32+
account: yup.number().required().positive().integer(),
33+
job: yup.number().required().positive().integer(),
2934
})
3035
),
3136
});
@@ -47,12 +52,12 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac
4752
};
4853

4954
return (
50-
<Formik // TODO extract to parent component, see if that helps with input focus issues
55+
<Formik
5156
onSubmit={onSubmit}
5257
initialValues={{ jobs: dbtCloudJobs }}
5358
validationSchema={dbtCloudJobListSchema}
5459
render={({ values, isValid, dirty }) => {
55-
return (
60+
return hasDbtIntegration ? (
5661
<Form className={styles.jobListForm}>
5762
<FormChangeTracker changed={dirty} />
5863
<FieldArray
@@ -62,29 +67,33 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac
6267
<Card
6368
title={
6469
<span className={styles.jobListTitle}>
65-
Transformations
66-
{hasDbtIntegration && (
67-
<Button
68-
variant="secondary"
69-
onClick={() => push({ account: "", job: "" })}
70-
icon={<FontAwesomeIcon icon={faPlus} />}
71-
>
72-
Add transformation
73-
</Button>
74-
)}
70+
<FormattedMessage id="connection.dbtCloudJobs.cardTitle" />
71+
<Button
72+
variant="secondary"
73+
onClick={() => push({ account: "", job: "" })}
74+
icon={<FontAwesomeIcon icon={faPlus} />}
75+
>
76+
<FormattedMessage id="connection.dbtCloudJobs.addJob" />
77+
</Button>
7578
</span>
7679
}
7780
>
78-
{hasDbtIntegration ? (
79-
<DbtJobsList jobs={values.jobs} remove={remove} isValid={isValid} dirty={dirty} />
80-
) : (
81-
<NoDbtIntegration className={styles.jobListContainer} />
82-
)}
81+
<DbtJobsList jobs={values.jobs} remove={remove} isValid={isValid} dirty={dirty} />
8382
</Card>
8483
);
8584
}}
8685
/>
8786
</Form>
87+
) : (
88+
<Card
89+
title={
90+
<span className={styles.jobListTitle}>
91+
<FormattedMessage id="connection.dbtCloudJobs.cardTitle" />
92+
</span>
93+
}
94+
>
95+
<NoDbtIntegration />
96+
</Card>
8897
);
8998
}}
9099
/>
@@ -102,14 +111,20 @@ const DbtJobsList = ({
102111
isValid: boolean;
103112
dirty: boolean;
104113
}) => (
105-
<div className={classNames(styles.jobListContainer, styles.emptyListContent)}>
106-
<p className={styles.contextExplanation}>After an Airbyte sync job has completed, the following jobs will run</p>
114+
<div className={classNames(styles.jobListContainer)}>
107115
{jobs.length ? (
108-
jobs.map((_j, i) => <JobsListItem key={i} jobIndex={i} removeJob={() => remove(i)} />)
116+
<>
117+
<Text className={styles.contextExplanation}>
118+
<FormattedMessage id="connection.dbtCloudJobs.explanation" />
119+
</Text>
120+
{jobs.map((_, i) => (
121+
<JobsListItem key={i} jobIndex={i} removeJob={() => remove(i)} />
122+
))}
123+
</>
109124
) : (
110125
<>
111-
<img src="/images/octavia/worker.png" alt="An octopus wearing a hard hat, tools at the ready" />
112-
No transformations
126+
<img src={octaviaWorker} alt="" className={styles.emptyListImage} />
127+
<FormattedMessage id="connection.dbtCloudJobs.noJobs" />
113128
</>
114129
)}
115130
<div className={styles.jobListButtonGroup}>
@@ -125,19 +140,20 @@ const DbtJobsList = ({
125140

126141
// TODO give feedback on validation errors (red outline and validation message)
127142
const JobsListItem = ({ jobIndex, removeJob }: { jobIndex: number; removeJob: () => void }) => {
143+
const { formatMessage } = useIntl();
128144
return (
129145
<Card className={styles.jobListItem}>
130146
<div className={styles.jobListItemIntegrationName}>
131-
<img src="/images/external/dbt-bit_tm.png" alt="dbt logo" />
132-
dbt Cloud transform
147+
<img src={dbtLogo} alt="" className={styles.dbtLogo} />
148+
<FormattedMessage id="connection.dbtCloudJobs.job.title" />
133149
</div>
134150
<div className={styles.jobListItemInputGroup}>
135151
<div className={styles.jobListItemInput}>
136152
<Field name={`jobs.${jobIndex}.account`}>
137153
{({ field }: FieldProps<string>) => (
138154
<>
139155
<label htmlFor={`jobs.${jobIndex}.account`} className={styles.jobListItemInputLabel}>
140-
Account ID
156+
<FormattedMessage id="connection.dbtCloudJobs.job.accountId" />
141157
</label>
142158
<Input {...field} type="text" />
143159
</>
@@ -149,33 +165,40 @@ const JobsListItem = ({ jobIndex, removeJob }: { jobIndex: number; removeJob: ()
149165
{({ field }: FieldProps<string>) => (
150166
<>
151167
<label htmlFor={`jobs.${jobIndex}.job`} className={styles.jobListItemInputLabel}>
152-
Job ID
168+
<FormattedMessage id="connection.dbtCloudJobs.job.jobId" />
153169
</label>
154170
<Input {...field} type="text" />
155171
</>
156172
)}
157173
</Field>
158174
</div>
159-
<button type="button" className={styles.jobListItemDelete} onClick={removeJob}>
175+
<Button
176+
variant="clear"
177+
size="lg"
178+
className={styles.jobListItemDelete}
179+
onClick={removeJob}
180+
aria-label={formatMessage({ id: "connection.dbtCloudJobs.job.deleteButton" })}
181+
>
160182
<FontAwesomeIcon icon={faXmark} />
161-
</button>
183+
</Button>
162184
</div>
163185
</Card>
164186
);
165187
};
166188

167-
const NoDbtIntegration = ({ className }: { className: string }) => {
189+
const NoDbtIntegration = () => {
168190
const { workspaceId } = useCurrentWorkspace();
169191
const dbtSettingsPath = `/${RoutePaths.Workspaces}/${workspaceId}/${RoutePaths.Settings}/dbt-cloud`;
170192
return (
171-
<div className={classNames(className, styles.emptyListContent)}>
172-
<p className={styles.contextExplanation}>After an Airbyte sync job has completed, the following jobs will run</p>
173-
<p className={styles.contextExplanation}>
174-
Go to your <Link to={dbtSettingsPath}>settings</Link> to connect your dbt Cloud account
175-
</p>
176-
<DbtCloudSignupBanner />
193+
<div className={classNames(styles.jobListContainer)}>
194+
<Text className={styles.contextExplanation}>
195+
<FormattedMessage
196+
id="connection.dbtCloudJobs.noIntegration"
197+
values={{
198+
settingsLink: (linkText: ReactNode) => <Link to={dbtSettingsPath}>{linkText}</Link>,
199+
}}
200+
/>
201+
</Text>
177202
</div>
178203
);
179204
};
180-
181-
const DbtCloudSignupBanner = () => <div />;
Loading

0 commit comments

Comments
 (0)