Skip to content

Commit 853b44e

Browse files
cvxluoclaude
andcommitted
fix(settings): Surface slug validation errors on org settings form
When changing the org slug to one already in use, the backend returns a field-level error but the frontend was showing a generic 'Unable to save change' toast. This was a regression from the form migration to TanStack which landed after the backend fix. Use setFieldErrors to display the API validation message inline on the slug field instead of swallowing it. Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8b28ee1 commit 853b44e

File tree

2 files changed

+44
-11
lines changed

2 files changed

+44
-11
lines changed

static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.spec.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,28 @@ describe('OrganizationSettingsForm', () => {
156156
expect(screen.queryByRole('button', {name: 'Cancel'})).not.toBeInTheDocument();
157157
});
158158

159+
it('shows field error when slug is already taken', async () => {
160+
MockApiClient.addMockResponse({
161+
url: `/organizations/${organization.slug}/`,
162+
method: 'PUT',
163+
statusCode: 400,
164+
body: {slug: ['The slug "taken" is in use by another organization.']},
165+
});
166+
167+
render(
168+
<OrganizationSettingsForm initialData={OrganizationFixture()} onSave={onSave} />
169+
);
170+
171+
const input = screen.getByRole('textbox', {name: 'Organization Slug'});
172+
await userEvent.clear(input);
173+
await userEvent.type(input, 'taken');
174+
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
175+
176+
expect(
177+
await screen.findByText('The slug "taken" is in use by another organization.')
178+
).toBeInTheDocument();
179+
});
180+
159181
it('can enable codecov', async () => {
160182
putMock = MockApiClient.addMockResponse({
161183
url: `/organizations/${organization.slug}/`,

static/app/views/settings/organizationGeneralSettings/organizationSettingsForm.tsx

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
defaultFormOptions,
1212
FieldGroup,
1313
FormSearch,
14+
setFieldErrors,
1415
useScrapsForm,
1516
} from '@sentry/scraps/form';
1617
import {Container, Flex} from '@sentry/scraps/layout';
@@ -29,6 +30,7 @@ import {ConfigStore} from 'sentry/stores/configStore';
2930
import type {MembershipSettingsProps} from 'sentry/types/hooks';
3031
import type {Organization} from 'sentry/types/organization';
3132
import {fetchMutation, useMutation} from 'sentry/utils/queryClient';
33+
import {RequestError} from 'sentry/utils/requestError/requestError';
3234
import {slugify} from 'sentry/utils/slugify';
3335
import {useMembers} from 'sentry/utils/useMembers';
3436
import {useOrganization} from 'sentry/utils/useOrganization';
@@ -420,27 +422,36 @@ export function OrganizationSettingsForm({initialData, onSave}: Props) {
420422

421423
const aiEnabled = hasGenAiFeatureFlag ? (initialData.hideAiFeatures ?? false) : false;
422424

423-
// Shared mutation options for most general fields
425+
const updateOrg = (data: Partial<GeneralSchema> | Partial<SlugSchema>) =>
426+
fetchMutation<Organization>({method: 'PUT', url: endpoint, data});
427+
428+
// Shared mutation options for auto-save fields
424429
const orgMutationOptions = mutationOptions({
425-
mutationFn: (data: Partial<GeneralSchema> | Partial<SlugSchema>) =>
426-
fetchMutation<Organization>({method: 'PUT', url: endpoint, data}),
427-
onSuccess: updated => {
428-
onSave(initialData, updated);
429-
},
430+
mutationFn: updateOrg,
431+
onSuccess: updated => onSave(initialData, updated),
430432
onError: () => addErrorMessage(t('Unable to save change')),
431433
});
432434

433-
const {mutateAsync: updateSlug} = useMutation(orgMutationOptions);
435+
const slugMutation = useMutation(
436+
mutationOptions({
437+
mutationFn: updateOrg,
438+
onSuccess: updated => onSave(initialData, updated),
439+
})
440+
);
434441

435442
// Slug form — uses explicit Save button instead of auto-save
436443
const slugForm = useScrapsForm({
437444
...defaultFormOptions,
438445
defaultValues: {slug: initialData.slug},
439446
validators: {onDynamic: slugSchema},
440-
onSubmit: ({value}) =>
441-
updateSlug({slug: value.slug})
442-
.then(() => slugForm.reset())
443-
.catch(() => {}),
447+
onSubmit: ({value, formApi}) =>
448+
slugMutation
449+
.mutateAsync({slug: value.slug}, {onSuccess: () => formApi.reset()})
450+
.catch(error => {
451+
if (error instanceof RequestError) {
452+
setFieldErrors(formApi, error);
453+
}
454+
}),
444455
});
445456

446457
return (

0 commit comments

Comments
 (0)