datajunction-ui 0.0.93 → 0.0.95
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 +1 -1
- package/src/app/components/NodeComponents.jsx +4 -0
- package/src/app/components/Tab.jsx +11 -16
- package/src/app/components/__tests__/Tab.test.jsx +4 -2
- package/src/app/hooks/useWorkspaceData.js +226 -0
- package/src/app/index.tsx +17 -1
- package/src/app/pages/MyWorkspacePage/ActiveBranchesSection.jsx +38 -107
- package/src/app/pages/MyWorkspacePage/MyNodesSection.jsx +31 -6
- package/src/app/pages/MyWorkspacePage/MyWorkspacePage.css +5 -0
- package/src/app/pages/MyWorkspacePage/NeedsAttentionSection.jsx +86 -100
- package/src/app/pages/MyWorkspacePage/TypeGroupGrid.jsx +7 -11
- package/src/app/pages/MyWorkspacePage/__tests__/ActiveBranchesSection.test.jsx +79 -11
- package/src/app/pages/MyWorkspacePage/__tests__/CollectionsSection.test.jsx +22 -0
- package/src/app/pages/MyWorkspacePage/__tests__/MaterializationsSection.test.jsx +57 -0
- package/src/app/pages/MyWorkspacePage/__tests__/MyNodesSection.test.jsx +60 -18
- package/src/app/pages/MyWorkspacePage/__tests__/MyWorkspacePage.test.jsx +156 -162
- package/src/app/pages/MyWorkspacePage/__tests__/NeedsAttentionSection.test.jsx +17 -18
- package/src/app/pages/MyWorkspacePage/__tests__/NotificationsSection.test.jsx +179 -0
- package/src/app/pages/MyWorkspacePage/__tests__/TypeGroupGrid.test.jsx +169 -49
- package/src/app/pages/MyWorkspacePage/index.jsx +41 -73
- package/src/app/pages/NodePage/NodeDataFlowTab.jsx +464 -0
- package/src/app/pages/NodePage/NodeDependenciesTab.jsx +1 -1
- package/src/app/pages/NodePage/NodeDimensionsTab.jsx +362 -0
- package/src/app/pages/NodePage/NodeLineageTab.jsx +1 -0
- package/src/app/pages/NodePage/NodesWithDimension.jsx +3 -3
- package/src/app/pages/NodePage/__tests__/NodeDataFlowTab.test.jsx +428 -0
- package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +18 -1
- package/src/app/pages/NodePage/__tests__/NodeDimensionsTab.test.jsx +412 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +28 -3
- package/src/app/pages/NodePage/__tests__/NodeWithDimension.test.jsx +2 -2
- package/src/app/pages/NodePage/index.jsx +15 -8
- package/src/app/pages/QueryPlannerPage/ResultsView.jsx +420 -86
- package/src/app/pages/QueryPlannerPage/SelectionPanel.jsx +32 -1
- package/src/app/pages/QueryPlannerPage/__tests__/ResultsView.test.jsx +322 -0
- package/src/app/pages/QueryPlannerPage/__tests__/SelectionPanel.test.jsx +431 -2
- package/src/app/pages/QueryPlannerPage/index.jsx +31 -5
- package/src/app/pages/QueryPlannerPage/styles.css +211 -2
- package/src/app/pages/Root/__tests__/index.test.jsx +2 -3
- package/src/app/pages/Root/index.tsx +1 -1
- package/src/app/services/DJService.js +133 -23
- package/src/app/services/__tests__/DJService.test.jsx +600 -11
- package/src/styles/index.css +32 -0
|
@@ -14,6 +14,49 @@ jest.mock('react-syntax-highlighter/src/styles/hljs', () => ({
|
|
|
14
14
|
}));
|
|
15
15
|
jest.mock('react-syntax-highlighter/dist/esm/languages/hljs/sql', () => ({}));
|
|
16
16
|
|
|
17
|
+
// Mock recharts to avoid canvas rendering issues in jsdom.
|
|
18
|
+
// YAxis calls tickFormatter with sample values (covering the formatYAxis helper).
|
|
19
|
+
jest.mock('recharts', () => {
|
|
20
|
+
const React = require('react');
|
|
21
|
+
const mockComponent =
|
|
22
|
+
name =>
|
|
23
|
+
({ children, ...props }) =>
|
|
24
|
+
React.createElement('div', { 'data-testid': name, ...props }, children);
|
|
25
|
+
|
|
26
|
+
// YAxis: call tickFormatter with a spread of magnitudes so formatYAxis branches are hit
|
|
27
|
+
const MockYAxis = ({ children, tickFormatter, ...props }) => {
|
|
28
|
+
if (tickFormatter) {
|
|
29
|
+
// Exercise all four branches of formatYAxis
|
|
30
|
+
tickFormatter(1_500_000_000); // >=1B
|
|
31
|
+
tickFormatter(2_500_000); // >=1M
|
|
32
|
+
tickFormatter(5_000); // >=1K
|
|
33
|
+
tickFormatter(42); // plain
|
|
34
|
+
}
|
|
35
|
+
return React.createElement(
|
|
36
|
+
'div',
|
|
37
|
+
{ 'data-testid': 'YAxis', ...props },
|
|
38
|
+
children,
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
LineChart: mockComponent('LineChart'),
|
|
44
|
+
BarChart: mockComponent('BarChart'),
|
|
45
|
+
Line: mockComponent('Line'),
|
|
46
|
+
Bar: mockComponent('Bar'),
|
|
47
|
+
XAxis: mockComponent('XAxis'),
|
|
48
|
+
YAxis: MockYAxis,
|
|
49
|
+
CartesianGrid: mockComponent('CartesianGrid'),
|
|
50
|
+
Tooltip: mockComponent('Tooltip'),
|
|
51
|
+
ResponsiveContainer: ({ children }) =>
|
|
52
|
+
React.createElement(
|
|
53
|
+
'div',
|
|
54
|
+
{ 'data-testid': 'ResponsiveContainer' },
|
|
55
|
+
children,
|
|
56
|
+
),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
17
60
|
// Mock clipboard API
|
|
18
61
|
const mockWriteText = jest.fn();
|
|
19
62
|
Object.assign(navigator, {
|
|
@@ -385,4 +428,283 @@ describe('ResultsView', () => {
|
|
|
385
428
|
);
|
|
386
429
|
});
|
|
387
430
|
});
|
|
431
|
+
|
|
432
|
+
describe('Chart Tab', () => {
|
|
433
|
+
// Results with a string dimension + numeric metric → bar chart config
|
|
434
|
+
const barChartResults = {
|
|
435
|
+
results: [
|
|
436
|
+
{
|
|
437
|
+
columns: [
|
|
438
|
+
{ name: 'country', type: 'STRING' },
|
|
439
|
+
{ name: 'revenue', type: 'FLOAT' },
|
|
440
|
+
],
|
|
441
|
+
rows: [
|
|
442
|
+
['US', 1000],
|
|
443
|
+
['UK', 500],
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// Results with a time dimension + numeric metric → line chart config
|
|
450
|
+
const lineChartResults = {
|
|
451
|
+
results: [
|
|
452
|
+
{
|
|
453
|
+
columns: [
|
|
454
|
+
{ name: 'date', type: 'DATE' },
|
|
455
|
+
{ name: 'revenue', type: 'FLOAT' },
|
|
456
|
+
],
|
|
457
|
+
rows: [
|
|
458
|
+
['2024-01-01', 1000],
|
|
459
|
+
['2024-01-02', 1500],
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
],
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
// Results with two numeric columns (no string/time dim) → line with first as x
|
|
466
|
+
const allNumericResults = {
|
|
467
|
+
results: [
|
|
468
|
+
{
|
|
469
|
+
columns: [
|
|
470
|
+
{ name: 'x_val', type: 'FLOAT' },
|
|
471
|
+
{ name: 'y_val', type: 'FLOAT' },
|
|
472
|
+
],
|
|
473
|
+
rows: [
|
|
474
|
+
[1.0, 10.5],
|
|
475
|
+
[2.0, 20.5],
|
|
476
|
+
],
|
|
477
|
+
},
|
|
478
|
+
],
|
|
479
|
+
};
|
|
480
|
+
|
|
481
|
+
// Scalar result: single numeric column, one row → KPI cards
|
|
482
|
+
const scalarResults = {
|
|
483
|
+
results: [
|
|
484
|
+
{
|
|
485
|
+
columns: [{ name: 'total_revenue', type: 'FLOAT' }],
|
|
486
|
+
rows: [[1234567.89]],
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
it('renders Chart tab button', () => {
|
|
492
|
+
render(<ResultsView {...defaultProps} results={barChartResults} />);
|
|
493
|
+
expect(screen.getByText('Chart')).toBeInTheDocument();
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('Chart tab is enabled when data is chartable (bar chart data)', () => {
|
|
497
|
+
render(<ResultsView {...defaultProps} results={barChartResults} />);
|
|
498
|
+
const chartTab = screen.getByText('Chart').closest('button');
|
|
499
|
+
expect(chartTab).not.toHaveClass('disabled');
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('Chart tab is disabled when data is not chartable (string-only columns)', () => {
|
|
503
|
+
render(
|
|
504
|
+
<ResultsView
|
|
505
|
+
{...defaultProps}
|
|
506
|
+
results={{
|
|
507
|
+
results: [
|
|
508
|
+
{
|
|
509
|
+
columns: [{ name: 'name', type: 'STRING' }],
|
|
510
|
+
rows: [['Alice']],
|
|
511
|
+
},
|
|
512
|
+
],
|
|
513
|
+
}}
|
|
514
|
+
/>,
|
|
515
|
+
);
|
|
516
|
+
const chartTab = screen.getByText('Chart').closest('button');
|
|
517
|
+
expect(chartTab).toHaveClass('disabled');
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
it('switches to chart view when Chart tab is clicked (bar chart)', () => {
|
|
521
|
+
render(<ResultsView {...defaultProps} results={barChartResults} />);
|
|
522
|
+
|
|
523
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
524
|
+
|
|
525
|
+
// The chart wrapper should appear
|
|
526
|
+
expect(
|
|
527
|
+
document.querySelector('.results-chart-wrapper'),
|
|
528
|
+
).toBeInTheDocument();
|
|
529
|
+
expect(screen.getByTestId('BarChart')).toBeInTheDocument();
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('switches to chart view when Chart tab is clicked (line chart)', () => {
|
|
533
|
+
render(<ResultsView {...defaultProps} results={lineChartResults} />);
|
|
534
|
+
|
|
535
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
536
|
+
|
|
537
|
+
expect(
|
|
538
|
+
document.querySelector('.results-chart-wrapper'),
|
|
539
|
+
).toBeInTheDocument();
|
|
540
|
+
expect(screen.getByTestId('LineChart')).toBeInTheDocument();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it('switches back to table view when Table tab is clicked', () => {
|
|
544
|
+
render(<ResultsView {...defaultProps} results={barChartResults} />);
|
|
545
|
+
|
|
546
|
+
// Switch to chart
|
|
547
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
548
|
+
expect(
|
|
549
|
+
document.querySelector('.results-chart-wrapper'),
|
|
550
|
+
).toBeInTheDocument();
|
|
551
|
+
|
|
552
|
+
// Switch back to table
|
|
553
|
+
fireEvent.click(screen.getByText('Table'));
|
|
554
|
+
expect(
|
|
555
|
+
document.querySelector('.results-table-wrapper'),
|
|
556
|
+
).toBeInTheDocument();
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
it('renders line chart for all-numeric columns (lines 93-95: no time/string dim)', () => {
|
|
560
|
+
render(<ResultsView {...defaultProps} results={allNumericResults} />);
|
|
561
|
+
|
|
562
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
563
|
+
|
|
564
|
+
expect(screen.getByTestId('LineChart')).toBeInTheDocument();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('renders KPI cards for scalar numeric result', () => {
|
|
568
|
+
render(<ResultsView {...defaultProps} results={scalarResults} />);
|
|
569
|
+
|
|
570
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
571
|
+
|
|
572
|
+
expect(document.querySelector('.kpi-cards')).toBeInTheDocument();
|
|
573
|
+
expect(document.querySelector('.kpi-label')).toHaveTextContent(
|
|
574
|
+
'total_revenue',
|
|
575
|
+
);
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('KPI card formats null value as em-dash', () => {
|
|
579
|
+
render(
|
|
580
|
+
<ResultsView
|
|
581
|
+
{...defaultProps}
|
|
582
|
+
results={{
|
|
583
|
+
results: [
|
|
584
|
+
{
|
|
585
|
+
columns: [{ name: 'revenue', type: 'FLOAT' }],
|
|
586
|
+
rows: [[null]],
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
}}
|
|
590
|
+
/>,
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
594
|
+
|
|
595
|
+
expect(document.querySelector('.kpi-value')).toHaveTextContent('—');
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('KPI card formats numeric value with toLocaleString', () => {
|
|
599
|
+
render(<ResultsView {...defaultProps} results={scalarResults} />);
|
|
600
|
+
|
|
601
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
602
|
+
|
|
603
|
+
// 1234567.89 formatted
|
|
604
|
+
const kpiValue = document.querySelector('.kpi-value');
|
|
605
|
+
expect(kpiValue).toBeInTheDocument();
|
|
606
|
+
// Value should be a localized number string (not null)
|
|
607
|
+
expect(kpiValue.textContent).not.toBe('—');
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
it('KPI card shows column type when present', () => {
|
|
611
|
+
render(<ResultsView {...defaultProps} results={scalarResults} />);
|
|
612
|
+
|
|
613
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
614
|
+
|
|
615
|
+
expect(document.querySelector('.kpi-type')).toHaveTextContent('FLOAT');
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it('renders small multiples when 3+ metric columns present', () => {
|
|
619
|
+
const threeMetricResults = {
|
|
620
|
+
results: [
|
|
621
|
+
{
|
|
622
|
+
columns: [
|
|
623
|
+
{ name: 'date', type: 'DATE' },
|
|
624
|
+
{ name: 'metric_a', type: 'FLOAT' },
|
|
625
|
+
{ name: 'metric_b', type: 'FLOAT' },
|
|
626
|
+
{ name: 'metric_c', type: 'FLOAT' },
|
|
627
|
+
],
|
|
628
|
+
rows: [
|
|
629
|
+
['2024-01-01', 10, 20, 30],
|
|
630
|
+
['2024-01-02', 15, 25, 35],
|
|
631
|
+
],
|
|
632
|
+
},
|
|
633
|
+
],
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
render(<ResultsView {...defaultProps} results={threeMetricResults} />);
|
|
637
|
+
|
|
638
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
639
|
+
|
|
640
|
+
// SMALL_MULTIPLES_THRESHOLD is 2, so 3 metric cols triggers small multiples
|
|
641
|
+
expect(document.querySelector('.small-multiples')).toBeInTheDocument();
|
|
642
|
+
const labels = document.querySelectorAll('.small-multiple-label');
|
|
643
|
+
expect(labels.length).toBe(3);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it('does not click disabled Chart tab (canChart false)', () => {
|
|
647
|
+
// Single string column → not chartable
|
|
648
|
+
render(
|
|
649
|
+
<ResultsView
|
|
650
|
+
{...defaultProps}
|
|
651
|
+
results={{
|
|
652
|
+
results: [
|
|
653
|
+
{
|
|
654
|
+
columns: [{ name: 'label', type: 'STRING' }],
|
|
655
|
+
rows: [['x']],
|
|
656
|
+
},
|
|
657
|
+
],
|
|
658
|
+
}}
|
|
659
|
+
/>,
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
const chartTab = screen.getByText('Chart').closest('button');
|
|
663
|
+
fireEvent.click(chartTab);
|
|
664
|
+
|
|
665
|
+
// Should still be on table view
|
|
666
|
+
expect(
|
|
667
|
+
document.querySelector('.results-table-wrapper'),
|
|
668
|
+
).toBeInTheDocument();
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
it('resets to table view if new results are not chartable while on chart tab', () => {
|
|
672
|
+
const { rerender } = render(
|
|
673
|
+
<ResultsView {...defaultProps} results={barChartResults} />,
|
|
674
|
+
);
|
|
675
|
+
|
|
676
|
+
// Switch to chart view
|
|
677
|
+
fireEvent.click(screen.getByText('Chart'));
|
|
678
|
+
expect(
|
|
679
|
+
document.querySelector('.results-chart-wrapper'),
|
|
680
|
+
).toBeInTheDocument();
|
|
681
|
+
|
|
682
|
+
// Re-render with non-chartable results (empty rows)
|
|
683
|
+
rerender(
|
|
684
|
+
<ResultsView
|
|
685
|
+
{...defaultProps}
|
|
686
|
+
results={{ results: [{ columns: [], rows: [] }] }}
|
|
687
|
+
/>,
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
// Should auto-reset to table view
|
|
691
|
+
expect(
|
|
692
|
+
document.querySelector('.results-table-wrapper'),
|
|
693
|
+
).toBeInTheDocument();
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it('shows links during loading when links prop is provided', () => {
|
|
697
|
+
render(
|
|
698
|
+
<ResultsView
|
|
699
|
+
{...defaultProps}
|
|
700
|
+
loading={true}
|
|
701
|
+
links={['https://example.com/query/123']}
|
|
702
|
+
/>,
|
|
703
|
+
);
|
|
704
|
+
|
|
705
|
+
const link = screen.getByText('View query ↗');
|
|
706
|
+
expect(link).toBeInTheDocument();
|
|
707
|
+
expect(link).toHaveAttribute('href', 'https://example.com/query/123');
|
|
708
|
+
});
|
|
709
|
+
});
|
|
388
710
|
});
|