datajunction-ui 0.0.27 → 0.0.30
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/__tests__/reportWebVitals.test.ts +27 -0
- package/src/app/components/NamespaceHeader.jsx +85 -26
- package/src/app/components/__tests__/NamespaceHeader.test.jsx +144 -0
- package/src/app/components/__tests__/NotificationBell.test.tsx +23 -0
- package/src/app/components/djgraph/__tests__/DJNode.test.tsx +36 -0
- package/src/app/icons/__tests__/Icons.test.jsx +24 -0
- package/src/app/pages/NamespacePage/Explorer.jsx +68 -10
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +21 -11
- package/src/app/pages/NamespacePage/index.jsx +625 -48
- package/src/app/pages/NotificationsPage/__tests__/index.test.jsx +28 -0
- package/src/app/pages/OverviewPage/OverviewPanel.jsx +1 -3
- package/src/app/pages/QueryPlannerPage/PreAggDetailsPanel.jsx +20 -20
- package/src/app/pages/QueryPlannerPage/index.jsx +1 -1
- package/src/app/pages/Root/__tests__/index.test.jsx +99 -4
- package/src/app/pages/SQLBuilderPage/__tests__/index.test.jsx +177 -0
- package/src/app/pages/SettingsPage/__tests__/CreateServiceAccountModal.test.jsx +50 -0
- package/src/app/pages/SettingsPage/__tests__/NotificationSubscriptionsSection.test.jsx +95 -0
- package/src/app/pages/SettingsPage/__tests__/index.test.jsx +315 -28
- package/src/app/providers/UserProvider.tsx +1 -5
- package/src/app/services/DJService.js +48 -2
- package/src/app/utils/__tests__/date.test.js +60 -140
- package/src/styles/index.css +51 -10
|
@@ -185,7 +185,7 @@ describe('SettingsPage', () => {
|
|
|
185
185
|
});
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
it('handles subscription update', async () => {
|
|
188
|
+
it('handles subscription update via edit mode and checkbox toggle', async () => {
|
|
189
189
|
mockDjClient.getNotificationPreferences.mockResolvedValue([
|
|
190
190
|
{
|
|
191
191
|
entity_name: 'default.my_metric',
|
|
@@ -218,18 +218,93 @@ describe('SettingsPage', () => {
|
|
|
218
218
|
expect(screen.getByText('default.my_metric')).toBeInTheDocument();
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
221
|
+
// First click Edit button to enter edit mode and show checkboxes
|
|
222
|
+
const editBtn = screen.getByTitle('Edit subscription');
|
|
223
|
+
fireEvent.click(editBtn);
|
|
224
|
+
|
|
225
|
+
// Wait for checkboxes to appear
|
|
226
|
+
await waitFor(() => {
|
|
227
|
+
expect(screen.getAllByRole('checkbox').length).toBeGreaterThan(0);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Add another activity type (e.g., 'create')
|
|
231
|
+
const createCheckbox = screen.getByLabelText('Create');
|
|
232
|
+
fireEvent.click(createCheckbox);
|
|
233
|
+
|
|
234
|
+
// Save changes
|
|
235
|
+
const saveBtn = screen.getByText('Save');
|
|
236
|
+
fireEvent.click(saveBtn);
|
|
237
|
+
|
|
238
|
+
await waitFor(() => {
|
|
239
|
+
expect(mockDjClient.subscribeToNotifications).toHaveBeenCalledWith({
|
|
240
|
+
entity_type: 'node',
|
|
241
|
+
entity_name: 'default.my_metric',
|
|
242
|
+
activity_types: expect.any(Array),
|
|
243
|
+
alert_types: ['web'],
|
|
228
244
|
});
|
|
229
|
-
}
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('updates local subscription state after subscription update', async () => {
|
|
249
|
+
mockDjClient.getNotificationPreferences.mockResolvedValue([
|
|
250
|
+
{
|
|
251
|
+
entity_name: 'default.my_metric',
|
|
252
|
+
entity_type: 'node',
|
|
253
|
+
activity_types: ['update', 'status_change'],
|
|
254
|
+
alert_types: ['web'],
|
|
255
|
+
},
|
|
256
|
+
]);
|
|
257
|
+
|
|
258
|
+
mockDjClient.getNodesByNames.mockResolvedValue([
|
|
259
|
+
{
|
|
260
|
+
name: 'default.my_metric',
|
|
261
|
+
type: 'METRIC',
|
|
262
|
+
current: {
|
|
263
|
+
displayName: 'My Metric',
|
|
264
|
+
status: 'VALID',
|
|
265
|
+
mode: 'PUBLISHED',
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
mockDjClient.subscribeToNotifications.mockResolvedValue({
|
|
271
|
+
status: 200,
|
|
272
|
+
json: { message: 'Updated' },
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
renderWithContext();
|
|
276
|
+
|
|
277
|
+
await waitFor(() => {
|
|
278
|
+
expect(screen.getByText('default.my_metric')).toBeInTheDocument();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// First click the Edit button to enter edit mode
|
|
282
|
+
const editBtn = screen.getByTitle('Edit subscription');
|
|
283
|
+
fireEvent.click(editBtn);
|
|
284
|
+
|
|
285
|
+
// Now checkboxes should be visible
|
|
286
|
+
await waitFor(() => {
|
|
287
|
+
expect(screen.getAllByRole('checkbox').length).toBeGreaterThan(0);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// Toggle a checkbox and save
|
|
291
|
+
const checkboxes = screen.getAllByRole('checkbox');
|
|
292
|
+
fireEvent.click(checkboxes[0]);
|
|
293
|
+
|
|
294
|
+
// Click Save button
|
|
295
|
+
const saveBtn = screen.getByText('Save');
|
|
296
|
+
fireEvent.click(saveBtn);
|
|
297
|
+
|
|
298
|
+
await waitFor(() => {
|
|
299
|
+
expect(mockDjClient.subscribeToNotifications).toHaveBeenCalled();
|
|
300
|
+
});
|
|
230
301
|
});
|
|
231
302
|
|
|
232
|
-
it('handles subscription unsubscribe', async () => {
|
|
303
|
+
it('handles subscription unsubscribe and removes from list', async () => {
|
|
304
|
+
// Mock window.confirm to return true
|
|
305
|
+
const originalConfirm = window.confirm;
|
|
306
|
+
window.confirm = jest.fn().mockReturnValue(true);
|
|
307
|
+
|
|
233
308
|
mockDjClient.getNotificationPreferences.mockResolvedValue([
|
|
234
309
|
{
|
|
235
310
|
entity_name: 'default.my_metric',
|
|
@@ -237,6 +312,12 @@ describe('SettingsPage', () => {
|
|
|
237
312
|
activity_types: ['update'],
|
|
238
313
|
alert_types: ['web'],
|
|
239
314
|
},
|
|
315
|
+
{
|
|
316
|
+
entity_name: 'default.another_metric',
|
|
317
|
+
entity_type: 'node',
|
|
318
|
+
activity_types: ['status_change'],
|
|
319
|
+
alert_types: ['web'],
|
|
320
|
+
},
|
|
240
321
|
]);
|
|
241
322
|
|
|
242
323
|
mockDjClient.getNodesByNames.mockResolvedValue([
|
|
@@ -249,6 +330,15 @@ describe('SettingsPage', () => {
|
|
|
249
330
|
mode: 'PUBLISHED',
|
|
250
331
|
},
|
|
251
332
|
},
|
|
333
|
+
{
|
|
334
|
+
name: 'default.another_metric',
|
|
335
|
+
type: 'METRIC',
|
|
336
|
+
current: {
|
|
337
|
+
displayName: 'Another Metric',
|
|
338
|
+
status: 'VALID',
|
|
339
|
+
mode: 'PUBLISHED',
|
|
340
|
+
},
|
|
341
|
+
},
|
|
252
342
|
]);
|
|
253
343
|
|
|
254
344
|
mockDjClient.unsubscribeFromNotifications.mockResolvedValue({
|
|
@@ -260,18 +350,20 @@ describe('SettingsPage', () => {
|
|
|
260
350
|
|
|
261
351
|
await waitFor(() => {
|
|
262
352
|
expect(screen.getByText('default.my_metric')).toBeInTheDocument();
|
|
353
|
+
expect(screen.getByText('default.another_metric')).toBeInTheDocument();
|
|
263
354
|
});
|
|
264
355
|
|
|
265
|
-
// Find unsubscribe
|
|
266
|
-
const
|
|
267
|
-
|
|
356
|
+
// Find unsubscribe buttons (there are multiple, one per subscription)
|
|
357
|
+
const unsubscribeBtns = screen.getAllByTitle('Unsubscribe');
|
|
358
|
+
fireEvent.click(unsubscribeBtns[0]);
|
|
359
|
+
|
|
360
|
+
await waitFor(() => {
|
|
361
|
+
expect(window.confirm).toHaveBeenCalled();
|
|
362
|
+
expect(mockDjClient.unsubscribeFromNotifications).toHaveBeenCalled();
|
|
268
363
|
});
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
expect(mockDjClient.unsubscribeFromNotifications).toHaveBeenCalled();
|
|
273
|
-
});
|
|
274
|
-
}
|
|
364
|
+
|
|
365
|
+
// Restore original confirm
|
|
366
|
+
window.confirm = originalConfirm;
|
|
275
367
|
});
|
|
276
368
|
|
|
277
369
|
it('opens create service account modal', async () => {
|
|
@@ -291,7 +383,69 @@ describe('SettingsPage', () => {
|
|
|
291
383
|
});
|
|
292
384
|
});
|
|
293
385
|
|
|
294
|
-
it('
|
|
386
|
+
it('creates service account and adds to list when successful', async () => {
|
|
387
|
+
const newAccount = {
|
|
388
|
+
id: 99,
|
|
389
|
+
name: 'new-account',
|
|
390
|
+
client_id: 'new-client-id-123',
|
|
391
|
+
client_secret: 'secret-xyz',
|
|
392
|
+
created_at: '2025-01-11T00:00:00Z',
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
mockDjClient.listServiceAccounts.mockResolvedValue([]);
|
|
396
|
+
mockDjClient.createServiceAccount.mockResolvedValue(newAccount);
|
|
397
|
+
|
|
398
|
+
renderWithContext();
|
|
399
|
+
|
|
400
|
+
await waitFor(() => {
|
|
401
|
+
expect(screen.getByText('Settings')).toBeInTheDocument();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Open create modal
|
|
405
|
+
const createBtn = screen.getByRole('button', { name: /create/i });
|
|
406
|
+
fireEvent.click(createBtn);
|
|
407
|
+
|
|
408
|
+
await waitFor(() => {
|
|
409
|
+
expect(screen.getByText('Create Service Account')).toBeInTheDocument();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Fill in the name input using ID
|
|
413
|
+
const nameInput = document.getElementById('service-account-name');
|
|
414
|
+
fireEvent.change(nameInput, { target: { value: 'new-account' } });
|
|
415
|
+
|
|
416
|
+
// Submit the form by clicking the submit button in the modal
|
|
417
|
+
const submitBtns = screen.getAllByRole('button', { name: /create/i });
|
|
418
|
+
// The second Create button is the submit button in the modal
|
|
419
|
+
fireEvent.click(submitBtns[submitBtns.length - 1]);
|
|
420
|
+
|
|
421
|
+
await waitFor(() => {
|
|
422
|
+
expect(mockDjClient.createServiceAccount).toHaveBeenCalledWith(
|
|
423
|
+
'new-account',
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('does not add service account to list if creation returns no client_id', async () => {
|
|
429
|
+
mockDjClient.listServiceAccounts.mockResolvedValue([]);
|
|
430
|
+
mockDjClient.createServiceAccount.mockResolvedValue({
|
|
431
|
+
error: 'Name already exists',
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
renderWithContext();
|
|
435
|
+
|
|
436
|
+
await waitFor(() => {
|
|
437
|
+
expect(screen.getByText('Settings')).toBeInTheDocument();
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// The service accounts section should still show empty state
|
|
441
|
+
expect(screen.getByText(/No service accounts yet/i)).toBeInTheDocument();
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it('handles service account deletion by clicking delete button with confirmation', async () => {
|
|
445
|
+
// Mock window.confirm to return true
|
|
446
|
+
const originalConfirm = window.confirm;
|
|
447
|
+
window.confirm = jest.fn().mockReturnValue(true);
|
|
448
|
+
|
|
295
449
|
mockDjClient.listServiceAccounts.mockResolvedValue([
|
|
296
450
|
{
|
|
297
451
|
id: 1,
|
|
@@ -299,6 +453,12 @@ describe('SettingsPage', () => {
|
|
|
299
453
|
client_id: 'abc-123',
|
|
300
454
|
created_at: '2024-12-01T00:00:00Z',
|
|
301
455
|
},
|
|
456
|
+
{
|
|
457
|
+
id: 2,
|
|
458
|
+
name: 'other-pipeline',
|
|
459
|
+
client_id: 'def-456',
|
|
460
|
+
created_at: '2024-12-02T00:00:00Z',
|
|
461
|
+
},
|
|
302
462
|
]);
|
|
303
463
|
|
|
304
464
|
mockDjClient.deleteServiceAccount.mockResolvedValue({
|
|
@@ -309,16 +469,98 @@ describe('SettingsPage', () => {
|
|
|
309
469
|
|
|
310
470
|
await waitFor(() => {
|
|
311
471
|
expect(screen.getByText('my-pipeline')).toBeInTheDocument();
|
|
472
|
+
expect(screen.getByText('other-pipeline')).toBeInTheDocument();
|
|
312
473
|
});
|
|
313
474
|
|
|
314
|
-
// Find delete button
|
|
315
|
-
const deleteBtn = screen.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
475
|
+
// Find delete button by title attribute (exact match) - first one for my-pipeline
|
|
476
|
+
const deleteBtn = screen.getAllByTitle('Delete service account')[0];
|
|
477
|
+
fireEvent.click(deleteBtn);
|
|
478
|
+
|
|
479
|
+
await waitFor(() => {
|
|
480
|
+
expect(window.confirm).toHaveBeenCalled();
|
|
481
|
+
expect(mockDjClient.deleteServiceAccount).toHaveBeenCalledWith('abc-123');
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Restore original confirm
|
|
485
|
+
window.confirm = originalConfirm;
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
it('removes service account from list after deletion', async () => {
|
|
489
|
+
// Mock window.confirm to return true
|
|
490
|
+
const originalConfirm = window.confirm;
|
|
491
|
+
window.confirm = jest.fn().mockReturnValue(true);
|
|
492
|
+
|
|
493
|
+
mockDjClient.listServiceAccounts.mockResolvedValue([
|
|
494
|
+
{
|
|
495
|
+
id: 1,
|
|
496
|
+
name: 'my-pipeline',
|
|
497
|
+
client_id: 'abc-123',
|
|
498
|
+
created_at: '2024-12-01T00:00:00Z',
|
|
499
|
+
},
|
|
500
|
+
]);
|
|
501
|
+
|
|
502
|
+
mockDjClient.deleteServiceAccount.mockResolvedValue({
|
|
503
|
+
message: 'Deleted',
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
renderWithContext();
|
|
507
|
+
|
|
508
|
+
await waitFor(() => {
|
|
509
|
+
expect(screen.getByText('my-pipeline')).toBeInTheDocument();
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// Find and click the delete button
|
|
513
|
+
const deleteBtn = screen.getByTitle('Delete service account');
|
|
514
|
+
fireEvent.click(deleteBtn);
|
|
515
|
+
|
|
516
|
+
await waitFor(() => {
|
|
517
|
+
expect(mockDjClient.deleteServiceAccount).toHaveBeenCalledWith('abc-123');
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
// After deletion, the account should be removed
|
|
521
|
+
await waitFor(() => {
|
|
522
|
+
expect(screen.queryByText('my-pipeline')).not.toBeInTheDocument();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// Restore original confirm
|
|
526
|
+
window.confirm = originalConfirm;
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('does not delete service account when confirmation is cancelled', async () => {
|
|
530
|
+
// Mock window.confirm to return false
|
|
531
|
+
const originalConfirm = window.confirm;
|
|
532
|
+
window.confirm = jest.fn().mockReturnValue(false);
|
|
533
|
+
|
|
534
|
+
mockDjClient.listServiceAccounts.mockResolvedValue([
|
|
535
|
+
{
|
|
536
|
+
id: 1,
|
|
537
|
+
name: 'my-pipeline',
|
|
538
|
+
client_id: 'abc-123',
|
|
539
|
+
created_at: '2024-12-01T00:00:00Z',
|
|
540
|
+
},
|
|
541
|
+
]);
|
|
542
|
+
|
|
543
|
+
renderWithContext();
|
|
544
|
+
|
|
545
|
+
await waitFor(() => {
|
|
546
|
+
expect(screen.getByText('my-pipeline')).toBeInTheDocument();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
// Find and click the delete button
|
|
550
|
+
const deleteBtn = screen.getByTitle('Delete service account');
|
|
551
|
+
fireEvent.click(deleteBtn);
|
|
552
|
+
|
|
553
|
+
// Should show confirmation
|
|
554
|
+
expect(window.confirm).toHaveBeenCalled();
|
|
555
|
+
|
|
556
|
+
// deleteServiceAccount should NOT be called since user cancelled
|
|
557
|
+
expect(mockDjClient.deleteServiceAccount).not.toHaveBeenCalled();
|
|
558
|
+
|
|
559
|
+
// Account should still be in the list
|
|
560
|
+
expect(screen.getByText('my-pipeline')).toBeInTheDocument();
|
|
561
|
+
|
|
562
|
+
// Restore original confirm
|
|
563
|
+
window.confirm = originalConfirm;
|
|
322
564
|
});
|
|
323
565
|
|
|
324
566
|
it('handles non-node subscription types gracefully', async () => {
|
|
@@ -352,4 +594,49 @@ describe('SettingsPage', () => {
|
|
|
352
594
|
// getNotificationPreferences should not be called while user is loading
|
|
353
595
|
expect(mockDjClient.getNotificationPreferences).not.toHaveBeenCalled();
|
|
354
596
|
});
|
|
597
|
+
|
|
598
|
+
it('handles notification preferences fetch error gracefully', async () => {
|
|
599
|
+
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
600
|
+
mockDjClient.getNotificationPreferences.mockRejectedValue(
|
|
601
|
+
new Error('Failed to fetch preferences'),
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
renderWithContext();
|
|
605
|
+
|
|
606
|
+
await waitFor(() => {
|
|
607
|
+
// Page should still render after error
|
|
608
|
+
expect(screen.getByText('Settings')).toBeInTheDocument();
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
// Error should be logged
|
|
612
|
+
expect(consoleSpy).toHaveBeenCalled();
|
|
613
|
+
|
|
614
|
+
consoleSpy.mockRestore();
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('handles null notification preferences response', async () => {
|
|
618
|
+
mockDjClient.getNotificationPreferences.mockResolvedValue(null);
|
|
619
|
+
|
|
620
|
+
renderWithContext();
|
|
621
|
+
|
|
622
|
+
await waitFor(() => {
|
|
623
|
+
expect(screen.getByText('Settings')).toBeInTheDocument();
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Should render subscriptions section with empty list
|
|
627
|
+
expect(screen.getByText(/not watching any nodes yet/i)).toBeInTheDocument();
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
it('handles null service accounts response', async () => {
|
|
631
|
+
mockDjClient.listServiceAccounts.mockResolvedValue(null);
|
|
632
|
+
|
|
633
|
+
renderWithContext();
|
|
634
|
+
|
|
635
|
+
await waitFor(() => {
|
|
636
|
+
expect(screen.getByText('Settings')).toBeInTheDocument();
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
// Should render service accounts section with empty list
|
|
640
|
+
expect(screen.getByText(/No service accounts yet/i)).toBeInTheDocument();
|
|
641
|
+
});
|
|
355
642
|
});
|
|
@@ -68,11 +68,7 @@ export function UserProvider({ children }: { children: React.ReactNode }) {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
export function useCurrentUser() {
|
|
71
|
-
|
|
72
|
-
if (context === undefined) {
|
|
73
|
-
throw new Error('useCurrentUser must be used within a UserProvider');
|
|
74
|
-
}
|
|
75
|
-
return context;
|
|
71
|
+
return useContext(UserContext);
|
|
76
72
|
}
|
|
77
73
|
|
|
78
74
|
export default UserContext;
|
|
@@ -10,6 +10,9 @@ const DJ_GQL = process.env.REACT_APP_DJ_GQL
|
|
|
10
10
|
? process.env.REACT_APP_DJ_GQL
|
|
11
11
|
: process.env.REACT_APP_DJ_URL + '/graphql';
|
|
12
12
|
|
|
13
|
+
// Export the base URL for components that need direct access
|
|
14
|
+
export const getDJUrl = () => DJ_URL;
|
|
15
|
+
|
|
13
16
|
export const DataJunctionAPI = {
|
|
14
17
|
listNodesForLanding: async function (
|
|
15
18
|
namespace,
|
|
@@ -202,9 +205,9 @@ export const DataJunctionAPI = {
|
|
|
202
205
|
const cubeMetrics = (current.cubeMetrics || []).map(m => m.name);
|
|
203
206
|
const cubeDimensions = (current.cubeDimensions || []).map(d => d.name);
|
|
204
207
|
|
|
205
|
-
// Extract druid_cube materialization if present
|
|
208
|
+
// Extract druid_cube materialization if present (v3 or legacy)
|
|
206
209
|
const druidMat = (current.materializations || []).find(
|
|
207
|
-
m => m.name === 'druid_cube',
|
|
210
|
+
m => m.name === 'druid_cube' || m.name === 'druid_cube_v3',
|
|
208
211
|
);
|
|
209
212
|
const cubeMaterialization = druidMat
|
|
210
213
|
? {
|
|
@@ -1014,6 +1017,40 @@ export const DataJunctionAPI = {
|
|
|
1014
1017
|
).json();
|
|
1015
1018
|
},
|
|
1016
1019
|
|
|
1020
|
+
namespaceSources: async function (namespace) {
|
|
1021
|
+
return await (
|
|
1022
|
+
await fetch(`${DJ_URL}/namespaces/${namespace}/sources`, {
|
|
1023
|
+
credentials: 'include',
|
|
1024
|
+
})
|
|
1025
|
+
).json();
|
|
1026
|
+
},
|
|
1027
|
+
|
|
1028
|
+
namespaceSourcesBulk: async function (namespaces) {
|
|
1029
|
+
return await (
|
|
1030
|
+
await fetch(`${DJ_URL}/namespaces/sources/bulk`, {
|
|
1031
|
+
method: 'POST',
|
|
1032
|
+
headers: {
|
|
1033
|
+
'Content-Type': 'application/json',
|
|
1034
|
+
},
|
|
1035
|
+
body: JSON.stringify({ namespaces }),
|
|
1036
|
+
credentials: 'include',
|
|
1037
|
+
})
|
|
1038
|
+
).json();
|
|
1039
|
+
},
|
|
1040
|
+
|
|
1041
|
+
listDeployments: async function (namespace, limit = 5) {
|
|
1042
|
+
const params = new URLSearchParams();
|
|
1043
|
+
if (namespace) {
|
|
1044
|
+
params.append('namespace', namespace);
|
|
1045
|
+
}
|
|
1046
|
+
params.append('limit', limit);
|
|
1047
|
+
return await (
|
|
1048
|
+
await fetch(`${DJ_URL}/deployments?${params.toString()}`, {
|
|
1049
|
+
credentials: 'include',
|
|
1050
|
+
})
|
|
1051
|
+
).json();
|
|
1052
|
+
},
|
|
1053
|
+
|
|
1017
1054
|
sql: async function (metric_name, selection) {
|
|
1018
1055
|
const params = new URLSearchParams(selection);
|
|
1019
1056
|
for (const [key, value] of Object.entries(selection)) {
|
|
@@ -1107,6 +1144,7 @@ export const DataJunctionAPI = {
|
|
|
1107
1144
|
metricSelection,
|
|
1108
1145
|
dimensionSelection,
|
|
1109
1146
|
filters = '',
|
|
1147
|
+
useMaterialized = true,
|
|
1110
1148
|
) {
|
|
1111
1149
|
const params = new URLSearchParams();
|
|
1112
1150
|
metricSelection.forEach(metric => params.append('metrics', metric));
|
|
@@ -1116,9 +1154,17 @@ export const DataJunctionAPI = {
|
|
|
1116
1154
|
if (filters) {
|
|
1117
1155
|
params.append('filters', filters);
|
|
1118
1156
|
}
|
|
1157
|
+
if (useMaterialized) {
|
|
1158
|
+
params.append('use_materialized', 'true');
|
|
1159
|
+
params.append('dialect', 'druid');
|
|
1160
|
+
} else {
|
|
1161
|
+
params.append('use_materialized', 'false');
|
|
1162
|
+
params.append('dialect', 'spark');
|
|
1163
|
+
}
|
|
1119
1164
|
return await (
|
|
1120
1165
|
await fetch(`${DJ_URL}/sql/metrics/v3/?${params}`, {
|
|
1121
1166
|
credentials: 'include',
|
|
1167
|
+
params: params,
|
|
1122
1168
|
})
|
|
1123
1169
|
).json();
|
|
1124
1170
|
},
|