datajunction-ui 0.0.1-a1 → 0.0.1-a101
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 +7 -1
- package/package.json +18 -7
- package/public/index.html +1 -1
- package/src/app/components/AddNodeDropdown.jsx +44 -0
- package/src/app/components/ListGroupItem.jsx +2 -1
- package/src/app/components/NodeListActions.jsx +69 -0
- package/src/app/components/NodeMaterializationDelete.jsx +80 -0
- package/src/app/components/QueryInfo.jsx +96 -1
- package/src/app/components/Search.jsx +94 -0
- package/src/app/components/__tests__/NodeListActions.test.jsx +94 -0
- package/src/app/components/__tests__/Search.test.jsx +63 -0
- package/src/app/components/__tests__/__snapshots__/ListGroupItem.test.tsx.snap +5 -3
- package/src/app/components/djgraph/Collapse.jsx +3 -2
- package/src/app/components/djgraph/DJNode.jsx +1 -1
- package/src/app/components/djgraph/DJNodeColumns.jsx +5 -1
- package/src/app/components/djgraph/LayoutFlow.jsx +5 -3
- package/src/app/components/forms/Action.jsx +8 -0
- package/src/app/components/forms/NodeNameField.jsx +64 -0
- package/src/app/components/search.css +17 -0
- package/src/app/icons/AddItemIcon.jsx +16 -0
- package/src/app/icons/CommitIcon.jsx +45 -0
- package/src/app/icons/DiffIcon.jsx +63 -0
- package/src/app/icons/EyeIcon.jsx +20 -0
- package/src/app/icons/FilterIcon.jsx +7 -0
- package/src/app/icons/JupyterExportIcon.jsx +25 -0
- package/src/app/icons/LoadingIcon.jsx +10 -10
- package/src/app/icons/PythonIcon.jsx +6 -44
- package/src/app/index.tsx +24 -0
- package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -0
- package/src/app/pages/AddEditNodePage/ColumnsSelect.jsx +84 -0
- package/src/app/pages/AddEditNodePage/DescriptionField.jsx +17 -0
- package/src/app/pages/AddEditNodePage/DisplayNameField.jsx +16 -0
- package/src/app/pages/AddEditNodePage/FormikSelect.jsx +5 -0
- package/src/app/pages/AddEditNodePage/FullNameField.jsx +3 -2
- package/src/app/pages/AddEditNodePage/Loadable.jsx +6 -2
- package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +75 -0
- package/src/app/pages/AddEditNodePage/MetricQueryField.jsx +71 -0
- package/src/app/pages/AddEditNodePage/NamespaceField.jsx +40 -0
- package/src/app/pages/AddEditNodePage/NodeModeField.jsx +14 -0
- package/src/app/pages/AddEditNodePage/NodeQueryField.jsx +8 -3
- package/src/app/pages/AddEditNodePage/RequiredDimensionsSelect.jsx +54 -0
- package/src/app/pages/AddEditNodePage/TagsField.jsx +47 -0
- package/src/app/pages/AddEditNodePage/UpstreamNodeField.jsx +49 -0
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormFailed.test.jsx +15 -9
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +167 -24
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +55 -25
- package/src/app/pages/AddEditNodePage/index.jsx +275 -194
- package/src/app/pages/CubeBuilderPage/DimensionsSelect.jsx +154 -0
- package/src/app/pages/CubeBuilderPage/Loadable.jsx +16 -0
- package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +77 -0
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +405 -0
- package/src/app/pages/CubeBuilderPage/index.jsx +267 -0
- package/src/app/pages/NamespacePage/AddNamespacePopover.jsx +5 -5
- package/src/app/pages/NamespacePage/Explorer.jsx +6 -2
- package/src/app/pages/NamespacePage/FieldControl.jsx +21 -0
- package/src/app/pages/NamespacePage/NodeTypeSelect.jsx +30 -0
- package/src/app/pages/NamespacePage/TagSelect.jsx +44 -0
- package/src/app/pages/NamespacePage/UserSelect.jsx +47 -0
- package/src/app/pages/NamespacePage/__tests__/index.test.jsx +98 -19
- package/src/app/pages/NamespacePage/index.jsx +272 -89
- package/src/app/pages/NodePage/AddBackfillPopover.jsx +60 -61
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +104 -51
- package/src/app/pages/NodePage/ClientCodePopover.jsx +73 -25
- package/src/app/pages/NodePage/DimensionFilter.jsx +86 -0
- package/src/app/pages/NodePage/EditColumnDescriptionPopover.jsx +116 -0
- package/src/app/pages/NodePage/LinkDimensionPopover.jsx +38 -23
- package/src/app/pages/NodePage/MaterializationConfigField.jsx +60 -0
- package/src/app/pages/NodePage/NodeColumnTab.jsx +183 -113
- package/src/app/pages/NodePage/NodeDependenciesTab.jsx +153 -0
- package/src/app/pages/NodePage/NodeGraphTab.jsx +56 -29
- package/src/app/pages/NodePage/NodeHistory.jsx +165 -161
- package/src/app/pages/NodePage/NodeInfoTab.jsx +148 -14
- package/src/app/pages/NodePage/NodeMaterializationTab.jsx +201 -104
- package/src/app/pages/NodePage/NodeStatus.jsx +96 -21
- package/src/app/pages/NodePage/NodeValidateTab.jsx +367 -0
- package/src/app/pages/NodePage/NotebookDownload.jsx +36 -0
- package/src/app/pages/NodePage/PartitionColumnPopover.jsx +3 -5
- package/src/app/pages/NodePage/PartitionValueForm.jsx +60 -0
- package/src/app/pages/NodePage/RevisionDiff.jsx +209 -0
- package/src/app/pages/NodePage/WatchNodeButton.jsx +226 -0
- package/src/app/pages/NodePage/__tests__/AddBackfillPopover.test.jsx +13 -4
- package/src/app/pages/NodePage/__tests__/AddMaterializationPopover.test.jsx +87 -0
- package/src/app/pages/NodePage/__tests__/DimensionFilter.test.jsx +74 -0
- package/src/app/pages/NodePage/__tests__/EditColumnDescriptionPopover.test.jsx +149 -0
- package/src/app/pages/NodePage/__tests__/LinkDimensionPopover.test.jsx +10 -14
- package/src/app/pages/NodePage/__tests__/NodeColumnTab.test.jsx +166 -0
- package/src/app/pages/NodePage/__tests__/NodeDependenciesTab.test.jsx +151 -0
- package/src/app/pages/NodePage/__tests__/NodeGraphTab.test.jsx +6 -2
- package/src/app/pages/NodePage/__tests__/NodeLineageTab.test.jsx +3 -2
- package/src/app/pages/NodePage/__tests__/NodeMaterializationTab.test.jsx +148 -0
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +159 -57
- package/src/app/pages/NodePage/__tests__/RevisionDiff.test.jsx +164 -0
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +2 -386
- package/src/app/pages/NodePage/index.jsx +94 -57
- package/src/app/pages/Root/__tests__/index.test.jsx +3 -1
- package/src/app/pages/Root/index.tsx +62 -12
- package/src/app/services/DJService.js +587 -55
- package/src/app/services/__tests__/DJService.test.jsx +382 -45
- package/src/index.tsx +1 -0
- package/src/mocks/mockNodes.jsx +265 -227
- package/src/styles/dag.css +4 -2
- package/src/styles/index.css +474 -10
- package/src/styles/loading.css +1 -1
- package/src/styles/node-creation.scss +84 -5
- package/src/styles/node-list.css +4 -0
- package/src/styles/sorted-table.css +15 -0
- package/src/app/components/DeleteNode.jsx +0 -55
- package/src/app/components/__tests__/DeleteNode.test.jsx +0 -53
- package/src/app/pages/NodePage/NodeSQLTab.jsx +0 -82
- package/src/app/pages/NodePage/__tests__/ClientCodePopover.test.jsx +0 -49
|
@@ -3,18 +3,27 @@
|
|
|
3
3
|
* node types is largely the same, with minor differences handled server-side. For the `query`
|
|
4
4
|
* field, this page will render a CodeMirror SQL editor with autocompletion and syntax highlighting.
|
|
5
5
|
*/
|
|
6
|
-
import { ErrorMessage,
|
|
7
|
-
|
|
6
|
+
import { ErrorMessage, Form, Formik } from 'formik';
|
|
8
7
|
import NamespaceHeader from '../../components/NamespaceHeader';
|
|
9
8
|
import { useContext, useEffect, useState } from 'react';
|
|
10
9
|
import DJClientContext from '../../providers/djclient';
|
|
11
10
|
import 'styles/node-creation.scss';
|
|
12
|
-
import
|
|
13
|
-
import { useParams } from 'react-router-dom';
|
|
11
|
+
import { useParams, useNavigate } from 'react-router-dom';
|
|
14
12
|
import { FullNameField } from './FullNameField';
|
|
15
|
-
import {
|
|
13
|
+
import { MetricQueryField } from './MetricQueryField';
|
|
14
|
+
import { displayMessageAfterSubmit } from '../../../utils/form';
|
|
16
15
|
import { NodeQueryField } from './NodeQueryField';
|
|
17
|
-
import {
|
|
16
|
+
import { MetricMetadataFields } from './MetricMetadataFields';
|
|
17
|
+
import { UpstreamNodeField } from './UpstreamNodeField';
|
|
18
|
+
import { TagsField } from './TagsField';
|
|
19
|
+
import { NamespaceField } from './NamespaceField';
|
|
20
|
+
import { AlertMessage } from './AlertMessage';
|
|
21
|
+
import { DisplayNameField } from './DisplayNameField';
|
|
22
|
+
import { DescriptionField } from './DescriptionField';
|
|
23
|
+
import { NodeModeField } from './NodeModeField';
|
|
24
|
+
import { RequiredDimensionsSelect } from './RequiredDimensionsSelect';
|
|
25
|
+
import LoadingIcon from '../../icons/LoadingIcon';
|
|
26
|
+
import { ColumnsSelect } from './ColumnsSelect';
|
|
18
27
|
|
|
19
28
|
class Action {
|
|
20
29
|
static Add = new Action('add');
|
|
@@ -25,24 +34,22 @@ class Action {
|
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
export function AddEditNodePage() {
|
|
37
|
+
export function AddEditNodePage({ extensions = {} }) {
|
|
29
38
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
39
|
+
const navigate = useNavigate();
|
|
30
40
|
|
|
31
41
|
let { nodeType, initialNamespace, name } = useParams();
|
|
32
42
|
const action = name !== undefined ? Action.Edit : Action.Add;
|
|
33
43
|
|
|
34
|
-
const [namespaces, setNamespaces] = useState([]);
|
|
35
|
-
const [tags, setTags] = useState([]);
|
|
36
|
-
|
|
37
44
|
const initialValues = {
|
|
38
45
|
name: action === Action.Edit ? name : '',
|
|
39
46
|
namespace: action === Action.Add ? initialNamespace : '',
|
|
40
47
|
display_name: '',
|
|
41
48
|
query: '',
|
|
42
|
-
|
|
49
|
+
type: nodeType,
|
|
43
50
|
description: '',
|
|
44
51
|
primary_key: '',
|
|
45
|
-
mode: '
|
|
52
|
+
mode: 'published',
|
|
46
53
|
};
|
|
47
54
|
|
|
48
55
|
const validator = values => {
|
|
@@ -50,26 +57,26 @@ export function AddEditNodePage() {
|
|
|
50
57
|
if (!values.name) {
|
|
51
58
|
errors.name = 'Required';
|
|
52
59
|
}
|
|
53
|
-
if (!values.query) {
|
|
60
|
+
if (values.type !== 'metric' && !values.query) {
|
|
54
61
|
errors.query = 'Required';
|
|
55
62
|
}
|
|
56
63
|
return errors;
|
|
57
64
|
};
|
|
58
65
|
|
|
59
|
-
const handleSubmit = (values, { setSubmitting, setStatus }) => {
|
|
66
|
+
const handleSubmit = async (values, { setSubmitting, setStatus }) => {
|
|
60
67
|
if (action === Action.Add) {
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
await createNode(values, setStatus).then(_ => {
|
|
69
|
+
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
|
|
63
70
|
setSubmitting(false);
|
|
64
|
-
}
|
|
71
|
+
});
|
|
65
72
|
} else {
|
|
66
|
-
|
|
67
|
-
|
|
73
|
+
await patchNode(values, setStatus).then(_ => {
|
|
74
|
+
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
|
|
68
75
|
setSubmitting(false);
|
|
69
|
-
}
|
|
76
|
+
});
|
|
70
77
|
}
|
|
71
|
-
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
|
|
72
78
|
};
|
|
79
|
+
const submitHandlers = [handleSubmit];
|
|
73
80
|
|
|
74
81
|
const pageTitle =
|
|
75
82
|
action === Action.Add ? (
|
|
@@ -86,16 +93,16 @@ export function AddEditNodePage() {
|
|
|
86
93
|
const staticFieldsInEdit = node => (
|
|
87
94
|
<>
|
|
88
95
|
<div className="NodeNameInput NodeCreationInput">
|
|
89
|
-
<label
|
|
96
|
+
<label>Name</label> {name}
|
|
90
97
|
</div>
|
|
91
98
|
<div className="NodeNameInput NodeCreationInput">
|
|
92
|
-
<label
|
|
99
|
+
<label>Type</label> {node.type}
|
|
93
100
|
</div>
|
|
94
101
|
</>
|
|
95
102
|
);
|
|
96
103
|
|
|
97
104
|
const primaryKeyToList = primaryKey => {
|
|
98
|
-
return primaryKey.
|
|
105
|
+
return primaryKey.map(columnName => columnName.trim());
|
|
99
106
|
};
|
|
100
107
|
|
|
101
108
|
const createNode = async (values, setStatus) => {
|
|
@@ -104,13 +111,20 @@ export function AddEditNodePage() {
|
|
|
104
111
|
values.name,
|
|
105
112
|
values.display_name,
|
|
106
113
|
values.description,
|
|
107
|
-
values.
|
|
114
|
+
values.type === 'metric'
|
|
115
|
+
? `SELECT ${values.aggregate_expression} FROM ${values.upstream_node}`
|
|
116
|
+
: values.query,
|
|
108
117
|
values.mode,
|
|
109
118
|
values.namespace,
|
|
110
119
|
values.primary_key ? primaryKeyToList(values.primary_key) : null,
|
|
120
|
+
values.metric_direction,
|
|
121
|
+
values.metric_unit,
|
|
122
|
+
values.required_dimensions,
|
|
111
123
|
);
|
|
112
124
|
if (status === 200 || status === 201) {
|
|
113
|
-
|
|
125
|
+
if (values.tags) {
|
|
126
|
+
await djClient.tagsNode(values.name, values.tags);
|
|
127
|
+
}
|
|
114
128
|
setStatus({
|
|
115
129
|
success: (
|
|
116
130
|
<>
|
|
@@ -131,11 +145,20 @@ export function AddEditNodePage() {
|
|
|
131
145
|
values.name,
|
|
132
146
|
values.display_name,
|
|
133
147
|
values.description,
|
|
134
|
-
values.
|
|
148
|
+
values.type === 'metric'
|
|
149
|
+
? `SELECT ${values.aggregate_expression} FROM ${values.upstream_node}`
|
|
150
|
+
: values.query,
|
|
135
151
|
values.mode,
|
|
136
152
|
values.primary_key ? primaryKeyToList(values.primary_key) : null,
|
|
153
|
+
values.metric_direction,
|
|
154
|
+
values.metric_unit,
|
|
155
|
+
values.significant_digits,
|
|
156
|
+
values.required_dimensions,
|
|
157
|
+
);
|
|
158
|
+
const tagsResponse = await djClient.tagsNode(
|
|
159
|
+
values.name,
|
|
160
|
+
values.tags.map(tag => tag),
|
|
137
161
|
);
|
|
138
|
-
const tagsResponse = await djClient.tagsNode(values.name, values.tags);
|
|
139
162
|
if ((status === 200 || status === 201) && tagsResponse.status === 200) {
|
|
140
163
|
setStatus({
|
|
141
164
|
success: (
|
|
@@ -147,31 +170,15 @@ export function AddEditNodePage() {
|
|
|
147
170
|
});
|
|
148
171
|
} else {
|
|
149
172
|
setStatus({
|
|
150
|
-
failure: `${json.message}
|
|
173
|
+
failure: `${json.message}`,
|
|
151
174
|
});
|
|
152
175
|
}
|
|
153
176
|
};
|
|
154
177
|
|
|
155
|
-
const namespaceInput = (
|
|
156
|
-
<div className="NamespaceInput">
|
|
157
|
-
<ErrorMessage name="namespace" component="span" />
|
|
158
|
-
<label htmlFor="react-select-3-input">Namespace</label>
|
|
159
|
-
<FormikSelect
|
|
160
|
-
selectOptions={namespaces}
|
|
161
|
-
formikFieldName="namespace"
|
|
162
|
-
placeholder="Choose Namespace"
|
|
163
|
-
defaultValue={{
|
|
164
|
-
value: initialNamespace,
|
|
165
|
-
label: initialNamespace,
|
|
166
|
-
}}
|
|
167
|
-
/>
|
|
168
|
-
</div>
|
|
169
|
-
);
|
|
170
|
-
|
|
171
178
|
const fullNameInput = (
|
|
172
179
|
<div className="FullNameInput NodeCreationInput">
|
|
173
180
|
<ErrorMessage name="name" component="span" />
|
|
174
|
-
<label htmlFor="FullName">Full Name
|
|
181
|
+
<label htmlFor="FullName">Full Name *</label>
|
|
175
182
|
<FullNameField type="text" name="name" />
|
|
176
183
|
</div>
|
|
177
184
|
);
|
|
@@ -180,7 +187,65 @@ export function AddEditNodePage() {
|
|
|
180
187
|
return new Set(['transform', 'metric', 'dimension']).has(nodeType);
|
|
181
188
|
};
|
|
182
189
|
|
|
183
|
-
const
|
|
190
|
+
const getExistingNodeData = async name => {
|
|
191
|
+
const node = await djClient.getNodeForEditing(name);
|
|
192
|
+
if (node === null) {
|
|
193
|
+
return { message: `Node ${name} does not exist` };
|
|
194
|
+
}
|
|
195
|
+
const baseData = {
|
|
196
|
+
name: node.name,
|
|
197
|
+
type: node.type.toLowerCase(),
|
|
198
|
+
display_name: node.current.displayName,
|
|
199
|
+
description: node.current.description,
|
|
200
|
+
primary_key: node.current.primaryKey,
|
|
201
|
+
query: node.current.query,
|
|
202
|
+
tags: node.tags,
|
|
203
|
+
mode: node.current.mode.toLowerCase(),
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
if (node.type === 'METRIC') {
|
|
207
|
+
return {
|
|
208
|
+
...baseData,
|
|
209
|
+
metric_direction: node.current.metricMetadata?.direction?.toLowerCase(),
|
|
210
|
+
metric_unit: node.current.metricMetadata?.unit?.name?.toLowerCase(),
|
|
211
|
+
significant_digits: node.current.metricMetadata?.significantDigits,
|
|
212
|
+
required_dimensions: node.current.requiredDimensions.map(
|
|
213
|
+
dim => dim.name,
|
|
214
|
+
),
|
|
215
|
+
upstream_node: node.current.parents[0]?.name,
|
|
216
|
+
aggregate_expression: node.current.metricMetadata?.expression,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return baseData;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const runValidityChecks = (data, setNode, setMessage) => {
|
|
223
|
+
// Check if node exists
|
|
224
|
+
if (data.message !== undefined) {
|
|
225
|
+
setNode(null);
|
|
226
|
+
setMessage(`Node ${name} does not exist!`);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
// Check if node type can be edited
|
|
230
|
+
if (!nodeCanBeEdited(data.type)) {
|
|
231
|
+
setNode(null);
|
|
232
|
+
if (data.type === 'cube') {
|
|
233
|
+
navigate(`/nodes/${data.name}/edit-cube`);
|
|
234
|
+
}
|
|
235
|
+
setMessage(`Node ${name} is of type ${data.type} and cannot be edited`);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const updateFieldsWithNodeData = (
|
|
240
|
+
data,
|
|
241
|
+
setFieldValue,
|
|
242
|
+
setNode,
|
|
243
|
+
setSelectTags,
|
|
244
|
+
setSelectPrimaryKey,
|
|
245
|
+
setSelectUpstreamNode,
|
|
246
|
+
setSelectRequiredDims,
|
|
247
|
+
) => {
|
|
248
|
+
// Update fields with existing data to prepare for edit
|
|
184
249
|
const fields = [
|
|
185
250
|
'display_name',
|
|
186
251
|
'query',
|
|
@@ -189,62 +254,71 @@ export function AddEditNodePage() {
|
|
|
189
254
|
'primary_key',
|
|
190
255
|
'mode',
|
|
191
256
|
'tags',
|
|
257
|
+
'aggregate_expression',
|
|
258
|
+
'upstream_node',
|
|
259
|
+
'metric_unit',
|
|
260
|
+
'metric_direction',
|
|
261
|
+
'significant_digits',
|
|
192
262
|
];
|
|
193
263
|
fields.forEach(field => {
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
data[field] = data[field].join(', ');
|
|
264
|
+
if (field === 'tags') {
|
|
265
|
+
setFieldValue(
|
|
266
|
+
field,
|
|
267
|
+
data[field].map(tag => tag.name),
|
|
268
|
+
);
|
|
200
269
|
} else {
|
|
201
270
|
setFieldValue(field, data[field] || '', false);
|
|
202
271
|
}
|
|
203
272
|
});
|
|
204
|
-
|
|
273
|
+
setNode(data);
|
|
205
274
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
{
|
|
211
|
-
|
|
275
|
+
// For react-select fields, we have to explicitly set the entire
|
|
276
|
+
// field rather than just the values
|
|
277
|
+
setSelectTags(
|
|
278
|
+
<TagsField
|
|
279
|
+
defaultValue={data.tags.map(t => {
|
|
280
|
+
return { value: t.name, label: t.displayName };
|
|
281
|
+
})}
|
|
282
|
+
/>,
|
|
212
283
|
);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
}, [action, djClient, djClient.metrics]);
|
|
230
|
-
|
|
231
|
-
// Get list of tags
|
|
232
|
-
useEffect(() => {
|
|
233
|
-
const fetchData = async () => {
|
|
234
|
-
const tags = await djClient.listTags();
|
|
235
|
-
setTags(
|
|
236
|
-
tags.map(tag => ({
|
|
237
|
-
value: tag.name,
|
|
238
|
-
label: tag.display_name,
|
|
239
|
-
})),
|
|
284
|
+
setSelectPrimaryKey(
|
|
285
|
+
<ColumnsSelect
|
|
286
|
+
defaultValue={data.primary_key}
|
|
287
|
+
fieldName="primary_key"
|
|
288
|
+
label="Primary Key"
|
|
289
|
+
isMulti={true}
|
|
290
|
+
/>,
|
|
291
|
+
);
|
|
292
|
+
if (data.required_dimensions) {
|
|
293
|
+
setSelectRequiredDims(
|
|
294
|
+
<RequiredDimensionsSelect
|
|
295
|
+
defaultValue={data.required_dimensions.map(dim => {
|
|
296
|
+
return { value: dim, label: dim };
|
|
297
|
+
})}
|
|
298
|
+
/>,
|
|
240
299
|
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
|
|
300
|
+
}
|
|
301
|
+
setSelectUpstreamNode(
|
|
302
|
+
<UpstreamNodeField
|
|
303
|
+
defaultValue={{
|
|
304
|
+
value: data.upstream_node,
|
|
305
|
+
label: data.upstream_node,
|
|
306
|
+
}}
|
|
307
|
+
/>,
|
|
308
|
+
);
|
|
309
|
+
};
|
|
244
310
|
|
|
245
311
|
return (
|
|
246
312
|
<div className="mid">
|
|
247
|
-
<NamespaceHeader
|
|
313
|
+
<NamespaceHeader
|
|
314
|
+
namespace={
|
|
315
|
+
initialNamespace
|
|
316
|
+
? initialNamespace
|
|
317
|
+
: name
|
|
318
|
+
? name.substring(0, name.lastIndexOf('.'))
|
|
319
|
+
: ''
|
|
320
|
+
}
|
|
321
|
+
/>
|
|
248
322
|
<div className="card">
|
|
249
323
|
<div className="card-header">
|
|
250
324
|
{pageTitle}
|
|
@@ -252,135 +326,142 @@ export function AddEditNodePage() {
|
|
|
252
326
|
<Formik
|
|
253
327
|
initialValues={initialValues}
|
|
254
328
|
validate={validator}
|
|
255
|
-
onSubmit={
|
|
329
|
+
onSubmit={async (values, { setSubmitting, setStatus }) => {
|
|
330
|
+
try {
|
|
331
|
+
for (const handler of submitHandlers) {
|
|
332
|
+
await handler(values, { setSubmitting, setStatus });
|
|
333
|
+
}
|
|
334
|
+
} catch (error) {
|
|
335
|
+
console.error('Error in submission', error);
|
|
336
|
+
} finally {
|
|
337
|
+
setSubmitting(false);
|
|
338
|
+
}
|
|
339
|
+
}}
|
|
256
340
|
>
|
|
257
341
|
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
258
342
|
const [node, setNode] = useState([]);
|
|
343
|
+
const [selectPrimaryKey, setSelectPrimaryKey] = useState(null);
|
|
344
|
+
const [selectRequiredDims, setSelectRequiredDims] =
|
|
345
|
+
useState(null);
|
|
346
|
+
const [selectUpstreamNode, setSelectUpstreamNode] =
|
|
347
|
+
useState(null);
|
|
259
348
|
const [selectTags, setSelectTags] = useState(null);
|
|
260
349
|
const [message, setMessage] = useState('');
|
|
261
350
|
|
|
262
|
-
const tagsInput = (
|
|
263
|
-
<div
|
|
264
|
-
className="TagsInput"
|
|
265
|
-
style={{ width: '25%', margin: '1rem 0 1rem 1.2rem' }}
|
|
266
|
-
>
|
|
267
|
-
<ErrorMessage name="tags" component="span" />
|
|
268
|
-
<label htmlFor="react-select-3-input">Tags</label>
|
|
269
|
-
<span data-testid="select-tags">
|
|
270
|
-
{action === Action.Edit ? (
|
|
271
|
-
selectTags
|
|
272
|
-
) : (
|
|
273
|
-
<FormikSelect
|
|
274
|
-
isMulti={true}
|
|
275
|
-
selectOptions={tags}
|
|
276
|
-
formikFieldName="tags"
|
|
277
|
-
placeholder="Choose Tags"
|
|
278
|
-
/>
|
|
279
|
-
)}
|
|
280
|
-
</span>
|
|
281
|
-
</div>
|
|
282
|
-
);
|
|
283
|
-
|
|
284
351
|
useEffect(() => {
|
|
285
352
|
const fetchData = async () => {
|
|
286
353
|
if (action === Action.Edit) {
|
|
287
|
-
const data = await
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
if (!nodeCanBeEdited(data.type)) {
|
|
298
|
-
setNode(null);
|
|
299
|
-
setMessage(
|
|
300
|
-
`Node ${name} is of type ${data.type} and cannot be edited`,
|
|
301
|
-
);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Update fields with existing data to prepare for edit
|
|
306
|
-
updateFieldsWithNodeData(data, setFieldValue);
|
|
307
|
-
setNode(data);
|
|
308
|
-
setSelectTags(
|
|
309
|
-
<FormikSelect
|
|
310
|
-
isMulti={true}
|
|
311
|
-
selectOptions={tags}
|
|
312
|
-
formikFieldName="tags"
|
|
313
|
-
placeholder="Choose Tags"
|
|
314
|
-
defaultValue={data.tags.map(t => {
|
|
315
|
-
return { value: t.name, label: t.display_name };
|
|
316
|
-
})}
|
|
317
|
-
/>,
|
|
354
|
+
const data = await getExistingNodeData(name);
|
|
355
|
+
runValidityChecks(data, setNode, setMessage);
|
|
356
|
+
updateFieldsWithNodeData(
|
|
357
|
+
data,
|
|
358
|
+
setFieldValue,
|
|
359
|
+
setNode,
|
|
360
|
+
setSelectTags,
|
|
361
|
+
setSelectPrimaryKey,
|
|
362
|
+
setSelectUpstreamNode,
|
|
363
|
+
setSelectRequiredDims,
|
|
318
364
|
);
|
|
319
365
|
}
|
|
320
366
|
};
|
|
321
367
|
fetchData().catch(console.error);
|
|
322
|
-
}, [setFieldValue
|
|
368
|
+
}, [setFieldValue]);
|
|
323
369
|
return (
|
|
324
370
|
<Form>
|
|
325
371
|
{displayMessageAfterSubmit(status)}
|
|
326
372
|
{action === Action.Edit && message ? (
|
|
327
|
-
|
|
373
|
+
<AlertMessage message={message} />
|
|
328
374
|
) : (
|
|
329
375
|
<>
|
|
330
|
-
{action === Action.Add
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
<Field
|
|
337
|
-
type="text"
|
|
338
|
-
name="display_name"
|
|
339
|
-
id="displayName"
|
|
340
|
-
placeholder="Human readable display name"
|
|
341
|
-
/>
|
|
342
|
-
</div>
|
|
376
|
+
{action === Action.Add ? (
|
|
377
|
+
<NamespaceField initialNamespace={initialNamespace} />
|
|
378
|
+
) : (
|
|
379
|
+
staticFieldsInEdit(node)
|
|
380
|
+
)}
|
|
381
|
+
<DisplayNameField />
|
|
343
382
|
{action === Action.Add ? fullNameInput : ''}
|
|
344
|
-
<
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
383
|
+
<DescriptionField />
|
|
384
|
+
<br />
|
|
385
|
+
{nodeType === 'metric' || node?.type === 'metric' ? (
|
|
386
|
+
action === Action.Edit ? (
|
|
387
|
+
selectUpstreamNode
|
|
388
|
+
) : (
|
|
389
|
+
<UpstreamNodeField />
|
|
390
|
+
)
|
|
391
|
+
) : (
|
|
392
|
+
''
|
|
393
|
+
)}
|
|
394
|
+
<br />
|
|
395
|
+
<br />
|
|
396
|
+
{nodeType === 'metric' || node.type === 'metric' ? (
|
|
397
|
+
<MetricQueryField
|
|
398
|
+
djClient={djClient}
|
|
399
|
+
value={
|
|
400
|
+
node.aggregate_expression
|
|
401
|
+
? node.aggregate_expression
|
|
402
|
+
: ''
|
|
403
|
+
}
|
|
353
404
|
/>
|
|
354
|
-
|
|
355
|
-
<div className="QueryInput NodeCreationInput">
|
|
356
|
-
<ErrorMessage name="query" component="span" />
|
|
357
|
-
<label htmlFor="Query">Query</label>
|
|
405
|
+
) : (
|
|
358
406
|
<NodeQueryField
|
|
359
407
|
djClient={djClient}
|
|
360
408
|
value={node.query ? node.query : ''}
|
|
361
409
|
/>
|
|
362
|
-
|
|
363
|
-
<
|
|
364
|
-
|
|
365
|
-
<
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
410
|
+
)}
|
|
411
|
+
<br />
|
|
412
|
+
{nodeType === 'metric' || node.type === 'metric' ? (
|
|
413
|
+
<MetricMetadataFields />
|
|
414
|
+
) : (
|
|
415
|
+
''
|
|
416
|
+
)}
|
|
417
|
+
{nodeType !== 'metric' && node.type !== 'metric' ? (
|
|
418
|
+
action === Action.Edit ? (
|
|
419
|
+
selectPrimaryKey
|
|
420
|
+
) : (
|
|
421
|
+
<ColumnsSelect
|
|
422
|
+
defaultValue={[]}
|
|
423
|
+
fieldName="primary_key"
|
|
424
|
+
label="Primary Key"
|
|
425
|
+
isMulti={true}
|
|
426
|
+
/>
|
|
427
|
+
)
|
|
428
|
+
) : action === Action.Edit ? (
|
|
429
|
+
selectRequiredDims
|
|
430
|
+
) : (
|
|
431
|
+
<RequiredDimensionsSelect />
|
|
432
|
+
)}
|
|
433
|
+
{Object.entries(extensions).map(
|
|
434
|
+
([key, ExtensionComponent]) => (
|
|
435
|
+
<div key={key} className="mt-4 border-t pt-4">
|
|
436
|
+
<ExtensionComponent
|
|
437
|
+
node={node}
|
|
438
|
+
action={action}
|
|
439
|
+
registerSubmitHandler={(
|
|
440
|
+
onSubmit,
|
|
441
|
+
{ prepend } = {},
|
|
442
|
+
) => {
|
|
443
|
+
if (!submitHandlers.includes(onSubmit)) {
|
|
444
|
+
if (prepend) {
|
|
445
|
+
submitHandlers.unshift(onSubmit);
|
|
446
|
+
} else {
|
|
447
|
+
submitHandlers.push(onSubmit);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}}
|
|
451
|
+
/>
|
|
452
|
+
</div>
|
|
453
|
+
),
|
|
454
|
+
)}
|
|
455
|
+
{action === Action.Edit ? selectTags : <TagsField />}
|
|
456
|
+
<NodeModeField />
|
|
457
|
+
|
|
382
458
|
<button type="submit" disabled={isSubmitting}>
|
|
383
|
-
{
|
|
459
|
+
{isSubmitting ? (
|
|
460
|
+
<LoadingIcon />
|
|
461
|
+
) : (
|
|
462
|
+
(action === Action.Add ? 'Create ' : 'Save ') +
|
|
463
|
+
(nodeType ? nodeType : '')
|
|
464
|
+
)}
|
|
384
465
|
</button>
|
|
385
466
|
</>
|
|
386
467
|
)}
|