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,154 @@
1
+ /**
2
+ * A select component for picking dimensions
3
+ */
4
+ import { useField, useFormikContext } from 'formik';
5
+ import Select from 'react-select';
6
+ import React, { useContext, useEffect, useState } from 'react';
7
+ import DJClientContext from '../../providers/djclient';
8
+ import { labelize } from '../../../utils/form';
9
+
10
+ export const DimensionsSelect = ({ cube }) => {
11
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
12
+ const { values, setFieldValue } = useFormikContext();
13
+
14
+ // eslint-disable-next-line no-unused-vars
15
+ const [field, _, helpers] = useField('dimensions');
16
+ const { setValue } = helpers;
17
+
18
+ // All common dimensions for the selected metrics, grouped by the dimension node and path
19
+ const [allDimensionsOptions, setAllDimensionsOptions] = useState([]);
20
+
21
+ // The selected dimensions, also grouped by dimension node and path
22
+ const [selectedDimensionsByGroup, setSelectedDimensionsByGroup] = useState(
23
+ {},
24
+ );
25
+
26
+ // The existing cube node's dimensions, if editing a cube
27
+ const [defaultDimensions, setDefaultDimensions] = useState([]);
28
+
29
+ useEffect(() => {
30
+ const fetchData = async () => {
31
+ let cubeDimensions = undefined;
32
+ if (cube) {
33
+ cubeDimensions = cube?.cube_elements
34
+ .filter(element => element.type === 'dimension')
35
+ .map(cubeDim => {
36
+ return {
37
+ value: cubeDim.node_name + '.' + cubeDim.name,
38
+ label:
39
+ labelize(cubeDim.name) +
40
+ (cubeDim.properties?.includes('primary_key') ? ' (PK)' : ''),
41
+ };
42
+ });
43
+ setDefaultDimensions(cubeDimensions);
44
+ setValue(cubeDimensions.map(m => m.value));
45
+ }
46
+
47
+ if (values.metrics && values.metrics.length > 0) {
48
+ // Populate the common dimensions list based on the selected metrics
49
+ const commonDimensions = await djClient.commonDimensions(
50
+ values.metrics,
51
+ );
52
+ const grouped = Object.entries(
53
+ commonDimensions.reduce((group, dimension) => {
54
+ group[dimension.node_name + dimension.path] =
55
+ group[dimension.node_name + dimension.path] ?? [];
56
+ group[dimension.node_name + dimension.path].push(dimension);
57
+ return group;
58
+ }, {}),
59
+ );
60
+ setAllDimensionsOptions(grouped);
61
+
62
+ // Set the selected cube dimensions if an existing cube is being edited
63
+ if (cube) {
64
+ const currentSelectedDimensionsByGroup = {};
65
+ grouped.forEach(grouping => {
66
+ const dimensionsInGroup = grouping[1];
67
+ currentSelectedDimensionsByGroup[dimensionsInGroup[0].node_name] =
68
+ getValue(
69
+ cubeDimensions.filter(
70
+ dim =>
71
+ dimensionsInGroup.filter(x => {
72
+ return dim.value === x.name;
73
+ }).length > 0,
74
+ ),
75
+ );
76
+ setSelectedDimensionsByGroup(currentSelectedDimensionsByGroup);
77
+ setValue(Object.values(currentSelectedDimensionsByGroup).flat(2));
78
+ });
79
+ }
80
+ } else {
81
+ setAllDimensionsOptions([]);
82
+ }
83
+ };
84
+ fetchData().catch(console.error);
85
+ }, [djClient, setFieldValue, setValue, values.metrics, cube]);
86
+
87
+ // Retrieves the selected values as a list (since it is a multi-select)
88
+ const getValue = options => {
89
+ if (options) {
90
+ return options.map(option => option.value);
91
+ } else {
92
+ return [];
93
+ }
94
+ };
95
+
96
+ // Builds the block of dimensions selectors, grouped by node name + path
97
+ return allDimensionsOptions.map(grouping => {
98
+ const dimensionsInGroup = grouping[1];
99
+ const groupHeader = (
100
+ <h5
101
+ style={{
102
+ fontWeight: 'normal',
103
+ marginBottom: '5px',
104
+ marginTop: '15px',
105
+ }}
106
+ >
107
+ <a href={`/nodes/${dimensionsInGroup[0].node_name}`}>
108
+ <b>{dimensionsInGroup[0].node_display_name}</b>
109
+ </a>{' '}
110
+ via{' '}
111
+ <span className="HighlightPath">
112
+ {dimensionsInGroup[0].path.join(' → ')}
113
+ </span>
114
+ </h5>
115
+ );
116
+ const dimensionGroupOptions = dimensionsInGroup.map(dim => {
117
+ return {
118
+ value: dim.name,
119
+ label:
120
+ labelize(dim.name.split('.').slice(-1)[0]) +
121
+ (dim.properties?.includes('primary_key') ? ' (PK)' : ''),
122
+ };
123
+ });
124
+ //
125
+ const cubeDimensions = defaultDimensions.filter(
126
+ dim =>
127
+ dimensionGroupOptions.filter(x => {
128
+ return dim.value === x.value;
129
+ }).length > 0,
130
+ );
131
+ return (
132
+ <>
133
+ {groupHeader}
134
+ <span data-testid={'dimensions-' + dimensionsInGroup[0].node_name}>
135
+ <Select
136
+ className=""
137
+ name={'dimensions-' + dimensionsInGroup[0].node_name}
138
+ defaultValue={cubeDimensions}
139
+ options={dimensionGroupOptions}
140
+ isMulti
141
+ isClearable
142
+ closeMenuOnSelect={false}
143
+ onChange={selected => {
144
+ selectedDimensionsByGroup[dimensionsInGroup[0].node_name] =
145
+ getValue(selected);
146
+ setSelectedDimensionsByGroup(selectedDimensionsByGroup);
147
+ setValue(Object.values(selectedDimensionsByGroup).flat(2));
148
+ }}
149
+ />
150
+ </span>
151
+ </>
152
+ );
153
+ });
154
+ };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Asynchronously loads the component for the Node page
3
+ */
4
+
5
+ import * as React from 'react';
6
+ import { lazyLoad } from '../../../utils/loadable';
7
+
8
+ export const CubeBuilderPage = props => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.CubeBuilderPage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )(props);
16
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * A select component for picking metrics.
3
+ */
4
+ import { useField, useFormikContext } from 'formik';
5
+ import Select from 'react-select';
6
+ import React, { useContext, useEffect, useState } from 'react';
7
+ import DJClientContext from '../../providers/djclient';
8
+
9
+ export const MetricsSelect = ({ cube }) => {
10
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
11
+ const { values } = useFormikContext();
12
+
13
+ // eslint-disable-next-line no-unused-vars
14
+ const [field, _, helpers] = useField('metrics');
15
+ const { setValue } = helpers;
16
+
17
+ // All metrics options
18
+ const [metrics, setMetrics] = useState([]);
19
+
20
+ // The existing cube's metrics, if editing a cube
21
+ const [defaultMetrics, setDefaultMetrics] = useState([]);
22
+
23
+ // Get metrics
24
+ useEffect(() => {
25
+ const fetchData = async () => {
26
+ if (cube) {
27
+ const cubeMetrics = cube?.cube_elements
28
+ .filter(element => element.type === 'metric')
29
+ .map(metric => {
30
+ return {
31
+ value: metric.node_name,
32
+ label: metric.node_name,
33
+ };
34
+ });
35
+ setDefaultMetrics(cubeMetrics);
36
+ await setValue(cubeMetrics.map(m => m.value));
37
+ }
38
+
39
+ const metrics = await djClient.metrics();
40
+ setMetrics(metrics.map(m => ({ value: m, label: m })));
41
+ };
42
+ fetchData().catch(console.error);
43
+ }, [djClient, djClient.metrics, cube]);
44
+
45
+ const getValue = options => {
46
+ if (options) {
47
+ return options.map(option => option.value);
48
+ } else {
49
+ return [];
50
+ }
51
+ };
52
+
53
+ const render = () => {
54
+ if (
55
+ metrics.length > 0 ||
56
+ (cube !== undefined && defaultMetrics.length > 0 && metrics.length > 0)
57
+ ) {
58
+ return (
59
+ <Select
60
+ defaultValue={defaultMetrics}
61
+ options={metrics}
62
+ name="metrics"
63
+ placeholder={`${metrics.length} Available Metrics`}
64
+ onBlur={field.onBlur}
65
+ onChange={selected => {
66
+ setValue(getValue(selected));
67
+ }}
68
+ noOptionsMessage={() => 'No metrics found.'}
69
+ isMulti
70
+ isClearable
71
+ closeMenuOnSelect={false}
72
+ />
73
+ );
74
+ }
75
+ };
76
+ return render();
77
+ };
@@ -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
+ properties: [],
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
+ properties: [],
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
+ properties: [],
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
+ properties: [],
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
+ properties: [],
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
+ properties: [],
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
+ properties: [],
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
+ properties: [],
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
+ 'published',
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
+ });