datajunction-ui 0.0.26 → 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 +3 -3
- 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 -6
- 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
|
@@ -1,65 +1,66 @@
|
|
|
1
|
-
import { render, screen } from '@testing-library/react';
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
2
|
import React from 'react';
|
|
3
3
|
|
|
4
|
-
// Mock
|
|
5
|
-
jest.mock('
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
>
|
|
32
|
-
{gg.parent_name?.split('.').pop()}
|
|
33
|
-
</div>
|
|
34
|
-
))}
|
|
35
|
-
{metricFormulas.map((m, i) => (
|
|
4
|
+
// Mock dagre module with inline class definition
|
|
5
|
+
jest.mock('dagre', () => {
|
|
6
|
+
// Define mock graph class inside the mock factory
|
|
7
|
+
function MockGraph() {
|
|
8
|
+
this.setDefaultEdgeLabel = jest.fn().mockReturnValue(this);
|
|
9
|
+
this.setGraph = jest.fn().mockReturnValue(this);
|
|
10
|
+
this.setNode = jest.fn().mockReturnValue(this);
|
|
11
|
+
this.setEdge = jest.fn().mockReturnValue(this);
|
|
12
|
+
this.node = jest.fn().mockReturnValue({ x: 100, y: 100 });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
graphlib: {
|
|
17
|
+
Graph: MockGraph,
|
|
18
|
+
},
|
|
19
|
+
layout: jest.fn(),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Mock ReactFlow and related components
|
|
24
|
+
jest.mock('reactflow', () => {
|
|
25
|
+
return {
|
|
26
|
+
__esModule: true,
|
|
27
|
+
default: ({ children, nodes, edges, onNodeClick, onPaneClick }) => (
|
|
28
|
+
<div data-testid="react-flow" onClick={onPaneClick}>
|
|
29
|
+
{nodes?.map(node => (
|
|
36
30
|
<div
|
|
37
|
-
key={
|
|
38
|
-
data-testid={`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
31
|
+
key={node.id}
|
|
32
|
+
data-testid={`node-${node.id}`}
|
|
33
|
+
className={`flow-node ${node.type}`}
|
|
34
|
+
onClick={e => {
|
|
35
|
+
e.stopPropagation();
|
|
36
|
+
onNodeClick?.(e, node);
|
|
37
|
+
}}
|
|
42
38
|
>
|
|
43
|
-
{
|
|
39
|
+
{/* For metrics, use shortName; for preaggs, use name */}
|
|
40
|
+
{node.data.shortName || node.data.name}
|
|
44
41
|
</div>
|
|
45
42
|
))}
|
|
46
|
-
|
|
47
|
-
<span>Pre-agg</span>
|
|
48
|
-
<span>Metric</span>
|
|
49
|
-
<span>Derived</span>
|
|
50
|
-
</div>
|
|
43
|
+
{children}
|
|
51
44
|
</div>
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
),
|
|
46
|
+
Background: () => <div data-testid="background" />,
|
|
47
|
+
Controls: () => <div data-testid="controls" />,
|
|
48
|
+
MarkerType: { ArrowClosed: 'arrowclosed' },
|
|
49
|
+
useNodesState: nodes => [nodes, jest.fn(), jest.fn()],
|
|
50
|
+
useEdgesState: edges => [edges, jest.fn(), jest.fn()],
|
|
51
|
+
Handle: ({ type, position }) => (
|
|
52
|
+
<div data-testid={`handle-${type}-${position}`} />
|
|
53
|
+
),
|
|
54
|
+
Position: { Left: 'left', Right: 'right' },
|
|
55
|
+
};
|
|
56
|
+
});
|
|
55
57
|
|
|
56
|
-
// Import after
|
|
57
|
-
|
|
58
|
+
// Import the component after mocks are set up
|
|
59
|
+
import { MetricFlowGraph } from '../MetricFlowGraph';
|
|
58
60
|
|
|
59
61
|
const mockGrainGroups = [
|
|
60
62
|
{
|
|
61
63
|
parent_name: 'default.repair_orders',
|
|
62
|
-
aggregability: 'FULL',
|
|
63
64
|
grain: ['date_id', 'customer_id'],
|
|
64
65
|
components: [
|
|
65
66
|
{ name: 'sum_revenue', expression: 'SUM(revenue)' },
|
|
@@ -68,7 +69,6 @@ const mockGrainGroups = [
|
|
|
68
69
|
},
|
|
69
70
|
{
|
|
70
71
|
parent_name: 'inventory.stock',
|
|
71
|
-
aggregability: 'LIMITED',
|
|
72
72
|
grain: ['warehouse_id'],
|
|
73
73
|
components: [{ name: 'sum_quantity', expression: 'SUM(quantity)' }],
|
|
74
74
|
},
|
|
@@ -76,164 +76,357 @@ const mockGrainGroups = [
|
|
|
76
76
|
|
|
77
77
|
const mockMetricFormulas = [
|
|
78
78
|
{
|
|
79
|
-
name: 'default.
|
|
80
|
-
short_name: '
|
|
81
|
-
combiner: 'SUM(sum_revenue)',
|
|
82
|
-
is_derived: false,
|
|
83
|
-
components: ['sum_revenue'],
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'default.order_count',
|
|
87
|
-
short_name: 'order_count',
|
|
79
|
+
name: 'default.num_repair_orders',
|
|
80
|
+
short_name: 'num_repair_orders',
|
|
88
81
|
combiner: 'SUM(count_orders)',
|
|
89
82
|
is_derived: false,
|
|
90
83
|
components: ['count_orders'],
|
|
91
84
|
},
|
|
92
85
|
{
|
|
93
|
-
name: 'default.
|
|
94
|
-
short_name: '
|
|
86
|
+
name: 'default.avg_repair_price',
|
|
87
|
+
short_name: 'avg_repair_price',
|
|
95
88
|
combiner: 'SUM(sum_revenue) / SUM(count_orders)',
|
|
96
89
|
is_derived: true,
|
|
97
90
|
components: ['sum_revenue', 'count_orders'],
|
|
98
91
|
},
|
|
92
|
+
{
|
|
93
|
+
name: 'inventory.total_stock',
|
|
94
|
+
short_name: 'total_stock',
|
|
95
|
+
combiner: 'SUM(sum_quantity)',
|
|
96
|
+
is_derived: false,
|
|
97
|
+
components: ['sum_quantity'],
|
|
98
|
+
},
|
|
99
99
|
];
|
|
100
100
|
|
|
101
101
|
describe('MetricFlowGraph', () => {
|
|
102
|
-
const defaultProps = {
|
|
103
|
-
grainGroups: mockGrainGroups,
|
|
104
|
-
metricFormulas: mockMetricFormulas,
|
|
105
|
-
selectedNode: null,
|
|
106
|
-
onNodeSelect: jest.fn(),
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
beforeEach(() => {
|
|
110
|
-
jest.clearAllMocks();
|
|
111
|
-
});
|
|
112
|
-
|
|
113
102
|
describe('Empty State', () => {
|
|
114
|
-
it('shows empty state when no grain groups', () => {
|
|
115
|
-
render(
|
|
116
|
-
|
|
103
|
+
it('shows empty state when no grain groups provided', () => {
|
|
104
|
+
render(
|
|
105
|
+
<MetricFlowGraph
|
|
106
|
+
grainGroups={[]}
|
|
107
|
+
metricFormulas={mockMetricFormulas}
|
|
108
|
+
onNodeSelect={jest.fn()}
|
|
109
|
+
/>,
|
|
110
|
+
);
|
|
111
|
+
|
|
117
112
|
expect(
|
|
118
113
|
screen.getByText(
|
|
119
|
-
|
|
114
|
+
/Select metrics and dimensions above to visualize the data flow/i,
|
|
120
115
|
),
|
|
121
116
|
).toBeInTheDocument();
|
|
122
117
|
});
|
|
123
118
|
|
|
124
|
-
it('shows empty state when no metric formulas', () => {
|
|
125
|
-
render(
|
|
126
|
-
|
|
119
|
+
it('shows empty state when no metric formulas provided', () => {
|
|
120
|
+
render(
|
|
121
|
+
<MetricFlowGraph
|
|
122
|
+
grainGroups={mockGrainGroups}
|
|
123
|
+
metricFormulas={[]}
|
|
124
|
+
onNodeSelect={jest.fn()}
|
|
125
|
+
/>,
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
expect(
|
|
129
|
+
screen.getByText(
|
|
130
|
+
/Select metrics and dimensions above to visualize the data flow/i,
|
|
131
|
+
),
|
|
132
|
+
).toBeInTheDocument();
|
|
127
133
|
});
|
|
128
134
|
|
|
129
135
|
it('shows empty state when both are null', () => {
|
|
130
136
|
render(
|
|
131
137
|
<MetricFlowGraph
|
|
132
|
-
{...defaultProps}
|
|
133
138
|
grainGroups={null}
|
|
134
139
|
metricFormulas={null}
|
|
140
|
+
onNodeSelect={jest.fn()}
|
|
135
141
|
/>,
|
|
136
142
|
);
|
|
137
|
-
|
|
143
|
+
|
|
144
|
+
expect(
|
|
145
|
+
screen.getByText(
|
|
146
|
+
/Select metrics and dimensions above to visualize the data flow/i,
|
|
147
|
+
),
|
|
148
|
+
).toBeInTheDocument();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('shows empty icon in empty state', () => {
|
|
152
|
+
render(
|
|
153
|
+
<MetricFlowGraph
|
|
154
|
+
grainGroups={[]}
|
|
155
|
+
metricFormulas={[]}
|
|
156
|
+
onNodeSelect={jest.fn()}
|
|
157
|
+
/>,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
expect(screen.getByText('◎')).toBeInTheDocument();
|
|
138
161
|
});
|
|
139
162
|
});
|
|
140
163
|
|
|
141
164
|
describe('Graph Rendering', () => {
|
|
142
|
-
it('renders
|
|
143
|
-
render(
|
|
144
|
-
|
|
165
|
+
it('renders ReactFlow when data is provided', () => {
|
|
166
|
+
render(
|
|
167
|
+
<MetricFlowGraph
|
|
168
|
+
grainGroups={mockGrainGroups}
|
|
169
|
+
metricFormulas={mockMetricFormulas}
|
|
170
|
+
onNodeSelect={jest.fn()}
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
expect(screen.getByTestId('react-flow')).toBeInTheDocument();
|
|
145
175
|
});
|
|
146
176
|
|
|
147
|
-
it('renders
|
|
148
|
-
render(
|
|
149
|
-
|
|
150
|
-
|
|
177
|
+
it('renders background and controls', () => {
|
|
178
|
+
render(
|
|
179
|
+
<MetricFlowGraph
|
|
180
|
+
grainGroups={mockGrainGroups}
|
|
181
|
+
metricFormulas={mockMetricFormulas}
|
|
182
|
+
onNodeSelect={jest.fn()}
|
|
183
|
+
/>,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
expect(screen.getByTestId('background')).toBeInTheDocument();
|
|
187
|
+
expect(screen.getByTestId('controls')).toBeInTheDocument();
|
|
151
188
|
});
|
|
152
189
|
|
|
153
|
-
it('
|
|
154
|
-
render(
|
|
190
|
+
it('renders pre-aggregation nodes', () => {
|
|
191
|
+
render(
|
|
192
|
+
<MetricFlowGraph
|
|
193
|
+
grainGroups={mockGrainGroups}
|
|
194
|
+
metricFormulas={mockMetricFormulas}
|
|
195
|
+
onNodeSelect={jest.fn()}
|
|
196
|
+
/>,
|
|
197
|
+
);
|
|
198
|
+
|
|
155
199
|
expect(screen.getByText('repair_orders')).toBeInTheDocument();
|
|
156
200
|
expect(screen.getByText('stock')).toBeInTheDocument();
|
|
157
201
|
});
|
|
158
202
|
|
|
159
|
-
it('
|
|
160
|
-
render(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
203
|
+
it('renders metric nodes', () => {
|
|
204
|
+
render(
|
|
205
|
+
<MetricFlowGraph
|
|
206
|
+
grainGroups={mockGrainGroups}
|
|
207
|
+
metricFormulas={mockMetricFormulas}
|
|
208
|
+
onNodeSelect={jest.fn()}
|
|
209
|
+
/>,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
|
|
213
|
+
expect(screen.getByText('avg_repair_price')).toBeInTheDocument();
|
|
214
|
+
expect(screen.getByText('total_stock')).toBeInTheDocument();
|
|
164
215
|
});
|
|
165
216
|
});
|
|
166
217
|
|
|
167
218
|
describe('Node Selection', () => {
|
|
168
|
-
it('calls onNodeSelect when preagg node is clicked', () => {
|
|
219
|
+
it('calls onNodeSelect with preagg data when preagg node is clicked', () => {
|
|
169
220
|
const onNodeSelect = jest.fn();
|
|
170
|
-
render(
|
|
221
|
+
render(
|
|
222
|
+
<MetricFlowGraph
|
|
223
|
+
grainGroups={mockGrainGroups}
|
|
224
|
+
metricFormulas={mockMetricFormulas}
|
|
225
|
+
onNodeSelect={onNodeSelect}
|
|
226
|
+
/>,
|
|
227
|
+
);
|
|
171
228
|
|
|
172
|
-
const preaggNode = screen.
|
|
173
|
-
|
|
229
|
+
const preaggNode = screen.getByText('repair_orders');
|
|
230
|
+
fireEvent.click(preaggNode);
|
|
174
231
|
|
|
175
232
|
expect(onNodeSelect).toHaveBeenCalledWith(
|
|
176
233
|
expect.objectContaining({
|
|
177
234
|
type: 'preagg',
|
|
178
235
|
index: 0,
|
|
236
|
+
data: mockGrainGroups[0],
|
|
179
237
|
}),
|
|
180
238
|
);
|
|
181
239
|
});
|
|
182
240
|
|
|
183
|
-
it('calls onNodeSelect when metric node is clicked', () => {
|
|
241
|
+
it('calls onNodeSelect with metric data when metric node is clicked', () => {
|
|
184
242
|
const onNodeSelect = jest.fn();
|
|
185
|
-
render(
|
|
243
|
+
render(
|
|
244
|
+
<MetricFlowGraph
|
|
245
|
+
grainGroups={mockGrainGroups}
|
|
246
|
+
metricFormulas={mockMetricFormulas}
|
|
247
|
+
onNodeSelect={onNodeSelect}
|
|
248
|
+
/>,
|
|
249
|
+
);
|
|
186
250
|
|
|
187
|
-
const metricNode = screen.
|
|
188
|
-
|
|
251
|
+
const metricNode = screen.getByText('num_repair_orders');
|
|
252
|
+
fireEvent.click(metricNode);
|
|
189
253
|
|
|
190
254
|
expect(onNodeSelect).toHaveBeenCalledWith(
|
|
191
255
|
expect.objectContaining({
|
|
192
256
|
type: 'metric',
|
|
193
257
|
index: 0,
|
|
258
|
+
data: mockMetricFormulas[0],
|
|
194
259
|
}),
|
|
195
260
|
);
|
|
196
261
|
});
|
|
197
262
|
|
|
198
|
-
it('
|
|
263
|
+
it('calls onNodeSelect with null when pane is clicked', () => {
|
|
264
|
+
const onNodeSelect = jest.fn();
|
|
265
|
+
render(
|
|
266
|
+
<MetricFlowGraph
|
|
267
|
+
grainGroups={mockGrainGroups}
|
|
268
|
+
metricFormulas={mockMetricFormulas}
|
|
269
|
+
onNodeSelect={onNodeSelect}
|
|
270
|
+
/>,
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const flowPane = screen.getByTestId('react-flow');
|
|
274
|
+
fireEvent.click(flowPane);
|
|
275
|
+
|
|
276
|
+
expect(onNodeSelect).toHaveBeenCalledWith(null);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('Legend', () => {
|
|
281
|
+
it('renders legend with Pre-agg, Metric, and Derived labels', () => {
|
|
282
|
+
render(
|
|
283
|
+
<MetricFlowGraph
|
|
284
|
+
grainGroups={mockGrainGroups}
|
|
285
|
+
metricFormulas={mockMetricFormulas}
|
|
286
|
+
onNodeSelect={jest.fn()}
|
|
287
|
+
/>,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
expect(screen.getByText('Pre-agg')).toBeInTheDocument();
|
|
291
|
+
expect(screen.getByText('Metric')).toBeInTheDocument();
|
|
292
|
+
expect(screen.getByText('Derived')).toBeInTheDocument();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe('Node Types', () => {
|
|
297
|
+
it('creates preagg nodes for each grain group', () => {
|
|
298
|
+
render(
|
|
299
|
+
<MetricFlowGraph
|
|
300
|
+
grainGroups={mockGrainGroups}
|
|
301
|
+
metricFormulas={mockMetricFormulas}
|
|
302
|
+
onNodeSelect={jest.fn()}
|
|
303
|
+
/>,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// 2 grain groups = 2 preagg nodes
|
|
307
|
+
const preaggNodes = document.querySelectorAll('.flow-node.preagg');
|
|
308
|
+
expect(preaggNodes.length).toBe(2);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('creates metric nodes for each metric formula', () => {
|
|
312
|
+
render(
|
|
313
|
+
<MetricFlowGraph
|
|
314
|
+
grainGroups={mockGrainGroups}
|
|
315
|
+
metricFormulas={mockMetricFormulas}
|
|
316
|
+
onNodeSelect={jest.fn()}
|
|
317
|
+
/>,
|
|
318
|
+
);
|
|
319
|
+
|
|
320
|
+
// 3 metric formulas = 3 metric nodes
|
|
321
|
+
const metricNodes = document.querySelectorAll('.flow-node.metric');
|
|
322
|
+
expect(metricNodes.length).toBe(3);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('Node Selection by Index', () => {
|
|
327
|
+
it('selects correct preagg when second preagg is clicked', () => {
|
|
199
328
|
const onNodeSelect = jest.fn();
|
|
200
|
-
render(
|
|
329
|
+
render(
|
|
330
|
+
<MetricFlowGraph
|
|
331
|
+
grainGroups={mockGrainGroups}
|
|
332
|
+
metricFormulas={mockMetricFormulas}
|
|
333
|
+
onNodeSelect={onNodeSelect}
|
|
334
|
+
/>,
|
|
335
|
+
);
|
|
201
336
|
|
|
202
|
-
const
|
|
203
|
-
|
|
337
|
+
const stockNode = screen.getByText('stock');
|
|
338
|
+
fireEvent.click(stockNode);
|
|
204
339
|
|
|
205
340
|
expect(onNodeSelect).toHaveBeenCalledWith(
|
|
206
341
|
expect.objectContaining({
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
342
|
+
type: 'preagg',
|
|
343
|
+
index: 1,
|
|
344
|
+
data: mockGrainGroups[1],
|
|
210
345
|
}),
|
|
211
346
|
);
|
|
212
347
|
});
|
|
213
348
|
|
|
214
|
-
it('
|
|
349
|
+
it('selects correct metric when derived metric is clicked', () => {
|
|
215
350
|
const onNodeSelect = jest.fn();
|
|
216
|
-
render(
|
|
351
|
+
render(
|
|
352
|
+
<MetricFlowGraph
|
|
353
|
+
grainGroups={mockGrainGroups}
|
|
354
|
+
metricFormulas={mockMetricFormulas}
|
|
355
|
+
onNodeSelect={onNodeSelect}
|
|
356
|
+
/>,
|
|
357
|
+
);
|
|
217
358
|
|
|
218
|
-
const
|
|
219
|
-
|
|
359
|
+
const derivedMetric = screen.getByText('avg_repair_price');
|
|
360
|
+
fireEvent.click(derivedMetric);
|
|
220
361
|
|
|
221
362
|
expect(onNodeSelect).toHaveBeenCalledWith(
|
|
222
363
|
expect.objectContaining({
|
|
364
|
+
type: 'metric',
|
|
365
|
+
index: 1,
|
|
223
366
|
data: expect.objectContaining({
|
|
224
|
-
|
|
367
|
+
is_derived: true,
|
|
368
|
+
short_name: 'avg_repair_price',
|
|
225
369
|
}),
|
|
226
370
|
}),
|
|
227
371
|
);
|
|
228
372
|
});
|
|
229
373
|
});
|
|
230
374
|
|
|
231
|
-
describe('
|
|
232
|
-
it('
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
375
|
+
describe('Graph Layout', () => {
|
|
376
|
+
it('uses dagre for layout computation', () => {
|
|
377
|
+
const dagre = require('dagre');
|
|
378
|
+
|
|
379
|
+
render(
|
|
380
|
+
<MetricFlowGraph
|
|
381
|
+
grainGroups={mockGrainGroups}
|
|
382
|
+
metricFormulas={mockMetricFormulas}
|
|
383
|
+
onNodeSelect={jest.fn()}
|
|
384
|
+
/>,
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
// dagre.layout should have been called
|
|
388
|
+
expect(dagre.layout).toHaveBeenCalled();
|
|
237
389
|
});
|
|
238
390
|
});
|
|
239
391
|
});
|
|
392
|
+
|
|
393
|
+
describe('MetricFlowGraph Node Display', () => {
|
|
394
|
+
it('shows parent name without namespace prefix', () => {
|
|
395
|
+
render(
|
|
396
|
+
<MetricFlowGraph
|
|
397
|
+
grainGroups={[
|
|
398
|
+
{
|
|
399
|
+
parent_name: 'default.namespace.my_table',
|
|
400
|
+
grain: ['id'],
|
|
401
|
+
components: [{ name: 'comp1' }],
|
|
402
|
+
},
|
|
403
|
+
]}
|
|
404
|
+
metricFormulas={[
|
|
405
|
+
{
|
|
406
|
+
name: 'default.metric',
|
|
407
|
+
short_name: 'metric',
|
|
408
|
+
components: ['comp1'],
|
|
409
|
+
},
|
|
410
|
+
]}
|
|
411
|
+
onNodeSelect={jest.fn()}
|
|
412
|
+
/>,
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
// Should show just 'my_table', not the full path
|
|
416
|
+
expect(screen.getByText('my_table')).toBeInTheDocument();
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('shows short name for metric', () => {
|
|
420
|
+
render(
|
|
421
|
+
<MetricFlowGraph
|
|
422
|
+
grainGroups={mockGrainGroups}
|
|
423
|
+
metricFormulas={mockMetricFormulas}
|
|
424
|
+
onNodeSelect={jest.fn()}
|
|
425
|
+
/>,
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
// Should show short_name
|
|
429
|
+
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
|
|
430
|
+
expect(screen.getByText('avg_repair_price')).toBeInTheDocument();
|
|
431
|
+
});
|
|
432
|
+
});
|