import * as React from 'react'; import { EnterHook, IndexRedirect, IndexRoute as BaseIndexRoute, IndexRouteProps, Redirect, Route as BaseRoute, RouteProps, } from 'react-router'; import LazyLoad from 'app/components/lazyLoad'; import {EXPERIMENTAL_SPA} from 'app/constants'; import {t} from 'app/locale'; import HookStore from 'app/stores/hookStore'; import {HookName} from 'app/types/hooks'; import errorHandler from 'app/utils/errorHandler'; import App from 'app/views/app'; import AuthLayout from 'app/views/auth/layout'; import IssueListContainer from 'app/views/issueList/container'; import IssueListOverview from 'app/views/issueList/overview'; import OrganizationContext from 'app/views/organizationContext'; import OrganizationDetails, { LightWeightOrganizationDetails, } from 'app/views/organizationDetails'; import {TAB} from 'app/views/organizationGroupDetails/header'; import OrganizationRoot from 'app/views/organizationRoot'; import ProjectEventRedirect from 'app/views/projectEventRedirect'; import redirectDeprecatedProjectRoute from 'app/views/projects/redirectDeprecatedProjectRoute'; import RouteNotFound from 'app/views/routeNotFound'; import SettingsProjectProvider from 'app/views/settings/components/settingsProjectProvider'; import SettingsWrapper from 'app/views/settings/components/settingsWrapper'; const appendTrailingSlash: EnterHook = (nextState, replace) => { const lastChar = nextState.location.pathname.slice(-1); if (lastChar !== '/') { const pathname = nextState.location.pathname + '/'; replace(pathname + nextState.location.search + nextState.location.hash); } }; type CustomProps = { name?: string; componentPromise?: () => Promise; }; /** * We add some additional props to our routes */ const Route = BaseRoute as React.ComponentClass; const IndexRoute = BaseIndexRoute as React.ComponentClass; type ComponentCallback = Parameters>[1]; /** * Use react-router to lazy load a route. Use this for codesplitting containers (e.g. SettingsLayout) * * The method for lazy loading a route leaf node is using the component + `componentPromise`. * The reason for this is because react-router handles the route tree better and if we use it will end * up having to re-render more components than necessary. */ const lazyLoad = (cb: ComponentCallback) => (m: {default: any}) => cb(null, m.default); const hook = (name: HookName) => HookStore.get(name).map(cb => cb()); function routes() { const accountSettingsRoutes = ( import('app/views/settings/account/accountDetails')} component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountNotifications') } component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountNotificationFineTuning') } component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountEmails')} component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountAuthorizations') } component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountSecurity/accountSecurityWrapper') } component={errorHandler(LazyLoad)} > import('app/views/settings/account/accountSecurity')} component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountSecurity/sessionHistory') } component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountSecurity/accountSecurityDetails') } component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountSecurity/accountSecurityEnroll') } component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountSubscriptions')} component={errorHandler(LazyLoad)} /> import('app/views/settings/account/accountIdentities')} component={errorHandler(LazyLoad)} /> import('app/views/settings/account/apiTokens')} component={errorHandler(LazyLoad)} /> import('app/views/settings/account/apiNewToken')} component={errorHandler(LazyLoad)} /> import('app/views/settings/account/apiApplications')} component={errorHandler(LazyLoad)} /> import('app/views/settings/account/apiApplications/details') } component={errorHandler(LazyLoad)} /> {hook('routes:api')} import('app/views/settings/account/accountClose')} component={errorHandler(LazyLoad)} /> ); const projectSettingsRoutes = ( import('app/views/settings/projectGeneralSettings')} component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectTeams')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectAlerts')} > import('app/views/settings/projectAlerts/settings')} /> import('app/views/settings/project/projectEnvironments')} component={errorHandler(LazyLoad)} > import('app/views/settings/projectTags')} component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectReleaseTracking') } component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectOwnership')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectDataForwarding')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectSecurityAndPrivacy')} /> import('app/views/settings/projectDebugFiles')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectProguard')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectPerformance')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectSourceMaps')} component={errorHandler(LazyLoad)} > import('app/views/settings/projectSourceMaps/list')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectSourceMaps/detail')} component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectProcessingIssues') } component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectFilters')} component={errorHandler(LazyLoad)} > import('app/views/settings/project/filtersAndSampling')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectIssueGrouping')} component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectServiceHooks')} component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectCreateServiceHook') } component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectServiceHookDetails') } component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectKeys/list')} component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectKeys/details') } component={errorHandler(LazyLoad)} /> import('app/views/settings/project/projectUserFeedback')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectSecurityHeaders')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectSecurityHeaders/csp')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectSecurityHeaders/expectCt') } component={errorHandler(LazyLoad)} /> import('app/views/settings/projectSecurityHeaders/hpkp') } component={errorHandler(LazyLoad)} /> import('app/views/settings/projectPlugins')} component={errorHandler(LazyLoad)} /> import('app/views/settings/projectPlugins/details')} component={errorHandler(LazyLoad)} /> import('app/views/projectInstall/overview')} component={errorHandler(LazyLoad)} /> import('app/views/projectInstall/platformOrIntegration') } component={errorHandler(LazyLoad)} /> ); // This is declared in the routes() function because some routes need the // hook store which is not available at import time. const orgSettingsRoutes = ( import('app/views/settings/organizationGeneralSettings')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationProjects')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationApiKeys')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationApiKeys/organizationApiKeyDetails') } component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationAuditLog')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationAuth')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationMembers/organizationMembersWrapper') } component={errorHandler(LazyLoad)} > import('app/views/settings/organizationMembers/organizationMembersList') } component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationMembers/organizationMemberDetail') } component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationRateLimits')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationRelay')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationRepositories')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationPerformance')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationGeneralSettings')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationSecurityAndPrivacy') } component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationTeams')} component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationTeams/teamDetails') } component={errorHandler(LazyLoad)} > import('app/views/settings/organizationTeams/teamMembers') } component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationTeams/teamNotifications') } component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationTeams/teamProjects') } component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationTeams/teamSettings') } component={errorHandler(LazyLoad)} /> import('app/views/organizationIntegrations/pluginDetailedView') } component={errorHandler(LazyLoad)} /> import('app/views/organizationIntegrations/sentryAppDetailedView') } component={errorHandler(LazyLoad)} /> import('app/views/organizationIntegrations/docIntegrationDetailedView') } component={errorHandler(LazyLoad)} /> import('app/views/organizationIntegrations/integrationListDirectory') } component={errorHandler(LazyLoad)} /> import('app/views/organizationIntegrations/integrationDetailedView') } component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationIntegrations/configureIntegration') } component={errorHandler(LazyLoad)} /> import('app/views/settings/organizationDeveloperSettings') } component={errorHandler(LazyLoad)} /> import( 'app/views/settings/organizationDeveloperSettings/sentryApplicationDetails' ) } component={errorHandler(LazyLoad)} /> import( 'app/views/settings/organizationDeveloperSettings/sentryApplicationDetails' ) } component={errorHandler(LazyLoad)} /> import( 'app/views/settings/organizationDeveloperSettings/sentryApplicationDetails' ) } component={errorHandler(LazyLoad)} /> import( 'app/views/settings/organizationDeveloperSettings/sentryApplicationDashboard' ) } component={errorHandler(LazyLoad)} /> ); return ( {EXPERIMENTAL_SPA && ( import('app/views/auth/login')} component={errorHandler(LazyLoad)} /> )} import('app/views/app/root')} component={errorHandler(LazyLoad)} /> import('app/views/acceptOrganizationInvite')} component={errorHandler(LazyLoad)} /> import('app/views/acceptProjectTransfer')} component={errorHandler(LazyLoad)} /> import('app/views/integrationOrganizationLink')} component={errorHandler(LazyLoad)} /> import('app/views/integrationOrganizationLink').then(lazyLoad(cb)) } /> import('app/views/sentryAppExternalInstallation')} component={errorHandler(LazyLoad)} /> import('app/views/sharedGroupDetails')} component={errorHandler(LazyLoad)} /> import('app/views/organizationCreate')} component={errorHandler(LazyLoad)} /> import('app/views/dataExport/dataDownload')} component={errorHandler(LazyLoad)} /> import('app/views/disabledMember')} component={errorHandler(LazyLoad)} /> import('app/views/organizationJoinRequest')} component={errorHandler(LazyLoad)} /> import('app/views/onboarding/onboarding')} component={errorHandler(LazyLoad)} /> {/* Settings routes */} import('app/views/settings/settingsIndex').then(lazyLoad(cb)) } /> import('app/views/settings/account/accountSettingsLayout').then( lazyLoad(cb) ) } > {accountSettingsRoutes} import( 'app/views/settings/organization/organizationSettingsLayout' ).then(lazyLoad(cb)) } > {hook('routes:organization')} {orgSettingsRoutes} import('app/views/settings/project/projectSettingsLayout').then( lazyLoad(cb) ) } > {projectSettingsRoutes} {/* A route tree for lightweight organizational detail views. We place this above the heavyweight organization detail views because there exist some redirects from deprecated routes which should not take precedence over these lightweight routes */} import('app/views/projectsDashboard')} component={errorHandler(LazyLoad)} /> import('app/views/dashboardsV2')} component={errorHandler(LazyLoad)} > import('app/views/dashboardsV2/manage')} component={errorHandler(LazyLoad)} /> import('app/views/userFeedback')} component={errorHandler(LazyLoad)} /> import('app/views/issueList/testSessionPercent')} component={errorHandler(LazyLoad)} /> {/* Once org issues is complete, these routes can be nested under /organizations/:orgId/issues */} import('app/views/organizationGroupDetails')} component={errorHandler(LazyLoad)} > import('app/views/organizationGroupDetails/groupEventDetails') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.DETAILS, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/groupActivity') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.ACTIVITY, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/groupEvents') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.EVENTS, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/groupTags') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.TAGS, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/groupTagValues') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.TAGS, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/groupUserFeedback') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.USER_FEEDBACK, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/groupEventAttachments') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.ATTACHMENTS, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/groupSimilarIssues') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.SIMILAR_ISSUES, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/groupMerged') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.MERGED, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/grouping') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.GROUPING, isEventRoute: false, }} /> import('app/views/organizationGroupDetails/groupEventDetails') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.DETAILS, isEventRoute: true, }} /> import('app/views/organizationGroupDetails/groupActivity') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.ACTIVITY, isEventRoute: true, }} /> import('app/views/organizationGroupDetails/groupEvents') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.EVENTS, isEventRoute: true, }} /> import('app/views/organizationGroupDetails/groupSimilarIssues') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.SIMILAR_ISSUES, isEventRoute: true, }} /> import('app/views/organizationGroupDetails/groupTags') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.TAGS, isEventRoute: true, }} /> import('app/views/organizationGroupDetails/groupTagValues') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.TAGS, isEventRoute: true, }} /> import('app/views/organizationGroupDetails/groupUserFeedback') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.USER_FEEDBACK, isEventRoute: true, }} /> import('app/views/organizationGroupDetails/groupEventAttachments') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.ATTACHMENTS, isEventRoute: true, }} /> import('app/views/organizationGroupDetails/groupMerged') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.MERGED, isEventRoute: true, }} /> import('app/views/organizationGroupDetails/grouping') } component={errorHandler(LazyLoad)} props={{ currentTab: TAB.GROUPING, isEventRoute: true, }} /> import('app/views/alerts')} component={errorHandler(LazyLoad)} > import('app/views/alerts/list')} component={errorHandler(LazyLoad)} /> import('app/views/alerts/rules/details')} /> import('app/views/alerts/rules')} /> import('app/views/alerts/builder/projectProvider') } component={errorHandler(LazyLoad)} > import('app/views/alerts/edit')} component={errorHandler(LazyLoad)} /> import('app/views/alerts/builder/projectProvider') } component={errorHandler(LazyLoad)} > import('app/views/alerts/edit')} component={errorHandler(LazyLoad)} /> import('app/views/alerts/rules')} component={errorHandler(LazyLoad)} /> import('app/views/alerts/details')} component={errorHandler(LazyLoad)} /> import('app/views/alerts/builder/projectProvider')} component={errorHandler(LazyLoad)} > import('app/views/alerts/create')} /> import('app/views/alerts/wizard')} /> import('app/views/monitors')} component={errorHandler(LazyLoad)} > import('app/views/monitors/monitors')} component={errorHandler(LazyLoad)} /> import('app/views/monitors/create')} component={errorHandler(LazyLoad)} /> import('app/views/monitors/details')} component={errorHandler(LazyLoad)} /> import('app/views/monitors/edit')} component={errorHandler(LazyLoad)} /> import('app/views/releases')} component={errorHandler(LazyLoad)} > import('app/views/releases/list')} component={errorHandler(LazyLoad)} /> import('app/views/releases/detail')} component={errorHandler(LazyLoad)} > import('app/views/releases/detail/overview')} component={errorHandler(LazyLoad)} /> import('app/views/releases/detail/commits')} component={errorHandler(LazyLoad)} /> import('app/views/releases/detail/filesChanged')} component={errorHandler(LazyLoad)} /> import('app/views/organizationActivity')} component={errorHandler(LazyLoad)} /> import( /* webpackChunkName: "OrganizationStats" */ 'app/views/organizationStats' ) } component={errorHandler(LazyLoad)} /> {/* TODO(mark) Long term this /queries route should go away and /discover should be the canonical route for discover2. We have a redirect right now as /discover was for discover 1 and most of the application is linking to /discover/queries and not /discover */} import('app/views/eventsV2')} component={errorHandler(LazyLoad)} > import('app/views/eventsV2/landing')} component={errorHandler(LazyLoad)} /> import('app/views/eventsV2/results')} component={errorHandler(LazyLoad)} /> import('app/views/eventsV2/eventDetails')} component={errorHandler(LazyLoad)} /> import('app/views/performance')} component={errorHandler(LazyLoad)} > import('app/views/performance/content')} component={errorHandler(LazyLoad)} /> import('app/views/performance')} component={errorHandler(LazyLoad)} > import('app/views/performance/trends')} component={errorHandler(LazyLoad)} /> import('app/views/performance')} component={errorHandler(LazyLoad)} > import('app/views/performance/transactionSummary')} component={errorHandler(LazyLoad)} /> import('app/views/performance/transactionSummary/transactionVitals') } component={errorHandler(LazyLoad)} /> import('app/views/performance/transactionSummary/transactionTags') } component={errorHandler(LazyLoad)} /> import('app/views/performance/transactionSummary/transactionEvents') } component={errorHandler(LazyLoad)} /> import('app/views/performance')} component={errorHandler(LazyLoad)} > import('app/views/performance/vitalDetail')} component={errorHandler(LazyLoad)} /> import('app/views/performance')} component={errorHandler(LazyLoad)} > import('app/views/performance/traceDetails')} component={errorHandler(LazyLoad)} /> import('app/views/performance')} component={errorHandler(LazyLoad)} > import('app/views/performance/transactionDetails')} component={errorHandler(LazyLoad)} /> import('app/views/performance')} component={errorHandler(LazyLoad)} > import('app/views/performance/compare')} component={errorHandler(LazyLoad)} /> import('app/views/dashboardsV2/create')} component={errorHandler(LazyLoad)} > import('app/views/dashboardsV2/widget')} component={errorHandler(LazyLoad)} /> import('app/views/dashboardsV2/widget')} component={errorHandler(LazyLoad)} /> import('app/views/dashboardsV2/view')} component={errorHandler(LazyLoad)} > import('app/views/dashboardsV2/widget')} component={errorHandler(LazyLoad)} /> import('app/views/dashboardsV2/widget')} component={errorHandler(LazyLoad)} /> {/* Admin/manage routes */} import('app/views/admin/adminLayout')} component={errorHandler(LazyLoad)} > import('app/views/admin/adminOverview')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminBuffer')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminRelays')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminOrganizations')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminProjects')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminQueue')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminQuotas')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminSettings')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminUsers')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminUserEdit')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminMail')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminEnvironment')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminPackages')} component={errorHandler(LazyLoad)} /> import('app/views/admin/adminWarnings')} component={errorHandler(LazyLoad)} /> {hook('routes:admin')} {/* The heavyweight organization detail views */} {hook('routes:organization-root')} import('app/views/projectInstall/gettingStarted')} component={errorHandler(LazyLoad)} > import('app/views/projectInstall/overview')} component={errorHandler(LazyLoad)} /> import('app/views/projectInstall/platformOrIntegration') } component={errorHandler(LazyLoad)} /> import('app/views/teamCreate')} component={errorHandler(LazyLoad)} /> {hook('routes:organization')} import('app/views/projectInstall/newProject')} component={errorHandler(LazyLoad)} /> import('app/views/projectInstall/gettingStarted')} component={errorHandler(LazyLoad)} > import('app/views/projectInstall/overview')} component={errorHandler(LazyLoad)} /> import('app/views/projectInstall/platformOrIntegration') } component={errorHandler(LazyLoad)} /> {/* A route tree for lightweight organizational detail views. This is strictly for deprecated URLs that we need to maintain */} {/* This is in the bottom lightweight group because "/organizations/:orgId/projects/new/" in heavyweight needs to be matched first */} import('app/views/projectDetail')} component={errorHandler(LazyLoad)} /> {/* Support for deprecated URLs (pre-Sentry 10). We just redirect users to new canonical URLs. */} `/organizations/${orgId}/issues/?project=${projectId}` ) )} /> `/organizations/${orgId}/issues/?project=${projectId}` ) )} /> `/organizations/${orgId}/dashboards/?project=${projectId}` ) )} /> `/organizations/${orgId}/user-feedback/?project=${projectId}` ) )} /> `/organizations/${orgId}/releases/?project=${projectId}` ) )} /> `/organizations/${orgId}/releases/${router.params.version}/?project=${projectId}` ) )} /> `/organizations/${orgId}/releases/${router.params.version}/new-events/?project=${projectId}` ) )} /> `/organizations/${orgId}/releases/${router.params.version}/all-events/?project=${projectId}` ) )} /> `/organizations/${orgId}/releases/${router.params.version}/commits/?project=${projectId}` ) )} /> {hook('routes')} ); } export default routes;