mirror of
https://github.com/inventree/InvenTree.git
synced 2026-11-10 02:47:49 +00:00
[bug] API form fixes (#13196)
* Improve API error feedback * Add 'message' field opt * Update error handling logic
This commit is contained in:
1 parent
5fe105b948
commit
c5c878d6af
1 file changed
+110
-23
@@ -40,6 +40,7 @@ import type {
|
||||
ApiFormFieldType,
|
||||
ApiFormProps
|
||||
} from '@lib/types/Forms';
|
||||
import { IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { isEquivalent } from '../../functions/comparison';
|
||||
import { constructField, extractAvailableFields } from '../../functions/forms';
|
||||
@@ -569,12 +570,35 @@ export function ApiForm({
|
||||
case 400:
|
||||
// Data validation errors
|
||||
const _nonFieldErrors: string[] = [];
|
||||
let _fieldErrorCount = 0;
|
||||
|
||||
const setFieldError = (path: string, message: string) => {
|
||||
_fieldErrorCount += 1;
|
||||
form.setError(path, { message: message });
|
||||
};
|
||||
|
||||
// Response keys which carry a general (non-field) error message
|
||||
const NON_FIELD_ERROR_KEYS: string[] = [
|
||||
'non_field_errors',
|
||||
'__all__',
|
||||
'detail',
|
||||
'error',
|
||||
'message',
|
||||
'reason'
|
||||
];
|
||||
|
||||
const processErrors = (errors: any, _path?: string) => {
|
||||
// Ignore empty values
|
||||
if (errors === null || errors === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle an array of errors
|
||||
if (Array.isArray(errors)) {
|
||||
errors.forEach((error: any) => {
|
||||
_nonFieldErrors.push(error.toString());
|
||||
if (typeof error === 'string') {
|
||||
_nonFieldErrors.push(error);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -585,42 +609,100 @@ export function ApiForm({
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore any other non-object values (numbers, booleans)
|
||||
if (typeof errors !== 'object') {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [k, v] of Object.entries(errors)) {
|
||||
const path = _path ? `${_path}.${k}` : k;
|
||||
|
||||
// Determine if field "k" is valid (exists and is visible)
|
||||
const field = fields[k];
|
||||
const valid = field && !field.hidden;
|
||||
|
||||
if (!valid || k == 'non_field_errors' || k == '__all__') {
|
||||
// Known error keys are displayed as non-field errors
|
||||
if (NON_FIELD_ERROR_KEYS.includes(k)) {
|
||||
processErrors(v);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof v === 'object' && Array.isArray(v)) {
|
||||
if (field?.field_type == 'table') {
|
||||
// Special handling for "table" fields - they have nested errors
|
||||
v.forEach((item: any, idx: number) => {
|
||||
for (const [key, value] of Object.entries(item)) {
|
||||
const path: string = `${k}.${idx}.${key}`;
|
||||
if (Array.isArray(value)) {
|
||||
form.setError(path, { message: value.join(', ') });
|
||||
}
|
||||
// Nested errors (below the top level) cannot be mapped to a form field,
|
||||
// so they are displayed as non-field errors rather than being dropped
|
||||
if (_path) {
|
||||
processErrors(v);
|
||||
continue;
|
||||
}
|
||||
|
||||
const field = fields[k];
|
||||
|
||||
// Ignore top-level keys which do not correspond to a form field
|
||||
// (e.g. response metadata such as "transition" or "state")
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Errors against hidden fields are displayed as non-field errors
|
||||
if (field.hidden) {
|
||||
processErrors(v);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
field.field_type == 'table' &&
|
||||
typeof v === 'object' &&
|
||||
v !== null
|
||||
) {
|
||||
// Special handling for "table" fields - they have nested (per-row) errors.
|
||||
// DRF may return these as an array (one entry per row),
|
||||
// or as an object keyed by row index (only rows with errors)
|
||||
const rows: [string, any][] = Array.isArray(v)
|
||||
? v.map((item: any, idx: number) => [`${idx}`, item])
|
||||
: Object.entries(v);
|
||||
|
||||
rows.forEach(([idx, item]) => {
|
||||
// Row-level error (not associated with a particular column)
|
||||
if (
|
||||
typeof item !== 'object' ||
|
||||
item === null ||
|
||||
Array.isArray(item)
|
||||
) {
|
||||
processErrors(item);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(item)) {
|
||||
const rowPath: string = `${k}.${idx}.${key}`;
|
||||
if (Array.isArray(value)) {
|
||||
setFieldError(rowPath, value.join(', '));
|
||||
} else if (typeof value === 'string') {
|
||||
setFieldError(rowPath, value);
|
||||
} else {
|
||||
processErrors(value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Standard error handling for other fields
|
||||
form.setError(path, { message: v.join(', ') });
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (Array.isArray(v)) {
|
||||
// Standard error handling for other fields
|
||||
setFieldError(path, v.join(', '));
|
||||
} else if (typeof v === 'string') {
|
||||
form.setError(path, { message: v });
|
||||
setFieldError(path, v);
|
||||
} else {
|
||||
processErrors(v, path);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
processErrors(error.response.data);
|
||||
try {
|
||||
processErrors(error.response.data);
|
||||
} catch (err) {
|
||||
// Malformed error response - fall through to the generic message below
|
||||
console.error('Failed to process form errors:', err);
|
||||
}
|
||||
|
||||
// No errors could be extracted from the response
|
||||
if (_nonFieldErrors.length == 0 && _fieldErrorCount == 0) {
|
||||
_nonFieldErrors.push(
|
||||
t`An unknown error occurred. Refer to the browser console for details.`
|
||||
);
|
||||
}
|
||||
|
||||
setNonFieldErrors(_nonFieldErrors);
|
||||
props.onFormError?.(error, form);
|
||||
|
||||
@@ -710,7 +792,12 @@ export function ApiForm({
|
||||
{/* Form Fields */}
|
||||
<Stack gap='sm'>
|
||||
{(!isValid || nonFieldErrors.length > 0) && (
|
||||
<Alert radius='sm' color='red' title={t`Form Error`}>
|
||||
<Alert
|
||||
icon={<IconExclamationCircle />}
|
||||
radius='sm'
|
||||
color='red'
|
||||
title={t`Form Error`}
|
||||
>
|
||||
{nonFieldErrors.length > 0 ? (
|
||||
<Stack gap='xs'>
|
||||
{nonFieldErrors
|
||||
|
||||
Reference in new issue
Block a user