datajunction-ui 0.0.94 → 0.0.96
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/components/Search.jsx +26 -25
- package/src/app/components/Tab.jsx +6 -1
- package/src/app/components/__tests__/Search.test.jsx +15 -23
- package/src/app/components/search.css +43 -5
- package/src/app/icons/ChartIcon.jsx +28 -0
- package/src/app/pages/NodePage/__tests__/NodeDimensionsTab.test.jsx +50 -0
- package/src/app/pages/NodePage/index.jsx +6 -2
- package/src/app/pages/QueryPlannerPage/ResultsView.jsx +566 -86
- package/src/app/pages/QueryPlannerPage/SelectionPanel.jsx +32 -1
- package/src/app/pages/QueryPlannerPage/__tests__/ResultsView.test.jsx +326 -0
- package/src/app/pages/QueryPlannerPage/__tests__/SelectionPanel.test.jsx +431 -2
- package/src/app/pages/QueryPlannerPage/index.jsx +31 -5
- package/src/app/pages/QueryPlannerPage/styles.css +220 -2
- package/src/app/pages/Root/__tests__/index.test.jsx +2 -2
- package/src/app/pages/Root/index.tsx +2 -2
- package/src/app/services/DJService.js +60 -17
- package/src/app/services/__tests__/DJService.test.jsx +13 -15
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
Materialization Planner - Three Column Layout
|
|
3
3
|
================================= */
|
|
4
4
|
|
|
5
|
+
/* Reset native browser input styling for all planner inputs */
|
|
6
|
+
.cube-search,
|
|
7
|
+
.combobox-search,
|
|
8
|
+
.filter-input {
|
|
9
|
+
-webkit-appearance: none;
|
|
10
|
+
appearance: none;
|
|
11
|
+
box-shadow: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
5
14
|
/* CSS Variables for theming */
|
|
6
15
|
:root {
|
|
7
16
|
--planner-bg: #f8fafc;
|
|
@@ -857,6 +866,13 @@
|
|
|
857
866
|
gap: 2px;
|
|
858
867
|
}
|
|
859
868
|
|
|
869
|
+
.dimension-full-name {
|
|
870
|
+
font-size: 10px;
|
|
871
|
+
color: var(--planner-text-muted);
|
|
872
|
+
font-family: var(--font-display);
|
|
873
|
+
word-break: break-all;
|
|
874
|
+
}
|
|
875
|
+
|
|
860
876
|
.dimension-path {
|
|
861
877
|
font-size: 10px;
|
|
862
878
|
color: var(--planner-text-dim);
|
|
@@ -3557,6 +3573,55 @@ a.action-btn {
|
|
|
3557
3573
|
cursor: not-allowed;
|
|
3558
3574
|
}
|
|
3559
3575
|
|
|
3576
|
+
/* =================================
|
|
3577
|
+
Engine Selection
|
|
3578
|
+
================================= */
|
|
3579
|
+
|
|
3580
|
+
.engine-section {
|
|
3581
|
+
display: flex;
|
|
3582
|
+
align-items: center;
|
|
3583
|
+
gap: 8px;
|
|
3584
|
+
padding: 10px 12px;
|
|
3585
|
+
border-top: 1px solid var(--planner-border);
|
|
3586
|
+
background: var(--planner-bg);
|
|
3587
|
+
flex-shrink: 0;
|
|
3588
|
+
}
|
|
3589
|
+
|
|
3590
|
+
.engine-label {
|
|
3591
|
+
font-size: 12px;
|
|
3592
|
+
font-weight: 500;
|
|
3593
|
+
color: var(--text-secondary);
|
|
3594
|
+
white-space: nowrap;
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
.engine-pills {
|
|
3598
|
+
display: flex;
|
|
3599
|
+
gap: 4px;
|
|
3600
|
+
}
|
|
3601
|
+
|
|
3602
|
+
.engine-pill {
|
|
3603
|
+
padding: 4px 10px;
|
|
3604
|
+
border: 1px solid var(--planner-border);
|
|
3605
|
+
border-radius: 12px;
|
|
3606
|
+
background: transparent;
|
|
3607
|
+
font-size: 12px;
|
|
3608
|
+
color: var(--text-secondary);
|
|
3609
|
+
cursor: pointer;
|
|
3610
|
+
transition: all 0.12s ease;
|
|
3611
|
+
}
|
|
3612
|
+
|
|
3613
|
+
.engine-pill:hover {
|
|
3614
|
+
border-color: #059669;
|
|
3615
|
+
color: #059669;
|
|
3616
|
+
}
|
|
3617
|
+
|
|
3618
|
+
.engine-pill.active {
|
|
3619
|
+
background: #059669;
|
|
3620
|
+
border-color: #059669;
|
|
3621
|
+
color: white;
|
|
3622
|
+
font-weight: 500;
|
|
3623
|
+
}
|
|
3624
|
+
|
|
3560
3625
|
/* =================================
|
|
3561
3626
|
Run Query Section
|
|
3562
3627
|
================================= */
|
|
@@ -3950,8 +4015,9 @@ a.action-btn {
|
|
|
3950
4015
|
.results-table th {
|
|
3951
4016
|
position: sticky;
|
|
3952
4017
|
top: 0;
|
|
4018
|
+
z-index: 1;
|
|
3953
4019
|
padding: 10px 12px;
|
|
3954
|
-
background:
|
|
4020
|
+
background: #f8fafc;
|
|
3955
4021
|
color: var(--planner-text);
|
|
3956
4022
|
font-weight: 600;
|
|
3957
4023
|
text-align: left;
|
|
@@ -3962,7 +4028,7 @@ a.action-btn {
|
|
|
3962
4028
|
}
|
|
3963
4029
|
|
|
3964
4030
|
.results-table th:hover {
|
|
3965
|
-
background:
|
|
4031
|
+
background: #f1f5f9;
|
|
3966
4032
|
}
|
|
3967
4033
|
|
|
3968
4034
|
.results-table th.sorted {
|
|
@@ -4051,3 +4117,155 @@ a.action-btn {
|
|
|
4051
4117
|
.filters-list .filter-chip {
|
|
4052
4118
|
padding: 4px 10px;
|
|
4053
4119
|
}
|
|
4120
|
+
|
|
4121
|
+
/* =================================
|
|
4122
|
+
Results View - Tab bar & Chart
|
|
4123
|
+
================================= */
|
|
4124
|
+
|
|
4125
|
+
.results-tabs-bar {
|
|
4126
|
+
display: flex;
|
|
4127
|
+
align-items: center;
|
|
4128
|
+
gap: 12px;
|
|
4129
|
+
padding: 8px 16px 0;
|
|
4130
|
+
border-bottom: 1px solid var(--planner-border);
|
|
4131
|
+
background: var(--planner-surface);
|
|
4132
|
+
flex-shrink: 0;
|
|
4133
|
+
}
|
|
4134
|
+
|
|
4135
|
+
.results-tabs {
|
|
4136
|
+
display: flex;
|
|
4137
|
+
gap: 2px;
|
|
4138
|
+
}
|
|
4139
|
+
|
|
4140
|
+
.results-tab {
|
|
4141
|
+
padding: 6px 14px;
|
|
4142
|
+
font-size: 12px;
|
|
4143
|
+
font-weight: 500;
|
|
4144
|
+
font-family: var(--font-body);
|
|
4145
|
+
background: none;
|
|
4146
|
+
border: none;
|
|
4147
|
+
border-bottom: 2px solid transparent;
|
|
4148
|
+
color: var(--planner-text-muted);
|
|
4149
|
+
cursor: pointer;
|
|
4150
|
+
transition: color 0.15s, border-color 0.15s;
|
|
4151
|
+
margin-bottom: -1px;
|
|
4152
|
+
}
|
|
4153
|
+
|
|
4154
|
+
.results-tab:hover:not(.disabled) {
|
|
4155
|
+
color: var(--planner-text);
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
.results-tab.active {
|
|
4159
|
+
color: var(--accent-primary);
|
|
4160
|
+
border-bottom-color: var(--accent-primary);
|
|
4161
|
+
}
|
|
4162
|
+
|
|
4163
|
+
.results-tab.disabled {
|
|
4164
|
+
opacity: 0.4;
|
|
4165
|
+
cursor: not-allowed;
|
|
4166
|
+
}
|
|
4167
|
+
|
|
4168
|
+
.results-tabs-meta {
|
|
4169
|
+
display: flex;
|
|
4170
|
+
align-items: center;
|
|
4171
|
+
gap: 8px;
|
|
4172
|
+
margin-left: auto;
|
|
4173
|
+
padding-bottom: 6px;
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4176
|
+
/* Chart wrapper - fills remaining height */
|
|
4177
|
+
.results-chart-wrapper {
|
|
4178
|
+
flex: 1;
|
|
4179
|
+
overflow: hidden;
|
|
4180
|
+
min-height: 0;
|
|
4181
|
+
padding: 16px 8px 8px;
|
|
4182
|
+
display: flex;
|
|
4183
|
+
align-items: stretch;
|
|
4184
|
+
}
|
|
4185
|
+
|
|
4186
|
+
/* KPI cards */
|
|
4187
|
+
.kpi-cards {
|
|
4188
|
+
display: flex;
|
|
4189
|
+
flex-wrap: wrap;
|
|
4190
|
+
gap: 16px;
|
|
4191
|
+
align-items: flex-start;
|
|
4192
|
+
padding: 8px;
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
.kpi-card {
|
|
4196
|
+
background: var(--planner-surface);
|
|
4197
|
+
border: 1px solid var(--planner-border);
|
|
4198
|
+
border-radius: var(--radius-lg);
|
|
4199
|
+
padding: 20px 28px;
|
|
4200
|
+
min-width: 160px;
|
|
4201
|
+
text-align: center;
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4204
|
+
.kpi-label {
|
|
4205
|
+
font-size: 11px;
|
|
4206
|
+
font-weight: 600;
|
|
4207
|
+
color: var(--planner-text-muted);
|
|
4208
|
+
text-transform: uppercase;
|
|
4209
|
+
letter-spacing: 0.04em;
|
|
4210
|
+
margin-bottom: 8px;
|
|
4211
|
+
}
|
|
4212
|
+
|
|
4213
|
+
.kpi-value {
|
|
4214
|
+
font-size: 28px;
|
|
4215
|
+
font-weight: 700;
|
|
4216
|
+
color: var(--planner-text);
|
|
4217
|
+
font-family: var(--font-display);
|
|
4218
|
+
line-height: 1.1;
|
|
4219
|
+
}
|
|
4220
|
+
|
|
4221
|
+
.kpi-type {
|
|
4222
|
+
font-size: 10px;
|
|
4223
|
+
color: var(--planner-text-dim);
|
|
4224
|
+
margin-top: 6px;
|
|
4225
|
+
}
|
|
4226
|
+
|
|
4227
|
+
.chart-no-data {
|
|
4228
|
+
display: flex;
|
|
4229
|
+
align-items: center;
|
|
4230
|
+
justify-content: center;
|
|
4231
|
+
width: 100%;
|
|
4232
|
+
color: var(--planner-text-muted);
|
|
4233
|
+
font-size: 13px;
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4236
|
+
/* =================================
|
|
4237
|
+
Small Multiples
|
|
4238
|
+
================================= */
|
|
4239
|
+
|
|
4240
|
+
.small-multiples {
|
|
4241
|
+
display: flex;
|
|
4242
|
+
flex-direction: column;
|
|
4243
|
+
gap: 0;
|
|
4244
|
+
width: 100%;
|
|
4245
|
+
height: 100%;
|
|
4246
|
+
overflow-y: auto;
|
|
4247
|
+
}
|
|
4248
|
+
|
|
4249
|
+
.small-multiple {
|
|
4250
|
+
flex-shrink: 0;
|
|
4251
|
+
height: 220px;
|
|
4252
|
+
padding: 0 0 8px 0;
|
|
4253
|
+
border-bottom: 1px solid var(--planner-border);
|
|
4254
|
+
}
|
|
4255
|
+
|
|
4256
|
+
.small-multiple:last-child {
|
|
4257
|
+
border-bottom: none;
|
|
4258
|
+
}
|
|
4259
|
+
|
|
4260
|
+
.small-multiple-label {
|
|
4261
|
+
font-size: 11px;
|
|
4262
|
+
font-weight: 600;
|
|
4263
|
+
color: var(--planner-text-muted);
|
|
4264
|
+
padding: 8px 0 0 8px;
|
|
4265
|
+
text-transform: uppercase;
|
|
4266
|
+
letter-spacing: 0.04em;
|
|
4267
|
+
}
|
|
4268
|
+
|
|
4269
|
+
.small-multiple-chart {
|
|
4270
|
+
height: 188px;
|
|
4271
|
+
}
|
|
@@ -42,8 +42,8 @@ describe('<Root />', () => {
|
|
|
42
42
|
});
|
|
43
43
|
|
|
44
44
|
// Check navigation links exist
|
|
45
|
-
expect(screen.
|
|
46
|
-
expect(screen.
|
|
45
|
+
expect(screen.getAllByText('Catalog')).toHaveLength(1);
|
|
46
|
+
expect(screen.getAllByText('Explore')).toHaveLength(1);
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it('renders Docs dropdown', async () => {
|
|
@@ -56,12 +56,12 @@ export function Root() {
|
|
|
56
56
|
<div className="menu-item here menu-here-bg menu-lg-down-accordion me-0 me-lg-2 fw-semibold">
|
|
57
57
|
<span className="menu-link">
|
|
58
58
|
<span className="menu-title">
|
|
59
|
-
<a href="/">
|
|
59
|
+
<a href="/">Catalog</a>
|
|
60
60
|
</span>
|
|
61
61
|
</span>
|
|
62
62
|
<span className="menu-link">
|
|
63
63
|
<span className="menu-title">
|
|
64
|
-
<a href="/planner">
|
|
64
|
+
<a href="/planner">Explore</a>
|
|
65
65
|
</span>
|
|
66
66
|
</span>
|
|
67
67
|
<span className="menu-link">
|
|
@@ -13,6 +13,9 @@ const DJ_GQL = process.env.REACT_APP_DJ_GQL
|
|
|
13
13
|
// Export the base URL for components that need direct access
|
|
14
14
|
export const getDJUrl = () => DJ_URL;
|
|
15
15
|
|
|
16
|
+
const QUERY_END_STATES = ['FINISHED', 'CANCELED', 'FAILED'];
|
|
17
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
18
|
+
|
|
16
19
|
export const DataJunctionAPI = {
|
|
17
20
|
listNodesForLanding: async function (
|
|
18
21
|
namespace,
|
|
@@ -1217,33 +1220,35 @@ export const DataJunctionAPI = {
|
|
|
1217
1220
|
metricsV3: async function (
|
|
1218
1221
|
metricSelection,
|
|
1219
1222
|
dimensionSelection,
|
|
1220
|
-
filters =
|
|
1223
|
+
filters = [],
|
|
1221
1224
|
useMaterialized = true,
|
|
1225
|
+
dialect = null,
|
|
1222
1226
|
) {
|
|
1223
1227
|
const params = new URLSearchParams();
|
|
1224
1228
|
metricSelection.forEach(metric => params.append('metrics', metric));
|
|
1225
1229
|
dimensionSelection.forEach(dimension =>
|
|
1226
1230
|
params.append('dimensions', dimension),
|
|
1227
1231
|
);
|
|
1228
|
-
if (filters) {
|
|
1229
|
-
params.append('filters',
|
|
1230
|
-
}
|
|
1231
|
-
if (useMaterialized) {
|
|
1232
|
-
params.append('use_materialized', 'true');
|
|
1233
|
-
params.append('dialect', 'druid');
|
|
1234
|
-
} else {
|
|
1235
|
-
params.append('use_materialized', 'false');
|
|
1236
|
-
params.append('dialect', 'spark');
|
|
1232
|
+
if (filters && filters.length > 0) {
|
|
1233
|
+
filters.forEach(f => params.append('filters', f));
|
|
1237
1234
|
}
|
|
1235
|
+
const resolvedDialect = dialect || (useMaterialized ? 'druid' : 'trino');
|
|
1236
|
+
params.append('use_materialized', useMaterialized ? 'true' : 'false');
|
|
1237
|
+
params.append('dialect', resolvedDialect);
|
|
1238
1238
|
return await (
|
|
1239
1239
|
await fetch(`${DJ_URL}/sql/metrics/v3/?${params}`, {
|
|
1240
1240
|
credentials: 'include',
|
|
1241
|
-
params: params,
|
|
1242
1241
|
})
|
|
1243
1242
|
).json();
|
|
1244
1243
|
},
|
|
1245
1244
|
|
|
1246
|
-
data: async function (
|
|
1245
|
+
data: async function (
|
|
1246
|
+
metricSelection,
|
|
1247
|
+
dimensionSelection,
|
|
1248
|
+
filters = [],
|
|
1249
|
+
dialect = null,
|
|
1250
|
+
onProgress = null,
|
|
1251
|
+
) {
|
|
1247
1252
|
const params = new URLSearchParams();
|
|
1248
1253
|
metricSelection.map(metric => params.append('metrics', metric));
|
|
1249
1254
|
dimensionSelection.map(dimension => params.append('dimensions', dimension));
|
|
@@ -1251,18 +1256,56 @@ export const DataJunctionAPI = {
|
|
|
1251
1256
|
filters.forEach(f => params.append('filters', f));
|
|
1252
1257
|
}
|
|
1253
1258
|
params.append('limit', '10000');
|
|
1254
|
-
|
|
1259
|
+
params.append('async_', 'true');
|
|
1260
|
+
params.append('dialect', dialect || 'trino');
|
|
1261
|
+
|
|
1262
|
+
let pollInterval = 1000;
|
|
1263
|
+
|
|
1264
|
+
// Submit the query once
|
|
1265
|
+
const submitResponse = await fetch(`${DJ_URL}/data/?${params}`, {
|
|
1255
1266
|
credentials: 'include',
|
|
1256
1267
|
});
|
|
1257
|
-
if (!
|
|
1258
|
-
const errorData = await
|
|
1268
|
+
if (!submitResponse.ok) {
|
|
1269
|
+
const errorData = await submitResponse.json().catch(() => ({}));
|
|
1259
1270
|
throw new Error(
|
|
1260
1271
|
errorData.message ||
|
|
1261
1272
|
errorData.detail ||
|
|
1262
|
-
`Query failed: ${
|
|
1273
|
+
`Query failed: ${submitResponse.status}`,
|
|
1263
1274
|
);
|
|
1264
1275
|
}
|
|
1265
|
-
|
|
1276
|
+
let results = await submitResponse.json();
|
|
1277
|
+
|
|
1278
|
+
// Report links from the first response so they're visible during polling
|
|
1279
|
+
if (onProgress && results.links?.length > 0) {
|
|
1280
|
+
onProgress({ links: results.links });
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
// Poll by query ID using GET /data/query/{id} to avoid re-submitting
|
|
1284
|
+
while (!QUERY_END_STATES.includes(results.state)) {
|
|
1285
|
+
await sleep(pollInterval);
|
|
1286
|
+
pollInterval = Math.min(pollInterval * 2, 10000);
|
|
1287
|
+
|
|
1288
|
+
const pollResponse = await fetch(`${DJ_URL}/data/query/${results.id}`, {
|
|
1289
|
+
credentials: 'include',
|
|
1290
|
+
});
|
|
1291
|
+
if (!pollResponse.ok) {
|
|
1292
|
+
const errorData = await pollResponse.json().catch(() => ({}));
|
|
1293
|
+
throw new Error(
|
|
1294
|
+
errorData.message ||
|
|
1295
|
+
errorData.detail ||
|
|
1296
|
+
`Query poll failed: ${pollResponse.status}`,
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
results = await pollResponse.json();
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
if (results.state === 'CANCELED') {
|
|
1303
|
+
throw new Error('Query execution was canceled');
|
|
1304
|
+
}
|
|
1305
|
+
if (results.state === 'FAILED') {
|
|
1306
|
+
throw new Error(results.errors?.[0] || 'Query execution failed');
|
|
1307
|
+
}
|
|
1308
|
+
return results;
|
|
1266
1309
|
},
|
|
1267
1310
|
|
|
1268
1311
|
nodeData: async function (nodeName, selection = null) {
|
|
@@ -619,19 +619,17 @@ describe('DataJunctionAPI', () => {
|
|
|
619
619
|
it('calls data correctly', async () => {
|
|
620
620
|
const metricSelection = ['metric1'];
|
|
621
621
|
const dimensionSelection = ['dimension1'];
|
|
622
|
-
|
|
623
|
-
metricSelection.forEach(metric => params.append('metrics', metric));
|
|
624
|
-
dimensionSelection.forEach(dimension =>
|
|
625
|
-
params.append('dimensions', dimension),
|
|
626
|
-
);
|
|
627
|
-
fetch.mockResponseOnce(JSON.stringify({}));
|
|
622
|
+
fetch.mockResponseOnce(JSON.stringify({ state: 'FINISHED', results: [] }));
|
|
628
623
|
await DataJunctionAPI.data(metricSelection, dimensionSelection);
|
|
629
624
|
expect(fetch).toHaveBeenCalledWith(
|
|
630
|
-
`${DJ_URL}/data
|
|
631
|
-
{
|
|
632
|
-
credentials: 'include',
|
|
633
|
-
},
|
|
625
|
+
expect.stringContaining(`${DJ_URL}/data/?`),
|
|
626
|
+
{ credentials: 'include' },
|
|
634
627
|
);
|
|
628
|
+
const url = fetch.mock.calls[0][0];
|
|
629
|
+
expect(url).toContain('metrics=metric1');
|
|
630
|
+
expect(url).toContain('dimensions=dimension1');
|
|
631
|
+
expect(url).toContain('limit=10000');
|
|
632
|
+
expect(url).toContain('async_=true');
|
|
635
633
|
});
|
|
636
634
|
|
|
637
635
|
it('calls compiledSql correctly', async () => {
|
|
@@ -2449,7 +2447,7 @@ describe('DataJunctionAPI', () => {
|
|
|
2449
2447
|
// Test metricsV3 (lines 1106-1124)
|
|
2450
2448
|
it('calls metricsV3 correctly', async () => {
|
|
2451
2449
|
fetch.mockResponseOnce(JSON.stringify({ sql: 'SELECT ...' }));
|
|
2452
|
-
await DataJunctionAPI.metricsV3(['metric1'], ['dim1'], 'filter=value');
|
|
2450
|
+
await DataJunctionAPI.metricsV3(['metric1'], ['dim1'], ['filter=value']);
|
|
2453
2451
|
expect(fetch).toHaveBeenCalledWith(
|
|
2454
2452
|
expect.stringContaining('/sql/metrics/v3/?'),
|
|
2455
2453
|
expect.objectContaining({ credentials: 'include' }),
|
|
@@ -2994,19 +2992,19 @@ describe('DataJunctionAPI', () => {
|
|
|
2994
2992
|
});
|
|
2995
2993
|
|
|
2996
2994
|
// ===== metricsV3 — useMaterialized=false branch (lines 1224-1225) =====
|
|
2997
|
-
it('calls metricsV3 with useMaterialized=false (
|
|
2995
|
+
it('calls metricsV3 with useMaterialized=false (trino dialect)', async () => {
|
|
2998
2996
|
fetch.mockResponseOnce(JSON.stringify({ sql: 'SELECT ...' }));
|
|
2999
|
-
await DataJunctionAPI.metricsV3(['metric1'], ['dim1'],
|
|
2997
|
+
await DataJunctionAPI.metricsV3(['metric1'], ['dim1'], [], false);
|
|
3000
2998
|
const url = fetch.mock.calls[0][0];
|
|
3001
2999
|
expect(url).toContain('use_materialized=false');
|
|
3002
|
-
expect(url).toContain('dialect=
|
|
3000
|
+
expect(url).toContain('dialect=trino');
|
|
3003
3001
|
});
|
|
3004
3002
|
|
|
3005
3003
|
// ===== data — filters non-empty branch (line 1240) =====
|
|
3006
3004
|
it('calls data with filters array', async () => {
|
|
3007
3005
|
fetch.mockResolvedValueOnce({
|
|
3008
3006
|
ok: true,
|
|
3009
|
-
json: () => Promise.resolve(
|
|
3007
|
+
json: () => Promise.resolve({ state: 'FINISHED', results: [] }),
|
|
3010
3008
|
});
|
|
3011
3009
|
await DataJunctionAPI.data(
|
|
3012
3010
|
['metric1'],
|