datajunction-ui 0.0.1-a42.dev0 → 0.0.1-a43.dev0
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/index.tsx +1 -0
- package/src/app/pages/AddEditNodePage/AlertMessage.jsx +10 -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 +2 -0
- package/src/app/pages/AddEditNodePage/FullNameField.jsx +3 -2
- package/src/app/pages/AddEditNodePage/MetricMetadataFields.jsx +60 -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 +5 -3
- package/src/app/pages/AddEditNodePage/PrimaryKeySelect.jsx +61 -0
- 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 +2 -1
- package/src/app/pages/AddEditNodePage/__tests__/AddEditNodePageFormSuccess.test.jsx +150 -14
- package/src/app/pages/AddEditNodePage/__tests__/index.test.jsx +35 -8
- package/src/app/pages/AddEditNodePage/index.jsx +177 -232
- package/src/app/pages/CubeBuilderPage/MetricsSelect.jsx +0 -1
- package/src/app/pages/CubeBuilderPage/__tests__/index.test.jsx +1 -1
- package/src/app/pages/CubeBuilderPage/index.jsx +1 -1
- package/src/app/pages/NodePage/AddMaterializationPopover.jsx +0 -1
- package/src/app/pages/NodePage/NodeHistory.jsx +1 -1
- package/src/app/pages/NodePage/NodeInfoTab.jsx +54 -13
- package/src/app/pages/NodePage/__tests__/NodePage.test.jsx +34 -28
- package/src/app/pages/NodePage/__tests__/__snapshots__/NodePage.test.jsx.snap +2 -18
- package/src/app/pages/NodePage/index.jsx +30 -27
- package/src/app/pages/Root/index.tsx +3 -2
- package/src/app/services/DJService.js +37 -0
- package/src/app/services/__tests__/DJService.test.jsx +23 -0
- package/src/mocks/mockNodes.jsx +63 -0
- package/src/styles/index.css +6 -0
- package/src/styles/node-creation.scss +63 -5
- package/dj.internal.db +0 -0
|
@@ -9,12 +9,21 @@ import NamespaceHeader from '../../components/NamespaceHeader';
|
|
|
9
9
|
import { useContext, useEffect, useState } from 'react';
|
|
10
10
|
import DJClientContext from '../../providers/djclient';
|
|
11
11
|
import 'styles/node-creation.scss';
|
|
12
|
-
import AlertIcon from '../../icons/AlertIcon';
|
|
13
12
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
14
13
|
import { FullNameField } from './FullNameField';
|
|
15
|
-
import {
|
|
14
|
+
import { MetricQueryField } from './MetricQueryField';
|
|
15
|
+
import { displayMessageAfterSubmit } from '../../../utils/form';
|
|
16
|
+
import { PrimaryKeySelect } from './PrimaryKeySelect';
|
|
16
17
|
import { NodeQueryField } from './NodeQueryField';
|
|
17
|
-
import {
|
|
18
|
+
import { MetricMetadataFields } from './MetricMetadataFields';
|
|
19
|
+
import { UpstreamNodeField } from './UpstreamNodeField';
|
|
20
|
+
import { TagsField } from './TagsField';
|
|
21
|
+
import { NamespaceField } from './NamespaceField';
|
|
22
|
+
import { AlertMessage } from './AlertMessage';
|
|
23
|
+
import { DisplayNameField } from './DisplayNameField';
|
|
24
|
+
import { DescriptionField } from './DescriptionField';
|
|
25
|
+
import { NodeModeField } from './NodeModeField';
|
|
26
|
+
import { RequiredDimensionsSelect } from './RequiredDimensionsSelect';
|
|
18
27
|
|
|
19
28
|
class Action {
|
|
20
29
|
static Add = new Action('add');
|
|
@@ -32,20 +41,15 @@ export function AddEditNodePage() {
|
|
|
32
41
|
let { nodeType, initialNamespace, name } = useParams();
|
|
33
42
|
const action = name !== undefined ? Action.Edit : Action.Add;
|
|
34
43
|
|
|
35
|
-
const [namespaces, setNamespaces] = useState([]);
|
|
36
|
-
const [tags, setTags] = useState([]);
|
|
37
|
-
const [metricUnits, setMetricUnits] = useState([]);
|
|
38
|
-
const [metricDirections, setMetricDirections] = useState([]);
|
|
39
|
-
|
|
40
44
|
const initialValues = {
|
|
41
45
|
name: action === Action.Edit ? name : '',
|
|
42
46
|
namespace: action === Action.Add ? initialNamespace : '',
|
|
43
47
|
display_name: '',
|
|
44
48
|
query: '',
|
|
45
|
-
|
|
49
|
+
type: nodeType,
|
|
46
50
|
description: '',
|
|
47
51
|
primary_key: '',
|
|
48
|
-
mode: '
|
|
52
|
+
mode: 'published',
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
const validator = values => {
|
|
@@ -53,7 +57,7 @@ export function AddEditNodePage() {
|
|
|
53
57
|
if (!values.name) {
|
|
54
58
|
errors.name = 'Required';
|
|
55
59
|
}
|
|
56
|
-
if (!values.query) {
|
|
60
|
+
if (values.type !== 'metric' && !values.query) {
|
|
57
61
|
errors.query = 'Required';
|
|
58
62
|
}
|
|
59
63
|
return errors;
|
|
@@ -98,7 +102,7 @@ export function AddEditNodePage() {
|
|
|
98
102
|
);
|
|
99
103
|
|
|
100
104
|
const primaryKeyToList = primaryKey => {
|
|
101
|
-
return primaryKey.
|
|
105
|
+
return primaryKey.map(columnName => columnName.trim());
|
|
102
106
|
};
|
|
103
107
|
|
|
104
108
|
const createNode = async (values, setStatus) => {
|
|
@@ -107,12 +111,15 @@ export function AddEditNodePage() {
|
|
|
107
111
|
values.name,
|
|
108
112
|
values.display_name,
|
|
109
113
|
values.description,
|
|
110
|
-
values.
|
|
114
|
+
values.type === 'metric'
|
|
115
|
+
? `SELECT ${values.aggregate_expression} FROM ${values.upstream_node}`
|
|
116
|
+
: values.query,
|
|
111
117
|
values.mode,
|
|
112
118
|
values.namespace,
|
|
113
119
|
values.primary_key ? primaryKeyToList(values.primary_key) : null,
|
|
114
120
|
values.metric_direction,
|
|
115
121
|
values.metric_unit,
|
|
122
|
+
values.required_dimensions,
|
|
116
123
|
);
|
|
117
124
|
if (status === 200 || status === 201) {
|
|
118
125
|
if (values.tags) {
|
|
@@ -138,11 +145,14 @@ export function AddEditNodePage() {
|
|
|
138
145
|
values.name,
|
|
139
146
|
values.display_name,
|
|
140
147
|
values.description,
|
|
141
|
-
values.
|
|
148
|
+
values.type === 'metric'
|
|
149
|
+
? `SELECT ${values.aggregate_expression} FROM ${values.upstream_node}`
|
|
150
|
+
: values.query,
|
|
142
151
|
values.mode,
|
|
143
152
|
values.primary_key ? primaryKeyToList(values.primary_key) : null,
|
|
144
153
|
values.metric_direction,
|
|
145
154
|
values.metric_unit,
|
|
155
|
+
values.required_dimensions,
|
|
146
156
|
);
|
|
147
157
|
const tagsResponse = await djClient.tagsNode(
|
|
148
158
|
values.name,
|
|
@@ -164,22 +174,6 @@ export function AddEditNodePage() {
|
|
|
164
174
|
}
|
|
165
175
|
};
|
|
166
176
|
|
|
167
|
-
const namespaceInput = (
|
|
168
|
-
<div className="NamespaceInput">
|
|
169
|
-
<ErrorMessage name="namespace" component="span" />
|
|
170
|
-
<label htmlFor="react-select-3-input">Namespace *</label>
|
|
171
|
-
<FormikSelect
|
|
172
|
-
selectOptions={namespaces}
|
|
173
|
-
formikFieldName="namespace"
|
|
174
|
-
placeholder="Choose Namespace"
|
|
175
|
-
defaultValue={{
|
|
176
|
-
value: initialNamespace,
|
|
177
|
-
label: initialNamespace,
|
|
178
|
-
}}
|
|
179
|
-
/>
|
|
180
|
-
</div>
|
|
181
|
-
);
|
|
182
|
-
|
|
183
177
|
const fullNameInput = (
|
|
184
178
|
<div className="FullNameInput NodeCreationInput">
|
|
185
179
|
<ErrorMessage name="name" component="span" />
|
|
@@ -192,7 +186,57 @@ export function AddEditNodePage() {
|
|
|
192
186
|
return new Set(['transform', 'metric', 'dimension']).has(nodeType);
|
|
193
187
|
};
|
|
194
188
|
|
|
195
|
-
const
|
|
189
|
+
const getExistingNodeData = async name => {
|
|
190
|
+
const data = await djClient.node(name);
|
|
191
|
+
if (data.type === 'metric') {
|
|
192
|
+
const metric = await djClient.metric(name);
|
|
193
|
+
data.upstream_node = metric.upstream_node;
|
|
194
|
+
data.expression = metric.expression;
|
|
195
|
+
data.required_dimensions = metric.required_dimensions;
|
|
196
|
+
}
|
|
197
|
+
return data;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const primaryKeyFromNode = node => {
|
|
201
|
+
return node.columns
|
|
202
|
+
.filter(
|
|
203
|
+
col =>
|
|
204
|
+
col.attributes &&
|
|
205
|
+
col.attributes.filter(
|
|
206
|
+
attr => attr.attribute_type.name === 'primary_key',
|
|
207
|
+
).length > 0,
|
|
208
|
+
)
|
|
209
|
+
.map(col => col.name);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const runValidityChecks = (data, setNode, setMessage) => {
|
|
213
|
+
// Check if node exists
|
|
214
|
+
if (data.message !== undefined) {
|
|
215
|
+
setNode(null);
|
|
216
|
+
setMessage(`Node ${name} does not exist!`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check if node type can be edited
|
|
221
|
+
if (!nodeCanBeEdited(data.type)) {
|
|
222
|
+
setNode(null);
|
|
223
|
+
if (data.type === 'cube') {
|
|
224
|
+
navigate(`/nodes/${data.name}/edit-cube`);
|
|
225
|
+
}
|
|
226
|
+
setMessage(`Node ${name} is of type ${data.type} and cannot be edited`);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const updateFieldsWithNodeData = (
|
|
231
|
+
data,
|
|
232
|
+
setFieldValue,
|
|
233
|
+
setNode,
|
|
234
|
+
setSelectTags,
|
|
235
|
+
setSelectPrimaryKey,
|
|
236
|
+
setSelectUpstreamNode,
|
|
237
|
+
setSelectRequiredDims,
|
|
238
|
+
) => {
|
|
239
|
+
// Update fields with existing data to prepare for edit
|
|
196
240
|
const fields = [
|
|
197
241
|
'display_name',
|
|
198
242
|
'query',
|
|
@@ -201,19 +245,13 @@ export function AddEditNodePage() {
|
|
|
201
245
|
'primary_key',
|
|
202
246
|
'mode',
|
|
203
247
|
'tags',
|
|
248
|
+
'expression',
|
|
249
|
+
'upstream_node',
|
|
204
250
|
];
|
|
205
|
-
const primaryKey = data
|
|
206
|
-
.filter(
|
|
207
|
-
col =>
|
|
208
|
-
col.attributes &&
|
|
209
|
-
col.attributes.filter(
|
|
210
|
-
attr => attr.attribute_type.name === 'primary_key',
|
|
211
|
-
).length > 0,
|
|
212
|
-
)
|
|
213
|
-
.map(col => col.name);
|
|
251
|
+
const primaryKey = primaryKeyFromNode(data);
|
|
214
252
|
fields.forEach(field => {
|
|
215
253
|
if (field === 'primary_key') {
|
|
216
|
-
setFieldValue(field, primaryKey
|
|
254
|
+
setFieldValue(field, primaryKey);
|
|
217
255
|
} else if (field === 'tags') {
|
|
218
256
|
setFieldValue(
|
|
219
257
|
field,
|
|
@@ -232,57 +270,47 @@ export function AddEditNodePage() {
|
|
|
232
270
|
data.metric_metadata.unit.name.toLowerCase(),
|
|
233
271
|
);
|
|
234
272
|
}
|
|
235
|
-
|
|
273
|
+
if (data.expression) {
|
|
274
|
+
setFieldValue('aggregate_expression', data.expression);
|
|
275
|
+
}
|
|
276
|
+
if (data.upstream_node) {
|
|
277
|
+
setFieldValue('upstream_node', data.upstream_node);
|
|
278
|
+
}
|
|
279
|
+
setNode(data);
|
|
236
280
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
{
|
|
242
|
-
|
|
281
|
+
// For react-select fields, we have to explicitly set the entire
|
|
282
|
+
// field rather than just the values
|
|
283
|
+
setSelectTags(
|
|
284
|
+
<TagsField
|
|
285
|
+
defaultValue={data.tags.map(t => {
|
|
286
|
+
return { value: t.name, label: t.display_name };
|
|
287
|
+
})}
|
|
288
|
+
/>,
|
|
289
|
+
);
|
|
290
|
+
setSelectPrimaryKey(
|
|
291
|
+
<PrimaryKeySelect
|
|
292
|
+
defaultValue={primaryKey.map(col => {
|
|
293
|
+
return { value: col, label: col };
|
|
294
|
+
})}
|
|
295
|
+
/>,
|
|
296
|
+
);
|
|
297
|
+
setSelectRequiredDims(
|
|
298
|
+
<RequiredDimensionsSelect
|
|
299
|
+
defaultValue={data.required_dimensions.map(dim => {
|
|
300
|
+
return { value: dim, label: dim };
|
|
301
|
+
})}
|
|
302
|
+
/>,
|
|
303
|
+
);
|
|
304
|
+
setSelectUpstreamNode(
|
|
305
|
+
<UpstreamNodeField
|
|
306
|
+
defaultValue={{
|
|
307
|
+
value: data.upstream_node,
|
|
308
|
+
label: data.upstream_node,
|
|
309
|
+
}}
|
|
310
|
+
/>,
|
|
243
311
|
);
|
|
244
312
|
};
|
|
245
313
|
|
|
246
|
-
// Get namespaces, only necessary when creating a node
|
|
247
|
-
useEffect(() => {
|
|
248
|
-
if (action === Action.Add) {
|
|
249
|
-
const fetchData = async () => {
|
|
250
|
-
const namespaces = await djClient.namespaces();
|
|
251
|
-
setNamespaces(
|
|
252
|
-
namespaces.map(m => ({
|
|
253
|
-
value: m['namespace'],
|
|
254
|
-
label: m['namespace'],
|
|
255
|
-
})),
|
|
256
|
-
);
|
|
257
|
-
};
|
|
258
|
-
fetchData().catch(console.error);
|
|
259
|
-
}
|
|
260
|
-
}, [action, djClient, djClient.metrics]);
|
|
261
|
-
|
|
262
|
-
// Get list of tags
|
|
263
|
-
useEffect(() => {
|
|
264
|
-
const fetchData = async () => {
|
|
265
|
-
const tags = await djClient.listTags();
|
|
266
|
-
setTags(
|
|
267
|
-
tags.map(tag => ({
|
|
268
|
-
value: tag.name,
|
|
269
|
-
label: tag.display_name,
|
|
270
|
-
})),
|
|
271
|
-
);
|
|
272
|
-
};
|
|
273
|
-
fetchData().catch(console.error);
|
|
274
|
-
}, [djClient, djClient.listTags]);
|
|
275
|
-
|
|
276
|
-
// Get metric metadata values
|
|
277
|
-
useEffect(() => {
|
|
278
|
-
const fetchData = async () => {
|
|
279
|
-
const metadata = await djClient.listMetricMetadata();
|
|
280
|
-
setMetricDirections(metadata.directions);
|
|
281
|
-
setMetricUnits(metadata.units);
|
|
282
|
-
};
|
|
283
|
-
fetchData().catch(console.error);
|
|
284
|
-
}, [djClient]);
|
|
285
|
-
|
|
286
314
|
return (
|
|
287
315
|
<div className="mid">
|
|
288
316
|
<NamespaceHeader namespace="" />
|
|
@@ -297,172 +325,89 @@ export function AddEditNodePage() {
|
|
|
297
325
|
>
|
|
298
326
|
{function Render({ isSubmitting, status, setFieldValue }) {
|
|
299
327
|
const [node, setNode] = useState([]);
|
|
328
|
+
const [selectPrimaryKey, setSelectPrimaryKey] = useState(null);
|
|
329
|
+
const [selectRequiredDims, setSelectRequiredDims] =
|
|
330
|
+
useState(null);
|
|
331
|
+
const [selectUpstreamNode, setSelectUpstreamNode] =
|
|
332
|
+
useState(null);
|
|
300
333
|
const [selectTags, setSelectTags] = useState(null);
|
|
301
334
|
const [message, setMessage] = useState('');
|
|
302
335
|
|
|
303
|
-
const tagsInput = (
|
|
304
|
-
<div
|
|
305
|
-
className="TagsInput"
|
|
306
|
-
style={{ width: '25%', margin: '1rem 0 1rem 1.2rem' }}
|
|
307
|
-
>
|
|
308
|
-
<ErrorMessage name="tags" component="span" />
|
|
309
|
-
<label htmlFor="react-select-3-input">Tags</label>
|
|
310
|
-
<span data-testid="select-tags">
|
|
311
|
-
{action === Action.Edit ? (
|
|
312
|
-
selectTags
|
|
313
|
-
) : (
|
|
314
|
-
<FormikSelect
|
|
315
|
-
isMulti={true}
|
|
316
|
-
selectOptions={tags}
|
|
317
|
-
formikFieldName="tags"
|
|
318
|
-
placeholder="Choose Tags"
|
|
319
|
-
/>
|
|
320
|
-
)}
|
|
321
|
-
</span>
|
|
322
|
-
</div>
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
const metricMetadataInput = (
|
|
326
|
-
<>
|
|
327
|
-
<div
|
|
328
|
-
className="MetricDirectionInput NodeCreationInput"
|
|
329
|
-
style={{ width: '25%' }}
|
|
330
|
-
>
|
|
331
|
-
<ErrorMessage name="metric_direction" component="span" />
|
|
332
|
-
<label htmlFor="MetricDirection">Metric Direction</label>
|
|
333
|
-
<Field
|
|
334
|
-
as="select"
|
|
335
|
-
name="metric_direction"
|
|
336
|
-
id="MetricDirection"
|
|
337
|
-
>
|
|
338
|
-
<option value=""></option>
|
|
339
|
-
{metricDirections.map(direction => (
|
|
340
|
-
<option value={direction}>
|
|
341
|
-
{labelize(direction)}
|
|
342
|
-
</option>
|
|
343
|
-
))}
|
|
344
|
-
</Field>
|
|
345
|
-
</div>
|
|
346
|
-
<div
|
|
347
|
-
className="MetricUnitInput NodeCreationInput"
|
|
348
|
-
style={{ width: '25%' }}
|
|
349
|
-
>
|
|
350
|
-
<ErrorMessage name="metric_unit" component="span" />
|
|
351
|
-
<label htmlFor="MetricUnit">Metric Unit</label>
|
|
352
|
-
<Field as="select" name="metric_unit" id="MetricUnit">
|
|
353
|
-
<option value=""></option>
|
|
354
|
-
{metricUnits.map(unit => (
|
|
355
|
-
<option value={unit.name}>{unit.label}</option>
|
|
356
|
-
))}
|
|
357
|
-
</Field>
|
|
358
|
-
</div>
|
|
359
|
-
</>
|
|
360
|
-
);
|
|
361
|
-
|
|
362
336
|
useEffect(() => {
|
|
363
337
|
const fetchData = async () => {
|
|
364
338
|
if (action === Action.Edit) {
|
|
365
|
-
const data = await
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
if (!nodeCanBeEdited(data.type)) {
|
|
376
|
-
setNode(null);
|
|
377
|
-
if (data.type === 'cube') {
|
|
378
|
-
navigate(`/nodes/${data.name}/edit-cube`);
|
|
379
|
-
}
|
|
380
|
-
setMessage(
|
|
381
|
-
`Node ${name} is of type ${data.type} and cannot be edited`,
|
|
382
|
-
);
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Update fields with existing data to prepare for edit
|
|
387
|
-
updateFieldsWithNodeData(data, setFieldValue);
|
|
388
|
-
setNode(data);
|
|
389
|
-
setSelectTags(
|
|
390
|
-
<FormikSelect
|
|
391
|
-
isMulti={true}
|
|
392
|
-
selectOptions={tags}
|
|
393
|
-
formikFieldName="tags"
|
|
394
|
-
placeholder="Choose Tags"
|
|
395
|
-
defaultValue={data.tags.map(t => {
|
|
396
|
-
return { value: t.name, label: t.display_name };
|
|
397
|
-
})}
|
|
398
|
-
/>,
|
|
339
|
+
const data = await getExistingNodeData(name);
|
|
340
|
+
runValidityChecks(data, setNode, setMessage);
|
|
341
|
+
updateFieldsWithNodeData(
|
|
342
|
+
data,
|
|
343
|
+
setFieldValue,
|
|
344
|
+
setNode,
|
|
345
|
+
setSelectTags,
|
|
346
|
+
setSelectPrimaryKey,
|
|
347
|
+
setSelectUpstreamNode,
|
|
348
|
+
setSelectRequiredDims,
|
|
399
349
|
);
|
|
400
350
|
}
|
|
401
351
|
};
|
|
402
352
|
fetchData().catch(console.error);
|
|
403
|
-
}, [setFieldValue
|
|
353
|
+
}, [setFieldValue]);
|
|
404
354
|
return (
|
|
405
355
|
<Form>
|
|
406
356
|
{displayMessageAfterSubmit(status)}
|
|
407
357
|
{action === Action.Edit && message ? (
|
|
408
|
-
|
|
358
|
+
<AlertMessage message={message} />
|
|
409
359
|
) : (
|
|
410
360
|
<>
|
|
411
|
-
{action === Action.Add
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
<Field
|
|
418
|
-
type="text"
|
|
419
|
-
name="display_name"
|
|
420
|
-
id="displayName"
|
|
421
|
-
placeholder="Human readable display name"
|
|
422
|
-
/>
|
|
423
|
-
</div>
|
|
361
|
+
{action === Action.Add ? (
|
|
362
|
+
<NamespaceField initialNamespace={initialNamespace} />
|
|
363
|
+
) : (
|
|
364
|
+
staticFieldsInEdit(node)
|
|
365
|
+
)}
|
|
366
|
+
<DisplayNameField />
|
|
424
367
|
{action === Action.Add ? fullNameInput : ''}
|
|
425
|
-
<
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
368
|
+
<DescriptionField />
|
|
369
|
+
<br />
|
|
370
|
+
{nodeType === 'metric' || node?.type === 'metric' ? (
|
|
371
|
+
action === Action.Edit ? (
|
|
372
|
+
selectUpstreamNode
|
|
373
|
+
) : (
|
|
374
|
+
<UpstreamNodeField />
|
|
375
|
+
)
|
|
376
|
+
) : (
|
|
377
|
+
''
|
|
378
|
+
)}
|
|
379
|
+
<br />
|
|
380
|
+
<br />
|
|
381
|
+
{nodeType === 'metric' || node.type === 'metric' ? (
|
|
382
|
+
<MetricQueryField
|
|
383
|
+
djClient={djClient}
|
|
384
|
+
value={node.expression ? node.expression : ''}
|
|
434
385
|
/>
|
|
435
|
-
|
|
436
|
-
{nodeType === 'metric' || node.type === 'metric'
|
|
437
|
-
? metricMetadataInput
|
|
438
|
-
: ''}
|
|
439
|
-
<div className="QueryInput NodeCreationInput">
|
|
440
|
-
<ErrorMessage name="query" component="span" />
|
|
441
|
-
<label htmlFor="Query">Query *</label>
|
|
386
|
+
) : (
|
|
442
387
|
<NodeQueryField
|
|
443
388
|
djClient={djClient}
|
|
444
389
|
value={node.query ? node.query : ''}
|
|
445
390
|
/>
|
|
446
|
-
|
|
447
|
-
<
|
|
448
|
-
|
|
449
|
-
<
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
391
|
+
)}
|
|
392
|
+
<br />
|
|
393
|
+
{nodeType === 'metric' || node.type === 'metric' ? (
|
|
394
|
+
<MetricMetadataFields />
|
|
395
|
+
) : (
|
|
396
|
+
''
|
|
397
|
+
)}
|
|
398
|
+
{nodeType !== 'metric' && node.type !== 'metric' ? (
|
|
399
|
+
action === Action.Edit ? (
|
|
400
|
+
selectPrimaryKey
|
|
401
|
+
) : (
|
|
402
|
+
<PrimaryKeySelect />
|
|
403
|
+
)
|
|
404
|
+
) : action === Action.Edit ? (
|
|
405
|
+
selectRequiredDims
|
|
406
|
+
) : (
|
|
407
|
+
<RequiredDimensionsSelect />
|
|
408
|
+
)}
|
|
409
|
+
{action === Action.Edit ? selectTags : <TagsField />}
|
|
410
|
+
<NodeModeField />
|
|
466
411
|
|
|
467
412
|
<button type="submit" disabled={isSubmitting}>
|
|
468
413
|
{action === Action.Add ? 'Create' : 'Save'} {nodeType}
|
|
@@ -38,7 +38,6 @@ export const MetricsSelect = ({ cube }) => {
|
|
|
38
38
|
|
|
39
39
|
const metrics = await djClient.metrics();
|
|
40
40
|
setMetrics(metrics.map(m => ({ value: m, label: m })));
|
|
41
|
-
console.log('metrics', metrics);
|
|
42
41
|
};
|
|
43
42
|
fetchData().catch(console.error);
|
|
44
43
|
}, [djClient, djClient.metrics, cube]);
|
|
@@ -40,7 +40,6 @@ export default function AddMaterializationPopover({ node, onSubmit }) {
|
|
|
40
40
|
setSubmitting(false);
|
|
41
41
|
const config = JSON.parse(values.config);
|
|
42
42
|
config.lookback_window = values.lookback_window;
|
|
43
|
-
console.log('values', values);
|
|
44
43
|
const response = await djClient.materialize(
|
|
45
44
|
values.node,
|
|
46
45
|
values.job_type,
|
|
@@ -11,8 +11,8 @@ export default function NodeHistory({ node, djClient }) {
|
|
|
11
11
|
const fetchData = async () => {
|
|
12
12
|
if (node) {
|
|
13
13
|
const data = await djClient.history('node', node.name);
|
|
14
|
-
setHistory(data);
|
|
15
14
|
const revisions = await djClient.revisions(node.name);
|
|
15
|
+
setHistory(data);
|
|
16
16
|
setRevisions(revisions);
|
|
17
17
|
}
|
|
18
18
|
};
|
|
@@ -20,6 +20,7 @@ export default function NodeInfoTab({ node }) {
|
|
|
20
20
|
</div>
|
|
21
21
|
));
|
|
22
22
|
const djClient = useContext(DJClientContext).DataJunctionAPI;
|
|
23
|
+
|
|
23
24
|
useEffect(() => {
|
|
24
25
|
const fetchData = async () => {
|
|
25
26
|
if (checked === true) {
|
|
@@ -38,6 +39,24 @@ export default function NodeInfoTab({ node }) {
|
|
|
38
39
|
function toggle(value) {
|
|
39
40
|
return !value;
|
|
40
41
|
}
|
|
42
|
+
const metricQueryDiv = (
|
|
43
|
+
<div className="list-group-item d-flex">
|
|
44
|
+
<div className="gap-2 w-100 justify-content-between py-3">
|
|
45
|
+
<div style={{ marginBottom: '30px' }}>
|
|
46
|
+
<h6 className="mb-0 w-100">Upstream Node</h6>
|
|
47
|
+
<p>
|
|
48
|
+
<a href={`/nodes/${node?.upstream_node}`}>{node?.upstream_node}</a>
|
|
49
|
+
</p>
|
|
50
|
+
</div>
|
|
51
|
+
<div>
|
|
52
|
+
<h6 className="mb-0 w-100">Aggregate Expression</h6>
|
|
53
|
+
<SyntaxHighlighter language="sql" style={foundation}>
|
|
54
|
+
{node?.expression}
|
|
55
|
+
</SyntaxHighlighter>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
41
60
|
const queryDiv = node?.query ? (
|
|
42
61
|
<div className="list-group-item d-flex">
|
|
43
62
|
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
@@ -153,8 +172,39 @@ export default function NodeInfoTab({ node }) {
|
|
|
153
172
|
) : (
|
|
154
173
|
<></>
|
|
155
174
|
);
|
|
175
|
+
|
|
176
|
+
const primaryKeyOrRequiredDims = (
|
|
177
|
+
<div style={{ maxWidth: '25%' }}>
|
|
178
|
+
<h6 className="mb-0 w-100">
|
|
179
|
+
{node?.type !== 'metric' ? 'Primary Key' : 'Required Dimensions'}
|
|
180
|
+
</h6>
|
|
181
|
+
<p
|
|
182
|
+
className="mb-0 opacity-75"
|
|
183
|
+
role="dialog"
|
|
184
|
+
aria-hidden="false"
|
|
185
|
+
aria-label={
|
|
186
|
+
node?.type !== 'metric' ? 'PrimaryKey' : 'RequiredDimensions'
|
|
187
|
+
}
|
|
188
|
+
>
|
|
189
|
+
{node?.type !== 'metric'
|
|
190
|
+
? node?.primary_key?.map(dim => (
|
|
191
|
+
<span className="rounded-pill badge bg-secondary-soft PrimaryKey">
|
|
192
|
+
<a href={`/nodes/${node?.name}`}>{dim}</a>
|
|
193
|
+
</span>
|
|
194
|
+
))
|
|
195
|
+
: node?.required_dimensions?.map(dim => (
|
|
196
|
+
<span className="rounded-pill badge bg-secondary-soft PrimaryKey">
|
|
197
|
+
<a href={`/nodes/${node?.upstream_node}`}>{dim}</a>
|
|
198
|
+
</span>
|
|
199
|
+
))}
|
|
200
|
+
</p>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
156
203
|
return (
|
|
157
|
-
<div
|
|
204
|
+
<div
|
|
205
|
+
className="list-group align-items-center justify-content-between flex-md-row gap-2"
|
|
206
|
+
style={{ minWidth: '700px' }}
|
|
207
|
+
>
|
|
158
208
|
<ListGroupItem label="Description" value={node?.description} />
|
|
159
209
|
<div className="list-group-item d-flex">
|
|
160
210
|
<div className="d-flex gap-2 w-100 justify-content-between py-3">
|
|
@@ -223,17 +273,7 @@ export default function NodeInfoTab({ node }) {
|
|
|
223
273
|
{nodeTags}
|
|
224
274
|
</p>
|
|
225
275
|
</div>
|
|
226
|
-
|
|
227
|
-
<h6 className="mb-0 w-100">Primary Key</h6>
|
|
228
|
-
<p
|
|
229
|
-
className="mb-0 opacity-75"
|
|
230
|
-
role="dialog"
|
|
231
|
-
aria-hidden="false"
|
|
232
|
-
aria-label="PrimaryKey"
|
|
233
|
-
>
|
|
234
|
-
{node?.primary_key?.join(', ')}
|
|
235
|
-
</p>
|
|
236
|
-
</div>
|
|
276
|
+
{primaryKeyOrRequiredDims}
|
|
237
277
|
<div>
|
|
238
278
|
<h6 className="mb-0 w-100">Last Updated</h6>
|
|
239
279
|
<p
|
|
@@ -248,7 +288,8 @@ export default function NodeInfoTab({ node }) {
|
|
|
248
288
|
</div>
|
|
249
289
|
</div>
|
|
250
290
|
{metricMetadataDiv}
|
|
251
|
-
{node?.type !== 'cube' ? queryDiv : ''}
|
|
291
|
+
{node?.type !== 'cube' && node?.type !== 'metric' ? queryDiv : ''}
|
|
292
|
+
{node?.type === 'metric' ? metricQueryDiv : ''}
|
|
252
293
|
{cubeElementsDiv}
|
|
253
294
|
</div>
|
|
254
295
|
);
|