datajunction-ui 0.0.1-a46.dev3 → 0.0.1-a48
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 +5 -0
- package/package.json +2 -2
- package/src/app/components/NodeMaterializationDelete.jsx +80 -0
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +46 -51
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +33 -24
- package/src/app/pages/NodePage/MaterializationConfigField.jsx +38 -31
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +179 -110
- package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +13 -3
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +12 -5
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +276 -191
- package/src/app/pages/Root/index.tsx +3 -1
- package/src/app/services/DJService.js +26 -25
- package/src/app/services/__tests__/DJService.test.jsx +37 -21
- package/src/mocks/mockNodes.jsx +61 -7
- package/src/styles/index.css +113 -11
package/Makefile
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "datajunction-ui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.1a48",
|
|
4
4
|
"description": "DataJunction Metrics Platform UI",
|
|
5
5
|
"module": "src/index.tsx",
|
|
6
6
|
"repository": {
|
|
@@ -166,7 +166,7 @@
|
|
|
166
166
|
"global": {
|
|
167
167
|
"statements": 89,
|
|
168
168
|
"branches": 75,
|
|
169
|
-
"lines":
|
|
169
|
+
"lines": 80,
|
|
170
170
|
"functions": 85
|
|
171
171
|
}
|
|
172
172
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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 NodeMaterializationDelete({
|
|
9
|
+
nodeName,
|
|
10
|
+
materializationName,
|
|
11
|
+
}) {
|
|
12
|
+
const [deleteButton, setDeleteButton] = React.useState(<DeleteIcon />);
|
|
13
|
+
|
|
14
|
+
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
15
|
+
const deleteNode = async (values, { setStatus }) => {
|
|
16
|
+
if (
|
|
17
|
+
!window.confirm(
|
|
18
|
+
'Deleting materialization job ' +
|
|
19
|
+
values.materializationName +
|
|
20
|
+
'. Are you sure?',
|
|
21
|
+
)
|
|
22
|
+
) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const { status, json } = await djClient.deleteMaterialization(
|
|
26
|
+
values.nodeName,
|
|
27
|
+
values.materializationName,
|
|
28
|
+
);
|
|
29
|
+
if (status === 200 || status === 201 || status === 204) {
|
|
30
|
+
window.location.reload();
|
|
31
|
+
setStatus({
|
|
32
|
+
success: (
|
|
33
|
+
<>
|
|
34
|
+
Successfully deleted materialization job:{' '}
|
|
35
|
+
{values.materializationName}
|
|
36
|
+
</>
|
|
37
|
+
),
|
|
38
|
+
});
|
|
39
|
+
setDeleteButton(''); // hide the Delete button
|
|
40
|
+
} else {
|
|
41
|
+
setStatus({
|
|
42
|
+
failure: `${json.message}`,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const initialValues = {
|
|
48
|
+
nodeName: nodeName,
|
|
49
|
+
materializationName: materializationName,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
<Formik initialValues={initialValues} onSubmit={deleteNode}>
|
|
55
|
+
{function Render({ status, setFieldValue }) {
|
|
56
|
+
return (
|
|
57
|
+
<Form className="deleteNode">
|
|
58
|
+
{displayMessageAfterSubmit(status)}
|
|
59
|
+
{
|
|
60
|
+
<>
|
|
61
|
+
<button
|
|
62
|
+
type="submit"
|
|
63
|
+
style={{
|
|
64
|
+
marginLeft: 0,
|
|
65
|
+
all: 'unset',
|
|
66
|
+
color: '#005c72',
|
|
67
|
+
cursor: 'pointer',
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{deleteButton}
|
|
71
|
+
</button>
|
|
72
|
+
</>
|
|
73
|
+
}
|
|
74
|
+
</Form>
|
|
75
|
+
);
|
|
76
|
+
}}
|
|
77
|
+
</Formik>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -3,6 +3,8 @@ import * as React from 'react';
|
|
|
3
3
|
import DJClientContext from '../../providers/djclient';
|
|
4
4
|
import { Field, Form, Formik } from 'formik';
|
|
5
5
|
import { displayMessageAfterSubmit } from '../../../utils/form';
|
|
6
|
+
import PartitionValueForm from './PartitionValueForm';
|
|
7
|
+
import LoadingIcon from '../../icons/LoadingIcon';
|
|
6
8
|
|
|
7
9
|
export default function AddBackfillPopover({
|
|
8
10
|
node,
|
|
@@ -26,30 +28,39 @@ export default function AddBackfillPopover({
|
|
|
26
28
|
}, [setPopoverAnchor]);
|
|
27
29
|
|
|
28
30
|
const partitionColumns = node.columns.filter(col => col.partition !== null);
|
|
29
|
-
|
|
30
|
-
const temporalPartitionColumns = partitionColumns.filter(
|
|
31
|
-
col => col.partition.type_ === 'temporal',
|
|
32
|
-
);
|
|
33
|
-
|
|
34
31
|
const initialValues = {
|
|
35
32
|
node: node.name,
|
|
36
33
|
materializationName: materialization.name,
|
|
37
|
-
|
|
38
|
-
temporalPartitionColumns.length > 0
|
|
39
|
-
? temporalPartitionColumns[0].name
|
|
40
|
-
: '',
|
|
41
|
-
from: '',
|
|
42
|
-
to: '',
|
|
34
|
+
partitionValues: {},
|
|
43
35
|
};
|
|
44
36
|
|
|
45
|
-
|
|
46
|
-
|
|
37
|
+
for (const partitionCol of partitionColumns) {
|
|
38
|
+
if (partitionCol.partition.type_ === 'temporal') {
|
|
39
|
+
initialValues.partitionValues[partitionCol.name] = {
|
|
40
|
+
from: '',
|
|
41
|
+
to: '',
|
|
42
|
+
};
|
|
43
|
+
} else {
|
|
44
|
+
initialValues.partitionValues[partitionCol.name] = '';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const runBackfill = async (values, setStatus) => {
|
|
47
49
|
const response = await djClient.runBackfill(
|
|
48
50
|
values.node,
|
|
49
51
|
values.materializationName,
|
|
50
|
-
values.
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
Object.entries(values.partitionValues).map(entry => {
|
|
53
|
+
if (typeof entry[1] === 'object' && entry[1] !== null) {
|
|
54
|
+
return {
|
|
55
|
+
columnName: entry[0],
|
|
56
|
+
range: [entry[1].from, entry[1].to],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
columnName: entry[0],
|
|
61
|
+
values: [entry[1]],
|
|
62
|
+
};
|
|
63
|
+
}),
|
|
53
64
|
);
|
|
54
65
|
if (response.status === 200 || response.status === 201) {
|
|
55
66
|
setStatus({ success: 'Saved!' });
|
|
@@ -58,21 +69,26 @@ export default function AddBackfillPopover({
|
|
|
58
69
|
failure: `${response.json.message}`,
|
|
59
70
|
});
|
|
60
71
|
}
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const submitBackfill = async (values, { setSubmitting, setStatus }) => {
|
|
75
|
+
await runBackfill(values, setStatus).then(_ => {
|
|
76
|
+
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
|
|
77
|
+
setSubmitting(false);
|
|
78
|
+
});
|
|
63
79
|
};
|
|
64
80
|
|
|
65
81
|
return (
|
|
66
82
|
<>
|
|
67
83
|
<button
|
|
68
|
-
className="edit_button"
|
|
84
|
+
className="edit_button add_button"
|
|
69
85
|
aria-label="AddBackfill"
|
|
70
86
|
tabIndex="0"
|
|
71
87
|
onClick={() => {
|
|
72
88
|
setPopoverAnchor(!popoverAnchor);
|
|
73
89
|
}}
|
|
74
90
|
>
|
|
75
|
-
<span className="
|
|
91
|
+
<span className="add_button">+ Run</span>
|
|
76
92
|
</button>
|
|
77
93
|
<div
|
|
78
94
|
className="fade modal-backdrop in"
|
|
@@ -85,10 +101,12 @@ export default function AddBackfillPopover({
|
|
|
85
101
|
style={{
|
|
86
102
|
display: popoverAnchor === false ? 'none' : 'block',
|
|
87
103
|
width: '50%',
|
|
104
|
+
minWidth: '800px',
|
|
105
|
+
left: '-25%',
|
|
88
106
|
}}
|
|
89
107
|
ref={ref}
|
|
90
108
|
>
|
|
91
|
-
<Formik initialValues={initialValues} onSubmit={
|
|
109
|
+
<Formik initialValues={initialValues} onSubmit={submitBackfill}>
|
|
92
110
|
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
93
111
|
return (
|
|
94
112
|
<Form>
|
|
@@ -115,41 +133,17 @@ export default function AddBackfillPopover({
|
|
|
115
133
|
<br />
|
|
116
134
|
<br />
|
|
117
135
|
<label htmlFor="partition" style={{ paddingBottom: '1rem' }}>
|
|
118
|
-
Partition
|
|
136
|
+
Partition
|
|
119
137
|
</label>
|
|
120
138
|
{node.columns
|
|
121
139
|
.filter(col => col.partition !== null)
|
|
122
140
|
.map(col => {
|
|
123
141
|
return (
|
|
124
|
-
<
|
|
125
|
-
|
|
142
|
+
<PartitionValueForm
|
|
143
|
+
col={col}
|
|
144
|
+
materialization={materialization}
|
|
126
145
|
key={col.name}
|
|
127
|
-
|
|
128
|
-
>
|
|
129
|
-
<div className="partition__header">
|
|
130
|
-
{col.display_name}
|
|
131
|
-
</div>
|
|
132
|
-
<div className="partition__body">
|
|
133
|
-
<span style={{ padding: '0.5rem' }}>From</span>{' '}
|
|
134
|
-
<Field
|
|
135
|
-
type="text"
|
|
136
|
-
name="from"
|
|
137
|
-
id={`${col.name}__from`}
|
|
138
|
-
placeholder="20230101"
|
|
139
|
-
default="20230101"
|
|
140
|
-
style={{ width: '7rem', paddingRight: '1rem' }}
|
|
141
|
-
/>{' '}
|
|
142
|
-
<span style={{ padding: '0.5rem' }}>To</span>
|
|
143
|
-
<Field
|
|
144
|
-
type="text"
|
|
145
|
-
name="to"
|
|
146
|
-
id={`${col.name}__to`}
|
|
147
|
-
placeholder="20230102"
|
|
148
|
-
default="20230102"
|
|
149
|
-
style={{ width: '7rem' }}
|
|
150
|
-
/>
|
|
151
|
-
</div>
|
|
152
|
-
</div>
|
|
146
|
+
/>
|
|
153
147
|
);
|
|
154
148
|
})}
|
|
155
149
|
<br />
|
|
@@ -158,8 +152,9 @@ export default function AddBackfillPopover({
|
|
|
158
152
|
type="submit"
|
|
159
153
|
aria-label="SaveEditColumn"
|
|
160
154
|
aria-hidden="false"
|
|
155
|
+
disabled={isSubmitting}
|
|
161
156
|
>
|
|
162
|
-
Save
|
|
157
|
+
{isSubmitting ? <LoadingIcon /> : 'Save'}
|
|
163
158
|
</button>
|
|
164
159
|
</Form>
|
|
165
160
|
);
|
|
@@ -39,6 +39,9 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
39
39
|
const config = {};
|
|
40
40
|
config.spark = values.spark_config;
|
|
41
41
|
config.lookback_window = values.lookback_window;
|
|
42
|
+
if (!values.job_type) {
|
|
43
|
+
values.job_type = 'spark_sql';
|
|
44
|
+
}
|
|
42
45
|
const { status, json } = await djClient.materialize(
|
|
43
46
|
values.node,
|
|
44
47
|
values.job_type,
|
|
@@ -48,6 +51,7 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
48
51
|
);
|
|
49
52
|
if (status === 200 || status === 201) {
|
|
50
53
|
setStatus({ success: json.message });
|
|
54
|
+
window.location.reload();
|
|
51
55
|
} else {
|
|
52
56
|
setStatus({
|
|
53
57
|
failure: `${json.message}`,
|
|
@@ -111,36 +115,41 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
111
115
|
<Form>
|
|
112
116
|
<h2>Configure Materialization</h2>
|
|
113
117
|
{displayMessageAfterSubmit(status)}
|
|
114
|
-
|
|
115
|
-
<
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
118
|
+
{node.type === 'cube' ? (
|
|
119
|
+
<span data-testid="job-type">
|
|
120
|
+
<label htmlFor="job_type">Job Type</label>
|
|
121
|
+
|
|
122
|
+
<Field as="select" name="job_type">
|
|
123
|
+
<>
|
|
124
|
+
<option
|
|
125
|
+
key={'druid_measures_cube'}
|
|
126
|
+
value={'druid_measures_cube'}
|
|
127
|
+
>
|
|
128
|
+
Druid Measures Cube (Pre-Agg Cube)
|
|
129
|
+
</option>
|
|
130
|
+
<option
|
|
131
|
+
key={'druid_metrics_cube'}
|
|
132
|
+
value={'druid_metrics_cube'}
|
|
133
|
+
>
|
|
134
|
+
Druid Metrics Cube (Post-Agg Cube)
|
|
135
|
+
</option>
|
|
136
|
+
<option key={'spark_sql'} value={'spark_sql'}>
|
|
137
|
+
Iceberg Table
|
|
138
|
+
</option>
|
|
139
|
+
</>
|
|
140
|
+
</Field>
|
|
141
|
+
<br />
|
|
142
|
+
<br />
|
|
143
|
+
</span>
|
|
144
|
+
) : (
|
|
145
|
+
''
|
|
146
|
+
)}
|
|
136
147
|
<input
|
|
137
148
|
hidden={true}
|
|
138
149
|
name="node"
|
|
139
150
|
value={node?.name}
|
|
140
151
|
readOnly={true}
|
|
141
152
|
/>
|
|
142
|
-
<br />
|
|
143
|
-
<br />
|
|
144
153
|
<span data-testid="edit-partition">
|
|
145
154
|
<label htmlFor="strategy">Strategy</label>
|
|
146
155
|
<Field as="select" name="strategy">
|
|
@@ -16,38 +16,45 @@ export const ConfigField = ({ djClient, value }) => {
|
|
|
16
16
|
|
|
17
17
|
return (
|
|
18
18
|
<div className="DescriptionInput">
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
name="spark_config"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
extensions={[jsonExt]}
|
|
33
|
-
value={JSON.stringify(value, null, ' ')}
|
|
34
|
-
options={{
|
|
35
|
-
theme: 'default',
|
|
36
|
-
lineNumbers: true,
|
|
37
|
-
}}
|
|
38
|
-
width="100%"
|
|
39
|
-
height="170px"
|
|
40
|
-
style={{
|
|
41
|
-
margin: '0 0 23px 0',
|
|
42
|
-
flex: 1,
|
|
43
|
-
fontSize: '150%',
|
|
44
|
-
textAlign: 'left',
|
|
45
|
-
}}
|
|
46
|
-
onChange={(value, viewUpdate) => {
|
|
47
|
-
updateFormik(value);
|
|
48
|
-
}}
|
|
19
|
+
<details>
|
|
20
|
+
<summary>
|
|
21
|
+
<label htmlFor="SparkConfig" style={{ display: 'inline-block' }}>
|
|
22
|
+
Spark Config
|
|
23
|
+
</label>
|
|
24
|
+
</summary>
|
|
25
|
+
<ErrorMessage name="spark_config" component="span" />
|
|
26
|
+
<Field
|
|
27
|
+
type="textarea"
|
|
28
|
+
style={{ display: 'none' }}
|
|
29
|
+
as="textarea"
|
|
30
|
+
name="spark_config"
|
|
31
|
+
id="SparkConfig"
|
|
49
32
|
/>
|
|
50
|
-
|
|
33
|
+
<div role="button" tabIndex={0} className="relative flex bg-[#282a36]">
|
|
34
|
+
<CodeMirror
|
|
35
|
+
id={'spark_config'}
|
|
36
|
+
name={'spark_config'}
|
|
37
|
+
extensions={[jsonExt]}
|
|
38
|
+
value={JSON.stringify(value, null, ' ')}
|
|
39
|
+
options={{
|
|
40
|
+
theme: 'default',
|
|
41
|
+
lineNumbers: true,
|
|
42
|
+
}}
|
|
43
|
+
width="100%"
|
|
44
|
+
height="170px"
|
|
45
|
+
style={{
|
|
46
|
+
margin: '0 0 23px 0',
|
|
47
|
+
flex: 1,
|
|
48
|
+
fontSize: '150%',
|
|
49
|
+
textAlign: 'left',
|
|
50
|
+
}}
|
|
51
|
+
onChange={(value, viewUpdate) => {
|
|
52
|
+
updateFormik(value);
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
</details>{' '}
|
|
57
|
+
<></>
|
|
51
58
|
</div>
|
|
52
59
|
);
|
|
53
60
|
};
|