datajunction-ui 0.0.1-a70.dev1 → 0.0.1-a71

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datajunction-ui",
3
- "version": "0.0.1-a70.dev1",
3
+ "version": "0.0.1a71",
4
4
  "description": "DataJunction Metrics Platform UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
@@ -45,7 +45,10 @@ const Explorer = ({ item = [], current }) => {
45
45
  }}
46
46
  key={index}
47
47
  >
48
- <div className={`${expand ? '' : 'inactive'}`} key={`nested-${index}`}>
48
+ <div
49
+ className={`${expand ? '' : 'inactive'}`}
50
+ key={`nested-${index}`}
51
+ >
49
52
  <Explorer item={item} current={highlight} />
50
53
  </div>
51
54
  </div>
@@ -3,8 +3,11 @@ import Control from './FieldControl';
3
3
 
4
4
  export default function NodeTypeSelect({ onChange }) {
5
5
  return (
6
- <span className="menu-link" style={{ marginLeft: '30px', width: '300px' }}
7
- data-testid="select-node-type">
6
+ <span
7
+ className="menu-link"
8
+ style={{ marginLeft: '30px', width: '300px' }}
9
+ data-testid="select-node-type"
10
+ >
8
11
  <Select
9
12
  name="node_type"
10
13
  isClearable
@@ -15,11 +18,11 @@ export default function NodeTypeSelect({ onChange }) {
15
18
  control: styles => ({ ...styles, backgroundColor: 'white' }),
16
19
  }}
17
20
  options={[
18
- {value: 'source', label: 'Source'},
19
- {value: 'transform', label: 'Transform'},
20
- {value: 'dimension', label: 'Dimension'},
21
- {value: 'metric', label: 'Metric'},
22
- {value: 'cube', label: 'Cube'},
21
+ { value: 'source', label: 'Source' },
22
+ { value: 'transform', label: 'Transform' },
23
+ { value: 'dimension', label: 'Dimension' },
24
+ { value: 'metric', label: 'Metric' },
25
+ { value: 'cube', label: 'Cube' },
23
26
  ]}
24
27
  />
25
28
  </span>
@@ -20,7 +20,11 @@ export default function TagSelect({ onChange }) {
20
20
  }, [djClient]);
21
21
 
22
22
  return (
23
- <span className="menu-link" style={{ marginLeft: '30px', width: '350px' }} data-testid="select-tag">
23
+ <span
24
+ className="menu-link"
25
+ style={{ marginLeft: '30px', width: '350px' }}
26
+ data-testid="select-tag"
27
+ >
24
28
  <Select
25
29
  name="tags"
26
30
  isClearable
@@ -19,7 +19,11 @@ export default function UserSelect({ onChange, currentUser }) {
19
19
  }, [djClient]);
20
20
 
