datajunction-ui 0.0.26-alpha.0 → 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 +1 -1
- 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 -1
- 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
|
@@ -426,4 +426,571 @@ describe('SelectionPanel', () => {
|
|
|
426
426
|
expect(checkbox).toBeChecked();
|
|
427
427
|
});
|
|
428
428
|
});
|
|
429
|
+
|
|
430
|
+
describe('Cube Preset Loading', () => {
|
|
431
|
+
const cubeProps = {
|
|
432
|
+
...defaultProps,
|
|
433
|
+
cubes: [
|
|
434
|
+
{ name: 'default.test_cube', display_name: 'Test Cube' },
|
|
435
|
+
{ name: 'sales.revenue_cube', display_name: 'Revenue Cube' },
|
|
436
|
+
],
|
|
437
|
+
onLoadCubePreset: jest.fn(),
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
it('shows Load from Cube button when cubes are available', () => {
|
|
441
|
+
render(<SelectionPanel {...cubeProps} />);
|
|
442
|
+
expect(screen.getByText('Load from Cube')).toBeInTheDocument();
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('opens dropdown when Load from Cube button is clicked', () => {
|
|
446
|
+
render(<SelectionPanel {...cubeProps} />);
|
|
447
|
+
|
|
448
|
+
fireEvent.click(screen.getByText('Load from Cube'));
|
|
449
|
+
|
|
450
|
+
expect(
|
|
451
|
+
screen.getByPlaceholderText('Search cubes...'),
|
|
452
|
+
).toBeInTheDocument();
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it('displays cube options in dropdown', () => {
|
|
456
|
+
render(<SelectionPanel {...cubeProps} />);
|
|
457
|
+
|
|
458
|
+
fireEvent.click(screen.getByText('Load from Cube'));
|
|
459
|
+
|
|
460
|
+
expect(screen.getByText('Test Cube')).toBeInTheDocument();
|
|
461
|
+
expect(screen.getByText('Revenue Cube')).toBeInTheDocument();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('filters cubes by search term', () => {
|
|
465
|
+
render(<SelectionPanel {...cubeProps} />);
|
|
466
|
+
|
|
467
|
+
fireEvent.click(screen.getByText('Load from Cube'));
|
|
468
|
+
|
|
469
|
+
const searchInput = screen.getByPlaceholderText('Search cubes...');
|
|
470
|
+
fireEvent.change(searchInput, { target: { value: 'Revenue' } });
|
|
471
|
+
|
|
472
|
+
expect(screen.getByText('Revenue Cube')).toBeInTheDocument();
|
|
473
|
+
expect(screen.queryByText('Test Cube')).not.toBeInTheDocument();
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('calls onLoadCubePreset when a cube is selected', () => {
|
|
477
|
+
const onLoadCubePreset = jest.fn();
|
|
478
|
+
render(
|
|
479
|
+
<SelectionPanel {...cubeProps} onLoadCubePreset={onLoadCubePreset} />,
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
fireEvent.click(screen.getByText('Load from Cube'));
|
|
483
|
+
fireEvent.click(screen.getByText('Test Cube'));
|
|
484
|
+
|
|
485
|
+
expect(onLoadCubePreset).toHaveBeenCalledWith('default.test_cube');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('shows loaded cube name in button when cube is loaded', () => {
|
|
489
|
+
render(
|
|
490
|
+
<SelectionPanel {...cubeProps} loadedCubeName="default.test_cube" />,
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
// Should show the cube display name or short name
|
|
494
|
+
expect(screen.getByText('Test Cube')).toBeInTheDocument();
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('shows "No cubes match your search" when search has no results', () => {
|
|
498
|
+
render(<SelectionPanel {...cubeProps} />);
|
|
499
|
+
|
|
500
|
+
fireEvent.click(screen.getByText('Load from Cube'));
|
|
501
|
+
|
|
502
|
+
const searchInput = screen.getByPlaceholderText('Search cubes...');
|
|
503
|
+
fireEvent.change(searchInput, { target: { value: 'nonexistent' } });
|
|
504
|
+
|
|
505
|
+
expect(
|
|
506
|
+
screen.getByText('No cubes match your search'),
|
|
507
|
+
).toBeInTheDocument();
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
it('closes dropdown when clicking outside', () => {
|
|
511
|
+
render(<SelectionPanel {...cubeProps} />);
|
|
512
|
+
|
|
513
|
+
fireEvent.click(screen.getByText('Load from Cube'));
|
|
514
|
+
expect(
|
|
515
|
+
screen.getByPlaceholderText('Search cubes...'),
|
|
516
|
+
).toBeInTheDocument();
|
|
517
|
+
|
|
518
|
+
// Simulate clicking outside
|
|
519
|
+
fireEvent.mouseDown(document.body);
|
|
520
|
+
|
|
521
|
+
expect(
|
|
522
|
+
screen.queryByPlaceholderText('Search cubes...'),
|
|
523
|
+
).not.toBeInTheDocument();
|
|
524
|
+
});
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
describe('Selected Metrics Chips', () => {
|
|
528
|
+
it('displays selected metrics as chips', () => {
|
|
529
|
+
render(
|
|
530
|
+
<SelectionPanel
|
|
531
|
+
{...defaultProps}
|
|
532
|
+
selectedMetrics={['default.num_repair_orders']}
|
|
533
|
+
/>,
|
|
534
|
+
);
|
|
535
|
+
|
|
536
|
+
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('removes metric when chip remove button is clicked', () => {
|
|
540
|
+
const onMetricsChange = jest.fn();
|
|
541
|
+
render(
|
|
542
|
+
<SelectionPanel
|
|
543
|
+
{...defaultProps}
|
|
544
|
+
selectedMetrics={[
|
|
545
|
+
'default.num_repair_orders',
|
|
546
|
+
'default.avg_repair_price',
|
|
547
|
+
]}
|
|
548
|
+
onMetricsChange={onMetricsChange}
|
|
549
|
+
/>,
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
// Find the remove button for num_repair_orders chip
|
|
553
|
+
const removeBtn = screen.getByTitle('Remove num_repair_orders');
|
|
554
|
+
fireEvent.click(removeBtn);
|
|
555
|
+
|
|
556
|
+
expect(onMetricsChange).toHaveBeenCalledWith([
|
|
557
|
+
'default.avg_repair_price',
|
|
558
|
+
]);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('shows "Show all" button when many metrics are selected', () => {
|
|
562
|
+
const manyMetrics = Array.from(
|
|
563
|
+
{ length: 12 },
|
|
564
|
+
(_, i) => `default.metric_${i}`,
|
|
565
|
+
);
|
|
566
|
+
render(
|
|
567
|
+
<SelectionPanel
|
|
568
|
+
{...defaultProps}
|
|
569
|
+
metrics={manyMetrics}
|
|
570
|
+
selectedMetrics={manyMetrics}
|
|
571
|
+
/>,
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
expect(screen.getByText(/Show all 12/)).toBeInTheDocument();
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it('toggles chips expansion when Show all/Show less is clicked', () => {
|
|
578
|
+
const manyMetrics = Array.from(
|
|
579
|
+
{ length: 12 },
|
|
580
|
+
(_, i) => `default.metric_${i}`,
|
|
581
|
+
);
|
|
582
|
+
render(
|
|
583
|
+
<SelectionPanel
|
|
584
|
+
{...defaultProps}
|
|
585
|
+
metrics={manyMetrics}
|
|
586
|
+
selectedMetrics={manyMetrics}
|
|
587
|
+
/>,
|
|
588
|
+
);
|
|
589
|
+
|
|
590
|
+
// Click to expand
|
|
591
|
+
const expandBtn = screen.getByText(/Show all 12/);
|
|
592
|
+
fireEvent.click(expandBtn);
|
|
593
|
+
|
|
594
|
+
// Should now show "Show less"
|
|
595
|
+
expect(screen.getByText('Show less')).toBeInTheDocument();
|
|
596
|
+
|
|
597
|
+
// Click to collapse
|
|
598
|
+
fireEvent.click(screen.getByText('Show less'));
|
|
599
|
+
|
|
600
|
+
// Should show "Show all" again
|
|
601
|
+
expect(screen.getByText(/Show all 12/)).toBeInTheDocument();
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
describe('Selected Dimensions Chips', () => {
|
|
606
|
+
it('displays selected dimensions as chips', () => {
|
|
607
|
+
render(
|
|
608
|
+
<SelectionPanel
|
|
609
|
+
{...defaultProps}
|
|
610
|
+
selectedMetrics={['default.test']}
|
|
611
|
+
selectedDimensions={['default.date_dim.dateint']}
|
|
612
|
+
/>,
|
|
613
|
+
);
|
|
614
|
+
|
|
615
|
+
// Check for chip by looking for the chip container with the dimension display name
|
|
616
|
+
const chipElements = screen.getAllByText('date_dim.dateint');
|
|
617
|
+
// Should have at least one chip (and possibly one in the list)
|
|
618
|
+
expect(chipElements.length).toBeGreaterThanOrEqual(1);
|
|
619
|
+
// The chip should have the chip-label class
|
|
620
|
+
expect(
|
|
621
|
+
document.querySelector('.dimension-chip .chip-label'),
|
|
622
|
+
).toBeInTheDocument();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it('removes dimension when chip remove button is clicked', () => {
|
|
626
|
+
const onDimensionsChange = jest.fn();
|
|
627
|
+
render(
|
|
628
|
+
<SelectionPanel
|
|
629
|
+
{...defaultProps}
|
|
630
|
+
selectedMetrics={['default.test']}
|
|
631
|
+
selectedDimensions={[
|
|
632
|
+
'default.date_dim.dateint',
|
|
633
|
+
'default.date_dim.month',
|
|
634
|
+
]}
|
|
635
|
+
onDimensionsChange={onDimensionsChange}
|
|
636
|
+
/>,
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
// Find the remove button for dateint chip
|
|
640
|
+
const removeBtn = screen.getByTitle('Remove date_dim.dateint');
|
|
641
|
+
fireEvent.click(removeBtn);
|
|
642
|
+
|
|
643
|
+
expect(onDimensionsChange).toHaveBeenCalledWith([
|
|
644
|
+
'default.date_dim.month',
|
|
645
|
+
]);
|
|
646
|
+
});
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
describe('Clear All Button', () => {
|
|
650
|
+
it('shows Clear all button when items are selected', () => {
|
|
651
|
+
render(
|
|
652
|
+
<SelectionPanel
|
|
653
|
+
{...defaultProps}
|
|
654
|
+
selectedMetrics={['default.num_repair_orders']}
|
|
655
|
+
cubes={[{ name: 'default.cube', display_name: 'Cube' }]}
|
|
656
|
+
/>,
|
|
657
|
+
);
|
|
658
|
+
|
|
659
|
+
expect(screen.getByText('Clear all')).toBeInTheDocument();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('calls onClearSelection when Clear all is clicked', () => {
|
|
663
|
+
const onClearSelection = jest.fn();
|
|
664
|
+
render(
|
|
665
|
+
<SelectionPanel
|
|
666
|
+
{...defaultProps}
|
|
667
|
+
selectedMetrics={['default.num_repair_orders']}
|
|
668
|
+
cubes={[{ name: 'default.cube', display_name: 'Cube' }]}
|
|
669
|
+
onClearSelection={onClearSelection}
|
|
670
|
+
/>,
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
fireEvent.click(screen.getByText('Clear all'));
|
|
674
|
+
|
|
675
|
+
expect(onClearSelection).toHaveBeenCalled();
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it('clears metrics and dimensions if no onClearSelection provided', () => {
|
|
679
|
+
const onMetricsChange = jest.fn();
|
|
680
|
+
const onDimensionsChange = jest.fn();
|
|
681
|
+
render(
|
|
682
|
+
<SelectionPanel
|
|
683
|
+
{...defaultProps}
|
|
684
|
+
selectedMetrics={['default.num_repair_orders']}
|
|
685
|
+
selectedDimensions={['default.date_dim.dateint']}
|
|
686
|
+
onMetricsChange={onMetricsChange}
|
|
687
|
+
onDimensionsChange={onDimensionsChange}
|
|
688
|
+
cubes={[{ name: 'default.cube', display_name: 'Cube' }]}
|
|
689
|
+
/>,
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
fireEvent.click(screen.getByText('Clear all'));
|
|
693
|
+
|
|
694
|
+
expect(onMetricsChange).toHaveBeenCalledWith([]);
|
|
695
|
+
expect(onDimensionsChange).toHaveBeenCalledWith([]);
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
describe('Dimension Path Display', () => {
|
|
700
|
+
it('shows dimension path when path has multiple segments', () => {
|
|
701
|
+
const dimensionsWithPath = [
|
|
702
|
+
{
|
|
703
|
+
name: 'default.date_dim.dateint',
|
|
704
|
+
type: 'timestamp',
|
|
705
|
+
path: ['default.orders', 'default.date_dim.dateint'],
|
|
706
|
+
},
|
|
707
|
+
];
|
|
708
|
+
|
|
709
|
+
render(
|
|
710
|
+
<SelectionPanel
|
|
711
|
+
{...defaultProps}
|
|
712
|
+
dimensions={dimensionsWithPath}
|
|
713
|
+
selectedMetrics={['default.test']}
|
|
714
|
+
/>,
|
|
715
|
+
);
|
|
716
|
+
|
|
717
|
+
// Should show the path
|
|
718
|
+
expect(screen.getByText('default.date_dim.dateint')).toBeInTheDocument();
|
|
719
|
+
});
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
describe('Namespace Sorting Logic', () => {
|
|
723
|
+
it('prioritizes namespaces that start with search term', () => {
|
|
724
|
+
const metricsWithNamespaces = [
|
|
725
|
+
'zebra.metric1',
|
|
726
|
+
'alpha.metric2',
|
|
727
|
+
'alpha_test.metric3',
|
|
728
|
+
'beta.metric4',
|
|
729
|
+
];
|
|
730
|
+
|
|
731
|
+
render(
|
|
732
|
+
<SelectionPanel {...defaultProps} metrics={metricsWithNamespaces} />,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
const searchInput = screen.getByPlaceholderText('Search metrics...');
|
|
736
|
+
fireEvent.change(searchInput, { target: { value: 'alpha' } });
|
|
737
|
+
|
|
738
|
+
// Alpha namespace should be expanded first since it starts with 'alpha'
|
|
739
|
+
const namespaces = document.querySelectorAll('.namespace-header');
|
|
740
|
+
expect(namespaces.length).toBeGreaterThan(0);
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
it('sorts namespaces with more matching items higher', () => {
|
|
744
|
+
const metricsWithNamespaces = [
|
|
745
|
+
'default.test_metric1',
|
|
746
|
+
'default.test_metric2',
|
|
747
|
+
'default.test_metric3',
|
|
748
|
+
'other.test_metric4',
|
|
749
|
+
];
|
|
750
|
+
|
|
751
|
+
render(
|
|
752
|
+
<SelectionPanel {...defaultProps} metrics={metricsWithNamespaces} />,
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
const searchInput = screen.getByPlaceholderText('Search metrics...');
|
|
756
|
+
fireEvent.change(searchInput, { target: { value: 'test' } });
|
|
757
|
+
|
|
758
|
+
// Should show namespaces - default has more matching items
|
|
759
|
+
expect(screen.getByText('default')).toBeInTheDocument();
|
|
760
|
+
expect(screen.getByText('other')).toBeInTheDocument();
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
it('sorts namespaces alphabetically when other criteria are equal', () => {
|
|
764
|
+
const metricsWithNamespaces = [
|
|
765
|
+
'zebra.metric1',
|
|
766
|
+
'alpha.metric2',
|
|
767
|
+
'beta.metric3',
|
|
768
|
+
];
|
|
769
|
+
|
|
770
|
+
render(
|
|
771
|
+
<SelectionPanel {...defaultProps} metrics={metricsWithNamespaces} />,
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
// Namespaces should be available
|
|
775
|
+
expect(screen.getByText('alpha')).toBeInTheDocument();
|
|
776
|
+
expect(screen.getByText('beta')).toBeInTheDocument();
|
|
777
|
+
expect(screen.getByText('zebra')).toBeInTheDocument();
|
|
778
|
+
});
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
describe('Dimension Sorting Logic', () => {
|
|
782
|
+
it('prioritizes dimensions that start with search term', () => {
|
|
783
|
+
const sortableDimensions = [
|
|
784
|
+
{ name: 'default.zebra.column', path: [] },
|
|
785
|
+
{ name: 'default.alpha.column', path: [] },
|
|
786
|
+
{ name: 'default.date_dim.alpha_col', path: [] },
|
|
787
|
+
];
|
|
788
|
+
|
|
789
|
+
render(
|
|
790
|
+
<SelectionPanel
|
|
791
|
+
{...defaultProps}
|
|
792
|
+
dimensions={sortableDimensions}
|
|
793
|
+
selectedMetrics={['default.test']}
|
|
794
|
+
/>,
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
const searchInput = screen.getByPlaceholderText('Search dimensions...');
|
|
798
|
+
fireEvent.change(searchInput, { target: { value: 'alpha' } });
|
|
799
|
+
|
|
800
|
+
// Should show matching dimensions
|
|
801
|
+
const checkboxes = screen.getAllByRole('checkbox');
|
|
802
|
+
expect(checkboxes.length).toBeGreaterThan(0);
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
it('sorts dimensions alphabetically by short name', () => {
|
|
806
|
+
const sortableDimensions = [
|
|
807
|
+
{ name: 'default.zebra.col', path: [] },
|
|
808
|
+
{ name: 'default.alpha.col', path: [] },
|
|
809
|
+
{ name: 'default.beta.col', path: [] },
|
|
810
|
+
];
|
|
811
|
+
|
|
812
|
+
render(
|
|
813
|
+
<SelectionPanel
|
|
814
|
+
{...defaultProps}
|
|
815
|
+
dimensions={sortableDimensions}
|
|
816
|
+
selectedMetrics={['default.test']}
|
|
817
|
+
/>,
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
const searchInput = screen.getByPlaceholderText('Search dimensions...');
|
|
821
|
+
fireEvent.change(searchInput, { target: { value: 'col' } });
|
|
822
|
+
|
|
823
|
+
// All three should be visible
|
|
824
|
+
expect(screen.getByText('alpha.col')).toBeInTheDocument();
|
|
825
|
+
expect(screen.getByText('beta.col')).toBeInTheDocument();
|
|
826
|
+
expect(screen.getByText('zebra.col')).toBeInTheDocument();
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
it('handles dimensions with prefix matches before contains matches', () => {
|
|
830
|
+
const sortableDimensions = [
|
|
831
|
+
{ name: 'default.country_code', path: [] },
|
|
832
|
+
{ name: 'default.customer.country', path: [] },
|
|
833
|
+
];
|
|
834
|
+
|
|
835
|
+
render(
|
|
836
|
+
<SelectionPanel
|
|
837
|
+
{...defaultProps}
|
|
838
|
+
dimensions={sortableDimensions}
|
|
839
|
+
selectedMetrics={['default.test']}
|
|
840
|
+
/>,
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
const searchInput = screen.getByPlaceholderText('Search dimensions...');
|
|
844
|
+
fireEvent.change(searchInput, { target: { value: 'country' } });
|
|
845
|
+
|
|
846
|
+
// Both should be visible
|
|
847
|
+
const checkboxes = screen.getAllByRole('checkbox');
|
|
848
|
+
expect(checkboxes.length).toBe(2);
|
|
849
|
+
});
|
|
850
|
+
});
|
|
851
|
+
|
|
852
|
+
describe('Dimensions Chips Toggle', () => {
|
|
853
|
+
it('shows "Show all" button when many dimensions are selected', () => {
|
|
854
|
+
const manyDimensions = Array.from({ length: 15 }, (_, i) => ({
|
|
855
|
+
name: `default.dim_${i}`,
|
|
856
|
+
path: [],
|
|
857
|
+
}));
|
|
858
|
+
|
|
859
|
+
render(
|
|
860
|
+
<SelectionPanel
|
|
861
|
+
{...defaultProps}
|
|
862
|
+
dimensions={manyDimensions}
|
|
863
|
+
selectedMetrics={['default.test']}
|
|
864
|
+
selectedDimensions={manyDimensions.map(d => d.name)}
|
|
865
|
+
/>,
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
expect(screen.getByText(/Show all 15/)).toBeInTheDocument();
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
it('toggles dimension chips expansion', () => {
|
|
872
|
+
const manyDimensions = Array.from({ length: 15 }, (_, i) => ({
|
|
873
|
+
name: `default.dim_${i}`,
|
|
874
|
+
path: [],
|
|
875
|
+
}));
|
|
876
|
+
|
|
877
|
+
render(
|
|
878
|
+
<SelectionPanel
|
|
879
|
+
{...defaultProps}
|
|
880
|
+
dimensions={manyDimensions}
|
|
881
|
+
selectedMetrics={['default.test']}
|
|
882
|
+
selectedDimensions={manyDimensions.map(d => d.name)}
|
|
883
|
+
/>,
|
|
884
|
+
);
|
|
885
|
+
|
|
886
|
+
// Click to expand
|
|
887
|
+
const expandBtn = screen.getByText(/Show all 15/);
|
|
888
|
+
fireEvent.click(expandBtn);
|
|
889
|
+
|
|
890
|
+
// Should show "Show less"
|
|
891
|
+
expect(screen.getByText('Show less')).toBeInTheDocument();
|
|
892
|
+
|
|
893
|
+
// Click to collapse
|
|
894
|
+
fireEvent.click(screen.getByText('Show less'));
|
|
895
|
+
|
|
896
|
+
// Should show "Show all" again
|
|
897
|
+
expect(screen.getByText(/Show all 15/)).toBeInTheDocument();
|
|
898
|
+
});
|
|
899
|
+
});
|
|
900
|
+
|
|
901
|
+
describe('Toggle Namespace', () => {
|
|
902
|
+
it('toggles namespace expansion state', () => {
|
|
903
|
+
render(<SelectionPanel {...defaultProps} />);
|
|
904
|
+
|
|
905
|
+
// Click to expand 'default'
|
|
906
|
+
fireEvent.click(screen.getByText('default'));
|
|
907
|
+
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
|
|
908
|
+
|
|
909
|
+
// Click again to collapse
|
|
910
|
+
fireEvent.click(screen.getByText('default'));
|
|
911
|
+
expect(screen.queryByText('num_repair_orders')).not.toBeInTheDocument();
|
|
912
|
+
|
|
913
|
+
// Click again to expand
|
|
914
|
+
fireEvent.click(screen.getByText('default'));
|
|
915
|
+
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
|
|
916
|
+
});
|
|
917
|
+
|
|
918
|
+
it('allows multiple namespaces to be expanded', () => {
|
|
919
|
+
render(<SelectionPanel {...defaultProps} />);
|
|
920
|
+
|
|
921
|
+
// Expand both default and sales
|
|
922
|
+
fireEvent.click(screen.getByText('default'));
|
|
923
|
+
fireEvent.click(screen.getByText('sales'));
|
|
924
|
+
|
|
925
|
+
// Both should show their metrics
|
|
926
|
+
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
|
|
927
|
+
expect(screen.getByText('revenue')).toBeInTheDocument();
|
|
928
|
+
});
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
describe('Clear Dimension Search', () => {
|
|
932
|
+
it('clears dimension search when clear button is clicked', () => {
|
|
933
|
+
render(
|
|
934
|
+
<SelectionPanel {...defaultProps} selectedMetrics={['default.test']} />,
|
|
935
|
+
);
|
|
936
|
+
|
|
937
|
+
const searchInput = screen.getByPlaceholderText('Search dimensions...');
|
|
938
|
+
fireEvent.change(searchInput, { target: { value: 'test' } });
|
|
939
|
+
|
|
940
|
+
expect(searchInput.value).toBe('test');
|
|
941
|
+
|
|
942
|
+
// Find the clear button (there are two × buttons, one for each search)
|
|
943
|
+
const clearButtons = screen.getAllByText('×');
|
|
944
|
+
// The second one is for dimension search
|
|
945
|
+
fireEvent.click(clearButtons[clearButtons.length - 1]);
|
|
946
|
+
|
|
947
|
+
expect(searchInput.value).toBe('');
|
|
948
|
+
});
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
describe('Remove Dimension from Selected', () => {
|
|
952
|
+
it('removes dimension when clicking X on dimension chip', () => {
|
|
953
|
+
const onDimensionsChange = jest.fn();
|
|
954
|
+
render(
|
|
955
|
+
<SelectionPanel
|
|
956
|
+
{...defaultProps}
|
|
957
|
+
selectedMetrics={['default.test']}
|
|
958
|
+
selectedDimensions={[
|
|
959
|
+
'default.date_dim.dateint',
|
|
960
|
+
'default.date_dim.month',
|
|
961
|
+
'default.date_dim.year',
|
|
962
|
+
]}
|
|
963
|
+
onDimensionsChange={onDimensionsChange}
|
|
964
|
+
/>,
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
// Find and click remove button for dateint
|
|
968
|
+
const removeBtn = screen.getByTitle('Remove date_dim.dateint');
|
|
969
|
+
fireEvent.click(removeBtn);
|
|
970
|
+
|
|
971
|
+
expect(onDimensionsChange).toHaveBeenCalledWith([
|
|
972
|
+
'default.date_dim.month',
|
|
973
|
+
'default.date_dim.year',
|
|
974
|
+
]);
|
|
975
|
+
});
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
describe('Toggle Dimension Selection', () => {
|
|
979
|
+
it('removes dimension when unchecking already selected dimension', () => {
|
|
980
|
+
const onDimensionsChange = jest.fn();
|
|
981
|
+
render(
|
|
982
|
+
<SelectionPanel
|
|
983
|
+
{...defaultProps}
|
|
984
|
+
selectedMetrics={['default.test']}
|
|
985
|
+
selectedDimensions={['default.date_dim.dateint']}
|
|
986
|
+
onDimensionsChange={onDimensionsChange}
|
|
987
|
+
/>,
|
|
988
|
+
);
|
|
989
|
+
|
|
990
|
+
const checkbox = screen.getByRole('checkbox', { name: /dateint/i });
|
|
991
|
+
fireEvent.click(checkbox);
|
|
992
|
+
|
|
993
|
+
expect(onDimensionsChange).toHaveBeenCalledWith([]);
|
|
994
|
+
});
|
|
995
|
+
});
|
|
429
996
|
});
|