From af2ca2071d1c654f2fee4a5173848fc33007583a Mon Sep 17 00:00:00 2001 From: Steve Konves Date: Thu, 27 Aug 2026 12:54:24 -0700 Subject: [PATCH] ref(admin): Point the platform migration action at its own endpoint The customer action sent migratedToBillingPlatform to PUT /customers/{org}/. It now sends migrated to POST /_admin/customers/{org}/billing-platform-migration/. The new endpoint returns no body. So the action refetches the customer instead of writing the response into the query cache. The unmigrate label now reads "Unmigrate from Billing Platform". The old label said "to". This needs the getsentry change that adds the endpoint. --- static/gsAdmin/views/customerDetails.spec.tsx | 74 +++++++++++++++++++ static/gsAdmin/views/customerDetails.tsx | 28 ++++++- 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/static/gsAdmin/views/customerDetails.spec.tsx b/static/gsAdmin/views/customerDetails.spec.tsx index 877f870ac71f..3514b626a273 100644 --- a/static/gsAdmin/views/customerDetails.spec.tsx +++ b/static/gsAdmin/views/customerDetails.spec.tsx @@ -1624,6 +1624,80 @@ describe('Customer Details', () => { }); }); + describe('billing platform migration', () => { + const migrationOrg = OrganizationFixture(); + const mockBillingAdminUser = UserFixture({ + permissions: new Set(['billing.admin']), + }); + + async function openMigrationAction(name: string) { + render(, { + initialRouterConfig: { + location: {pathname: `/customers/${migrationOrg.slug}`}, + route: '/customers/:orgId', + }, + organization: migrationOrg, + }); + + await screen.findByRole('heading', {name: 'Customers'}); + + await userEvent.click( + screen.getAllByRole('button', { + name: 'Customers Actions', + })[0]! + ); + + await userEvent.click(screen.getByText(name)); + + renderGlobalModal(); + await userEvent.click(screen.getByRole('button', {name: 'Confirm'})); + } + + it('migrates an org to the billing platform', async () => { + ConfigStore.set('user', mockBillingAdminUser); + setUpMocks(migrationOrg, {hasMigratedToBillingPlatform: false}); + + const migrateMock = MockApiClient.addMockResponse({ + url: `/_admin/customers/${migrationOrg.slug}/billing-platform-migration/`, + method: 'POST', + }); + + await openMigrationAction('[Do Not Use] Migrate to Billing Platform'); + + await waitFor(() => { + expect(migrateMock).toHaveBeenCalledWith( + `/_admin/customers/${migrationOrg.slug}/billing-platform-migration/`, + expect.objectContaining({ + method: 'POST', + data: {migrated: true}, + }) + ); + }); + }); + + it('unmigrates an org from the billing platform', async () => { + ConfigStore.set('user', mockBillingAdminUser); + setUpMocks(migrationOrg, {hasMigratedToBillingPlatform: true}); + + const unmigrateMock = MockApiClient.addMockResponse({ + url: `/_admin/customers/${migrationOrg.slug}/billing-platform-migration/`, + method: 'POST', + }); + + await openMigrationAction('[Do Not Use] Unmigrate from Billing Platform'); + + await waitFor(() => { + expect(unmigrateMock).toHaveBeenCalledWith( + `/_admin/customers/${migrationOrg.slug}/billing-platform-migration/`, + expect.objectContaining({ + method: 'POST', + data: {migrated: false}, + }) + ); + }); + }); + }); + describe('close account', () => { it('closes an account', async () => { setUpMocks(organization); diff --git a/static/gsAdmin/views/customerDetails.tsx b/static/gsAdmin/views/customerDetails.tsx index 72dddc3bbdf9..ef6a21aec44a 100644 --- a/static/gsAdmin/views/customerDetails.tsx +++ b/static/gsAdmin/views/customerDetails.tsx @@ -191,6 +191,28 @@ export function CustomerDetails() { refetchBillingConfig(); }; + const onToggleBillingPlatformMigrationMutation = useMutation({ + mutationFn: (params: Record) => + fetchMutation({ + url: `/_admin/customers/${orgId}/billing-platform-migration/`, + method: 'POST', + data: params, + }), + onMutate: () => addLoadingMessage('Saving changes\u2026'), + onSuccess: (_data, variables) => { + addSuccessMessage( + variables.migrated + ? 'Marked this org as migrated to the billing platform.' + : 'Marked this org as not migrated to the billing platform.' + ); + reloadData(); + }, + onError: (error: RequestError) => { + const detail = error.responseJSON?.detail; + addErrorMessage(typeof detail === 'string' ? detail : DEFAULT_ERROR_MESSAGE); + }, + }); + if (isPendingSubscription || isPendingOrganization || isPendingBillingConfig) { return ; } @@ -460,15 +482,15 @@ export function CustomerDetails() { { key: 'toggleBillingPlatformMigration', name: subscription.hasMigratedToBillingPlatform - ? '[Do Not Use] Unmigrate to Billing Platform' + ? '[Do Not Use] Unmigrate from Billing Platform' : '[Do Not Use] Migrate to Billing Platform', help: subscription.hasMigratedToBillingPlatform ? 'Mark this org as not migrated to the billing platform.' : 'Mark this org as migrated to the billing platform.', onAction: params => - onUpdateMutation.mutate({ + onToggleBillingPlatformMigrationMutation.mutate({ ...params, - migratedToBillingPlatform: !subscription.hasMigratedToBillingPlatform, + migrated: !subscription.hasMigratedToBillingPlatform, }), ...actionRequiresBillingAdmin, },