datajunction-ui 0.0.1-rc.24 → 0.0.1-rc.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.
Files changed (44) hide show
  1. package/.env +1 -0
  2. package/package.json +3 -2
  3. package/src/app/components/Tab.jsx +0 -1
  4. package/src/app/constants.js +2 -2
  5. package/src/app/icons/LoadingIcon.jsx +14 -0
  6. package/src/app/index.tsx +11 -1
  7. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +28 -2
  8. package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +44 -9
  9. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +1 -0
  10. package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +0 -50
  11. package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +2 -0
  12. package/src/app/pages/AddEditNodePage/index.jsx +60 -6
  13. package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
  14. package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
  15. package/src/app/pages/AddEditTagPage/index.jsx +132 -0
  16. package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
  17. package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
  18. package/src/app/pages/LoginPage/__tests__/index.test.jsx +34 -2
  19. package/src/app/pages/LoginPage/index.jsx +9 -82
  20. package/src/app/pages/NamespacePage/index.jsx +5 -0
  21. package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
  22. package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
  23. package/src/app/pages/NodePage/EditColumnPopover.jsx +1 -1
  24. package/src/app/pages/NodePage/LinkDimensionPopover.jsx +0 -1
  25. package/src/app/pages/NodePage/NodeColumnTab.jsx +102 -25
  26. package/src/app/pages/NodePage/NodeInfoTab.jsx +33 -23
  27. package/src/app/pages/NodePage/NodeMaterializationTab.jsx +158 -99
  28. package/src/app/pages/NodePage/PartitionColumnPopover.jsx +153 -0
  29. package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +47 -0
  30. package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +47 -17
  31. package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +101 -100
  32. package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +1 -0
  33. package/src/app/pages/Root/index.tsx +1 -1
  34. package/src/app/pages/TagPage/Loadable.jsx +16 -0
  35. package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
  36. package/src/app/pages/TagPage/index.jsx +79 -0
  37. package/src/app/services/DJService.js +166 -1
  38. package/src/app/services/__tests__/DJService.test.jsx +196 -1
  39. package/src/mocks/mockNodes.jsx +64 -31
  40. package/src/styles/dag.css +4 -0
  41. package/src/styles/index.css +89 -1
  42. package/src/styles/loading.css +34 -0
  43. package/src/styles/login.css +17 -3
  44. package/src/utils/form.jsx +2 -2
