datajunction-ui 0.0.98 → 0.0.100

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.
@@ -45,13 +45,14 @@
45
45
 
46
46
  /* Full page layout */
47
47
  .planner-page {
48
- height: 100vh;
48
+ height: calc(100vh - 90px);
49
49
  display: flex;
50
50
  flex-direction: column;
51
51
  background: var(--planner-bg);
52
52
  color: var(--planner-text);
53
53
  font-family: var(--font-body);
54
54
  overflow: hidden;
55
+ border-top: 1px solid #eee;
55
56
  }
56
57
 
57
58
  /* Header */
@@ -89,6 +90,14 @@
89
90
  font-size: 13px;
90
91
  }
91
92
 
93
+ .planner-error-banner {
94
+ flex-shrink: 0;
95
+ border-radius: 0;
96
+ border-left: none;
97
+ border-right: none;
98
+ border-top: none;
99
+ }
100
+
92
101
  /* Materialization Error Banner */
93
102
  .materialization-error {
94
103
  display: flex;
@@ -374,16 +383,17 @@
374
383
  .planner-layout {
375
384
  display: flex;
376
385
  flex: 1;
386
+ min-height: 0;
377
387
  overflow: hidden;
378
388
  }
379
389
 
380
390
  /* Left: Selection Panel */
381
391
  .planner-selection {
382
392
  width: 20%;
383
- /* min-width: 280px; */
393
+ min-width: 200px;
394
+ min-height: 0;
384
395
  background: var(--planner-surface);
385
- border-right: 1px solid var(--planner-border);
386
- overflow: visible; /* Allow cube dropdown to overflow */
396
+ overflow: hidden;
387
397
  display: flex;
388
398
  flex-direction: column;
389
399
  }
@@ -464,12 +474,37 @@
464
474
  max-width: 300px;
465
475
  }
466
476
 
477
+ /* Vertical resizer between graph and details panels */
478
+ .vertical-resizer {
479
+ width: 1px;
480
+ background: var(--planner-border);
481
+ cursor: col-resize;
482
+ flex-shrink: 0;
483
+ position: relative;
484
+ z-index: 1;
485
+ transition: background 0.15s;
486
+ }
487
+
488
+ /* Wider invisible hit area */
489
+ .vertical-resizer::after {
490
+ content: '';
491
+ position: absolute;
492
+ top: 0;
493
+ bottom: 0;
494
+ left: -4px;
495
+ right: -4px;
496
+ }
497
+
498
+ .vertical-resizer:hover,
499
+ .vertical-resizer:active {
500
+ background: var(--accent-primary);
501
+ }
502
+
467
503
  /* Right: Details Panel */
468
504
  .planner-details {
469
505
  width: 40%;
470
- min-width: 380px;
506
+ min-width: 280px;
471
507
  background: var(--planner-surface);
472
- border-left: 1px solid var(--planner-border);
473
508
  overflow-y: auto;
474
509
  }
475
510
 
@@ -480,8 +515,17 @@
480
515
  .selection-panel {
481
516
  display: flex;
482
517
  flex-direction: column;
483
- height: 100%;
484
- overflow: visible; /* Allow cube dropdown to overflow */
518
+ flex: 1;
519
+ min-height: 0;
520
+ overflow: hidden;
521
+ }
522
+
523
+ .resizable-sections {
524
+ display: flex;
525
+ flex-direction: column;
526
+ flex: 1;
527
+ min-height: 0;
528
+ overflow: hidden;
485
529
  }
486
530
 
487
531
  /* Cube Preset Section */
@@ -850,6 +894,20 @@
850
894
  accent-color: var(--accent-primary);
851
895
  }
852
896
 
897
+ /* Incompatible metric (dims selected, this metric doesn't support them) */
898
+ .selection-item.metric-incompatible {
899
+ opacity: 0.35;
900
+ }
901
+
902
+ /* Compatible badge shown next to metrics that match selected dims */
903
+ .metric-compatible-badge {
904
+ margin-left: auto;
905
+ font-size: 11px;
906
+ color: var(--accent-success);
907
+ font-weight: 600;
908
+ flex-shrink: 0;
909
+ }
910
+
853
911
  .item-name {
854
912
  font-size: 13px;
855
913
  color: var(--planner-text);
@@ -879,6 +937,115 @@
879
937
  font-family: var(--font-display);
880
938
  }
881
939
 
