Skip to content

Commit 8909110

Browse files
fix(site): Fix template icon field validation (#7394)
1 parent 614bdfb commit 8909110

File tree

41 files changed

+258
-457
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+258
-457
lines changed

site/src/api/errors.test.ts

+31-46
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { mockApiError } from "testHelpers/entities"
12
import {
23
getValidationErrorMessage,
34
isApiError,
@@ -7,17 +8,14 @@ import {
78
describe("isApiError", () => {
89
it("returns true when the object is an API Error", () => {
910
expect(
10-
isApiError({
11-
isAxiosError: true,
12-
response: {
13-
data: {
14-
message: "Invalid entry",
15-
errors: [
16-
{ detail: "Username is already in use", field: "username" },
17-
],
18-
},
19-
},
20-
}),
11+
isApiError(
12+
mockApiError({
13+
message: "Invalid entry",
14+
validations: [
15+
{ detail: "Username is already in use", field: "username" },
16+
],
17+
}),
18+
),
2119
).toBe(true)
2220
})
2321

@@ -48,53 +46,40 @@ describe("mapApiErrorToFieldErrors", () => {
4846
describe("getValidationErrorMessage", () => {
4947
it("returns multiple validation messages", () => {
5048
expect(
51-
getValidationErrorMessage({
52-
response: {
53-
data: {
54-
message: "Invalid user search query.",
55-
validations: [
56-
{
57-
field: "status",
58-
detail: `Query param "status" has invalid value: "inactive" is not a valid user status`,
59-
},
60-
{
61-
field: "q",
62-
detail: `Query element "role:a:e" can only contain 1 ':'`,
63-
},
64-
],
65-
},
66-
},
67-
isAxiosError: true,
68-
}),
49+
getValidationErrorMessage(
50+
mockApiError({
51+
message: "Invalid user search query.",
52+
validations: [
53+
{
54+
field: "status",
55+
detail: `Query param "status" has invalid value: "inactive" is not a valid user status`,
56+
},
57+
{
58+
field: "q",
59+
detail: `Query element "role:a:e" can only contain 1 ':'`,
60+
},
61+
],
62+
}),
63+
),
6964
).toEqual(
7065
`Query param "status" has invalid value: "inactive" is not a valid user status\nQuery element "role:a:e" can only contain 1 ':'`,
7166
)
7267
})
7368

7469
it("non-API error returns empty validation message", () => {
7570
expect(
76-
getValidationErrorMessage({
77-
response: {
78-
data: {
79-
error: "Invalid user search query.",
80-
},
81-
},
82-
isAxiosError: true,
83-
}),
71+
getValidationErrorMessage(new Error("Invalid user search query.")),
8472
).toEqual("")
8573
})
8674

8775
it("no validations field returns empty validation message", () => {
8876
expect(
89-
getValidationErrorMessage({
90-
response: {
91-
data: {
92-
message: "Invalid user search query.",
93-
detail: `Query element "role:a:e" can only contain 1 ':'`,
94-
},
95-
},
96-
isAxiosError: true,
97-
}),
77+
getValidationErrorMessage(
78+
mockApiError({
79+
message: "Invalid user search query.",
80+
detail: `Query element "role:a:e" can only contain 1 ':'`,
81+
}),
82+
),
9883
).toEqual("")
9984
})
10085
})

site/src/api/errors.ts

+5-23
Original file line numberDiff line numberDiff line change
@@ -24,30 +24,16 @@ export type ApiError = AxiosError<ApiErrorResponse> & {
2424
}
2525

2626
export const isApiError = (err: unknown): err is ApiError => {
27-
if (axios.isAxiosError(err)) {
28-
const response = err.response?.data
29-
if (!response) {
30-
return false
31-
}
32-
33-
return (
34-
typeof response.message === "string" &&
35-
(typeof response.errors === "undefined" || Array.isArray(response.errors))
36-
)
37-
}
38-
39-
return false
27+
return axios.isAxiosError(err) && err.response !== undefined
4028
}
4129

42-
/**
43-
* ApiErrors contain useful error messages in their response body. They contain an overall message
44-
* and may also contain errors for specific form fields.
45-
* @param error ApiError
46-
* @returns true if the ApiError contains error messages for specific form fields.
47-
*/
4830
export const hasApiFieldErrors = (error: ApiError): boolean =>
4931
Array.isArray(error.response.data.validations)
5032

33+
export const isApiValidationError = (error: unknown): error is ApiError => {
34+
return isApiError(error) && hasApiFieldErrors(error)
35+
}
36+
5137
export const mapApiErrorToFieldErrors = (
5238
apiErrorResponse: ApiErrorResponse,
5339
): FieldErrors => {
@@ -63,10 +49,6 @@ export const mapApiErrorToFieldErrors = (
6349
return result
6450
}
6551

66-
export const isApiValidationError = (error: unknown): error is ApiError => {
67-
return isApiError(error) && hasApiFieldErrors(error)
68-
}
69-
7052
/**
7153
*
7254
* @param error

site/src/components/AlertBanner/AlertBanner.stories.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Story } from "@storybook/react"
22
import { AlertBanner } from "./AlertBanner"
33
import Button from "@material-ui/core/Button"
4-
import { makeMockApiError } from "testHelpers/entities"
4+
import { mockApiError } from "testHelpers/entities"
55
import { AlertBannerProps } from "./alertTypes"
66
import Link from "@material-ui/core/Link"
77

@@ -16,7 +16,7 @@ const ExampleAction = (
1616
</Button>
1717
)
1818

19-
const mockError = makeMockApiError({
19+
const mockError = mockApiError({
2020
message: "Email or password was invalid",
2121
detail: "Password is invalid",
2222
})

site/src/components/CreateUserForm/CreateUserForm.stories.tsx

+4-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { action } from "@storybook/addon-actions"
22
import { Story } from "@storybook/react"
33
import { CreateUserForm, CreateUserFormProps } from "./CreateUserForm"
4+
import { mockApiError } from "testHelpers/entities"
45

56
export default {
67
title: "components/CreateUserForm",
@@ -18,22 +19,14 @@ Ready.args = {
1819
isLoading: false,
1920
}
2021

21-
export const UnknownError = Template.bind({})
22-
UnknownError.args = {
23-
onCancel: action("cancel"),
24-
onSubmit: action("submit"),
25-
isLoading: false,
26-
error: "Something went wrong",
27-
}
28-
2922
export const FormError = Template.bind({})
3023
FormError.args = {
3124
onCancel: action("cancel"),
3225
onSubmit: action("submit"),
3326
isLoading: false,
34-
formErrors: {
35-
username: "Username taken",
36-
},
27+
error: mockApiError({
28+
validations: [{ field: "username", detail: "Username taken" }],
29+
}),
3730
}
3831

3932
export const Loading = Template.bind({})

site/src/components/CreateUserForm/CreateUserForm.tsx

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import FormHelperText from "@material-ui/core/FormHelperText"
21
import TextField from "@material-ui/core/TextField"
3-
import { FormikContextType, FormikErrors, useFormik } from "formik"
2+
import { FormikContextType, useFormik } from "formik"
43
import { FC } from "react"
54
import * as Yup from "yup"
65
import * as TypesGen from "../../api/typesGenerated"
@@ -27,9 +26,8 @@ export const Language = {
2726
export interface CreateUserFormProps {
2827
onSubmit: (user: TypesGen.CreateUserRequest) => void
2928
onCancel: () => void
30-
formErrors?: FormikErrors<TypesGen.CreateUserRequest>
29+
error?: unknown
3130
isLoading: boolean
32-
error?: string
3331
myOrgId: string
3432
}
3533

@@ -44,7 +42,7 @@ const validationSchema = Yup.object({
4442

4543
export const CreateUserForm: FC<
4644
React.PropsWithChildren<CreateUserFormProps>
47-
> = ({ onSubmit, onCancel, formErrors, isLoading, error, myOrgId }) => {
45+
> = ({ onSubmit, onCancel, error, isLoading, myOrgId }) => {
4846
const form: FormikContextType<TypesGen.CreateUserRequest> =
4947
useFormik<TypesGen.CreateUserRequest>({
5048
initialValues: {
@@ -58,7 +56,7 @@ export const CreateUserForm: FC<
5856
})
5957
const getFieldHelpers = getFormHelpers<TypesGen.CreateUserRequest>(
6058
form,
61-
formErrors,
59+
error,
6260
)
6361

6462
return (
@@ -92,7 +90,6 @@ export const CreateUserForm: FC<
9290
variant="outlined"
9391
/>
9492
</Stack>
95-
{error && <FormHelperText error>{error}</FormHelperText>}
9693
<FormFooter onCancel={onCancel} isLoading={isLoading} />
9794
</form>
9895
</FullPageForm>

site/src/components/SearchBarWithFilter/SearchBarWithFilter.stories.tsx

+9-13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
SearchBarWithFilter,
55
SearchBarWithFilterProps,
66
} from "./SearchBarWithFilter"
7+
import { mockApiError } from "testHelpers/entities"
78

89
export default {
910
title: "components/SearchBarWithFilter",
@@ -34,18 +35,13 @@ WithError.args = {
3435
{ query: userFilterQuery.active, name: "Active users" },
3536
{ query: "random query", name: "Random query" },
3637
],
37-
error: {
38-
response: {
39-
data: {
40-
message: "Invalid user search query.",
41-
validations: [
42-
{
43-
field: "status",
44-
detail: `Query param "status" has invalid value: "inactive" is not a valid user status`,
45-
},
46-
],
38+
error: mockApiError({
39+
message: "Invalid user search query.",
40+
validations: [
41+
{
42+
field: "status",
43+
detail: `Query param "status" has invalid value: "inactive" is not a valid user status`,
4744
},
48-
},
49-
isAxiosError: true,
50-
},
45+
],
46+
}),
5147
}

site/src/components/SettingsAccountForm/SettingsAccountForm.stories.tsx

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Story } from "@storybook/react"
22
import { AccountForm, AccountFormProps } from "./SettingsAccountForm"
3+
import { mockApiError } from "testHelpers/entities"
34

45
export default {
56
title: "components/SettingsAccountForm",
@@ -35,20 +36,15 @@ Loading.args = {
3536
export const WithError = Template.bind({})
3637
WithError.args = {
3738
...Example.args,
38-
updateProfileError: {
39-
response: {
40-
data: {
41-
message: "Username is invalid",
42-
validations: [
43-
{
44-
field: "username",
45-
detail: "Username is too long.",
46-
},
47-
],
39+
updateProfileError: mockApiError({
40+
message: "Username is invalid",
41+
validations: [
42+
{
43+
field: "username",
44+
detail: "Username is too long.",
4845
},
49-
},
50-
isAxiosError: true,
51-
},
46+
],
47+
}),
5248
initialTouched: {
5349
username: true,
5450
},

site/src/components/SettingsSecurityForm/SettingsSecurityForm.stories.tsx

+9-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Story } from "@storybook/react"
22
import { SecurityForm, SecurityFormProps } from "./SettingsSecurityForm"
3+
import { mockApiError } from "testHelpers/entities"
34

45
export default {
56
title: "components/SettingsSecurityForm",
@@ -36,20 +37,15 @@ Loading.args = {
3637
export const WithError = Template.bind({})
3738
WithError.args = {
3839
...Example.args,
39-
updateSecurityError: {
40-
response: {
41-
data: {
42-
message: "Old password is incorrect",
43-
validations: [
44-
{
45-
field: "old_password",
46-
detail: "Old password is incorrect.",
47-
},
48-
],
40+
updateSecurityError: mockApiError({
41+
message: "Old password is incorrect",
42+
validations: [
43+
{
44+
field: "old_password",
45+
detail: "Old password is incorrect.",
4946
},
50-
},
51-
isAxiosError: true,
52-
},
47+
],
48+
}),
5349
initialTouched: {
5450
old_password: true,
5551
},

site/src/components/SignInForm/SignInForm.stories.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Story } from "@storybook/react"
2-
import { makeMockApiError } from "testHelpers/entities"
2+
import { mockApiError } from "testHelpers/entities"
33
import { SignInForm, SignInFormProps } from "./SignInForm"
44

55
export default {
@@ -37,7 +37,7 @@ SigningIn.args = {
3737
export const WithError = Template.bind({})
3838
WithError.args = {
3939
...SignedOut.args,
40-
error: makeMockApiError({
40+
error: mockApiError({
4141
message: "Email or password was invalid",
4242
validations: [
4343
{

0 commit comments

Comments
 (0)