21
21
  return (
22
- <span className="menu-link" style={{ marginLeft: '30px', width: '400px' }} data-testid="select-user">
22
+ <span
23
+ className="menu-link"
24
+ style={{ marginLeft: '30px', width: '400px' }}
25
+ data-testid="select-user"
26
+ >
23
27
  {retrieved ? (
24
28
  <Select
25
29
  name="edited_by"
@@ -38,9 +38,15 @@ describe('NamespacePage', () => {
38
38
 
39
39
  beforeEach(() => {
40
40
  fetch.resetMocks();
41
- mockDjClient.whoami.mockResolvedValue({username: 'dj'});
42
- mockDjClient.users.mockResolvedValue([{username: 'dj'}, {username: 'user1'}]);
43
- mockDjClient.listTags.mockResolvedValue([{name: 'tag1'}, {name: 'tag2'}]);
41
+ mockDjClient.whoami.mockResolvedValue({ username: 'dj' });
42
+ mockDjClient.users.mockResolvedValue([
43
+ { username: 'dj' },
44
+ { username: 'user1' },
45
+ ]);
46
+ mockDjClient.listTags.mockResolvedValue([
47
+ { name: 'tag1' },
48
+ { name: 'tag2' },
49
+ ]);
44
50
  mockDjClient.namespaces.mockResolvedValue([
45
51
  {
46
52
  namespace: 'common.one',
@@ -82,45 +88,43 @@ describe('NamespacePage', () => {
82
88
  type: 'transform',
83
89
  mode: 'active',
84
90
  updated_at: new Date(),
85
- tags: [{name: 'tag1'}],
91
+ tags: [{ name: 'tag1' }],
86
92
  edited_by: ['dj'],
87
93
  },
88
94
  ]);
89
- mockDjClient.listNodesForLanding.mockResolvedValue(
90
- {
91
- "data": {
92
- "findNodesPaginated": {
93
- "pageInfo": {
94
- "hasNextPage": true,
95
- "endCursor": "eyJjcmVhdGVkX2F0IjogIjIwMjQtMDQtMTZUMjM6MjI6MjIuNDQxNjg2KzAwOjAwIiwgImlkIjogNjE0fQ==",
96
- "hasPrevPage": true,
97
- "startCursor": "eyJjcmVhdGVkX2F0IjogIjIwMjQtMTAtMTZUMTY6MDM6MTcuMDgzMjY3KzAwOjAwIiwgImlkIjogMjQwOX0="
95
+ mockDjClient.listNodesForLanding.mockResolvedValue({
96
+ data: {
97
+ findNodesPaginated: {
98
+ pageInfo: {
99
+ hasNextPage: true,
100
+ endCursor:
101
+ 'eyJjcmVhdGVkX2F0IjogIjIwMjQtMDQtMTZUMjM6MjI6MjIuNDQxNjg2KzAwOjAwIiwgImlkIjogNjE0fQ==',
102
+ hasPrevPage: true,
103
+ startCursor:
104
+ 'eyJjcmVhdGVkX2F0IjogIjIwMjQtMTAtMTZUMTY6MDM6MTcuMDgzMjY3KzAwOjAwIiwgImlkIjogMjQwOX0=',
105
+ },
106
+ edges: [
107
+ {
108
+ node: {
109
+ name: 'default.test_node',
110
+ type: 'DIMENSION',
111
+ currentVersion: 'v4.0',
112
+ tags: [],
113
+ editedBy: ['dj'],
114
+ current: {
115
+ displayName: 'Test Node',
116
+ status: 'VALID',
117
+ updatedAt: '2024-10-18T15:15:33.532949+00:00',
118
+ },
119
+ createdBy: {
120
+ username: 'dj',
121
+ },
98
122
  },
99
- "edges": [
100
- {
101
- "node": {
102
- "name": "default.test_node",
103
- "type": "DIMENSION",
104
- "currentVersion": "v4.0",
105
- "tags": [],
106
- "editedBy": [
107
- "dj",
108
- ],
109
- "current": {
110
- "displayName": "Test Node",
111
- "status": "VALID",
112
- "updatedAt": "2024-10-18T15:15:33.532949+00:00"
113
- },
114
- "createdBy": {
115
- "username": "dj"
116
- }
117
- }
118
- },
119
- ]
120
- }
121
- }
122
- }
123
- );
123
+ },
124
+ ],
125
+ },
126
+ },
127
+ });
124
128
  });
125
129
 
126
130
  afterEach(() => {
@@ -142,56 +146,58 @@ describe('NamespacePage', () => {
142
146
  </MemoryRouter>,
143
147
  );
144
148
 
145
- await waitFor(() => {
146
- expect(mockDjClient.listNodesForLanding).toHaveBeenCalled();
147
- expect(screen.getByText('Namespaces')).toBeInTheDocument();
149
+ await waitFor(
150
+ () => {
151
+ expect(mockDjClient.listNodesForLanding).toHaveBeenCalled();
152
+ expect(screen.getByText('Namespaces')).toBeInTheDocument();
148
153
 
149
- // check that it displays namespaces
150
- expect(screen.getByText('common')).toBeInTheDocument();
151
- expect(screen.getByText('one')).toBeInTheDocument();
152
- expect(screen.getByText('fruits')).toBeInTheDocument();
153
- expect(screen.getByText('vegetables')).toBeInTheDocument();
154
+ // check that it displays namespaces
155
+ expect(screen.getByText('common')).toBeInTheDocument();
156
+ expect(screen.getByText('one')).toBeInTheDocument();
157
+ expect(screen.getByText('fruits')).toBeInTheDocument();
158
+ expect(screen.getByText('vegetables')).toBeInTheDocument();
154
159
 
155
- // check that it renders nodes
156
- expect(screen.getByText('Test Node')).toBeInTheDocument();
160
+ // check that it renders nodes
161
+ expect(screen.getByText('Test Node')).toBeInTheDocument();
157
162
 
158
- // check that it sorts nodes
159
- fireEvent.click(screen.getByText('name'));
160
- fireEvent.click(screen.getByText('name'));
161
- fireEvent.click(screen.getByText('display Name'));
163
+ // check that it sorts nodes
164
+ fireEvent.click(screen.getByText('name'));
165
+ fireEvent.click(screen.getByText('name'));
166
+ fireEvent.click(screen.getByText('display Name'));
162
167
 
163
- // paginate
164
- const previousButton = screen.getByText('← Previous');
165
- expect(previousButton).toBeDefined();
166
- fireEvent.click(previousButton);
167
- const nextButton = screen.getByText('Next →');
168
- expect(nextButton).toBeDefined();
169
- fireEvent.click(nextButton);
168
+ // paginate
169
+ const previousButton = screen.getByText('← Previous');
170
+ expect(previousButton).toBeDefined();
171
+ fireEvent.click(previousButton);
172
+ const nextButton = screen.getByText('Next →');
173
+ expect(nextButton).toBeDefined();
174
+ fireEvent.click(nextButton);
170
175
 
171
- // check that we can filter by node type
172
- const selectNodeType = screen.getAllByTestId('select-node-type')[0];
173
- expect(selectNodeType).toBeDefined();
174
- expect(selectNodeType).not.toBeNull();
175
- fireEvent.keyDown(selectNodeType.firstChild, { key: 'ArrowDown' });
176
- fireEvent.click(screen.getByText('Source'));
176
+ // check that we can filter by node type
177
+ const selectNodeType = screen.getAllByTestId('select-node-type')[0];
178
+ expect(selectNodeType).toBeDefined();
179
+ expect(selectNodeType).not.toBeNull();
180
+ fireEvent.keyDown(selectNodeType.firstChild, { key: 'ArrowDown' });
181
+ fireEvent.click(screen.getByText('Source'));
177
182
 
178
- // check that we can filter by tag
179
- const selectTag = screen.getAllByTestId('select-tag')[0];
180
- expect(selectTag).toBeDefined();
181
- expect(selectTag).not.toBeNull();
182
- fireEvent.keyDown(selectTag.firstChild, { key: 'ArrowDown' });
183
+ // check that we can filter by tag
184
+ const selectTag = screen.getAllByTestId('select-tag')[0];
185
+ expect(selectTag).toBeDefined();
186
+ expect(selectTag).not.toBeNull();
187
+ fireEvent.keyDown(selectTag.firstChild, { key: 'ArrowDown' });
183
188
 
184
- // check that we can filter by user
185
- const selectUser = screen.getAllByTestId('select-user')[0];
186
- expect(selectUser).toBeDefined();
187
- expect(selectUser).not.toBeNull();
188
- fireEvent.keyDown(selectUser.firstChild, { key: 'ArrowDown' });
189
+ // check that we can filter by user
190
+ const selectUser = screen.getAllByTestId('select-user')[0];
191
+ expect(selectUser).toBeDefined();
192
+ expect(selectUser).not.toBeNull();
193
+ fireEvent.keyDown(selectUser.firstChild, { key: 'ArrowDown' });
189
194
 
190
- // click to open and close tab
191
- fireEvent.click(screen.getByText('common'));
192
- fireEvent.click(screen.getByText('common'));
193
- },
194
- { timeout: 3000 });
195
+ // click to open and close tab
196
+ fireEvent.click(screen.getByText('common'));
197
+ fireEvent.click(screen.getByText('common'));
198
+ },
199
+ { timeout: 3000 },
200
+ );
195
201
  }, 60000);
196
202
 
197
203
  it('can add new namespace via add namespace popover', async () => {
@@ -57,10 +57,16 @@ export function NamespacePage() {
57
57
  let sortableData = [...Object.values(state.nodes)];
58
58
  if (sortConfig !== null) {
59
59
  sortableData.sort((a, b) => {
60
- if (a[sortConfig.key] < b[sortConfig.key] || a.current[sortConfig.key] < b.current[sortConfig.key]) {
60
+ if (
61
+ a[sortConfig.key] < b[sortConfig.key] ||
62
+ a.current[sortConfig.key] < b.current[sortConfig.key]
63
+ ) {
61
64
  return sortConfig.direction === ASC ? -1 : 1;
62
65
  }
63
- if (a[sortConfig.key] > b[sortConfig.key] || a.current[sortConfig.key] > b.current[sortConfig.key]) {
66
+ if (
67
+ a[sortConfig.key] > b[sortConfig.key] ||
68
+ a.current[sortConfig.key] > b.current[sortConfig.key]
69
+ ) {
64
70
  return sortConfig.direction === ASC ? 1 : -1;
65
71
  }
66
72
  return 0;
@@ -131,19 +137,41 @@ export function NamespacePage() {
131
137
  const nodes = await djClient.listNodesForLanding(
132
138
  namespace,
133
139
  filters.node_type ? [filters.node_type.toUpperCase()] : [],
134
- filters.tags, filters.edited_by, before, after, 50);
140
+ filters.tags,
141
+ filters.edited_by,
142
+ before,
143
+ after,
144
+ 50,
145
+ );
135
146
  console.log('nodes', nodes);
136
147
 
137
148
  setState({
138
149
  namespace: namespace,
139
- nodes: nodes.data ? nodes.data.findNodesPaginated.edges.map(n => n.node) : [],
150
+ nodes: nodes.data
151
+ ? nodes.data.findNodesPaginated.edges.map(n => n.node)
152
+ : [],
140
153
  });
141
154
  if (nodes.data) {
142
- setPrevCursor(nodes.data ? nodes.data.findNodesPaginated.pageInfo.startCursor : '');
143
- setNextCursor(nodes.data ? nodes.data.findNodesPaginated.pageInfo.endCursor : '');
144
- console.log('setting hasPrevPage, ', nodes.data.findNodesPaginated.pageInfo.hasPrevPage);
145
- setHasPrevPage(nodes.data ? nodes.data.findNodesPaginated.pageInfo.hasPrevPage : false);
146
- setHasNextPage(nodes.data ? nodes.data.findNodesPaginated.pageInfo.hasNextPage : false);
155
+ setPrevCursor(
156
+ nodes.data ? nodes.data.findNodesPaginated.pageInfo.startCursor : '',
157
+ );
158
+ setNextCursor(
159
+ nodes.data ? nodes.data.findNodesPaginated.pageInfo.endCursor : '',
160
+ );
161
+ console.log(
162
+ 'setting hasPrevPage, ',
163
+ nodes.data.findNodesPaginated.pageInfo.hasPrevPage,
164
+ );
165
+ setHasPrevPage(
166
+ nodes.data
167
+ ? nodes.data.findNodesPaginated.pageInfo.hasPrevPage
168
+ : false,
169
+ );
170
+ setHasNextPage(
171
+ nodes.data
172
+ ? nodes.data.findNodesPaginated.pageInfo.hasNextPage
173
+ : false,
174
+ );
147
175
  }
148
176
  setRetrieved(true);
149
177
  };
@@ -164,51 +192,64 @@ export function NamespacePage() {
164
192
 
165
193
  const nodesList = retrieved ? (
166
194
  sortedNodes.length > 0 ? (
167
- sortedNodes.map(node => (
168
- <tr key={node.name}>
195
+ sortedNodes.map(node => (
196
+ <tr key={node.name}>
197
+ <td>
198
+ <a href={'/nodes/' + node.name} className="link-table">
199
+ {node.name}
200
+ </a>
201
+ <span
202
+ className="rounded-pill badge bg-secondary-soft"
203
+ style={{ marginLeft: '0.5rem' }}
204
+ >
205
+ {node.currentVersion}
206
+ </span>
207
+ </td>
208
+ <td>
209
+ <a href={'/nodes/' + node.name} className="link-table">
210
+ {node.type !== 'source' ? node.current.displayName : ''}
211
+ </a>
212
+ </td>
213
+ <td>
214
+ <span
215
+ className={
216
+ 'node_type__' + node.type.toLowerCase() + ' badge node_type'
217
+ }
218
+ >
219
+ {node.type}
220
+ </span>
221
+ </td>
222
+ <td>
223
+ <NodeStatus node={node} revalidate={false} />
224
+ </td>
225
+ <td>
226
+ <span className="status">
227
+ {new Date(node.current.updatedAt).toLocaleString('en-us')}
228
+ </span>
229
+ </td>
230
+ <td>
231
+ <NodeListActions nodeName={node?.name} />
232
+ </td>
233
+ </tr>
234
+ ))
235
+ ) : (
236
+ <tr>
169
237
  <td>
170
- <a href={'/nodes/' + node.name} className="link-table">
171
- {node.name}
172
- </a>
173
238
  <span
174
- className="rounded-pill badge bg-secondary-soft"
175
- style={{ marginLeft: '0.5rem' }}
239
+ style={{
240
+ display: 'block',
241
+ marginTop: '2rem',
242
+ marginLeft: '2rem',
243
+ fontSize: '16px',
244
+ }}
176
245
  >
177
- {node.currentVersion}
246
+ There are no nodes in{' '}
247
+ <a href={`/namespaces/${namespace}`}>{namespace}</a> with the above
248
+ filters!
178
249
  </span>
179
250
  </td>
180
- <td>
181
- <a href={'/nodes/' + node.name} className="link-table">
182
- {node.type !== 'source' ? node.current.displayName : ''}
183
- </a>
184
- </td>
185
- <td>
186
- <span className={'node_type__' + node.type.toLowerCase() + ' badge node_type'}>
187
- {node.type}
188
- </span>
189
- </td>
190
- <td>
191
- <NodeStatus node={node} revalidate={false} />
192
- </td>
193
- <td>
194
- <span className="status">
195
- {new Date(node.current.updatedAt).toLocaleString('en-us')}
196
- </span>
197
- </td>
198
- <td>
199
- <NodeListActions nodeName={node?.name} />
200
- </td>
201
251
  </tr>
202
- ))
203
- ) : (
204
- <tr>
205
- <td>
206
- <span style={{ display: 'block', marginTop: '2rem', marginLeft: '2rem', fontSize: '16px' }}>
207
- There are no nodes in <a href={`/namespaces/${namespace}`}>{namespace}</a> with the above filters!
208
- </span>
209
- </td>
210
- </tr>
211
- )
252
+ )
212
253
  ) : (
213
254
  <tr>
214
255
  <td>
@@ -318,8 +359,23 @@ export function NamespacePage() {
318
359
  <tfoot>
319
360
  <tr>
320
361
  <td>
321
- {retrieved && hasPrevPage ? <a onClick={loadPrev} className="previous round pagination">← Previous</a> : ''}
322
- {retrieved && hasNextPage ? <a onClick={loadNext} className="next round pagination">Next →</a> : ''}
362
+ {retrieved && hasPrevPage ? (
363
+ <a
364
+ onClick={loadPrev}
365
+ className="previous round pagination"
366
+ >
367
+ ← Previous
368
+ </a>
369
+ ) : (
370
+ ''
371
+ )}
372
+ {retrieved && hasNextPage ? (
373
+ <a onClick={loadNext} className="next round pagination">
374
+ Next →
375
+ </a>
376
+ ) : (
377
+ ''
378
+ )}
323
379
  </td>
324
380
  </tr>
325
381
  </tfoot>
@@ -256,6 +256,7 @@ export default function NodeMaterializationTab({ node, djClient }) {
256
256
  <th className="text-start">Output Dataset</th>
257
257
  <th>Valid Through</th>
258
258
  <th>Partitions</th>
259
+ <th>Links</th>
259
260
  </tr>
260
261
  </thead>
261
262
  <tbody>
@@ -299,6 +300,21 @@ export default function NodeMaterializationTab({ node, djClient }) {
299
300
  </span>
300
301
  </span>
301
302
  </td>
303
+ <td>
304
+ {node.availability.links !== null ? (
305
+ Object.entries(node.availability.links).map(
306
+ ([key, value]) => (
307
+ <div key={key}>
308
+ <a href={value} target="_blank" rel="noreferrer">
309
+ {key}
310
+ </a>
311
+ </div>
312
+ ),
313
+ )
314
+ ) : (
315
+ <></>
316
+ )}
317
+ </td>
302
318
  </tr>
303
319
  </tbody>
304
320
  </table>
@@ -81,7 +81,9 @@ export default function NodeStatus({ node, revalidate = true }) {
81
81
  <>
82
82
  {revalidate && validation?.errors?.length > 0 ? (
83
83
  displayValidation
84
- ) : validation?.status === 'valid' || node?.status === 'valid' || node?.current?.status === 'VALID' ? (
84
+ ) : validation?.status === 'valid' ||
85
+ node?.status === 'valid' ||
86
+ node?.current?.status === 'VALID' ? (
85
87
  <span
86
88
  className="status__valid status"
87
89
  style={{ alignContent: 'center' }}
@@ -25,7 +25,7 @@ export default function NotebookDownload({ node }) {
25
25
  <>
26
26
  <div
27
27
  className="badge download_notebook"
28
- style={{cursor: 'pointer', backgroundColor: '#ffefd0'}}
28
+ style={{ cursor: 'pointer', backgroundColor: '#ffefd0' }}
29
29
  tabIndex="0"
30
30
  height="45px"
31
31
  onClick={downloadFile}
@@ -0,0 +1,148 @@
1
+ import React from 'react';
2
+ import { render, waitFor, screen } from '@testing-library/react';
3
+ import NodeMaterializationTab from '../NodeMaterializationTab';
4
+
5
+ describe('<NodeMaterializationTab />', () => {
6
+ const mockDjClient = {
7
+ node: jest.fn(),
8
+ materializations: jest.fn(),
9
+ };
10
+
11
+ const mockMaterializations = [
12
+ {
13
+ name: 'mat_one',
14
+ config: {},
15
+ schedule: '@daily',
16
+ job: 'SparkSqlMaterializationJob',
17
+ backfills: [
18
+ {
19
+ spec: [
20
+ {
21
+ column_name: 'date',
22
+ values: ['20200101'],
23
+ range: ['20201010'],
24
+ },
25
+ ],
26
+ urls: ['https://example.com/'],
27
+ },
28
+ ],
29
+ strategy: 'full',
30
+ output_tables: ['table1'],
31
+ urls: ['https://example.com/'],
32
+ },
33
+ ];
34
+
35
+ const mockNode = {
36
+ node_revision_id: 1,
37
+ node_id: 1,
38
+ type: 'source',
39
+ name: 'default.repair_orders',
40
+ display_name: 'Default: Repair Orders',
41
+ version: 'v1.0',
42
+ status: 'valid',
43
+ mode: 'published',
44
+ catalog: {
45
+ id: 1,
46
+ uuid: '0fc18295-e1a2-4c3c-b72a-894725c12488',
47
+ created_at: '2023-08-21T16:48:51.146121+00:00',
48
+ updated_at: '2023-08-21T16:48:51.146122+00:00',
49
+ extra_params: {},
50
+ name: 'warehouse',
51
+ },
52
+ schema_: 'roads',
53
+ table: 'repair_orders',
54
+ description: 'Repair orders',
55
+ query: null,
56
+ availability: {
57
+ catalog: 'default',
58
+ categorical_partitions: [],
59
+ max_temporal_partition: ['2023', '01', '25'],
60
+ min_temporal_partition: ['2022', '01', '01'],
61
+ partitions: [],
62
+ schema_: 'foo',
63
+ table: 'bar',
64
+ temporal_partitions: [],
65
+ valid_through_ts: 1729667463,
66
+ url: 'https://www.table.com',
67
+ links: { dashboard: 'https://www.foobar.com/dashboard' },
68
+ },
69
+ columns: [
70
+ {
71
+ name: 'repair_order_id',
72
+ type: 'int',
73
+ attributes: [],
74
+ dimension: null,
75
+ partition: {
76
+ type_: 'temporal',
77
+ format: 'YYYYMMDD',
78
+ granularity: 'day',
79
+ },
80
+ },
81
+ {
82
+ name: 'municipality_id',
83
+ type: 'string',
84
+ attributes: [],
85
+ dimension: null,
86
+ partition: null,
87
+ },
88
+ {
89
+ name: 'hard_hat_id',
90
+ type: 'int',
91
+ attributes: [],
92
+ dimension: null,
93
+ partition: null,
94
+ },
95
+ ],
96
+ updated_at: '2023-08-21T16:48:52.880498+00:00',
97
+ materializations: [
98
+ {
99
+ name: 'mat1',
100
+ config: {},
101
+ schedule: 'string',
102
+ job: 'string',
103
+ backfills: [
104
+ {
105
+ spec: [
106
+ {
107
+ column_name: 'string',
108
+ values: ['string'],
109
+ range: ['string'],
110
+ },
111
+ ],
112
+ urls: ['string'],
113
+ },
114
+ ],
115
+ strategy: 'string',
116
+ output_tables: ['string'],
117
+ urls: ['https://example.com/'],
118
+ },
119
+ ],
120
+ parents: [],
121
+ dimension_links: [
122
+ {
123
+ dimension: {
124
+ name: 'default.contractor',
125
+ },
126
+ join_type: 'left',
127
+ join_sql:
128
+ 'default.contractor.contractor_id = default.repair_orders.contractor_id',
129
+ join_cardinality: 'one_to_one',
130
+ role: 'contractor',
131
+ },
132
+ ],
133
+ };
134
+
135
+ beforeEach(() => {
136
+ mockDjClient.materializations.mockReset();
137
+ });
138
+
139
+ it('renders NodeMaterializationTab tab correctly', async () => {
140
+ mockDjClient.materializations.mockReturnValue(mockMaterializations);
141
+
142
+ render(<NodeMaterializationTab node={mockNode} djClient={mockDjClient} />);
143
+ await waitFor(() => {
144
+ const link = screen.getByText('dashboard').closest('a');
145
+ expect(link).toHaveAttribute('href', `https://www.foobar.com/dashboard`);
146
+ });
147
+ });
148
+ });
@@ -72,7 +72,7 @@ export function Root() {
72
72
  <div className="dropdown">
73
73
  <a
74
74
  className="btn btn-link dropdown-toggle"
75
- href="#"
75
+ href="/"
76
76
  id="docsDropdown"
77
77
  role="button"
78
78
  aria-expanded="false"
@@ -6,7 +6,7 @@ const DJ_URL = process.env.REACT_APP_DJ_URL
6
6
 
7
7
  const DJ_GQL = process.env.REACT_APP_DJ_GQL
8
8
  ? process.env.REACT_APP_DJ_GQL
9
- : 'http://localhost:8000/graphql';
9
+ : process.env.REACT_APP_DJ_URL + '/graphql';
10
10
 
11
11
  export const DataJunctionAPI = {
12
12
  listNodesForLanding: async function (
@@ -426,9 +426,12 @@ describe('DataJunctionAPI', () => {
426
426
  const nmspce = 'sampleNamespace';
427
427
  fetch.mockResponseOnce(JSON.stringify({}));
428
428
  await DataJunctionAPI.namespace(nmspce);
429
- expect(fetch).toHaveBeenCalledWith(`${DJ_URL}/namespaces/${nmspce}?edited_by=undefined&with_edited_by=true`, {
430
- credentials: 'include',
431
- });
429
+ expect(fetch).toHaveBeenCalledWith(
430
+ `${DJ_URL}/namespaces/${nmspce}?edited_by=undefined&with_edited_by=true`,
431
+ {
432
+ credentials: 'include',
433
+ },
434
+ );
432
435
  });
433
436
 
434
437
  it('calls sql correctly', async () => {
@@ -489,9 +492,9 @@ describe('DataJunctionAPI', () => {
489
492
  const sampleNode = {
490
493
  name: 'sampleNode',
491
494
  columns: [
492
- { name: 'column1', dimension: { name: 'dimension1' }, },
495
+ { name: 'column1', dimension: { name: 'dimension1' } },
493
496
  { name: 'column2', dimension: null },
494
- { name: 'column3', dimension: { name: 'dimension2' }, },
497
+ { name: 'column3', dimension: { name: 'dimension2' } },
495
498
  ],
496
499
  };
497
500
 
@@ -1073,7 +1076,15 @@ describe('DataJunctionAPI', () => {
1073
1076
  it('calls listNodesForLanding correctly', () => {
1074
1077
  fetch.mockResponseOnce(JSON.stringify({}));
1075
1078
 
1076
- DataJunctionAPI.listNodesForLanding('', ['source'], [], '', null, null, 100);
1079
+ DataJunctionAPI.listNodesForLanding(
1080
+ '',
1081
+ ['source'],
1082
+ [],
1083
+ '',
1084
+ null,
1085
+ null,
1086
+ 100,
1087
+ );
1077
1088
  expect(fetch).toHaveBeenCalledWith(
1078
1089
  `${DJ_URL}/graphql`,
1079
1090
  expect.objectContaining({
@@ -1284,67 +1284,67 @@ pre {
1284
1284
 
1285
1285
  .backfills {
1286
1286
  margin-left: -4rem;
1287
- --spacing : 1.5rem;
1288
- --radius : 10px;
1287
+ --spacing: 1.5rem;
1288
+ --radius: 10px;
1289
1289
  }
1290
1290
 
1291
- .backfills li{
1292
- display : block;
1293
- position : relative;
1294
- padding-left : calc(2 * var(--spacing) - var(--radius) - 2px);
1291
+ .backfills li {
1292
+ display: block;
1293
+ position: relative;
1294
+ padding-left: calc(2 * var(--spacing) - var(--radius) - 2px);
1295
1295
  }
1296
1296
 
1297
- .backfills ul{
1298
- margin-left : calc(var(--radius) - var(--spacing));
1299
- padding-left : 2rem;
1297
+ .backfills ul {
1298
+ margin-left: calc(var(--radius) - var(--spacing));
1299
+ padding-left: 2rem;
1300
1300
  }
1301
1301
 
1302
- .backfills ul li{
1303
- border-left : 2px solid #ddd;
1302
+ .backfills ul li {
1303
+ border-left: 2px solid #ddd;
1304
1304
  }
1305
1305
 
1306
- .backfills ul li:last-child{
1307
- border-color : transparent;
1306
+ .backfills ul li:last-child {
1307
+ border-color: transparent;
1308
1308
  }
1309
1309
 
1310
- .backfills ul li::before{
1311
- content : '';
1312
- display : block;
1313
- position : absolute;
1314
- top : calc(var(--spacing) / -2);
1315
- left : -2px;
1316
- width : calc(var(--spacing) + 2px);
1317
- height : calc(var(--spacing) + 1px);
1318
- border : solid #ddd;
1319
- border-width : 0 0 2px 2px;
1310
+ .backfills ul li::before {
1311
+ content: '';
1312
+ display: block;
1313
+ position: absolute;
1314
+ top: calc(var(--spacing) / -2);
1315
+ left: -2px;
1316
+ width: calc(var(--spacing) + 2px);
1317
+ height: calc(var(--spacing) + 1px);
1318
+ border: solid #ddd;
1319
+ border-width: 0 0 2px 2px;
1320
1320
  }
1321
1321
 
1322
- .backfills summary{
1323
- display : block;
1324
- cursor : pointer;
1322
+ .backfills summary {
1323
+ display: block;
1324
+ cursor: pointer;
1325
1325
  margin-bottom: 10px;
1326
1326
  }
1327
1327
 
1328
1328
  .backfills summary::marker,
1329
- .backfills summary::-webkit-details-marker{
1330
- display : none;
1329
+ .backfills summary::-webkit-details-marker {
1330
+ display: none;
1331
1331
  }
1332
1332
 
1333
- .backfills summary:focus{
1334
- outline : none;
1333
+ .backfills summary:focus {
1334
+ outline: none;
1335
1335
  }
1336
1336
 
1337
- .backfills summary:focus-visible{
1338
- outline : 1px dotted #000;
1337
+ .backfills summary:focus-visible {
1338
+ outline: 1px dotted #000;
1339
1339
  }
1340
1340
 
1341
- .backfills summary::before{
1342
- z-index : 1;
1341
+ .backfills summary::before {
1342
+ z-index: 1;
1343
1343
  /*background : #696 url('expand-collapse.svg') 0 0;*/
1344
1344
  }
1345
1345
 
1346
- .backfills details[open] > summary::before{
1347
- background-position : calc(-2 * var(--radius)) 0;
1346
+ .backfills details[open] > summary::before {
1347
+ background-position: calc(-2 * var(--radius)) 0;
1348
1348
  }
1349
1349
 
1350
1350
  .backfills_header {
@@ -1371,32 +1371,32 @@ pre {
1371
1371
  border-radius: 2px;
1372
1372
  border-style: none;
1373
1373
  padding: 0.4rem;
1374
- font-family: Lato, "sans-serif";
1374
+ font-family: Lato, 'sans-serif';
1375
1375
  font-size: 110%;
1376
1376
  border-right: 16px solid transparent;
1377
1377
  margin-left: 0.4rem;
1378
1378
  }
1379
1379
 
1380
1380
  .backfills summary::marker,
1381
- .backfills summary::-webkit-details-marker{
1382
- display : none;
1381
+ .backfills summary::-webkit-details-marker {
1382
+ display: none;
1383
1383
  }
1384
1384
 
1385
- .backfills summary:focus{
1386
- outline : none;
1385
+ .backfills summary:focus {
1386
+ outline: none;
1387
1387
  }
1388
1388
 
1389
- .backfills summary:focus-visible{
1390
- outline : 1px dotted #000;
1389
+ .backfills summary:focus-visible {
1390
+ outline: 1px dotted #000;
1391
1391
  }
1392
1392
 
1393
- .backfills summary::before{
1394
- z-index : 1;
1393
+ .backfills summary::before {
1394
+ z-index: 1;
1395
1395
  /*background : #696 url('expand-collapse.svg') 0 0;*/
1396
1396
  }
1397
1397
 
1398
- .backfills details[open] > summary::before{
1399
- background-position : calc(-2 * var(--radius)) 0;
1398
+ .backfills details[open] > summary::before {
1399
+ background-position: calc(-2 * var(--radius)) 0;
1400
1400
  }
1401
1401
 
1402
1402
  .backfills_header {
@@ -1445,8 +1445,8 @@ table {
1445
1445
  position: absolute;
1446
1446
  z-index: 1;
1447
1447
 
1448
- top: -5px;
1449
- left: 125%;
1448
+ top: -5px;
1449
+ left: 125%;
1450
1450
  /*bottom: 125%;*/
1451
1451
  /*left: 50%;*/
1452
1452
  margin-left: -60px;
@@ -1455,7 +1455,7 @@ table {
1455
1455
  }
1456
1456
 
1457
1457
  .tooltip .tooltiptext::after {
1458
- content: "";
1458
+ content: '';
1459
1459
  position: absolute;
1460
1460
  top: 100%;
1461
1461
  left: 50%;
@@ -1476,13 +1476,19 @@ table {
1476
1476
  grid-template-rows: 250px 1fr;
1477
1477
  gap: 20px;
1478
1478
  grid-template-areas:
1479
- "left righttop"
1480
- "left rightbottom";
1479
+ 'left righttop'
1480
+ 'left rightbottom';
1481
1481
  margin-top: 1rem;
1482
1482
  }
1483
- .left { grid-area: left; }
1484
- .righttop { grid-area: righttop; }
1485
- .rightbottom { grid-area: rightbottom; }
1483
+ .left {
1484
+ grid-area: left;
1485
+ }
1486
+ .righttop {
1487
+ grid-area: righttop;
1488
+ }
1489
+ .rightbottom {
1490
+ grid-area: rightbottom;
1491
+ }
1486
1492
 
1487
1493
  .queryrunner-query pre {
1488
1494
  border-radius: 0;
@@ -1538,4 +1544,4 @@ table {
1538
1544
  }
1539
1545
  .dropdown:hover .dropdown-menu {
1540
1546
  display: block;
1541
- }
1547
+ }
@@ -1,4 +1,4 @@
1
1
  table {
2
2
  width: 100%;
3
3
  height: min-content;
4
- }
4
+ }