datajunction-ui 0.0.23 → 0.0.26
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 +11 -4
- package/src/app/index.tsx +6 -0
- package/src/app/pages/NamespacePage/CompactSelect.jsx +100 -0
- package/src/app/pages/NamespacePage/NodeModeSelect.jsx +8 -5
- package/src/app/pages/NamespacePage/__tests__/CompactSelect.test.jsx +190 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +297 -8
- package/src/app/pages/NamespacePage/index.jsx +489 -62
- package/src/app/pages/QueryPlannerPage/Loadable.jsx +6 -0
- package/src/app/pages/QueryPlannerPage/MetricFlowGraph.jsx +311 -0
- package/src/app/pages/QueryPlannerPage/PreAggDetailsPanel.jsx +470 -0
- package/src/app/pages/QueryPlannerPage/SelectionPanel.jsx +384 -0
- package/src/app/pages/QueryPlannerPage/__tests__/MetricFlowGraph.test.jsx +239 -0
- package/src/app/pages/QueryPlannerPage/__tests__/PreAggDetailsPanel.test.jsx +638 -0
- package/src/app/pages/QueryPlannerPage/__tests__/SelectionPanel.test.jsx +429 -0
- package/src/app/pages/QueryPlannerPage/__tests__/index.test.jsx +317 -0
- package/src/app/pages/QueryPlannerPage/index.jsx +209 -0
- package/src/app/pages/QueryPlannerPage/styles.css +1251 -0
- package/src/app/pages/Root/index.tsx +5 -0
- package/src/app/services/DJService.js +61 -2
- package/src/styles/index.css +2 -2
- package/src/app/icons/FilterIcon.jsx +0 -7
- package/src/app/pages/NamespacePage/FieldControl.jsx +0 -21
- package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +0 -30
- package/src/app/pages/NamespacePage/TagSelect.jsx +0 -44
- package/src/app/pages/NamespacePage/UserSelect.jsx +0 -47
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { MemoryRouter } from 'react-router-dom';
|
|
3
|
+
import {
|
|
4
|
+
QueryOverviewPanel,
|
|
5
|
+
PreAggDetailsPanel,
|
|
6
|
+
MetricDetailsPanel,
|
|
7
|
+
} from '../PreAggDetailsPanel';
|
|
8
|
+
import React from 'react';
|
|
9
|
+
|
|
10
|
+
// Mock the syntax highlighter to avoid issues with CSS imports
|
|
11
|
+
jest.mock('react-syntax-highlighter', () => ({
|
|
12
|
+
Light: ({ children }) => (
|
|
13
|
+
<pre data-testid="syntax-highlighter">{children}</pre>
|
|
14
|
+
),
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
jest.mock('react-syntax-highlighter/src/styles/hljs', () => ({
|
|
18
|
+
atomOneLight: {},
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
const mockMeasuresResult = {
|
|
22
|
+
grain_groups: [
|
|
23
|
+
{
|
|
24
|
+
parent_name: 'default.repair_orders',
|
|
25
|
+
aggregability: 'FULL',
|
|
26
|
+
grain: ['date_id', 'customer_id'],
|
|
27
|
+
components: [
|
|
28
|
+
{
|
|
29
|
+
name: 'sum_revenue',
|
|
30
|
+
expression: 'SUM(revenue)',
|
|
31
|
+
aggregation: 'SUM',
|
|
32
|
+
merge: 'SUM',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'count_orders',
|
|
36
|
+
expression: 'COUNT(*)',
|
|
37
|
+
aggregation: 'COUNT',
|
|
38
|
+
merge: 'SUM',
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
sql: 'SELECT date_id, customer_id, SUM(revenue) FROM orders GROUP BY 1, 2',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
parent_name: 'inventory.stock',
|
|
45
|
+
aggregability: 'LIMITED',
|
|
46
|
+
grain: ['warehouse_id'],
|
|
47
|
+
components: [
|
|
48
|
+
{
|
|
49
|
+
name: 'sum_quantity',
|
|
50
|
+
expression: 'SUM(quantity)',
|
|
51
|
+
aggregation: 'SUM',
|
|
52
|
+
merge: 'SUM',
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
metric_formulas: [
|
|
58
|
+
{
|
|
59
|
+
name: 'default.num_repair_orders',
|
|
60
|
+
short_name: 'num_repair_orders',
|
|
61
|
+
combiner: 'SUM(count_orders)',
|
|
62
|
+
is_derived: false,
|
|
63
|
+
components: ['count_orders'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'default.avg_repair_price',
|
|
67
|
+
short_name: 'avg_repair_price',
|
|
68
|
+
combiner: 'SUM(sum_revenue) / SUM(count_orders)',
|
|
69
|
+
is_derived: true,
|
|
70
|
+
components: ['sum_revenue', 'count_orders'],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const mockMetricsResult = {
|
|
76
|
+
sql: 'SELECT date_id, SUM(revenue) as total_revenue FROM orders GROUP BY 1',
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const renderWithRouter = component => {
|
|
80
|
+
return render(<MemoryRouter>{component}</MemoryRouter>);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
describe('QueryOverviewPanel', () => {
|
|
84
|
+
const defaultProps = {
|
|
85
|
+
measuresResult: mockMeasuresResult,
|
|
86
|
+
metricsResult: mockMetricsResult,
|
|
87
|
+
selectedMetrics: ['default.num_repair_orders', 'default.avg_repair_price'],
|
|
88
|
+
selectedDimensions: [
|
|
89
|
+
'default.date_dim.dateint',
|
|
90
|
+
'default.customer.country',
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
describe('Empty States', () => {
|
|
95
|
+
it('shows hint when no metrics selected', () => {
|
|
96
|
+
renderWithRouter(
|
|
97
|
+
<QueryOverviewPanel
|
|
98
|
+
{...defaultProps}
|
|
99
|
+
selectedMetrics={[]}
|
|
100
|
+
selectedDimensions={[]}
|
|
101
|
+
/>,
|
|
102
|
+
);
|
|
103
|
+
expect(screen.getByText('Query Planner')).toBeInTheDocument();
|
|
104
|
+
expect(
|
|
105
|
+
screen.getByText(/Select metrics and dimensions/),
|
|
106
|
+
).toBeInTheDocument();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('shows loading state when results are pending', () => {
|
|
110
|
+
renderWithRouter(
|
|
111
|
+
<QueryOverviewPanel
|
|
112
|
+
{...defaultProps}
|
|
113
|
+
measuresResult={null}
|
|
114
|
+
metricsResult={null}
|
|
115
|
+
/>,
|
|
116
|
+
);
|
|
117
|
+
expect(screen.getByText('Building query plan...')).toBeInTheDocument();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('Header', () => {
|
|
122
|
+
it('renders the overview header', () => {
|
|
123
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
124
|
+
expect(screen.getByText('Generated Query Overview')).toBeInTheDocument();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('shows metric and dimension counts', () => {
|
|
128
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
129
|
+
expect(screen.getByText('2 metrics × 2 dimensions')).toBeInTheDocument();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('Pre-Aggregations Summary', () => {
|
|
134
|
+
it('displays pre-aggregations section', () => {
|
|
135
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
136
|
+
expect(screen.getByText(/Pre-Aggregations/)).toBeInTheDocument();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('shows correct count of pre-aggregations', () => {
|
|
140
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
141
|
+
expect(screen.getByText('Pre-Aggregations (2)')).toBeInTheDocument();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('displays pre-agg source names', () => {
|
|
145
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
146
|
+
expect(screen.getByText('repair_orders')).toBeInTheDocument();
|
|
147
|
+
expect(screen.getByText('stock')).toBeInTheDocument();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('shows aggregability badge', () => {
|
|
151
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
152
|
+
expect(screen.getByText('FULL')).toBeInTheDocument();
|
|
153
|
+
expect(screen.getByText('LIMITED')).toBeInTheDocument();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('displays grain columns', () => {
|
|
157
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
158
|
+
expect(screen.getByText('date_id, customer_id')).toBeInTheDocument();
|
|
159
|
+
expect(screen.getByText('warehouse_id')).toBeInTheDocument();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('shows materialization status', () => {
|
|
163
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
164
|
+
expect(screen.getAllByText('Not materialized').length).toBe(2);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('Metrics Summary', () => {
|
|
169
|
+
it('displays metrics section', () => {
|
|
170
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
171
|
+
expect(screen.getByText(/Metrics \(2\)/)).toBeInTheDocument();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('shows metric short names', () => {
|
|
175
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
176
|
+
expect(screen.getByText('num_repair_orders')).toBeInTheDocument();
|
|
177
|
+
expect(screen.getByText('avg_repair_price')).toBeInTheDocument();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('shows derived badge for derived metrics', () => {
|
|
181
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
182
|
+
expect(screen.getByText('Derived')).toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('renders metric links', () => {
|
|
186
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
187
|
+
const links = screen.getAllByRole('link');
|
|
188
|
+
expect(
|
|
189
|
+
links.some(
|
|
190
|
+
link =>
|
|
191
|
+
link.getAttribute('href') === '/nodes/default.num_repair_orders',
|
|
192
|
+
),
|
|
193
|
+
).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe('Dimensions Summary', () => {
|
|
198
|
+
it('displays dimensions section', () => {
|
|
199
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
200
|
+
expect(screen.getByText(/Dimensions \(2\)/)).toBeInTheDocument();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('shows dimension short names', () => {
|
|
204
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
205
|
+
expect(screen.getByText('dateint')).toBeInTheDocument();
|
|
206
|
+
expect(screen.getByText('country')).toBeInTheDocument();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('renders dimension links', () => {
|
|
210
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
211
|
+
const links = screen.getAllByRole('link');
|
|
212
|
+
expect(
|
|
213
|
+
links.some(
|
|
214
|
+
link => link.getAttribute('href') === '/nodes/default.date_dim',
|
|
215
|
+
),
|
|
216
|
+
).toBe(true);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('SQL Section', () => {
|
|
221
|
+
it('displays generated SQL section', () => {
|
|
222
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
223
|
+
expect(screen.getByText('Generated SQL')).toBeInTheDocument();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('shows copy SQL button', () => {
|
|
227
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
228
|
+
expect(screen.getByText('Copy SQL')).toBeInTheDocument();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('renders SQL in syntax highlighter', () => {
|
|
232
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
233
|
+
expect(screen.getByTestId('syntax-highlighter')).toBeInTheDocument();
|
|
234
|
+
expect(screen.getByText(mockMetricsResult.sql)).toBeInTheDocument();
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('copies SQL to clipboard when copy button clicked', () => {
|
|
238
|
+
const mockClipboard = { writeText: jest.fn() };
|
|
239
|
+
Object.assign(navigator, { clipboard: mockClipboard });
|
|
240
|
+
|
|
241
|
+
renderWithRouter(<QueryOverviewPanel {...defaultProps} />);
|
|
242
|
+
|
|
243
|
+
fireEvent.click(screen.getByText('Copy SQL'));
|
|
244
|
+
expect(mockClipboard.writeText).toHaveBeenCalledWith(
|
|
245
|
+
mockMetricsResult.sql,
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe('PreAggDetailsPanel', () => {
|
|
252
|
+
const mockPreAgg = {
|
|
253
|
+
parent_name: 'default.repair_orders',
|
|
254
|
+
aggregability: 'FULL',
|
|
255
|
+
grain: ['date_id', 'customer_id'],
|
|
256
|
+
components: [
|
|
257
|
+
{
|
|
258
|
+
name: 'sum_revenue',
|
|
259
|
+
expression: 'SUM(revenue)',
|
|
260
|
+
aggregation: 'SUM',
|
|
261
|
+
merge: 'SUM',
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: 'count_orders',
|
|
265
|
+
expression: 'COUNT(*)',
|
|
266
|
+
aggregation: 'COUNT',
|
|
267
|
+
merge: 'SUM',
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
sql: 'SELECT date_id, customer_id, SUM(revenue) FROM orders GROUP BY 1, 2',
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const mockMetricFormulas = [
|
|
274
|
+
{
|
|
275
|
+
name: 'default.total_revenue',
|
|
276
|
+
short_name: 'total_revenue',
|
|
277
|
+
combiner: 'SUM(sum_revenue)',
|
|
278
|
+
is_derived: false,
|
|
279
|
+
components: ['sum_revenue'],
|
|
280
|
+
},
|
|
281
|
+
];
|
|
282
|
+
|
|
283
|
+
const onClose = jest.fn();
|
|
284
|
+
|
|
285
|
+
beforeEach(() => {
|
|
286
|
+
jest.clearAllMocks();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it('returns null when no preAgg provided', () => {
|
|
290
|
+
const { container } = render(
|
|
291
|
+
<PreAggDetailsPanel preAgg={null} onClose={onClose} />,
|
|
292
|
+
);
|
|
293
|
+
expect(container.firstChild).toBeNull();
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('renders pre-aggregation badge', () => {
|
|
297
|
+
render(
|
|
298
|
+
<PreAggDetailsPanel
|
|
299
|
+
preAgg={mockPreAgg}
|
|
300
|
+
metricFormulas={mockMetricFormulas}
|
|
301
|
+
onClose={onClose}
|
|
302
|
+
/>,
|
|
303
|
+
);
|
|
304
|
+
expect(screen.getByText('Pre-aggregation')).toBeInTheDocument();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it('displays source name', () => {
|
|
308
|
+
render(
|
|
309
|
+
<PreAggDetailsPanel
|
|
310
|
+
preAgg={mockPreAgg}
|
|
311
|
+
metricFormulas={mockMetricFormulas}
|
|
312
|
+
onClose={onClose}
|
|
313
|
+
/>,
|
|
314
|
+
);
|
|
315
|
+
expect(screen.getByText('repair_orders')).toBeInTheDocument();
|
|
316
|
+
expect(screen.getByText('default.repair_orders')).toBeInTheDocument();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('displays close button', () => {
|
|
320
|
+
render(
|
|
321
|
+
<PreAggDetailsPanel
|
|
322
|
+
preAgg={mockPreAgg}
|
|
323
|
+
metricFormulas={mockMetricFormulas}
|
|
324
|
+
onClose={onClose}
|
|
325
|
+
/>,
|
|
326
|
+
);
|
|
327
|
+
expect(screen.getByTitle('Close panel')).toBeInTheDocument();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('calls onClose when close button clicked', () => {
|
|
331
|
+
render(
|
|
332
|
+
<PreAggDetailsPanel
|
|
333
|
+
preAgg={mockPreAgg}
|
|
334
|
+
metricFormulas={mockMetricFormulas}
|
|
335
|
+
onClose={onClose}
|
|
336
|
+
/>,
|
|
337
|
+
);
|
|
338
|
+
fireEvent.click(screen.getByTitle('Close panel'));
|
|
339
|
+
expect(onClose).toHaveBeenCalled();
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
describe('Grain Section', () => {
|
|
343
|
+
it('displays grain section', () => {
|
|
344
|
+
render(
|
|
345
|
+
<PreAggDetailsPanel
|
|
346
|
+
preAgg={mockPreAgg}
|
|
347
|
+
metricFormulas={mockMetricFormulas}
|
|
348
|
+
onClose={onClose}
|
|
349
|
+
/>,
|
|
350
|
+
);
|
|
351
|
+
expect(screen.getByText('Grain (GROUP BY)')).toBeInTheDocument();
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('shows grain columns as pills', () => {
|
|
355
|
+
render(
|
|
356
|
+
<PreAggDetailsPanel
|
|
357
|
+
preAgg={mockPreAgg}
|
|
358
|
+
metricFormulas={mockMetricFormulas}
|
|
359
|
+
onClose={onClose}
|
|
360
|
+
/>,
|
|
361
|
+
);
|
|
362
|
+
expect(screen.getByText('date_id')).toBeInTheDocument();
|
|
363
|
+
expect(screen.getByText('customer_id')).toBeInTheDocument();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('shows empty message when no grain', () => {
|
|
367
|
+
const noGrainPreAgg = { ...mockPreAgg, grain: [] };
|
|
368
|
+
render(
|
|
369
|
+
<PreAggDetailsPanel
|
|
370
|
+
preAgg={noGrainPreAgg}
|
|
371
|
+
metricFormulas={mockMetricFormulas}
|
|
372
|
+
onClose={onClose}
|
|
373
|
+
/>,
|
|
374
|
+
);
|
|
375
|
+
expect(screen.getByText('No grain columns')).toBeInTheDocument();
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe('Related Metrics Section', () => {
|
|
380
|
+
it('displays metrics using this section', () => {
|
|
381
|
+
render(
|
|
382
|
+
<PreAggDetailsPanel
|
|
383
|
+
preAgg={mockPreAgg}
|
|
384
|
+
metricFormulas={mockMetricFormulas}
|
|
385
|
+
onClose={onClose}
|
|
386
|
+
/>,
|
|
387
|
+
);
|
|
388
|
+
expect(screen.getByText('Metrics Using This')).toBeInTheDocument();
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
it('shows related metrics', () => {
|
|
392
|
+
render(
|
|
393
|
+
<PreAggDetailsPanel
|
|
394
|
+
preAgg={mockPreAgg}
|
|
395
|
+
metricFormulas={mockMetricFormulas}
|
|
396
|
+
onClose={onClose}
|
|
397
|
+
/>,
|
|
398
|
+
);
|
|
399
|
+
expect(screen.getByText('total_revenue')).toBeInTheDocument();
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe('Components Table', () => {
|
|
404
|
+
it('displays components section', () => {
|
|
405
|
+
render(
|
|
406
|
+
<PreAggDetailsPanel
|
|
407
|
+
preAgg={mockPreAgg}
|
|
408
|
+
metricFormulas={mockMetricFormulas}
|
|
409
|
+
onClose={onClose}
|
|
410
|
+
/>,
|
|
411
|
+
);
|
|
412
|
+
expect(screen.getByText('Components (2)')).toBeInTheDocument();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('shows component names', () => {
|
|
416
|
+
render(
|
|
417
|
+
<PreAggDetailsPanel
|
|
418
|
+
preAgg={mockPreAgg}
|
|
419
|
+
metricFormulas={mockMetricFormulas}
|
|
420
|
+
onClose={onClose}
|
|
421
|
+
/>,
|
|
422
|
+
);
|
|
423
|
+
expect(screen.getByText('sum_revenue')).toBeInTheDocument();
|
|
424
|
+
expect(screen.getByText('count_orders')).toBeInTheDocument();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('shows component expressions', () => {
|
|
428
|
+
render(
|
|
429
|
+
<PreAggDetailsPanel
|
|
430
|
+
preAgg={mockPreAgg}
|
|
431
|
+
metricFormulas={mockMetricFormulas}
|
|
432
|
+
onClose={onClose}
|
|
433
|
+
/>,
|
|
434
|
+
);
|
|
435
|
+
expect(screen.getByText('SUM(revenue)')).toBeInTheDocument();
|
|
436
|
+
expect(screen.getByText('COUNT(*)')).toBeInTheDocument();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('shows aggregation functions', () => {
|
|
440
|
+
render(
|
|
441
|
+
<PreAggDetailsPanel
|
|
442
|
+
preAgg={mockPreAgg}
|
|
443
|
+
metricFormulas={mockMetricFormulas}
|
|
444
|
+
onClose={onClose}
|
|
445
|
+
/>,
|
|
446
|
+
);
|
|
447
|
+
expect(screen.getAllByText('SUM').length).toBeGreaterThan(0);
|
|
448
|
+
expect(screen.getByText('COUNT')).toBeInTheDocument();
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
describe('SQL Section', () => {
|
|
453
|
+
it('displays SQL section when sql is present', () => {
|
|
454
|
+
render(
|
|
455
|
+
<PreAggDetailsPanel
|
|
456
|
+
preAgg={mockPreAgg}
|
|
457
|
+
metricFormulas={mockMetricFormulas}
|
|
458
|
+
onClose={onClose}
|
|
459
|
+
/>,
|
|
460
|
+
);
|
|
461
|
+
expect(screen.getByText('Pre-Aggregation SQL')).toBeInTheDocument();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('shows copy button', () => {
|
|
465
|
+
render(
|
|
466
|
+
<PreAggDetailsPanel
|
|
467
|
+
preAgg={mockPreAgg}
|
|
468
|
+
metricFormulas={mockMetricFormulas}
|
|
469
|
+
onClose={onClose}
|
|
470
|
+
/>,
|
|
471
|
+
);
|
|
472
|
+
expect(screen.getByText('Copy SQL')).toBeInTheDocument();
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
describe('MetricDetailsPanel', () => {
|
|
478
|
+
const mockMetric = {
|
|
479
|
+
name: 'default.avg_repair_price',
|
|
480
|
+
short_name: 'avg_repair_price',
|
|
481
|
+
combiner: 'SUM(sum_revenue) / SUM(count_orders)',
|
|
482
|
+
is_derived: true,
|
|
483
|
+
components: ['sum_revenue', 'count_orders'],
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const mockGrainGroups = [
|
|
487
|
+
{
|
|
488
|
+
parent_name: 'default.repair_orders',
|
|
489
|
+
components: [{ name: 'sum_revenue' }, { name: 'count_orders' }],
|
|
490
|
+
},
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
const onClose = jest.fn();
|
|
494
|
+
|
|
495
|
+
beforeEach(() => {
|
|
496
|
+
jest.clearAllMocks();
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
it('returns null when no metric provided', () => {
|
|
500
|
+
const { container } = render(
|
|
501
|
+
<MetricDetailsPanel metric={null} onClose={onClose} />,
|
|
502
|
+
);
|
|
503
|
+
expect(container.firstChild).toBeNull();
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('renders metric badge', () => {
|
|
507
|
+
render(
|
|
508
|
+
<MetricDetailsPanel
|
|
509
|
+
metric={mockMetric}
|
|
510
|
+
grainGroups={mockGrainGroups}
|
|
511
|
+
onClose={onClose}
|
|
512
|
+
/>,
|
|
513
|
+
);
|
|
514
|
+
expect(screen.getByText('Derived Metric')).toBeInTheDocument();
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('renders regular metric badge for non-derived', () => {
|
|
518
|
+
const nonDerivedMetric = { ...mockMetric, is_derived: false };
|
|
519
|
+
render(
|
|
520
|
+
<MetricDetailsPanel
|
|
521
|
+
metric={nonDerivedMetric}
|
|
522
|
+
grainGroups={mockGrainGroups}
|
|
523
|
+
onClose={onClose}
|
|
524
|
+
/>,
|
|
525
|
+
);
|
|
526
|
+
expect(screen.getByText('Metric')).toBeInTheDocument();
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('displays metric name', () => {
|
|
530
|
+
render(
|
|
531
|
+
<MetricDetailsPanel
|
|
532
|
+
metric={mockMetric}
|
|
533
|
+
grainGroups={mockGrainGroups}
|
|
534
|
+
onClose={onClose}
|
|
535
|
+
/>,
|
|
536
|
+
);
|
|
537
|
+
expect(screen.getByText('avg_repair_price')).toBeInTheDocument();
|
|
538
|
+
expect(screen.getByText('default.avg_repair_price')).toBeInTheDocument();
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('calls onClose when close button clicked', () => {
|
|
542
|
+
render(
|
|
543
|
+
<MetricDetailsPanel
|
|
544
|
+
metric={mockMetric}
|
|
545
|
+
grainGroups={mockGrainGroups}
|
|
546
|
+
onClose={onClose}
|
|
547
|
+
/>,
|
|
548
|
+
);
|
|
549
|
+
fireEvent.click(screen.getByTitle('Close panel'));
|
|
550
|
+
expect(onClose).toHaveBeenCalled();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe('Formula Section', () => {
|
|
554
|
+
it('displays combiner formula section', () => {
|
|
555
|
+
render(
|
|
556
|
+
<MetricDetailsPanel
|
|
557
|
+
metric={mockMetric}
|
|
558
|
+
grainGroups={mockGrainGroups}
|
|
559
|
+
onClose={onClose}
|
|
560
|
+
/>,
|
|
561
|
+
);
|
|
562
|
+
expect(screen.getByText('Combiner Formula')).toBeInTheDocument();
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
it('shows the formula', () => {
|
|
566
|
+
render(
|
|
567
|
+
<MetricDetailsPanel
|
|
568
|
+
metric={mockMetric}
|
|
569
|
+
grainGroups={mockGrainGroups}
|
|
570
|
+
onClose={onClose}
|
|
571
|
+
/>,
|
|
572
|
+
);
|
|
573
|
+
expect(
|
|
574
|
+
screen.getByText('SUM(sum_revenue) / SUM(count_orders)'),
|
|
575
|
+
).toBeInTheDocument();
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
describe('Components Section', () => {
|
|
580
|
+
it('displays components used section', () => {
|
|
581
|
+
render(
|
|
582
|
+
<MetricDetailsPanel
|
|
583
|
+
metric={mockMetric}
|
|
584
|
+
grainGroups={mockGrainGroups}
|
|
585
|
+
onClose={onClose}
|
|
586
|
+
/>,
|
|
587
|
+
);
|
|
588
|
+
expect(screen.getByText('Components Used')).toBeInTheDocument();
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
it('shows component tags', () => {
|
|
592
|
+
render(
|
|
593
|
+
<MetricDetailsPanel
|
|
594
|
+
metric={mockMetric}
|
|
595
|
+
grainGroups={mockGrainGroups}
|
|
596
|
+
onClose={onClose}
|
|
597
|
+
/>,
|
|
598
|
+
);
|
|
599
|
+
expect(screen.getByText('sum_revenue')).toBeInTheDocument();
|
|
600
|
+
expect(screen.getByText('count_orders')).toBeInTheDocument();
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
describe('Source Pre-aggregations Section', () => {
|
|
605
|
+
it('displays source pre-aggregations section', () => {
|
|
606
|
+
render(
|
|
607
|
+
<MetricDetailsPanel
|
|
608
|
+
metric={mockMetric}
|
|
609
|
+
grainGroups={mockGrainGroups}
|
|
610
|
+
onClose={onClose}
|
|
611
|
+
/>,
|
|
612
|
+
);
|
|
613
|
+
expect(screen.getByText('Source Pre-aggregations')).toBeInTheDocument();
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it('shows related pre-agg sources', () => {
|
|
617
|
+
render(
|
|
618
|
+
<MetricDetailsPanel
|
|
619
|
+
metric={mockMetric}
|
|
620
|
+
grainGroups={mockGrainGroups}
|
|
621
|
+
onClose={onClose}
|
|
622
|
+
/>,
|
|
623
|
+
);
|
|
624
|
+
expect(screen.getByText('repair_orders')).toBeInTheDocument();
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
it('shows empty message when no sources found', () => {
|
|
628
|
+
render(
|
|
629
|
+
<MetricDetailsPanel
|
|
630
|
+
metric={mockMetric}
|
|
631
|
+
grainGroups={[]}
|
|
632
|
+
onClose={onClose}
|
|
633
|
+
/>,
|
|
634
|
+
);
|
|
635
|
+
expect(screen.getByText('No source found')).toBeInTheDocument();
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
});
|