From 6c9aa7d3ee9bc9b6c4105b2c5ce0d3d5fa41faff Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Wed, 15 Oct 2025 17:32:05 -0700 Subject: [PATCH 1/2] feat(ui): Remove deprecated route props from root route Removes deprecated route props from the root element of our app --- static/app/routes.tsx | 1 - static/app/views/app/index.spec.tsx | 87 +++++++++++++---------------- static/app/views/app/index.tsx | 13 ++--- 3 files changed, 44 insertions(+), 57 deletions(-) diff --git a/static/app/routes.tsx b/static/app/routes.tsx index 05842c067fa244..3148c57af27b47 100644 --- a/static/app/routes.tsx +++ b/static/app/routes.tsx @@ -3135,7 +3135,6 @@ function buildRoutes(): RouteObject[] { { path: '/', component: errorHandler(App), - deprecatedRouteProps: true, children: [ rootRoutes, authV2Routes, diff --git a/static/app/views/app/index.spec.tsx b/static/app/views/app/index.spec.tsx index 6222a67a5ebb6b..03cb17d26c546a 100644 --- a/static/app/views/app/index.spec.tsx +++ b/static/app/views/app/index.spec.tsx @@ -1,7 +1,6 @@ import {InstallWizardFixture} from 'sentry-fixture/installWizard'; import {OrganizationFixture} from 'sentry-fixture/organization'; -import {initializeOrg} from 'sentry-test/initializeOrg'; import {act, render, screen, waitFor} from 'sentry-test/reactTestingLibrary'; import AlertStore from 'sentry/stores/alertStore'; @@ -20,9 +19,23 @@ function HookWrapper(props: any) { ); } +function PlaceholderContent() { + return
placeholder content
; +} + +const defaultRouterConfig = { + location: {pathname: '/organizations/org-slug/'}, + children: [ + { + index: true, + element: , + }, + ], + route: '/organizations/:orgSlug/', +}; + describe('App', () => { const configState = ConfigStore.getState(); - const {routerProps} = initializeOrg(); beforeEach(() => { const organization = OrganizationFixture(); @@ -68,11 +81,7 @@ describe('App', () => { }); it('renders', async () => { - render( - -
placeholder content
-
- ); + render(, {initialRouterConfig: defaultRouterConfig}); await waitFor(() => OrganizationsStore.getAll().length === 1); expect(screen.getByText('placeholder content')).toBeInTheDocument(); @@ -83,11 +92,7 @@ describe('App', () => { const user = ConfigStore.get('user'); user.flags.newsletter_consent_prompt = true; - render( - -
placeholder content
-
- ); + render(, {initialRouterConfig: defaultRouterConfig}); await waitFor(() => OrganizationsStore.getAll().length === 1); @@ -113,11 +118,7 @@ describe('App', () => { }, }); - render( - -
placeholder content
-
- ); + render(, {initialRouterConfig: defaultRouterConfig}); await waitFor(() => OrganizationsStore.getAll().length === 1); @@ -135,11 +136,7 @@ describe('App', () => { agreements: ['standard', 'partner_presence'], }); HookStore.add('component:partnership-agreement', () => ); - render( - -
placeholder content
-
- ); + render(, {initialRouterConfig: defaultRouterConfig}); await waitFor(() => OrganizationsStore.getAll().length === 1); expect(HookStore.get('component:partnership-agreement')).toHaveLength(1); @@ -149,11 +146,7 @@ describe('App', () => { it('does not render PartnerAgreement for non-partnered orgs', async () => { ConfigStore.set('partnershipAgreementPrompt', null); HookStore.add('component:partnership-agreement', () => ); - render( - -
placeholder content
-
- ); + render(, {initialRouterConfig: defaultRouterConfig}); await waitFor(() => OrganizationsStore.getAll().length === 1); expect(screen.getByText('placeholder content')).toBeInTheDocument(); @@ -172,11 +165,7 @@ describe('App', () => { }, }); - render( - -
placeholder content
-
- ); + render(, {initialRouterConfig: defaultRouterConfig}); await waitFor(() => OrganizationsStore.getAll().length === 1); @@ -191,11 +180,7 @@ describe('App', () => { ConfigStore.set('needsUpgrade', true); ConfigStore.set('isSelfHosted', false); - render( - -
placeholder content
-
- ); + render(, {initialRouterConfig: defaultRouterConfig}); await waitFor(() => OrganizationsStore.getAll().length === 1); expect(screen.getByText('placeholder content')).toBeInTheDocument(); @@ -204,13 +189,23 @@ describe('App', () => { it('redirects to sentryUrl on invalid org slug', async () => { const {sentryUrl} = ConfigStore.get('links'); - render( - -
placeholder content
-
- ); + // Avoid mounting OrganizationContextProvider and related preloads which + // would issue org-specific requests for the invalid slug + ConfigStore.set('shouldPreloadData', false); + render(, { + initialRouterConfig: { + location: {pathname: '/organizations/albertos%2fapples/'}, + route: '/organizations/:orgId/', + children: [ + { + index: true, + element: , + }, + ], + }, + }); - await waitFor(() => OrganizationsStore.getAll().length === 1); + await waitFor(() => expect(testableWindowLocation.replace).toHaveBeenCalled()); expect(screen.queryByText('placeholder content')).not.toBeInTheDocument(); expect(sentryUrl).toBe('https://sentry.io'); expect(testableWindowLocation.replace).toHaveBeenCalledWith('https://sentry.io'); @@ -234,11 +229,7 @@ describe('App', () => { const restore = ConfigStore.get('isSelfHosted'); ConfigStore.set('isSelfHosted', true); - render( - -
placeholder content
-
- ); + render(, {initialRouterConfig: defaultRouterConfig}); act(() => ConfigStore.set('isSelfHosted', restore)); await waitFor(() => OrganizationsStore.getAll().length === 1); diff --git a/static/app/views/app/index.tsx b/static/app/views/app/index.tsx index f4c7a1294a7501..1f6ed5da10292a 100644 --- a/static/app/views/app/index.tsx +++ b/static/app/views/app/index.tsx @@ -1,4 +1,5 @@ import {lazy, Suspense, useCallback, useEffect, useRef} from 'react'; +import {Outlet} from 'react-router-dom'; import styled from '@emotion/styled'; import { @@ -22,7 +23,6 @@ import GuideStore from 'sentry/stores/guideStore'; import HookStore from 'sentry/stores/hookStore'; import OrganizationsStore from 'sentry/stores/organizationsStore'; import {useLegacyStore} from 'sentry/stores/useLegacyStore'; -import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; import {DemoToursProvider} from 'sentry/utils/demoMode/demoTours'; import isValidOrgSlug from 'sentry/utils/isValidOrgSlug'; import {onRenderCallback, Profiler} from 'sentry/utils/performanceForSentry'; @@ -33,6 +33,7 @@ import {useColorscheme} from 'sentry/utils/useColorscheme'; import {GlobalFeedbackForm} from 'sentry/utils/useFeedbackForm'; import {useHotkeys} from 'sentry/utils/useHotkeys'; import {useLocation} from 'sentry/utils/useLocation'; +import {useParams} from 'sentry/utils/useParams'; import {useUser} from 'sentry/utils/useUser'; import {AsyncSDKIntegrationContextProvider} from 'sentry/views/app/asyncSDKIntegrationProvider'; import LastKnownRouteContextProvider from 'sentry/views/lastKnownRouteContextProvider'; @@ -41,10 +42,6 @@ import RouteAnalyticsContextProvider from 'sentry/views/routeAnalyticsContextPro import ExplorerPanel from 'sentry/views/seerExplorer/explorerPanel'; import {useExplorerPanel} from 'sentry/views/seerExplorer/useExplorerPanel'; -type Props = { - children: React.ReactNode; -} & RouteComponentProps<{orgId?: string}>; - const InstallWizard = lazy(() => import('sentry/views/admin/installWizard')); const NewsletterConsent = lazy(() => import('sentry/views/newsletterConsent')); const BeaconConsent = lazy(() => import('sentry/views/beaconConsent')); @@ -52,7 +49,7 @@ const BeaconConsent = lazy(() => import('sentry/views/beaconConsent')); /** * App is the root level container for all uathenticated routes. */ -function App({children, params}: Props) { +function App() { useColorscheme(); const api = useApi(); @@ -125,7 +122,7 @@ function App({children, params}: Props) { }, [api, config.isSelfHosted]); const {sentryUrl} = ConfigStore.get('links'); - const {orgId} = params; + const {orgId} = useParams<{orgId?: string}>(); const isOrgSlugValid = orgId ? isValidOrgSlug(orgId) : true; useEffect(() => { @@ -243,7 +240,7 @@ function App({children, params}: Props) { return null; } - return children; + return ; } const renderOrganizationContextProvider = useCallback( From ac67bdb290ee2cd21b7629f9f3f897b1e719d6b2 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Thu, 16 Oct 2025 15:59:14 -0700 Subject: [PATCH 2/2] remove child from another test --- static/app/components/modals/sudoModal.spec.tsx | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/static/app/components/modals/sudoModal.spec.tsx b/static/app/components/modals/sudoModal.spec.tsx index f0efd7dba4e0ea..01d00a2f82bb5d 100644 --- a/static/app/components/modals/sudoModal.spec.tsx +++ b/static/app/components/modals/sudoModal.spec.tsx @@ -1,6 +1,5 @@ import {OrganizationFixture} from 'sentry-fixture/organization'; -import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary'; import ConfigStore from 'sentry/stores/configStore'; @@ -63,7 +62,6 @@ describe('Sudo Modal', () => { }); it('can delete an org with sudo flow', async () => { - const {routerProps} = initializeOrg({router: {params: {}}}); setHasPasswordAuth(true); const successCb = jest.fn(); @@ -76,11 +74,7 @@ describe('Sudo Modal', () => { error: errorCb, }); - render( - -
placeholder content
-
- ); + render(); // Should have Modal + input expect(await screen.findByRole('dialog')).toBeInTheDocument(); @@ -137,17 +131,12 @@ describe('Sudo Modal', () => { }); it('shows button to redirect if user does not have password auth', async () => { - const {routerProps} = initializeOrg({router: {params: {}}}); setHasPasswordAuth(false); // Should return w/ `sudoRequired` and trigger the modal to open new MockApiClient().request('/organizations/org-slug/', {method: 'DELETE'}); - render( - -
placeholder content
-
- ); + render(); // Should have Modal + input expect(await screen.findByRole('dialog')).toBeInTheDocument();