datajunction-ui 0.0.43 → 0.0.45
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/__tests__/NamespaceHeader.test.jsx +349 -1
- package/src/app/pages/NodePage/NodeInfoTab.jsx +38 -40
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +0 -133
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +9 -7
- package/src/app/pages/NodePage/index.jsx +12 -11
- package/src/app/pages/QueryPlannerPage/PreAggDetailsPanel.jsx +46 -1
- package/src/app/pages/QueryPlannerPage/ResultsView.jsx +281 -0
- package/src/app/pages/QueryPlannerPage/SelectionPanel.jsx +225 -100
- package/src/app/pages/QueryPlannerPage/__tests__/MetricFlowGraph.test.jsx +193 -0
- package/src/app/pages/QueryPlannerPage/__tests__/ResultsView.test.jsx +388 -0
- package/src/app/pages/QueryPlannerPage/__tests__/SelectionPanel.test.jsx +31 -51
- package/src/app/pages/QueryPlannerPage/__tests__/index.test.jsx +720 -34
- package/src/app/pages/QueryPlannerPage/index.jsx +237 -117
- package/src/app/pages/QueryPlannerPage/styles.css +765 -15
- package/src/app/services/DJService.js +29 -6
- package/src/app/services/__tests__/DJService.test.jsx +163 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
3
3
|
import { createRenderer } from 'react-test-renderer/shallow';
|
|
4
4
|
import { MemoryRouter } from 'react-router-dom';
|
|
5
5
|
|
|
@@ -160,4 +160,352 @@ describe('<NamespaceHeader />', () => {
|
|
|
160
160
|
expect(screen.getByText('namespace')).toBeInTheDocument();
|
|
161
161
|
expect(screen.queryByText(/Git Managed/)).not.toBeInTheDocument();
|
|
162
162
|
});
|
|
163
|
+
|
|
164
|
+
it('should open dropdown when clicking the git managed button', async () => {
|
|
165
|
+
const mockDjClient = {
|
|
166
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
167
|
+
total_deployments: 5,
|
|
168
|
+
primary_source: {
|
|
169
|
+
type: 'git',
|
|
170
|
+
repository: 'github.com/test/repo',
|
|
171
|
+
branch: 'main',
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
listDeployments: jest.fn().mockResolvedValue([
|
|
175
|
+
{
|
|
176
|
+
uuid: 'deploy-1',
|
|
177
|
+
status: 'success',
|
|
178
|
+
created_at: '2024-01-15T10:00:00Z',
|
|
179
|
+
source: {
|
|
180
|
+
type: 'git',
|
|
181
|
+
repository: 'github.com/test/repo',
|
|
182
|
+
branch: 'main',
|
|
183
|
+
commit_sha: 'abc1234567890',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
]),
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
render(
|
|
190
|
+
<MemoryRouter>
|
|
191
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
192
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
193
|
+
</DJClientContext.Provider>
|
|
194
|
+
</MemoryRouter>,
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
await waitFor(() => {
|
|
198
|
+
expect(screen.getByText(/Git Managed/)).toBeInTheDocument();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Click the dropdown button
|
|
202
|
+
fireEvent.click(screen.getByText(/Git Managed/));
|
|
203
|
+
|
|
204
|
+
// Should show repository link in dropdown
|
|
205
|
+
await waitFor(() => {
|
|
206
|
+
expect(screen.getByText(/github.com\/test\/repo/)).toBeInTheDocument();
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should open dropdown when clicking local deploy button', async () => {
|
|
211
|
+
const mockDjClient = {
|
|
212
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
213
|
+
total_deployments: 2,
|
|
214
|
+
primary_source: {
|
|
215
|
+
type: 'local',
|
|
216
|
+
hostname: 'localhost',
|
|
217
|
+
},
|
|
218
|
+
}),
|
|
219
|
+
listDeployments: jest.fn().mockResolvedValue([
|
|
220
|
+
{
|
|
221
|
+
uuid: 'deploy-1',
|
|
222
|
+
status: 'success',
|
|
223
|
+
created_at: '2024-01-15T10:00:00Z',
|
|
224
|
+
created_by: 'testuser',
|
|
225
|
+
source: {
|
|
226
|
+
type: 'local',
|
|
227
|
+
hostname: 'localhost',
|
|
228
|
+
reason: 'testing',
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
]),
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
render(
|
|
235
|
+
<MemoryRouter>
|
|
236
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
237
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
238
|
+
</DJClientContext.Provider>
|
|
239
|
+
</MemoryRouter>,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
await waitFor(() => {
|
|
243
|
+
expect(screen.getByText(/Local Deploy/)).toBeInTheDocument();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Click the dropdown button
|
|
247
|
+
fireEvent.click(screen.getByText(/Local Deploy/));
|
|
248
|
+
|
|
249
|
+
// Should show local deploy info in dropdown
|
|
250
|
+
await waitFor(() => {
|
|
251
|
+
expect(screen.getByText(/Local deploys by testuser/)).toBeInTheDocument();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should show recent deployments list with git source', async () => {
|
|
256
|
+
const mockDjClient = {
|
|
257
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
258
|
+
total_deployments: 3,
|
|
259
|
+
primary_source: {
|
|
260
|
+
type: 'git',
|
|
261
|
+
repository: 'github.com/test/repo',
|
|
262
|
+
branch: 'main',
|
|
263
|
+
},
|
|
264
|
+
}),
|
|
265
|
+
listDeployments: jest.fn().mockResolvedValue([
|
|
266
|
+
{
|
|
267
|
+
uuid: 'deploy-1',
|
|
268
|
+
status: 'success',
|
|
269
|
+
created_at: '2024-01-15T10:00:00Z',
|
|
270
|
+
source: {
|
|
271
|
+
type: 'git',
|
|
272
|
+
repository: 'github.com/test/repo',
|
|
273
|
+
branch: 'feature-branch',
|
|
274
|
+
commit_sha: 'abc1234567890',
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
uuid: 'deploy-2',
|
|
279
|
+
status: 'failed',
|
|
280
|
+
created_at: '2024-01-14T10:00:00Z',
|
|
281
|
+
source: {
|
|
282
|
+
type: 'git',
|
|
283
|
+
repository: 'github.com/test/repo',
|
|
284
|
+
branch: 'main',
|
|
285
|
+
commit_sha: 'def4567890123',
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
]),
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
render(
|
|
292
|
+
<MemoryRouter>
|
|
293
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
294
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
295
|
+
</DJClientContext.Provider>
|
|
296
|
+
</MemoryRouter>,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
await waitFor(() => {
|
|
300
|
+
expect(screen.getByText(/Git Managed/)).toBeInTheDocument();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
fireEvent.click(screen.getByText(/Git Managed/));
|
|
304
|
+
|
|
305
|
+
// Should show branch names in deployment list
|
|
306
|
+
await waitFor(() => {
|
|
307
|
+
expect(screen.getByText(/feature-branch/)).toBeInTheDocument();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
// Should show short commit SHA
|
|
311
|
+
expect(screen.getByText(/abc1234/)).toBeInTheDocument();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should show local deployments with reason', async () => {
|
|
315
|
+
const mockDjClient = {
|
|
316
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
317
|
+
total_deployments: 2,
|
|
318
|
+
primary_source: {
|
|
319
|
+
type: 'local',
|
|
320
|
+
},
|
|
321
|
+
}),
|
|
322
|
+
listDeployments: jest.fn().mockResolvedValue([
|
|
323
|
+
{
|
|
324
|
+
uuid: 'deploy-1',
|
|
325
|
+
status: 'success',
|
|
326
|
+
created_at: '2024-01-15T10:00:00Z',
|
|
327
|
+
source: {
|
|
328
|
+
type: 'local',
|
|
329
|
+
reason: 'hotfix deployment',
|
|
330
|
+
hostname: 'dev-machine',
|
|
331
|
+
},
|
|
332
|
+
},
|
|
333
|
+
]),
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
render(
|
|
337
|
+
<MemoryRouter>
|
|
338
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
339
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
340
|
+
</DJClientContext.Provider>
|
|
341
|
+
</MemoryRouter>,
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
await waitFor(() => {
|
|
345
|
+
expect(screen.getByText(/Local Deploy/)).toBeInTheDocument();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
fireEvent.click(screen.getByText(/Local Deploy/));
|
|
349
|
+
|
|
350
|
+
// Should show reason in deployment list
|
|
351
|
+
await waitFor(() => {
|
|
352
|
+
expect(screen.getByText(/hotfix deployment/)).toBeInTheDocument();
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it('should close dropdown when clicking outside', async () => {
|
|
357
|
+
const mockDjClient = {
|
|
358
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
359
|
+
total_deployments: 5,
|
|
360
|
+
primary_source: {
|
|
361
|
+
type: 'git',
|
|
362
|
+
repository: 'github.com/test/repo',
|
|
363
|
+
branch: 'main',
|
|
364
|
+
},
|
|
365
|
+
}),
|
|
366
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
render(
|
|
370
|
+
<MemoryRouter>
|
|
371
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
372
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
373
|
+
</DJClientContext.Provider>
|
|
374
|
+
</MemoryRouter>,
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
await waitFor(() => {
|
|
378
|
+
expect(screen.getByText(/Git Managed/)).toBeInTheDocument();
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// Open dropdown
|
|
382
|
+
fireEvent.click(screen.getByText(/Git Managed/));
|
|
383
|
+
|
|
384
|
+
await waitFor(() => {
|
|
385
|
+
expect(screen.getByText(/github.com\/test\/repo/)).toBeInTheDocument();
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Click outside (on the breadcrumb)
|
|
389
|
+
fireEvent.mouseDown(document.body);
|
|
390
|
+
|
|
391
|
+
// Dropdown should close
|
|
392
|
+
await waitFor(() => {
|
|
393
|
+
expect(
|
|
394
|
+
screen.queryByText(/github.com\/test\/repo.*\(main\)/),
|
|
395
|
+
).not.toBeInTheDocument();
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should toggle dropdown arrow indicator', async () => {
|
|
400
|
+
const mockDjClient = {
|
|
401
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
402
|
+
total_deployments: 5,
|
|
403
|
+
primary_source: {
|
|
404
|
+
type: 'git',
|
|
405
|
+
repository: 'github.com/test/repo',
|
|
406
|
+
branch: 'main',
|
|
407
|
+
},
|
|
408
|
+
}),
|
|
409
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
render(
|
|
413
|
+
<MemoryRouter>
|
|
414
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
415
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
416
|
+
</DJClientContext.Provider>
|
|
417
|
+
</MemoryRouter>,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
await waitFor(() => {
|
|
421
|
+
expect(screen.getByText(/Git Managed/)).toBeInTheDocument();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Initially shows down arrow
|
|
425
|
+
expect(screen.getByText('▼')).toBeInTheDocument();
|
|
426
|
+
|
|
427
|
+
// Click to open
|
|
428
|
+
fireEvent.click(screen.getByText(/Git Managed/));
|
|
429
|
+
|
|
430
|
+
// Should show up arrow when open
|
|
431
|
+
await waitFor(() => {
|
|
432
|
+
expect(screen.getByText('▲')).toBeInTheDocument();
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should handle git repository URL with https prefix', async () => {
|
|
437
|
+
const mockDjClient = {
|
|
438
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
439
|
+
total_deployments: 1,
|
|
440
|
+
primary_source: {
|
|
441
|
+
type: 'git',
|
|
442
|
+
repository: 'https://github.com/test/repo',
|
|
443
|
+
branch: 'main',
|
|
444
|
+
},
|
|
445
|
+
}),
|
|
446
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
render(
|
|
450
|
+
<MemoryRouter>
|
|
451
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
452
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
453
|
+
</DJClientContext.Provider>
|
|
454
|
+
</MemoryRouter>,
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
await waitFor(() => {
|
|
458
|
+
expect(screen.getByText(/Git Managed/)).toBeInTheDocument();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
fireEvent.click(screen.getByText(/Git Managed/));
|
|
462
|
+
|
|
463
|
+
await waitFor(() => {
|
|
464
|
+
// Find link by its text content (repository URL)
|
|
465
|
+
const link = screen.getByRole('link', {
|
|
466
|
+
name: /github\.com\/test\/repo/,
|
|
467
|
+
});
|
|
468
|
+
expect(link).toHaveAttribute('href', 'https://github.com/test/repo');
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should render adhoc deployment label when no created_by', async () => {
|
|
473
|
+
const mockDjClient = {
|
|
474
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
475
|
+
total_deployments: 1,
|
|
476
|
+
primary_source: {
|
|
477
|
+
type: 'local',
|
|
478
|
+
},
|
|
479
|
+
}),
|
|
480
|
+
listDeployments: jest.fn().mockResolvedValue([
|
|
481
|
+
{
|
|
482
|
+
uuid: 'deploy-1',
|
|
483
|
+
status: 'success',
|
|
484
|
+
created_at: '2024-01-15T10:00:00Z',
|
|
485
|
+
created_by: null,
|
|
486
|
+
source: {
|
|
487
|
+
type: 'local',
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
]),
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
render(
|
|
494
|
+
<MemoryRouter>
|
|
495
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
496
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
497
|
+
</DJClientContext.Provider>
|
|
498
|
+
</MemoryRouter>,
|
|
499
|
+
);
|
|
500
|
+
|
|
501
|
+
await waitFor(() => {
|
|
502
|
+
expect(screen.getByText(/Local Deploy/)).toBeInTheDocument();
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
fireEvent.click(screen.getByText(/Local Deploy/));
|
|
506
|
+
|
|
507
|
+
await waitFor(() => {
|
|
508
|
+
expect(screen.getByText(/Local\/adhoc deployments/)).toBeInTheDocument();
|
|
509
|
+
});
|
|
510
|
+
});
|
|
163
511
|
});
|
|
@@ -4,7 +4,6 @@ import { foundation } from 'react-syntax-highlighter/src/styles/hljs';
|
|
|
4
4
|
import sql from 'react-syntax-highlighter/dist/esm/languages/hljs/sql';
|
|
5
5
|
import NodeStatus from './NodeStatus';
|
|
6
6
|
import ListGroupItem from '../../components/ListGroupItem';
|
|
7
|
-
import ToggleSwitch from '../../components/ToggleSwitch';
|
|
8
7
|
import DJClientContext from '../../providers/djclient';
|
|
9
8
|
import { labelize } from '../../../utils/form';
|
|
10
9
|
|
|
@@ -30,9 +29,6 @@ foundation.hljs['padding'] = '2rem';
|
|
|
30
29
|
// }
|
|
31
30
|
|
|
32
31
|
export default function NodeInfoTab({ node }) {
|
|
33
|
-
const [compiledSQL, setCompiledSQL] = useState('');
|
|
34
|
-
const [checked, setChecked] = useState(false);
|
|
35
|
-
|
|
36
32
|
// For metrics
|
|
37
33
|
const [metricInfo, setMetricInfo] = useState(null);
|
|
38
34
|
|
|
@@ -47,22 +43,6 @@ export default function NodeInfoTab({ node }) {
|
|
|
47
43
|
));
|
|
48
44
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
49
45
|
|
|
50
|
-
useEffect(() => {
|
|
51
|
-
const fetchData = async () => {
|
|
52
|
-
if (checked === true) {
|
|
53
|
-
const data = await djClient.compiledSql(node.name);
|
|
54
|
-
if (data.sql) {
|
|
55
|
-
setCompiledSQL(data.sql);
|
|
56
|
-
} else {
|
|
57
|
-
setCompiledSQL(
|
|
58
|
-
'/* Ran into an issue while generating compiled SQL */',
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
};
|
|
63
|
-
fetchData().catch(console.error);
|
|
64
|
-
}, [node, djClient, checked]);
|
|
65
|
-
|
|
66
46
|
useEffect(() => {
|
|
67
47
|
const fetchData = async () => {
|
|
68
48
|
const metric = await djClient.getMetric(node.name);
|
|
@@ -96,9 +76,6 @@ export default function NodeInfoTab({ node }) {
|
|
|
96
76
|
}
|
|
97
77
|
}, [node, djClient]);
|
|
98
78
|
|
|
99
|
-
function toggle(value) {
|
|
100
|
-
return !value;
|
|
101
|
-
}
|
|
102
79
|
const metricsWarning =
|
|
103
80
|
node?.type === 'metric' &&
|
|
104
81
|
metricInfo?.incompatible_druid_functions?.length > 0 ? (
|
|
@@ -149,7 +126,11 @@ export default function NodeInfoTab({ node }) {
|
|
|
149
126
|
)}
|
|
150
127
|
<div>
|
|
151
128
|
<h6 className="mb-0 w-100">Aggregate Expression</h6>
|
|
152
|
-
<SyntaxHighlighter
|
|
129
|
+
<SyntaxHighlighter
|
|
130
|
+
language="sql"
|
|
131
|
+
style={foundation}
|
|
132
|
+
wrapLongLines={true}
|
|
133
|
+
>
|
|
153
134
|
{metricInfo?.expression}
|
|
154
135
|
</SyntaxHighlighter>
|
|
155
136
|
</div>
|
|
@@ -167,18 +148,12 @@ export default function NodeInfoTab({ node }) {
|
|
|
167
148
|
}}
|
|
168
149
|
>
|
|
169
150
|
<h6 className="mb-0 w-100">Query</h6>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
/>
|
|
177
|
-
) : (
|
|
178
|
-
<></>
|
|
179
|
-
)}
|
|
180
|
-
<SyntaxHighlighter language="sql" style={foundation}>
|
|
181
|
-
{checked ? compiledSQL : node?.query}
|
|
151
|
+
<SyntaxHighlighter
|
|
152
|
+
language="sql"
|
|
153
|
+
style={foundation}
|
|
154
|
+
wrapLongLines={true}
|
|
155
|
+
>
|
|
156
|
+
{node?.query}
|
|
182
157
|
</SyntaxHighlighter>
|
|
183
158
|
</div>
|
|
184
159
|
</div>
|
|
@@ -218,7 +193,26 @@ export default function NodeInfoTab({ node }) {
|
|
|
218
193
|
node?.type === 'metric' ? (
|
|
219
194
|
<div className="list-group-item d-flex">
|
|
220
195
|
<div className="d-flex gap-2 w-100 py-3">
|
|
221
|
-
<div>
|
|
196
|
+
<div style={{ marginRight: '2rem' }}>
|
|
197
|
+
<h6 className="mb-0 w-100">Output Type</h6>
|
|
198
|
+
<p
|
|
199
|
+
className="mb-0 opacity-75"
|
|
200
|
+
role="dialog"
|
|
201
|
+
aria-hidden="false"
|
|
202
|
+
aria-label="OutputType"
|
|
203
|
+
>
|
|
204
|
+
<code
|
|
205
|
+
style={{
|
|
206
|
+
background: '#f5f5f5',
|
|
207
|
+
padding: '2px 6px',
|
|
208
|
+
borderRadius: '3px',
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
{node?.columns?.[0]?.type || 'Unknown'}
|
|
212
|
+
</code>
|
|
213
|
+
</p>
|
|
214
|
+
</div>
|
|
215
|
+
<div style={{ marginRight: '2rem' }}>
|
|
222
216
|
<h6 className="mb-0 w-100">Direction</h6>
|
|
223
217
|
<p
|
|
224
218
|
className="mb-0 opacity-75"
|
|
@@ -275,7 +269,11 @@ export default function NodeInfoTab({ node }) {
|
|
|
275
269
|
}}
|
|
276
270
|
>
|
|
277
271
|
<h6 className="mb-0 w-100">Custom Metadata</h6>
|
|
278
|
-
<SyntaxHighlighter
|
|
272
|
+
<SyntaxHighlighter
|
|
273
|
+
language="json"
|
|
274
|
+
style={foundation}
|
|
275
|
+
wrapLongLines={true}
|
|
276
|
+
>
|
|
279
277
|
{JSON.stringify(node.custom_metadata, null, 2)}
|
|
280
278
|
</SyntaxHighlighter>
|
|
281
279
|
</div>
|
|
@@ -331,12 +329,12 @@ export default function NodeInfoTab({ node }) {
|
|
|
331
329
|
<a href={`/nodes/${node?.name}`}>{dim}</a>
|
|
332
330
|
</span>
|
|
333
331
|
))
|
|
334
|
-
:
|
|
332
|
+
: metricInfo?.required_dimensions?.map((dim, idx) => (
|
|
335
333
|
<span
|
|
336
334
|
key={`rd-${idx}`}
|
|
337
335
|
className="rounded-pill badge bg-secondary-soft PrimaryKey"
|
|
338
336
|
>
|
|
339
|
-
<a href={`/nodes/${
|
|
337
|
+
<a href={`/nodes/${metricInfo?.upstream_node}`}>{dim.name}</a>
|
|
340
338
|
</span>
|
|
341
339
|
))}
|
|
342
340
|
</p>
|
|
@@ -591,32 +591,6 @@ describe('<NodePage />', () => {
|
|
|
591
591
|
});
|
|
592
592
|
});
|
|
593
593
|
|
|
594
|
-
it('renders compiled sql correctly', async () => {
|
|
595
|
-
const djClient = mockDJClient();
|
|
596
|
-
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockTransformNode);
|
|
597
|
-
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
|
|
598
|
-
djClient.DataJunctionAPI.compiledSql.mockReturnValue('select 1');
|
|
599
|
-
|
|
600
|
-
const element = (
|
|
601
|
-
<DJClientContext.Provider value={djClient}>
|
|
602
|
-
<NodePage />
|
|
603
|
-
</DJClientContext.Provider>
|
|
604
|
-
);
|
|
605
|
-
render(
|
|
606
|
-
<MemoryRouter initialEntries={[`/nodes/${mocks.mockTransformNode.name}`]}>
|
|
607
|
-
<Routes>
|
|
608
|
-
<Route path="nodes/:name" element={element} />
|
|
609
|
-
</Routes>
|
|
610
|
-
</MemoryRouter>,
|
|
611
|
-
);
|
|
612
|
-
await waitFor(() => {
|
|
613
|
-
fireEvent.click(screen.getByRole('checkbox', { name: 'ToggleSwitch' }));
|
|
614
|
-
expect(djClient.DataJunctionAPI.compiledSql).toHaveBeenCalledWith(
|
|
615
|
-
mocks.mockTransformNode.name,
|
|
616
|
-
);
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
|
|
620
594
|
it('renders an empty NodeMaterialization tab correctly', async () => {
|
|
621
595
|
const djClient = mockDJClient();
|
|
622
596
|
djClient.DataJunctionAPI.node.mockResolvedValue(mocks.mockMetricNode);
|
|
@@ -708,113 +682,6 @@ describe('<NodePage />', () => {
|
|
|
708
682
|
});
|
|
709
683
|
});
|
|
710
684
|
|
|
711
|
-
it('renders the NodeValidate tab', async () => {
|
|
712
|
-
const djClient = mockDJClient();
|
|
713
|
-
window.scrollTo = jest.fn();
|
|
714
|
-
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
|
|
715
|
-
djClient.DataJunctionAPI.nodeDimensions.mockReturnValue([]);
|
|
716
|
-
djClient.DataJunctionAPI.getMetric.mockReturnValue(
|
|
717
|
-
mocks.mockMetricNodeJson,
|
|
718
|
-
);
|
|
719
|
-
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
|
|
720
|
-
djClient.DataJunctionAPI.sql.mockReturnValue({
|
|
721
|
-
sql: 'SELECT * FROM testNode',
|
|
722
|
-
});
|
|
723
|
-
const streamNodeData = {
|
|
724
|
-
onmessage: jest.fn(),
|
|
725
|
-
onerror: jest.fn(),
|
|
726
|
-
close: jest.fn(),
|
|
727
|
-
};
|
|
728
|
-
djClient.DataJunctionAPI.streamNodeData.mockResolvedValue(streamNodeData);
|
|
729
|
-
djClient.DataJunctionAPI.streamNodeData.mockResolvedValueOnce({
|
|
730
|
-
state: 'FINISHED',
|
|
731
|
-
results: [
|
|
732
|
-
{
|
|
733
|
-
columns: [{ name: 'column1' }, { name: 'column2' }],
|
|
734
|
-
rows: [
|
|
735
|
-
[1, 'value1'],
|
|
736
|
-
[2, 'value2'],
|
|
737
|
-
],
|
|
738
|
-
},
|
|
739
|
-
],
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
const element = (
|
|
743
|
-
<DJClientContext.Provider value={djClient}>
|
|
744
|
-
<NodePage />
|
|
745
|
-
</DJClientContext.Provider>
|
|
746
|
-
);
|
|
747
|
-
render(
|
|
748
|
-
<MemoryRouter
|
|
749
|
-
initialEntries={['/nodes/default.num_repair_orders/validate']}
|
|
750
|
-
>
|
|
751
|
-
<Routes>
|
|
752
|
-
<Route path="nodes/:name/:tab" element={element} />
|
|
753
|
-
</Routes>
|
|
754
|
-
</MemoryRouter>,
|
|
755
|
-
);
|
|
756
|
-
|
|
757
|
-
await waitFor(() => {
|
|
758
|
-
expect(screen.getByText('Group By')).toBeInTheDocument();
|
|
759
|
-
expect(screen.getByText('Add Filters')).toBeInTheDocument();
|
|
760
|
-
expect(screen.getByText('Generated Query')).toBeInTheDocument();
|
|
761
|
-
expect(screen.getByText('Results')).toBeInTheDocument();
|
|
762
|
-
});
|
|
763
|
-
// Click on the 'Validate' tab
|
|
764
|
-
fireEvent.click(screen.getByRole('button', { name: '► Validate' }));
|
|
765
|
-
|
|
766
|
-
await waitFor(() => {
|
|
767
|
-
expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
|
|
768
|
-
mocks.mockMetricNode.name,
|
|
769
|
-
);
|
|
770
|
-
expect(djClient.DataJunctionAPI.sql).toHaveBeenCalledWith(
|
|
771
|
-
mocks.mockMetricNode.name,
|
|
772
|
-
{ dimensions: [], filters: [] },
|
|
773
|
-
);
|
|
774
|
-
expect(djClient.DataJunctionAPI.nodeDimensions).toHaveBeenCalledWith(
|
|
775
|
-
mocks.mockMetricNode.name,
|
|
776
|
-
);
|
|
777
|
-
});
|
|
778
|
-
|
|
779
|
-
// Click on 'Run' to run the node query
|
|
780
|
-
const runButton = screen.getByText('► Run');
|
|
781
|
-
fireEvent.click(runButton);
|
|
782
|
-
|
|
783
|
-
await waitFor(() => {
|
|
784
|
-
expect(djClient.DataJunctionAPI.streamNodeData).toHaveBeenCalledWith(
|
|
785
|
-
mocks.mockMetricNode.name,
|
|
786
|
-
{ dimensions: [], filters: [] },
|
|
787
|
-
);
|
|
788
|
-
expect(streamNodeData.onmessage).toBeDefined();
|
|
789
|
-
expect(streamNodeData.onerror).toBeDefined();
|
|
790
|
-
});
|
|
791
|
-
|
|
792
|
-
const infoTab = screen.getByRole('button', { name: 'QueryInfo' });
|
|
793
|
-
const resultsTab = screen.getByText('Results');
|
|
794
|
-
|
|
795
|
-
// Initially, the Results tab should be active
|
|
796
|
-
expect(resultsTab).toHaveClass('active');
|
|
797
|
-
expect(infoTab).not.toHaveClass('active');
|
|
798
|
-
|
|
799
|
-
// Click on the Info tab first
|
|
800
|
-
fireEvent.click(infoTab);
|
|
801
|
-
|
|
802
|
-
await waitFor(() => {
|
|
803
|
-
// Now, the Info tab should be active
|
|
804
|
-
expect(infoTab).toHaveClass('active');
|
|
805
|
-
expect(resultsTab).not.toHaveClass('active');
|
|
806
|
-
});
|
|
807
|
-
|
|
808
|
-
// Click on the Results tab
|
|
809
|
-
fireEvent.click(resultsTab);
|
|
810
|
-
|
|
811
|
-
await waitFor(() => {
|
|
812
|
-
// Now, the Results tab should be active again
|
|
813
|
-
expect(resultsTab).toHaveClass('active');
|
|
814
|
-
expect(infoTab).not.toHaveClass('active');
|
|
815
|
-
});
|
|
816
|
-
});
|
|
817
|
-
|
|
818
685
|
it('renders a NodeColumnLineage tab correctly', async () => {
|
|
819
686
|
const djClient = mockDJClient();
|
|
820
687
|
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
|
|
@@ -4,15 +4,17 @@ exports[`<NodePage /> renders the NodeInfo tab correctly for a metric node 1`] =
|
|
|
4
4
|
HTMLCollection [
|
|
5
5
|
<code
|
|
6
6
|
class="language-sql"
|
|
7
|
-
style="white-space: pre;"
|
|
7
|
+
style="white-space: pre-wrap;"
|
|
8
8
|
>
|
|
9
|
-
<span
|
|
10
|
-
class="hljs-built_in"
|
|
11
|
-
>
|
|
12
|
-
count
|
|
13
|
-
</span>
|
|
14
9
|
<span>
|
|
15
|
-
|
|
10
|
+
<span
|
|
11
|
+
class="hljs-built_in"
|
|
12
|
+
>
|
|
13
|
+
count
|
|
14
|
+
</span>
|
|
15
|
+
<span>
|
|
16
|
+
(repair_order_id)
|
|
17
|
+
</span>
|
|
16
18
|
</span>
|
|
17
19
|
</code>,
|
|
18
20
|
]
|