datajunction-ui 0.0.1-a36 → 0.0.1-a38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +1 -4
- package/src/app/pages/AddEditNodePage/index.jsx +11 -6
- package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +1 -1
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +43 -16
- package/src/app/pages/CubeBuilderPage/index.jsx +1 -1
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +13 -6
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +47 -24
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +44 -32
- package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +84 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +78 -174
- package/src/app/services/DJService.js +11 -12
- package/src/app/services/__tests__/DJService.test.jsx +5 -7
package/package.json
CHANGED
|
@@ -113,7 +113,7 @@ describe('AddEditNodePage submission succeeded', () => {
|
|
|
113
113
|
'Number of repair orders!!!',
|
|
114
114
|
'SELECT count(repair_order_id) default_DOT_num_repair_orders FROM default.repair_orders',
|
|
115
115
|
'published',
|
|
116
|
-
|
|
116
|
+
null,
|
|
117
117
|
'neutral',
|
|
118
118
|
'unitless',
|
|
119
119
|
);
|
|
@@ -126,9 +126,6 @@ describe('AddEditNodePage submission succeeded', () => {
|
|
|
126
126
|
expect(mockDjClient.DataJunctionAPI.listMetricMetadata).toBeCalledTimes(
|
|
127
127
|
1,
|
|
128
128
|
);
|
|
129
|
-
expect(
|
|
130
|
-
await screen.getByDisplayValue('repair_order_id, country'),
|
|
131
|
-
).toBeInTheDocument();
|
|
132
129
|
expect(
|
|
133
130
|
await screen.getByText(/Successfully updated metric node/),
|
|
134
131
|
).toBeInTheDocument();
|
|
@@ -202,13 +202,18 @@ export function AddEditNodePage() {
|
|
|
202
202
|
'mode',
|
|
203
203
|
'tags',
|
|
204
204
|
];
|
|
205
|
+
const primaryKey = data.columns
|
|
206
|
+
.filter(
|
|
207
|
+
col =>
|
|
208
|
+
col.attributes &&
|
|
209
|
+
col.attributes.filter(
|
|
210
|
+
attr => attr.attribute_type.name === 'primary_key',
|
|
211
|
+
).length > 0,
|
|
212
|
+
)
|
|
213
|
+
.map(col => col.name);
|
|
205
214
|
fields.forEach(field => {
|
|
206
|
-
if (
|
|
207
|
-
field
|
|
208
|
-
data[field] !== undefined &&
|
|
209
|
-
Array.isArray(data[field])
|
|
210
|
-
) {
|
|
211
|
-
data[field] = data[field].join(', ');
|
|
215
|
+
if (field === 'primary_key') {
|
|
216
|
+
setFieldValue(field, primaryKey.join(', '));
|
|
212
217
|
} else {
|
|
213
218
|
setFieldValue(field, data[field] || '', false);
|
|
214
219
|
}
|
|
@@ -23,7 +23,7 @@ export const MetricsSelect = ({ cube }) => {
|
|
|
23
23
|
// Get metrics
|
|
24
24
|
useEffect(() => {
|
|
25
25
|
const fetchData = async () => {
|
|
26
|
-
if (cube) {
|
|
26
|
+
if (cube && cube !== []) {
|
|
27
27
|
const cubeMetrics = cube?.cube_elements
|
|
28
28
|
.filter(element => element.type === 'metric')
|
|
29
29
|
.map(metric => {
|
|
@@ -10,6 +10,10 @@ const mockDjClient = {
|
|
|
10
10
|
createCube: jest.fn(),
|
|
11
11
|
namespaces: jest.fn(),
|
|
12
12
|
cube: jest.fn(),
|
|
13
|
+
node: jest.fn(),
|
|
14
|
+
listTags: jest.fn(),
|
|
15
|
+
tagsNode: jest.fn(),
|
|
16
|
+
patchCube: jest.fn(),
|
|
13
17
|
};
|
|
14
18
|
|
|
15
19
|
const mockMetrics = [
|
|
@@ -202,14 +206,12 @@ describe('CubeBuilderPage', () => {
|
|
|
202
206
|
mockDjClient.createCube.mockResolvedValue({ status: 201, json: {} });
|
|
203
207
|
mockDjClient.namespaces.mockResolvedValue(['default']);
|
|
204
208
|
mockDjClient.cube.mockResolvedValue(mockCube);
|
|
209
|
+
mockDjClient.node.mockResolvedValue(mockCube);
|
|
210
|
+
mockDjClient.listTags.mockResolvedValue([]);
|
|
211
|
+
mockDjClient.tagsNode.mockResolvedValue([]);
|
|
212
|
+
mockDjClient.patchCube.mockResolvedValue({ status: 201, json: {} });
|
|
205
213
|
|
|
206
214
|
window.scrollTo = jest.fn();
|
|
207
|
-
|
|
208
|
-
render(
|
|
209
|
-
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
210
|
-
<CubeBuilderPage />
|
|
211
|
-
</DJClientContext.Provider>,
|
|
212
|
-
);
|
|
213
215
|
});
|
|
214
216
|
|
|
215
217
|
afterEach(() => {
|
|
@@ -217,14 +219,29 @@ describe('CubeBuilderPage', () => {
|
|
|
217
219
|
});
|
|
218
220
|
|
|
219
221
|
it('renders without crashing', () => {
|
|
222
|
+
render(
|
|
223
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
224
|
+
<CubeBuilderPage />
|
|
225
|
+
</DJClientContext.Provider>,
|
|
226
|
+
);
|
|
220
227
|
expect(screen.getByText('Cube')).toBeInTheDocument();
|
|
221
228
|
});
|
|
222
229
|
|
|
223
230
|
it('renders the Metrics section', () => {
|
|
231
|
+
render(
|
|
232
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
233
|
+
<CubeBuilderPage />
|
|
234
|
+
</DJClientContext.Provider>,
|
|
235
|
+
);
|
|
224
236
|
expect(screen.getByText('Metrics *')).toBeInTheDocument();
|
|
225
237
|
});
|
|
226
238
|
|
|
227
239
|
it('renders the Dimensions section', () => {
|
|
240
|
+
render(
|
|
241
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
242
|
+
<CubeBuilderPage />
|
|
243
|
+
</DJClientContext.Provider>,
|
|
244
|
+
);
|
|
228
245
|
expect(screen.getByText('Dimensions *')).toBeInTheDocument();
|
|
229
246
|
});
|
|
230
247
|
|
|
@@ -307,9 +324,11 @@ describe('CubeBuilderPage', () => {
|
|
|
307
324
|
|
|
308
325
|
const renderEditNode = element => {
|
|
309
326
|
return render(
|
|
310
|
-
<MemoryRouter
|
|
327
|
+
<MemoryRouter
|
|
328
|
+
initialEntries={['/nodes/default.repair_orders_cube/edit-cube']}
|
|
329
|
+
>
|
|
311
330
|
<Routes>
|
|
312
|
-
<Route path="nodes/:name/edit" element={element} />
|
|
331
|
+
<Route path="nodes/:name/edit-cube" element={element} />
|
|
313
332
|
</Routes>
|
|
314
333
|
</MemoryRouter>,
|
|
315
334
|
);
|
|
@@ -321,7 +340,10 @@ describe('CubeBuilderPage', () => {
|
|
|
321
340
|
<CubeBuilderPage />
|
|
322
341
|
</DJClientContext.Provider>,
|
|
323
342
|
);
|
|
324
|
-
|
|
343
|
+
expect(screen.getAllByText('Edit')[0]).toBeInTheDocument();
|
|
344
|
+
await waitFor(() => {
|
|
345
|
+
expect(mockDjClient.cube).toHaveBeenCalled();
|
|
346
|
+
});
|
|
325
347
|
await waitFor(() => {
|
|
326
348
|
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
327
349
|
});
|
|
@@ -329,7 +351,7 @@ describe('CubeBuilderPage', () => {
|
|
|
329
351
|
const selectMetrics = screen.getAllByTestId('select-metrics')[0];
|
|
330
352
|
expect(selectMetrics).toBeDefined();
|
|
331
353
|
expect(selectMetrics).not.toBeNull();
|
|
332
|
-
expect(screen.
|
|
354
|
+
expect(screen.getByText('default.num_repair_orders')).toBeInTheDocument();
|
|
333
355
|
|
|
334
356
|
fireEvent.click(screen.getAllByText('Dimensions *')[0]);
|
|
335
357
|
|
|
@@ -364,13 +386,18 @@ describe('CubeBuilderPage', () => {
|
|
|
364
386
|
fireEvent.click(createCube);
|
|
365
387
|
});
|
|
366
388
|
await waitFor(() => {
|
|
367
|
-
expect(mockDjClient.
|
|
368
|
-
'',
|
|
369
|
-
'',
|
|
370
|
-
'',
|
|
389
|
+
expect(mockDjClient.patchCube).toHaveBeenCalledWith(
|
|
390
|
+
'default.repair_orders_cube',
|
|
391
|
+
'Default: Repair Orders Cube',
|
|
392
|
+
'Repairs cube',
|
|
371
393
|
'draft',
|
|
372
|
-
[],
|
|
373
|
-
[
|
|
394
|
+
['default.total_repair_cost', 'default.num_repair_orders'],
|
|
395
|
+
[
|
|
396
|
+
'default.date_dim.day',
|
|
397
|
+
'default.date_dim.month',
|
|
398
|
+
'default.date_dim.year',
|
|
399
|
+
'default.date_dim.dateint',
|
|
400
|
+
],
|
|
374
401
|
[],
|
|
375
402
|
);
|
|
376
403
|
});
|
|
@@ -87,7 +87,7 @@ export function CubeBuilderPage() {
|
|
|
87
87
|
);
|
|
88
88
|
const tagsResponse = await djClient.tagsNode(
|
|
89
89
|
values.name,
|
|
90
|
-
values.tags.map(tag => tag),
|
|
90
|
+
(values.tags || []).map(tag => tag),
|
|
91
91
|
);
|
|
92
92
|
if ((status === 200 || status === 201) && tagsResponse.status === 200) {
|
|
93
93
|
setStatus({
|
|
@@ -97,13 +97,20 @@ export default function AddBackfillPopover({
|
|
|
97
97
|
{displayMessageAfterSubmit(status)}
|
|
98
98
|
<h2>Run Backfill</h2>
|
|
99
99
|
<span data-testid="edit-partition">
|
|
100
|
-
<label
|
|
101
|
-
|
|
100
|
+
<label
|
|
101
|
+
htmlFor="materializationName"
|
|
102
|
+
style={{ paddingBottom: '1rem' }}
|
|
103
|
+
>
|
|
104
|
+
Materialization Name
|
|
102
105
|
</label>
|
|
103
|
-
<Field
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
<Field
|
|
107
|
+
as="select"
|
|
108
|
+
name="materializationName"
|
|
109
|
+
id="materializationName"
|
|
110
|
+
disabled={true}
|
|
111
|
+
>
|
|
112
|
+
<option value={materialization?.name}>
|
|
113
|
+
{materialization?.name}{' '}
|
|
107
114
|
</option>
|
|
108
115
|
</Field>
|
|
109
116
|
</span>
|
|
@@ -2,27 +2,24 @@ import { useContext, useEffect, useRef, useState } from 'react';
|
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import DJClientContext from '../../providers/djclient';
|
|
4
4
|
import { ErrorMessage, Field, Form, Formik } from 'formik';
|
|
5
|
-
import { FormikSelect } from '../AddEditNodePage/FormikSelect';
|
|
6
|
-
import EditIcon from '../../icons/EditIcon';
|
|
7
5
|
import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
|
|
8
6
|
|
|
9
7
|
export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
10
8
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
9
|
const [popoverAnchor, setPopoverAnchor] = useState(false);
|
|
12
|
-
const [
|
|
13
|
-
const [
|
|
10
|
+
const [options, setOptions] = useState([]);
|
|
11
|
+
const [jobs, setJobs] = useState([]);
|
|
14
12
|
|
|
15
13
|
const ref = useRef(null);
|
|
16
14
|
|
|
17
15
|
useEffect(() => {
|
|
18
16
|
const fetchData = async () => {
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
? engines[0].name + '__' + engines[0].version
|
|
24
|
-
: '',
|
|
17
|
+
const options = await djClient.materializationInfo();
|
|
18
|
+
setOptions(options);
|
|
19
|
+
const allowedJobs = options.job_types?.filter(job =>
|
|
20
|
+
job.allowed_node_types.includes(node.type),
|
|
25
21
|
);
|
|
22
|
+
setJobs(allowedJobs);
|
|
26
23
|
};
|
|
27
24
|
fetchData().catch(console.error);
|
|
28
25
|
const handleClickOutside = event => {
|
|
@@ -41,14 +38,15 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
41
38
|
{ setSubmitting, setStatus },
|
|
42
39
|
) => {
|
|
43
40
|
setSubmitting(false);
|
|
44
|
-
const
|
|
45
|
-
|
|
41
|
+
const config = JSON.parse(values.config);
|
|
42
|
+
config.lookback_window = values.lookback_window;
|
|
43
|
+
console.log('values', values);
|
|
46
44
|
const response = await djClient.materialize(
|
|
47
45
|
values.node,
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
values.job_type,
|
|
47
|
+
values.strategy,
|
|
50
48
|
values.schedule,
|
|
51
|
-
|
|
49
|
+
config,
|
|
52
50
|
);
|
|
53
51
|
if (response.status === 200 || response.status === 201) {
|
|
54
52
|
setStatus({ success: 'Saved!' });
|
|
@@ -65,7 +63,7 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
65
63
|
<>
|
|
66
64
|
<button
|
|
67
65
|
className="edit_button"
|
|
68
|
-
aria-label="
|
|
66
|
+
aria-label="AddMaterialization"
|
|
69
67
|
tabIndex="0"
|
|
70
68
|
onClick={() => {
|
|
71
69
|
setPopoverAnchor(!popoverAnchor);
|
|
@@ -90,9 +88,11 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
90
88
|
<Formik
|
|
91
89
|
initialValues={{
|
|
92
90
|
node: node?.name,
|
|
93
|
-
|
|
91
|
+
job_type: 'spark_sql',
|
|
92
|
+
strategy: 'full',
|
|
94
93
|
config: '{"spark": {"spark.executor.memory": "6g"}}',
|
|
95
94
|
schedule: '@daily',
|
|
95
|
+
lookback_window: '1 DAY',
|
|
96
96
|
}}
|
|
97
97
|
onSubmit={configureMaterialization}
|
|
98
98
|
>
|
|
@@ -101,16 +101,15 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
101
101
|
<Form>
|
|
102
102
|
<h2>Configure Materialization</h2>
|
|
103
103
|
{displayMessageAfterSubmit(status)}
|
|
104
|
-
<span data-testid="
|
|
105
|
-
<label htmlFor="
|
|
106
|
-
<Field as="select" name="
|
|
104
|
+
<span data-testid="job-type">
|
|
105
|
+
<label htmlFor="job_type">Job Type</label>
|
|
106
|
+
<Field as="select" name="job_type">
|
|
107
107
|
<>
|
|
108
|
-
{
|
|
109
|
-
<option
|
|
110
|
-
{
|
|
108
|
+
{jobs?.map(job => (
|
|
109
|
+
<option key={job.name} value={job.name}>
|
|
110
|
+
{job.label}
|
|
111
111
|
</option>
|
|
112
112
|
))}
|
|
113
|
-
<option value=""></option>
|
|
114
113
|
</>
|
|
115
114
|
</Field>
|
|
116
115
|
</span>
|
|
@@ -122,6 +121,18 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
122
121
|
/>
|
|
123
122
|
<br />
|
|
124
123
|
<br />
|
|
124
|
+
<span data-testid="edit-partition">
|
|
125
|
+
<label htmlFor="strategy">Strategy</label>
|
|
126
|
+
<Field as="select" name="strategy">
|
|
127
|
+
<>
|
|
128
|
+
{options.strategies?.map(strategy => (
|
|
129
|
+
<option value={strategy.name}>{strategy.label}</option>
|
|
130
|
+
))}
|
|
131
|
+
</>
|
|
132
|
+
</Field>
|
|
133
|
+
</span>
|
|
134
|
+
<br />
|
|
135
|
+
<br />
|
|
125
136
|
<label htmlFor="schedule">Schedule</label>
|
|
126
137
|
<Field
|
|
127
138
|
type="text"
|
|
@@ -132,6 +143,18 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
132
143
|
/>
|
|
133
144
|
<br />
|
|
134
145
|
<br />
|
|
146
|
+
<div className="DescriptionInput">
|
|
147
|
+
<ErrorMessage name="description" component="span" />
|
|
148
|
+
<label htmlFor="Config">Lookback Window</label>
|
|
149
|
+
<Field
|
|
150
|
+
type="text"
|
|
151
|
+
name="lookback_window"
|
|
152
|
+
id="lookback_window"
|
|
153
|
+
placeholder="1 DAY"
|
|
154
|
+
default="1 DAY"
|
|
155
|
+
/>
|
|
156
|
+
</div>
|
|
157
|
+
<br />
|
|
135
158
|
<div className="DescriptionInput">
|
|
136
159
|
<ErrorMessage name="description" component="span" />
|
|
137
160
|
<label htmlFor="Config">Config</label>
|
|
@@ -53,11 +53,9 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
53
53
|
<div className={`cron-description`}>{cron(materialization)} </div>
|
|
54
54
|
</td>
|
|
55
55
|
<td>
|
|
56
|
-
{materialization.
|
|
57
|
-
<br />
|
|
58
|
-
{materialization.engine.version}
|
|
59
|
-
<ClientCodePopover code={materialization.clientCode} />
|
|
56
|
+
{materialization.job?.replace('MaterializationJob', '').toUpperCase()}
|
|
60
57
|
</td>
|
|
58
|
+
<td>{materialization.strategy?.toUpperCase()}</td>
|
|
61
59
|
<td>
|
|
62
60
|
{node.columns
|
|
63
61
|
.filter(col => col.partition !== null)
|
|
@@ -90,27 +88,34 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
90
88
|
</div>
|
|
91
89
|
))}
|
|
92
90
|
</td>
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
<
|
|
97
|
-
<div
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
<
|
|
102
|
-
{backfill.spec.
|
|
103
|
-
</
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
91
|
+
{materializations[0].strategy === 'incremental_time' ? (
|
|
92
|
+
<td>
|
|
93
|
+
{materialization.backfills.map(backfill => (
|
|
94
|
+
<a href={backfill.urls[0]} className="partitionLink">
|
|
95
|
+
<div
|
|
96
|
+
className="partition__full"
|
|
97
|
+
key={backfill.spec.column_name}
|
|
98
|
+
>
|
|
99
|
+
<div className="partition__header">
|
|
100
|
+
{partitionColumnsMap[backfill.spec.column_name]}
|
|
101
|
+
</div>
|
|
102
|
+
<div className="partition__body">
|
|
103
|
+
<span className="badge partition_value">
|
|
104
|
+
{backfill.spec.range[0]}
|
|
105
|
+
</span>
|
|
106
|
+
to
|
|
107
|
+
<span className="badge partition_value">
|
|
108
|
+
{backfill.spec.range[1]}
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
108
111
|
</div>
|
|
109
|
-
</
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
</a>
|
|
113
|
+
))}
|
|
114
|
+
<AddBackfillPopover node={node} materialization={materialization} />
|
|
115
|
+
</td>
|
|
116
|
+
) : (
|
|
117
|
+
<></>
|
|
118
|
+
)}
|
|
114
119
|
<td>
|
|
115
120
|
{materialization.urls.map((url, idx) => (
|
|
116
121
|
<a href={url} key={`url-${idx}`}>
|
|
@@ -118,6 +123,9 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
118
123
|
</a>
|
|
119
124
|
))}
|
|
120
125
|
</td>
|
|
126
|
+
<td>
|
|
127
|
+
<ClientCodePopover code={materialization.clientCode} />
|
|
128
|
+
</td>
|
|
121
129
|
</tr>
|
|
122
130
|
));
|
|
123
131
|
};
|
|
@@ -136,10 +144,15 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
136
144
|
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
137
145
|
<tr>
|
|
138
146
|
<th className="text-start">Schedule</th>
|
|
139
|
-
<th>
|
|
147
|
+
<th>Job Type</th>
|
|
148
|
+
<th>Strategy</th>
|
|
140
149
|
<th>Partitions</th>
|
|
141
|
-
<th>Output Tables</th>
|
|
142
|
-
|
|
150
|
+
<th>Intended Output Tables</th>
|
|
151
|
+
{materializations[0].strategy === 'incremental_time' ? (
|
|
152
|
+
<th>Backfills</th>
|
|
153
|
+
) : (
|
|
154
|
+
<></>
|
|
155
|
+
)}
|
|
143
156
|
<th>URLs</th>
|
|
144
157
|
</tr>
|
|
145
158
|
</thead>
|
|
@@ -171,16 +184,13 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
171
184
|
>
|
|
172
185
|
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
173
186
|
<tr>
|
|
174
|
-
<th className="text-start">
|
|
175
|
-
<th>Schema</th>
|
|
176
|
-
<th>Table</th>
|
|
187
|
+
<th className="text-start">Output Dataset</th>
|
|
177
188
|
<th>Valid Through</th>
|
|
178
189
|
<th>Partitions</th>
|
|
179
190
|
</tr>
|
|
180
191
|
</thead>
|
|
181
192
|
<tbody>
|
|
182
193
|
<tr>
|
|
183
|
-
<td>{node.availability.schema_}</td>
|
|
184
194
|
<td>
|
|
185
195
|
{
|
|
186
196
|
<div
|
|
@@ -203,7 +213,9 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
203
213
|
</div>
|
|
204
214
|
}
|
|
205
215
|
</td>
|
|
206
|
-
<td>
|
|
216
|
+
<td>
|
|
217
|
+
{new Date(node.availability.valid_through_ts).toISOString()}
|
|
218
|
+
</td>
|
|
207
219
|
<td>
|
|
208
220
|
<span
|
|
209
221
|
className={`badge partition_value`}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
|
3
|
+
import DJClientContext from '../../../providers/djclient';
|
|
4
|
+
import AddMaterializationPopover from '../AddMaterializationPopover';
|
|
5
|
+
import { mocks } from '../../../../mocks/mockNodes';
|
|
6
|
+
|
|
7
|
+
const mockDjClient = {
|
|
8
|
+
DataJunctionAPI: {
|
|
9
|
+
materialize: jest.fn(),
|
|
10
|
+
materializationInfo: jest.fn(),
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('<AddMaterializationPopover />', () => {
|
|
15
|
+
it('renders correctly and handles form submission', async () => {
|
|
16
|
+
// Mock onSubmit function
|
|
17
|
+
const onSubmitMock = jest.fn();
|
|
18
|
+
mockDjClient.DataJunctionAPI.materialize.mockReturnValue({
|
|
19
|
+
status: 201,
|
|
20
|
+
});
|
|
21
|
+
mockDjClient.DataJunctionAPI.materializationInfo.mockReturnValue({
|
|
22
|
+
status: 200,
|
|
23
|
+
json: {
|
|
24
|
+
job_types: [
|
|
25
|
+
{
|
|
26
|
+
name: 'spark_sql',
|
|
27
|
+
label: 'Spark SQL',
|
|
28
|
+
description: 'Spark SQL materialization job',
|
|
29
|
+
allowed_node_types: ['transform', 'dimension', 'cube'],
|
|
30
|
+
job_class: 'SparkSqlMaterializationJob',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'druid_cube',
|
|
34
|
+
label: 'Druid Cube',
|
|
35
|
+
description:
|
|
36
|
+
'Used to materialize a cube to Druid for low-latency access to a set of metrics and dimensions. While the logical cube definition is at the level of metrics and dimensions, a materialized Druid cube will reference measures and dimensions, with rollup configured on the measures where appropriate.',
|
|
37
|
+
allowed_node_types: ['cube'],
|
|
38
|
+
job_class: 'DruidCubeMaterializationJob',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
strategies: [
|
|
42
|
+
{
|
|
43
|
+
name: 'full',
|
|
44
|
+
label: 'Full',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'snapshot',
|
|
48
|
+
label: 'Snapshot',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: 'incremental_time',
|
|
52
|
+
label: 'Incremental Time',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'view',
|
|
56
|
+
label: 'View',
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Render the component
|
|
63
|
+
const { getByText } = render(
|
|
64
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
65
|
+
<AddMaterializationPopover
|
|
66
|
+
node={mocks.mockMetricNode}
|
|
67
|
+
onSubmit={onSubmitMock}
|
|
68
|
+
/>
|
|
69
|
+
</DJClientContext.Provider>,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Open the popover
|
|
73
|
+
fireEvent.click(getByText('+ Add Materialization'));
|
|
74
|
+
|
|
75
|
+
// Save the materialization
|
|
76
|
+
fireEvent.click(getByText('Save'));
|
|
77
|
+
|
|
78
|
+
// Expect setAttributes to be called
|
|
79
|
+
await waitFor(() => {
|
|
80
|
+
expect(mockDjClient.DataJunctionAPI.materialize).toHaveBeenCalled();
|
|
81
|
+
expect(getByText('Saved!')).toBeInTheDocument();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -107,16 +107,16 @@ exports[`<NodePage /> renders the NodeMaterialization tab with materializations
|
|
|
107
107
|
Schedule
|
|
108
108
|
</th>
|
|
109
109
|
<th>
|
|
110
|
-
|
|
110
|
+
Job Type
|
|
111
111
|
</th>
|
|
112
112
|
<th>
|
|
113
|
-
|
|
113
|
+
Strategy
|
|
114
114
|
</th>
|
|
115
115
|
<th>
|
|
116
|
-
|
|
116
|
+
Partitions
|
|
117
117
|
</th>
|
|
118
118
|
<th>
|
|
119
|
-
|
|
119
|
+
Intended Output Tables
|
|
120
120
|
</th>
|
|
121
121
|
<th>
|
|
122
122
|
URLs
|
|
@@ -141,9 +141,80 @@ exports[`<NodePage /> renders the NodeMaterialization tab with materializations
|
|
|
141
141
|
</div>
|
|
142
142
|
</td>
|
|
143
143
|
<td>
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
SPARKSQL
|
|
145
|
+
</td>
|
|
146
|
+
<td />
|
|
147
|
+
<td />
|
|
148
|
+
<td>
|
|
149
|
+
<div
|
|
150
|
+
class="table__full"
|
|
151
|
+
>
|
|
152
|
+
<div
|
|
153
|
+
class="table__header"
|
|
154
|
+
>
|
|
155
|
+
<svg
|
|
156
|
+
class="bi bi-table"
|
|
157
|
+
fill="currentColor"
|
|
158
|
+
height="16"
|
|
159
|
+
viewBox="0 0 16 16"
|
|
160
|
+
width="16"
|
|
161
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
162
|
+
>
|
|
163
|
+
<path
|
|
164
|
+
d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"
|
|
165
|
+
/>
|
|
166
|
+
</svg>
|
|
167
|
+
|
|
168
|
+
<span
|
|
169
|
+
class="entity-info"
|
|
170
|
+
>
|
|
171
|
+
common.a
|
|
172
|
+
</span>
|
|
173
|
+
</div>
|
|
174
|
+
<div
|
|
175
|
+
class="table__body upstream_tables"
|
|
176
|
+
/>
|
|
177
|
+
</div>
|
|
178
|
+
<div
|
|
179
|
+
class="table__full"
|
|
180
|
+
>
|
|
181
|
+
<div
|
|
182
|
+
class="table__header"
|
|
183
|
+
>
|
|
184
|
+
<svg
|
|
185
|
+
class="bi bi-table"
|
|
186
|
+
fill="currentColor"
|
|
187
|
+
height="16"
|
|
188
|
+
viewBox="0 0 16 16"
|
|
189
|
+
width="16"
|
|
190
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
191
|
+
>
|
|
192
|
+
<path
|
|
193
|
+
d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"
|
|
194
|
+
/>
|
|
195
|
+
</svg>
|
|
196
|
+
|
|
197
|
+
<span
|
|
198
|
+
class="entity-info"
|
|
199
|
+
>
|
|
200
|
+
common.b
|
|
201
|
+
</span>
|
|
202
|
+
</div>
|
|
203
|
+
<div
|
|
204
|
+
class="table__body upstream_tables"
|
|
205
|
+
/>
|
|
206
|
+
</div>
|
|
207
|
+
</td>
|
|
208
|
+
<td>
|
|
209
|
+
<a
|
|
210
|
+
href="http://fake.url/job"
|
|
211
|
+
>
|
|
212
|
+
[
|
|
213
|
+
1
|
|
214
|
+
]
|
|
215
|
+
</a>
|
|
216
|
+
</td>
|
|
217
|
+
<td>
|
|
147
218
|
<button
|
|
148
219
|
aria-label="code-button"
|
|
149
220
|
class="code-button"
|
|
@@ -230,173 +301,6 @@ exports[`<NodePage /> renders the NodeMaterialization tab with materializations
|
|
|
230
301
|
</pre>
|
|
231
302
|
</div>
|
|
232
303
|
</td>
|
|
233
|
-
<td />
|
|
234
|
-
<td>
|
|
235
|
-
<div
|
|
236
|
-
class="table__full"
|
|
237
|
-
>
|
|
238
|
-
<div
|
|
239
|
-
class="table__header"
|
|
240
|
-
>
|
|
241
|
-
<svg
|
|
242
|
-
class="bi bi-table"
|
|
243
|
-
fill="currentColor"
|
|
244
|
-
height="16"
|
|
245
|
-
viewBox="0 0 16 16"
|
|
246
|
-
width="16"
|
|
247
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
248
|
-
>
|
|
249
|
-
<path
|
|
250
|
-
d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"
|
|
251
|
-
/>
|
|
252
|
-
</svg>
|
|
253
|
-
|
|
254
|
-
<span
|
|
255
|
-
class="entity-info"
|
|
256
|
-
>
|
|
257
|
-
common.a
|
|
258
|
-
</span>
|
|
259
|
-
</div>
|
|
260
|
-
<div
|
|
261
|
-
class="table__body upstream_tables"
|
|
262
|
-
/>
|
|
263
|
-
</div>
|
|
264
|
-
<div
|
|
265
|
-
class="table__full"
|
|
266
|
-
>
|
|
267
|
-
<div
|
|
268
|
-
class="table__header"
|
|
269
|
-
>
|
|
270
|
-
<svg
|
|
271
|
-
class="bi bi-table"
|
|
272
|
-
fill="currentColor"
|
|
273
|
-
height="16"
|
|
274
|
-
viewBox="0 0 16 16"
|
|
275
|
-
width="16"
|
|
276
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
277
|
-
>
|
|
278
|
-
<path
|
|
279
|
-
d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2zm15 2h-4v3h4V4zm0 4h-4v3h4V8zm0 4h-4v3h3a1 1 0 0 0 1-1v-2zm-5 3v-3H6v3h4zm-5 0v-3H1v2a1 1 0 0 0 1 1h3zm-4-4h4V8H1v3zm0-4h4V4H1v3zm5-3v3h4V4H6zm4 4H6v3h4V8z"
|
|
280
|
-
/>
|
|
281
|
-
</svg>
|
|
282
|
-
|
|
283
|
-
<span
|
|
284
|
-
class="entity-info"
|
|
285
|
-
>
|
|
286
|
-
common.b
|
|
287
|
-
</span>
|
|
288
|
-
</div>
|
|
289
|
-
<div
|
|
290
|
-
class="table__body upstream_tables"
|
|
291
|
-
/>
|
|
292
|
-
</div>
|
|
293
|
-
</td>
|
|
294
|
-
<td>
|
|
295
|
-
<a
|
|
296
|
-
class="partitionLink"
|
|
297
|
-
>
|
|
298
|
-
<div
|
|
299
|
-
class="partition__full"
|
|
300
|
-
>
|
|
301
|
-
<div
|
|
302
|
-
class="partition__header"
|
|
303
|
-
/>
|
|
304
|
-
<div
|
|
305
|
-
class="partition__body"
|
|
306
|
-
>
|
|
307
|
-
<span
|
|
308
|
-
class="badge partition_value"
|
|
309
|
-
>
|
|
310
|
-
20230101
|
|
311
|
-
</span>
|
|
312
|
-
to
|
|
313
|
-
<span
|
|
314
|
-
class="badge partition_value"
|
|
315
|
-
>
|
|
316
|
-
20230102
|
|
317
|
-
</span>
|
|
318
|
-
</div>
|
|
319
|
-
</div>
|
|
320
|
-
</a>
|
|
321
|
-
<button
|
|
322
|
-
aria-label="AddBackfill"
|
|
323
|
-
class="edit_button"
|
|
324
|
-
tabindex="0"
|
|
325
|
-
>
|
|
326
|
-
<span
|
|
327
|
-
class="add_node"
|
|
328
|
-
>
|
|
329
|
-
+ Add Backfill
|
|
330
|
-
</span>
|
|
331
|
-
</button>
|
|
332
|
-
<div
|
|
333
|
-
class="fade modal-backdrop in"
|
|
334
|
-
style="display: none;"
|
|
335
|
-
/>
|
|
336
|
-
<div
|
|
337
|
-
aria-label="client-code"
|
|
338
|
-
class="centerPopover"
|
|
339
|
-
role="dialog"
|
|
340
|
-
style="display: none; width: 50%;"
|
|
341
|
-
>
|
|
342
|
-
<form
|
|
343
|
-
action="#"
|
|
344
|
-
>
|
|
345
|
-
<h2>
|
|
346
|
-
Run Backfill
|
|
347
|
-
</h2>
|
|
348
|
-
<span
|
|
349
|
-
data-testid="edit-partition"
|
|
350
|
-
>
|
|
351
|
-
<label
|
|
352
|
-
for="engine"
|
|
353
|
-
style="padding-bottom: 1rem;"
|
|
354
|
-
>
|
|
355
|
-
Engine
|
|
356
|
-
</label>
|
|
357
|
-
<select
|
|
358
|
-
disabled=""
|
|
359
|
-
id="engine"
|
|
360
|
-
name="engine"
|
|
361
|
-
>
|
|
362
|
-
<option
|
|
363
|
-
value="spark"
|
|
364
|
-
>
|
|
365
|
-
spark
|
|
366
|
-
|
|
367
|
-
2.4.4
|
|
368
|
-
</option>
|
|
369
|
-
</select>
|
|
370
|
-
</span>
|
|
371
|
-
<br />
|
|
372
|
-
<br />
|
|
373
|
-
<label
|
|
374
|
-
for="partition"
|
|
375
|
-
style="padding-bottom: 1rem;"
|
|
376
|
-
>
|
|
377
|
-
Partition Range
|
|
378
|
-
</label>
|
|
379
|
-
<br />
|
|
380
|
-
<button
|
|
381
|
-
aria-hidden="false"
|
|
382
|
-
aria-label="SaveEditColumn"
|
|
383
|
-
class="add_node"
|
|
384
|
-
type="submit"
|
|
385
|
-
>
|
|
386
|
-
Save
|
|
387
|
-
</button>
|
|
388
|
-
</form>
|
|
389
|
-
</div>
|
|
390
|
-
</td>
|
|
391
|
-
<td>
|
|
392
|
-
<a
|
|
393
|
-
href="http://fake.url/job"
|
|
394
|
-
>
|
|
395
|
-
[
|
|
396
|
-
1
|
|
397
|
-
]
|
|
398
|
-
</a>
|
|
399
|
-
</td>
|
|
400
304
|
</tr>
|
|
401
305
|
</tbody>
|
|
402
306
|
</table>
|
|
@@ -696,13 +696,7 @@ export const DataJunctionAPI = {
|
|
|
696
696
|
);
|
|
697
697
|
return { status: response.status, json: await response.json() };
|
|
698
698
|
},
|
|
699
|
-
materialize: async function (
|
|
700
|
-
nodeName,
|
|
701
|
-
engineName,
|
|
702
|
-
engineVersion,
|
|
703
|
-
schedule,
|
|
704
|
-
config,
|
|
705
|
-
) {
|
|
699
|
+
materialize: async function (nodeName, jobType, strategy, schedule, config) {
|
|
706
700
|
const response = await fetch(
|
|
707
701
|
`${DJ_URL}/nodes/${nodeName}/materialization`,
|
|
708
702
|
{
|
|
@@ -711,12 +705,10 @@ export const DataJunctionAPI = {
|
|
|
711
705
|
'Content-Type': 'application/json',
|
|
712
706
|
},
|
|
713
707
|
body: JSON.stringify({
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
version: engineVersion,
|
|
717
|
-
},
|
|
708
|
+
job: jobType,
|
|
709
|
+
strategy: strategy,
|
|
718
710
|
schedule: schedule,
|
|
719
|
-
config:
|
|
711
|
+
config: config,
|
|
720
712
|
}),
|
|
721
713
|
credentials: 'include',
|
|
722
714
|
},
|
|
@@ -756,4 +748,11 @@ export const DataJunctionAPI = {
|
|
|
756
748
|
});
|
|
757
749
|
return await response.json();
|
|
758
750
|
},
|
|
751
|
+
materializationInfo: async function () {
|
|
752
|
+
return await (
|
|
753
|
+
await fetch(`${DJ_URL}/materialization/info`, {
|
|
754
|
+
credentials: 'include',
|
|
755
|
+
})
|
|
756
|
+
).json();
|
|
757
|
+
},
|
|
759
758
|
};
|
|
@@ -826,10 +826,10 @@ describe('DataJunctionAPI', () => {
|
|
|
826
826
|
fetch.mockResponseOnce(JSON.stringify({}));
|
|
827
827
|
await DataJunctionAPI.materialize(
|
|
828
828
|
'default.hard_hat',
|
|
829
|
-
'
|
|
830
|
-
'
|
|
829
|
+
'spark_sql',
|
|
830
|
+
'full',
|
|
831
831
|
'@daily',
|
|
832
|
-
|
|
832
|
+
{},
|
|
833
833
|
);
|
|
834
834
|
expect(fetch).toHaveBeenCalledWith(
|
|
835
835
|
`${DJ_URL}/nodes/default.hard_hat/materialization`,
|
|
@@ -839,10 +839,8 @@ describe('DataJunctionAPI', () => {
|
|
|
839
839
|
'Content-Type': 'application/json',
|
|
840
840
|
},
|
|
841
841
|
body: JSON.stringify({
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
version: '3.3',
|
|
845
|
-
},
|
|
842
|
+
job: 'spark_sql',
|
|
843
|
+
strategy: 'full',
|
|
846
844
|
schedule: '@daily',
|
|
847
845
|
config: {},
|
|
848
846
|
}),
|