datajunction-ui 0.0.11 → 0.0.12

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,6 +1,6 @@
1
1
  {
2
2
  "name": "datajunction-ui",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "DataJunction Metrics Platform UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
@@ -2,59 +2,86 @@
2
2
  * Custom metadata field component for nodes
3
3
  */
4
4
  import { ErrorMessage, Field } from 'formik';
5
- import { useState } from 'react';
5
+ import { useEffect } from 'react';
6
6
 
7
- export const CustomMetadataField = ({ initialValue = {} }) => {
8
- const [jsonString, setJsonString] = useState(
9
- JSON.stringify(initialValue, null, 2),
10
- );
11
- const [error, setError] = useState('');
7
+ export const CustomMetadataField = () => {
8
+ const formatValue = value => {
9
+ if (value === null || value === undefined) {
10
+ return '';
11
+ }
12
+ if (typeof value === 'string') {
13
+ return value;
14
+ }
15
+ return JSON.stringify(value, null, 2);
16
+ };
12
17
 
13
18
  const handleChange = (e, setFieldValue) => {
14
- const value = e.target.value;
15
- setJsonString(value);
16
-
17
- try {
18
- if (value.trim() === '') {
19
- setFieldValue('custom_metadata', null);
20
- setError('');
21
- } else {
22
- const parsed = JSON.parse(value);
23
- setFieldValue('custom_metadata', parsed);
24
- setError('');
25
- }
26
- } catch (err) {
27
- setError('Invalid JSON format');
28
- }
19
+ setFieldValue('custom_metadata', e.target.value);
29
20
  };
30
21
 
31
22
  return (
32
23
  <div className="NodeCreationInput" style={{ marginTop: '20px' }}>
33
- <ErrorMessage name="custom_metadata" component="span" />
34
24
  <label htmlFor="CustomMetadata">Custom Metadata (JSON)</label>
35
- <Field name="custom_metadata">
36
- {({ field, form }) => (
37
- <div>
38
- <textarea
39
- id="CustomMetadata"
40
- value={jsonString}
41
- onChange={e => handleChange(e, form.setFieldValue)}
42
- style={{
43
- width: '100%',
44
- minHeight: '100px',
45
- fontFamily: 'monospace',
46
- fontSize: '12px',
47
- padding: '8px',
48
- border: error ? '1px solid red' : '1px solid #ccc',
49
- borderRadius: '4px',
50
- }}
51
- placeholder='{"key": "value"}'
52
- />
53
- {error && (
54
- <span style={{ color: 'red', fontSize: '12px' }}>{error}</span>
55
- )}
56
- </div>
57
- )}
25
+ <Field
26
+ name="custom_metadata"
27
+ validate={value => {
28
+ if (!value || value.trim() === '') {
29
+ return undefined;
30
+ }
31
+ try {
32
+ const parsed = JSON.parse(value);
33
+
34
+ if (
35
+ typeof parsed === 'object' &&
36
+ parsed !== null &&
37
+ !Array.isArray(parsed)
38
+ ) {
39
+ const keys = Object.keys(parsed);
40
+ const originalKeyMatches = value.match(/"([^"]+)"\s*:/g);
41
+ if (
42
+ originalKeyMatches &&
43
+ originalKeyMatches.length > keys.length
44
+ ) {
45
+ return 'Duplicate keys detected';
46
+ }
47
+ }
48
+
49
+ return undefined;
50
+ } catch (err) {
51
+ return 'Invalid JSON format';
52
+ }
53
+ }}
54
+ >
55
+ {({ field, form }) => {
56
+ const errorMessage = form.errors.custom_metadata;
57
+ const hasError = errorMessage && form.touched.custom_metadata;
58
+
59
+ return (
60
+ <div>
61
+ <textarea
62
+ id="CustomMetadata"
63
+ value={formatValue(field.value)}
64
+ onChange={e => handleChange(e, form.setFieldValue)}
65
+ onBlur={() => form.setFieldTouched('custom_metadata', true)}
66
+ style={{
67
+ width: '100%',
68
+ minHeight: '100px',
69
+ fontFamily: 'monospace',
70
+ fontSize: '12px',
71
+ padding: '8px',
72
+ border: hasError ? '1px solid red' : '1px solid #ccc',
73
+ borderRadius: '4px',
74
+ }}
75
+ placeholder="Define custom node metadata here..."
76
+ />
77
+ {hasError && (
78
+ <span style={{ color: 'red', fontSize: '12px' }}>
79
+ {errorMessage}
80
+ </span>
81
+ )}
82
+ </div>
83
+ );
84
+ }}
58
85
  </Field>
59
86
  </div>
60
87
  );
@@ -203,7 +203,7 @@ describe('AddEditNodePage submission succeeded', () => {
203
203
  '',
204
204
  undefined,
205
205
  ['dj'],
206
- '', // custom_metadata is set to '' when null in the form
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({ isSubmitting, status, setFieldValue }) {
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,7 @@ export function AddEditNodePage({ extensions = {} }) {
449
474
  ) : (
450
475
  ''
451
476
  )}
452
- <CustomMetadataField
453
- initialValue={node.custom_metadata || {}}
454
- />
477
+ <CustomMetadataField />
455
478
  {nodeType !== 'metric' && node.type !== 'metric' ? (
456
479
  action === Action.Edit ? (
457
480
  selectPrimaryKey
@@ -493,7 +516,10 @@ export function AddEditNodePage({ extensions = {} }) {
493
516
  {action === Action.Edit ? selectTags : <TagsField />}
494
517
  <NodeModeField />
495
518
 
496
- <button type="submit" disabled={isSubmitting}>
519
+ <button
520
+ type="submit"
521
+ disabled={isSubmitting || !isValid}
522
+ >
497
523
  {isSubmitting ? (
498
524
  <LoadingIcon />
499
525
  ) : (