940
+ /* Dimension group (node-level header + items) */
941
+ .dim-group {
942
+ margin-bottom: 4px;
943
+ }
944
+
945
+ .dim-group-header {
946
+ display: flex;
947
+ align-items: center;
948
+ gap: 8px;
949
+ padding: 8px 12px;
950
+ cursor: pointer;
951
+ user-select: none;
952
+ transition: background 0.15s;
953
+ }
954
+
955
+ .dim-group-header:hover {
956
+ background: var(--planner-surface-hover);
957
+ }
958
+
959
+ .dim-group-name {
960
+ flex: 1;
961
+ font-size: 12px;
962
+ font-weight: 600;
963
+ color: var(--planner-text);
964
+ font-family: var(--font-display);
965
+ overflow: hidden;
966
+ text-overflow: ellipsis;
967
+ white-space: nowrap;
968
+ }
969
+
970
+ .dim-group-meta {
971
+ font-size: 10px;
972
+ font-weight: 400;
973
+ color: var(--planner-text-muted);
974
+ background: var(--planner-bg-subtle, rgba(0, 0, 0, 0.06));
975
+ padding: 1px 5px;
976
+ border-radius: 8px;
977
+ white-space: nowrap;
978
+ flex-shrink: 0;
979
+ }
980
+
981
+ .dim-group-count {
982
+ font-size: 11px;
983
+ color: var(--planner-text-dim);
984
+ flex-shrink: 0;
985
+ }
986
+
987
+ .dim-group-item {
988
+ padding-left: 28px;
989
+ }
990
+
991
+ /* Role path sub-group (second level) */
992
+ .dim-role-group {
993
+ padding-left: 20px;
994
+ }
995
+
996
+ .dim-role-header {
997
+ display: flex;
998
+ align-items: center;
999
+ gap: 8px;
1000
+ padding: 5px 12px;
1001
+ cursor: pointer;
1002
+ user-select: none;
1003
+ transition: background 0.15s;
1004
+ }
1005
+
1006
+ .dim-role-header:hover {
1007
+ background: var(--planner-surface-hover);
1008
+ }
1009
+
1010
+ .dim-role-label {
1011
+ flex: 1;
1012
+ font-size: 11px;
1013
+ color: var(--planner-text-muted);
1014
+ font-family: var(--font-display);
1015
+ white-space: nowrap;
1016
+ overflow: hidden;
1017
+ text-overflow: ellipsis;
1018
+ }
1019
+
1020
+ .dim-role-item {
1021
+ padding-left: 22px;
1022
+ }
1023
+
1024
+ .dim-filter-btn {
1025
+ opacity: 0;
1026
+ pointer-events: none;
1027
+ margin-left: auto;
1028
+ align-self: center;
1029
+ flex-shrink: 0;
1030
+ background: none;
1031
+ border: none;
1032
+ color: var(--planner-text-dim);
1033
+ font-size: 10px;
1034
+ font-family: var(--font-display);
1035
+ padding: 0 2px;
1036
+ cursor: pointer;
1037
+ transition: opacity 0.1s, color 0.1s;
1038
+ }
1039
+
1040
+ .dim-role-item:hover .dim-filter-btn {
1041
+ opacity: 1;
1042
+ pointer-events: auto;
1043
+ }
1044
+
1045
+ .dim-filter-btn:hover {
1046
+ color: var(--accent-dimension);
1047
+ }
1048
+
882
1049
  /* Search result items (flat list) */
883
1050
  .search-result-item {
884
1051
  padding: 8px 12px;
@@ -903,6 +1070,19 @@
903
1070
  flex-shrink: 0;
904
1071
  }
905
1072
 
