datajunction-ui 0.0.43 → 0.0.45
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/__tests__/NamespaceHeader.test.jsx +349 -1
- package/src/app/pages/NodePage/NodeInfoTab.jsx +38 -40
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +0 -133
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +9 -7
- package/src/app/pages/NodePage/index.jsx +12 -11
- package/src/app/pages/QueryPlannerPage/PreAggDetailsPanel.jsx +46 -1
- package/src/app/pages/QueryPlannerPage/ResultsView.jsx +281 -0
- package/src/app/pages/QueryPlannerPage/SelectionPanel.jsx +225 -100
- package/src/app/pages/QueryPlannerPage/__tests__/MetricFlowGraph.test.jsx +193 -0
- package/src/app/pages/QueryPlannerPage/__tests__/ResultsView.test.jsx +388 -0
- package/src/app/pages/QueryPlannerPage/__tests__/SelectionPanel.test.jsx +31 -51
- package/src/app/pages/QueryPlannerPage/__tests__/index.test.jsx +720 -34
- package/src/app/pages/QueryPlannerPage/index.jsx +237 -117
- package/src/app/pages/QueryPlannerPage/styles.css +765 -15
- package/src/app/services/DJService.js +29 -6
- package/src/app/services/__tests__/DJService.test.jsx +163 -0
|
@@ -17,6 +17,11 @@ export function SelectionPanel({
|
|
|
17
17
|
onLoadCubePreset,
|
|
18
18
|
loadedCubeName = null, // Managed by parent for URL persistence
|
|
19
19
|
onClearSelection,
|
|
20
|
+
filters = [],
|
|
21
|
+
onFiltersChange,
|
|
22
|
+
onRunQuery,
|
|
23
|
+
canRunQuery = false,
|
|
24
|
+
queryLoading = false,
|
|
20
25
|
}) {
|
|
21
26
|
const [metricsSearch, setMetricsSearch] = useState('');
|
|
22
27
|
const [dimensionsSearch, setDimensionsSearch] = useState('');
|
|
@@ -25,8 +30,11 @@ export function SelectionPanel({
|
|
|
25
30
|
const [cubeSearch, setCubeSearch] = useState('');
|
|
26
31
|
const [metricsChipsExpanded, setMetricsChipsExpanded] = useState(false);
|
|
27
32
|
const [dimensionsChipsExpanded, setDimensionsChipsExpanded] = useState(false);
|
|
33
|
+
const [filterInput, setFilterInput] = useState('');
|
|
28
34
|
const prevSearchRef = useRef('');
|
|
29
35
|
const cubeDropdownRef = useRef(null);
|
|
36
|
+
const metricsSearchRef = useRef(null);
|
|
37
|
+
const dimensionsSearchRef = useRef(null);
|
|
30
38
|
|
|
31
39
|
// Threshold for showing expand/collapse button
|
|
32
40
|
const CHIPS_COLLAPSE_THRESHOLD = 8;
|
|
@@ -261,6 +269,27 @@ export function SelectionPanel({
|
|
|
261
269
|
}
|
|
262
270
|
};
|
|
263
271
|
|
|
272
|
+
const handleAddFilter = () => {
|
|
273
|
+
const trimmed = filterInput.trim();
|
|
274
|
+
if (trimmed && !filters.includes(trimmed) && onFiltersChange) {
|
|
275
|
+
onFiltersChange([...filters, trimmed]);
|
|
276
|
+
setFilterInput('');
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const handleFilterKeyDown = e => {
|
|
281
|
+
if (e.key === 'Enter') {
|
|
282
|
+
e.preventDefault();
|
|
283
|
+
handleAddFilter();
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const handleRemoveFilter = filterToRemove => {
|
|
288
|
+
if (onFiltersChange) {
|
|
289
|
+
onFiltersChange(filters.filter(f => f !== filterToRemove));
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
|
|
264
293
|
return (
|
|
265
294
|
<div className="selection-panel">
|
|
266
295
|
{/* Cube Preset Dropdown */}
|
|
@@ -286,7 +315,7 @@ export function SelectionPanel({
|
|
|
286
315
|
</button>
|
|
287
316
|
{(selectedMetrics.length > 0 || selectedDimensions.length > 0) && (
|
|
288
317
|
<button className="clear-all-btn" onClick={clearSelection}>
|
|
289
|
-
Clear
|
|
318
|
+
Clear
|
|
290
319
|
</button>
|
|
291
320
|
)}
|
|
292
321
|
</div>
|
|
@@ -312,7 +341,9 @@ export function SelectionPanel({
|
|
|
312
341
|
filteredCubes.map(cube => (
|
|
313
342
|
<button
|
|
314
343
|
key={cube.name}
|
|
315
|
-
className=
|
|
344
|
+
className={`cube-option ${
|
|
345
|
+
loadedCubeName === cube.name ? 'selected' : ''
|
|
346
|
+
}`}
|
|
316
347
|
onClick={() => handleCubeSelect(cube)}
|
|
317
348
|
>
|
|
318
349
|
<span className="cube-name">
|
|
@@ -320,6 +351,9 @@ export function SelectionPanel({
|
|
|
320
351
|
(cube.name ? cube.name.split('.').pop() : 'Unknown')}
|
|
321
352
|
</span>
|
|
322
353
|
<span className="cube-info">{cube.name}</span>
|
|
354
|
+
{loadedCubeName === cube.name && (
|
|
355
|
+
<span className="cube-selected-icon">✓</span>
|
|
356
|
+
)}
|
|
323
357
|
</button>
|
|
324
358
|
))
|
|
325
359
|
)}
|
|
@@ -338,62 +372,71 @@ export function SelectionPanel({
|
|
|
338
372
|
</span>
|
|
339
373
|
</div>
|
|
340
374
|
|
|
341
|
-
{/*
|
|
342
|
-
|
|
343
|
-
|
|
375
|
+
{/* Combined Chips + Search Input */}
|
|
376
|
+
<div
|
|
377
|
+
className="combobox-input"
|
|
378
|
+
onClick={() => metricsSearchRef.current?.focus()}
|
|
379
|
+
>
|
|
380
|
+
{selectedMetrics.length > 0 && (
|
|
344
381
|
<div
|
|
345
|
-
className={`
|
|
346
|
-
|
|
382
|
+
className={`combobox-chips ${
|
|
383
|
+
selectedMetrics.length > CHIPS_COLLAPSE_THRESHOLD
|
|
384
|
+
? metricsChipsExpanded
|
|
385
|
+
? 'expanded'
|
|
386
|
+
: 'collapsed'
|
|
387
|
+
: ''
|
|
347
388
|
}`}
|
|
348
389
|
>
|
|
349
|
-
|
|
350
|
-
{
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
390
|
+
{selectedMetrics.map(metric => (
|
|
391
|
+
<span key={metric} className="selected-chip metric-chip">
|
|
392
|
+
{getShortName(metric)}
|
|
393
|
+
<button
|
|
394
|
+
className="chip-remove"
|
|
395
|
+
onClick={e => {
|
|
396
|
+
e.stopPropagation();
|
|
397
|
+
removeMetric(metric);
|
|
398
|
+
}}
|
|
399
|
+
title={`Remove ${getShortName(metric)}`}
|
|
400
|
+
>
|
|
401
|
+
×
|
|
402
|
+
</button>
|
|
403
|
+
</span>
|
|
404
|
+
))}
|
|
363
405
|
</div>
|
|
406
|
+
)}
|
|
407
|
+
<div className="combobox-input-row">
|
|
408
|
+
<input
|
|
409
|
+
ref={metricsSearchRef}
|
|
410
|
+
type="text"
|
|
411
|
+
className="combobox-search"
|
|
412
|
+
placeholder="Search metrics..."
|
|
413
|
+
value={metricsSearch}
|
|
414
|
+
onChange={e => setMetricsSearch(e.target.value)}
|
|
415
|
+
onClick={e => e.stopPropagation()}
|
|
416
|
+
/>
|
|
364
417
|
{selectedMetrics.length > CHIPS_COLLAPSE_THRESHOLD && (
|
|
365
418
|
<button
|
|
366
|
-
className="
|
|
367
|
-
onClick={
|
|
419
|
+
className="combobox-action"
|
|
420
|
+
onClick={e => {
|
|
421
|
+
e.stopPropagation();
|
|
422
|
+
setMetricsChipsExpanded(!metricsChipsExpanded);
|
|
423
|
+
}}
|
|
368
424
|
>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
425
|
+
{metricsChipsExpanded ? 'Show less' : 'Show all'}
|
|
426
|
+
</button>
|
|
427
|
+
)}
|
|
428
|
+
{selectedMetrics.length > 0 && (
|
|
429
|
+
<button
|
|
430
|
+
className="combobox-action"
|
|
431
|
+
onClick={e => {
|
|
432
|
+
e.stopPropagation();
|
|
433
|
+
onMetricsChange([]);
|
|
434
|
+
}}
|
|
435
|
+
>
|
|
436
|
+
Clear
|
|
377
437
|
</button>
|
|
378
438
|
)}
|
|
379
439
|
</div>
|
|
380
|
-
)}
|
|
381
|
-
|
|
382
|
-
<div className="search-box">
|
|
383
|
-
<input
|
|
384
|
-
type="text"
|
|
385
|
-
placeholder="Search metrics..."
|
|
386
|
-
value={metricsSearch}
|
|
387
|
-
onChange={e => setMetricsSearch(e.target.value)}
|
|
388
|
-
/>
|
|
389
|
-
{metricsSearch && (
|
|
390
|
-
<button
|
|
391
|
-
className="clear-search"
|
|
392
|
-
onClick={() => setMetricsSearch('')}
|
|
393
|
-
>
|
|
394
|
-
×
|
|
395
|
-
</button>
|
|
396
|
-
)}
|
|
397
440
|
</div>
|
|
398
441
|
|
|
399
442
|
<div className="selection-list">
|
|
@@ -489,69 +532,74 @@ export function SelectionPanel({
|
|
|
489
532
|
<div className="empty-list">Loading dimensions...</div>
|
|
490
533
|
) : (
|
|
491
534
|
<>
|
|
492
|
-
{/*
|
|
493
|
-
|
|
494
|
-
|
|
535
|
+
{/* Combined Chips + Search Input */}
|
|
536
|
+
<div
|
|
537
|
+
className="combobox-input"
|
|
538
|
+
onClick={() => dimensionsSearchRef.current?.focus()}
|
|
539
|
+
>
|
|
540
|
+
{selectedDimensions.length > 0 && (
|
|
495
541
|
<div
|
|
496
|
-
className={`
|
|
497
|
-
|
|
542
|
+
className={`combobox-chips ${
|
|
543
|
+
selectedDimensions.length > CHIPS_COLLAPSE_THRESHOLD
|
|
544
|
+
? dimensionsChipsExpanded
|
|
545
|
+
? 'expanded'
|
|
546
|
+
: 'collapsed'
|
|
547
|
+
: ''
|
|
498
548
|
}`}
|
|
499
549
|
>
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
550
|
+
{selectedDimensions.map(dimName => (
|
|
551
|
+
<span
|
|
552
|
+
key={dimName}
|
|
553
|
+
className="selected-chip dimension-chip"
|
|
554
|
+
>
|
|
555
|
+
{getDimDisplayName(dimName)}
|
|
556
|
+
<button
|
|
557
|
+
className="chip-remove"
|
|
558
|
+
onClick={e => {
|
|
559
|
+
e.stopPropagation();
|
|
560
|
+
removeDimension(dimName);
|
|
561
|
+
}}
|
|
562
|
+
title={`Remove ${getDimDisplayName(dimName)}`}
|
|
505
563
|
>
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
className="chip-remove"
|
|
511
|
-
onClick={() => removeDimension(dimName)}
|
|
512
|
-
title={`Remove ${getDimDisplayName(dimName)}`}
|
|
513
|
-
>
|
|
514
|
-
×
|
|
515
|
-
</button>
|
|
516
|
-
</span>
|
|
517
|
-
))}
|
|
518
|
-
</div>
|
|
564
|
+
×
|
|
565
|
+
</button>
|
|
566
|
+
</span>
|
|
567
|
+
))}
|
|
519
568
|
</div>
|
|
569
|
+
)}
|
|
570
|
+
<div className="combobox-input-row">
|
|
571
|
+
<input
|
|
572
|
+
ref={dimensionsSearchRef}
|
|
573
|
+
type="text"
|
|
574
|
+
className="combobox-search"
|
|
575
|
+
placeholder="Search dimensions..."
|
|
576
|
+
value={dimensionsSearch}
|
|
577
|
+
onChange={e => setDimensionsSearch(e.target.value)}
|
|
578
|
+
onClick={e => e.stopPropagation()}
|
|
579
|
+
/>
|
|
520
580
|
{selectedDimensions.length > CHIPS_COLLAPSE_THRESHOLD && (
|
|
521
581
|
<button
|
|
522
|
-
className="
|
|
523
|
-
onClick={
|
|
524
|
-
|
|
525
|
-
|
|
582
|
+
className="combobox-action"
|
|
583
|
+
onClick={e => {
|
|
584
|
+
e.stopPropagation();
|
|
585
|
+
setDimensionsChipsExpanded(!dimensionsChipsExpanded);
|
|
586
|
+
}}
|
|
526
587
|
>
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
588
|
+
{dimensionsChipsExpanded ? 'Show less' : 'Show all'}
|
|
589
|
+
</button>
|
|
590
|
+
)}
|
|
591
|
+
{selectedDimensions.length > 0 && (
|
|
592
|
+
<button
|
|
593
|
+
className="combobox-action"
|
|
594
|
+
onClick={e => {
|
|
595
|
+
e.stopPropagation();
|
|
596
|
+
onDimensionsChange([]);
|
|
597
|
+
}}
|
|
598
|
+
>
|
|
599
|
+
Clear
|
|
535
600
|
</button>
|
|
536
601
|
)}
|
|
537
602
|
</div>
|
|
538
|
-
)}
|
|
539
|
-
|
|
540
|
-
<div className="search-box">
|
|
541
|
-
<input
|
|
542
|
-
type="text"
|
|
543
|
-
placeholder="Search dimensions..."
|
|
544
|
-
value={dimensionsSearch}
|
|
545
|
-
onChange={e => setDimensionsSearch(e.target.value)}
|
|
546
|
-
/>
|
|
547
|
-
{dimensionsSearch && (
|
|
548
|
-
<button
|
|
549
|
-
className="clear-search"
|
|
550
|
-
onClick={() => setDimensionsSearch('')}
|
|
551
|
-
>
|
|
552
|
-
×
|
|
553
|
-
</button>
|
|
554
|
-
)}
|
|
555
603
|
</div>
|
|
556
604
|
|
|
557
605
|
<div className="selection-list dimensions-list">
|
|
@@ -586,6 +634,83 @@ export function SelectionPanel({
|
|
|
586
634
|
</>
|
|
587
635
|
)}
|
|
588
636
|
</div>
|
|
637
|
+
|
|
638
|
+
{/* Divider */}
|
|
639
|
+
<div className="section-divider" />
|
|
640
|
+
|
|
641
|
+
{/* Filters Section */}
|
|
642
|
+
<div className="selection-section filters-section">
|
|
643
|
+
<div className="section-header">
|
|
644
|
+
<h3>Filters</h3>
|
|
645
|
+
<span className="selection-count">{filters.length} applied</span>
|
|
646
|
+
</div>
|
|
647
|
+
|
|
648
|
+
{/* Filter chips */}
|
|
649
|
+
{filters.length > 0 && (
|
|
650
|
+
<div className="filter-chips-container">
|
|
651
|
+
{filters.map((filter, idx) => (
|
|
652
|
+
<span key={idx} className="filter-chip">
|
|
653
|
+
<span className="filter-chip-text">{filter}</span>
|
|
654
|
+
<button
|
|
655
|
+
className="filter-chip-remove"
|
|
656
|
+
onClick={() => handleRemoveFilter(filter)}
|
|
657
|
+
title="Remove filter"
|
|
658
|
+
>
|
|
659
|
+
×
|
|
660
|
+
</button>
|
|
661
|
+
</span>
|
|
662
|
+
))}
|
|
663
|
+
</div>
|
|
664
|
+
)}
|
|
665
|
+
|
|
666
|
+
{/* Filter input */}
|
|
667
|
+
<div className="filter-input-container">
|
|
668
|
+
<input
|
|
669
|
+
type="text"
|
|
670
|
+
className="filter-input"
|
|
671
|
+
placeholder="e.g. v3.date.date_id >= '2024-01-01'"
|
|
672
|
+
value={filterInput}
|
|
673
|
+
onChange={e => setFilterInput(e.target.value)}
|
|
674
|
+
onKeyDown={handleFilterKeyDown}
|
|
675
|
+
/>
|
|
676
|
+
<button
|
|
677
|
+
className="filter-add-btn"
|
|
678
|
+
onClick={handleAddFilter}
|
|
679
|
+
disabled={!filterInput.trim()}
|
|
680
|
+
>
|
|
681
|
+
Add
|
|
682
|
+
</button>
|
|
683
|
+
</div>
|
|
684
|
+
</div>
|
|
685
|
+
|
|
686
|
+
{/* Run Query Section */}
|
|
687
|
+
<div className="run-query-section">
|
|
688
|
+
<button
|
|
689
|
+
className="run-query-btn"
|
|
690
|
+
onClick={onRunQuery}
|
|
691
|
+
disabled={!canRunQuery || queryLoading}
|
|
692
|
+
>
|
|
693
|
+
{queryLoading ? (
|
|
694
|
+
<>
|
|
695
|
+
<span className="spinner small" />
|
|
696
|
+
Running...
|
|
697
|
+
</>
|
|
698
|
+
) : (
|
|
699
|
+
<>
|
|
700
|
+
<span className="run-icon">▶</span>
|
|
701
|
+
Run Query
|
|
702
|
+
</>
|
|
703
|
+
)}
|
|
704
|
+
</button>
|
|
705
|
+
{!canRunQuery && selectedMetrics.length > 0 && (
|
|
706
|
+
<span className="run-hint">Select at least one dimension</span>
|
|
707
|
+
)}
|
|
708
|
+
{!canRunQuery && selectedMetrics.length === 0 && (
|
|
709
|
+
<span className="run-hint">
|
|
710
|
+
Select metrics and dimensions to run a query
|
|
711
|
+
</span>
|
|
712
|
+
)}
|
|
713
|
+
</div>
|
|
589
714
|
</div>
|
|
590
715
|
);
|
|
591
716
|
}
|
|
@@ -429,4 +429,197 @@ describe('MetricFlowGraph Node Display', () => {
|
|
|
429
429
|
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
|
|
430
430
|
expect(screen.getByText('avg_repair_price')).toBeInTheDocument();
|
|
431
431
|
});
|
|
432
|
+
|
|
433
|
+
describe('Branch Coverage - Edge Cases', () => {
|
|
434
|
+
it('handles grain group with empty grain array', () => {
|
|
435
|
+
const grainGroupsEmptyGrain = [
|
|
436
|
+
{
|
|
437
|
+
parent_name: 'default.orders',
|
|
438
|
+
grain: [], // Empty grain array
|
|
439
|
+
components: [{ name: 'count_orders', expression: 'COUNT(*)' }],
|
|
440
|
+
},
|
|
441
|
+
];
|
|
442
|
+
|
|
443
|
+
render(
|
|
444
|
+
<MetricFlowGraph
|
|
445
|
+
grainGroups={grainGroupsEmptyGrain}
|
|
446
|
+
metricFormulas={[
|
|
447
|
+
{
|
|
448
|
+
name: 'default.metric',
|
|
449
|
+
short_name: 'metric',
|
|
450
|
+
components: ['count_orders'],
|
|
451
|
+
is_derived: false,
|
|
452
|
+
},
|
|
453
|
+
]}
|
|
454
|
+
onNodeSelect={jest.fn()}
|
|
455
|
+
/>,
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
expect(screen.getByTestId('react-flow')).toBeInTheDocument();
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('handles grain group with no components', () => {
|
|
462
|
+
const grainGroupsNoComponents = [
|
|
463
|
+
{
|
|
464
|
+
parent_name: 'default.orders',
|
|
465
|
+
grain: ['date_id'],
|
|
466
|
+
components: [], // Empty components
|
|
467
|
+
},
|
|
468
|
+
];
|
|
469
|
+
|
|
470
|
+
render(
|
|
471
|
+
<MetricFlowGraph
|
|
472
|
+
grainGroups={grainGroupsNoComponents}
|
|
473
|
+
metricFormulas={[
|
|
474
|
+
{
|
|
475
|
+
name: 'default.metric',
|
|
476
|
+
short_name: 'metric',
|
|
477
|
+
components: [],
|
|
478
|
+
is_derived: false,
|
|
479
|
+
},
|
|
480
|
+
]}
|
|
481
|
+
onNodeSelect={jest.fn()}
|
|
482
|
+
/>,
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
expect(screen.getByTestId('react-flow')).toBeInTheDocument();
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('handles grain group with undefined components', () => {
|
|
489
|
+
const grainGroupsUndefinedComponents = [
|
|
490
|
+
{
|
|
491
|
+
parent_name: 'default.orders',
|
|
492
|
+
grain: ['date_id'],
|
|
493
|
+
// components is undefined
|
|
494
|
+
},
|
|
495
|
+
];
|
|
496
|
+
|
|
497
|
+
render(
|
|
498
|
+
<MetricFlowGraph
|
|
499
|
+
grainGroups={grainGroupsUndefinedComponents}
|
|
500
|
+
metricFormulas={[
|
|
501
|
+
{
|
|
502
|
+
name: 'default.metric',
|
|
503
|
+
short_name: 'metric',
|
|
504
|
+
components: [],
|
|
505
|
+
is_derived: false,
|
|
506
|
+
},
|
|
507
|
+
]}
|
|
508
|
+
onNodeSelect={jest.fn()}
|
|
509
|
+
/>,
|
|
510
|
+
);
|
|
511
|
+
|
|
512
|
+
expect(screen.getByTestId('react-flow')).toBeInTheDocument();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it('handles metric with is_derived false', () => {
|
|
516
|
+
render(
|
|
517
|
+
<MetricFlowGraph
|
|
518
|
+
grainGroups={mockGrainGroups}
|
|
519
|
+
metricFormulas={[
|
|
520
|
+
{
|
|
521
|
+
name: 'default.simple_metric',
|
|
522
|
+
short_name: 'simple_metric',
|
|
523
|
+
combiner: 'SUM(count)',
|
|
524
|
+
is_derived: false,
|
|
525
|
+
components: ['count_orders'],
|
|
526
|
+
},
|
|
527
|
+
]}
|
|
528
|
+
onNodeSelect={jest.fn()}
|
|
529
|
+
/>,
|
|
530
|
+
);
|
|
531
|
+
|
|
532
|
+
expect(screen.getByText('simple_metric')).toBeInTheDocument();
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('handles metric with is_derived true', () => {
|
|
536
|
+
render(
|
|
537
|
+
<MetricFlowGraph
|
|
538
|
+
grainGroups={mockGrainGroups}
|
|
539
|
+
metricFormulas={[
|
|
540
|
+
{
|
|
541
|
+
name: 'default.derived_metric',
|
|
542
|
+
short_name: 'derived_metric',
|
|
543
|
+
combiner: 'SUM(a) / SUM(b)',
|
|
544
|
+
is_derived: true,
|
|
545
|
+
components: ['sum_revenue', 'count_orders'],
|
|
546
|
+
},
|
|
547
|
+
]}
|
|
548
|
+
onNodeSelect={jest.fn()}
|
|
549
|
+
/>,
|
|
550
|
+
);
|
|
551
|
+
|
|
552
|
+
expect(screen.getByText('derived_metric')).toBeInTheDocument();
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
it('handles selectedNode prop for preagg', () => {
|
|
556
|
+
render(
|
|
557
|
+
<MetricFlowGraph
|
|
558
|
+
grainGroups={mockGrainGroups}
|
|
559
|
+
metricFormulas={mockMetricFormulas}
|
|
560
|
+
onNodeSelect={jest.fn()}
|
|
561
|
+
selectedNode={{ type: 'preagg', index: 0, data: mockGrainGroups[0] }}
|
|
562
|
+
/>,
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
expect(screen.getByTestId('react-flow')).toBeInTheDocument();
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('handles selectedNode prop for metric', () => {
|
|
569
|
+
render(
|
|
570
|
+
<MetricFlowGraph
|
|
571
|
+
grainGroups={mockGrainGroups}
|
|
572
|
+
metricFormulas={mockMetricFormulas}
|
|
573
|
+
onNodeSelect={jest.fn()}
|
|
574
|
+
selectedNode={{
|
|
575
|
+
type: 'metric',
|
|
576
|
+
index: 0,
|
|
577
|
+
data: mockMetricFormulas[0],
|
|
578
|
+
}}
|
|
579
|
+
/>,
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
expect(screen.getByTestId('react-flow')).toBeInTheDocument();
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
it('handles no selectedNode', () => {
|
|
586
|
+
render(
|
|
587
|
+
<MetricFlowGraph
|
|
588
|
+
grainGroups={mockGrainGroups}
|
|
589
|
+
metricFormulas={mockMetricFormulas}
|
|
590
|
+
onNodeSelect={jest.fn()}
|
|
591
|
+
selectedNode={null}
|
|
592
|
+
/>,
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
expect(screen.getByTestId('react-flow')).toBeInTheDocument();
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
it('handles grain group with undefined grain', () => {
|
|
599
|
+
const grainGroupsUndefinedGrain = [
|
|
600
|
+
{
|
|
601
|
+
parent_name: 'default.orders',
|
|
602
|
+
// grain is undefined
|
|
603
|
+
components: [{ name: 'count_orders', expression: 'COUNT(*)' }],
|
|
604
|
+
},
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
render(
|
|
608
|
+
<MetricFlowGraph
|
|
609
|
+
grainGroups={grainGroupsUndefinedGrain}
|
|
610
|
+
metricFormulas={[
|
|
611
|
+
{
|
|
612
|
+
name: 'default.metric',
|
|
613
|
+
short_name: 'metric',
|
|
614
|
+
components: ['count_orders'],
|
|
615
|
+
is_derived: false,
|
|
616
|
+
},
|
|
617
|
+
]}
|
|
618
|
+
onNodeSelect={jest.fn()}
|
|
619
|
+
/>,
|
|
620
|
+
);
|
|
621
|
+
|
|
622
|
+
expect(screen.getByTestId('react-flow')).toBeInTheDocument();
|
|
623
|
+
});
|
|
624
|
+
});
|
|
432
625
|
});
|