datajunction-ui 0.0.26 → 0.0.27
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 +2 -2
- package/src/app/components/Search.jsx +41 -33
- package/src/app/components/__tests__/Search.test.jsx +46 -11
- package/src/app/index.tsx +3 -3
- package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +57 -8
- package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +17 -5
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +97 -1
- package/src/app/pages/AddEditNodePage/index.jsx +61 -17
- package/src/app/pages/NodePage/WatchNodeButton.jsx +12 -5
- package/src/app/pages/QueryPlannerPage/MetricFlowGraph.jsx +93 -15
- package/src/app/pages/QueryPlannerPage/PreAggDetailsPanel.jsx +2320 -65
- package/src/app/pages/QueryPlannerPage/SelectionPanel.jsx +234 -25
- package/src/app/pages/QueryPlannerPage/__tests__/MetricFlowGraph.test.jsx +315 -122
- package/src/app/pages/QueryPlannerPage/__tests__/PreAggDetailsPanel.test.jsx +2672 -314
- package/src/app/pages/QueryPlannerPage/__tests__/SelectionPanel.test.jsx +567 -0
- package/src/app/pages/QueryPlannerPage/__tests__/index.test.jsx +480 -55
- package/src/app/pages/QueryPlannerPage/index.jsx +1021 -14
- package/src/app/pages/QueryPlannerPage/styles.css +1990 -62
- package/src/app/pages/Root/__tests__/index.test.jsx +79 -8
- package/src/app/pages/Root/index.tsx +1 -6
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +82 -0
- package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +37 -0
- package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +48 -0
- package/src/app/pages/SettingsPage/__tests__/index.test.jsx +169 -1
- package/src/app/services/DJService.js +492 -3
- package/src/app/services/__tests__/DJService.test.jsx +582 -0
- package/src/mocks/mockNodes.jsx +36 -0
- package/webpack.config.js +27 -0
|
@@ -1,17 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
render,
|
|
3
|
+
screen,
|
|
4
|
+
fireEvent,
|
|
5
|
+
waitFor,
|
|
6
|
+
act,
|
|
7
|
+
} from '@testing-library/react';
|
|
2
8
|
import DJClientContext from '../../../providers/djclient';
|
|
3
9
|
import { QueryPlannerPage } from '../index';
|
|
4
|
-
import { MemoryRouter } from 'react-router-dom';
|
|
10
|
+
import { MemoryRouter, Routes, Route } from 'react-router-dom';
|
|
5
11
|
import React from 'react';
|
|
6
12
|
|
|
7
13
|
// Mock the MetricFlowGraph component to avoid dagre dependency issues
|
|
8
14
|
jest.mock('../MetricFlowGraph', () => ({
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
metricFormulas,
|
|
12
|
-
selectedNode,
|
|
13
|
-
onNodeSelect,
|
|
14
|
-
}) => {
|
|
15
|
+
__esModule: true,
|
|
16
|
+
default: ({ grainGroups, metricFormulas, selectedNode, onNodeSelect }) => {
|
|
15
17
|
if (!grainGroups?.length || !metricFormulas?.length) {
|
|
16
18
|
return <div data-testid="graph-empty">Select metrics and dimensions</div>;
|
|
17
19
|
}
|
|
@@ -21,6 +23,30 @@ jest.mock('../MetricFlowGraph', () => ({
|
|
|
21
23
|
{grainGroups.length} pre-aggregations → {metricFormulas.length}{' '}
|
|
22
24
|
metrics
|
|
23
25
|
</span>
|
|
26
|
+
<button
|
|
27
|
+
data-testid="select-preagg"
|
|
28
|
+
onClick={() =>
|
|
29
|
+
onNodeSelect?.({
|
|
30
|
+
type: 'preagg',
|
|
31
|
+
index: 0,
|
|
32
|
+
data: grainGroups[0],
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
>
|
|
36
|
+
Select Pre-agg
|
|
37
|
+
</button>
|
|
38
|
+
<button
|
|
39
|
+
data-testid="select-metric"
|
|
40
|
+
onClick={() =>
|
|
41
|
+
onNodeSelect?.({
|
|
42
|
+
type: 'metric',
|
|
43
|
+
index: 0,
|
|
44
|
+
data: metricFormulas[0],
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
Select Metric
|
|
49
|
+
</button>
|
|
24
50
|
</div>
|
|
25
51
|
);
|
|
26
52
|
},
|
|
@@ -31,6 +57,21 @@ const mockDjClient = {
|
|
|
31
57
|
commonDimensions: jest.fn(),
|
|
32
58
|
measuresV3: jest.fn(),
|
|
33
59
|
metricsV3: jest.fn(),
|
|
60
|
+
listCubesForPreset: jest.fn(),
|
|
61
|
+
cubeForPlanner: jest.fn(),
|
|
62
|
+
planPreaggs: jest.fn(),
|
|
63
|
+
updatePreaggConfig: jest.fn(),
|
|
64
|
+
materializePreagg: jest.fn(),
|
|
65
|
+
runPreaggBackfill: jest.fn(),
|
|
66
|
+
deactivatePreaggWorkflow: jest.fn(),
|
|
67
|
+
deactivateCubeWorkflow: jest.fn(),
|
|
68
|
+
createCube: jest.fn(),
|
|
69
|
+
materializeCubeV2: jest.fn(),
|
|
70
|
+
refreshCubeWorkflow: jest.fn(),
|
|
71
|
+
runCubeBackfill: jest.fn(),
|
|
72
|
+
listPreaggs: jest.fn(),
|
|
73
|
+
getNodeColumnsWithPartitions: jest.fn(),
|
|
74
|
+
setPartition: jest.fn(),
|
|
34
75
|
};
|
|
35
76
|
|
|
36
77
|
const mockMetrics = [
|
|
@@ -107,18 +148,42 @@ const mockMeasuresResult = {
|
|
|
107
148
|
components: ['sum_revenue', 'count_orders'],
|
|
108
149
|
},
|
|
109
150
|
],
|
|
151
|
+
requested_dimensions: ['default.date_dim.dateint'],
|
|
110
152
|
};
|
|
111
153
|
|
|
112
154
|
const mockMetricsResult = {
|
|
113
155
|
sql: 'SELECT date_id, SUM(revenue) as total_revenue FROM orders GROUP BY 1',
|
|
114
156
|
};
|
|
115
157
|
|
|
116
|
-
const
|
|
158
|
+
const mockCubes = [
|
|
159
|
+
{ name: 'default.test_cube', display_name: 'Test Cube' },
|
|
160
|
+
{ name: 'sales.revenue_cube', display_name: 'Revenue Cube' },
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
const mockCubeData = {
|
|
164
|
+
cube_node_metrics: ['default.num_repair_orders', 'default.avg_repair_price'],
|
|
165
|
+
cube_node_dimensions: ['default.date_dim.dateint'],
|
|
166
|
+
cubeMaterialization: {
|
|
167
|
+
schedule: '0 6 * * *',
|
|
168
|
+
strategy: 'incremental_time',
|
|
169
|
+
lookbackWindow: '1 DAY',
|
|
170
|
+
workflowUrls: ['http://workflow.example.com/1'],
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const renderPage = (initialEntries = ['/query-planner']) => {
|
|
117
175
|
return render(
|
|
118
|
-
<MemoryRouter>
|
|
119
|
-
<
|
|
120
|
-
<
|
|
121
|
-
|
|
176
|
+
<MemoryRouter initialEntries={initialEntries}>
|
|
177
|
+
<Routes>
|
|
178
|
+
<Route
|
|
179
|
+
path="/query-planner"
|
|
180
|
+
element={
|
|
181
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
182
|
+
<QueryPlannerPage />
|
|
183
|
+
</DJClientContext.Provider>
|
|
184
|
+
}
|
|
185
|
+
/>
|
|
186
|
+
</Routes>
|
|
122
187
|
</MemoryRouter>,
|
|
123
188
|
);
|
|
124
189
|
};
|
|
@@ -129,6 +194,13 @@ describe('QueryPlannerPage', () => {
|
|
|
129
194
|
mockDjClient.commonDimensions.mockResolvedValue(mockCommonDimensions);
|
|
130
195
|
mockDjClient.measuresV3.mockResolvedValue(mockMeasuresResult);
|
|
131
196
|
mockDjClient.metricsV3.mockResolvedValue(mockMetricsResult);
|
|
197
|
+
mockDjClient.listCubesForPreset.mockResolvedValue(mockCubes);
|
|
198
|
+
mockDjClient.cubeForPlanner.mockResolvedValue(null);
|
|
199
|
+
mockDjClient.listPreaggs.mockResolvedValue({ items: [] });
|
|
200
|
+
mockDjClient.getNodeColumnsWithPartitions.mockResolvedValue({
|
|
201
|
+
columns: [{ name: 'date_id', type: 'int' }],
|
|
202
|
+
temporalPartitions: [],
|
|
203
|
+
});
|
|
132
204
|
});
|
|
133
205
|
|
|
134
206
|
afterEach(() => {
|
|
@@ -136,25 +208,26 @@ describe('QueryPlannerPage', () => {
|
|
|
136
208
|
});
|
|
137
209
|
|
|
138
210
|
describe('Initial Render', () => {
|
|
139
|
-
it('renders the page header', () => {
|
|
211
|
+
it('renders the page header', async () => {
|
|
140
212
|
renderPage();
|
|
141
213
|
// Page has "Query Planner" text in multiple places (header and empty state)
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
'Explore metrics and dimensions and plan materializations',
|
|
146
|
-
),
|
|
147
|
-
).toBeInTheDocument();
|
|
214
|
+
await waitFor(() => {
|
|
215
|
+
expect(screen.getAllByText('Query Planner').length).toBeGreaterThan(0);
|
|
216
|
+
});
|
|
148
217
|
});
|
|
149
218
|
|
|
150
|
-
it('renders the metrics section', () => {
|
|
219
|
+
it('renders the metrics section', async () => {
|
|
151
220
|
renderPage();
|
|
152
|
-
|
|
221
|
+
await waitFor(() => {
|
|
222
|
+
expect(screen.getByText('Metrics')).toBeInTheDocument();
|
|
223
|
+
});
|
|
153
224
|
});
|
|
154
225
|
|
|
155
|
-
it('renders the dimensions section', () => {
|
|
226
|
+
it('renders the dimensions section', async () => {
|
|
156
227
|
renderPage();
|
|
157
|
-
|
|
228
|
+
await waitFor(() => {
|
|
229
|
+
expect(screen.getByText('Dimensions')).toBeInTheDocument();
|
|
230
|
+
});
|
|
158
231
|
});
|
|
159
232
|
|
|
160
233
|
it('fetches metrics on mount', async () => {
|
|
@@ -164,11 +237,20 @@ describe('QueryPlannerPage', () => {
|
|
|
164
237
|
});
|
|
165
238
|
});
|
|
166
239
|
|
|
167
|
-
it('
|
|
240
|
+
it('fetches cube list on mount', async () => {
|
|
168
241
|
renderPage();
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
)
|
|
242
|
+
await waitFor(() => {
|
|
243
|
+
expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('shows empty state when no metrics/dimensions selected', async () => {
|
|
248
|
+
renderPage();
|
|
249
|
+
await waitFor(() => {
|
|
250
|
+
expect(
|
|
251
|
+
screen.getByText('Select Metrics & Dimensions'),
|
|
252
|
+
).toBeInTheDocument();
|
|
253
|
+
});
|
|
172
254
|
});
|
|
173
255
|
});
|
|
174
256
|
|
|
@@ -275,43 +357,386 @@ describe('QueryPlannerPage', () => {
|
|
|
275
357
|
});
|
|
276
358
|
});
|
|
277
359
|
|
|
278
|
-
describe('
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
360
|
+
describe('Cube Preset Loading', () => {
|
|
361
|
+
it('displays cube dropdown button when cubes are available', async () => {
|
|
362
|
+
renderPage();
|
|
363
|
+
|
|
364
|
+
await waitFor(() => {
|
|
365
|
+
expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
expect(screen.getByText('Load from Cube')).toBeInTheDocument();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
it('opens cube dropdown when clicked', async () => {
|
|
372
|
+
renderPage();
|
|
373
|
+
|
|
374
|
+
await waitFor(() => {
|
|
375
|
+
expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const cubeButton = screen.getByText('Load from Cube');
|
|
379
|
+
fireEvent.click(cubeButton);
|
|
380
|
+
|
|
381
|
+
expect(
|
|
382
|
+
screen.getByPlaceholderText('Search cubes...'),
|
|
383
|
+
).toBeInTheDocument();
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it('loads cube data when a cube is selected', async () => {
|
|
387
|
+
mockDjClient.cubeForPlanner.mockResolvedValue(mockCubeData);
|
|
388
|
+
|
|
389
|
+
renderPage();
|
|
390
|
+
|
|
391
|
+
await waitFor(() => {
|
|
392
|
+
expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Open dropdown
|
|
396
|
+
const cubeButton = screen.getByText('Load from Cube');
|
|
397
|
+
fireEvent.click(cubeButton);
|
|
398
|
+
|
|
399
|
+
// Select a cube
|
|
400
|
+
const cubeOption = screen.getByText('Test Cube');
|
|
401
|
+
fireEvent.click(cubeOption);
|
|
402
|
+
|
|
403
|
+
await waitFor(() => {
|
|
404
|
+
expect(mockDjClient.cubeForPlanner).toHaveBeenCalledWith(
|
|
405
|
+
'default.test_cube',
|
|
406
|
+
);
|
|
407
|
+
});
|
|
286
408
|
});
|
|
287
409
|
});
|
|
288
410
|
|
|
289
|
-
describe('
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
411
|
+
describe('URL Parameter Handling', () => {
|
|
412
|
+
it('initializes from URL with metrics parameter', async () => {
|
|
413
|
+
mockDjClient.cubeForPlanner.mockResolvedValue(mockCubeData);
|
|
414
|
+
|
|
415
|
+
renderPage([
|
|
416
|
+
'/query-planner?metrics=default.num_repair_orders,default.avg_repair_price',
|
|
417
|
+
]);
|
|
418
|
+
|
|
419
|
+
await waitFor(() => {
|
|
420
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it('initializes from URL with cube parameter', async () => {
|
|
425
|
+
mockDjClient.cubeForPlanner.mockResolvedValue(mockCubeData);
|
|
426
|
+
|
|
427
|
+
renderPage(['/query-planner?cube=default.test_cube']);
|
|
428
|
+
|
|
429
|
+
await waitFor(() => {
|
|
430
|
+
expect(mockDjClient.cubeForPlanner).toHaveBeenCalledWith(
|
|
431
|
+
'default.test_cube',
|
|
432
|
+
);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
describe('Graph Interactions', () => {
|
|
438
|
+
it('displays graph when metrics and dimensions are selected', async () => {
|
|
439
|
+
renderPage();
|
|
440
|
+
|
|
441
|
+
await waitFor(() => {
|
|
442
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// Expand and select metric
|
|
446
|
+
fireEvent.click(screen.getByText('default'));
|
|
447
|
+
await waitFor(() => {
|
|
448
|
+
fireEvent.click(
|
|
449
|
+
screen.getByRole('checkbox', { name: /num_repair_orders/i }),
|
|
450
|
+
);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Wait for dimensions and select one
|
|
454
|
+
await waitFor(() => {
|
|
455
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const dimCheckbox = screen.getByRole('checkbox', { name: /dateint/i });
|
|
459
|
+
fireEvent.click(dimCheckbox);
|
|
460
|
+
|
|
461
|
+
// Wait for measures to be fetched
|
|
462
|
+
await waitFor(() => {
|
|
463
|
+
expect(mockDjClient.measuresV3).toHaveBeenCalled();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
// Graph should now be displayed
|
|
467
|
+
await waitFor(() => {
|
|
468
|
+
expect(screen.getByTestId('metric-flow-graph')).toBeInTheDocument();
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('shows loading state while building data flow', async () => {
|
|
473
|
+
// Delay the measures response
|
|
474
|
+
mockDjClient.measuresV3.mockImplementation(
|
|
475
|
+
() =>
|
|
476
|
+
new Promise(resolve =>
|
|
477
|
+
setTimeout(() => resolve(mockMeasuresResult), 100),
|
|
478
|
+
),
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
renderPage();
|
|
482
|
+
|
|
483
|
+
await waitFor(() => {
|
|
484
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Select metric and dimension quickly
|
|
488
|
+
fireEvent.click(screen.getByText('default'));
|
|
489
|
+
await waitFor(() => {
|
|
490
|
+
fireEvent.click(
|
|
491
|
+
screen.getByRole('checkbox', { name: /num_repair_orders/i }),
|
|
492
|
+
);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
await waitFor(() => {
|
|
496
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
fireEvent.click(screen.getByRole('checkbox', { name: /dateint/i }));
|
|
500
|
+
|
|
501
|
+
// Should show loading state
|
|
502
|
+
await waitFor(() => {
|
|
503
|
+
expect(screen.getByText('Building data flow...')).toBeInTheDocument();
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
it('shows pre-agg details when preagg node is selected', async () => {
|
|
508
|
+
renderPage();
|
|
509
|
+
|
|
510
|
+
await waitFor(() => {
|
|
511
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Select metric and dimension
|
|
515
|
+
fireEvent.click(screen.getByText('default'));
|
|
516
|
+
await waitFor(() => {
|
|
517
|
+
fireEvent.click(
|
|
518
|
+
screen.getByRole('checkbox', { name: /num_repair_orders/i }),
|
|
519
|
+
);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
await waitFor(() => {
|
|
523
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
fireEvent.click(screen.getByRole('checkbox', { name: /dateint/i }));
|
|
527
|
+
|
|
528
|
+
// Wait for graph to appear
|
|
529
|
+
await waitFor(() => {
|
|
530
|
+
expect(screen.getByTestId('metric-flow-graph')).toBeInTheDocument();
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
// Click on preagg node
|
|
534
|
+
fireEvent.click(screen.getByTestId('select-preagg'));
|
|
535
|
+
|
|
536
|
+
// Should show pre-agg details
|
|
537
|
+
await waitFor(() => {
|
|
538
|
+
expect(screen.getByText('Pre-aggregation')).toBeInTheDocument();
|
|
539
|
+
});
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it('shows metric details when metric node is selected', async () => {
|
|
543
|
+
renderPage();
|
|
544
|
+
|
|
545
|
+
await waitFor(() => {
|
|
546
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Select metric and dimension
|
|
550
|
+
fireEvent.click(screen.getByText('default'));
|
|
551
|
+
await waitFor(() => {
|
|
552
|
+
fireEvent.click(
|
|
553
|
+
screen.getByRole('checkbox', { name: /num_repair_orders/i }),
|
|
554
|
+
);
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
await waitFor(() => {
|
|
558
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
fireEvent.click(screen.getByRole('checkbox', { name: /dateint/i }));
|
|
562
|
+
|
|
563
|
+
// Wait for graph to appear
|
|
564
|
+
await waitFor(() => {
|
|
565
|
+
expect(screen.getByTestId('metric-flow-graph')).toBeInTheDocument();
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
// Click on metric node
|
|
569
|
+
fireEvent.click(screen.getByTestId('select-metric'));
|
|
570
|
+
|
|
571
|
+
// Should show metric details (badge shows "Metric" for non-derived)
|
|
572
|
+
await waitFor(() => {
|
|
573
|
+
expect(screen.getByText('Metric')).toBeInTheDocument();
|
|
574
|
+
});
|
|
298
575
|
});
|
|
299
576
|
});
|
|
300
577
|
|
|
301
578
|
describe('Error Handling', () => {
|
|
302
|
-
it('
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
579
|
+
it('displays error when API call fails', async () => {
|
|
580
|
+
mockDjClient.measuresV3.mockRejectedValue(new Error('API Error'));
|
|
581
|
+
|
|
582
|
+
renderPage();
|
|
583
|
+
|
|
584
|
+
await waitFor(() => {
|
|
585
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
// Select metric and dimension
|
|
589
|
+
fireEvent.click(screen.getByText('default'));
|
|
590
|
+
await waitFor(() => {
|
|
591
|
+
fireEvent.click(
|
|
592
|
+
screen.getByRole('checkbox', { name: /num_repair_orders/i }),
|
|
593
|
+
);
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
await waitFor(() => {
|
|
597
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
fireEvent.click(screen.getByRole('checkbox', { name: /dateint/i }));
|
|
601
|
+
|
|
602
|
+
// Should show error
|
|
603
|
+
await waitFor(() => {
|
|
604
|
+
expect(screen.getByText('API Error')).toBeInTheDocument();
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it('handles commonDimensions API error gracefully', async () => {
|
|
609
|
+
mockDjClient.commonDimensions.mockRejectedValue(
|
|
610
|
+
new Error('Dimensions fetch failed'),
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
renderPage();
|
|
614
|
+
|
|
615
|
+
await waitFor(() => {
|
|
616
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// Select a metric
|
|
620
|
+
fireEvent.click(screen.getByText('default'));
|
|
621
|
+
await waitFor(() => {
|
|
622
|
+
fireEvent.click(
|
|
623
|
+
screen.getByRole('checkbox', { name: /num_repair_orders/i }),
|
|
624
|
+
);
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
// Should handle gracefully (empty dimensions)
|
|
628
|
+
await waitFor(() => {
|
|
629
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
630
|
+
});
|
|
307
631
|
});
|
|
308
632
|
});
|
|
309
633
|
|
|
310
|
-
describe('Dimension
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
634
|
+
describe('Dimension Selection', () => {
|
|
635
|
+
it('clears invalid dimension selections when dimensions change', async () => {
|
|
636
|
+
// First commonDimensions call returns 2 dimensions
|
|
637
|
+
mockDjClient.commonDimensions.mockResolvedValueOnce(mockCommonDimensions);
|
|
638
|
+
// Second call returns only 1 dimension
|
|
639
|
+
mockDjClient.commonDimensions.mockResolvedValueOnce([
|
|
640
|
+
mockCommonDimensions[0],
|
|
641
|
+
]);
|
|
642
|
+
|
|
643
|
+
renderPage();
|
|
644
|
+
|
|
645
|
+
await waitFor(() => {
|
|
646
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// Select first metric
|
|
650
|
+
fireEvent.click(screen.getByText('default'));
|
|
651
|
+
await waitFor(() => {
|
|
652
|
+
fireEvent.click(
|
|
653
|
+
screen.getByRole('checkbox', { name: /num_repair_orders/i }),
|
|
654
|
+
);
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// Wait for dimensions
|
|
658
|
+
await waitFor(() => {
|
|
659
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Select a dimension
|
|
663
|
+
const dimCheckbox = screen.getByRole('checkbox', { name: /dateint/i });
|
|
664
|
+
fireEvent.click(dimCheckbox);
|
|
665
|
+
|
|
666
|
+
// Select another metric (triggers new commonDimensions call)
|
|
667
|
+
await waitFor(() => {
|
|
668
|
+
fireEvent.click(
|
|
669
|
+
screen.getByRole('checkbox', { name: /avg_repair_price/i }),
|
|
670
|
+
);
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
// Invalid dimensions should be cleared automatically
|
|
674
|
+
await waitFor(() => {
|
|
675
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalledTimes(2);
|
|
676
|
+
});
|
|
677
|
+
});
|
|
678
|
+
});
|
|
679
|
+
|
|
680
|
+
describe('Clear Selection', () => {
|
|
681
|
+
it('clears all selections when Clear all is clicked', async () => {
|
|
682
|
+
mockDjClient.cubeForPlanner.mockResolvedValue(mockCubeData);
|
|
683
|
+
|
|
684
|
+
renderPage();
|
|
685
|
+
|
|
686
|
+
await waitFor(() => {
|
|
687
|
+
expect(mockDjClient.listCubesForPreset).toHaveBeenCalled();
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// Load a cube
|
|
691
|
+
fireEvent.click(screen.getByText('Load from Cube'));
|
|
692
|
+
fireEvent.click(screen.getByText('Test Cube'));
|
|
693
|
+
|
|
694
|
+
await waitFor(() => {
|
|
695
|
+
expect(mockDjClient.cubeForPlanner).toHaveBeenCalled();
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
// Wait for common dimensions to load after cube selection
|
|
699
|
+
await waitFor(() => {
|
|
700
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// Click Clear all
|
|
704
|
+
const clearButton = screen.getByText('Clear all');
|
|
705
|
+
fireEvent.click(clearButton);
|
|
706
|
+
|
|
707
|
+
// Should show "Load from Cube" again (cube unloaded)
|
|
708
|
+
await waitFor(() => {
|
|
709
|
+
expect(screen.getByText('Load from Cube')).toBeInTheDocument();
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
describe('Pre-aggregation Lookup', () => {
|
|
715
|
+
it('fetches existing pre-aggregations when measures result changes', async () => {
|
|
716
|
+
renderPage();
|
|
717
|
+
|
|
718
|
+
await waitFor(() => {
|
|
719
|
+
expect(mockDjClient.metrics).toHaveBeenCalled();
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
// Select metric and dimension
|
|
723
|
+
fireEvent.click(screen.getByText('default'));
|
|
724
|
+
await waitFor(() => {
|
|
725
|
+
fireEvent.click(
|
|
726
|
+
screen.getByRole('checkbox', { name: /num_repair_orders/i }),
|
|
727
|
+
);
|
|
728
|
+
});
|
|
729
|
+
|
|
730
|
+
await waitFor(() => {
|
|
731
|
+
expect(mockDjClient.commonDimensions).toHaveBeenCalled();
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
fireEvent.click(screen.getByRole('checkbox', { name: /dateint/i }));
|
|
735
|
+
|
|
736
|
+
// Should fetch existing pre-aggs
|
|
737
|
+
await waitFor(() => {
|
|
738
|
+
expect(mockDjClient.listPreaggs).toHaveBeenCalled();
|
|
739
|
+
});
|
|
315
740
|
});
|
|
316
741
|
});
|
|
317
742
|
});
|