1073
+ .section-divider.draggable-divider {
1074
+ height: 5px;
1075
+ cursor: row-resize;
1076
+ background: var(--planner-border);
1077
+ transition: background 0.15s;
1078
+ position: relative;
1079
+ }
1080
+
1081
+ .section-divider.draggable-divider:hover,
1082
+ .section-divider.draggable-divider:active {
1083
+ background: var(--accent-primary);
1084
+ }
1085
+
906
1086
  .empty-list {
907
1087
  padding: 24px 16px;
908
1088
  text-align: center;
@@ -3406,6 +3586,13 @@ a.action-btn {
3406
3586
  font-family: var(--font-display);
3407
3587
  transition: all 0.12s ease;
3408
3588
  white-space: nowrap;
3589
+ max-width: 260px;
3590
+ }
3591
+
3592
+ .selected-chip .chip-label {
3593
+ overflow: hidden;
3594
+ text-overflow: ellipsis;
3595
+ white-space: nowrap;
3409
3596
  }
3410
3597
 
3411
3598
  /* Metrics: Pink (matches node_type__metric background #fad7dd) */
@@ -3469,9 +3656,8 @@ a.action-btn {
3469
3656
  ================================= */
3470
3657
 
3471
3658
  .filters-section {
3472
- flex: 0 0 auto !important;
3473
- min-height: auto !important;
3474
- overflow: visible !important;
3659
+ min-height: 0;
3660
+ overflow-y: auto;
3475
3661
  }
3476
3662
 
3477
3663
  .filter-chips-container {
@@ -3577,6 +3763,14 @@ a.action-btn {
3577
3763
  Engine Selection
3578
3764
  ================================= */
3579
3765
 
3766
+ .engine-run-section {
3767
+ display: flex;
3768
+ flex-direction: column;
3769
+ overflow: hidden;
3770
+ min-height: 0;
3771
+ flex-shrink: 0;
3772
+ }
3773
+
3580
3774
  .engine-section {
3581
3775
  display: flex;
3582
3776
  align-items: center;
@@ -3628,7 +3822,6 @@ a.action-btn {
3628
3822
 
3629
3823
  .run-query-section {
3630
3824
  padding: 16px 12px;
3631
- border-top: 1px solid var(--planner-border);
3632
3825
  background: var(--planner-bg);
3633
3826
  flex-shrink: 0;
3634
3827
  }
@@ -3772,12 +3965,37 @@ a.action-btn {
3772
3965
  flex: 0 0 33.333%;
3773
3966
  display: flex;
3774
3967
  flex-direction: column;
3775
- border-bottom: 2px solid var(--planner-border);
3776
3968
  background: var(--planner-surface);
3777
- min-height: 150px;
3969
+ min-height: 80px;
3778
3970
  max-height: 33.333%;
3779
3971
  }
3780
3972
 
3973
+ /* Horizontal resizer between SQL pane and results pane */
3974
+ .horizontal-resizer {
3975
+ height: 1px;
3976
+ background: var(--planner-border);
3977
+ cursor: row-resize;
3978
+ flex-shrink: 0;
3979
+ position: relative;
3980
+ z-index: 1;
3981
+ transition: background 0.15s;
3982
+ }
3983
+
3984
+ /* Wider invisible hit area */
3985
+ .horizontal-resizer::after {
3986
+ content: '';
3987
+ position: absolute;
3988
+ left: 0;
3989
+ right: 0;
3990
+ top: -4px;
3991
+ bottom: -4px;
3992
+ }
3993
+
3994
+ .horizontal-resizer:hover,
3995
+ .horizontal-resizer:active {
3996
+ background: var(--accent-primary);
3997
+ }
3998
+
3781
3999
  .sql-pane-header {
3782
4000
  display: flex;
3783
4001
  align-items: center;
@@ -43,7 +43,7 @@ describe('<Root />', () => {
43
43
 
44
44
  // Check navigation links exist
45
45
  expect(screen.getAllByText('Catalog')).toHaveLength(1);
46
- expect(screen.getAllByText('Explore')).toHaveLength(1);
46
+ expect(screen.getAllByText('Explorer')).toHaveLength(1);
47
47
  });
48
48
 
49
49
  it('renders Docs dropdown', async () => {
@@ -61,7 +61,7 @@ export function Root() {
61
61
  </span>
62
62
  <span className="menu-link">
63
63
  <span className="menu-title">
64
- <a href="/planner">Explore</a>
64
+ <a href="/planner">Explorer</a>
65
65
  </span>
66
66
  </span>
67
67
  <span className="menu-link">
@@ -1053,6 +1053,33 @@ export const DataJunctionAPI = {
1053
1053
  ).json();
1054
1054
  },
1055
1055
 
1056
+ commonMetrics: async function (dimensions) {
1057
+ // Dimension names are "node.attribute[role]" — strip role path and attribute to get node name
1058
+ const stripped = [
1059
+ ...new Set(
1060
+ dimensions
1061
+ .map(d =>
1062
+ d
1063
+ .replace(/\[[^\]]*\]$/, '')
1064
+ .split('.')
1065
+ .slice(0, -1)
1066
+ .join('.'),
1067
+ )
1068
+ .filter(Boolean),
1069
+ ),
1070
+ ];
1071
+ if (stripped.length === 0) return [];
1072
+ const dimsQuery =
1073
+ '?' +
1074
+ stripped.map(d => `dimension=${encodeURIComponent(d)}`).join('&') +
1075
+ '&node_type=metric';
1076
+ return await (
1077
+ await fetch(`${DJ_URL}/dimensions/common/${dimsQuery}`, {
1078
+ credentials: 'include',
1079
+ })
1080
+ ).json();
1081
+ },
1082
+
1056
1083
  history: async function (type, name, offset, limit) {
1057
1084
  return await (
1058
1085
  await fetch(