datajunction-ui 0.0.26-alpha.0 → 0.0.27-alpha.0
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
|
@@ -7,10 +7,7 @@ import CollapsedIcon from '../../icons/CollapsedIcon';
|
|
|
7
7
|
const EVENT_TYPES = ['delete', 'update'];
|
|
8
8
|
|
|
9
9
|
export default function WatchButton({ node }) {
|
|
10
|
-
|
|
11
|
-
return null;
|
|
12
|
-
}
|
|
13
|
-
|
|
10
|
+
// All hooks must be called before any early returns
|
|
14
11
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
15
12
|
const [selectedEvents, setSelectedEvents] = useState([]);
|
|
16
13
|
const [loading, setLoading] = useState(false);
|
|
@@ -19,6 +16,10 @@ export default function WatchButton({ node }) {
|
|
|
19
16
|
|
|
20
17
|
// Load existing preferences
|
|
21
18
|
useEffect(() => {
|
|
19
|
+
if (!node || !node.name || !node.type) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
22
23
|
const loadPreferences = async () => {
|
|
23
24
|
try {
|
|
24
25
|
const preferences = await djClient.getNotificationPreferences({
|
|
@@ -38,7 +39,7 @@ export default function WatchButton({ node }) {
|
|
|
38
39
|
};
|
|
39
40
|
|
|
40
41
|
loadPreferences();
|
|
41
|
-
}, [djClient, node
|
|
42
|
+
}, [djClient, node?.name, node?.type]);
|
|
42
43
|
|
|
43
44
|
// Close dropdown on outside click
|
|
44
45
|
useEffect(() => {
|
|
@@ -51,6 +52,12 @@ export default function WatchButton({ node }) {
|
|
|
51
52
|
document.addEventListener('mousedown', handleClickOutside);
|
|
52
53
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
53
54
|
}, []);
|
|
55
|
+
|
|
56
|
+
// Early return after all hooks are called
|
|
57
|
+
if (!node || !node.name || !node.type) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
const toggleEvent = async event => {
|
|
55
62
|
const isSelected = selectedEvents.includes(event);
|
|
56
63
|
|
|
@@ -60,8 +60,32 @@ function MetricNode({ data, selected }) {
|
|
|
60
60
|
);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Component node - shows metric building blocks (e.g., SUM, COUNT)
|
|
65
|
+
*/
|
|
66
|
+
function ComponentNode({ data, selected }) {
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className={`compact-node compact-node-component ${
|
|
70
|
+
selected ? 'selected' : ''
|
|
71
|
+
}`}
|
|
72
|
+
>
|
|
73
|
+
<Handle type="target" position={Position.Left} />
|
|
74
|
+
<div className="compact-node-icon">●</div>
|
|
75
|
+
<div className="compact-node-content">
|
|
76
|
+
<div className="compact-node-name">{data.shortName}</div>
|
|
77
|
+
<div className="compact-node-meta">
|
|
78
|
+
<span className="meta-item">{data.aggregation || 'RAW'}</span>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<Handle type="source" position={Position.Right} />
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
63
86
|
const nodeTypes = {
|
|
64
87
|
preagg: PreAggNode,
|
|
88
|
+
component: ComponentNode,
|
|
65
89
|
metric: MetricNode,
|
|
66
90
|
};
|
|
67
91
|
|
|
@@ -132,6 +156,7 @@ export function MetricFlowGraph({
|
|
|
132
156
|
|
|
133
157
|
// Track mappings
|
|
134
158
|
const preAggNodesMap = new Map();
|
|
159
|
+
const componentNodeIds = new Map();
|
|
135
160
|
const componentToPreAgg = new Map();
|
|
136
161
|
|
|
137
162
|
let nodeId = 0;
|
|
@@ -167,6 +192,37 @@ export function MetricFlowGraph({
|
|
|
167
192
|
});
|
|
168
193
|
});
|
|
169
194
|
|
|
195
|
+
// Create component nodes (building blocks for metrics)
|
|
196
|
+
grainGroups.forEach((gg, ggIdx) => {
|
|
197
|
+
gg.components?.forEach(comp => {
|
|
198
|
+
if (!componentNodeIds.has(comp.name)) {
|
|
199
|
+
const id = getNextId();
|
|
200
|
+
componentNodeIds.set(comp.name, id);
|
|
201
|
+
// Shorten name for display (e.g., "unit_price_sum" -> "price_sum")
|
|
202
|
+
const shortName =
|
|
203
|
+
comp.name.length > 40
|
|
204
|
+
? '...' + comp.name.split('_').slice(-2).join('_')
|
|
205
|
+
: comp.name;
|
|
206
|
+
|
|
207
|
+
rawNodes.push({
|
|
208
|
+
id,
|
|
209
|
+
type: 'component',
|
|
210
|
+
position: { x: 0, y: 0 },
|
|
211
|
+
data: {
|
|
212
|
+
name: comp.name,
|
|
213
|
+
shortName,
|
|
214
|
+
aggregation: comp.aggregation,
|
|
215
|
+
merge: comp.merge,
|
|
216
|
+
grainGroupIndex: ggIdx,
|
|
217
|
+
},
|
|
218
|
+
selected:
|
|
219
|
+
selectedNode?.type === 'component' &&
|
|
220
|
+
selectedNode?.name === comp.name,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
170
226
|
// Create metric nodes
|
|
171
227
|
const metricNodeIds = new Map();
|
|
172
228
|
|
|
@@ -191,26 +247,38 @@ export function MetricFlowGraph({
|
|
|
191
247
|
});
|
|
192
248
|
});
|
|
193
249
|
|
|
194
|
-
// Create edges
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
250
|
+
// Create edges: PreAgg -> Component
|
|
251
|
+
grainGroups.forEach((gg, ggIdx) => {
|
|
252
|
+
const preAggId = preAggNodesMap.get(ggIdx);
|
|
253
|
+
gg.components?.forEach(comp => {
|
|
254
|
+
const compId = componentNodeIds.get(comp.name);
|
|
255
|
+
if (preAggId && compId) {
|
|
256
|
+
rawEdges.push({
|
|
257
|
+
id: `edge-preagg-${preAggId}-${compId}`,
|
|
258
|
+
source: preAggId,
|
|
259
|
+
target: compId,
|
|
260
|
+
style: { stroke: '#64748b', strokeWidth: 2 },
|
|
261
|
+
markerEnd: {
|
|
262
|
+
type: MarkerType.ArrowClosed,
|
|
263
|
+
color: '#64748b',
|
|
264
|
+
width: 16,
|
|
265
|
+
height: 16,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
203
268
|
}
|
|
204
269
|
});
|
|
270
|
+
});
|
|
205
271
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
272
|
+
// Create edges: Component -> Metric
|
|
273
|
+
metricFormulas.forEach(metric => {
|
|
274
|
+
const metricId = metricNodeIds.get(metric.name);
|
|
275
|
+
metric.components?.forEach(compName => {
|
|
276
|
+
const compId = componentNodeIds.get(compName);
|
|
277
|
+
if (compId && metricId) {
|
|
209
278
|
rawEdges.push({
|
|
210
|
-
id: `edge-${
|
|
211
|
-
source:
|
|
279
|
+
id: `edge-comp-${compId}-${metricId}`,
|
|
280
|
+
source: compId,
|
|
212
281
|
target: metricId,
|
|
213
|
-
type: 'default', // Straight/bezier edges
|
|
214
282
|
style: { stroke: '#64748b', strokeWidth: 2 },
|
|
215
283
|
markerEnd: {
|
|
216
284
|
type: MarkerType.ArrowClosed,
|
|
@@ -244,6 +312,12 @@ export function MetricFlowGraph({
|
|
|
244
312
|
index: node.data.grainGroupIndex,
|
|
245
313
|
data: grainGroups[node.data.grainGroupIndex],
|
|
246
314
|
});
|
|
315
|
+
} else if (node.type === 'component') {
|
|
316
|
+
onNodeSelect?.({
|
|
317
|
+
type: 'component',
|
|
318
|
+
name: node.data.name,
|
|
319
|
+
data: node.data,
|
|
320
|
+
});
|
|
247
321
|
} else if (node.type === 'metric') {
|
|
248
322
|
onNodeSelect?.({
|
|
249
323
|
type: 'metric',
|
|
@@ -295,6 +369,10 @@ export function MetricFlowGraph({
|
|
|
295
369
|
<span className="legend-dot preagg"></span>
|
|
296
370
|
<span>Pre-agg</span>
|
|
297
371
|
</div>
|
|
372
|
+
<div className="legend-item">
|
|
373
|
+
<span className="legend-dot component"></span>
|
|
374
|
+
<span>Component</span>
|
|
375
|
+
</div>
|
|
298
376
|
<div className="legend-item">
|
|
299
377
|
<span className="legend-dot metric"></span>
|
|
300
378
|
<span>Metric</span>
|