Skip to content

Commit db2b4bb

Browse files
refactor: Refactor error messages helpers for start form (#1053)
* Search attribute component Signed-off-by: Assem Hafez <assem.hafez@uber.com> * Fix type check Signed-off-by: Assem Hafez <assem.hafez@uber.com> * revert errors to have key and value Signed-off-by: Assem Hafez <assem.hafez@uber.com> * Address the nits * change button label * Create hook for fetching search attributes Signed-off-by: Assem Hafez <assem.hafez@uber.com> * Refactor error message helpers * feat: Create hook for fetching search attributes (#1046) * Create hook for fetching search attributes Signed-off-by: Assem Hafez <assem.hafez@uber.com> * use stringifyUrl Signed-off-by: Assem Hafez <assem.hafez@uber.com> --------- Signed-off-by: Assem Hafez <assem.hafez@uber.com> * fix: propagate user headers between internal api requests (#1049) * propagate user headers from server renders * fix tests * dynamically import headers * log propagated header keys * add test cases * import headers inline * Add import/named to lint rules (#1050) *Add import/named rule to .eslintrc to enable detecting import issues * Use deep imports for Lodash * Fix other misc lint issues (unused imports/variables) Signed-off-by: Adhitya Mamallan <adhitya.mamallan@uber.com> * remove type Signed-off-by: Assem Hafez <assem.hafez@uber.com> * lint fixes Signed-off-by: Assem Hafez <assem.hafez@uber.com> --------- Signed-off-by: Assem Hafez <assem.hafez@uber.com> Signed-off-by: Adhitya Mamallan <adhitya.mamallan@uber.com> Co-authored-by: Adhitya Mamallan <adhitya.mamallan@uber.com>
1 parent a2df99b commit db2b4bb

13 files changed

+365
-76
lines changed

src/components/multi-json-input/multi-json-input.types.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ export type Props = {
33
placeholder?: string;
44
value?: string[];
55
onChange: (value: string[]) => void;
6-
error?: string | string[];
6+
error?: string | Array<string | undefined>;
77
addButtonText?: string;
88
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import getFieldErrorMessage from '../get-field-error-message';
2+
3+
describe('getFieldErrorMessage', () => {
4+
it('should return undefined when field has no error', () => {
5+
const fieldErrors = {};
6+
7+
const result = getFieldErrorMessage(fieldErrors, 'name');
8+
9+
expect(result).toBeUndefined();
10+
});
11+
12+
it('should return string message for simple field error', () => {
13+
const fieldErrors = {
14+
name: {
15+
message: 'Task list name is required',
16+
type: 'required',
17+
},
18+
};
19+
20+
const result = getFieldErrorMessage(fieldErrors, 'name');
21+
22+
expect(result).toBe('Task list name is required');
23+
});
24+
25+
it('should handle nested field paths with dot notation', () => {
26+
const fieldErrors = {
27+
retryPolicy: {
28+
initialIntervalSeconds: {
29+
message: 'Invalid initial interval',
30+
type: 'invalid',
31+
},
32+
},
33+
};
34+
35+
const result = getFieldErrorMessage(
36+
fieldErrors,
37+
'retryPolicy.initialIntervalSeconds'
38+
);
39+
40+
expect(result).toBe('Invalid initial interval');
41+
});
42+
43+
it('should return undefined for non-existent nested field', () => {
44+
const fieldErrors = {
45+
retryPolicy: {
46+
initialIntervalSeconds: {
47+
message: 'Invalid initial interval',
48+
type: 'invalid',
49+
},
50+
},
51+
};
52+
53+
const result = getFieldErrorMessage(fieldErrors, 'retryPolicy.nonExistent');
54+
55+
expect(result).toBeUndefined();
56+
});
57+
});
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import getFieldObjectErrorMessages from '../get-field-object-error-messages';
2+
3+
describe('getFieldObjectErrorMessages', () => {
4+
it('should return undefined when field has no error', () => {
5+
const fieldErrors = {};
6+
7+
const result = getFieldObjectErrorMessages(fieldErrors, 'cronSchedule');
8+
9+
expect(result).toBeUndefined();
10+
});
11+
12+
it('should return string message when field has a simple message error', () => {
13+
const fieldErrors = {
14+
cronSchedule: {
15+
message: 'Invalid cron schedule',
16+
type: 'invalid',
17+
},
18+
};
19+
20+
const result = getFieldObjectErrorMessages(fieldErrors, 'cronSchedule');
21+
22+
expect(result).toBe('Invalid cron schedule');
23+
});
24+
25+
it('should return Record<string, string> for object field errors without message', () => {
26+
const fieldErrors = {
27+
cronSchedule: {
28+
minute: {
29+
message: 'Must be a positive number',
30+
type: 'min',
31+
},
32+
hour: {
33+
message: 'Must be at least 0',
34+
type: 'min',
35+
},
36+
},
37+
};
38+
39+
const result = getFieldObjectErrorMessages(fieldErrors, 'cronSchedule');
40+
41+
expect(result).toEqual({
42+
minute: 'Must be a positive number',
43+
hour: 'Must be at least 0',
44+
});
45+
});
46+
47+
it('should handle object errors with some fields having no message', () => {
48+
const fieldErrors = {
49+
cronSchedule: {
50+
minute: {
51+
message: 'Invalid minute',
52+
type: 'invalid',
53+
},
54+
hour: undefined,
55+
day: {
56+
message: 'Invalid day',
57+
type: 'invalid',
58+
},
59+
},
60+
};
61+
62+
const result = getFieldObjectErrorMessages(fieldErrors, 'cronSchedule');
63+
64+
expect(result).toEqual({
65+
minute: 'Invalid minute',
66+
day: 'Invalid day',
67+
});
68+
});
69+
70+
it('should return undefined when object has no error messages', () => {
71+
const fieldErrors = {
72+
cronSchedule: {
73+
minute: undefined,
74+
hour: undefined,
75+
},
76+
};
77+
78+
const result = getFieldObjectErrorMessages(fieldErrors, 'cronSchedule');
79+
80+
expect(result).toBeUndefined();
81+
});
82+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import getMultiJsonErrorMessage from '../get-multi-json-error-message';
2+
3+
describe('getMultiJsonErrorMessage', () => {
4+
it('should return undefined when field has no error', () => {
5+
const fieldErrors = {};
6+
7+
const result = getMultiJsonErrorMessage(fieldErrors, 'input');
8+
9+
expect(result).toBeUndefined();
10+
});
11+
12+
it('should return string message when field is not an array', () => {
13+
const fieldErrors = {
14+
input: {
15+
message: 'Single error',
16+
type: 'invalid',
17+
},
18+
};
19+
20+
const result = getMultiJsonErrorMessage(fieldErrors, 'input');
21+
22+
expect(result).toBe('Single error');
23+
});
24+
25+
it('should return array of messages for array field errors including undefined', () => {
26+
const fieldErrors = {
27+
input: [
28+
{ message: 'Invalid JSON at index 0', type: 'invalid' },
29+
{ message: 'Invalid JSON at index 1', type: 'invalid' },
30+
undefined,
31+
{ message: 'Invalid JSON at index 3', type: 'invalid' },
32+
],
33+
};
34+
35+
const result = getMultiJsonErrorMessage(fieldErrors, 'input');
36+
37+
expect(result).toEqual([
38+
'Invalid JSON at index 0',
39+
'Invalid JSON at index 1',
40+
undefined,
41+
'Invalid JSON at index 3',
42+
]);
43+
});
44+
45+
it('should return empty array for empty array of errors', () => {
46+
const fieldErrors = {
47+
input: [],
48+
};
49+
50+
const result = getMultiJsonErrorMessage(fieldErrors, 'input');
51+
52+
expect(result).toEqual([]);
53+
});
54+
55+
it('should return array including undefined for null/undefined entries', () => {
56+
const fieldErrors = {
57+
input: [
58+
{ message: 'Error 1', type: 'invalid' },
59+
undefined,
60+
{ message: 'Error 2', type: 'invalid' },
61+
null,
62+
],
63+
};
64+
65+
const result = getMultiJsonErrorMessage(fieldErrors, 'input');
66+
67+
expect(result).toEqual(['Error 1', undefined, 'Error 2', undefined]);
68+
});
69+
70+
it('should handle arrays with empty entries', () => {
71+
// array with [Empty] entries
72+
const fieldErrors = {
73+
input: new Array(3),
74+
};
75+
fieldErrors.input[1] = { message: 'Error 1', type: 'invalid' };
76+
77+
const result = getMultiJsonErrorMessage(fieldErrors, 'input');
78+
79+
expect(result).toEqual([undefined, 'Error 1', undefined]);
80+
});
81+
});
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import get from 'lodash/get';
2+
3+
/**
4+
* Extracts a simple error message from react-hook-form field errors.
5+
* This is the default behavior for most form fields.
6+
*/
7+
export default function getFieldErrorMessage(
8+
fieldErrors: Record<string, any>,
9+
field: string
10+
): string | undefined {
11+
const error = get(fieldErrors, field);
12+
13+
return error?.message;
14+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import get from 'lodash/get';
2+
import isObjectLike from 'lodash/isObjectLike';
3+
4+
/**
5+
* Extracts error messages from object fields errors (a field with subfields).
6+
* Returns a map of field names to error messages.
7+
*/
8+
export default function getFieldObjectErrorMessages(
9+
fieldErrors: Record<string, any>,
10+
field: string
11+
): string | Record<string, string> | undefined {
12+
const error = get(fieldErrors, field);
13+
14+
if (error?.message) {
15+
return error.message;
16+
}
17+
18+
if (isObjectLike(error)) {
19+
const entries = Object.entries(error);
20+
const result = entries.reduce(
21+
(acc, [key, err]: [string, any]) => {
22+
if (err?.message && typeof err.message === 'string') {
23+
acc[key] = err.message;
24+
}
25+
return acc;
26+
},
27+
{} as Record<string, string>
28+
);
29+
30+
return Object.keys(result).length > 0 ? result : undefined;
31+
}
32+
33+
return undefined;
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import get from 'lodash/get';
2+
3+
/**
4+
* Extracts error messages from array field errors for multi-json-input components.
5+
* Returns only string error messages, filtering out any complex nested errors.
6+
*/
7+
export default function getMultiJsonErrorMessage(
8+
fieldErrors: Record<string, any>,
9+
field: string
10+
): string | Array<string | undefined> | undefined {
11+
const error = get(fieldErrors, field);
12+
13+
if (!error || !Array.isArray(error)) {
14+
return error?.message;
15+
}
16+
17+
const messages = error.map((err) => {
18+
if (!err) return undefined;
19+
return err.message;
20+
});
21+
22+
return messages;
23+
}

0 commit comments

Comments
 (0)