fix: show error when creating a new group fails (#11560)

This commit is contained in:
Kayla Washburn-Love
2024-01-12 16:06:02 -07:00
committed by GitHub
parent 905292053a
commit 4c3f05b8aa
5 changed files with 38 additions and 17 deletions

View File

@ -48,7 +48,8 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request)
if req.Name == database.EveryoneGroup { if req.Name == database.EveryoneGroup {
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
Message: fmt.Sprintf("%q is a reserved keyword and cannot be used for a group name.", database.EveryoneGroup), Message: "Invalid group name.",
Validations: []codersdk.ValidationError{{Field: "name", Detail: fmt.Sprintf("%q is a reserved group name", req.Name)}},
}) })
return return
} }
@ -63,7 +64,8 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request)
}) })
if database.IsUniqueViolation(err) { if database.IsUniqueViolation(err) {
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
Message: fmt.Sprintf("Group with name %q already exists.", req.Name), Message: fmt.Sprintf("A group named %q already exists.", req.Name),
Validations: []codersdk.ValidationError{{Field: "name", Detail: "Group names must be unique"}},
}) })
return return
} }

View File

@ -26,7 +26,7 @@ export const CreateGroupPage: FC = () => {
}); });
navigate(`/groups/${newGroup.id}`); navigate(`/groups/${newGroup.id}`);
}} }}
formErrors={createGroupMutation.error} error={createGroupMutation.error}
isLoading={createGroupMutation.isLoading} isLoading={createGroupMutation.isLoading}
/> />
</> </>

View File

@ -1,3 +1,4 @@
import { mockApiError } from "testHelpers/entities";
import { CreateGroupPageView } from "./CreateGroupPageView"; import { CreateGroupPageView } from "./CreateGroupPageView";
import type { Meta, StoryObj } from "@storybook/react"; import type { Meta, StoryObj } from "@storybook/react";
@ -9,6 +10,14 @@ const meta: Meta<typeof CreateGroupPageView> = {
export default meta; export default meta;
type Story = StoryObj<typeof CreateGroupPageView>; type Story = StoryObj<typeof CreateGroupPageView>;
const Example: Story = {}; export const Example: Story = {};
export { Example as CreateGroupPageView }; export const WithError: Story = {
args: {
error: mockApiError({
message: "A group named new-group already exists.",
validations: [{ field: "name", detail: "Group names must be unique" }],
}),
initialTouched: { name: true },
},
};

View File

@ -1,15 +1,17 @@
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import { CreateGroupRequest } from "api/typesGenerated"; import { type FormikTouched, useFormik } from "formik";
import { type FC } from "react";
import { useNavigate } from "react-router-dom";
import * as Yup from "yup";
import type { CreateGroupRequest } from "api/typesGenerated";
import { ErrorAlert } from "components/Alert/ErrorAlert";
import { FormFooter } from "components/FormFooter/FormFooter"; import { FormFooter } from "components/FormFooter/FormFooter";
import { FullPageForm } from "components/FullPageForm/FullPageForm"; import { FullPageForm } from "components/FullPageForm/FullPageForm";
import { IconField } from "components/IconField/IconField"; import { IconField } from "components/IconField/IconField";
import { Margins } from "components/Margins/Margins"; import { Margins } from "components/Margins/Margins";
import { Stack } from "components/Stack/Stack"; import { Stack } from "components/Stack/Stack";
import { useFormik } from "formik";
import { FC } from "react";
import { useNavigate } from "react-router-dom";
import { getFormHelpers, onChangeTrimmed } from "utils/formUtils"; import { getFormHelpers, onChangeTrimmed } from "utils/formUtils";
import * as Yup from "yup"; import { isApiValidationError } from "api/errors";
const validationSchema = Yup.object({ const validationSchema = Yup.object({
name: Yup.string().required().label("Name"), name: Yup.string().required().label("Name"),
@ -17,14 +19,17 @@ const validationSchema = Yup.object({
export type CreateGroupPageViewProps = { export type CreateGroupPageViewProps = {
onSubmit: (data: CreateGroupRequest) => void; onSubmit: (data: CreateGroupRequest) => void;
formErrors?: unknown; error?: unknown;
isLoading: boolean; isLoading: boolean;
// Helpful to show field errors on Storybook
initialTouched?: FormikTouched<CreateGroupRequest>;
}; };
export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({ export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({
onSubmit, onSubmit,
formErrors, error,
isLoading, isLoading,
initialTouched,
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const form = useFormik<CreateGroupRequest>({ const form = useFormik<CreateGroupRequest>({
@ -36,8 +41,9 @@ export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({
}, },
validationSchema, validationSchema,
onSubmit, onSubmit,
initialTouched,
}); });
const getFieldHelpers = getFormHelpers<CreateGroupRequest>(form, formErrors); const getFieldHelpers = getFormHelpers<CreateGroupRequest>(form, error);
const onCancel = () => navigate("/groups"); const onCancel = () => navigate("/groups");
return ( return (
@ -45,6 +51,10 @@ export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({
<FullPageForm title="Create group"> <FullPageForm title="Create group">
<form onSubmit={form.handleSubmit}> <form onSubmit={form.handleSubmit}>
<Stack spacing={2.5}> <Stack spacing={2.5}>
{Boolean(error) && !isApiValidationError(error) && (
<ErrorAlert error={error} />
)}
<TextField <TextField
{...getFieldHelpers("name")} {...getFieldHelpers("name")}
autoFocus autoFocus

View File

@ -1946,7 +1946,7 @@ type MockAPIOutput = {
}; };
export const mockApiError = ({ export const mockApiError = ({
message, message = "Something went wrong.",
detail, detail,
validations, validations,
}: MockAPIInput): MockAPIOutput => ({ }: MockAPIInput): MockAPIOutput => ({
@ -1954,9 +1954,9 @@ export const mockApiError = ({
isAxiosError: true, isAxiosError: true,
response: { response: {
data: { data: {
message: message ?? "Something went wrong.", message,
detail: detail ?? undefined, detail,
validations: validations ?? undefined, validations,
}, },
}, },
}); });