Skip to content

Commit e875af5

Browse files
authored
fix(web): improve error message for required fields (#3209)
* fix(web): improve error message for required fields * chore: change error msg
1 parent 0b0dead commit e875af5

File tree

2 files changed

+66
-19
lines changed

2 files changed

+66
-19
lines changed

web/src/components/wizard/config/ConfigurationStep.tsx

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,29 @@ interface ConfigurationStepProps {
2727
onNext: () => void;
2828
}
2929

30+
// Helper function to parse server validation errors from API response
31+
function parseServerErrors(error: ApiError): Record<string, string> {
32+
const itemErrors: Record<string, string> = {};
33+
34+
// Check if error has structured item errors
35+
if (error.fieldErrors) {
36+
error.fieldErrors.forEach((itemError) => {
37+
// Pass through server error message directly - no client-side enhancement
38+
itemErrors[itemError.field] = itemError.message;
39+
});
40+
}
41+
42+
return itemErrors;
43+
};
44+
45+
// Helper function to get the general error message we show at the bottom of the page after a form submission
46+
export function getGeneralErrorMsgForSubmission(error: ApiError) {
47+
if (error.fieldErrors && error.fieldErrors.length > 0) {
48+
return "Please address the issues above before proceeding"
49+
}
50+
return error?.details || error?.message || 'Failed to save configuration'
51+
}
52+
3053
const ConfigurationStep: React.FC<ConfigurationStepProps> = ({ onNext }) => {
3154
const { text, target, mode } = useWizard();
3255
const { token } = useAuth();
@@ -145,21 +168,6 @@ const ConfigurationStep: React.FC<ConfigurationStepProps> = ({ onNext }) => {
145168
setItemToFocus(item);
146169
};
147170

148-
// Helper function to parse server validation errors from API response
149-
const parseServerErrors = (error: ApiError): Record<string, string> => {
150-
const itemErrors: Record<string, string> = {};
151-
152-
// Check if error has structured item errors
153-
if (error.fieldErrors) {
154-
error.fieldErrors.forEach((itemError) => {
155-
// Pass through server error message directly - no client-side enhancement
156-
itemErrors[itemError.field] = itemError.message;
157-
});
158-
}
159-
160-
return itemErrors;
161-
};
162-
163171
// Mutation to save config values
164172
const { mutate: submitConfigValues } = useMutation<void, ApiError>({
165173
mutationFn: async () => {
@@ -183,7 +191,7 @@ const ConfigurationStep: React.FC<ConfigurationStepProps> = ({ onNext }) => {
183191
onError: (error: ApiError) => {
184192
const parsedItemErrors = parseServerErrors(error);
185193
setItemErrors(parsedItemErrors);
186-
setGeneralError(error?.details || error?.message || 'Failed to save configuration');
194+
setGeneralError(getGeneralErrorMsgForSubmission(error));
187195

188196
// Focus on the first item with validation error
189197
const firstErrorItem = findFirstItemWithError(parsedItemErrors);

web/src/components/wizard/tests/ConfigurationStep.test.tsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { describe, it, expect, vi, beforeAll, afterEach, afterAll, beforeEach }
22
import { screen, waitFor, fireEvent } from "@testing-library/react";
33
import { setupServer } from "msw/node";
44
import { renderWithProviders } from "../../../test/setup.tsx";
5-
import ConfigurationStep from "../config/ConfigurationStep.tsx";
5+
import ConfigurationStep, { getGeneralErrorMsgForSubmission } from "../config/ConfigurationStep.tsx";
66
import { mockHandlers, type Target, type Mode } from "../../../test/mockHandlers.ts";
7+
import { ApiError } from "../../../api/error.ts";
78
import '@testing-library/jest-dom/vitest';
89

910
import type { components } from "../../../types/api";
@@ -1954,9 +1955,9 @@ describe.each([
19541955
expect(screen.getByText("Required Field is required")).toBeInTheDocument();
19551956
});
19561957

1957-
// Verify the raw server error message at the bottom
1958+
// Verify error component at the bottom exists
19581959
await waitFor(() => {
1959-
expect(screen.getByText("required fields not completed")).toBeInTheDocument();
1960+
expect(screen.getByTestId("config-submit-error")).toBeInTheDocument();
19601961
});
19611962

19621963
// Verify onNext was not called due to validation error
@@ -2788,4 +2789,42 @@ describe.each([
27882789
expect(submittedValues!.values.db_type).toEqual({ value: 'mysql' });
27892790
});
27902791
});
2792+
2793+
describe("getGeneralErrorMsgForSubmission", () => {
2794+
it("returns generic error message when error has field errors", () => {
2795+
const error = new ApiError(400, "Validation error");
2796+
error.fieldErrors = [
2797+
{ field: "app_name", message: "This field is required" },
2798+
];
2799+
2800+
const result = getGeneralErrorMsgForSubmission(error);
2801+
2802+
expect(result).toContain("Please address the issues above");
2803+
});
2804+
2805+
it("returns error.details when no field errors but details exist", () => {
2806+
const error = new ApiError(500, "Generic message");
2807+
error.details = "Detailed error message";
2808+
2809+
const result = getGeneralErrorMsgForSubmission(error);
2810+
2811+
expect(result).toBe("Detailed error message");
2812+
});
2813+
2814+
it("returns error.message when no field errors or details", () => {
2815+
const error = new ApiError(500, "Generic error message");
2816+
2817+
const result = getGeneralErrorMsgForSubmission(error);
2818+
2819+
expect(result).toBe("Generic error message");
2820+
});
2821+
2822+
it("returns fallback message when error has no field errors, details, or message", () => {
2823+
const error = new ApiError(500, "");
2824+
2825+
const result = getGeneralErrorMsgForSubmission(error);
2826+
2827+
expect(result).toBe("Failed to save configuration");
2828+
});
2829+
});
27912830
});

0 commit comments

Comments
 (0)