datajunction-ui 0.0.1-a45 → 0.0.1-a45.dev5

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.
@@ -0,0 +1,179 @@
1
+ import React from 'react';
2
+ import { render, waitFor, screen } from '@testing-library/react';
3
+ import RevisionDiff from '../RevisionDiff';
4
+ import DJClientContext from '../../../providers/djclient';
5
+ import { NodePage } from '../Loadable';
6
+ import { MemoryRouter, Route, Routes } from 'react-router-dom';
7
+
8
+ describe('<RevisionDiff />', () => {
9
+ const mockDjClient = {
10
+ DataJunctionAPI: {
11
+ revisions: jest.fn(),
12
+ },
13
+ };
14
+
15
+ const mockNodesWithDimension = [
16
+ {
17
+ node_revision_id: 2,
18
+ node_id: 2,
19
+ type: 'source',
20
+ name: 'default.repair_order_details',
21
+ display_name: 'Default: Repair Order Details',
22
+ version: 'v1.0',
23
+ status: 'valid',
24
+ mode: 'published',
25
+ catalog: {
26
+ id: 1,
27
+ uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
28
+ created_at: '2023-08-21T16:48:51.146121+00:00',
29
+ updated_at: '2023-08-21T16:48:51.146122+00:00',
30
+ extra_params: {},
31
+ name: 'warehouse',
32
+ },
33
+ schema_: 'roads',
34
+ table: 'repair_order_details',
35
+ description: 'Details on repair orders',
36
+ query: null,
37
+ availability: null,
38
+ columns: [
39
+ {
40
+ name: 'repair_order_id',
41
+ type: 'int',
42
+ attributes: [],
43
+ dimension: {
44
+ name: 'default.repair_order',
45
+ },
46
+ },
47
+ {
48
+ name: 'repair_type_id',
49
+ type: 'int',
50
+ attributes: [],
51
+ dimension: null,
52
+ },
53
+ {
54
+ name: 'price',
55
+ type: 'float',
56
+ attributes: [],
57
+ dimension: null,
58
+ },
59
+ {
60
+ name: 'quantity',
61
+ type: 'int',
62
+ attributes: [],
63
+ dimension: null,
64
+ },
65
+ {
66
+ name: 'discount',
67
+ type: 'float',
68
+ attributes: [],
69
+ dimension: null,
70
+ },
71
+ ],
72
+ updated_at: '2023-08-21T16:48:52.981201+00:00',
73
+ materializations: [],
74
+ parents: [],
75
+ },
76
+ {
77
+ node_revision_id: 1,
78
+ node_id: 1,
79
+ type: 'source',
80
+ name: 'default.repair_orders',
81
+ display_name: 'Default: Repair Orders',
82
+ version: 'v1.0',
83
+ status: 'valid',
84
+ mode: 'published',
85
+ catalog: {
86
+ id: 1,
87
+ uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
88
+ created_at: '2023-08-21T16:48:51.146121+00:00',
89
+ updated_at: '2023-08-21T16:48:51.146122+00:00',
90
+ extra_params: {},
91
+ name: 'warehouse',
92
+ },
93
+ schema_: 'roads',
94
+ table: 'repair_orders',
95
+ description: 'Repair orders',
96
+ query: null,
97
+ availability: null,
98
+ columns: [
99
+ {
100
+ name: 'repair_order_id',
101
+ type: 'int',
102
+ attributes: [],
103
+ dimension: {
104
+ name: 'default.repair_order',
105
+ },
106
+ },
107
+ {
108
+ name: 'municipality_id',
109
+ type: 'string',
110
+ attributes: [],
111
+ dimension: null,
112
+ },
113
+ {
114
+ name: 'hard_hat_id',
115
+ type: 'int',
116
+ attributes: [],
117
+ dimension: null,
118
+ },
119
+ {
120
+ name: 'order_date',
121
+ type: 'date',
122
+ attributes: [],
123
+ dimension: null,
124
+ },
125
+ {
126
+ name: 'required_date',
127
+ type: 'date',
128
+ attributes: [],
129
+ dimension: null,
130
+ },
131
+ {
132
+ name: 'dispatched_date',
133
+ type: 'date',
134
+ attributes: [],
135
+ dimension: null,
136
+ },
137
+ {
138
+ name: 'dispatcher_id',
139
+ type: 'int',
140
+ attributes: [],
141
+ dimension: null,
142
+ },
143
+ ],
144
+ updated_at: '2023-08-21T16:48:52.880498+00:00',
145
+ materializations: [],
146
+ parents: [],
147
+ },
148
+ ];
149
+
150
+ beforeEach(() => {
151
+ // Reset the mocks before each test
152
+ mockDjClient.DataJunctionAPI.revisions.mockReset();
153
+ });
154
+
155
+ it('renders revision diff', async () => {
156
+ mockDjClient.DataJunctionAPI.revisions.mockReturnValue(
157
+ mockNodesWithDimension,
158
+ );
159
+ const element = (
160
+ <DJClientContext.Provider value={mockDjClient}>
161
+ <RevisionDiff />
162
+ </DJClientContext.Provider>
163
+ );
164
+ const { container } = render(
165
+ <MemoryRouter
166
+ initialEntries={['/nodes/default.repair_orders_cube/revisions/v1.0']}
167
+ >
168
+ <Routes>
169
+ <Route path="nodes/:name/revisions/:revision" element={element} />
170
+ </Routes>
171
+ </MemoryRouter>,
172
+ );
173
+ await waitFor(() => {
174
+ expect(mockDjClient.DataJunctionAPI.revisions).toHaveBeenCalledWith(
175
+ 'default.repair_orders_cube',
176
+ );
177
+ });
178
+ });
179
+ });
@@ -1,105 +1,5 @@
1
1
  // Jest Snapshot v1, https://goo.gl/fbAQLP
