datajunction-ui 0.0.1-a1 → 0.0.1-a101

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.
Files changed (110) hide show
  1. package/Makefile +7 -1
  2. package/package.json +18 -7
  3. package/public/index.html +1 -1
  4. package/src/app/components/AddNodeDropdown.jsx +44 -0
  5. package/src/app/components/ListGroupItem.jsx +2 -1
  6. package/src/app/components/NodeListActions.jsx +69 -0
  7. package/src/app/components/NodeMaterializationDelete.jsx +80 -0
  8. package/src/app/components/QueryInfo.jsx +96 -1
  9. package/src/app/components/Search.jsx +94 -0
  10. package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
  11. package/src/app/components/__tests__/Search.test.jsx +63 -0
  12. package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +5 -3
  13. package/src/app/components/djgraph/Collapse.jsx +3 -2
  14. package/src/app/components/djgraph/DJNode.jsx +1 -1
  15. package/src/app/components/djgraph/DJNodeColumns.jsx +5 -1
  16. package/src/app/components/djgraph/LayoutFlow.jsx +5 -3
  17. package/src/app/components/forms/Action.jsx +8 -0
  18. package/src/app/components/forms/NodeNameField.jsx +64 -0
  19. package/src/app/components/search.css +17 -0
  20. package/src/app/icons/AddItemIcon.jsx +16 -0
  21. package/src/app/icons/CommitIcon.jsx +45 -0
  22. package/src/app/icons/DiffIcon.jsx +63 -0
  23. package/src/app/icons/EyeIcon.jsx +20 -0
  24. package/src/app/icons/FilterIcon.jsx +7 -0
  25. package/src/app/icons/JupyterExportIcon.jsx +25 -0
  26. package/src/app/icons/LoadingIcon.jsx +10 -10
  27. package/src/app/icons/PythonIcon.jsx +6 -44
  28. package/src/app/index.tsx +24 -0
  29. package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
  30. package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
  31. package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
  32. package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
  33. package/src/app/pages/AddEditNodePage/FormikSelect.jsx +5 -0
  34. package/src/app/pages/AddEditNodePage/FullNameField.jsx +3 -2
  35. package/src/app/pages/AddEditNodePage/Loadable.jsx +6 -2
  36. package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
  37. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
  38. package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
  39. package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
  40. package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +8 -3
  41. package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
  42. package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
  43. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
  44. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +15 -9
  45. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +167 -24
  46. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +55 -25
  47. package/src/app/pages/AddEditNodePage/index.jsx +275 -194
  48. package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +154 -0
  49. package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
  50. package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +77 -0
  51. package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +405 -0
  52. package/src/app/pages/CubeBuilderPage/index.jsx +267 -0
  53. package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +5 -5
  54. package/src/app/pages/NamespacePage/Explorer.jsx +6 -2
  55. package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
  56. package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
  57. package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
  58. package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
  59. package/src/app/pages/NamespacePage/__tests__/index.test.jsx +98 -19
  60. package/src/app/pages/NamespacePage/index.jsx +272 -89
  61. package/src/app/pages/NodePage/AddBackfillPopover.jsx +60 -61
  62. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +104 -51
  63. package/src/app/pages/NodePage/ClientCodePopover.jsx +73 -25
  64. package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
  65. package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
  66. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +38 -23
  67. package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
  68. package/src/app/pages/NodePage/NodeColumnTab.jsx +183 -113
  69. package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
  70. package/src/app/pages/NodePage/NodeGraphTab.jsx +56 -29
  71. package/src/app/pages/NodePage/NodeHistory.jsx +165 -161
  72. package/src/app/pages/NodePage/NodeInfoTab.jsx +148 -14
  73. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +201 -104
  74. package/src/app/pages/NodePage/NodeStatus.jsx +96 -21
  75. package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
  76. package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
  77. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +3 -5
  78. package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
  79. package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
  80. package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
  81. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +13 -4
  82. package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
  83. package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
  84. package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
  85. package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +10 -14
  86. package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
  87. package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
  88. package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +6 -2
  89. package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +3 -2
  90. package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +148 -0
  91. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +159 -57
  92. package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
  93. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +2 -386
  94. package/src/app/pages/NodePage/index.jsx +94 -57
  95. package/src/app/pages/Root/__tests__/index.test.jsx +3 -1
  96. package/src/app/pages/Root/index.tsx +62 -12
  97. package/src/app/services/DJService.js +587 -55
  98. package/src/app/services/__tests__/DJService.test.jsx +382 -45
  99. package/src/index.tsx +1 -0
  100. package/src/mocks/mockNodes.jsx +265 -227
  101. package/src/styles/dag.css +4 -2
  102. package/src/styles/index.css +474 -10
  103. package/src/styles/loading.css +1 -1
  104. package/src/styles/node-creation.scss +84 -5
  105. package/src/styles/node-list.css +4 -0
  106. package/src/styles/sorted-table.css +15 -0
  107. package/src/app/components/DeleteNode.jsx +0 -55
  108. package/src/app/components/__tests__/DeleteNode.test.jsx +0 -53
  109. package/src/app/pages/NodePage/NodeSQLTab.jsx +0 -82
  110. package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +0 -49
