datajunction-ui 0.0.11 → 0.0.13
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
CHANGED
|
@@ -1,61 +1,144 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Custom metadata field component for nodes
|
|
3
3
|
*/
|
|
4
|
-
import { ErrorMessage, Field } from 'formik';
|
|
5
|
-
import
|
|
4
|
+
import { ErrorMessage, Field, useFormikContext } from 'formik';
|
|
5
|
+
import CodeMirror from '@uiw/react-codemirror';
|
|
6
|
+
import { langs } from '@uiw/codemirror-extensions-langs';
|
|
7
|
+
import { useState, useEffect } from 'react';
|
|
6
8
|
|
|
7
|
-
export const CustomMetadataField = ({
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
);
|
|
11
|
-
|
|
9
|
+
export const CustomMetadataField = ({ value }) => {
|
|
10
|
+
const formik = useFormikContext();
|
|
11
|
+
const jsonExt = langs.json();
|
|
12
|
+
const [hasError, setHasError] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (!value || value === '') {
|
|
16
|
+
setHasError(false);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
12
19
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
setJsonString(value);
|
|
20
|
+
const stringValue =
|
|
21
|
+
typeof value === 'string' ? value : JSON.stringify(value, null, 2);
|
|
16
22
|
|
|
17
23
|
try {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
setError('');
|
|
21
|
-
} else {
|
|
22
|
-
const parsed = JSON.parse(value);
|
|
23
|
-
setFieldValue('custom_metadata', parsed);
|
|
24
|
-
setError('');
|
|
25
|
-
}
|
|
24
|
+
JSON.parse(stringValue);
|
|
25
|
+
setHasError(false);
|
|
26
26
|
} catch (err) {
|
|
27
|
-
|
|
27
|
+
setHasError(true);
|
|
28
|
+
}
|
|
29
|
+
}, [value]);
|
|
30
|
+
|
|
31
|
+
const formatValue = value => {
|
|
32
|
+
if (value === null || value === undefined) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
if (typeof value === 'string') {
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
return JSON.stringify(value, null, 2);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const updateFormik = val => {
|
|
42
|
+
formik.setFieldValue('custom_metadata', val);
|
|
43
|
+
formik.setFieldTouched('custom_metadata', true);
|
|
44
|
+
|
|
45
|
+
if (!val || val.trim() === '') {
|
|
46
|
+
setHasError(false);
|
|
47
|
+
} else {
|
|
48
|
+
try {
|
|
49
|
+
JSON.parse(val);
|
|
50
|
+
setHasError(false);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
setHasError(true);
|
|
53
|
+
}
|
|
28
54
|
}
|
|
29
55
|
};
|
|
30
56
|
|
|
31
57
|
return (
|
|
32
|
-
<div className="NodeCreationInput"
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
<div className="QueryInput NodeCreationInput">
|
|
59
|
+
<details>
|
|
60
|
+
<summary style={{ cursor: 'pointer' }}>
|
|
61
|
+
<label
|
|
62
|
+
style={{
|
|
63
|
+
paddingLeft: '3px',
|
|
64
|
+
display: 'inline-block',
|
|
65
|
+
pointerEvents: 'none',
|
|
66
|
+
}}
|
|
67
|
+
>
|
|
68
|
+
Custom Metadata (JSON)
|
|
69
|
+
</label>
|
|
70
|
+
</summary>
|
|
71
|
+
<ErrorMessage name="custom_metadata" component="span" />
|
|
72
|
+
<Field
|
|
73
|
+
type="textarea"
|
|
74
|
+
style={{ display: 'none' }}
|
|
75
|
+
as="textarea"
|
|
76
|
+
name="custom_metadata"
|
|
77
|
+
id="CustomMetadata"
|
|
78
|
+
validate={value => {
|
|
79
|
+
if (!value || value.trim() === '') {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const parsed = JSON.parse(value);
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
typeof parsed === 'object' &&
|
|
87
|
+
parsed !== null &&
|
|
88
|
+
!Array.isArray(parsed)
|
|
89
|
+
) {
|
|
90
|
+
const keys = Object.keys(parsed);
|
|
91
|
+
const originalKeyMatches = value.match(/"([^"]+)"\s*:/g);
|
|
92
|
+
if (
|
|
93
|
+
originalKeyMatches &&
|
|
94
|
+
originalKeyMatches.length > keys.length
|
|
95
|
+
) {
|
|
96
|
+
return 'Duplicate keys detected';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return undefined;
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return 'Invalid JSON format';
|
|
103
|
+
}
|
|
104
|
+
}}
|
|
105
|
+
/>
|
|
106
|
+
<div
|
|
107
|
+
role="button"
|
|
108
|
+
tabIndex={0}
|
|
109
|
+
className={`relative flex ${
|
|
110
|
+
hasError ? 'bg-red-900/20' : 'bg-[#282a36]'
|
|
111
|
+
}`}
|
|
112
|
+
style={{
|
|
113
|
+
border: hasError ? '2px solid #ef4444' : 'none',
|
|
114
|
+
borderRadius: '4px',
|
|
115
|
+
boxShadow: hasError ? '0 0 0 1px rgba(239, 68, 68, 0.3)' : 'none',
|
|
116
|
+
}}
|
|
117
|
+
>
|
|
118
|
+
<CodeMirror
|
|
119
|
+
id={'custom_metadata'}
|
|
120
|
+
name={'custom_metadata'}
|
|
121
|
+
extensions={[jsonExt]}
|
|
122
|
+
value={formatValue(value)}
|
|
123
|
+
placeholder={'{\n "key": "value"\n}'}
|
|
124
|
+
options={{
|
|
125
|
+
theme: 'default',
|
|
126
|
+
lineNumbers: true,
|
|
127
|
+
}}
|
|
128
|
+
width="100%"
|
|
129
|
+
height="200px"
|
|
130
|
+
style={{
|
|
131
|
+
margin: '0 0 23px 0',
|
|
132
|
+
flex: 1,
|
|
133
|
+
fontSize: '150%',
|
|
134
|
+
textAlign: 'left',
|
|
135
|
+
}}
|
|
136
|
+
onChange={(value, viewUpdate) => {
|
|
137
|
+
updateFormik(value);
|
|
138
|
+
}}
|
|
139
|
+
/>
|
|
140
|
+
</div>
|
|
141
|
+
</details>
|
|
59
142
|
</div>
|
|
60
143
|
);
|
|
61
144
|
};
|
|
@@ -203,7 +203,7 @@ describe('AddEditNodePage submission succeeded', () => {
|
|
|
203
203
|
'',
|
|
204
204
|
undefined,
|
|
205
205
|
['dj'],
|
|
206
|
-
|
|
206
|
+
null,
|
|
207
207
|
);
|
|
208
208
|
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledTimes(1);
|
|
209
209
|
expect(mockDjClient.DataJunctionAPI.tagsNode).toBeCalledWith(
|
|
@@ -109,6 +109,17 @@ export function AddEditNodePage({ extensions = {} }) {
|
|
|
109
109
|
return primaryKey.map(columnName => columnName.trim());
|
|
110
110
|
};
|
|
111
111
|
|
|
112
|
+
const parseCustomMetadata = customMetadata => {
|
|
113
|
+
if (!customMetadata || customMetadata.trim() === '') {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
return JSON.parse(customMetadata);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
112
123
|
const createNode = async (values, setStatus) => {
|
|
113
124
|
const { status, json } = await djClient.createNode(
|
|
114
125
|
nodeType,
|
|
@@ -124,7 +135,7 @@ export function AddEditNodePage({ extensions = {} }) {
|
|
|
124
135
|
values.metric_direction,
|
|
125
136
|
values.metric_unit,
|
|
126
137
|
values.required_dimensions,
|
|
127
|
-
values.custom_metadata,
|
|
138
|
+
parseCustomMetadata(values.custom_metadata),
|
|
128
139
|
);
|
|
129
140
|
if (status === 200 || status === 201) {
|
|
130
141
|
if (values.tags) {
|
|
@@ -160,7 +171,7 @@ export function AddEditNodePage({ extensions = {} }) {
|
|
|
160
171
|
values.significant_digits,
|
|
161
172
|
values.required_dimensions,
|
|
162
173
|
values.owners,
|
|
163
|
-
values.custom_metadata,
|
|
174
|
+
parseCustomMetadata(values.custom_metadata),
|
|
164
175
|
);
|
|
165
176
|
const tagsResponse = await djClient.tagsNode(
|
|
166
177
|
values.name,
|
|
@@ -283,6 +294,9 @@ export function AddEditNodePage({ extensions = {} }) {
|
|
|
283
294
|
field,
|
|
284
295
|
data[field].map(owner => owner.username),
|
|
285
296
|
);
|
|
297
|
+
} else if (field === 'custom_metadata') {
|
|
298
|
+
const value = data[field] ? JSON.stringify(data[field], null, 2) : '';
|
|
299
|
+
setFieldValue(field, value, false);
|
|
286
300
|
} else {
|
|
287
301
|
setFieldValue(field, data[field] || '', false);
|
|
288
302
|
}
|
|
@@ -352,6 +366,8 @@ export function AddEditNodePage({ extensions = {} }) {
|
|
|
352
366
|
<Formik
|
|
353
367
|
initialValues={initialValues}
|
|
354
368
|
validate={validator}
|
|
369
|
+
validateOnChange={true}
|
|
370
|
+
validateOnBlur={true}
|
|
355
371
|
onSubmit={async (values, { setSubmitting, setStatus }) => {
|
|
356
372
|
try {
|
|
357
373
|
for (const handler of submitHandlers) {
|
|
@@ -364,7 +380,16 @@ export function AddEditNodePage({ extensions = {} }) {
|
|
|
364
380
|
}
|
|
365
381
|
}}
|
|
366
382
|
>
|
|
367
|
-
{function Render(
|
|
383
|
+
{function Render(formikProps) {
|
|
384
|
+
const {
|
|
385
|
+
isSubmitting,
|
|
386
|
+
status,
|
|
387
|
+
setFieldValue,
|
|
388
|
+
errors,
|
|
389
|
+
touched,
|
|
390
|
+
isValid,
|
|
391
|
+
dirty,
|
|
392
|
+
} = formikProps;
|
|
368
393
|
const [node, setNode] = useState([]);
|
|
369
394
|
const [selectPrimaryKey, setSelectPrimaryKey] = useState(null);
|
|
370
395
|
const [selectRequiredDims, setSelectRequiredDims] =
|
|
@@ -449,9 +474,6 @@ export function AddEditNodePage({ extensions = {} }) {
|
|
|
449
474
|
) : (
|
|
450
475
|
''
|
|
451
476
|
)}
|
|
452
|
-
<CustomMetadataField
|
|
453
|
-
initialValue={node.custom_metadata || {}}
|
|
454
|
-
/>
|
|
455
477
|
{nodeType !== 'metric' && node.type !== 'metric' ? (
|
|
456
478
|
action === Action.Edit ? (
|
|
457
479
|
selectPrimaryKey
|
|
@@ -468,6 +490,11 @@ export function AddEditNodePage({ extensions = {} }) {
|
|
|
468
490
|
) : (
|
|
469
491
|
<RequiredDimensionsSelect />
|
|
470
492
|
)}
|
|
493
|
+
<CustomMetadataField
|
|
494
|
+
value={
|
|
495
|
+
node.custom_metadata ? node.custom_metadata : ''
|
|
496
|
+
}
|
|
497
|
+
/>
|
|
471
498
|
{Object.entries(extensions).map(
|
|
472
499
|
([key, ExtensionComponent]) => (
|
|
473
500
|
<div key={key} className="mt-4 border-t pt-4">
|
|
@@ -493,7 +520,10 @@ export function AddEditNodePage({ extensions = {} }) {
|
|
|
493
520
|
{action === Action.Edit ? selectTags : <TagsField />}
|
|
494
521
|
<NodeModeField />
|
|
495
522
|
|
|
496
|
-
<button
|
|
523
|
+
<button
|
|
524
|
+
type="submit"
|
|
525
|
+
disabled={isSubmitting || !isValid}
|
|
526
|
+
>
|
|
497
527
|
{isSubmitting ? (
|
|
498
528
|
<LoadingIcon />
|
|
499
529
|
) : (
|