datajunction-ui 0.0.77 → 0.0.79

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.77",
3
+ "version": "0.0.79",
4
4
  "description": "DataJunction UI",
5
5
  "module": "src/index.tsx",
6
6
  "repository": {
@@ -21,10 +21,12 @@ export const ColumnsSelect = ({
21
21
 
22
22
  // The available columns, determined from validating the node query
23
23
  const [availableColumns, setAvailableColumns] = useState([]);
24
+ const [validationError, setValidationError] = useState(null);
24
25
  const selectableOptions = useMemo(() => {
25
26
  if (availableColumns && availableColumns.length > 0) {
26
27
  return availableColumns;
27
28
  }
29
+ return [];
28
30
  }, [availableColumns]);
29
31
 
30
32
  // Fetch columns by validating the latest node query
@@ -41,9 +43,18 @@ export const ColumnsSelect = ({
41
43
  setAvailableColumns(
42
44
  json.columns.map(col => ({ value: col.name, label: col.name })),
43
45
  );
46
+ setValidationError(null);
47
+ } else if (json?.message) {
48
+ setValidationError(json.message);
49
+ setAvailableColumns([]);
44
50
  }
45
51
  } catch (error) {
46
52
  console.error('Error fetching columns:', error);
53
+ setValidationError(
54
+ error.message ||
55
+ 'Failed to validate query. Please check your query syntax.',
56
+ );
57
+ setAvailableColumns([]);
47
58
  }
48
59
  };
49
60
 
@@ -57,6 +68,22 @@ export const ColumnsSelect = ({
57
68
  <label htmlFor={fieldName} style={labelStyle}>
58
69
  {label}
59
70
  </label>
71
+ {validationError && (
72
+ <div
73
+ className="validation-error"
74
+ style={{
75
+ color: '#d32f2f',
76
+ fontSize: '0.875rem',
77
+ marginBottom: '8px',
78
+ padding: '8px',
79
+ backgroundColor: '#ffebee',
80
+ borderRadius: '4px',
81
+ border: '1px solid #ef9a9a',
82
+ }}
83
+ >
84
+ <strong>Validation Error:</strong> {validationError}
85
+ </div>
86
+ )}
60
87
  <span data-testid={`select-${fieldName}`}>
61
88
  <FormikSelect
62
89
  className={isMulti ? 'MultiSelectInput' : 'SelectInput'}
@@ -41,7 +41,7 @@ export const FormikSelect = ({
41
41
 
42
42
  // Convert Formik field value to React Select format
43
43
  const getSelectValue = () => {
44
- if (!field.value) {
44
+ if (!field.value || !selectOptions) {
45
45
  return isMulti ? [] : null;
46
46
  }
47
47
 
@@ -270,7 +270,7 @@ describe('AddEditNodePage submission succeeded', () => {
270
270
  'neutral',
271
271
  'unitless',
272
272
  5,
273
- undefined,
273
+ [],
274
274
  ['dj'],
275
275
  { key1: 'value1', key2: 'value2' },
276
276
  );
@@ -72,4 +72,81 @@ describe('FormikSelect', () => {
72
72
  await userEvent.click(screen.getByRole('combobox')); // to open the dropdown
73
73
  expect(screen.getByText('basic.one')).toBeInTheDocument();
74
74
  });
75
+
76
+ it('handles undefined selectOptions gracefully for single-select', () => {
77
+ const utils = render(
78
+ <Formik initialValues={{ test: '' }} onSubmit={jest.fn()}>
79
+ <Form>
80
+ <FormikSelect
81
+ selectOptions={undefined}
82
+ formikFieldName="test"
83
+ placeholder="Choose Option"
84
+ />
85
+ </Form>
86
+ </Formik>,
87
+ );
88
+
89
+ // Should render without crashing
90
+ const selectInput = screen.getByRole('combobox');
91
+ expect(selectInput).toBeInTheDocument();
92
+ });
93
+
94
+ it('handles undefined selectOptions gracefully for multi-select', () => {
95
+ const utils = render(
96
+ <Formik initialValues={{ test: [] }} onSubmit={jest.fn()}>
97
+ <Form>
98
+ <FormikSelect
99
+ selectOptions={undefined}
100
+ formikFieldName="test"
101
+ placeholder="Choose Options"
102
+ isMulti={true}
103
+ />
104
+ </Form>
105
+ </Formik>,
106
+ );
107
+
108
+ // Should render without crashing
109
+ const selectInput = screen.getByRole('combobox');
110
+ expect(selectInput).toBeInTheDocument();
111
+ });
112
+
113
+ it('handles undefined selectOptions with existing field value for single-select', () => {
114
+ const utils = render(
115
+ <Formik initialValues={{ test: 'existing-value' }} onSubmit={jest.fn()}>
116
+ <Form>
117
+ <FormikSelect
118
+ selectOptions={undefined}
119
+ formikFieldName="test"
120
+ placeholder="Choose Option"
121
+ />
122
+ </Form>
123
+ </Formik>,
124
+ );
125
+
126
+ // Should render without crashing even when field has a value
127
+ const selectInput = screen.getByRole('combobox');
128
+ expect(selectInput).toBeInTheDocument();
129
+ });
130
+
131
+ it('handles undefined selectOptions with existing field value for multi-select', () => {
132
+ const utils = render(
133
+ <Formik
134
+ initialValues={{ test: ['value1', 'value2'] }}
135
+ onSubmit={jest.fn()}
136
+ >
137
+ <Form>
138
+ <FormikSelect
139
+ selectOptions={undefined}
140
+ formikFieldName="test"
141
+ placeholder="Choose Options"
142
+ isMulti={true}
143
+ />
144
+ </Form>
145
+ </Formik>,
146
+ );
147
+
148
+ // Should render without crashing even when field has values
149
+ const selectInput = screen.getByRole('combobox');
150
+ expect(selectInput).toBeInTheDocument();
151
+ });
75
152
  });
@@ -147,7 +147,10 @@ export function AddEditNodePage({ extensions = {} }) {
147
147
  values.primary_key ? primaryKeyToList(values.primary_key) : null,
148
148
  values.metric_direction,
149
149
  values.metric_unit,
150
- values.required_dimensions,
150
+ // Only send required_dimensions if it's set (for metrics)
151
+ values.required_dimensions && Array.isArray(values.required_dimensions)
152
+ ? values.required_dimensions
153
+ : undefined,
151
154
  parseCustomMetadata(values.custom_metadata),
152
155
  );
153
156
  if (status === 200 || status === 201) {
@@ -182,7 +185,10 @@ export function AddEditNodePage({ extensions = {} }) {
182
185
  values.metric_direction,
183
186
  values.metric_unit,
184
187
  values.significant_digits,
185
- values.required_dimensions,
188
+ // Only send required_dimensions if it's set (for metrics)
189
+ values.required_dimensions && Array.isArray(values.required_dimensions)
190
+ ? values.required_dimensions
191
+ : undefined,
186
192
  values.owners,
187
193
  parseCustomMetadata(values.custom_metadata),
188
194
  );
@@ -325,6 +331,7 @@ export function AddEditNodePage({ extensions = {} }) {
325
331
  'metric_unit',
326
332
  'metric_direction',
327
333
  'significant_digits',
334
+ 'required_dimensions',
328
335
  'owners',
329
336
  'custom_metadata',
330
337
  ];
@@ -673,23 +673,30 @@ export const DataJunctionAPI = {
673
673
  unit: metric_unit,
674
674
  }
675
675
  : null;
676
+ // Build request body, filtering out undefined values
677
+ const requestBody = {
678
+ name: name,
679
+ display_name: display_name,
680
+ description: description,
681
+ query: query,
682
+ mode: mode,
683
+ namespace: namespace,
684
+ primary_key: primary_key,
685
+ metric_metadata: metricMetadata,
686
+ required_dimensions: required_dimensions,
687
+ custom_metadata: custom_metadata,
688
+ };
689
+ // Remove undefined fields to avoid sending them to the API
690
+ Object.keys(requestBody).forEach(
691
+ key => requestBody[key] === undefined && delete requestBody[key],
692
+ );
693
+
676
694
  const response = await fetch(`${DJ_URL}/nodes/${nodeType}`, {
677
695
  method: 'POST',
678
696
  headers: {
679
697
  'Content-Type': 'application/json',
680
698
  },
681
- body: JSON.stringify({
682
- name: name,
683
- display_name: display_name,
684
- description: description,
685
- query: query,
686
- mode: mode,
687
- namespace: namespace,
688
- primary_key: primary_key,
689
- metric_metadata: metricMetadata,
690
- required_dimensions: required_dimensions,
691
- custom_metadata: custom_metadata,
692
- }),
699
+ body: JSON.stringify(requestBody),
693
700
  credentials: 'include',
694
701
  });
695
702
  return { status: response.status, json: await response.json() };
@@ -718,22 +725,29 @@ export const DataJunctionAPI = {
718
725
  significant_digits: significant_digits || null,
719
726
  }
720
727
  : null;
728
+ // Build request body, filtering out undefined values
729
+ const requestBody = {
730
+ display_name: display_name,
731
+ description: description,
732
+ query: query,
733
+ mode: mode,
734
+ primary_key: primary_key,
735
+ metric_metadata: metricMetadata,
736
+ required_dimensions: required_dimensions,
737
+ owners: owners,
738
+ custom_metadata: custom_metadata,
739
+ };
740
+ // Remove undefined fields to avoid sending them to the API
741
+ Object.keys(requestBody).forEach(
742
+ key => requestBody[key] === undefined && delete requestBody[key],
743
+ );
744
+
721
745
  const response = await fetch(`${DJ_URL}/nodes/${name}`, {
722
746
  method: 'PATCH',
723
747
  headers: {
724
748
  'Content-Type': 'application/json',
725
749
  },
726
- body: JSON.stringify({
727
- display_name: display_name,
728
- description: description,
729
- query: query,
730
- mode: mode,
731
- primary_key: primary_key,
732
- metric_metadata: metricMetadata,
733
- required_dimensions: required_dimensions,
734
- owners: owners,
735
- custom_metadata: custom_metadata,
736
- }),
750
+ body: JSON.stringify(requestBody),
737
751
  credentials: 'include',
738
752
  });
739
753
  return { status: response.status, json: await response.json() };