@@ -0,0 +1,148 @@
1
+ import React from 'react';
2
+ import { render, waitFor, screen } from '@testing-library/react';
3
+ import NodeMaterializationTab from '../NodeMaterializationTab';
4
+
5
+ describe('<NodeMaterializationTab />', () => {
6
+ const mockDjClient = {
7
+ node: jest.fn(),
8
+ materializations: jest.fn(),
9
+ };
10
+
11
+ const mockMaterializations = [
12
+ {
13
+ name: 'mat_one',
14
+ config: {},
15
+ schedule: '@daily',
16
+ job: 'SparkSqlMaterializationJob',
17
+ backfills: [
18
+ {
19
+ spec: [
20
+ {
21
+ column_name: 'date',
22
+ values: ['20200101'],
23
+ range: ['20201010'],
24
+ },
25
+ ],
26
+ urls: ['https://example.com/'],
27
+ },
28
+ ],
29
+ strategy: 'full',
30
+ output_tables: ['table1'],
31
+ urls: ['https://example.com/'],
32
+ },
33
+ ];
34
+
35
+ const mockNode = {
36
+ node_revision_id: 1,
37
+ node_id: 1,
38
+ type: 'source',
39
+ name: 'default.repair_orders',
40
+ display_name: 'Default: Repair Orders',
41
+ version: 'v1.0',
42
+ status: 'valid',
43
+ mode: 'published',
44
+ catalog: {
45
+ id: 1,
46
+ uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
47
+ created_at: '2023-08-21T16:48:51.146121+00:00',
48
+ updated_at: '2023-08-21T16:48:51.146122+00:00',
49
+ extra_params: {},
50
+ name: 'warehouse',
51
+ },
52
+ schema_: 'roads',
53
+ table: 'repair_orders',
54
+ description: 'Repair orders',
55
+ query: null,
56
+ availability: {
57
+ catalog: 'default',
58
+ categorical_partitions: [],
59
+ max_temporal_partition: ['2023', '01', '25'],
60
+ min_temporal_partition: ['2022', '01', '01'],
61
+ partitions: [],
62
+ schema_: 'foo',
63
+ table: 'bar',
64
+ temporal_partitions: [],
65
+ valid_through_ts: 1729667463,
66
+ url: 'https://www.table.com',
67
+ links: { dashboard: 'https://www.foobar.com/dashboard' },
68
+ },
69
+ columns: [
70
+ {
71
+ name: 'repair_order_id',
72
+ type: 'int',
73
+ attributes: [],
74
+ dimension: null,
75
+ partition: {
76
+ type_: 'temporal',
77
+ format: 'YYYYMMDD',
78
+ granularity: 'day',
79
+ },
80
+ },
81
+ {
82
+ name: 'municipality_id',
83
+ type: 'string',
84
+ attributes: [],
85
+ dimension: null,
86
+ partition: null,
87
+ },
88
+ {
89
+ name: 'hard_hat_id',
90
+ type: 'int',
91
+ attributes: [],
92
+ dimension: null,
93
+ partition: null,
94
+ },
95
+ ],
96
+ updated_at: '2023-08-21T16:48:52.880498+00:00',
97
+ materializations: [
98
+ {
99
+ name: 'mat1',
100
+ config: {},
101
+ schedule: 'string',
102
+ job: 'string',
103
+ backfills: [
104
+ {
105
+ spec: [
106
+ {
107
+ column_name: 'string',
108
+ values: ['string'],
109
+ range: ['string'],
110
+ },
111
+ ],
112
+ urls: ['string'],
113
+ },
114
+ ],
115
+ strategy: 'string',
116
+ output_tables: ['string'],
117
+ urls: ['https://example.com/'],
118
+ },
119
+ ],
120
+ parents: [],
121
+ dimension_links: [
122
+ {
123
+ dimension: {
124
+ name: 'default.contractor',
125
+ },
126
+ join_type: 'left',
127
+ join_sql:
128
+ 'default.contractor.contractor_id = default.repair_orders.contractor_id',
129
+ join_cardinality: 'one_to_one',
130
+ role: 'contractor',
131
+ },
132
+ ],
133
+ };
134
+
135
+ beforeEach(() => {
136
+ mockDjClient.materializations.mockReset();
137
+ });
138
+
139
+ it('renders NodeMaterializationTab tab correctly', async () => {
140
+ mockDjClient.materializations.mockReturnValue(mockMaterializations);
141
+
142
+ render(<NodeMaterializationTab node={mockNode} djClient={mockDjClient} />);
143
+ await waitFor(() => {
144
+ const link = screen.getByText('dashboard').closest('a');
145
+ expect(link).toHaveAttribute('href', `https://www.foobar.com/dashboard`);
146
+ });
147
+ });
148
+ });
@@ -23,12 +23,15 @@ describe('<NodePage />', () => {
23
23
  DataJunctionAPI: {
24
24
  node: jest.fn(),
25
25
  metric: jest.fn(),
26
+ getMetric: jest.fn(),
27
+ revalidate: jest.fn().mockReturnValue({ status: 'valid' }),
26
28
  node_dag: jest.fn().mockReturnValue(mocks.mockNodeDAG),
27
29
  clientCode: jest.fn().mockReturnValue('dj_client = DJClient()'),
28
30
  columns: jest.fn(),
29
31
  history: jest.fn(),
30
32
  revisions: jest.fn(),
31
33
  materializations: jest.fn(),
34
+ materializationInfo: jest.fn(),
32
35
  sql: jest.fn(),
33
36
  cube: jest.fn(),
34
37
  compiledSql: jest.fn(),
@@ -38,6 +41,8 @@ describe('<NodePage />', () => {
38
41
  dimensions: jest.fn(),
39
42
  setPartition: jest.fn(),
40
43
  engines: jest.fn(),
44
+ streamNodeData: jest.fn(),
45
+ nodeDimensions: jest.fn(),
41
46
  },
42
47
  };
43
48
  };
@@ -68,6 +73,7 @@ describe('<NodePage />', () => {
68
73
  query:
69
74
  'SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n',
70
75
  availability: null,
76
+ dimension_links: [],
71
77
  columns: [
72
78
  {
73
79
  name: 'default_DOT_avg_repair_price',
@@ -87,6 +93,7 @@ describe('<NodePage />', () => {
87
93
  created_at: '2023-08-21T16:48:56.932162+00:00',
88
94
  tags: [{ name: 'purpose', display_name: 'Purpose' }],
89
95
  primary_key: [],
96
+ incompatible_druid_functions: ['IF'],
90
97
  createNodeClientCode:
91
98
  'dj = DJBuilder(DJ_URL)\n\navg_repair_price = dj.create_metric(\n description="Average repair price",\n display_name="Default: Avg Repair Price",\n name="default.avg_repair_price",\n primary_key=[],\n query="""SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n"""\n)',
92
99
  dimensions: [
@@ -270,19 +277,21 @@ describe('<NodePage />', () => {
270
277
  },
271
278
  };
272
279
 
273
- it('renders the NodeInfo tab correctly', async () => {
280
+ it('renders the NodeInfo tab correctly for a metric node', async () => {
274
281
  const djClient = mockDJClient();
275
282
  djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
276
- djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
283
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
284
+ mocks.mockMetricNodeJson,
285
+ );
277
286
  const element = (
278
287
  <DJClientContext.Provider value={djClient}>
279
288
  <NodePage {...defaultProps} />
280
289
  </DJClientContext.Provider>
281
290
  );
282
291
  const { container } = render(
283
- <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
292
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders/info']}>
284
293
  <Routes>
285
- <Route path="nodes/:name" element={element} />
294
+ <Route path="nodes/:name/:tab" element={element} />
286
295
  </Routes>
287
296
  </MemoryRouter>,
288
297
  );
@@ -291,7 +300,6 @@ describe('<NodePage />', () => {
291
300
  expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
292
301
  'default.num_repair_orders',
293
302
  );
294
- userEvent.click(screen.getByRole('button', { name: 'Info' }));
295
303
 
296
304
  expect(
297
305
  screen.getByRole('dialog', { name: 'NodeName' }),
@@ -315,8 +323,8 @@ describe('<NodePage />', () => {
315
323
  );
316
324
 
317
325
  expect(
318
- screen.getByRole('dialog', { name: 'PrimaryKey' }),
319
- ).toHaveTextContent('repair_order_id, country');
326
+ screen.getByRole('dialog', { name: 'RequiredDimensions' }),
327
+ ).toHaveTextContent('');
320
328
 
321
329
  expect(
322
330
  screen.getByRole('dialog', { name: 'DisplayName' }),
@@ -394,7 +402,9 @@ describe('<NodePage />', () => {
394
402
  it('renders the NodeColumns tab correctly', async () => {
395
403
  const djClient = mockDJClient();
396
404
  djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
397
- djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
405
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
406
+ mocks.mockMetricNodeJson,
407
+ );
398
408
  djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
399
409
  djClient.DataJunctionAPI.attributes.mockReturnValue(mocks.attributes);
400
410
  djClient.DataJunctionAPI.dimensions.mockReturnValue(mocks.dimensions);
@@ -410,14 +420,15 @@ describe('<NodePage />', () => {
410
420
  </DJClientContext.Provider>
411
421
  );
412
422
  render(
413
- <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
423
+ <MemoryRouter
424
+ initialEntries={['/nodes/default.num_repair_orders/columns']}
425
+ >
414
426
  <Routes>
415
- <Route path="nodes/:name" element={element} />
427
+ <Route path="nodes/:name/:tab" element={element} />
416
428
  </Routes>
417
429
  </MemoryRouter>,
418
430
  );
419
431
  await waitFor(() => {
420
- fireEvent.click(screen.getByRole('button', { name: 'Columns' }));
421
432
  expect(djClient.DataJunctionAPI.columns).toHaveBeenCalledWith(
422
433
  mocks.mockMetricNode,
423
434
  );
@@ -464,13 +475,15 @@ describe('<NodePage />', () => {
464
475
  fireEvent.click(savePartition);
465
476
  expect(screen.getByText('Saved!'));
466
477
  });
467
- });
478
+ }, 60000);
468
479
  // check compiled SQL on nodeInfo page
469
480
 
470
481
  it('renders the NodeHistory tab correctly', async () => {
471
482
  const djClient = mockDJClient();
472
483
  djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
473
- djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
484
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
485
+ mocks.mockMetricNodeJson,
486
+ );
474
487
  djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
475
488
  djClient.DataJunctionAPI.history.mockReturnValue(mocks.metricNodeHistory);
476
489
  djClient.DataJunctionAPI.revisions.mockReturnValue(
@@ -483,9 +496,11 @@ describe('<NodePage />', () => {
483
496
  </DJClientContext.Provider>
484
497
  );
485
498
  const { container } = render(
486
- <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
499
+ <MemoryRouter
500
+ initialEntries={['/nodes/default.num_repair_orders/history']}
501
+ >
487
502
  <Routes>
488
- <Route path="nodes/:name" element={element} />
503
+ <Route path="nodes/:name/:tab" element={element} />
489
504
  </Routes>
490
505
  </MemoryRouter>,
491
506
  );
@@ -498,15 +513,9 @@ describe('<NodePage />', () => {
498
513
  'node',
499
514
  mocks.mockMetricNode.name,
500
515
  );
501
- expect(djClient.DataJunctionAPI.revisions).toHaveBeenCalledWith(
502
- mocks.mockMetricNode.name,
503
- );
504
516
  expect(
505
- screen.getByRole('table', { name: 'Revisions' }),
506
- ).toMatchSnapshot();
507
- expect(screen.getByRole('table', { name: 'Activity' })).toHaveTextContent(
508
- 'ActivityTypeNameUserTimestampDetailscreatenodedefault.avg_repair_priceunknown2023-08-21T16:48:56.950482+00:00',
509
- );
517
+ screen.getByRole('list', { name: 'Activity' }),
518
+ ).toBeInTheDocument();
510
519
  screen
511
520
  .queryAllByRole('cell', {
512
521
  name: 'HistoryAttribute',
@@ -542,14 +551,12 @@ describe('<NodePage />', () => {
542
551
  'Status changed from valid to invalid Caused by a change in upstream default.repair_order_details',
543
552
  ),
544
553
  );
545
- screen.debug();
546
554
  });
547
555
  });
548
556
 
549
557
  it('renders compiled sql correctly', async () => {
550
558
  const djClient = mockDJClient();
551
- djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
552
- djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
559
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockTransformNode);
553
560
  djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
554
561
  djClient.DataJunctionAPI.compiledSql.mockReturnValue('select 1');
555
562
 
@@ -559,7 +566,7 @@ describe('<NodePage />', () => {
559
566
  </DJClientContext.Provider>
560
567
  );
561
568
  render(
562
- <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
569
+ <MemoryRouter initialEntries={[`/nodes/${mocks.mockTransformNode.name}`]}>
563
570
  <Routes>
564
571
  <Route path="nodes/:name" element={element} />
565
572
  </Routes>
@@ -568,7 +575,7 @@ describe('<NodePage />', () => {
568
575
  await waitFor(() => {
569
576
  fireEvent.click(screen.getByRole('checkbox', { name: 'ToggleSwitch' }));
570
577
  expect(djClient.DataJunctionAPI.compiledSql).toHaveBeenCalledWith(
571
- mocks.mockMetricNode.name,
578
+ mocks.mockTransformNode.name,
572
579
  );
573
580
  });
574
581
  });
@@ -576,7 +583,9 @@ describe('<NodePage />', () => {
576
583
  it('renders an empty NodeMaterialization tab correctly', async () => {
577
584
  const djClient = mockDJClient();
578
585
  djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
579
- djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
586
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
587
+ mocks.mockMetricNodeJson,
588
+ );
580
589
  djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
581
590
  djClient.DataJunctionAPI.materializations.mockReturnValue([]);
582
591
 
@@ -586,9 +595,11 @@ describe('<NodePage />', () => {
586
595
  </DJClientContext.Provider>
587
596
  );
588
597
  render(
589
- <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
598
+ <MemoryRouter
599
+ initialEntries={['/nodes/default.num_repair_orders/materializations']}
600
+ >
590
601
  <Routes>
591
- <Route path="nodes/:name" element={element} />
602
+ <Route path="nodes/:name/:tab" element={element} />
592
603
  </Routes>
593
604
  </MemoryRouter>,
594
605
  );
@@ -606,22 +617,32 @@ describe('<NodePage />', () => {
606
617
 
607
618
  it('renders the NodeMaterialization tab with materializations correctly', async () => {
608
619
  const djClient = mockDJClient();
609
- djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
610
- djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
620
+ djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockTransformNode);
621
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
622
+ mocks.mockMetricNodeJson,
623
+ );
611
624
  djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
612
625
  djClient.DataJunctionAPI.materializations.mockReturnValue(
613
626
  mocks.nodeMaterializations,
614
627
  );
615
628
 
629
+ djClient.DataJunctionAPI.materializationInfo.mockReturnValue(
630
+ mocks.materializationInfo,
631
+ );
632
+
616
633
  const element = (
617
634
  <DJClientContext.Provider value={djClient}>
618
635
  <NodePage />
619
636
  </DJClientContext.Provider>
620
637
  );
621
638
  render(
622
- <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
639
+ <MemoryRouter
640
+ initialEntries={[
641
+ '/nodes/default.repair_order_transform/materializations',
642
+ ]}
643
+ >
623
644
  <Routes>
624
- <Route path="nodes/:name" element={element} />
645
+ <Route path="nodes/:name/:tab" element={element} />
625
646
  </Routes>
626
647
  </MemoryRouter>,
627
648
  );
@@ -631,52 +652,129 @@ describe('<NodePage />', () => {
631
652
  screen.getByRole('button', { name: 'Materializations' }),
632
653
  );
633
654
  expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
634
- mocks.mockMetricNode.name,
655
+ mocks.mockTransformNode.name,
635
656
  );
636
657
  expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
637
- mocks.mockMetricNode.name,
658
+ mocks.mockTransformNode.name,
638
659
  );
639
-
640
- expect(
641
- screen.getByRole('table', { name: 'Materializations' }),
642
- ).toMatchSnapshot();
643
660
  },
644
661
  { timeout: 3000 },
645
662
  );
646
663
  }, 60000);
647
664
 
648
- it('renders the NodeSQL tab', async () => {
665
+ it('renders the NodeValidate tab', async () => {
649
666
  const djClient = mockDJClient();
667
+ window.scrollTo = jest.fn();
650
668
  djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
651
- djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
669
+ djClient.DataJunctionAPI.nodeDimensions.mockReturnValue([]);
670
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
671
+ mocks.mockMetricNodeJson,
672
+ );
652
673
  djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
653
- djClient.DataJunctionAPI.sql.mockReturnValue(mocks.metricNodeColumns);
674
+ djClient.DataJunctionAPI.sql.mockReturnValue({
675
+ sql: 'SELECT * FROM testNode',
676
+ });
677
+ const streamNodeData = {
678
+ onmessage: jest.fn(),
679
+ onerror: jest.fn(),
680
+ close: jest.fn(),
681
+ };
682
+ djClient.DataJunctionAPI.streamNodeData.mockResolvedValue(streamNodeData);
683
+ djClient.DataJunctionAPI.streamNodeData.mockResolvedValueOnce({
684
+ state: 'FINISHED',
685
+ results: [
686
+ {
687
+ columns: [{ name: 'column1' }, { name: 'column2' }],
688
+ rows: [
689
+ [1, 'value1'],
690
+ [2, 'value2'],
691
+ ],
692
+ },
693
+ ],
694
+ });
695
+
654
696
  const element = (
655
697
  <DJClientContext.Provider value={djClient}>
656
698
  <NodePage />
657
699
  </DJClientContext.Provider>
658
700
  );
659
701
  render(
660
- <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
702
+ <MemoryRouter
703
+ initialEntries={['/nodes/default.num_repair_orders/validate']}
704
+ >
661
705
  <Routes>
662
- <Route path="nodes/:name" element={element} />
706
+ <Route path="nodes/:name/:tab" element={element} />
663
707
  </Routes>
664
708
  </MemoryRouter>,
665
709
  );
710
+
711
+ await waitFor(() => {
712
+ expect(screen.getByText('Group By')).toBeInTheDocument();
713
+ expect(screen.getByText('Add Filters')).toBeInTheDocument();
714
+ expect(screen.getByText('Generated Query')).toBeInTheDocument();
715
+ expect(screen.getByText('Results')).toBeInTheDocument();
716
+ });
717
+ // Click on the 'Validate' tab
718
+ fireEvent.click(screen.getByRole('button', { name: '► Validate' }));
719
+
666
720
  await waitFor(() => {
667
- const sqlButton = screen.getByRole('button', { name: 'SQL' });
668
- sqlButton.click();
721
+ expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
722
+ mocks.mockMetricNode.name,
723
+ );
669
724
  expect(djClient.DataJunctionAPI.sql).toHaveBeenCalledWith(
670
- 'default.num_repair_orders',
725
+ mocks.mockMetricNode.name,
726
+ { dimensions: [], filters: [] },
727
+ );
728
+ expect(djClient.DataJunctionAPI.nodeDimensions).toHaveBeenCalledWith(
729
+ mocks.mockMetricNode.name,
730
+ );
731
+ });
732
+
733
+ // Click on 'Run' to run the node query
734
+ const runButton = screen.getByText('► Run');
735
+ fireEvent.click(runButton);
736
+
737
+ await waitFor(() => {
738
+ expect(djClient.DataJunctionAPI.streamNodeData).toHaveBeenCalledWith(
739
+ mocks.mockMetricNode.name,
671
740
  { dimensions: [], filters: [] },
672
741
  );
742
+ expect(streamNodeData.onmessage).toBeDefined();
743
+ expect(streamNodeData.onerror).toBeDefined();
744
+ });
745
+
746
+ const infoTab = screen.getByRole('button', { name: 'QueryInfo' });
747
+ const resultsTab = screen.getByText('Results');
748
+
749
+ // Initially, the Results tab should be active
750
+ expect(resultsTab).toHaveClass('active');
751
+ expect(infoTab).not.toHaveClass('active');
752
+
753
+ // Click on the Info tab first
754
+ fireEvent.click(infoTab);
755
+
756
+ await waitFor(() => {
757
+ // Now, the Info tab should be active
758
+ expect(infoTab).toHaveClass('active');
759
+ expect(resultsTab).not.toHaveClass('active');
760
+ });
761
+
762
+ // Click on the Results tab
763
+ fireEvent.click(resultsTab);
764
+
765
+ await waitFor(() => {
766
+ // Now, the Results tab should be active again
767
+ expect(resultsTab).toHaveClass('active');
768
+ expect(infoTab).not.toHaveClass('active');
673
769
  });
674
770
  });
675
771
 
676
772
  it('renders a NodeColumnLineage tab correctly', async () => {
677
773
  const djClient = mockDJClient();
678
774
  djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
679
- djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNodeJson);
775
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
776
+ mocks.mockMetricNodeJson,
777
+ );
680
778
  djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
681
779
  djClient.DataJunctionAPI.node_lineage.mockReturnValue(
682
780
  mocks.mockNodeLineage,
@@ -688,9 +786,11 @@ describe('<NodePage />', () => {
688
786
  </DJClientContext.Provider>
689
787
  );
690
788
  render(
691
- <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
789
+ <MemoryRouter
790
+ initialEntries={['/nodes/default.num_repair_orders/lineage']}
791
+ >
692
792
  <Routes>
693
- <Route path="nodes/:name" element={element} />
793
+ <Route path="nodes/:name/:tab" element={element} />
694
794
  </Routes>
695
795
  </MemoryRouter>,
696
796
  );
@@ -705,7 +805,9 @@ describe('<NodePage />', () => {
705
805
  it('renders a NodeGraph tab correctly', async () => {
706
806
  const djClient = mockDJClient();
707
807
  djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
708
- djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNodeJson);
808
+ djClient.DataJunctionAPI.getMetric.mockReturnValue(
809
+ mocks.mockMetricNodeJson,
810
+ );
709
811
  djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
710
812
 
711
813
  const element = (
@@ -714,9 +816,9 @@ describe('<NodePage />', () => {
714
816
  </DJClientContext.Provider>
715
817
  );
716
818
  render(
717
- <MemoryRouter initialEntries={['/nodes/default.num_repair_orders']}>
819
+ <MemoryRouter initialEntries={['/nodes/default.num_repair_orders/graph']}>
718
820
  <Routes>
719
- <Route path="nodes/:name" element={element} />
821
+ <Route path="nodes/:name/:tab" element={element} />
720
822
  </Routes>
721
823
  </MemoryRouter>,
722
824
  );
@@ -741,9 +843,9 @@ describe('<NodePage />', () => {
741
843
  </DJClientContext.Provider>
742
844
  );
743
845
  render(
744
- <MemoryRouter initialEntries={['/nodes/default.dispatcher']}>
846
+ <MemoryRouter initialEntries={['/nodes/default.dispatcher/linked']}>
745
847
  <Routes>
746
- <Route path="nodes/:name" element={element} />
848
+ <Route path="nodes/:name/:tab" element={element} />
747
849
  </Routes>
748
850
  </MemoryRouter>,
749
851
  );