datajunction-ui 0.0.1-a35.dev0 → 0.0.1-a37
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/components/forms/Action.jsx +8 -0
- package/src/app/components/forms/NodeNameField.jsx +64 -0
- package/src/app/components/forms/NodeTagsInput.jsx +61 -0
- package/src/app/index.tsx +18 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +1 -1
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +2 -5
- package/src/app/pages/AddEditNodePage/index.jsx +16 -7
- package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +154 -0
- package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +79 -0
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +405 -0
- package/src/app/pages/CubeBuilderPage/index.jsx +254 -0
- package/src/app/pages/NamespacePage/index.jsx +5 -0
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +13 -6
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +47 -24
- package/src/app/pages/NodePage/NodeInfoTab.jsx +6 -1
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +44 -32
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +0 -1
- 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 +66 -12
- package/src/app/services/__tests__/DJService.test.jsx +72 -7
- package/src/styles/index.css +4 -0
- package/src/styles/node-creation.scss +12 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
import DJClientContext from '../../../providers/djclient';
|
|
3
|
+
import { CubeBuilderPage } from '../index';
|
|
4
|
+
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
|
5
|
+
import React from 'react';
|
|
6
|
+
|
|
7
|
+
const mockDjClient = {
|
|
8
|
+
metrics: jest.fn(),
|
|
9
|
+
commonDimensions: jest.fn(),
|
|
10
|
+
createCube: jest.fn(),
|
|
11
|
+
namespaces: jest.fn(),
|
|
12
|
+
cube: jest.fn(),
|
|
13
|
+
node: jest.fn(),
|
|
14
|
+
listTags: jest.fn(),
|
|
15
|
+
tagsNode: jest.fn(),
|
|
16
|
+
patchCube: jest.fn(),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const mockMetrics = [
|
|
20
|
+
'default.num_repair_orders',
|
|
21
|
+
'default.avg_repair_price',
|
|
22
|
+
'default.total_repair_cost',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const mockCube = {
|
|
26
|
+
node_revision_id: 102,
|
|
27
|
+
node_id: 33,
|
|
28
|
+
type: 'cube',
|
|
29
|
+
name: 'default.repair_orders_cube',
|
|
30
|
+
display_name: 'Default: Repair Orders Cube',
|
|
31
|
+
version: 'v4.0',
|
|
32
|
+
description: 'Repairs cube',
|
|
33
|
+
availability: null,
|
|
34
|
+
cube_elements: [
|
|
35
|
+
{
|
|
36
|
+
name: 'default_DOT_total_repair_cost',
|
|
37
|
+
display_name: 'Total Repair Cost',
|
|
38
|
+
node_name: 'default.total_repair_cost',
|
|
39
|
+
type: 'metric',
|
|
40
|
+
partition: null,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: 'default_DOT_num_repair_orders',
|
|
44
|
+
display_name: 'Num Repair Orders',
|
|
45
|
+
node_name: 'default.num_repair_orders',
|
|
46
|
+
type: 'metric',
|
|
47
|
+
partition: null,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'country',
|
|
51
|
+
display_name: 'Country',
|
|
52
|
+
node_name: 'default.hard_hat',
|
|
53
|
+
type: 'dimension',
|
|
54
|
+
partition: null,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'state',
|
|
58
|
+
display_name: 'State',
|
|
59
|
+
node_name: 'default.hard_hat',
|
|
60
|
+
type: 'dimension',
|
|
61
|
+
partition: null,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
query: '',
|
|
65
|
+
columns: [
|
|
66
|
+
{
|
|
67
|
+
name: 'default.total_repair_cost',
|
|
68
|
+
display_name: 'Total Repair Cost',
|
|
69
|
+
type: 'double',
|
|
70
|
+
attributes: [],
|
|
71
|
+
dimension: null,
|
|
72
|
+
partition: null,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
name: 'default.num_repair_orders',
|
|
76
|
+
display_name: 'Num Repair Orders',
|
|
77
|
+
type: 'bigint',
|
|
78
|
+
attributes: [],
|
|
79
|
+
dimension: null,
|
|
80
|
+
partition: null,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: 'default.hard_hat.country',
|
|
84
|
+
display_name: 'Country',
|
|
85
|
+
type: 'string',
|
|
86
|
+
attributes: [],
|
|
87
|
+
dimension: null,
|
|
88
|
+
partition: null,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'default.hard_hat.state',
|
|
92
|
+
display_name: 'State',
|
|
93
|
+
type: 'string',
|
|
94
|
+
attributes: [],
|
|
95
|
+
dimension: null,
|
|
96
|
+
partition: null,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
updated_at: '2023-12-03T06:51:09.598532+00:00',
|
|
100
|
+
materializations: [],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const mockCommonDimensions = [
|
|
104
|
+
{
|
|
105
|
+
name: 'default.date_dim.dateint',
|
|
106
|
+
type: 'timestamp',
|
|
107
|
+
node_name: 'default.date_dim',
|
|
108
|
+
node_display_name: 'Date',
|
|
109
|
+
is_primary_key: false,
|
|
110
|
+
path: [
|
|
111
|
+
'default.repair_order_details.repair_order_id',
|
|
112
|
+
'default.repair_order.hard_hat_id',
|
|
113
|
+
'default.hard_hat.birth_date',
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'default.date_dim.dateint',
|
|
118
|
+
type: 'timestamp',
|
|
119
|
+
node_name: 'default.date_dim',
|
|
120
|
+
node_display_name: 'Date',
|
|
121
|
+
is_primary_key: true,
|
|
122
|
+
path: [
|
|
123
|
+
'default.repair_order_details.repair_order_id',
|
|
124
|
+
'default.repair_order.hard_hat_id',
|
|
125
|
+
'default.hard_hat.hire_date',
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'default.date_dim.day',
|
|
130
|
+
type: 'int',
|
|
131
|
+
node_name: 'default.date_dim',
|
|
132
|
+
node_display_name: 'Date',
|
|
133
|
+
is_primary_key: false,
|
|
134
|
+
path: [
|
|
135
|
+
'default.repair_order_details.repair_order_id',
|
|
136
|
+
'default.repair_order.hard_hat_id',
|
|
137
|
+
'default.hard_hat.birth_date',
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'default.date_dim.day',
|
|
142
|
+
type: 'int',
|
|
143
|
+
node_name: 'default.date_dim',
|
|
144
|
+
node_display_name: 'Date',
|
|
145
|
+
is_primary_key: false,
|
|
146
|
+
path: [
|
|
147
|
+
'default.repair_order_details.repair_order_id',
|
|
148
|
+
'default.repair_order.hard_hat_id',
|
|
149
|
+
'default.hard_hat.hire_date',
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'default.date_dim.month',
|
|
154
|
+
type: 'int',
|
|
155
|
+
node_name: 'default.date_dim',
|
|
156
|
+
node_display_name: 'Date',
|
|
157
|
+
is_primary_key: false,
|
|
158
|
+
path: [
|
|
159
|
+
'default.repair_order_details.repair_order_id',
|
|
160
|
+
'default.repair_order.hard_hat_id',
|
|
161
|
+
'default.hard_hat.birth_date',
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'default.date_dim.month',
|
|
166
|
+
type: 'int',
|
|
167
|
+
node_name: 'default.date_dim',
|
|
168
|
+
node_display_name: 'Date',
|
|
169
|
+
is_primary_key: false,
|
|
170
|
+
path: [
|
|
171
|
+
'default.repair_order_details.repair_order_id',
|
|
172
|
+
'default.repair_order.hard_hat_id',
|
|
173
|
+
'default.hard_hat.hire_date',
|
|
174
|
+
],
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'default.date_dim.year',
|
|
178
|
+
type: 'int',
|
|
179
|
+
node_name: 'default.date_dim',
|
|
180
|
+
node_display_name: 'Date',
|
|
181
|
+
is_primary_key: false,
|
|
182
|
+
path: [
|
|
183
|
+
'default.repair_order_details.repair_order_id',
|
|
184
|
+
'default.repair_order.hard_hat_id',
|
|
185
|
+
'default.hard_hat.birth_date',
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'default.date_dim.year',
|
|
190
|
+
type: 'int',
|
|
191
|
+
node_name: 'default.date_dim',
|
|
192
|
+
node_display_name: 'Date',
|
|
193
|
+
is_primary_key: false,
|
|
194
|
+
path: [
|
|
195
|
+
'default.repair_order_details.repair_order_id',
|
|
196
|
+
'default.repair_order.hard_hat_id',
|
|
197
|
+
'default.hard_hat.hire_date',
|
|
198
|
+
],
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
|
|
202
|
+
describe('CubeBuilderPage', () => {
|
|
203
|
+
beforeEach(() => {
|
|
204
|
+
mockDjClient.metrics.mockResolvedValue(mockMetrics);
|
|
205
|
+
mockDjClient.commonDimensions.mockResolvedValue(mockCommonDimensions);
|
|
206
|
+
mockDjClient.createCube.mockResolvedValue({ status: 201, json: {} });
|
|
207
|
+
mockDjClient.namespaces.mockResolvedValue(['default']);
|
|
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: {} });
|
|
213
|
+
|
|
214
|
+
window.scrollTo = jest.fn();
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
afterEach(() => {
|
|
218
|
+
jest.clearAllMocks();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('renders without crashing', () => {
|
|
222
|
+
render(
|
|
223
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
224
|
+
<CubeBuilderPage />
|
|
225
|
+
</DJClientContext.Provider>,
|
|
226
|
+
);
|
|
227
|
+
expect(screen.getByText('Cube')).toBeInTheDocument();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('renders the Metrics section', () => {
|
|
231
|
+
render(
|
|
232
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
233
|
+
<CubeBuilderPage />
|
|
234
|
+
</DJClientContext.Provider>,
|
|
235
|
+
);
|
|
236
|
+
expect(screen.getByText('Metrics *')).toBeInTheDocument();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('renders the Dimensions section', () => {
|
|
240
|
+
render(
|
|
241
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
242
|
+
<CubeBuilderPage />
|
|
243
|
+
</DJClientContext.Provider>,
|
|
244
|
+
);
|
|
245
|
+
expect(screen.getByText('Dimensions *')).toBeInTheDocument();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('creates a new cube', async () => {
|
|
249
|
+
render(
|
|
250
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
251
|
+
<CubeBuilderPage />
|
|
252
|
+
</DJClientContext.Provider>,
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
await waitFor(() => {
|
|
256
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const selectMetrics = screen.getAllByTestId('select-metrics')[0];
|
|
260
|
+
expect(selectMetrics).toBeDefined();
|
|
261
|
+
expect(selectMetrics).not.toBeNull();
|
|
262
|
+
expect(screen.getAllByText('3 Available Metrics')[0]).toBeInTheDocument();
|
|
263
|
+
|
|
264
|
+
fireEvent.keyDown(selectMetrics.firstChild, { key: 'ArrowDown' });
|
|
265
|
+
for (const metric of mockMetrics) {
|
|
266
|
+
await waitFor(() => {
|
|
267
|
+
expect(screen.getByText(metric)).toBeInTheDocument();
|
|
268
|
+
fireEvent.click(screen.getByText(metric));
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
fireEvent.click(screen.getAllByText('Dimensions *')[0]);
|
|
272
|
+
|
|
273
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
274
|
+
|
|
275
|
+
const selectDimensions = screen.getAllByTestId('select-dimensions')[0];
|
|
276
|
+
expect(selectDimensions).toBeDefined();
|
|
277
|
+
expect(selectDimensions).not.toBeNull();
|
|
278
|
+
expect(
|
|
279
|
+
screen.getByText(
|
|
280
|
+
'default.repair_order_details.repair_order_id → default.repair_order.hard_hat_id → default.hard_hat.birth_date',
|
|
281
|
+
),
|
|
282
|
+
).toBeInTheDocument();
|
|
283
|
+
|
|
284
|
+
const selectDimensionsDate = screen.getAllByTestId(
|
|
285
|
+
'dimensions-default.date_dim',
|
|
286
|
+
)[0];
|
|
287
|
+
|
|
288
|
+
fireEvent.keyDown(selectDimensionsDate.firstChild, { key: 'ArrowDown' });
|
|
289
|
+
fireEvent.click(screen.getByText('Day'));
|
|
290
|
+
fireEvent.click(screen.getByText('Month'));
|
|
291
|
+
fireEvent.click(screen.getByText('Year'));
|
|
292
|
+
fireEvent.click(screen.getByText('Dateint'));
|
|
293
|
+
|
|
294
|
+
// Save
|
|
295
|
+
const createCube = screen.getAllByRole('button', {
|
|
296
|
+
name: 'CreateCube',
|
|
297
|
+
})[0];
|
|
298
|
+
expect(createCube).toBeInTheDocument();
|
|
299
|
+
|
|
300
|
+
await waitFor(() => {
|
|
301
|
+
fireEvent.click(createCube);
|
|
302
|
+
});
|
|
303
|
+
await waitFor(() => {
|
|
304
|
+
expect(mockDjClient.createCube).toHaveBeenCalledWith(
|
|
305
|
+
'',
|
|
306
|
+
'',
|
|
307
|
+
'',
|
|
308
|
+
'draft',
|
|
309
|
+
[
|
|
310
|
+
'default.num_repair_orders',
|
|
311
|
+
'default.avg_repair_price',
|
|
312
|
+
'default.total_repair_cost',
|
|
313
|
+
],
|
|
314
|
+
[
|
|
315
|
+
'default.date_dim.day',
|
|
316
|
+
'default.date_dim.month',
|
|
317
|
+
'default.date_dim.year',
|
|
318
|
+
'default.date_dim.dateint',
|
|
319
|
+
],
|
|
320
|
+
[],
|
|
321
|
+
);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
const renderEditNode = element => {
|
|
326
|
+
return render(
|
|
327
|
+
<MemoryRouter
|
|
328
|
+
initialEntries={['/nodes/default.repair_orders_cube/edit-cube']}
|
|
329
|
+
>
|
|
330
|
+
<Routes>
|
|
331
|
+
<Route path="nodes/:name/edit-cube" element={element} />
|
|
332
|
+
</Routes>
|
|
333
|
+
</MemoryRouter>,
|
|
334
|
+
);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
it('updates an existing cube', async () => {
|
|
338
|
+
renderEditNode(
|
|
339
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
340
|
+
<CubeBuilderPage />
|
|
341
|
+
</DJClientContext.Provider>,
|
|
342
|
+
);
|
|
343
|
+
expect(screen.getAllByText('Edit')[0]).toBeInTheDocument();
|
|
344
|
+
await waitFor(() => {
|
|
345
|
+
expect(mockDjClient.cube).toHaveBeenCalled();
|
|
346
|
+
});
|
|
347
|
+
await waitFor(() => {
|
|
348
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const selectMetrics = screen.getAllByTestId('select-metrics')[0];
|
|
352
|
+
expect(selectMetrics).toBeDefined();
|
|
353
|
+
expect(selectMetrics).not.toBeNull();
|
|
354
|
+
expect(screen.getByText('default.num_repair_orders')).toBeInTheDocument();
|
|
355
|
+
|
|
356
|
+
fireEvent.click(screen.getAllByText('Dimensions *')[0]);
|
|
357
|
+
|
|
358
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
359
|
+
|
|
360
|
+
const selectDimensions = screen.getAllByTestId('select-dimensions')[0];
|
|
361
|
+
expect(selectDimensions).toBeDefined();
|
|
362
|
+
expect(selectDimensions).not.toBeNull();
|
|
363
|
+
expect(
|
|
364
|
+
screen.getByText(
|
|
365
|
+
'default.repair_order_details.repair_order_id → default.repair_order.hard_hat_id → default.hard_hat.birth_date',
|
|
366
|
+
),
|
|
367
|
+
).toBeInTheDocument();
|
|
368
|
+
|
|
369
|
+
const selectDimensionsDate = screen.getAllByTestId(
|
|
370
|
+
'dimensions-default.date_dim',
|
|
371
|
+
)[0];
|
|
372
|
+
|
|
373
|
+
fireEvent.keyDown(selectDimensionsDate.firstChild, { key: 'ArrowDown' });
|
|
374
|
+
fireEvent.click(screen.getByText('Day'));
|
|
375
|
+
fireEvent.click(screen.getByText('Month'));
|
|
376
|
+
fireEvent.click(screen.getByText('Year'));
|
|
377
|
+
fireEvent.click(screen.getByText('Dateint'));
|
|
378
|
+
|
|
379
|
+
// Save
|
|
380
|
+
const createCube = screen.getAllByRole('button', {
|
|
381
|
+
name: 'CreateCube',
|
|
382
|
+
})[0];
|
|
383
|
+
expect(createCube).toBeInTheDocument();
|
|
384
|
+
|
|
385
|
+
await waitFor(() => {
|
|
386
|
+
fireEvent.click(createCube);
|
|
387
|
+
});
|
|
388
|
+
await waitFor(() => {
|
|
389
|
+
expect(mockDjClient.patchCube).toHaveBeenCalledWith(
|
|
390
|
+
'default.repair_orders_cube',
|
|
391
|
+
'Default: Repair Orders Cube',
|
|
392
|
+
'Repairs cube',
|
|
393
|
+
'draft',
|
|
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
|
+
],
|
|
401
|
+
[],
|
|
402
|
+
);
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
});
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import React, { useContext, useEffect, useState } from 'react';
|
|
2
|
+
import NamespaceHeader from '../../components/NamespaceHeader';
|
|
3
|
+
import { DataJunctionAPI } from '../../services/DJService';
|
|
4
|
+
import DJClientContext from '../../providers/djclient';
|
|
5
|
+
import 'react-querybuilder/dist/query-builder.scss';
|
|
6
|
+
import 'styles/styles.scss';
|
|
7
|
+
import { ErrorMessage, Field, Form, Formik } from 'formik';
|
|
8
|
+
import { displayMessageAfterSubmit } from '../../../utils/form';
|
|
9
|
+
import { useParams } from 'react-router-dom';
|
|
10
|
+
import { Action } from '../../components/forms/Action';
|
|
11
|
+
import NodeNameField from '../../components/forms/NodeNameField';
|
|
12
|
+
import NodeTagsInput from '../../components/forms/NodeTagsInput';
|
|
13
|
+
import { MetricsSelect } from './MetricsSelect';
|
|
14
|
+
import { DimensionsSelect } from './DimensionsSelect';
|
|
15
|
+
|
|
16
|
+
export function CubeBuilderPage() {
|
|
17
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
18
|
+
|
|
19
|
+
let { nodeType, initialNamespace, name } = useParams();
|
|
20
|
+
const action = name !== undefined ? Action.Edit : Action.Add;
|
|
21
|
+
const validator = ruleType => !!ruleType.value;
|
|
22
|
+
|
|
23
|
+
const initialValues = {
|
|
24
|
+
name: action === Action.Edit ? name : '',
|
|
25
|
+
namespace: action === Action.Add ? initialNamespace : '',
|
|
26
|
+
display_name: '',
|
|
27
|
+
description: '',
|
|
28
|
+
mode: 'draft',
|
|
29
|
+
metrics: [],
|
|
30
|
+
dimensions: [],
|
|
31
|
+
filters: [],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleSubmit = (values, { setSubmitting, setStatus }) => {
|
|
35
|
+
if (action === Action.Add) {
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
createNode(values, setStatus);
|
|
38
|
+
setSubmitting(false);
|
|
39
|
+
}, 400);
|
|
40
|
+
} else {
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
patchNode(values, setStatus);
|
|
43
|
+
setSubmitting(false);
|
|
44
|
+
}, 400);
|
|
45
|
+
}
|
|
46
|
+
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const createNode = async (values, setStatus) => {
|
|
50
|
+
const { status, json } = await djClient.createCube(
|
|
51
|
+
values.name,
|
|
52
|
+
values.display_name,
|
|
53
|
+
values.description,
|
|
54
|
+
values.mode,
|
|
55
|
+
values.metrics,
|
|
56
|
+
values.dimensions,
|
|
57
|
+
values.filters || [],
|
|
58
|
+
);
|
|
59
|
+
if (status === 200 || status === 201) {
|
|
60
|
+
if (values.tags) {
|
|
61
|
+
await djClient.tagsNode(values.name, values.tags);
|
|
62
|
+
}
|
|
63
|
+
setStatus({
|
|
64
|
+
success: (
|
|
65
|
+
<>
|
|
66
|
+
Successfully created {json.type} node{' '}
|
|
67
|
+
<a href={`/nodes/${json.name}`}>{json.name}</a>!
|
|
68
|
+
</>
|
|
69
|
+
),
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
setStatus({
|
|
73
|
+
failure: `${json.message}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const patchNode = async (values, setStatus) => {
|
|
79
|
+
const { status, json } = await djClient.patchCube(
|
|
80
|
+
values.name,
|
|
81
|
+
values.display_name,
|
|
82
|
+
values.description,
|
|
83
|
+
values.mode,
|
|
84
|
+
values.metrics,
|
|
85
|
+
values.dimensions,
|
|
86
|
+
values.filters || [],
|
|
87
|
+
);
|
|
88
|
+
const tagsResponse = await djClient.tagsNode(
|
|
89
|
+
values.name,
|
|
90
|
+
(values.tags || []).map(tag => tag),
|
|
91
|
+
);
|
|
92
|
+
if ((status === 200 || status === 201) && tagsResponse.status === 200) {
|
|
93
|
+
setStatus({
|
|
94
|
+
success: (
|
|
95
|
+
<>
|
|
96
|
+
Successfully updated {json.type} node{' '}
|
|
97
|
+
<a href={`/nodes/${json.name}`}>{json.name}</a>!
|
|
98
|
+
</>
|
|
99
|
+
),
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
setStatus({
|
|
103
|
+
failure: `${json.message}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const updateFieldsWithNodeData = (data, setFieldValue) => {
|
|
109
|
+
setFieldValue('display_name', data.display_name || '', false);
|
|
110
|
+
setFieldValue('description', data.description || '', false);
|
|
111
|
+
setFieldValue('mode', data.mode || 'draft', false);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const staticFieldsInEdit = () => (
|
|
115
|
+
<>
|
|
116
|
+
<div className="NodeNameInput NodeCreationInput">
|
|
117
|
+
<label htmlFor="name">Name</label> {name}
|
|
118
|
+
</div>
|
|
119
|
+
<div className="NodeNameInput NodeCreationInput">
|
|
120
|
+
<label htmlFor="name">Type</label> cube
|
|
121
|
+
</div>
|
|
122
|
+
<div className="DisplayNameInput NodeCreationInput">
|
|
123
|
+
<ErrorMessage name="display_name" component="span" />
|
|
124
|
+
<label htmlFor="displayName">Display Name</label>
|
|
125
|
+
<Field
|
|
126
|
+
type="text"
|
|
127
|
+
name="display_name"
|
|
128
|
+
id="displayName"
|
|
129
|
+
placeholder="Human readable display name"
|
|
130
|
+
/>
|
|
131
|
+
</div>
|
|
132
|
+
</>
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// @ts-ignore
|
|
136
|
+
return (
|
|
137
|
+
<>
|
|
138
|
+
<div className="mid">
|
|
139
|
+
<NamespaceHeader namespace="" />
|
|
140
|
+
<Formik
|
|
141
|
+
initialValues={initialValues}
|
|
142
|
+
validate={validator}
|
|
143
|
+
onSubmit={handleSubmit}
|
|
144
|
+
>
|
|
145
|
+
{function Render({ isSubmitting, status, setFieldValue, props }) {
|
|
146
|
+
const [node, setNode] = useState([]);
|
|
147
|
+
|
|
148
|
+
// Get cube
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
const fetchData = async () => {
|
|
151
|
+
if (name) {
|
|
152
|
+
const node = await djClient.node(name);
|
|
153
|
+
const cube = await djClient.cube(name);
|
|
154
|
+
cube.tags = node.tags;
|
|
155
|
+
setNode(cube);
|
|
156
|
+
updateFieldsWithNodeData(cube, setFieldValue);
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
fetchData().catch(console.error);
|
|
160
|
+
}, [djClient, djClient.metrics, name]);
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<Form>
|
|
164
|
+
<div className="card">
|
|
165
|
+
<div className="card-header">
|
|
166
|
+
<h2>
|
|
167
|
+
{action === Action.Edit ? 'Edit' : 'Create'}{' '}
|
|
168
|
+
<span
|
|
169
|
+
className={`node_type__cube node_type_creation_heading`}
|
|
170
|
+
>
|
|
171
|
+
Cube
|
|
172
|
+
</span>
|
|
173
|
+
</h2>
|
|
174
|
+
{displayMessageAfterSubmit(status)}
|
|
175
|
+
{action === Action.Add ? (
|
|
176
|
+
<NodeNameField />
|
|
177
|
+
) : (
|
|
178
|
+
staticFieldsInEdit(node)
|
|
179
|
+
)}
|
|
180
|
+
<div className="DescriptionInput NodeCreationInput">
|
|
181
|
+
<ErrorMessage name="description" component="span" />
|
|
182
|
+
<label htmlFor="Description">Description</label>
|
|
183
|
+
<Field
|
|
184
|
+
type="textarea"
|
|
185
|
+
as="textarea"
|
|
186
|
+
name="description"
|
|
187
|
+
id="Description"
|
|
188
|
+
placeholder="Describe your node"
|
|
189
|
+
/>
|
|
190
|
+
</div>
|
|
191
|
+
<div className="CubeCreationInput">
|
|
192
|
+
<label htmlFor="react-select-3-input">Metrics *</label>
|
|
193
|
+
<p>Select metrics to include in the cube.</p>
|
|
194
|
+
<span
|
|
195
|
+
data-testid="select-metrics"
|
|
196
|
+
style={{ marginTop: '15px' }}
|
|
197
|
+
>
|
|
198
|
+
{action === Action.Edit ? (
|
|
199
|
+
<MetricsSelect cube={node} />
|
|
200
|
+
) : (
|
|
201
|
+
<MetricsSelect />
|
|
202
|
+
)}
|
|
203
|
+
</span>
|
|
204
|
+
</div>
|
|
205
|
+
<br />
|
|
206
|
+
<br />
|
|
207
|
+
<div className="CubeCreationInput">
|
|
208
|
+
<label htmlFor="react-select-3-input">Dimensions *</label>
|
|
209
|
+
<p>
|
|
210
|
+
Select dimensions to include in the cube. As metrics are
|
|
211
|
+
selected above, the list of available dimensions will be
|
|
212
|
+
filtered to those shared by the selected metrics. If the
|
|
213
|
+
dimensions list is empty, no shared dimensions were
|
|
214
|
+
discovered.
|
|
215
|
+
</p>
|
|
216
|
+
<span data-testid="select-dimensions">
|
|
217
|
+
{action === Action.Edit ? (
|
|
218
|
+
<DimensionsSelect cube={node} />
|
|
219
|
+
) : (
|
|
220
|
+
<DimensionsSelect />
|
|
221
|
+
)}
|
|
222
|
+
</span>
|
|
223
|
+
</div>
|
|
224
|
+
<div className="NodeModeInput NodeCreationInput">
|
|
225
|
+
<ErrorMessage name="mode" component="span" />
|
|
226
|
+
<label htmlFor="Mode">Mode</label>
|
|
227
|
+
<Field as="select" name="mode" id="Mode">
|
|
228
|
+
<option value="draft">Draft</option>
|
|
229
|
+
<option value="published">Published</option>
|
|
230
|
+
</Field>
|
|
231
|
+
</div>
|
|
232
|
+
<NodeTagsInput action={action} node={node} />
|
|
233
|
+
<button
|
|
234
|
+
type="submit"
|
|
235
|
+
disabled={isSubmitting}
|
|
236
|
+
aria-label="CreateCube"
|
|
237
|
+
>
|
|
238
|
+
{action === Action.Add ? 'Create Cube' : 'Save'}{' '}
|
|
239
|
+
{nodeType}
|
|
240
|
+
</button>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
</Form>
|
|
244
|
+
);
|
|
245
|
+
}}
|
|
246
|
+
</Formik>
|
|
247
|
+
</div>
|
|
248
|
+
</>
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
CubeBuilderPage.defaultProps = {
|
|
253
|
+
djClient: DataJunctionAPI,
|
|
254
|
+
};
|
|
@@ -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>
|