Skip to content

Commit 4c3f05b

Browse files
authored
fix: show error when creating a new group fails (#11560)
1 parent 9052920 commit 4c3f05b

File tree

5 files changed

+38
-17
lines changed

5 files changed

+38
-17
lines changed

enterprise/coderd/groups.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ func (api *API) postGroupByOrganization(rw http.ResponseWriter, r *http.Request)
4848

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

site/src/pages/GroupsPage/CreateGroupPage.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const CreateGroupPage: FC = () => {
2626
});
2727
navigate(`/groups/${newGroup.id}`);
2828
}}
29-
formErrors={createGroupMutation.error}
29+
error={createGroupMutation.error}
3030
isLoading={createGroupMutation.isLoading}
3131
/>
3232
</>

site/src/pages/GroupsPage/CreateGroupPageView.stories.tsx

+11-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { mockApiError } from "testHelpers/entities";
12
import { CreateGroupPageView } from "./CreateGroupPageView";
23
import type { Meta, StoryObj } from "@storybook/react";
34

@@ -9,6 +10,14 @@ const meta: Meta<typeof CreateGroupPageView> = {
910
export default meta;
1011
type Story = StoryObj<typeof CreateGroupPageView>;
1112

12-
const Example: Story = {};
13+
export const Example: Story = {};
1314

14-
export { Example as CreateGroupPageView };
15+
export const WithError: Story = {
16+
args: {
17+
error: mockApiError({
18+
message: "A group named new-group already exists.",
19+
validations: [{ field: "name", detail: "Group names must be unique" }],
20+
}),
21+
initialTouched: { name: true },
22+
},
23+
};

site/src/pages/GroupsPage/CreateGroupPageView.tsx

+18-8
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,35 @@
11
import TextField from "@mui/material/TextField";
2-
import { CreateGroupRequest } from "api/typesGenerated";
2+
import { type FormikTouched, useFormik } from "formik";
3+
import { type FC } from "react";
4+
import { useNavigate } from "react-router-dom";
5+
import * as Yup from "yup";
6+
import type { CreateGroupRequest } from "api/typesGenerated";
7+
import { ErrorAlert } from "components/Alert/ErrorAlert";
38
import { FormFooter } from "components/FormFooter/FormFooter";
49
import { FullPageForm } from "components/FullPageForm/FullPageForm";
510
import { IconField } from "components/IconField/IconField";
611
import { Margins } from "components/Margins/Margins";
712
import { Stack } from "components/Stack/Stack";
8-
import { useFormik } from "formik";
9-
import { FC } from "react";
10-
import { useNavigate } from "react-router-dom";
1113
import { getFormHelpers, onChangeTrimmed } from "utils/formUtils";
12-
import * as Yup from "yup";
14+
import { isApiValidationError } from "api/errors";
1315

1416
const validationSchema = Yup.object({
1517
name: Yup.string().required().label("Name"),
1618
});
1719

1820
export type CreateGroupPageViewProps = {
1921
onSubmit: (data: CreateGroupRequest) => void;
20-
formErrors?: unknown;
22+
error?: unknown;
2123
isLoading: boolean;
24+
// Helpful to show field errors on Storybook
25+
initialTouched?: FormikTouched<CreateGroupRequest>;
2226
};
2327

2428
export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({
2529
onSubmit,
26-
formErrors,
30+
error,
2731
isLoading,
32+
initialTouched,
2833
}) => {
2934
const navigate = useNavigate();
3035
const form = useFormik<CreateGroupRequest>({
@@ -36,15 +41,20 @@ export const CreateGroupPageView: FC<CreateGroupPageViewProps> = ({
3641
},
3742
validationSchema,
3843
onSubmit,
44+
initialTouched,
3945
});
40-
const getFieldHelpers = getFormHelpers<CreateGroupRequest>(form, formErrors);
46+
const getFieldHelpers = getFormHelpers<CreateGroupRequest>(form, error);
4147
const onCancel = () => navigate("/groups");
4248

4349
return (
4450
<Margins>
4551
<FullPageForm title="Create group">
4652
<form onSubmit={form.handleSubmit}>
4753
<Stack spacing={2.5}>
54+
{Boolean(error) && !isApiValidationError(error) && (
55+
<ErrorAlert error={error} />
56+
)}
57+
4858
<TextField
4959
{...getFieldHelpers("name")}
5060
autoFocus

site/src/testHelpers/entities.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1946,17 +1946,17 @@ type MockAPIOutput = {
19461946
};
19471947

19481948
export const mockApiError = ({
1949-
message,
1949+
message = "Something went wrong.",
19501950
detail,
19511951
validations,
19521952
}: MockAPIInput): MockAPIOutput => ({
19531953
// This is how axios can check if it is an axios error when calling isAxiosError
19541954
isAxiosError: true,
19551955
response: {
19561956
data: {
1957-
message: message ?? "Something went wrong.",
1958-
detail: detail ?? undefined,
1959-
validations: validations ?? undefined,
1957+
message,
1958+
detail,
1959+
validations,
19601960
},
19611961
},
19621962
});

0 commit comments

Comments
 (0)