-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
ref(settings): migrate integration external mapping form to new form system #110298
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ca71e01
cec4461
d3f9425
99e2055
81e694f
25afec0
8d78d22
ef60198
3bc8836
7647a0c
406f488
356c85d
cb12e87
6856881
d2b88f2
b9a5154
a78442e
0415467
6d100d5
456402f
7297a0c
94295d3
89ecc6d
b4f12e4
37ddb5c
001b66e
8e081de
0bcf7bb
31e731b
7c15a60
3b60078
c4ea190
f561685
6b64715
a75a492
6cc7fe3
4f44aa3
fde6d27
a34aa5b
00a5310
a2ebcef
b592426
5f3e107
6e84699
1484478
db0c89c
a274f93
2af0bdf
e5d67ed
b6e8e77
1ffd4c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import {setFieldErrors} from '@sentry/scraps/form'; | ||
|
|
||
| import RequestError from 'sentry/utils/requestError/requestError'; | ||
|
|
||
| function createMockFormApi(values: Record<string, unknown>) { | ||
| return { | ||
| setErrorMap: jest.fn(), | ||
| state: {values}, | ||
| }; | ||
| } | ||
|
|
||
| function createRequestError(responseJSON?: Record<string, unknown>): RequestError { | ||
| const error = new RequestError('POST', '/test/', new Error('test')); | ||
| if (responseJSON) { | ||
| error.responseJSON = responseJSON; | ||
| } | ||
| return error; | ||
| } | ||
|
|
||
| describe('setFieldErrors', () => { | ||
| describe('with FieldErrors object', () => { | ||
| it('sets field errors directly via setErrorMap', () => { | ||
| const formApi = createMockFormApi({name: '', email: ''}); | ||
| const errors = {name: {message: 'Name is required'}}; | ||
|
|
||
| setFieldErrors(formApi, errors); | ||
|
|
||
| expect(formApi.setErrorMap).toHaveBeenCalledWith({ | ||
| onSubmit: {fields: errors}, | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe('with RequestError', () => { | ||
| it('handles string values', () => { | ||
| const formApi = createMockFormApi({fieldName: ''}); | ||
| const error = createRequestError({fieldName: 'error message'}); | ||
|
|
||
| setFieldErrors(formApi, error); | ||
|
|
||
| expect(formApi.setErrorMap).toHaveBeenCalledWith({ | ||
| onSubmit: {fields: {fieldName: {message: 'error message'}}}, | ||
| }); | ||
| }); | ||
|
|
||
| it('handles array values by taking the first element', () => { | ||
| const formApi = createMockFormApi({fieldName: ''}); | ||
| const error = createRequestError({fieldName: ['first error', 'second']}); | ||
|
|
||
| setFieldErrors(formApi, error); | ||
|
|
||
| expect(formApi.setErrorMap).toHaveBeenCalledWith({ | ||
| onSubmit: {fields: {fieldName: {message: 'first error'}}}, | ||
| }); | ||
| }); | ||
|
|
||
| it('only sets errors for known fields', () => { | ||
| const formApi = createMockFormApi({name: '', email: ''}); | ||
| const error = createRequestError({ | ||
| name: ['err'], | ||
| unknown_field: ['err'], | ||
| }); | ||
|
|
||
| setFieldErrors(formApi, error); | ||
|
|
||
| expect(formApi.setErrorMap).toHaveBeenCalledWith({ | ||
| onSubmit: {fields: {name: {message: 'err'}}}, | ||
| }); | ||
| }); | ||
|
|
||
| it('does not set errors when responseJSON is missing', () => { | ||
| const formApi = createMockFormApi({name: ''}); | ||
| const error = createRequestError(); | ||
|
|
||
| setFieldErrors(formApi, error); | ||
|
|
||
| expect(formApi.setErrorMap).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('does not set errors when responseJSON is empty', () => { | ||
| const formApi = createMockFormApi({name: ''}); | ||
| const error = createRequestError({}); | ||
|
|
||
| setFieldErrors(formApi, error); | ||
|
|
||
| expect(formApi.setErrorMap).not.toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it('handles mixed field types (string and array)', () => { | ||
| const formApi = createMockFormApi({name: '', email: ''}); | ||
| const error = createRequestError({ | ||
| name: 'string error', | ||
| email: ['array error'], | ||
| }); | ||
|
|
||
| setFieldErrors(formApi, error); | ||
|
|
||
| expect(formApi.setErrorMap).toHaveBeenCalledWith({ | ||
| onSubmit: { | ||
| fields: { | ||
| name: {message: 'string error'}, | ||
| email: {message: 'array error'}, | ||
| }, | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| it('stringifies non-string array items', () => { | ||
| const formApi = createMockFormApi({field: ''}); | ||
| const error = createRequestError({field: [123]}); | ||
|
|
||
| setFieldErrors(formApi, error); | ||
|
|
||
| expect(formApi.setErrorMap).toHaveBeenCalledWith({ | ||
| onSubmit: {fields: {field: {message: '123'}}}, | ||
| }); | ||
| }); | ||
| }); | ||
| }); |
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. changes in this file are so that we can support object values in our select component. We need that because when the user selects a team, we need more than one value from that team (we need slug and id). react-select actually supports this easily because it wants the this complicates things a lot, because objects aren’t I really hope these changes don’t break any other parts but I don’t think selects with object values are used a lot. Suffice to say I 🤬 select 😂 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,23 +117,9 @@ export function NewProviderForm({ | |
| defaultValues, | ||
| validators: {onDynamic: schema}, | ||
| onSubmit: ({value, formApi}) => { | ||
| return mutation.mutateAsync(schema.parse(value)).catch((error: RequestError) => { | ||
| const responseJSON = error.responseJSON; | ||
| if (responseJSON?.secret || responseJSON?.provider) { | ||
| const extractMessage = (val: unknown): string => { | ||
| if (Array.isArray(val)) { | ||
| return typeof val[0] === 'string' ? val[0] : JSON.stringify(val[0]); | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| return typeof val === 'string' ? val : JSON.stringify(val); | ||
| }; | ||
| const errors: Record<string, {message: string}> = {}; | ||
| if (responseJSON.secret) { | ||
| errors.secret = {message: extractMessage(responseJSON.secret)}; | ||
| } | ||
| if (responseJSON.provider) { | ||
| errors.provider = {message: extractMessage(responseJSON.provider)}; | ||
| } | ||
| setFieldErrors(formApi, errors); | ||
| return mutation.mutateAsync(schema.parse(value)).catch(error => { | ||
| if (error instanceof RequestError) { | ||
| setFieldErrors(formApi, error); | ||
|
Comment on lines
+120
to
+122
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👌 nice refactor! |
||
| } | ||
| }); | ||
| }, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we have a
isJSONResponseErroror some type of helper that could assert on the type or perform the extraction for us?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
depends on if there’s a known structure that the backend sends. I know sometimes, it sends
detail, which can be a string or an object too:sentry/static/app/components/issues/groupList.tsx
Lines 256 to 262 in e16fe15
A good solution would be to make sure all backend endpoints return field errors in a standardized format, then we can add our types accordingly directly to
RequestError