datajunction-ui 0.0.143 → 0.0.145
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/pages/CubeBuilderPage/CubePreviewPanel.jsx +173 -0
- package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +268 -97
- package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +273 -60
- package/src/app/pages/CubeBuilderPage/__tests__/CubePreviewPanel.test.jsx +108 -0
- package/src/app/pages/CubeBuilderPage/__tests__/DimensionsSelect.test.jsx +229 -0
- package/src/app/pages/CubeBuilderPage/__tests__/MetricsSelect.test.jsx +137 -0
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +145 -37
- package/src/app/pages/CubeBuilderPage/index.jsx +367 -125
- package/src/app/pages/CubeBuilderPage/styles.css +489 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +4 -4
- package/src/app/pages/NamespacePage/index.jsx +12 -8
- package/src/app/pages/NodePage/NodeInfoTab.jsx +12 -1
- package/src/app/services/DJService.js +70 -0
- package/src/app/services/__tests__/DJService.test.jsx +100 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
/* CubeBuilderPage - Query Planner inspired styling */
|
|
2
|
+
|
|
3
|
+
/* ================================
|
|
4
|
+
Page Container - viewport-constrained
|
|
5
|
+
================================ */
|
|
6
|
+
.cube-builder {
|
|
7
|
+
padding: 16px 24px;
|
|
8
|
+
background: var(--planner-bg, #f8fafc);
|
|
9
|
+
height: calc(100vh - 150px);
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
font-family: var(
|
|
13
|
+
--font-body,
|
|
14
|
+
'Inter',
|
|
15
|
+
-apple-system,
|
|
16
|
+
BlinkMacSystemFont,
|
|
17
|
+
sans-serif
|
|
18
|
+
);
|
|
19
|
+
color: var(--planner-text, #1e293b);
|
|
20
|
+
font-size: 13px;
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Header */
|
|
25
|
+
.cube-builder-header {
|
|
26
|
+
margin-bottom: 16px;
|
|
27
|
+
flex-shrink: 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.cube-builder-header h2 {
|
|
31
|
+
margin: 0;
|
|
32
|
+
font-size: 18px;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* ================================
|
|
37
|
+
Two-Column Layout
|
|
38
|
+
================================ */
|
|
39
|
+
.cube-builder-layout {
|
|
40
|
+
display: flex;
|
|
41
|
+
gap: 16px;
|
|
42
|
+
flex: 1;
|
|
43
|
+
min-height: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* Left: Main form area - scrolls internally */
|
|
47
|
+
.cube-builder-main {
|
|
48
|
+
flex: 1;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
background: var(--planner-surface, #ffffff);
|
|
51
|
+
border: 1px solid var(--planner-border, #e2e8f0);
|
|
52
|
+
overflow-y: auto;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Right: Sidebar - fixed height matching main column */
|
|
56
|
+
.cube-builder-sidebar {
|
|
57
|
+
width: 480px;
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
min-height: 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.cube-builder-sidebar .cube-preview-panel {
|
|
65
|
+
flex: 1;
|
|
66
|
+
min-height: 0;
|
|
67
|
+
display: flex;
|
|
68
|
+
flex-direction: column;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.cube-builder-sidebar .cube-preview-panel .preview-sql-container {
|
|
72
|
+
flex: 1;
|
|
73
|
+
min-height: 100px;
|
|
74
|
+
overflow: auto;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* ================================
|
|
78
|
+
Section styling (like Query Planner panels)
|
|
79
|
+
================================ */
|
|
80
|
+
.cube-form-section {
|
|
81
|
+
border-bottom: 1px solid var(--planner-border, #e2e8f0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.cube-form-section:last-child {
|
|
85
|
+
border-bottom: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.cube-form-section-header {
|
|
89
|
+
display: flex;
|
|
90
|
+
justify-content: space-between;
|
|
91
|
+
align-items: center;
|
|
92
|
+
padding: 12px 16px;
|
|
93
|
+
background: var(--planner-surface, #ffffff);
|
|
94
|
+
border-bottom: 1px solid var(--planner-border, #e2e8f0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.cube-form-section-header h3 {
|
|
98
|
+
margin: 0;
|
|
99
|
+
font-size: 11px;
|
|
100
|
+
font-weight: 700;
|
|
101
|
+
text-transform: uppercase;
|
|
102
|
+
letter-spacing: 0.5px;
|
|
103
|
+
color: var(--planner-text, #1e293b);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.cube-form-section-body {
|
|
107
|
+
padding: 12px 16px;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ================================
|
|
111
|
+
Form Fields
|
|
112
|
+
================================ */
|
|
113
|
+
.cube-field {
|
|
114
|
+
margin-bottom: 16px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.cube-field:last-child {
|
|
118
|
+
margin-bottom: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* Two-column row */
|
|
122
|
+
.cube-field-row {
|
|
123
|
+
display: flex;
|
|
124
|
+
gap: 16px;
|
|
125
|
+
margin-bottom: 16px;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.cube-field-row:last-child {
|
|
129
|
+
margin-bottom: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.cube-field-row .cube-field {
|
|
133
|
+
margin-bottom: 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.cube-field-grow {
|
|
137
|
+
flex: 1;
|
|
138
|
+
min-width: 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.cube-field-small {
|
|
142
|
+
width: 140px;
|
|
143
|
+
flex-shrink: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.cube-field-half {
|
|
147
|
+
flex: 1;
|
|
148
|
+
min-width: 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.cube-field-label {
|
|
152
|
+
display: block;
|
|
153
|
+
font-size: 11px;
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
text-transform: uppercase;
|
|
156
|
+
letter-spacing: 0.3px;
|
|
157
|
+
color: var(--planner-text-muted, #64748b);
|
|
158
|
+
margin-bottom: 6px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.cube-field-input,
|
|
162
|
+
.cube-builder input[type='text'],
|
|
163
|
+
.cube-builder textarea,
|
|
164
|
+
.cube-builder select {
|
|
165
|
+
width: 100%;
|
|
166
|
+
height: 36px;
|
|
167
|
+
padding: 8px 12px;
|
|
168
|
+
font-size: 13px;
|
|
169
|
+
font-family: inherit;
|
|
170
|
+
color: var(--planner-text, #1e293b);
|
|
171
|
+
background: var(--planner-surface, #ffffff);
|
|
172
|
+
border: 1px solid var(--planner-border, #e2e8f0);
|
|
173
|
+
border-radius: var(--radius-sm, 4px);
|
|
174
|
+
box-sizing: border-box;
|
|
175
|
+
transition: border-color 0.15s ease;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.cube-builder input[type='text']:focus,
|
|
179
|
+
.cube-builder textarea:focus,
|
|
180
|
+
.cube-builder select:focus {
|
|
181
|
+
outline: none;
|
|
182
|
+
border-color: var(--accent-primary, #3b82f6);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.cube-builder textarea {
|
|
186
|
+
height: auto;
|
|
187
|
+
min-height: 36px;
|
|
188
|
+
max-height: 80px;
|
|
189
|
+
resize: vertical;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.cube-field-static {
|
|
193
|
+
height: 36px;
|
|
194
|
+
padding: 8px 12px;
|
|
195
|
+
font-size: 13px;
|
|
196
|
+
color: var(--planner-text, #1e293b);
|
|
197
|
+
background: var(--planner-surface-hover, #f8fafc);
|
|
198
|
+
border: 1px solid var(--planner-border, #e2e8f0);
|
|
199
|
+
border-radius: var(--radius-sm, 4px);
|
|
200
|
+
box-sizing: border-box;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* Override legacy form field styles */
|
|
204
|
+
.cube-builder .NodeCreationInput,
|
|
205
|
+
.cube-builder .CubeCreationInput,
|
|
206
|
+
.cube-builder .NodeNameInput,
|
|
207
|
+
.cube-builder .DisplayNameInput,
|
|
208
|
+
.cube-builder .DescriptionInput,
|
|
209
|
+
.cube-builder .NamespaceInput,
|
|
210
|
+
.cube-builder .FullNameInput,
|
|
211
|
+
.cube-builder .TagsInput,
|
|
212
|
+
.cube-builder .cube-field-half > div,
|
|
213
|
+
.cube-builder .cube-field-row .TagsInput,
|
|
214
|
+
.cube-builder .cube-field-row .NodeCreationInput {
|
|
215
|
+
display: block !important;
|
|
216
|
+
width: 100% !important;
|
|
217
|
+
margin: 0 !important;
|
|
218
|
+
padding: 0 !important;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.cube-builder .cube-form-section-body > .NodeCreationInput,
|
|
222
|
+
.cube-builder .cube-form-section-body > .NamespaceInput {
|
|
223
|
+
margin-bottom: 16px !important;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.cube-builder .NodeCreationInput label,
|
|
227
|
+
.cube-builder .CubeCreationInput label,
|
|
228
|
+
.cube-builder .NamespaceInput label,
|
|
229
|
+
.cube-builder .FullNameInput label,
|
|
230
|
+
.cube-builder .TagsInput label,
|
|
231
|
+
.cube-builder .cube-field-half label {
|
|
232
|
+
display: block !important;
|
|
233
|
+
font-size: 11px !important;
|
|
234
|
+
font-weight: 600 !important;
|
|
235
|
+
text-transform: uppercase !important;
|
|
236
|
+
letter-spacing: 0.3px !important;
|
|
237
|
+
color: var(--planner-text-muted, #64748b) !important;
|
|
238
|
+
margin-bottom: 6px !important;
|
|
239
|
+
padding: 0 !important;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* ================================
|
|
243
|
+
Settings Panel (in sidebar)
|
|
244
|
+
================================ */
|
|
245
|
+
.cube-settings {
|
|
246
|
+
margin-top: 12px;
|
|
247
|
+
flex-shrink: 0;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.save-cube-btn {
|
|
251
|
+
width: 100%;
|
|
252
|
+
padding: 10px 16px;
|
|
253
|
+
font-size: 13px;
|
|
254
|
+
font-weight: 600;
|
|
255
|
+
font-family: inherit;
|
|
256
|
+
background: var(--accent-primary, #3b82f6);
|
|
257
|
+
color: white;
|
|
258
|
+
border: none;
|
|
259
|
+
border-radius: var(--radius-sm, 4px);
|
|
260
|
+
cursor: pointer;
|
|
261
|
+
transition: background 0.15s ease;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.save-cube-btn:hover {
|
|
265
|
+
background: #2563eb;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.save-cube-btn:disabled {
|
|
269
|
+
opacity: 0.6;
|
|
270
|
+
cursor: not-allowed;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Success state - briefly shown after save */
|
|
274
|
+
.save-cube-btn.save-cube-btn--saved,
|
|
275
|
+
.save-cube-btn.save-cube-btn--saved:disabled {
|
|
276
|
+
background: var(--accent-success, #059669);
|
|
277
|
+
opacity: 1;
|
|
278
|
+
cursor: default;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* Loading state with spinner */
|
|
282
|
+
.save-cube-btn.save-cube-btn--loading,
|
|
283
|
+
.save-cube-btn.save-cube-btn--loading:disabled {
|
|
284
|
+
opacity: 1;
|
|
285
|
+
display: inline-flex;
|
|
286
|
+
align-items: center;
|
|
287
|
+
justify-content: center;
|
|
288
|
+
gap: 8px;
|
|
289
|
+
cursor: wait;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.save-spinner {
|
|
293
|
+
width: 14px;
|
|
294
|
+
height: 14px;
|
|
295
|
+
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
296
|
+
border-top-color: #ffffff;
|
|
297
|
+
border-radius: 50%;
|
|
298
|
+
animation: save-spinner-spin 0.6s linear infinite;
|
|
299
|
+
display: inline-block;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@keyframes save-spinner-spin {
|
|
303
|
+
to {
|
|
304
|
+
transform: rotate(360deg);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Error message shown above the save button */
|
|
309
|
+
.save-error-message {
|
|
310
|
+
padding: 8px 12px;
|
|
311
|
+
margin-bottom: 8px;
|
|
312
|
+
background: rgba(220, 38, 38, 0.08);
|
|
313
|
+
color: var(--accent-error, #dc2626);
|
|
314
|
+
border: 1px solid rgba(220, 38, 38, 0.2);
|
|
315
|
+
border-radius: var(--radius-sm, 4px);
|
|
316
|
+
font-size: 12px;
|
|
317
|
+
line-height: 1.4;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/* ================================
|
|
321
|
+
Preview Panel (SQL)
|
|
322
|
+
================================ */
|
|
323
|
+
.cube-preview-panel {
|
|
324
|
+
background: var(--planner-surface, #ffffff);
|
|
325
|
+
border: 1px solid var(--planner-border, #e2e8f0);
|
|
326
|
+
overflow: hidden;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Flatten the scan estimate banner so it doesn't look like a nested panel */
|
|
330
|
+
.cube-preview-panel .scan-estimate-banner {
|
|
331
|
+
margin: 0 !important;
|
|
332
|
+
border-radius: 0 !important;
|
|
333
|
+
border: none !important;
|
|
334
|
+
border-bottom: 1px solid var(--planner-border, #e2e8f0) !important;
|
|
335
|
+
padding: 10px 16px !important;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.preview-section-header {
|
|
339
|
+
display: flex;
|
|
340
|
+
align-items: center;
|
|
341
|
+
gap: 8px;
|
|
342
|
+
padding: 12px 16px;
|
|
343
|
+
border-bottom: 1px solid var(--planner-border, #e2e8f0);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.preview-section-icon {
|
|
347
|
+
font-size: 12px;
|
|
348
|
+
color: var(--planner-text-muted, #64748b);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.preview-section-title {
|
|
352
|
+
margin: 0;
|
|
353
|
+
font-size: 11px;
|
|
354
|
+
font-weight: 700;
|
|
355
|
+
text-transform: uppercase;
|
|
356
|
+
letter-spacing: 0.5px;
|
|
357
|
+
color: var(--planner-text, #1e293b);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.preview-sql-container {
|
|
361
|
+
max-height: 600px;
|
|
362
|
+
overflow: auto;
|
|
363
|
+
background: var(--planner-surface-hover, #f8fafc);
|
|
364
|
+
padding: 16px;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.preview-sql-container pre,
|
|
368
|
+
.preview-sql-container code {
|
|
369
|
+
border-radius: 0 !important;
|
|
370
|
+
background: transparent !important;
|
|
371
|
+
padding: 0 !important;
|
|
372
|
+
margin: 0 !important;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.preview-loading,
|
|
376
|
+
.preview-error,
|
|
377
|
+
.preview-empty {
|
|
378
|
+
font-size: 12px;
|
|
379
|
+
color: var(--planner-text-muted, #64748b);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.preview-error {
|
|
383
|
+
color: var(--accent-error, #dc2626);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* ================================
|
|
387
|
+
Dimensions (hop groups)
|
|
388
|
+
================================ */
|
|
389
|
+
.hop-section {
|
|
390
|
+
margin-bottom: 20px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.hop-section:last-child {
|
|
394
|
+
margin-bottom: 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.hop-header {
|
|
398
|
+
display: flex;
|
|
399
|
+
align-items: center;
|
|
400
|
+
padding: 8px 0;
|
|
401
|
+
border-bottom: 1px solid var(--planner-border, #e2e8f0);
|
|
402
|
+
margin-bottom: 12px;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.hop-title {
|
|
406
|
+
font-size: 11px;
|
|
407
|
+
font-weight: 700;
|
|
408
|
+
text-transform: uppercase;
|
|
409
|
+
letter-spacing: 0.3px;
|
|
410
|
+
color: var(--planner-text, #1e293b);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.hop-count {
|
|
414
|
+
margin-left: 8px;
|
|
415
|
+
font-size: 11px;
|
|
416
|
+
color: var(--planner-text-dim, #94a3b8);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.dimension-groups {
|
|
420
|
+
padding-left: 16px;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.dimension-group {
|
|
424
|
+
margin-bottom: 12px;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.dimension-group-header {
|
|
428
|
+
font-size: 13px;
|
|
429
|
+
font-weight: normal;
|
|
430
|
+
margin: 0 0 6px 0;
|
|
431
|
+
color: var(--planner-text, #1e293b);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.dimension-group-header a {
|
|
435
|
+
color: var(--accent-primary, #3b82f6);
|
|
436
|
+
text-decoration: none;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.dimension-group-header a:hover {
|
|
440
|
+
text-decoration: underline;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
.dimension-group-header .via-text {
|
|
444
|
+
color: var(--planner-text-muted, #64748b);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* ================================
|
|
448
|
+
React-Select styling (matching Query Planner chips exactly)
|
|
449
|
+
================================ */
|
|
450
|
+
.cube-builder .cube-field-half > div > div {
|
|
451
|
+
font-size: 13px;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* Multi-value chips - simple spacing */
|
|
455
|
+
.cube-builder div[class*='multiValue'] {
|
|
456
|
+
margin: 2px 4px 2px 0 !important;
|
|
457
|
+
border-radius: 3px !important;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
.cube-builder div[class*='multiValueLabel'] {
|
|
461
|
+
font-size: 10px !important;
|
|
462
|
+
font-weight: 500 !important;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
.cube-builder div[class*='multiValueRemove'] svg {
|
|
466
|
+
width: 12px !important;
|
|
467
|
+
height: 12px !important;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/* Reduce Input wrapper height to match chip height */
|
|
471
|
+
.cube-builder div[class*='-Input'],
|
|
472
|
+
.cube-builder div[class*='Input'] {
|
|
473
|
+
margin: 0 !important;
|
|
474
|
+
padding: 0 !important;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.cube-builder div[class*='Input'] > div,
|
|
478
|
+
.cube-builder div[class*='Input'] > input {
|
|
479
|
+
height: 20px !important;
|
|
480
|
+
line-height: 20px !important;
|
|
481
|
+
margin: 0 !important;
|
|
482
|
+
padding: 0 !important;
|
|
483
|
+
font-size: 13px !important;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/* Target the grid sizer inside Input that forces a minimum height */
|
|
487
|
+
.cube-builder div[class*='Input'] [data-value] {
|
|
488
|
+
height: 20px !important;
|
|
489
|
+
}
|
|
@@ -639,14 +639,14 @@ describe('NamespacePage', () => {
|
|
|
639
639
|
git_branch: 'main',
|
|
640
640
|
num_nodes: 10,
|
|
641
641
|
invalid_node_count: 1,
|
|
642
|
-
|
|
642
|
+
last_updated_at: '2024-10-18T12:00:00+00:00',
|
|
643
643
|
},
|
|
644
644
|
{
|
|
645
645
|
namespace: 'default.feature-xyz',
|
|
646
646
|
git_branch: 'feature-xyz',
|
|
647
647
|
num_nodes: 5,
|
|
648
648
|
invalid_node_count: 0,
|
|
649
|
-
|
|
649
|
+
last_updated_at: null,
|
|
650
650
|
},
|
|
651
651
|
];
|
|
652
652
|
|
|
@@ -797,7 +797,7 @@ describe('NamespacePage', () => {
|
|
|
797
797
|
});
|
|
798
798
|
|
|
799
799
|
describe('formatRelativeTime', () => {
|
|
800
|
-
it('shows
|
|
800
|
+
it('shows last_updated_at timestamp on branch cards', async () => {
|
|
801
801
|
mockDjClient.getNamespaceGitConfig.mockResolvedValue({
|
|
802
802
|
github_repo_path: 'org/repo',
|
|
803
803
|
git_branch: 'main',
|
|
@@ -811,7 +811,7 @@ describe('NamespacePage', () => {
|
|
|
811
811
|
git_branch: 'main',
|
|
812
812
|
num_nodes: 3,
|
|
813
813
|
invalid_node_count: 0,
|
|
814
|
-
|
|
814
|
+
last_updated_at: new Date(
|
|
815
815
|
Date.now() - 2 * 24 * 60 * 60 * 1000,
|
|
816
816
|
).toISOString(),
|
|
817
817
|
},
|
|
@@ -56,7 +56,7 @@ function DefaultBranchPreview({ groups, defaultBranchNs }) {
|
|
|
56
56
|
margin: '20px',
|
|
57
57
|
}}
|
|
58
58
|
>
|
|
59
|
-
{filtered.map(({ type, nodes: typeNodes, hasMore }, idx) => {
|
|
59
|
+
{filtered.map(({ type, nodes: typeNodes, hasMore, totalCount }, idx) => {
|
|
60
60
|
const shown = typeNodes;
|
|
61
61
|
const isLeftCol = idx % 2 === 0;
|
|
62
62
|
return (
|
|
@@ -100,7 +100,8 @@ function DefaultBranchPreview({ groups, defaultBranchNs }) {
|
|
|
100
100
|
borderRadius: '8px',
|
|
101
101
|
}}
|
|
102
102
|
>
|
|
103
|
-
{
|
|
103
|
+
{totalCount ??
|
|
104
|
+
(hasMore ? `${MAX_PER_TYPE}+` : typeNodes.length)}
|
|
104
105
|
</span>
|
|
105
106
|
</span>
|
|
106
107
|
{hasMore && (
|
|
@@ -440,6 +441,8 @@ export function NamespacePage() {
|
|
|
440
441
|
)
|
|
441
442
|
.then(result => {
|
|
442
443
|
const edges = result?.data?.findNodesPaginated?.edges ?? [];
|
|
444
|
+
const totalCount =
|
|
445
|
+
result?.data?.findNodesPaginated?.totalCount ?? null;
|
|
443
446
|
const nodes = edges.map(e => ({
|
|
444
447
|
...e.node,
|
|
445
448
|
status: e.node.current?.status,
|
|
@@ -449,9 +452,10 @@ export function NamespacePage() {
|
|
|
449
452
|
type,
|
|
450
453
|
nodes: nodes.slice(0, MAX_PER_TYPE),
|
|
451
454
|
hasMore: nodes.length > MAX_PER_TYPE,
|
|
455
|
+
totalCount,
|
|
452
456
|
};
|
|
453
457
|
})
|
|
454
|
-
.catch(() => ({ type, nodes: [], hasMore: false })),
|
|
458
|
+
.catch(() => ({ type, nodes: [], hasMore: false, totalCount: null })),
|
|
455
459
|
),
|
|
456
460
|
)
|
|
457
461
|
.then(groups => setDefaultBranchGroups(groups))
|
|
@@ -1430,7 +1434,7 @@ export function NamespacePage() {
|
|
|
1430
1434
|
{b.invalid_node_count} invalid
|
|
1431
1435
|
</span>
|
|
1432
1436
|
)}
|
|
1433
|
-
{b.
|
|
1437
|
+
{b.last_updated_at && (
|
|
1434
1438
|
<span
|
|
1435
1439
|
style={{
|
|
1436
1440
|
display: 'flex',
|
|
@@ -1438,9 +1442,9 @@ export function NamespacePage() {
|
|
|
1438
1442
|
gap: '3px',
|
|
1439
1443
|
color: '#94a3b8',
|
|
1440
1444
|
}}
|
|
1441
|
-
title={new Date(
|
|
1442
|
-
b.
|
|
1443
|
-
).toLocaleString()}
|
|
1445
|
+
title={`Last node update: ${new Date(
|
|
1446
|
+
b.last_updated_at,
|
|
1447
|
+
).toLocaleString()}`}
|
|
1444
1448
|
>
|
|
1445
1449
|
<svg
|
|
1446
1450
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -1456,7 +1460,7 @@ export function NamespacePage() {
|
|
|
1456
1460
|
<circle cx="12" cy="12" r="10" />
|
|
1457
1461
|
<polyline points="12 6 12 12 16 14" />
|
|
1458
1462
|
</svg>
|
|
1459
|
-
{formatRelativeTime(b.
|
|
1463
|
+
{formatRelativeTime(b.last_updated_at)}
|
|
1460
1464
|
</span>
|
|
1461
1465
|
)}
|
|
1462
1466
|
</div>
|
|
@@ -169,7 +169,7 @@ export default function NodeInfoTab({ node }) {
|
|
|
169
169
|
return (
|
|
170
170
|
<div
|
|
171
171
|
className="button-3 cube-element"
|
|
172
|
-
key={cubeElem.name}
|
|
172
|
+
key={cubeElem.name + (cubeElem.role || '')}
|
|
173
173
|
role="cell"
|
|
174
174
|
aria-label="CubeElement"
|
|
175
175
|
aria-hidden="false"
|
|
@@ -179,6 +179,17 @@ export default function NodeInfoTab({ node }) {
|
|
|
179
179
|
? labelize(cubeElem.node_name.split('.').slice(-1)[0]) + ' → '
|
|
180
180
|
: ''}
|
|
181
181
|
{cubeElem.display_name}
|
|
182
|
+
{cubeElem.role && (
|
|
183
|
+
<span
|
|
184
|
+
style={{
|
|
185
|
+
marginLeft: '4px',
|
|
186
|
+
fontSize: '85%',
|
|
187
|
+
color: '#6c757d',
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
[{cubeElem.role}]
|
|
191
|
+
</span>
|
|
192
|
+
)}
|
|
182
193
|
</a>
|
|
183
194
|
<span
|
|
184
195
|
className={`badge node_type__${
|
|
@@ -60,6 +60,7 @@ export const DataJunctionAPI = {
|
|
|
60
60
|
hasPrevPage
|
|
61
61
|
startCursor
|
|
62
62
|
}
|
|
63
|
+
totalCount
|
|
63
64
|
edges {
|
|
64
65
|
node {
|
|
65
66
|
name
|
|
@@ -572,10 +573,12 @@ export const DataJunctionAPI = {
|
|
|
572
573
|
mode
|
|
573
574
|
cubeMetrics {
|
|
574
575
|
name
|
|
576
|
+
displayName
|
|
575
577
|
}
|
|
576
578
|
cubeDimensions {
|
|
577
579
|
name
|
|
578
580
|
attribute
|
|
581
|
+
role
|
|
579
582
|
properties
|
|
580
583
|
}
|
|
581
584
|
}
|
|
@@ -1047,6 +1050,73 @@ export const DataJunctionAPI = {
|
|
|
1047
1050
|
).json();
|
|
1048
1051
|
},
|
|
1049
1052
|
|
|
1053
|
+
getMetricsInfo: async function (names) {
|
|
1054
|
+
if (!names || names.length === 0) return [];
|
|
1055
|
+
const gqlQuery = `
|
|
1056
|
+
query GetMetricsInfo($names: [String!]!) {
|
|
1057
|
+
findNodes(names: $names) {
|
|
1058
|
+
name
|
|
1059
|
+
current {
|
|
1060
|
+
displayName
|
|
1061
|
+
}
|
|
1062
|
+
gitInfo {
|
|
1063
|
+
branch
|
|
1064
|
+
isDefaultBranch
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
`;
|
|
1069
|
+
const response = await fetch(DJ_GQL, {
|
|
1070
|
+
method: 'POST',
|
|
1071
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1072
|
+
credentials: 'include',
|
|
1073
|
+
body: JSON.stringify({
|
|
1074
|
+
query: gqlQuery,
|
|
1075
|
+
variables: { names },
|
|
1076
|
+
}),
|
|
1077
|
+
});
|
|
1078
|
+
const result = await response.json();
|
|
1079
|
+
return (result?.data?.findNodes || []).map(node => ({
|
|
1080
|
+
value: node.name,
|
|
1081
|
+
label: node.current?.displayName || node.name,
|
|
1082
|
+
name: node.name,
|
|
1083
|
+
gitInfo: node.gitInfo,
|
|
1084
|
+
}));
|
|
1085
|
+
},
|
|
1086
|
+
|
|
1087
|
+
searchMetrics: async function (query, limit = 50) {
|
|
1088
|
+
const gqlQuery = `
|
|
1089
|
+
query SearchMetrics($q: String!, $limit: Int!) {
|
|
1090
|
+
findNodes(search: $q, nodeTypes: [METRIC], limit: $limit) {
|
|
1091
|
+
name
|
|
1092
|
+
current {
|
|
1093
|
+
displayName
|
|
1094
|
+
}
|
|
1095
|
+
gitInfo {
|
|
1096
|
+
branch
|
|
1097
|
+
isDefaultBranch
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
`;
|
|
1102
|
+
const response = await fetch(DJ_GQL, {
|
|
1103
|
+
method: 'POST',
|
|
1104
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1105
|
+
credentials: 'include',
|
|
1106
|
+
body: JSON.stringify({
|
|
1107
|
+
query: gqlQuery,
|
|
1108
|
+
variables: { q: query, limit },
|
|
1109
|
+
}),
|
|
1110
|
+
});
|
|
1111
|
+
const result = await response.json();
|
|
1112
|
+
return (result?.data?.findNodes || []).map(node => ({
|
|
1113
|
+
value: node.name,
|
|
1114
|
+
label: node.current?.displayName || node.name,
|
|
1115
|
+
name: node.name,
|
|
1116
|
+
gitInfo: node.gitInfo,
|
|
1117
|
+
}));
|
|
1118
|
+
},
|
|
1119
|
+
|
|
1050
1120
|
commonDimensions: async function (metrics) {
|
|
1051
1121
|
const metricsQuery = '?' + metrics.map(m => `metric=${m}`).join('&');
|
|
1052
1122
|
return await (
|