2
2
 
3
- exports[`<NodePage /> renders the NodeHistory tab correctly 1`] = `
4
- <table
5
- aria-label="Revisions"
6
- class="card-inner-table table"
7
- >
8
- <thead
9
- class="fs-7 fw-bold text-gray-400 border-bottom-0"
10
- >
11
- <tr>
12
- <th
13
- class="text-start"
14
- >
15
- Version
16
- </th>
17
- <th>
18
- Display Name
19
- </th>
20
- <th>
21
- Description
22
- </th>
23
- <th>
24
- Query
25
- </th>
26
- <th>
27
- Tags
28
- </th>
29
- </tr>
30
- </thead>
31
- <tbody>
32
- <tr>
33
- <td
34
- class="text-start"
35
- >
36
- <span
37
- class="badge node_type__source"
38
- >
39
- v1.0
40
- </span>
41
- </td>
42
- <td>
43
- Default: Avg Repair Price
44
- </td>
45
- <td>
46
- Average repair price
47
- </td>
48
- <td>
49
- <pre
50
- style="display: block; overflow-x: auto; padding: 2rem; background: rgb(238, 238, 238); color: black;"
51
- >
52
- <code
53
- class="language-sql"
54
- style="white-space: pre-wrap;"
55
- >
56
- <span>
57
- <span
58
- style="color: rgb(0, 153, 153);"
59
- >
60
- SELECT
61
- </span>
62
- <span>
63
-
64
- </span>
65
- <span
66
- class="hljs-built_in"
67
- >
68
- avg
69
- </span>
70
- <span>
71
- (price) default_DOT_avg_repair_price
72
-
73
- </span>
74
- </span>
75
- <span>
76
- <span>
77
-
78
- </span>
79
- <span
80
- style="color: rgb(0, 153, 153);"
81
- >
82
- FROM
83
- </span>
84
- <span>
85
- default.repair_order_details
86
-
87
- </span>
88
- </span>
89
- <span>
90
-
91
-
92
- </span>
93
- <span />
94
- </code>
95
- </pre>
96
- </td>
97
- <td />
98
- </tr>
99
- </tbody>
100
- </table>
101
- `;
102
-
103
3
  exports[`<NodePage /> renders the NodeInfo tab correctly for a metric node 1`] = `
104
4
  HTMLCollection [
105
5
  <code
@@ -49,6 +49,57 @@ describe('DataJunctionAPI', () => {
49
49
  expect(nodeData.primary_key).toEqual([]);
50
50
  });
51
51
 
52
+ it('calls nodeDetails correctly', async () => {
53
+ fetch.mockResponseOnce(JSON.stringify([mocks.mockMetricNode]));
54
+ const nodeData = await DataJunctionAPI.nodeDetails();
55
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/details/`, {
56
+ credentials: 'include',
57
+ });
58
+ expect(nodeData).toEqual([mocks.mockMetricNode]);
59
+ });
60
+
61
+ it('calls validate correctly', async () => {
62
+ fetch.mockResponseOnce(JSON.stringify([mocks.mockMetricNode]));
63
+ await DataJunctionAPI.validateNode(
64
+ 'metric',
65
+ 'default.num_repair_orders',
66
+ 'aa',
67
+ 'desc',
68
+ 'select 1',
69
+ );
70
+ expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/nodes/validate`, {
71
+ credentials: 'include',
72
+ body: JSON.stringify({
73
+ name: 'default.num_repair_orders',
74
+ display_name: 'aa',
75
+ description: 'desc',
76
+ query: 'select 1',
77
+ type: 'metric',
78
+ mode: 'published',
79
+ }),
80
+
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ },
84
+ method: 'POST',
85
+ });
86
+ });
87
+
88
+ it('calls registerTable correctly', async () => {
89
+ fetch.mockResponseOnce(JSON.stringify([mocks.mockMetricNode]));
90
+ await DataJunctionAPI.registerTable('default', 'xyz', 'abc');
91
+ expect(fetch).toHaveBeenCalledWith(
92
+ `${DJ_URL}/register/table/default/xyz/abc`,
93
+ {
94
+ credentials: 'include',
95
+ headers: {
96
+ 'Content-Type': 'application/json',
97
+ },
98
+ method: 'POST',
99
+ },
100
+ );
101
+ });
102
+
52
103
  it('node with errors are handled', async () => {
53
104
  const mockNotFound = { message: 'node not found' };
54
105
  fetch.mockResponseOnce(JSON.stringify(mockNotFound));
package/src/index.tsx CHANGED
@@ -15,6 +15,7 @@ import FontFaceObserver from 'fontfaceobserver';
15
15
  // Use consistent styling
16
16
  import 'sanitize.css/sanitize.css';
17
17
  import './styles/index.css';
18
+ import 'react-diff-view/style/index.css';
18
19
 
19
20
  import { App } from './app';
20
21
 
@@ -408,6 +408,53 @@ export const mocks = {
408
408
  },
409
409
  created_at: '2023-08-21T16:48:56.950482+00:00',
410
410
  },
411
+ {
412
+ id: 7,
413
+ entity_type: 'backfill',
414
+ entity_name: 'default.avg_repair_price',
415
+ node: 'default.avg_repair_price',
416
+ activity_type: 'create',
417
+ user: null,
418
+ pre: { status: 'valid' },
419
+ post: { status: 'invalid' },
420
+ details: {
421
+ materialization: 'druid_metrics_cube__incremental_time__xyz',
422
+ partition: {
423
+ column_name: 'xyz.abc',
424
+ values: null,
425
+ range: ['20240201', '20240301'],
426
+ },
427
+ },
428
+ created_at: '2023-08-21T16:48:56.950482+00:00',
429
+ },
430
+ {
431
+ id: 8,
432
+ entity_type: 'node',
433
+ entity_name: 'default.avg_repair_price',
434
+ node: 'default.avg_repair_price',
435
+ activity_type: 'tag',
436
+ user: null,
437
+ pre: {},
438
+ post: {},
439
+ details: {
440
+ tags: ['a', 'b'],
441
+ },
442
+ created_at: '2023-08-21T16:48:56.950482+00:00',
443
+ },
444
+ {
445
+ id: 9,
446
+ entity_type: 'link',
447
+ entity_name: 'default.avg_repair_price',
448
+ node: 'default.avg_repair_price',
449
+ activity_type: 'create',
450
+ user: null,
451
+ pre: {},
452
+ post: {},
453
+ details: {
454
+ dimension: 'abcde',
455
+ },
456
+ created_at: '2023-08-21T16:48:56.950482+00:00',
457
+ },
411
458
  ],
412
459
  nodeMaterializations: [
413
460
  {
@@ -1113,3 +1113,92 @@ pre {
1113
1113
  font-size: 100%;
1114
1114
  margin-bottom: 5px;
1115
1115
  }
1116
+
1117
+ .history {
1118
+ padding: 20px;
1119
+ list-style: none;
1120
+ border-top-left-radius: 6px;
1121
+ border-top-right-radius: 6px;
1122
+ border-bottom-width: 1px;
1123
+ border-bottom-style: solid;
1124
+ border-color: #748f7c75;
1125
+ display: grid;
1126
+
1127
+ position: relative;
1128
+ min-height: 2rem;
1129
+ font-size: 14px;
1130
+ grid-template-rows: repeat(3, auto);
1131
+ grid-template-areas:
1132
+ 'description description details'
1133
+ 'description description details'
1134
+ 'metadata metadata details';
1135
+ grid-template-columns: min-content minmax(30%, 1fr);
1136
+ gap: 4px;
1137
+ }
1138
+
1139
+ .history-border {
1140
+ display: block;
1141
+ list-style-type: disc;
1142
+ padding-inline-start: 0;
1143
+ unicode-bidi: isolate;
1144
+ border-radius: 6px !important;
1145
+ border: 1px solid #748f7c75;
1146
+ margin-block-start: 1em;
1147
+ margin-block-end: 1em;
1148
+ margin-inline-start: 0;
1149
+ margin-inline-end: 0;
1150
+ }
1151
+ .history-left {
1152
+ grid-area: description;
1153
+ -webkit-box-flex: 2;
1154
+ flex-grow: 2;
1155
+ display: inline;
1156
+ font-size: 16px;
1157
+ }
1158
+ .history-small {
1159
+ display: flex;
1160
+ flex-direction: row;
1161
+ -webkit-box-align: center;
1162
+ align-items: center;
1163
+ gap: 5px;
1164
+ padding-right: 8px;
1165
+ -webkit-box-flex: 1;
1166
+ flex-grow: 1;
1167
+ overflow: hidden;
1168
+ grid-area: metadata;
1169
+ font-size: 12px;
1170
+ }
1171
+ .history-right {
1172
+ display: flex;
1173
+ flex-flow: wrap;
1174
+ padding-top: 10px;
1175
+ gap: 19px;
1176
+ grid-area: details;
1177
+ -webkit-box-flex: 2;
1178
+ }
1179
+
1180
+ .version {
1181
+ color: #055d20;
1182
+ font-weight: bold;
1183
+ font-size: 13px;
1184
+ border: solid #055d20 1px;
1185
+ background-color: #ccf7e545;
1186
+ margin: 0.25rem;
1187
+ }
1188
+
1189
+ .highlight-svg:hover {
1190
+ filter: invert(56%) sepia(68%) saturate(4415%) hue-rotate(162deg)
1191
+ brightness(96%) contrast(101%);
1192
+ text-decoration: none;
1193
+ }
1194
+ .no-change {
1195
+ padding: 10px;
1196
+ display: inline-block;
1197
+ margin-top: -25px;
1198
+ }
1199
+
1200
+ .no-change-banner {
1201
+ color: #055d20;
1202
+ text-transform: uppercase;
1203
+ padding-left: 10px;
1204
+ }
@@ -1,55 +0,0 @@
1
- import DJClientContext from '../providers/djclient';
2
- import * as React from 'react';
3
- import DeleteIcon from '../icons/DeleteIcon';
4
- import { Form, Formik } from 'formik';
5
- import { useContext } from 'react';
6
- import { displayMessageAfterSubmit } from '../../utils/form';
7
-
8
- export default function DeleteNode({ nodeName }) {
9
- const djClient = useContext(DJClientContext).DataJunctionAPI;
10
- const deleteNode = async (values, { setSubmitting, setStatus }) => {
11
- const { status, json } = await djClient.deactivate(values.nodeName);
12
- if (status === 200 || status === 201 || status === 204) {
13
- setStatus({
14
- success: <>Successfully deleted node {values.nodeName}</>,
15
- });
16
- } else {
17
- setStatus({
18
- failure: `${json.message}`,
19
- });
20
- }
21
- setSubmitting(false);
22
- };
23
-
24
- const initialValues = {
25
- nodeName: nodeName,
26
- };
27
-
28
- return (
29
- <Formik initialValues={initialValues} onSubmit={deleteNode}>
30
- {function Render({ isSubmitting, status, setFieldValue }) {
31
- return (
32
- <Form className="deleteNode">
33
- {displayMessageAfterSubmit(status)}
34
- {
35
- <>
36
- <button
37
- type="submit"
38
- disabled={isSubmitting}
39
- style={{
40
- marginLeft: 0,
41
- all: 'unset',
42
- color: '#005c72',
43
- cursor: 'pointer',
44
- }}
45
- >
46
- <DeleteIcon />
47
- </button>
48
- </>
49
- }
50
- </Form>
51
- );
52
- }}
53
- </Formik>
54
- );
55
- }
@@ -1,53 +0,0 @@
1
- import React from 'react';
2
- import { screen, waitFor } from '@testing-library/react';
3
- import fetchMock from 'jest-fetch-mock';
4
- import userEvent from '@testing-library/user-event';
5
- import { render } from '../../../setupTests';
6
- import DJClientContext from '../../providers/djclient';
7
- import DeleteNode from '../DeleteNode';
8
-
9
- describe('<DeleteNode />', () => {
10
- beforeEach(() => {
11
- fetchMock.resetMocks();
12
- jest.clearAllMocks();
13
- window.scrollTo = jest.fn();
14
- });
15
-
16
- const renderElement = djClient => {
17
- return render(
18
- <DJClientContext.Provider value={djClient}>
19
- <DeleteNode nodeName="default.hard_hat" />
20
- </DJClientContext.Provider>,
21
- );
22
- };
23
-
24
- const initializeMockDJClient = () => {
25
- return {
26
- DataJunctionAPI: {
27
- deactivate: jest.fn(),
28
- },
29
- };
30
- };
31
-
32
- it('deletes a node when clicked', async () => {
33
- const mockDjClient = initializeMockDJClient();
34
- mockDjClient.DataJunctionAPI.deactivate.mockReturnValue({
35
- status: 204,
36
- json: { name: 'source.warehouse.schema.some_table' },
37
- });
38
-
39
- renderElement(mockDjClient);
40
-
41
- await userEvent.click(screen.getByRole('button'));
42
-
43
- await waitFor(() => {
44
- expect(mockDjClient.DataJunctionAPI.deactivate).toBeCalled();
45
- expect(mockDjClient.DataJunctionAPI.deactivate).toBeCalledWith(
46
- 'default.hard_hat',
47
- );
48
- });
49
- expect(
50
- screen.getByText('Successfully deleted node default.hard_hat'),
51
- ).toBeInTheDocument();
52
- }, 60000);
53
- });