datajunction-ui 0.0.1-a46 → 0.0.1-a46.dev3
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/Makefile +0 -5
- package/package.json +2 -2
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +51 -46
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +24 -33
- package/src/app/pages/NodePage/MaterializationConfigField.jsx +31 -38
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +110 -179
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +3 -13
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +5 -12
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +191 -276
- package/src/app/services/DJService.js +25 -26
- package/src/app/services/__tests__/DJService.test.jsx +21 -37
- package/src/mocks/mockNodes.jsx +7 -61
- package/src/styles/index.css +11 -113
- package/src/app/components/NodeMaterializationDelete.jsx +0 -80
- package/src/app/pages/NodePage/PartitionValueForm.jsx +0 -60
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
|
+
import ClientCodePopover from './ClientCodePopover';
|
|
2
3
|
import TableIcon from '../../icons/TableIcon';
|
|
3
4
|
import AddMaterializationPopover from './AddMaterializationPopover';
|
|
4
5
|
import * as React from 'react';
|
|
5
6
|
import AddBackfillPopover from './AddBackfillPopover';
|
|
6
|
-
import { labelize } from '../../../utils/form';
|
|
7
|
-
import NodeMaterializationDelete from '../../components/NodeMaterializationDelete';
|
|
8
7
|
|
|
9
8
|
const cronstrue = require('cronstrue');
|
|
10
9
|
|
|
@@ -19,6 +18,17 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
19
18
|
};
|
|
20
19
|
fetchData().catch(console.error);
|
|
21
20
|
}, [djClient, node]);
|
|
21
|
+
//
|
|
22
|
+
// const rangePartition = partition => {
|
|
23
|
+
// return (
|
|
24
|
+
// <div>
|
|
25
|
+
// <span className="badge partition_value">
|
|
26
|
+
// <span className="badge partition_value">{partition.range[0]}</span>to
|
|
27
|
+
// <span className="badge partition_value">{partition.range[1]}</span>
|
|
28
|
+
// </span>
|
|
29
|
+
// </div>
|
|
30
|
+
// );
|
|
31
|
+
// };
|
|
22
32
|
|
|
23
33
|
const partitionColumnsMap = node
|
|
24
34
|
? Object.fromEntries(
|
|
@@ -37,195 +47,116 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
37
47
|
|
|
38
48
|
const materializationRows = materializations => {
|
|
39
49
|
return materializations.map(materialization => (
|
|
40
|
-
|
|
41
|
-
<
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
50
|
+
<tr key={materialization.name}>
|
|
51
|
+
<td className="text-start node_name">
|
|
52
|
+
<span className={`badge cron`}>{materialization.schedule}</span>
|
|
53
|
+
<div className={`cron-description`}>{cron(materialization)} </div>
|
|
54
|
+
</td>
|
|
55
|
+
<td>
|
|
56
|
+
{materialization.job?.replace('MaterializationJob', '').toUpperCase()}
|
|
57
|
+
</td>
|
|
58
|
+
<td>{materialization.strategy?.toUpperCase()}</td>
|
|
59
|
+
<td>
|
|
60
|
+
{node.columns
|
|
61
|
+
.filter(col => col.partition !== null)
|
|
62
|
+
.map(column => {
|
|
63
|
+
return (
|
|
64
|
+
<div className="partition__full" key={column.name}>
|
|
65
|
+
<div className="partition__header">{column.display_name}</div>
|
|
66
|
+
<div className="partition__body">
|
|
67
|
+
<code>{column.name}</code>
|
|
68
|
+
<span className="badge partition_value">
|
|
69
|
+
{column.partition.type_}
|
|
70
|
+
</span>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
})}
|
|
75
|
+
</td>
|
|
76
|
+
<td>
|
|
77
|
+
{materialization.output_tables.map(table => (
|
|
78
|
+
<div className={`table__full`} key={table}>
|
|
79
|
+
<div className="table__header">
|
|
80
|
+
<TableIcon />{' '}
|
|
81
|
+
<span className={`entity-info`}>
|
|
82
|
+
{table.split('.')[0] + '.' + table.split('.')[1]}
|
|
83
|
+
</span>
|
|
84
|
+
</div>
|
|
85
|
+
<div className={`table__body upstream_tables`}>
|
|
86
|
+
{table.split('.')[2]}
|
|
87
|
+
</div>
|
|
66
88
|
</div>
|
|
67
|
-
|
|
68
|
-
</
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
|
|
72
|
-
<
|
|
73
|
-
<div
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
{table.split('.')[0] + '.' + table.split('.')[1]}
|
|
80
|
-
</span>
|
|
81
|
-
</div>
|
|
82
|
-
<div className={`table__body upstream_tables`}>
|
|
83
|
-
{table.split('.')[2]}
|
|
84
|
-
</div>
|
|
89
|
+
))}
|
|
90
|
+
</td>
|
|
91
|
+
{materializations[0].strategy === 'incremental_time' ? (
|
|
92
|
+
<td>
|
|
93
|
+
{materialization.backfills.map(backfill => (
|
|
94
|
+
<a href={backfill.urls[0]} className="partitionLink">
|
|
95
|
+
<div
|
|
96
|
+
className="partition__full"
|
|
97
|
+
key={backfill.spec.column_name}
|
|
98
|
+
>
|
|
99
|
+
<div className="partition__header">
|
|
100
|
+
{partitionColumnsMap[backfill.spec.column_name]}
|
|
85
101
|
</div>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
</li>
|
|
115
|
-
</ul>
|
|
116
|
-
</div>
|
|
117
|
-
|
|
118
|
-
<div style={{ display: 'inline-flex' }}>
|
|
119
|
-
<ul className="backfills">
|
|
120
|
-
<li className="backfill">
|
|
121
|
-
<details open>
|
|
122
|
-
<summary>
|
|
123
|
-
<span className="backfills_header">Backfills</span>{' '}
|
|
124
|
-
</summary>
|
|
125
|
-
{materialization.strategy === 'incremental_time' ? (
|
|
126
|
-
<ul>
|
|
127
|
-
<li>
|
|
128
|
-
<AddBackfillPopover
|
|
129
|
-
node={node}
|
|
130
|
-
materialization={materialization}
|
|
131
|
-
/>
|
|
132
|
-
</li>
|
|
133
|
-
{materialization.backfills.map(backfill => (
|
|
134
|
-
<li className="backfill">
|
|
135
|
-
<div className="partitionLink">
|
|
136
|
-
<a href={backfill.urls[0]}>
|
|
137
|
-
{backfill.spec.map(partition => {
|
|
138
|
-
const partitionBody =
|
|
139
|
-
'range' in partition &&
|
|
140
|
-
partition['range'] !== null ? (
|
|
141
|
-
<>
|
|
142
|
-
<span className="badge partition_value">
|
|
143
|
-
{partition.range[0]}
|
|
144
|
-
</span>
|
|
145
|
-
to
|
|
146
|
-
<span className="badge partition_value">
|
|
147
|
-
{partition.range[1]}
|
|
148
|
-
</span>
|
|
149
|
-
</>
|
|
150
|
-
) : (
|
|
151
|
-
<span className="badge partition_value">
|
|
152
|
-
{partition.values.join(', ')}
|
|
153
|
-
</span>
|
|
154
|
-
);
|
|
155
|
-
return (
|
|
156
|
-
<>
|
|
157
|
-
<div>
|
|
158
|
-
{
|
|
159
|
-
partitionColumnsMap[
|
|
160
|
-
partition.column_name.replaceAll(
|
|
161
|
-
'_DOT_',
|
|
162
|
-
'.',
|
|
163
|
-
)
|
|
164
|
-
]
|
|
165
|
-
}{' '}
|
|
166
|
-
{partitionBody}
|
|
167
|
-
</div>
|
|
168
|
-
</>
|
|
169
|
-
);
|
|
170
|
-
})}
|
|
171
|
-
</a>
|
|
172
|
-
</div>
|
|
173
|
-
</li>
|
|
174
|
-
))}
|
|
175
|
-
</ul>
|
|
176
|
-
) : (
|
|
177
|
-
<ul>
|
|
178
|
-
<li>N/A</li>
|
|
179
|
-
</ul>
|
|
180
|
-
)}
|
|
181
|
-
</details>
|
|
182
|
-
</li>
|
|
183
|
-
</ul>
|
|
184
|
-
</div>
|
|
185
|
-
<div className="td">
|
|
186
|
-
<ul className="backfills">
|
|
187
|
-
<li className="backfill">
|
|
188
|
-
<div className="backfills_header">Partitions</div>{' '}
|
|
189
|
-
<ul>
|
|
190
|
-
{node.columns
|
|
191
|
-
.filter(col => col.partition !== null)
|
|
192
|
-
.map(column => {
|
|
193
|
-
return (
|
|
194
|
-
<li>
|
|
195
|
-
<div className="partitionLink">
|
|
196
|
-
{column.display_name}
|
|
197
|
-
<span className="badge partition_value">
|
|
198
|
-
{column.partition.type_}
|
|
199
|
-
</span>
|
|
200
|
-
</div>
|
|
201
|
-
</li>
|
|
202
|
-
);
|
|
203
|
-
})}
|
|
204
|
-
</ul>
|
|
205
|
-
</li>
|
|
206
|
-
</ul>
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
209
|
-
</>
|
|
102
|
+
<div className="partition__body">
|
|
103
|
+
<span className="badge partition_value">
|
|
104
|
+
{backfill.spec.range[0]}
|
|
105
|
+
</span>
|
|
106
|
+
to
|
|
107
|
+
<span className="badge partition_value">
|
|
108
|
+
{backfill.spec.range[1]}
|
|
109
|
+
</span>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</a>
|
|
113
|
+
))}
|
|
114
|
+
<AddBackfillPopover node={node} materialization={materialization} />
|
|
115
|
+
</td>
|
|
116
|
+
) : (
|
|
117
|
+
<></>
|
|
118
|
+
)}
|
|
119
|
+
<td>
|
|
120
|
+
{materialization.urls.map((url, idx) => (
|
|
121
|
+
<a href={url} key={`url-${idx}`}>
|
|
122
|
+
[{idx + 1}]
|
|
123
|
+
</a>
|
|
124
|
+
))}
|
|
125
|
+
</td>
|
|
126
|
+
<td>
|
|
127
|
+
<ClientCodePopover code={materialization.clientCode} />
|
|
128
|
+
</td>
|
|
129
|
+
</tr>
|
|
210
130
|
));
|
|
211
131
|
};
|
|
212
132
|
return (
|
|
213
133
|
<>
|
|
214
|
-
<div
|
|
215
|
-
className="table-vertical"
|
|
216
|
-
role="table"
|
|
217
|
-
aria-label="Materializations"
|
|
218
|
-
>
|
|
134
|
+
<div className="table-vertical">
|
|
219
135
|
<div>
|
|
220
136
|
<h2>Materializations</h2>
|
|
221
137
|
{node ? <AddMaterializationPopover node={node} /> : <></>}
|
|
222
138
|
{materializations.length > 0 ? (
|
|
223
|
-
<
|
|
139
|
+
<table
|
|
224
140
|
className="card-inner-table table"
|
|
225
141
|
aria-label="Materializations"
|
|
226
142
|
aria-hidden="false"
|
|
227
143
|
>
|
|
228
|
-
<
|
|
144
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
145
|
+
<tr>
|
|
146
|
+
<th className="text-start">Schedule</th>
|
|
147
|
+
<th>Job Type</th>
|
|
148
|
+
<th>Strategy</th>
|
|
149
|
+
<th>Partitions</th>
|
|
150
|
+
<th>Intended Output Tables</th>
|
|
151
|
+
{materializations[0].strategy === 'incremental_time' ? (
|
|
152
|
+
<th>Backfills</th>
|
|
153
|
+
) : (
|
|
154
|
+
<></>
|
|
155
|
+
)}
|
|
156
|
+
<th>URLs</th>
|
|
157
|
+
</tr>
|
|
158
|
+
</thead>
|
|
159
|
+
<tbody>
|
|
229
160
|
{materializationRows(
|
|
230
161
|
materializations.filter(
|
|
231
162
|
materialization =>
|
|
@@ -235,8 +166,8 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
235
166
|
),
|
|
236
167
|
),
|
|
237
168
|
)}
|
|
238
|
-
</
|
|
239
|
-
</
|
|
169
|
+
</tbody>
|
|
170
|
+
</table>
|
|
240
171
|
) : (
|
|
241
172
|
<div className="message alert" style={{ marginTop: '10px' }}>
|
|
242
173
|
No materialization workflows configured for this node.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { render, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
|
3
|
+
import EditColumnPopover from '../EditColumnPopover';
|
|
3
4
|
import DJClientContext from '../../../providers/djclient';
|
|
4
5
|
import AddBackfillPopover from '../AddBackfillPopover';
|
|
5
6
|
import { mocks } from '../../../../mocks/mockNodes';
|
|
@@ -10,17 +11,6 @@ const mockDjClient = {
|
|
|
10
11
|
},
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
let reloadMock = jest.fn();
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
delete window.location;
|
|
17
|
-
window.location = { reload: reloadMock };
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
afterEach(() => {
|
|
21
|
-
reloadMock.mockClear();
|
|
22
|
-
});
|
|
23
|
-
|
|
24
14
|
describe('<AddBackfillPopover />', () => {
|
|
25
15
|
it('renders correctly and handles form submission', async () => {
|
|
26
16
|
// Mock onSubmit function
|
|
@@ -35,7 +25,7 @@ describe('<AddBackfillPopover />', () => {
|
|
|
35
25
|
const { getByLabelText, getByText } = render(
|
|
36
26
|
<DJClientContext.Provider value={mockDjClient}>
|
|
37
27
|
<AddBackfillPopover
|
|
38
|
-
node={mocks.
|
|
28
|
+
node={mocks.mockMetricNode}
|
|
39
29
|
materialization={mocks.nodeMaterializations}
|
|
40
30
|
onSubmit={onSubmitMock}
|
|
41
31
|
/>
|
|
@@ -29,7 +29,6 @@ describe('<NodePage />', () => {
|
|
|
29
29
|
history: jest.fn(),
|
|
30
30
|
revisions: jest.fn(),
|
|
31
31
|
materializations: jest.fn(),
|
|
32
|
-
materializationInfo: jest.fn(),
|
|
33
32
|
sql: jest.fn(),
|
|
34
33
|
cube: jest.fn(),
|
|
35
34
|
compiledSql: jest.fn(),
|
|
@@ -605,17 +604,13 @@ describe('<NodePage />', () => {
|
|
|
605
604
|
|
|
606
605
|
it('renders the NodeMaterialization tab with materializations correctly', async () => {
|
|
607
606
|
const djClient = mockDJClient();
|
|
608
|
-
djClient.DataJunctionAPI.node.mockReturnValue(mocks.
|
|
609
|
-
|
|
607
|
+
djClient.DataJunctionAPI.node.mockReturnValue(mocks.mockMetricNode);
|
|
608
|
+
djClient.DataJunctionAPI.metric.mockReturnValue(mocks.mockMetricNode);
|
|
610
609
|
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
|
|
611
610
|
djClient.DataJunctionAPI.materializations.mockReturnValue(
|
|
612
611
|
mocks.nodeMaterializations,
|
|
613
612
|
);
|
|
614
613
|
|
|
615
|
-
djClient.DataJunctionAPI.materializationInfo.mockReturnValue(
|
|
616
|
-
mocks.materializationInfo,
|
|
617
|
-
);
|
|
618
|
-
|
|
619
614
|
const element = (
|
|
620
615
|
<DJClientContext.Provider value={djClient}>
|
|
621
616
|
<NodePage />
|
|
@@ -623,9 +618,7 @@ describe('<NodePage />', () => {
|
|
|
623
618
|
);
|
|
624
619
|
render(
|
|
625
620
|
<MemoryRouter
|
|
626
|
-
initialEntries={[
|
|
627
|
-
'/nodes/default.repair_order_transform/materializations',
|
|
628
|
-
]}
|
|
621
|
+
initialEntries={['/nodes/default.num_repair_orders/materializations']}
|
|
629
622
|
>
|
|
630
623
|
<Routes>
|
|
631
624
|
<Route path="nodes/:name/:tab" element={element} />
|
|
@@ -638,10 +631,10 @@ describe('<NodePage />', () => {
|
|
|
638
631
|
screen.getByRole('button', { name: 'Materializations' }),
|
|
639
632
|
);
|
|
640
633
|
expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
|
|
641
|
-
mocks.
|
|
634
|
+
mocks.mockMetricNode.name,
|
|
642
635
|
);
|
|
643
636
|
expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
|
|
644
|
-
mocks.
|
|
637
|
+
mocks.mockMetricNode.name,
|
|
645
638
|
);
|
|
646
639
|
|
|
647
640
|
expect(
|