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.
- package/.env +1 -0
- package/package.json +3 -2
- package/src/app/components/Tab.jsx +0 -1
- package/src/app/constants.js +2 -2
- package/src/app/icons/LoadingIcon.jsx +14 -0
- package/src/app/index.tsx +11 -1
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +28 -2
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +44 -9
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormFailed.test.jsx.snap +1 -0
- package/src/app/pages/AddEditNodePage/__tests__/__snapshots__/AddEditNodePageFormSuccess.test.jsx.snap +0 -50
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +2 -0
- package/src/app/pages/AddEditNodePage/index.jsx +60 -6
- package/src/app/pages/AddEditTagPage/Loadable.jsx +16 -0
- package/src/app/pages/AddEditTagPage/__tests__/AddEditTagPage.test.jsx +107 -0
- package/src/app/pages/AddEditTagPage/index.jsx +132 -0
- package/src/app/pages/LoginPage/LoginForm.jsx +124 -0
- package/src/app/pages/LoginPage/SignupForm.jsx +156 -0
- package/src/app/pages/LoginPage/__tests__/index.test.jsx +34 -2
- package/src/app/pages/LoginPage/index.jsx +9 -82
- package/src/app/pages/NamespacePage/index.jsx +5 -0
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
- package/src/app/pages/NodePage/EditColumnPopover.jsx +1 -1
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +0 -1
- package/src/app/pages/NodePage/NodeColumnTab.jsx +102 -25
- package/src/app/pages/NodePage/NodeInfoTab.jsx +33 -23
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +158 -99
- package/src/app/pages/NodePage/PartitionColumnPopover.jsx +153 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +47 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +47 -17
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +101 -100
- package/src/app/pages/RegisterTablePage/__tests__/__snapshots__/RegisterTablePage.test.jsx.snap +1 -0
- package/src/app/pages/Root/index.tsx +1 -1
- package/src/app/pages/TagPage/Loadable.jsx +16 -0
- package/src/app/pages/TagPage/__tests__/TagPage.test.jsx +70 -0
- package/src/app/pages/TagPage/index.jsx +79 -0
- package/src/app/services/DJService.js +166 -1
- package/src/app/services/__tests__/DJService.test.jsx +196 -1
- package/src/mocks/mockNodes.jsx +64 -31
- package/src/styles/dag.css +4 -0
- package/src/styles/index.css +89 -1
- package/src/styles/loading.css +34 -0
- package/src/styles/login.css +17 -3
- package/src/utils/form.jsx +2 -2
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
2
|
import ClientCodePopover from './ClientCodePopover';
|
|
3
3
|
import TableIcon from '../../icons/TableIcon';
|
|
4
|
+
import AddMaterializationPopover from './AddMaterializationPopover';
|
|
5
|
+
import * as React from 'react';
|
|
6
|
+
import AddBackfillPopover from './AddBackfillPopover';
|
|
4
7
|
|
|
5
8
|
const cronstrue = require('cronstrue');
|
|
6
9
|
|
|
@@ -15,18 +18,25 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
15
18
|
};
|
|
16
19
|
fetchData().catch(console.error);
|
|
17
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
|
+
// };
|
|
18
32
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
</div>
|
|
27
|
-
);
|
|
28
|
-
};
|
|
29
|
-
|
|
33
|
+
const partitionColumnsMap = node
|
|
34
|
+
? Object.fromEntries(
|
|
35
|
+
node?.columns
|
|
36
|
+
.filter(col => col.partition !== null)
|
|
37
|
+
.map(col => [col.name, col.display_name]),
|
|
38
|
+
)
|
|
39
|
+
: {};
|
|
30
40
|
const cron = materialization => {
|
|
31
41
|
var parsedCron = '';
|
|
32
42
|
try {
|
|
@@ -39,10 +49,6 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
39
49
|
return materializations.map(materialization => (
|
|
40
50
|
<tr key={materialization.name}>
|
|
41
51
|
<td className="text-start node_name">
|
|
42
|
-
<a href={materialization.urls[0]}>{materialization.name}</a>
|
|
43
|
-
<ClientCodePopover code={materialization.clientCode} />
|
|
44
|
-
</td>
|
|
45
|
-
<td>
|
|
46
52
|
<span className={`badge cron`}>{materialization.schedule}</span>
|
|
47
53
|
<div className={`cron-description`}>{cron(materialization)} </div>
|
|
48
54
|
</td>
|
|
@@ -50,41 +56,24 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
50
56
|
{materialization.engine.name}
|
|
51
57
|
<br />
|
|
52
58
|
{materialization.engine.version}
|
|
59
|
+
<ClientCodePopover code={materialization.clientCode} />
|
|
53
60
|
</td>
|
|
54
61
|
<td>
|
|
55
|
-
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
{node.columns
|
|
63
|
+
.filter(col => col.partition !== null)
|
|
64
|
+
.map(column => {
|
|
65
|
+
return (
|
|
66
|
+
<div className="partition__full" key={column.name}>
|
|
67
|
+
<div className="partition__header">{column.display_name}</div>
|
|
60
68
|
<div className="partition__body">
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
key={`partition-value-${val}`}
|
|
66
|
-
>
|
|
67
|
-
{val}
|
|
68
|
-
</span>
|
|
69
|
-
))
|
|
70
|
-
: null}
|
|
71
|
-
{partition.range !== null && partition.range.length > 0
|
|
72
|
-
? rangePartition(partition)
|
|
73
|
-
: null}
|
|
74
|
-
{(partition.range === null && partition.values === null) ||
|
|
75
|
-
(partition.range?.length === 0 &&
|
|
76
|
-
partition.values?.length === 0) ? (
|
|
77
|
-
<span className={`badge partition_value_highlight`}>
|
|
78
|
-
ALL
|
|
79
|
-
</span>
|
|
80
|
-
) : null}
|
|
69
|
+
<code>{column.name}</code>
|
|
70
|
+
<span className="badge partition_value">
|
|
71
|
+
{column.partition.type_}
|
|
72
|
+
</span>
|
|
81
73
|
</div>
|
|
82
74
|
</div>
|
|
83
|
-
)
|
|
84
|
-
)
|
|
85
|
-
) : (
|
|
86
|
-
<br />
|
|
87
|
-
)}
|
|
75
|
+
);
|
|
76
|
+
})}
|
|
88
77
|
</td>
|
|
89
78
|
<td>
|
|
90
79
|
{materialization.output_tables.map(table => (
|
|
@@ -101,30 +90,26 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
101
90
|
</div>
|
|
102
91
|
))}
|
|
103
92
|
</td>
|
|
104
|
-
{/*<td>{Object.keys(materialization.config.spark).map(key => <li className={`list-group-item`}>{key}: {materialization.config.spark[key]}</li>)}</td>*/}
|
|
105
|
-
|
|
106
93
|
<td>
|
|
107
|
-
{materialization.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
<div className="
|
|
111
|
-
|
|
112
|
-
<div className="partition__body">
|
|
113
|
-
{partition.values !== null && partition.values.length > 0
|
|
114
|
-
? partition.values.map(val => (
|
|
115
|
-
<span className="badge partition_value">{val}</span>
|
|
116
|
-
))
|
|
117
|
-
: null}
|
|
118
|
-
{partition.range !== null && partition.range.length > 0
|
|
119
|
-
? rangePartition(partition)
|
|
120
|
-
: null}
|
|
121
|
-
</div>
|
|
94
|
+
{materialization.backfills.map(backfill => (
|
|
95
|
+
<a href={backfill.urls[0]} className="partitionLink">
|
|
96
|
+
<div className="partition__full" key={backfill.spec.column_name}>
|
|
97
|
+
<div className="partition__header">
|
|
98
|
+
{partitionColumnsMap[backfill.spec.column_name]}
|
|
122
99
|
</div>
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
100
|
+
<div className="partition__body">
|
|
101
|
+
<span className="badge partition_value">
|
|
102
|
+
{backfill.spec.range[0]}
|
|
103
|
+
</span>
|
|
104
|
+
to
|
|
105
|
+
<span className="badge partition_value">
|
|
106
|
+
{backfill.spec.range[1]}
|
|
107
|
+
</span>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</a>
|
|
111
|
+
))}
|
|
112
|
+
<AddBackfillPopover node={node} materialization={materialization} />
|
|
128
113
|
</td>
|
|
129
114
|
<td>
|
|
130
115
|
{materialization.urls.map((url, idx) => (
|
|
@@ -137,38 +122,112 @@ export default function NodeMaterializationTab({ node, djClient }) {
|
|
|
137
122
|
));
|
|
138
123
|
};
|
|
139
124
|
return (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
<
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
125
|
+
<>
|
|
126
|
+
<div className="table-vertical">
|
|
127
|
+
<div>
|
|
128
|
+
<h2>Materializations</h2>
|
|
129
|
+
<AddMaterializationPopover node={node} />
|
|
130
|
+
{materializations.length > 0 ? (
|
|
131
|
+
<table
|
|
132
|
+
className="card-inner-table table"
|
|
133
|
+
aria-label="Materializations"
|
|
134
|
+
aria-hidden="false"
|
|
135
|
+
>
|
|
136
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
137
|
+
<tr>
|
|
138
|
+
<th className="text-start">Schedule</th>
|
|
139
|
+
<th>Engine</th>
|
|
140
|
+
<th>Partitions</th>
|
|
141
|
+
<th>Output Tables</th>
|
|
142
|
+
<th>Backfills</th>
|
|
143
|
+
<th>URLs</th>
|
|
144
|
+
</tr>
|
|
145
|
+
</thead>
|
|
146
|
+
<tbody>
|
|
147
|
+
{materializationRows(
|
|
148
|
+
materializations.filter(
|
|
149
|
+
materialization =>
|
|
150
|
+
!(
|
|
151
|
+
materialization.name === 'default' &&
|
|
152
|
+
node.type === 'cube'
|
|
153
|
+
),
|
|
154
|
+
),
|
|
155
|
+
)}
|
|
156
|
+
</tbody>
|
|
157
|
+
</table>
|
|
158
|
+
) : (
|
|
159
|
+
<div className="message alert" style={{ marginTop: '10px' }}>
|
|
160
|
+
No materialization workflows configured for this node.
|
|
161
|
+
</div>
|
|
162
|
+
)}
|
|
170
163
|
</div>
|
|
171
|
-
|
|
172
|
-
|
|
164
|
+
<div>
|
|
165
|
+
<h2>Materialized Datasets</h2>
|
|
166
|
+
{node && node.availability !== null ? (
|
|
167
|
+
<table
|
|
168
|
+
className="card-inner-table table"
|
|
169
|
+
aria-label="Availability"
|
|
170
|
+
aria-hidden="false"
|
|
171
|
+
>
|
|
172
|
+
<thead className="fs-7 fw-bold text-gray-400 border-bottom-0">
|
|
173
|
+
<tr>
|
|
174
|
+
<th className="text-start">Catalog</th>
|
|
175
|
+
<th>Schema</th>
|
|
176
|
+
<th>Table</th>
|
|
177
|
+
<th>Valid Through</th>
|
|
178
|
+
<th>Partitions</th>
|
|
179
|
+
</tr>
|
|
180
|
+
</thead>
|
|
181
|
+
<tbody>
|
|
182
|
+
<tr>
|
|
183
|
+
<td>{node.availability.schema_}</td>
|
|
184
|
+
<td>
|
|
185
|
+
{
|
|
186
|
+
<div
|
|
187
|
+
className={`table__full`}
|
|
188
|
+
key={node.availability.table}
|
|
189
|
+
>
|
|
190
|
+
<div className="table__header">
|
|
191
|
+
<TableIcon />{' '}
|
|
192
|
+
<span className={`entity-info`}>
|
|
193
|
+
{node.availability.catalog +
|
|
194
|
+
'.' +
|
|
195
|
+
node.availability.schema_}
|
|
196
|
+
</span>
|
|
197
|
+
</div>
|
|
198
|
+
<div className={`table__body upstream_tables`}>
|
|
199
|
+
<a href={node.availability.url}>
|
|
200
|
+
{node.availability.table}
|
|
201
|
+
</a>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
}
|
|
205
|
+
</td>
|
|
206
|
+
<td>{node.availability.valid_through_ts}</td>
|
|
207
|
+
<td>
|
|
208
|
+
<span
|
|
209
|
+
className={`badge partition_value`}
|
|
210
|
+
style={{ fontSize: '100%' }}
|
|
211
|
+
>
|
|
212
|
+
<span className={`badge partition_value_highlight`}>
|
|
213
|
+
{node.availability.min_temporal_partition}
|
|
214
|
+
</span>
|
|
215
|
+
to
|
|
216
|
+
<span className={`badge partition_value_highlight`}>
|
|
217
|
+
{node.availability.max_temporal_partition}
|
|
218
|
+
</span>
|
|
219
|
+
</span>
|
|
220
|
+
</td>
|
|
221
|
+
</tr>
|
|
222
|
+
</tbody>
|
|
223
|
+
</table>
|
|
224
|
+
) : (
|
|
225
|
+
<div className="message alert" style={{ marginTop: '10px' }}>
|
|
226
|
+
No materialized datasets available for this node.
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
</>
|
|
173
232
|
);
|
|
174
233
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { useContext, useEffect, useRef, useState } from 'react';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import DJClientContext from '../../providers/djclient';
|
|
4
|
+
import { Field, Form, Formik } from 'formik';
|
|
5
|
+
import { FormikSelect } from '../AddEditNodePage/FormikSelect';
|
|
6
|
+
import EditIcon from '../../icons/EditIcon';
|
|
7
|
+
import { displayMessageAfterSubmit, labelize } from '../../../utils/form';
|
|
8
|
+
|
|
9
|
+
export default function PartitionColumnPopover({ column, node, onSubmit }) {
|
|
10
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
11
|
+
const [popoverAnchor, setPopoverAnchor] = useState(false);
|
|
12
|
+
const ref = useRef(null);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const handleClickOutside = event => {
|
|
16
|
+
if (ref.current && !ref.current.contains(event.target)) {
|
|
17
|
+
setPopoverAnchor(false);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
document.addEventListener('click', handleClickOutside, true);
|
|
21
|
+
return () => {
|
|
22
|
+
document.removeEventListener('click', handleClickOutside, true);
|
|
23
|
+
};
|
|
24
|
+
}, [setPopoverAnchor]);
|
|
25
|
+
|
|
26
|
+
const savePartition = async (
|
|
27
|
+
{ node, column, partition_type, format, granularity },
|
|
28
|
+
{ setSubmitting, setStatus },
|
|
29
|
+
) => {
|
|
30
|
+
setSubmitting(false);
|
|
31
|
+
const response = await djClient.setPartition(
|
|
32
|
+
node,
|
|
33
|
+
column,
|
|
34
|
+
partition_type,
|
|
35
|
+
format,
|
|
36
|
+
granularity,
|
|
37
|
+
);
|
|
38
|
+
if (response.status === 200 || response.status === 201) {
|
|
39
|
+
setStatus({ success: 'Saved!' });
|
|
40
|
+
} else {
|
|
41
|
+
setStatus({
|
|
42
|
+
failure: `${response.json.message}`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
onSubmit();
|
|
46
|
+
// window.location.reload();
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<>
|
|
51
|
+
<button
|
|
52
|
+
className="edit_button"
|
|
53
|
+
aria-label="PartitionColumn"
|
|
54
|
+
tabIndex="0"
|
|
55
|
+
onClick={() => {
|
|
56
|
+
setPopoverAnchor(!popoverAnchor);
|
|
57
|
+
}}
|
|
58
|
+
>
|
|
59
|
+
<EditIcon />
|
|
60
|
+
</button>
|
|
61
|
+
<div
|
|
62
|
+
className="popover"
|
|
63
|
+
role="dialog"
|
|
64
|
+
aria-label="client-code"
|
|
65
|
+
style={{ display: popoverAnchor === false ? 'none' : 'block' }}
|
|
66
|
+
ref={ref}
|
|
67
|
+
>
|
|
68
|
+
<Formik
|
|
69
|
+
initialValues={{
|
|
70
|
+
column: column.name,
|
|
71
|
+
node: node.name,
|
|
72
|
+
partition_type: '',
|
|
73
|
+
format: 'yyyyMMdd',
|
|
74
|
+
granularity: 'day',
|
|
75
|
+
}}
|
|
76
|
+
onSubmit={savePartition}
|
|
77
|
+
>
|
|
78
|
+
{function Render({ values, isSubmitting, status, setFieldValue }) {
|
|
79
|
+
return (
|
|
80
|
+
<Form>
|
|
81
|
+
{displayMessageAfterSubmit(status)}
|
|
82
|
+
<span data-testid="edit-partition">
|
|
83
|
+
<label htmlFor="react-select-3-input">Partition Type</label>
|
|
84
|
+
<Field
|
|
85
|
+
as="select"
|
|
86
|
+
name="partition_type"
|
|
87
|
+
id="partitionType"
|
|
88
|
+
placeholder="Partition Type"
|
|
89
|
+
>
|
|
90
|
+
<option value=""></option>
|
|
91
|
+
<option value="temporal">Temporal</option>
|
|
92
|
+
<option value="categorical">Categorical</option>
|
|
93
|
+
</Field>
|
|
94
|
+
</span>
|
|
95
|
+
<input
|
|
96
|
+
hidden={true}
|
|
97
|
+
name="column"
|
|
98
|
+
value={column.name}
|
|
99
|
+
readOnly={true}
|
|
100
|
+
/>
|
|
101
|
+
<input
|
|
102
|
+
hidden={true}
|
|
103
|
+
name="node"
|
|
104
|
+
value={node.name}
|
|
105
|
+
readOnly={true}
|
|
106
|
+
/>
|
|
107
|
+
<br />
|
|
108
|
+
<br />
|
|
109
|
+
{values.partition_type === 'temporal' ? (
|
|
110
|
+
<>
|
|
111
|
+
<label htmlFor="react-select-3-input">
|
|
112
|
+
Partition Format
|
|
113
|
+
</label>
|
|
114
|
+
<Field
|
|
115
|
+
type="text"
|
|
116
|
+
name="format"
|
|
117
|
+
id="partitionFormat"
|
|
118
|
+
placeholder="Optional temporal partition format (ex: yyyyMMdd)"
|
|
119
|
+
/>
|
|
120
|
+
<br />
|
|
121
|
+
<br />
|
|
122
|
+
<label htmlFor="react-select-3-input">
|
|
123
|
+
Partition Granularity
|
|
124
|
+
</label>
|
|
125
|
+
<Field
|
|
126
|
+
as="select"
|
|
127
|
+
name="granularity"
|
|
128
|
+
id="partitionGranularity"
|
|
129
|
+
placeholder="Granularity"
|
|
130
|
+
>
|
|
131
|
+
<option value="day">Day</option>
|
|
132
|
+
<option value="hour">Hour</option>
|
|
133
|
+
</Field>
|
|
134
|
+
</>
|
|
135
|
+
) : (
|
|
136
|
+
''
|
|
137
|
+
)}
|
|
138
|
+
<button
|
|
139
|
+
className="add_node"
|
|
140
|
+
type="submit"
|
|
141
|
+
aria-label="SaveEditColumn"
|
|
142
|
+
aria-hidden="false"
|
|
143
|
+
>
|
|
144
|
+
Save
|
|
145
|
+
</button>
|
|
146
|
+
</Form>
|
|
147
|
+
);
|
|
148
|
+
}}
|
|
149
|
+
</Formik>
|
|
150
|
+
</div>
|
|
151
|
+
</>
|
|
152
|
+
);
|
|
153
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
|
3
|
+
import EditColumnPopover from '../EditColumnPopover';
|
|
4
|
+
import DJClientContext from '../../../providers/djclient';
|
|
5
|
+
import AddBackfillPopover from '../AddBackfillPopover';
|
|
6
|
+
import { mocks } from '../../../../mocks/mockNodes';
|
|
7
|
+
|
|
8
|
+
const mockDjClient = {
|
|
9
|
+
DataJunctionAPI: {
|
|
10
|
+
runBackfill: jest.fn(),
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
describe('<AddBackfillPopover />', () => {
|
|
15
|
+
it('renders correctly and handles form submission', async () => {
|
|
16
|
+
// Mock onSubmit function
|
|
17
|
+
const onSubmitMock = jest.fn();
|
|
18
|
+
|
|
19
|
+
mockDjClient.DataJunctionAPI.runBackfill.mockReturnValue({
|
|
20
|
+
status: 201,
|
|
21
|
+
json: { message: '' },
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Render the component
|
|
25
|
+
const { getByLabelText, getByText } = render(
|
|
26
|
+
<DJClientContext.Provider value={mockDjClient}>
|
|
27
|
+
<AddBackfillPopover
|
|
28
|
+
node={mocks.mockMetricNode}
|
|
29
|
+
materialization={mocks.nodeMaterializations}
|
|
30
|
+
onSubmit={onSubmitMock}
|
|
31
|
+
/>
|
|
32
|
+
</DJClientContext.Provider>,
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
// Open the popover
|
|
36
|
+
fireEvent.click(getByLabelText('AddBackfill'));
|
|
37
|
+
|
|
38
|
+
fireEvent.click(getByText('Save'));
|
|
39
|
+
getByText('Save').click();
|
|
40
|
+
|
|
41
|
+
// Expect setAttributes to be called
|
|
42
|
+
await waitFor(() => {
|
|
43
|
+
expect(mockDjClient.DataJunctionAPI.runBackfill).toHaveBeenCalled();
|
|
44
|
+
expect(getByText('Saved!')).toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -36,6 +36,7 @@ describe('<NodePage />', () => {
|
|
|
36
36
|
nodesWithDimension: jest.fn(),
|
|
37
37
|
attributes: jest.fn(),
|
|
38
38
|
dimensions: jest.fn(),
|
|
39
|
+
setPartition: jest.fn(),
|
|
39
40
|
},
|
|
40
41
|
};
|
|
41
42
|
};
|
|
@@ -70,6 +71,7 @@ describe('<NodePage />', () => {
|
|
|
70
71
|
{
|
|
71
72
|
name: 'default_DOT_avg_repair_price',
|
|
72
73
|
type: 'double',
|
|
74
|
+
display_name: 'Default DOT avg repair price',
|
|
73
75
|
attributes: [],
|
|
74
76
|
dimension: null,
|
|
75
77
|
},
|
|
@@ -82,7 +84,7 @@ describe('<NodePage />', () => {
|
|
|
82
84
|
},
|
|
83
85
|
],
|
|
84
86
|
created_at: '2023-08-21T16:48:56.932162+00:00',
|
|
85
|
-
tags: [],
|
|
87
|
+
tags: [{ name: 'purpose', display_name: 'Purpose' }],
|
|
86
88
|
primary_key: [],
|
|
87
89
|
createNodeClientCode:
|
|
88
90
|
'dj = DJBuilder(DJ_URL)\n\navg_repair_price = dj.create_metric(\n description="Average repair price",\n display_name="Default: Avg Repair Price",\n name="default.avg_repair_price",\n primary_key=[],\n query="""SELECT avg(price) default_DOT_avg_repair_price \n FROM default.repair_order_details\n\n"""\n)',
|
|
@@ -303,13 +305,12 @@ describe('<NodePage />', () => {
|
|
|
303
305
|
'v1.0',
|
|
304
306
|
);
|
|
305
307
|
|
|
306
|
-
// expect(screen.getByRole('dialog', { name: 'Table' })).not.toBeInTheDocument();
|
|
307
308
|
expect(
|
|
308
309
|
screen.getByRole('dialog', { name: 'NodeStatus' }),
|
|
309
310
|
).toBeInTheDocument();
|
|
310
311
|
|
|
311
312
|
expect(screen.getByRole('dialog', { name: 'Tags' })).toHaveTextContent(
|
|
312
|
-
'',
|
|
313
|
+
'Purpose',
|
|
313
314
|
);
|
|
314
315
|
|
|
315
316
|
expect(
|
|
@@ -396,6 +397,11 @@ describe('<NodePage />', () => {
|
|
|
396
397
|
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
|
|
397
398
|
djClient.DataJunctionAPI.attributes.mockReturnValue(mocks.attributes);
|
|
398
399
|
djClient.DataJunctionAPI.dimensions.mockReturnValue(mocks.dimensions);
|
|
400
|
+
djClient.DataJunctionAPI.setPartition.mockReturnValue({
|
|
401
|
+
status: 200,
|
|
402
|
+
json: { message: '' },
|
|
403
|
+
});
|
|
404
|
+
|
|
399
405
|
const element = (
|
|
400
406
|
<DJClientContext.Provider value={djClient}>
|
|
401
407
|
<NodePage />
|
|
@@ -416,6 +422,9 @@ describe('<NodePage />', () => {
|
|
|
416
422
|
expect(
|
|
417
423
|
screen.getByRole('columnheader', { name: 'ColumnName' }),
|
|
418
424
|
).toHaveTextContent('default_DOT_avg_repair_price');
|
|
425
|
+
expect(
|
|
426
|
+
screen.getByRole('columnheader', { name: 'ColumnDisplayName' }),
|
|
427
|
+
).toHaveTextContent('Default DOT avg repair price');
|
|
419
428
|
expect(
|
|
420
429
|
screen.getByRole('columnheader', { name: 'ColumnType' }),
|
|
421
430
|
).toHaveTextContent('double');
|
|
@@ -439,6 +448,19 @@ describe('<NodePage />', () => {
|
|
|
439
448
|
expect(
|
|
440
449
|
screen.getByRole('button', { name: 'SaveLinkDimension' }),
|
|
441
450
|
).toBeInTheDocument();
|
|
451
|
+
|
|
452
|
+
// check that the set column partition popover can be clicked
|
|
453
|
+
const partitionColumnPopover = screen.getByRole('button', {
|
|
454
|
+
name: 'PartitionColumn',
|
|
455
|
+
});
|
|
456
|
+
expect(partitionColumnPopover).toBeInTheDocument();
|
|
457
|
+
fireEvent.click(partitionColumnPopover);
|
|
458
|
+
const savePartition = screen.getByRole('button', {
|
|
459
|
+
name: 'SaveEditColumn',
|
|
460
|
+
});
|
|
461
|
+
expect(savePartition).toBeInTheDocument();
|
|
462
|
+
fireEvent.click(savePartition);
|
|
463
|
+
expect(screen.getByText('Saved!'));
|
|
442
464
|
});
|
|
443
465
|
});
|
|
444
466
|
// check compiled SQL on nodeInfo page
|
|
@@ -573,7 +595,10 @@ describe('<NodePage />', () => {
|
|
|
573
595
|
expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
|
|
574
596
|
mocks.mockMetricNode.name,
|
|
575
597
|
);
|
|
576
|
-
screen.getByText(
|
|
598
|
+
screen.getByText(
|
|
599
|
+
'No materialization workflows configured for this node.',
|
|
600
|
+
);
|
|
601
|
+
screen.getByText('No materialized datasets available for this node.');
|
|
577
602
|
});
|
|
578
603
|
});
|
|
579
604
|
|
|
@@ -598,20 +623,25 @@ describe('<NodePage />', () => {
|
|
|
598
623
|
</Routes>
|
|
599
624
|
</MemoryRouter>,
|
|
600
625
|
);
|
|
601
|
-
await waitFor(
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
626
|
+
await waitFor(
|
|
627
|
+
() => {
|
|
628
|
+
fireEvent.click(
|
|
629
|
+
screen.getByRole('button', { name: 'Materializations' }),
|
|
630
|
+
);
|
|
631
|
+
expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
|
|
632
|
+
mocks.mockMetricNode.name,
|
|
633
|
+
);
|
|
634
|
+
expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
|
|
635
|
+
mocks.mockMetricNode.name,
|
|
636
|
+
);
|
|
609
637
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
638
|
+
expect(
|
|
639
|
+
screen.getByRole('table', { name: 'Materializations' }),
|
|
640
|
+
).toMatchSnapshot();
|
|
641
|
+
},
|
|
642
|
+
{ timeout: 3000 },
|
|
643
|
+
);
|
|
644
|
+
}, 60000);
|
|
615
645
|
|
|
616
646
|
it('renders the NodeSQL tab', async () => {
|
|
617
647
|
const djClient = mockDJClient();
|