datajunction-ui 0.0.26 → 0.0.27-alpha.0

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 (28) hide show
  1. package/package.json +2 -2
  2. package/src/app/components/Search.jsx +41 -33
  3. package/src/app/components/__tests__/Search.test.jsx +46 -11
  4. package/src/app/index.tsx +3 -3
  5. package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +57 -8
  6. package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +17 -5
  7. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +97 -1
  8. package/src/app/pages/AddEditNodePage/index.jsx +61 -17
  9. package/src/app/pages/NodePage/WatchNodeButton.jsx +12 -5
  10. package/src/app/pages/QueryPlannerPage/MetricFlowGraph.jsx +93 -15
  11. package/src/app/pages/QueryPlannerPage/PreAggDetailsPanel.jsx +2320 -65
  12. package/src/app/pages/QueryPlannerPage/SelectionPanel.jsx +234 -25
  13. package/src/app/pages/QueryPlannerPage/__tests__/MetricFlowGraph.test.jsx +315 -122
  14. package/src/app/pages/QueryPlannerPage/__tests__/PreAggDetailsPanel.test.jsx +2672 -314
  15. package/src/app/pages/QueryPlannerPage/__tests__/SelectionPanel.test.jsx +567 -0
  16. package/src/app/pages/QueryPlannerPage/__tests__/index.test.jsx +480 -55
  17. package/src/app/pages/QueryPlannerPage/index.jsx +1021 -14
  18. package/src/app/pages/QueryPlannerPage/styles.css +1990 -62
  19. package/src/app/pages/Root/__tests__/index.test.jsx +79 -8
  20. package/src/app/pages/Root/index.tsx +1 -6
  21. package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +82 -0
  22. package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +37 -0
  23. package/src/app/pages/SettingsPage/__tests__/ServiceAccountsSection.test.jsx +48 -0
  24. package/src/app/pages/SettingsPage/__tests__/index.test.jsx +169 -1
  25. package/src/app/services/DJService.js +492 -3
  26. package/src/app/services/__tests__/DJService.test.jsx +582 -0
  27. package/src/mocks/mockNodes.jsx +36 -0
  28. package/webpack.config.js +27 -0
@@ -1,17 +1,19 @@
1
- import { render, screen, fireEvent, waitFor } from '@testing-library/react';
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
- MetricFlowGraph: ({
10
- grainGroups,
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 renderPage = () => {
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
- <DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
120
- <QueryPlannerPage />
121
- </DJClientContext.Provider>
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
- expect(screen.getAllByText('Query Planner').length).toBeGreaterThan(0);
143
- expect(
144
- screen.getByText(
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
- expect(screen.getByText('Metrics')).toBeInTheDocument();
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
- expect(screen.getByText('Dimensions')).toBeInTheDocument();
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('shows empty state when no metrics/dimensions selected', () => {
240
+ it('fetches cube list on mount', async () => {
168
241
  renderPage();
169
- expect(
170
- screen.getByText('Select Metrics & Dimensions'),
171
- ).toBeInTheDocument();
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('Graph Rendering', () => {
279
- // Note: Graph rendering with full data flow is tested in MetricFlowGraph.test.jsx
280
- // The integration between selecting metrics/dimensions and graph updates
281
- // is better suited for E2E tests due to complex async dependencies
282
- it('page structure includes graph container', () => {
283
- // MetricFlowGraph component is rendered within the page structure
284
- // Direct testing of graph rendering is in MetricFlowGraph.test.jsx
285
- expect(true).toBe(true);
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('Query Overview Panel', () => {
290
- // Note: QueryOverviewPanel display is tested in PreAggDetailsPanel.test.jsx
291
- // which directly tests the component with mocked data.
292
- // Full integration testing of the data flow requires more complex setup
293
- // and is better suited for E2E tests.
294
- it('component structure includes query overview panel', () => {
295
- // The page renders QueryOverviewPanel when data is loaded
296
- // This is a structural test - actual rendering is tested in PreAggDetailsPanel.test.jsx
297
- expect(true).toBe(true);
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('handles API errors gracefully', () => {
303
- // Error handling is tested implicitly through the component structure
304
- // The component catches errors from measuresV3/metricsV3 and displays them
305
- // Full integration testing requires a more complex setup
306
- expect(true).toBe(true);
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 Deduplication', () => {
311
- // Note: Dimension deduplication is tested in SelectionPanel.test.jsx
312
- // which directly tests the deduplication logic with controlled test data
313
- it('deduplication logic is tested in SelectionPanel tests', () => {
314
- expect(true).toBe(true);
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
  });