@@ -2,30 +2,35 @@ import { faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
2
2
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" ;
3
3
import classNames from "classnames" ;
4
4
import { Field , Form , Formik , FieldArray , FieldProps , FormikHelpers } from "formik" ;
5
+ import { ReactNode } from "react" ;
6
+ import { FormattedMessage , useIntl } from "react-intl" ;
5
7
import { Link } from "react-router-dom" ;
6
- import { array , object , number } from "yup" ;
8
+ import * as yup from "yup" ;
7
9
8
10
import { FormChangeTracker } from "components/common/FormChangeTracker" ;
9
11
import { Button } from "components/ui/Button" ;
10
12
import { Card } from "components/ui/Card" ;
11
13
import { Input } from "components/ui/Input" ;
14
+ import { Text } from "components/ui/Text" ;
12
15
13
16
import { WebBackendConnectionRead } from "core/request/AirbyteClient" ;
14
17
import { useCurrentWorkspace } from "hooks/services/useWorkspace" ;
15
18
import { DbtCloudJob , useDbtIntegration } from "packages/cloud/services/dbtCloud" ;
16
19
import { RoutePaths } from "pages/routePaths" ;
17
20
21
+ import dbtLogo from "./dbt-bit_tm.svg" ;
18
22
import styles from "./DbtCloudTransformationsCard.module.scss" ;
23
+ import octaviaWorker from "./octavia-worker.png" ;
19
24
20
25
interface DbtJobListValues {
21
26
jobs : DbtCloudJob [ ] ;
22
27
}
23
28
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 ( ) ,
29
34
} )
30
35
) ,
31
36
} ) ;
@@ -47,12 +52,12 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac
47
52
} ;
48
53
49
54
return (
50
- < Formik // TODO extract to parent component, see if that helps with input focus issues
55
+ < Formik
51
56
onSubmit = { onSubmit }
52
57
initialValues = { { jobs : dbtCloudJobs } }
53
58
validationSchema = { dbtCloudJobListSchema }
54
59
render = { ( { values, isValid, dirty } ) => {
55
- return (
60
+ return hasDbtIntegration ? (
56
61
< Form className = { styles . jobListForm } >
57
62
< FormChangeTracker changed = { dirty } />
58
63
< FieldArray
@@ -62,29 +67,33 @@ export const DbtCloudTransformationsCard = ({ connection }: { connection: WebBac
62
67
< Card
63
68
title = {
64
69
< 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 >
75
78
</ span >
76
79
}
77
80
>
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 } />
83
82
</ Card >
84
83
) ;
85
84
} }
86
85
/>
87
86
</ 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 >
88
97
) ;
89
98
} }
90
99
/>
@@ -102,14 +111,20 @@ const DbtJobsList = ({
102
111
isValid : boolean ;
103
112
dirty : boolean ;
104
113
} ) => (
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 ) } >
107
115
{ 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
+ </ >
109
124
) : (
110
125
< >
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" />
113
128
</ >
114
129
) }
115
130
< div className = { styles . jobListButtonGroup } >
@@ -125,19 +140,20 @@ const DbtJobsList = ({
125
140
126
141
// TODO give feedback on validation errors (red outline and validation message)
127
142
const JobsListItem = ( { jobIndex, removeJob } : { jobIndex : number ; removeJob : ( ) => void } ) => {
143
+ const { formatMessage } = useIntl ( ) ;
128
144
return (
129
145
< Card className = { styles . jobListItem } >
130
146
< 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" />
133
149
</ div >
134
150
< div className = { styles . jobListItemInputGroup } >
135
151
< div className = { styles . jobListItemInput } >
136
152
< Field name = { `jobs.${ jobIndex } .account` } >
137
153
{ ( { field } : FieldProps < string > ) => (
138
154
< >
139
155
< label htmlFor = { `jobs.${ jobIndex } .account` } className = { styles . jobListItemInputLabel } >
140
- Account ID
156
+ < FormattedMessage id = "connection.dbtCloudJobs.job.accountId" />
141
157
</ label >
142
158
< Input { ...field } type = "text" />
143
159
</ >
@@ -149,33 +165,40 @@ const JobsListItem = ({ jobIndex, removeJob }: { jobIndex: number; removeJob: ()
149
165
{ ( { field } : FieldProps < string > ) => (
150
166
< >
151
167
< label htmlFor = { `jobs.${ jobIndex } .job` } className = { styles . jobListItemInputLabel } >
152
- Job ID
168
+ < FormattedMessage id = "connection.dbtCloudJobs.job.jobId" />
153
169
</ label >
154
170
< Input { ...field } type = "text" />
155
171
</ >
156
172
) }
157
173
</ Field >
158
174
</ 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
+ >
160
182
< FontAwesomeIcon icon = { faXmark } />
161
- </ button >
183
+ </ Button >
162
184
</ div >
163
185
</ Card >
164
186
) ;
165
187
} ;
166
188
167
- const NoDbtIntegration = ( { className } : { className : string } ) => {
189
+ const NoDbtIntegration = ( ) => {
168
190
const { workspaceId } = useCurrentWorkspace ( ) ;
169
191
const dbtSettingsPath = `/${ RoutePaths . Workspaces } /${ workspaceId } /${ RoutePaths . Settings } /dbt-cloud` ;
170
192
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 >
177
202
</ div >
178
203
) ;
179
204
} ;
180
-
181
- const DbtCloudSignupBanner = ( ) => < div /> ;
0 commit comments