datajunction-ui 0.0.1-rc.25 → 0.0.1-rc.27
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 +1 -1
- package/src/app/pages/LoginPage/LoginForm.jsx +10 -2
- package/src/app/pages/LoginPage/SignupForm.jsx +15 -3
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +166 -0
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +161 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +0 -1
- package/src/app/pages/NodePage/NodeColumnTab.jsx +91 -25
- package/src/app/pages/NodePage/NodeInfoTab.jsx +28 -22
- 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 +43 -14
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +101 -100
- package/src/app/services/DJService.js +87 -0
- package/src/app/services/__tests__/DJService.test.jsx +112 -0
- package/src/mocks/mockNodes.jsx +56 -67
- package/src/styles/dag.css +4 -0
- package/src/styles/index.css +70 -1
- package/src/styles/loading.css +31 -32
- package/src/styles/login.css +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,8 @@ describe('<NodePage />', () => {
|
|
|
36
36
|
nodesWithDimension: jest.fn(),
|
|
37
37
|
attributes: jest.fn(),
|
|
38
38
|
dimensions: jest.fn(),
|
|
39
|
+
setPartition: jest.fn(),
|
|
40
|
+
engines: jest.fn(),
|
|
39
41
|
},
|
|
40
42
|
};
|
|
41
43
|
};
|
|
@@ -396,6 +398,12 @@ describe('<NodePage />', () => {
|
|
|
396
398
|
djClient.DataJunctionAPI.columns.mockReturnValue(mocks.metricNodeColumns);
|
|
397
399
|
djClient.DataJunctionAPI.attributes.mockReturnValue(mocks.attributes);
|
|
398
400
|
djClient.DataJunctionAPI.dimensions.mockReturnValue(mocks.dimensions);
|
|
401
|
+
djClient.DataJunctionAPI.engines.mockReturnValue([]);
|
|
402
|
+
djClient.DataJunctionAPI.setPartition.mockReturnValue({
|
|
403
|
+
status: 200,
|
|
404
|
+
json: { message: '' },
|
|
405
|
+
});
|
|
406
|
+
|
|
399
407
|
const element = (
|
|
400
408
|
<DJClientContext.Provider value={djClient}>
|
|
401
409
|
<NodePage />
|
|
@@ -442,6 +450,19 @@ describe('<NodePage />', () => {
|
|
|
442
450
|
expect(
|
|
443
451
|
screen.getByRole('button', { name: 'SaveLinkDimension' }),
|
|
444
452
|
).toBeInTheDocument();
|
|
453
|
+
|
|
454
|
+
// check that the set column partition popover can be clicked
|
|
455
|
+
const partitionColumnPopover = screen.getByRole('button', {
|
|
456
|
+
name: 'PartitionColumn',
|
|
457
|
+
});
|
|
458
|
+
expect(partitionColumnPopover).toBeInTheDocument();
|
|
459
|
+
fireEvent.click(partitionColumnPopover);
|
|
460
|
+
const savePartition = screen.getByRole('button', {
|
|
461
|
+
name: 'SaveEditColumn',
|
|
462
|
+
});
|
|
463
|
+
expect(savePartition).toBeInTheDocument();
|
|
464
|
+
fireEvent.click(savePartition);
|
|
465
|
+
expect(screen.getByText('Saved!'));
|
|
445
466
|
});
|
|
446
467
|
});
|
|
447
468
|
// check compiled SQL on nodeInfo page
|
|
@@ -576,7 +597,10 @@ describe('<NodePage />', () => {
|
|
|
576
597
|
expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
|
|
577
598
|
mocks.mockMetricNode.name,
|
|
578
599
|
);
|
|
579
|
-
screen.getByText(
|
|
600
|
+
screen.getByText(
|
|
601
|
+
'No materialization workflows configured for this node.',
|
|
602
|
+
);
|
|
603
|
+
screen.getByText('No materialized datasets available for this node.');
|
|
580
604
|
});
|
|
581
605
|
});
|
|
582
606
|
|
|
@@ -601,20 +625,25 @@ describe('<NodePage />', () => {
|
|
|
601
625
|
</Routes>
|
|
602
626
|
</MemoryRouter>,
|
|
603
627
|
);
|
|
604
|
-
await waitFor(
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
628
|
+
await waitFor(
|
|
629
|
+
() => {
|
|
630
|
+
fireEvent.click(
|
|
631
|
+
screen.getByRole('button', { name: 'Materializations' }),
|
|
632
|
+
);
|
|
633
|
+
expect(djClient.DataJunctionAPI.node).toHaveBeenCalledWith(
|
|
634
|
+
mocks.mockMetricNode.name,
|
|
635
|
+
);
|
|
636
|
+
expect(djClient.DataJunctionAPI.materializations).toHaveBeenCalledWith(
|
|
637
|
+
mocks.mockMetricNode.name,
|
|
638
|
+
);
|
|
612
639
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
640
|
+
expect(
|
|
641
|
+
screen.getByRole('table', { name: 'Materializations' }),
|
|
642
|
+
).toMatchSnapshot();
|
|
643
|
+
},
|
|
644
|
+
{ timeout: 3000 },
|
|
645
|
+
);
|
|
646
|
+
}, 60000);
|
|
618
647
|
|
|
619
648
|
it('renders the NodeSQL tab', async () => {
|
|
620
649
|
const djClient = mockDJClient();
|