@@ -104,9 +104,6 @@ exports[`<NodePage /> renders the NodeMaterialization tab with materializations
104
104
  <th
105
105
  class="text-start"
106
106
  >
107
- Name
108
- </th>
109
- <th>
110
107
  Schedule
111
108
  </th>
112
109
  <th>
@@ -131,11 +128,22 @@ exports[`<NodePage /> renders the NodeMaterialization tab with materializations
131
128
  <td
132
129
  class="text-start node_name"
133
130
  >
134
- <a
135
- href="http://fake.url/job"
131
+ <span
132
+ class="badge cron"
136
133
  >
137
- country_birth_date_contractor_id_379232101
138
- </a>
134
+ 0 * * * *
135
+ </span>
136
+ <div
137
+ class="cron-description"
138
+ >
139
+ Every hour
140
+
141
+ </div>
142
+ </td>
143
+ <td>
144
+ spark
145
+ <br />
146
+ 2.4.4
139
147
  <button
140
148
  aria-label="code-button"
141
149
  class="code-button"
@@ -222,79 +230,7 @@ exports[`<NodePage /> renders the NodeMaterialization tab with materializations
222
230
  </pre>
223
231
  </div>
224
232
  </td>
225
- <td>
226
- <span
227
- class="badge cron"
228
- >
229
- 0 * * * *
230
- </span>
231
- <div
232
- class="cron-description"
233
- >
234
- Every hour
235
-
236
- </div>
237
- </td>
238
- <td>
239
- spark
240
- <br />
241
- 2.4.4
242
- </td>
243
- <td>
244
- <div
245
- class="partition__full"
246
- >
247
- <div
248
- class="partition__header"
249
- >
250
- country
251
- </div>
252
- <div
253
- class="partition__body"
254
- >
255
- <span
256
- class="badge partition_value"
257
- >
258
- DE
259
- </span>
260
- <span
261
- class="badge partition_value"
262
- >
263
- MY
264
- </span>
265
- </div>
266
- </div>
267
- <div
268
- class="partition__full"
269
- >
270
- <div
271
- class="partition__header"
272
- >
273
- contractor_id
274
- </div>
275
- <div
276
- class="partition__body"
277
- >
278
- <div>
279
- <span
280
- class="badge partition_value"
281
- >
282
- <span
283
- class="badge partition_value"
284
- >
285
- 1
286
- </span>
287
- to
288
- <span
289
- class="badge partition_value"
290
- >
291
- 10
292
- </span>
293
- </span>
294
- </div>
295
- </div>
296
- </div>
297
- </td>
233
+ <td />
298
234
  <td>
299
235
  <div
300
236
  class="table__full"
@@ -356,35 +292,100 @@ exports[`<NodePage /> renders the NodeMaterialization tab with materializations
356
292
  </div>
357
293
  </td>
358
294
  <td>
359
- <div
360
- class="partition__full"
295
+ <a
296
+ class="partitionLink"
361
297
  >
362
298
  <div
363
- class="partition__header"
299
+ class="partition__full"
364
300
  >
365
- birth_date
366
- </div>
367
- <div
368
- class="partition__body"
369
- >
370
- <div>
301
+ <div
302
+ class="partition__header"
303
+ />
304
+ <div
305
+ class="partition__body"
306
+ >
371
307
  <span
372
308
  class="badge partition_value"
373
309
  >
374
- <span
375
- class="badge partition_value"
376
- >
377
- 20010101
378
- </span>
379
- to
380
- <span
381
- class="badge partition_value"
382
- >
383
- 20020101
384
- </span>
310
+ 20230101
311
+ </span>
312
+ to
313
+ <span
314
+ class="badge partition_value"
315
+ >
316
+ 20230102
385
317
  </span>
386
318
  </div>
387
319
  </div>
320
+ </a>
321
+ <button
322
+ aria-label="AddBackfill"
323
+ class="edit_button"
324
+ tabindex="0"
325
+ >
326
+ <span
327
+ class="add_node"
328
+ >
329
+ + Add Backfill
330
+ </span>
331
+ </button>
332
+ <div
333
+ class="fade modal-backdrop in"
334
+ style="display: none;"
335
+ />
336
+ <div
337
+ aria-label="client-code"
338
+ class="centerPopover"
339
+ role="dialog"
340
+ style="display: none; width: 50%;"
341
+ >
342
+ <form
343
+ action="#"
344
+ >
345
+ <h2>
346
+ Run Backfill
347
+ </h2>
348
+ <span
349
+ data-testid="edit-partition"
350
+ >
351
+ <label
352
+ for="engine"
353
+ style="padding-bottom: 1rem;"
354
+ >
355
+ Engine
356
+ </label>
357
+ <select
358
+ disabled=""
359
+ id="engine"
360
+ name="engine"
361
+ >
362
+ <option
363
+ value="spark"
364
+ >
365
+ spark
366
+
367
+ 2.4.4
368
+ </option>
369
+ </select>
370
+ </span>
371
+ <br />
372
+ <br />
373
+ <label
374
+ for="partition"
375
+ style="padding-bottom: 1rem;"
376
+ >
377
+ Partition Range
378
+ </label>
379
+ <br />
380
+ <button
381
+ aria-hidden="false"
382
+ aria-label="SaveEditColumn"
383
+ class="add_node"
384
+ type="submit"
385
+ >
386
+ Save
387
+ </button>
388
+ </form>
388
389
  </div>
389
390
  </td>
390
391
  <td>
@@ -4,6 +4,7 @@ exports[`<RegisterTablePage /> registers a table correctly 1`] = `
4
4
  HTMLCollection [
5
5
  <div
6
6
  class="message success"
7
+ data-testid="success"
7
8
  >
8
9
  <svg
9
10
  class="bi bi-check-circle-fill"
@@ -59,7 +59,7 @@ export function Root() {
59
59
  ) : (
60
60
  <span className="menu-link">
61
61
  <span className="menu-title">
62
- <button onClick={handleLogout}>Logout</button>
62
+ <a onClick={handleLogout}>Logout</a>
63
63
  </span>
64
64
  </span>
65
65
  )}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Asynchronously loads the component for the Node page
3
+ */
4
+
5
+ import * as React from 'react';
6
+ import { lazyLoad } from '../../../utils/loadable';
7
+
8
+ export const TagPage = () => {
9
+ return lazyLoad(
10
+ () => import('./index'),
11
+ module => module.TagPage,
12
+ {
13
+ fallback: <div></div>,
14
+ },
15
+ )();
16
+ };
@@ -0,0 +1,70 @@
1
+ import React from 'react';
2
+ import { screen, waitFor } from '@testing-library/react';
3
+ import fetchMock from 'jest-fetch-mock';
4
+ import { render } from '../../../../setupTests';
5
+ import { MemoryRouter, Route, Routes } from 'react-router-dom';
6
+ import DJClientContext from '../../../providers/djclient';
7
+ import { TagPage } from '../index';
8
+
9
+ describe('<TagPage />', () => {
10
+ const initializeMockDJClient = () => {
11
+ return {
12
+ DataJunctionAPI: {
13
+ getTag: jest.fn(),
14
+ listNodesForTag: jest.fn(),
15
+ },
16
+ };
17
+ };
18
+
19
+ const mockDjClient = initializeMockDJClient();
20
+
21
+ beforeEach(() => {
22
+ fetchMock.resetMocks();
23
+ jest.clearAllMocks();
24
+ window.scrollTo = jest.fn();
25
+
26
+ mockDjClient.DataJunctionAPI.getTag.mockReturnValue({
27
+ name: 'domains.com',
28
+ tag_type: 'domains',
29
+ description: 'Top-level domain .com',
30
+ });
31
+ mockDjClient.DataJunctionAPI.listNodesForTag.mockReturnValue([
32
+ {
33
+ name: 'random.node_a',
34
+ type: 'metric',
35
+ display_name: 'Node A',
36
+ },
37
+ ]);
38
+ });
39
+
40
+ const renderTagsPage = element => {
41
+ return render(
42
+ <MemoryRouter initialEntries={['/tags/:name']}>
43
+ <Routes>
44
+ <Route path="tags/:name" element={element} />
45
+ </Routes>
46
+ </MemoryRouter>,
47
+ );
48
+ };
49
+
50
+ const testElement = djClient => {
51
+ return (
52
+ <DJClientContext.Provider value={djClient}>
53
+ <TagPage />
54
+ </DJClientContext.Provider>
55
+ );
56
+ };
57
+
58
+ it('renders the tag page correctly', async () => {
59
+ const element = testElement(mockDjClient);
60
+ renderTagsPage(element);
61
+ await waitFor(() => {
62
+ expect(mockDjClient.DataJunctionAPI.getTag).toHaveBeenCalledTimes(1);
63
+ expect(
64
+ mockDjClient.DataJunctionAPI.listNodesForTag,
65
+ ).toHaveBeenCalledTimes(1);
66
+ });
67
+ expect(screen.getByText('Nodes')).toBeInTheDocument();
68
+ expect(screen.getByText('Node A')).toBeInTheDocument();
69
+ }, 60000);
70
+ });
@@ -0,0 +1,79 @@
1
+ /**
2
+ * For a given tag, displays nodes tagged with it
3
+ */
4
+ import NamespaceHeader from '../../components/NamespaceHeader';
5
+ import React, { useContext, useEffect, useState } from 'react';
6
+ import 'styles/node-creation.scss';
7
+ import DJClientContext from '../../providers/djclient';
8
+ import { useParams } from 'react-router-dom';
9
+
10
+ export function TagPage() {
11
+ const [nodes, setNodes] = useState([]);
12
+ const [tag, setTag] = useState([]);
13
+
14
+ const { name } = useParams();
15
+
16
+ const djClient = useContext(DJClientContext).DataJunctionAPI;
17
+ useEffect(() => {
18
+ const fetchData = async () => {
19
+ const data = await djClient.listNodesForTag(name);
20
+ const tagData = await djClient.getTag(name);
21
+ setNodes(data);
22
+ setTag(tagData);
23
+ };
24
+ fetchData().catch(console.error);
25
+ }, [djClient, name]);
26
+
27
+ return (
28
+ <div className="mid">
29
+ <NamespaceHeader namespace="" />
30
+ <div className="card">
31
+ <div className="card-header">
32
+ <h3
33
+ className="card-title align-items-start flex-column"
34
+ style={{ display: 'inline-block' }}
35
+ >
36
+ Tag
37
+ </h3>
38
+ <div>
39
+ <div style={{ marginBottom: '1.5rem' }}>
40
+ <h6 className="mb-0 w-100">Display Name</h6>
41
+ <p className="mb-0 opacity-75">{tag.display_name}</p>
42
+ </div>
43
+ <div style={{ marginBottom: '1.5rem' }}>
44
+ <h6 className="mb-0 w-100">Name</h6>
45
+ <p className="mb-0 opacity-75">{name}</p>
46
+ </div>
47
+ <div style={{ marginBottom: '1.5rem' }}>
48
+ <h6 className="mb-0 w-100">Tag Type</h6>
49
+ <p className="mb-0 opacity-75">{tag.tag_type}</p>
50
+ </div>
51
+ <div style={{ marginBottom: '1.5rem' }}>
52
+ <h6 className="mb-0 w-100">Description</h6>
53
+ <p className="mb-0 opacity-75">{tag.description}</p>
54
+ </div>
55
+ <div style={{ marginBottom: '1.5rem' }}>
56
+ <h6 className="mb-0 w-100">Nodes</h6>
57
+ <div className={`list-group-item`}>
58
+ {nodes?.map(node => (
59
+ <div
60
+ className="button-3 cube-element"
61
+ key={node.name}
62
+ role="cell"
63
+ aria-label="CubeElement"
64
+ aria-hidden="false"
65
+ >
66
+ <a href={`/nodes/${node.name}`}>{node.display_name}</a>
67
+ <span className={`badge node_type__${node.type}`}>
68
+ {node.type}
69
+ </span>
70
+ </div>
71
+ ))}
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ );
79
+ }
@@ -12,7 +12,7 @@ export const DataJunctionAPI = {
12
12
  },
13
13
 
14
14
  logout: async function () {
15
- await await fetch(`${DJ_URL}/basic/logout/`, {
15
+ await await fetch(`${DJ_URL}/logout/`, {
16
16
  credentials: 'include',
17
17
  method: 'POST',
18
18
  });
@@ -26,6 +26,14 @@ export const DataJunctionAPI = {
26
26
  ).json();
27
27
  },
28
28
 
29
+ engines: async function () {
30
+ return await (
31
+ await fetch(`${DJ_URL}/engines`, {
32
+ credentials: 'include',
33
+ })
34
+ ).json();
35
+ },
36
+
29
37
  node: async function (name) {
30
38
  const data = await (
31
39
  await fetch(`${DJ_URL}/nodes/${name}/`, {
@@ -497,4 +505,161 @@ export const DataJunctionAPI = {
497
505
  });
498
506
  return { status: response.status, json: await response.json() };
499
507
  },
508
+ listTags: async function () {
509
+ const response = await fetch(`${DJ_URL}/tags`, {
510
+ method: 'GET',
511
+ headers: {
512
+ 'Content-Type': 'application/json',
513
+ },
514
+ credentials: 'include',
515
+ });
516
+ return await response.json();
517
+ },
518
+ getTag: async function (tagName) {
519
+ const response = await fetch(`${DJ_URL}/tags/${tagName}`, {
520
+ method: 'GET',
521
+ headers: {
522
+ 'Content-Type': 'application/json',
523
+ },
524
+ credentials: 'include',
525
+ });
526
+ return await response.json();
527
+ },
528
+ listNodesForTag: async function (tagName) {
529
+ const response = await fetch(`${DJ_URL}/tags/${tagName}/nodes`, {
530
+ method: 'GET',
531
+ headers: {
532
+ 'Content-Type': 'application/json',
533
+ },
534
+ credentials: 'include',
535
+ });
536
+ return await response.json();
537
+ },
538
+ tagsNode: async function (nodeName, tagNames) {
539
+ const url = tagNames
540
+ .map(value => `tag_names=${encodeURIComponent(value)}`)
541
+ .join('&');
542
+ const response = await fetch(`${DJ_URL}/nodes/${nodeName}/tags?${url}`, {
543
+ method: 'POST',
544
+ headers: {
545
+ 'Content-Type': 'application/json',
546
+ },
547
+ credentials: 'include',
548
+ });
549
+ return { status: response.status, json: await response.json() };
550
+ },
551
+ addTag: async function (name, displayName, tagType, description) {
552
+ const response = await fetch(`${DJ_URL}/tags`, {
553
+ method: 'POST',
554
+ headers: {
555
+ 'Content-Type': 'application/json',
556
+ },
557
+ body: JSON.stringify({
558
+ name: name,
559
+ display_name: displayName,
560
+ tag_type: tagType,
561
+ description: description,
562
+ }),
563
+ credentials: 'include',
564
+ });
565
+ return { status: response.status, json: await response.json() };
566
+ },
567
+ editTag: async function (name, description, displayName) {
568
+ const updates = {};
569
+ if (description) {
570
+ updates.description = description;
571
+ }
572
+ if (displayName) {
573
+ updates.display_name = displayName;
574
+ }
575
+
576
+ const response = await fetch(`${DJ_URL}/tags/${name}`, {
577
+ method: 'PATCH',
578
+ headers: {
579
+ 'Content-Type': 'application/json',
580
+ },
581
+ body: JSON.stringify(updates),
582
+ credentials: 'include',
583
+ });
584
+ return { status: response.status, json: await response.json() };
585
+ },
586
+ setPartition: async function (
587
+ nodeName,
588
+ columnName,
589
+ partitionType,
590
+ format,
591
+ granularity,
592
+ ) {
593
+ const body = {
594
+ type_: partitionType,
595
+ };
596
+ if (format) {
597
+ body.format = format;
598
+ }
599
+ if (granularity) {
600
+ body.granularity = granularity;
601
+ }
602
+ const response = await fetch(
603
+ `${DJ_URL}/nodes/${nodeName}/columns/${columnName}/partition`,
604
+ {
605
+ method: 'POST',
606
+ headers: {
607
+ 'Content-Type': 'application/json',
608
+ },
609
+ body: JSON.stringify(body),
610
+ credentials: 'include',
611
+ },
612
+ );
613
+ return { status: response.status, json: await response.json() };
614
+ },
615
+ materialize: async function (
616
+ nodeName,
617
+ engineName,
618
+ engineVersion,
619
+ schedule,
620
+ config,
621
+ ) {
622
+ const response = await fetch(
623
+ `${DJ_URL}/nodes/${nodeName}/materialization`,
624
+ {
625
+ method: 'POST',
626
+ headers: {
627
+ 'Content-Type': 'application/json',
628
+ },
629
+ body: JSON.stringify({
630
+ engine: {
631
+ name: engineName,
632
+ version: engineVersion,
633
+ },
634
+ schedule: schedule,
635
+ config: JSON.parse(config),
636
+ }),
637
+ credentials: 'include',
638
+ },
639
+ );
640
+ return { status: response.status, json: await response.json() };
641
+ },
642
+ runBackfill: async function (
643
+ nodeName,
644
+ materializationName,
645
+ partitionColumn,
646
+ from,
647
+ to,
648
+ ) {
649
+ const response = await fetch(
650
+ `${DJ_URL}/nodes/${nodeName}/materializations/${materializationName}/backfill`,
651
+ {
652
+ method: 'POST',
653
+ headers: {
654
+ 'Content-Type': 'application/json',
655
+ },
656
+ body: JSON.stringify({
657
+ column_name: partitionColumn,
658
+ range: [from, to],
659
+ }),
660
+ credentials: 'include',
661
+ },
662
+ );
663
+ return { status: response.status, json: await response.json() };
664
+ },
500
665
  };