mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
FSL-1.0-Apache-2.0 grants us the code, not the name. "Sentry" is Functional Software's trademark, so every place the running app says it to a user -- page titles, onboarding, empty states, toasts, settings copy, the self-hosted footer -- now says Sentinel. Attribution is the other half of the deal and stays untouched: upstream copyright headers, LICENSE, NOTICE. So does anything where the mark is a fact rather than a brand -- @sentry/* packages, sentry-cli, the wizard and bundler plugins, docs.sentry.io, the whole gettingStartedDocs tree. Renaming those would leave instructions that no longer work. Nothing structural moved. No import, identifier, route path, feature flag, API field or test id changed; the diff is 312 lines in and 312 out. Colocated specs were updated in lockstep with the copy they assert. Marketing and community links point at hanzo.ai, only where a real page exists to point at. Co-authored-by: Hanzo Dev <[email protected]>
2569 lines
80 KiB
TypeScript
2569 lines
80 KiB
TypeScript
import {Fragment} from 'react';
|
|
import type {IndexRouteProps, RouteProps} from 'react-router';
|
|
import {
|
|
IndexRedirect,
|
|
IndexRoute as BaseIndexRoute,
|
|
Redirect,
|
|
Route as BaseRoute,
|
|
} from 'react-router';
|
|
import memoize from 'lodash/memoize';
|
|
|
|
import LazyLoad from 'sentry/components/lazyLoad';
|
|
import {EXPERIMENTAL_SPA, USING_CUSTOMER_DOMAIN} from 'sentry/constants';
|
|
import {t} from 'sentry/locale';
|
|
import HookStore from 'sentry/stores/hookStore';
|
|
import type {HookName} from 'sentry/types/hooks';
|
|
import errorHandler from 'sentry/utils/errorHandler';
|
|
import withDomainRedirect from 'sentry/utils/withDomainRedirect';
|
|
import withDomainRequired from 'sentry/utils/withDomainRequired';
|
|
import App from 'sentry/views/app';
|
|
import AuthLayout from 'sentry/views/auth/layout';
|
|
import {Tab, TabPaths} from 'sentry/views/issueDetails/types';
|
|
import IssueListContainer from 'sentry/views/issueList';
|
|
import IssueListOverview from 'sentry/views/issueList/overview';
|
|
import OrganizationContainer from 'sentry/views/organizationContainer';
|
|
import OrganizationLayout from 'sentry/views/organizationLayout';
|
|
import OrganizationRoot from 'sentry/views/organizationRoot';
|
|
import ProjectEventRedirect from 'sentry/views/projectEventRedirect';
|
|
import redirectDeprecatedProjectRoute from 'sentry/views/projects/redirectDeprecatedProjectRoute';
|
|
import RouteNotFound from 'sentry/views/routeNotFound';
|
|
import SettingsWrapper from 'sentry/views/settings/components/settingsWrapper';
|
|
|
|
type CustomProps = {
|
|
name?: string;
|
|
};
|
|
|
|
/**
|
|
* We add some additional props to our routes
|
|
*/
|
|
|
|
const Route = BaseRoute as React.ComponentClass<
|
|
React.PropsWithChildren<RouteProps & CustomProps>
|
|
>;
|
|
|
|
const IndexRoute = BaseIndexRoute as React.ComponentClass<IndexRouteProps & CustomProps>;
|
|
|
|
const hook = (name: HookName) => HookStore.get(name).map(cb => cb());
|
|
|
|
const SafeLazyLoad = errorHandler(LazyLoad);
|
|
|
|
// NOTE: makeLazyloadComponent is exported for use in the sentry.io (getsentry)
|
|
// pirvate routing tree.
|
|
|
|
/**
|
|
* Factory function to produce a component that will render the SafeLazyLoad
|
|
* _with_ the required props.
|
|
*/
|
|
export function makeLazyloadComponent<C extends React.ComponentType<any>>(
|
|
resolve: () => Promise<{default: C}>
|
|
) {
|
|
// XXX: Assign the component to a variable so it has a displayname
|
|
function RouteLazyLoad(props: React.ComponentProps<C>) {
|
|
// we can use this hook to set the organization as it's
|
|
// a child of the organization context
|
|
return <SafeLazyLoad {...props} component={resolve} />;
|
|
}
|
|
|
|
return RouteLazyLoad;
|
|
}
|
|
|
|
// Shorthand to avoid extra line wrapping
|
|
const make = makeLazyloadComponent;
|
|
|
|
function buildRoutes() {
|
|
// Read this to understand where to add new routes, how / why the routing
|
|
// tree is structured the way it is, and how the lazy-loading /
|
|
// code-splitting works for pages.
|
|
//
|
|
// ## Formatting
|
|
//
|
|
// NOTE that there are intentionally NO blank lines within route tree blocks.
|
|
// This helps make it easier to navigate within the file by using your
|
|
// editors shortcuts to jump between 'paragraphs' of code.
|
|
//
|
|
// [!!] Do NOT add blank lines within route blocks to preserve this behavior!
|
|
//
|
|
//
|
|
// ## Lazy loading
|
|
//
|
|
// * The `SafeLazyLoad` component
|
|
//
|
|
// Most routes are rendered as LazyLoad components (SafeLazyLoad is the
|
|
// errorHandler wrapped version). This means the rendered component for the
|
|
// route will only be loaded when the route is loaded. This helps us
|
|
// "code-split" the app.
|
|
//
|
|
// ## Hooks
|
|
//
|
|
// There are a number of `hook()` routes placed within the routing tree to
|
|
// allow for additional routes to be augmented into the application via the
|
|
// hookStore mechanism.
|
|
//
|
|
//
|
|
// ## The structure
|
|
//
|
|
// * `experimentalSpaRoutes`
|
|
//
|
|
// These routes are specifically for the experimental single-page-app mode,
|
|
// where Sentry is run separate from Django. These are NOT part of the root
|
|
// <App /> component.
|
|
//
|
|
// Right now these are mainly used for authentication pages. In the future
|
|
// they would be used for other pages like registration.
|
|
//
|
|
// * `rootRoutes`
|
|
//
|
|
// These routes live directly under the <App /> container, and generally
|
|
// are not specific to an organization.
|
|
//
|
|
// * `settingsRoutes`
|
|
//
|
|
// This is the route tree for all of `/settings/`. This route tree is
|
|
// composed of a few different sub-trees.
|
|
//
|
|
// - `accountSettingsRoutes` User specific settings
|
|
// - `orgSettingsRoutes` Specific to a organization
|
|
// - `projectSettingsRoutes` Specific to a project
|
|
// - `legacySettingsRedirects` Routes that used to exist in settings
|
|
//
|
|
// * `organizationRoutes`
|
|
//
|
|
// This is where a majority of the app routes live. This is wrapped with
|
|
// the <OrganizationLayout /> component, which renders the sidebar and
|
|
// loads the organiztion into context (though in some cases, there may be
|
|
// no organiztion)
|
|
//
|
|
// When adding new routes make sure you have both a route that starts
|
|
// with `/organizations/:orgId` and also 'customer-domains' route that
|
|
// does not include `/organizations/:orgId`. Often you'll only need to
|
|
// worry about this for the container route for that section of the UI.
|
|
// Child routes should access the current organization with `useOrganization()`
|
|
// or `withOrganization()` methods.
|
|
//
|
|
// Within these routes are a variety of subroutes. They are not all
|
|
// listed here as the subroutes will be added and removed, and most are
|
|
// self explanatory.
|
|
//
|
|
// * `legacyRedirectRoutes`
|
|
//
|
|
// This route tree contains <Redirect /> routes for many old legacy paths.
|
|
//
|
|
// You may also find <Redirect />'s collocated next to the feature routes
|
|
// they have redirects for. A good rule here is to place 'helper' redirects
|
|
// next to the routes they redirect to, and place 'legacy route' redirects
|
|
// for routes that have completely changed in this tree.
|
|
|
|
const experimentalSpaRoutes = EXPERIMENTAL_SPA ? (
|
|
<Route path="/auth/login/" component={errorHandler(AuthLayout)}>
|
|
<IndexRoute component={make(() => import('sentry/views/auth/login'))} />
|
|
<Route path=":orgId/" component={make(() => import('sentry/views/auth/login'))} />
|
|
</Route>
|
|
) : null;
|
|
|
|
const rootRoutes = (
|
|
<Fragment>
|
|
<IndexRoute component={make(() => import('sentry/views/app/root'))} />
|
|
{hook('routes:root')}
|
|
<Route
|
|
path="/accept/:orgId/:memberId/:token/"
|
|
component={make(() => import('sentry/views/acceptOrganizationInvite'))}
|
|
/>
|
|
<Route
|
|
path="/accept/:memberId/:token/"
|
|
component={make(() => import('sentry/views/acceptOrganizationInvite'))}
|
|
/>
|
|
<Route
|
|
path="/accept-transfer/"
|
|
component={make(() => import('sentry/views/acceptProjectTransfer'))}
|
|
/>
|
|
<Route component={errorHandler(OrganizationContainer)}>
|
|
<Route
|
|
path="/extensions/external-install/:integrationSlug/:installationId"
|
|
component={make(() => import('sentry/views/integrationOrganizationLink'))}
|
|
/>
|
|
<Route
|
|
path="/extensions/:integrationSlug/link/"
|
|
component={make(() => import('sentry/views/integrationOrganizationLink'))}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="/sentry-apps/:sentryAppSlug/external-install/"
|
|
component={make(() => import('sentry/views/sentryAppExternalInstallation'))}
|
|
/>
|
|
<Redirect from="/account/" to="/settings/account/details/" />
|
|
<Redirect from="/share/group/:shareId/" to="/share/issue/:shareId/" />
|
|
{/* TODO: remove share/issue orgless url */}
|
|
<Route
|
|
path="/share/issue/:shareId/"
|
|
component={make(() => import('sentry/views/sharedGroupDetails'))}
|
|
/>
|
|
<Route
|
|
path="/organizations/:orgId/share/issue/:shareId/"
|
|
component={make(() => import('sentry/views/sharedGroupDetails'))}
|
|
/>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/unsubscribe/project/:id/"
|
|
component={make(() => import('sentry/views/unsubscribe/project'))}
|
|
/>
|
|
)}
|
|
<Route
|
|
path="/unsubscribe/:orgId/project/:id/"
|
|
component={make(() => import('sentry/views/unsubscribe/project'))}
|
|
/>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/unsubscribe/issue/:id/"
|
|
component={make(() => import('sentry/views/unsubscribe/issue'))}
|
|
/>
|
|
)}
|
|
<Route
|
|
path="/unsubscribe/:orgId/issue/:id/"
|
|
component={make(() => import('sentry/views/unsubscribe/issue'))}
|
|
/>
|
|
<Route
|
|
path="/organizations/new/"
|
|
component={make(() => import('sentry/views/organizationCreate'))}
|
|
/>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/data-export/:dataExportId"
|
|
component={withDomainRequired(
|
|
make(() => import('sentry/views/dataExport/dataDownload'))
|
|
)}
|
|
key="orgless-data-export-route"
|
|
/>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/data-export/:dataExportId"
|
|
component={withDomainRedirect(
|
|
make(() => import('sentry/views/dataExport/dataDownload'))
|
|
)}
|
|
key="org-data-export"
|
|
/>
|
|
<Route component={errorHandler(OrganizationContainer)}>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/disabled-member/"
|
|
component={withDomainRequired(
|
|
make(() => import('sentry/views/disabledMember'))
|
|
)}
|
|
key="orgless-disabled-member-route"
|
|
/>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/disabled-member/"
|
|
component={withDomainRedirect(
|
|
make(() => import('sentry/views/disabledMember'))
|
|
)}
|
|
key="org-disabled-member"
|
|
/>
|
|
</Route>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/restore/"
|
|
component={make(() => import('sentry/views/organizationRestore'))}
|
|
/>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/restore/"
|
|
component={make(() => import('sentry/views/organizationRestore'))}
|
|
/>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/join-request/"
|
|
component={withDomainRequired(
|
|
make(() => import('sentry/views/organizationJoinRequest'))
|
|
)}
|
|
key="orgless-join-request"
|
|
/>
|
|
)}
|
|
<Route
|
|
path="/join-request/:orgId/"
|
|
component={withDomainRedirect(
|
|
make(() => import('sentry/views/organizationJoinRequest'))
|
|
)}
|
|
key="org-join-request"
|
|
/>
|
|
<Route
|
|
path="/relocation/"
|
|
component={make(() => import('sentry/views/relocation'))}
|
|
key="orgless-relocation"
|
|
>
|
|
<IndexRedirect to="get-started/" />
|
|
<Route path=":step/" component={make(() => import('sentry/views/relocation'))} />
|
|
</Route>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/onboarding/"
|
|
component={errorHandler(withDomainRequired(OrganizationContainer))}
|
|
key="orgless-onboarding"
|
|
>
|
|
<IndexRedirect to="welcome/" />
|
|
<Route
|
|
path=":step/"
|
|
component={make(() => import('sentry/views/onboarding'))}
|
|
/>
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/onboarding/:orgId/"
|
|
component={withDomainRedirect(errorHandler(OrganizationContainer))}
|
|
key="org-onboarding"
|
|
>
|
|
<IndexRedirect to="welcome/" />
|
|
<Route path=":step/" component={make(() => import('sentry/views/onboarding'))} />
|
|
</Route>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/stories/"
|
|
component={make(() => import('sentry/views/stories/index'))}
|
|
key="orgless-stories"
|
|
/>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/stories/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/stories/index')))}
|
|
key="org-stories"
|
|
/>
|
|
</Fragment>
|
|
);
|
|
|
|
const accountSettingsRoutes = (
|
|
<Route
|
|
path="account/"
|
|
name={t('Account')}
|
|
component={make(
|
|
() => import('sentry/views/settings/account/accountSettingsLayout')
|
|
)}
|
|
>
|
|
<IndexRedirect to="details/" />
|
|
<Route
|
|
path="details/"
|
|
name={t('Details')}
|
|
component={make(() => import('sentry/views/settings/account/accountDetails'))}
|
|
/>
|
|
<Route path="notifications/" name={t('Notifications')}>
|
|
<IndexRoute
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/account/notifications/notificationSettingsController'
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":fineTuneType/"
|
|
name={t('Fine Tune Alerts')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/account/accountNotificationFineTuningController'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="emails/"
|
|
name={t('Emails')}
|
|
component={make(() => import('sentry/views/settings/account/accountEmails'))}
|
|
/>
|
|
<Route
|
|
path="authorizations/"
|
|
component={make(
|
|
() => import('sentry/views/settings/account/accountAuthorizations')
|
|
)}
|
|
/>
|
|
<Route path="security/" name={t('Security')}>
|
|
<Route
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/account/accountSecurity/accountSecurityWrapper'
|
|
)
|
|
)}
|
|
>
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/settings/account/accountSecurity')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="session-history/"
|
|
name={t('Session History')}
|
|
component={make(
|
|
() => import('sentry/views/settings/account/accountSecurity/sessionHistory')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="mfa/:authId/"
|
|
name={t('Details')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/account/accountSecurity/accountSecurityDetails'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="mfa/:authId/enroll/"
|
|
name={t('Enroll')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/account/accountSecurity/accountSecurityEnroll'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="subscriptions/"
|
|
name={t('Subscriptions')}
|
|
component={make(
|
|
() => import('sentry/views/settings/account/accountSubscriptions')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="identities/"
|
|
name={t('Identities')}
|
|
component={make(() => import('sentry/views/settings/account/accountIdentities'))}
|
|
/>
|
|
<Route path="api/" name={t('API')}>
|
|
<IndexRedirect to="auth-tokens/" />
|
|
<Route path="auth-tokens/" name={t('User Auth Tokens')}>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/account/apiTokens'))}
|
|
/>
|
|
<Route
|
|
path="new-token/"
|
|
name={t('Create New Token')}
|
|
component={make(() => import('sentry/views/settings/account/apiNewToken'))}
|
|
/>
|
|
</Route>
|
|
<Route path="applications/" name={t('Applications')}>
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/settings/account/apiApplications')
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":appId/"
|
|
name={t('Details')}
|
|
component={make(
|
|
() => import('sentry/views/settings/account/apiApplications/details')
|
|
)}
|
|
/>
|
|
</Route>
|
|
{hook('routes:api')}
|
|
</Route>
|
|
<Route
|
|
path="close-account/"
|
|
name={t('Close Account')}
|
|
component={make(() => import('sentry/views/settings/account/accountClose'))}
|
|
/>
|
|
</Route>
|
|
);
|
|
|
|
const projectSettingsRoutes = (
|
|
<Route
|
|
path="projects/:projectId/"
|
|
name={t('Project')}
|
|
component={make(
|
|
() => import('sentry/views/settings/project/projectSettingsLayout')
|
|
)}
|
|
>
|
|
<IndexRoute
|
|
name={t('General')}
|
|
component={make(() => import('sentry/views/settings/projectGeneralSettings'))}
|
|
/>
|
|
<Redirect from="install/" to="/projects/:projectId/getting-started/" />
|
|
<Route
|
|
path="teams/"
|
|
name={t('Teams')}
|
|
component={make(() => import('sentry/views/settings/project/projectTeams'))}
|
|
/>
|
|
<Route
|
|
path="alerts/"
|
|
name={t('Alerts')}
|
|
component={make(() => import('sentry/views/settings/projectAlerts'))}
|
|
>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/projectAlerts/settings'))}
|
|
/>
|
|
<Redirect from="new/" to="/organizations/:orgId/alerts/:projectId/new/" />
|
|
<Redirect from="rules/" to="/organizations/:orgId/alerts/rules/" />
|
|
<Redirect from="rules/new/" to="/organizations/:orgId/alerts/:projectId/new/" />
|
|
<Redirect
|
|
from="metric-rules/new/"
|
|
to="/organizations/:orgId/alerts/:projectId/new/"
|
|
/>
|
|
<Redirect
|
|
from="rules/:ruleId/"
|
|
to="/organizations/:orgId/alerts/rules/:projectId/:ruleId/"
|
|
/>
|
|
<Redirect
|
|
from="metric-rules/:ruleId/"
|
|
to="/organizations/:orgId/alerts/metric-rules/:projectId/:ruleId/"
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="environments/"
|
|
name={t('Environments')}
|
|
component={make(
|
|
() => import('sentry/views/settings/project/projectEnvironments')
|
|
)}
|
|
>
|
|
<IndexRoute />
|
|
<Route path="hidden/" />
|
|
</Route>
|
|
<Route
|
|
path="tags/"
|
|
name={t('Tags')}
|
|
component={make(() => import('sentry/views/settings/projectTags'))}
|
|
/>
|
|
<Redirect from="issue-tracking/" to="/settings/:orgId/:projectId/plugins/" />
|
|
<Route
|
|
path="release-tracking/"
|
|
name={t('Release Tracking')}
|
|
component={make(
|
|
() => import('sentry/views/settings/project/projectReleaseTracking')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="ownership/"
|
|
name={t('Ownership Rules')}
|
|
component={make(() => import('sentry/views/settings/project/projectOwnership'))}
|
|
/>
|
|
<Route
|
|
path="data-forwarding/"
|
|
name={t('Data Forwarding')}
|
|
component={make(() => import('sentry/views/settings/projectDataForwarding'))}
|
|
/>
|
|
<Route path="security-and-privacy/" name={t('Security & Privacy')}>
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/settings/projectSecurityAndPrivacy')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="advanced-data-scrubbing/:scrubbingId/"
|
|
component={make(
|
|
() => import('sentry/views/settings/projectSecurityAndPrivacy')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="debug-symbols/"
|
|
name={t('Debug Information Files')}
|
|
component={make(() => import('sentry/views/settings/projectDebugFiles'))}
|
|
/>
|
|
<Route
|
|
path="proguard/"
|
|
name={t('ProGuard Mappings')}
|
|
component={make(() => import('sentry/views/settings/projectProguard'))}
|
|
/>
|
|
<Route
|
|
path="performance/"
|
|
name={t('Performance')}
|
|
component={make(() => import('sentry/views/settings/projectPerformance'))}
|
|
/>
|
|
<Route path="metrics/" name={t('Metrics')}>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/projectMetrics'))}
|
|
/>
|
|
<Route
|
|
name={t('Metrics Details')}
|
|
path=":mri/"
|
|
component={make(
|
|
() => import('sentry/views/settings/projectMetrics/projectMetricsDetails')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="replays/"
|
|
name={t('Replays')}
|
|
component={make(() => import('sentry/views/settings/project/projectReplays'))}
|
|
/>
|
|
|
|
<Route path="source-maps/" name={t('Source Maps')}>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/projectSourceMaps'))}
|
|
/>
|
|
<Route
|
|
path="artifact-bundles/"
|
|
name={t('Artifact Bundles')}
|
|
component={make(() => import('sentry/views/settings/projectSourceMaps'))}
|
|
>
|
|
<Route
|
|
name={t('Artifact Bundle')}
|
|
path=":bundleId/"
|
|
component={make(() => import('sentry/views/settings/projectSourceMaps'))}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="release-bundles/"
|
|
name={t('Release Bundles')}
|
|
component={make(() => import('sentry/views/settings/projectSourceMaps'))}
|
|
>
|
|
<Route
|
|
name={t('Release Bundle')}
|
|
path=":bundleId/"
|
|
component={make(() => import('sentry/views/settings/projectSourceMaps'))}
|
|
/>
|
|
</Route>
|
|
<Redirect from=":name/" to="release-bundles/:name/" />
|
|
</Route>
|
|
<Route
|
|
path="processing-issues/"
|
|
name={t('Processing Issues')}
|
|
component={make(
|
|
() => import('sentry/views/settings/project/projectProcessingIssues')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="filters/"
|
|
name={t('Inbound Filters')}
|
|
component={make(() => import('sentry/views/settings/project/projectFilters'))}
|
|
>
|
|
<IndexRedirect to="data-filters/" />
|
|
<Route path=":filterType/" />
|
|
</Route>
|
|
<Redirect from="dynamic-sampling/" to="performance/" />
|
|
<Route
|
|
path="issue-grouping/"
|
|
name={t('Issue Grouping')}
|
|
component={make(() => import('sentry/views/settings/projectIssueGrouping'))}
|
|
/>
|
|
<Route
|
|
path="hooks/"
|
|
name={t('Service Hooks')}
|
|
component={make(
|
|
() => import('sentry/views/settings/project/projectServiceHooks')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="hooks/new/"
|
|
name={t('Create Service Hook')}
|
|
component={make(
|
|
() => import('sentry/views/settings/project/projectCreateServiceHook')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="hooks/:hookId/"
|
|
name={t('Service Hook Details')}
|
|
component={make(
|
|
() => import('sentry/views/settings/project/projectServiceHookDetails')
|
|
)}
|
|
/>
|
|
<Route path="keys/" name={t('Client Keys')}>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/project/projectKeys/list'))}
|
|
/>
|
|
<Route
|
|
path=":keyId/"
|
|
name={t('Details')}
|
|
component={make(
|
|
() => import('sentry/views/settings/project/projectKeys/details')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="loader-script/"
|
|
name={t('Loader Script')}
|
|
component={make(() => import('sentry/views/settings/project/loaderScript'))}
|
|
/>
|
|
<Route
|
|
path="user-feedback/"
|
|
name={t('User Feedback')}
|
|
component={make(
|
|
() => import('sentry/views/settings/project/projectUserFeedback')
|
|
)}
|
|
/>
|
|
<Redirect from="csp/" to="security-headers/" />
|
|
<Route path="security-headers/" name={t('Security Headers')}>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/projectSecurityHeaders'))}
|
|
/>
|
|
<Route
|
|
path="csp/"
|
|
name={t('Content Security Policy')}
|
|
component={make(
|
|
() => import('sentry/views/settings/projectSecurityHeaders/csp')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="expect-ct/"
|
|
name={t('Certificate Transparency')}
|
|
component={make(
|
|
() => import('sentry/views/settings/projectSecurityHeaders/expectCt')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="hpkp/"
|
|
name={t('HPKP')}
|
|
component={make(
|
|
() => import('sentry/views/settings/projectSecurityHeaders/hpkp')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route path="plugins/" name={t('Legacy Integrations')}>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/projectPlugins'))}
|
|
/>
|
|
<Route
|
|
path=":pluginId/"
|
|
name={t('Integration Details')}
|
|
component={make(() => import('sentry/views/settings/projectPlugins/details'))}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
);
|
|
|
|
const orgSettingsRoutes = (
|
|
<Route
|
|
component={make(
|
|
() => import('sentry/views/settings/organization/organizationSettingsLayout')
|
|
)}
|
|
>
|
|
{hook('routes:organization')}
|
|
{!USING_CUSTOMER_DOMAIN && (
|
|
<IndexRoute
|
|
name={t('General')}
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationGeneralSettings')
|
|
)}
|
|
/>
|
|
)}
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/settings/organization/"
|
|
name={t('General')}
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationGeneralSettings')
|
|
)}
|
|
/>
|
|
)}
|
|
<Route
|
|
path="projects/"
|
|
name={t('Projects')}
|
|
component={make(() => import('sentry/views/settings/organizationProjects'))}
|
|
/>
|
|
<Route path="api-keys/" name={t('API Key')}>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/organizationApiKeys'))}
|
|
/>
|
|
<Route
|
|
path=":apiKey/"
|
|
name={t('Details')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationApiKeys/organizationApiKeyDetails'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="audit-log/"
|
|
name={t('Audit Log')}
|
|
component={make(() => import('sentry/views/settings/organizationAuditLog'))}
|
|
/>
|
|
<Route
|
|
path="auth/"
|
|
name={t('Auth Providers')}
|
|
component={make(() => import('sentry/views/settings/organizationAuth'))}
|
|
/>
|
|
<Redirect from="members/requests" to="members/" />
|
|
<Route path="members/" name={t('Members')}>
|
|
<Route
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationMembers/organizationMembersWrapper'
|
|
)
|
|
)}
|
|
>
|
|
<IndexRoute
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationMembers/organizationMembersList'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path=":memberId/"
|
|
name={t('Details')}
|
|
component={make(
|
|
() =>
|
|
import('sentry/views/settings/organizationMembers/organizationMemberDetail')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="rate-limits/"
|
|
name={t('Rate Limits')}
|
|
component={make(() => import('sentry/views/settings/organizationRateLimits'))}
|
|
/>
|
|
<Route
|
|
path="relay/"
|
|
name={t('Relay')}
|
|
component={make(() => import('sentry/views/settings/organizationRelay'))}
|
|
/>
|
|
<Route
|
|
path="repos/"
|
|
name={t('Repositories')}
|
|
component={make(() => import('sentry/views/settings/organizationRepositories'))}
|
|
/>
|
|
<Route
|
|
path="settings/"
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationGeneralSettings')
|
|
)}
|
|
/>
|
|
<Route path="security-and-privacy/" name={t('Security & Privacy')}>
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationSecurityAndPrivacy')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="advanced-data-scrubbing/:scrubbingId/"
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationSecurityAndPrivacy')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route path="teams/" name={t('Teams')}>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/organizationTeams'))}
|
|
/>
|
|
<Route
|
|
path=":teamId/"
|
|
name={t('Team')}
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationTeams/teamDetails')
|
|
)}
|
|
>
|
|
<IndexRedirect to="members/" />
|
|
<Route
|
|
path="members/"
|
|
name={t('Members')}
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationTeams/teamMembers')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="notifications/"
|
|
name={t('Notifications')}
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationTeams/teamNotifications')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="projects/"
|
|
name={t('Projects')}
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationTeams/teamProjects')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="settings/"
|
|
name={t('Settings')}
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationTeams/teamSettings')
|
|
)}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
<Redirect from="plugins/" to="integrations/" />
|
|
<Route path="plugins/" name={t('Integrations')}>
|
|
<Route
|
|
path=":integrationSlug/"
|
|
name={t('Integration Details')}
|
|
component={make(
|
|
() =>
|
|
import('sentry/views/settings/organizationIntegrations/pluginDetailedView')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Redirect from="sentry-apps/" to="integrations/" />
|
|
<Route path="sentry-apps/" name={t('Integrations')}>
|
|
<Route
|
|
path=":integrationSlug"
|
|
name={t('Details')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationIntegrations/sentryAppDetailedView'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Redirect from="document-integrations/" to="integrations/" />
|
|
<Route path="document-integrations/" name={t('Integrations')}>
|
|
<Route
|
|
path=":integrationSlug"
|
|
name={t('Details')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationIntegrations/docIntegrationDetailedView'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route path="integrations/" name={t('Integrations')}>
|
|
<IndexRoute
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationIntegrations/integrationListDirectory'
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":integrationSlug"
|
|
name={t('Integration Details')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationIntegrations/integrationDetailedView'
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":providerKey/:integrationId/"
|
|
name={t('Configure Integration')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationIntegrations/configureIntegration'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Redirect from="developer-settings/sentry-functions/" to="developer-settings/" />
|
|
<Route path="developer-settings/" name={t('Custom Integrations')}>
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationDeveloperSettings')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="new-public/"
|
|
name={t('Create Integration')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationDeveloperSettings/sentryApplicationDetails'
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path="new-internal/"
|
|
name={t('Create Integration')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationDeveloperSettings/sentryApplicationDetails'
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":appSlug/"
|
|
name={t('Edit Integration')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationDeveloperSettings/sentryApplicationDetails'
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":appSlug/dashboard/"
|
|
name={t('Integration Dashboard')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationDeveloperSettings/sentryApplicationDashboard'
|
|
)
|
|
)}
|
|
/>
|
|
<Route path="sentry-functions/" name={t('Sentinel Functions')}>
|
|
<Route
|
|
path="new/"
|
|
name={t('Create Sentinel Function')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationDeveloperSettings/sentryFunctionDetails'
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":functionSlug/"
|
|
name={t('Edit Sentinel Function')}
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/settings/organizationDeveloperSettings/sentryFunctionDetails'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
<Route path="auth-tokens/" name={t('Auth Tokens')}>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/settings/organizationAuthTokens'))}
|
|
/>
|
|
<Route
|
|
path="new-token/"
|
|
name={t('Create New Auth Token')}
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationAuthTokens/newAuthToken')
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":tokenId/"
|
|
name={t('Edit Auth Token')}
|
|
component={make(
|
|
() => import('sentry/views/settings/organizationAuthTokens/authTokenDetails')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="early-features/"
|
|
name={t('Early Features')}
|
|
component={make(() => import('sentry/views/settings/earlyFeatures'))}
|
|
/>
|
|
</Route>
|
|
);
|
|
|
|
const legacySettingsRedirects = (
|
|
<Fragment>
|
|
<Redirect from=":projectId/" to="projects/:projectId/" />
|
|
<Redirect from=":projectId/alerts/" to="projects/:projectId/alerts/" />
|
|
<Redirect from=":projectId/alerts/rules/" to="projects/:projectId/alerts/rules/" />
|
|
<Redirect
|
|
from=":projectId/alerts/rules/:ruleId/"
|
|
to="projects/:projectId/alerts/rules/:ruleId/"
|
|
/>
|
|
</Fragment>
|
|
);
|
|
|
|
const settingsRoutes = (
|
|
<Route path="/settings/" name={t('Settings')} component={SettingsWrapper}>
|
|
<IndexRoute component={make(() => import('sentry/views/settings/settingsIndex'))} />
|
|
{accountSettingsRoutes}
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
name={t('Organization')}
|
|
component={withDomainRequired(NoOp)}
|
|
key="orgless-settings-route"
|
|
>
|
|
{orgSettingsRoutes}
|
|
{projectSettingsRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path=":orgId/"
|
|
name={t('Organization')}
|
|
component={withDomainRedirect(NoOp)}
|
|
key="org-settings"
|
|
>
|
|
{orgSettingsRoutes}
|
|
{projectSettingsRoutes}
|
|
{legacySettingsRedirects}
|
|
</Route>
|
|
</Fragment>
|
|
</Route>
|
|
);
|
|
|
|
const projectsChildRoutes = (
|
|
<Fragment>
|
|
<IndexRoute component={make(() => import('sentry/views/projectsDashboard'))} />
|
|
<Route
|
|
path="new/"
|
|
component={make(() => import('sentry/views/projectInstall/newProject'))}
|
|
/>
|
|
<Route
|
|
path=":projectId/"
|
|
component={make(() => import('sentry/views/projectDetail'))}
|
|
/>
|
|
<Route
|
|
path=":projectId/events/:eventId/"
|
|
component={errorHandler(ProjectEventRedirect)}
|
|
/>
|
|
<Route
|
|
path=":projectId/getting-started/"
|
|
component={make(
|
|
() => import('sentry/views/projectInstall/platformOrIntegration')
|
|
)}
|
|
/>
|
|
</Fragment>
|
|
);
|
|
const projectsRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/projects/"
|
|
component={make(() => import('sentry/views/projects/'))}
|
|
key="orgless-projects-route"
|
|
>
|
|
{projectsChildRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/projects/"
|
|
component={make(() => import('sentry/views/projects/'))}
|
|
key="org-projects"
|
|
>
|
|
{projectsChildRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const dashboardWidgetRoutes = (
|
|
<Fragment>
|
|
<Route
|
|
path="widget/:widgetIndex/edit/"
|
|
component={make(() => import('sentry/views/dashboards/widgetBuilder'))}
|
|
/>
|
|
<Route
|
|
path="widget/new/"
|
|
component={make(() => import('sentry/views/dashboards/widgetBuilder'))}
|
|
/>
|
|
<Route
|
|
path="widget/:widgetId/"
|
|
component={make(() => import('sentry/views/dashboards/view'))}
|
|
/>
|
|
</Fragment>
|
|
);
|
|
const dashboardRoutes = (
|
|
<Fragment>
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/dashboards/"
|
|
component={withDomainRequired(make(() => import('sentry/views/dashboards')))}
|
|
key="orgless-dashboards-route"
|
|
>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/dashboards/manage'))}
|
|
/>
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/dashboards/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/dashboards')))}
|
|
key="org-dashboards"
|
|
>
|
|
<IndexRoute component={make(() => import('sentry/views/dashboards/manage'))} />
|
|
</Route>
|
|
</Fragment>
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/dashboards/new/"
|
|
component={withDomainRequired(
|
|
make(() => import('sentry/views/dashboards/create'))
|
|
)}
|
|
key="orgless-dashboards-new-route"
|
|
>
|
|
<Route
|
|
path="widget/:widgetIndex/edit/"
|
|
component={make(() => import('sentry/views/dashboards/widgetBuilder'))}
|
|
/>
|
|
<Route
|
|
path="widget/new/"
|
|
component={make(() => import('sentry/views/dashboards/widgetBuilder'))}
|
|
/>
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/dashboards/new/"
|
|
component={withDomainRedirect(
|
|
make(() => import('sentry/views/dashboards/create'))
|
|
)}
|
|
key="org-dashboards-new"
|
|
>
|
|
<Route
|
|
path="widget/:widgetIndex/edit/"
|
|
component={make(() => import('sentry/views/dashboards/widgetBuilder'))}
|
|
/>
|
|
<Route
|
|
path="widget/new/"
|
|
component={make(() => import('sentry/views/dashboards/widgetBuilder'))}
|
|
/>
|
|
</Route>
|
|
</Fragment>
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/dashboards/new/:templateId"
|
|
component={withDomainRequired(
|
|
make(() => import('sentry/views/dashboards/create'))
|
|
)}
|
|
key="orgless-dashboards-new-template-route"
|
|
>
|
|
<Route
|
|
path="widget/:widgetId/"
|
|
component={make(() => import('sentry/views/dashboards/create'))}
|
|
/>
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/dashboards/new/:templateId"
|
|
component={withDomainRedirect(
|
|
make(() => import('sentry/views/dashboards/create'))
|
|
)}
|
|
key="org-dashboards-new-template"
|
|
>
|
|
<Route
|
|
path="widget/:widgetId/"
|
|
component={make(() => import('sentry/views/dashboards/create'))}
|
|
/>
|
|
</Route>
|
|
</Fragment>
|
|
<Redirect
|
|
from="/organizations/:orgId/dashboards/:dashboardId/"
|
|
to="/organizations/:orgId/dashboard/:dashboardId/"
|
|
/>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Redirect from="/dashboards/:dashboardId/" to="/dashboard/:dashboardId/" />
|
|
)}
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/dashboard/:dashboardId/"
|
|
component={withDomainRequired(
|
|
make(() => import('sentry/views/dashboards/view'))
|
|
)}
|
|
key="orgless-dashboards-dashboard-id-route"
|
|
>
|
|
{dashboardWidgetRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/dashboard/:dashboardId/"
|
|
component={withDomainRedirect(
|
|
make(() => import('sentry/views/dashboards/view'))
|
|
)}
|
|
key="org-dashboards-dashboard-id"
|
|
>
|
|
{dashboardWidgetRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
</Fragment>
|
|
);
|
|
|
|
const alertChildRoutes = ({forCustomerDomain}: {forCustomerDomain: boolean}) => {
|
|
return (
|
|
<Fragment>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/alerts/list/incidents'))}
|
|
/>
|
|
<Route path="rules/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/alerts/list/rules/alertRulesList')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="details/:ruleId/"
|
|
component={make(() => import('sentry/views/alerts/rules/metric/details'))}
|
|
/>
|
|
<Route
|
|
path=":projectId/"
|
|
component={make(() => import('sentry/views/alerts/builder/projectProvider'))}
|
|
>
|
|
<IndexRedirect
|
|
to={
|
|
forCustomerDomain
|
|
? '/alerts/rules/'
|
|
: '/organizations/:orgId/alerts/rules/'
|
|
}
|
|
/>
|
|
<Route
|
|
path=":ruleId/"
|
|
component={make(() => import('sentry/views/alerts/edit'))}
|
|
/>
|
|
</Route>
|
|
<Route path=":projectId/:ruleId/details/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/alerts/rules/issue/details/ruleDetails')
|
|
)}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
<Route path="metric-rules/">
|
|
<IndexRedirect
|
|
to={
|
|
forCustomerDomain ? '/alerts/rules/' : '/organizations/:orgId/alerts/rules/'
|
|
}
|
|
/>
|
|
<Route
|
|
path=":projectId/"
|
|
component={make(() => import('sentry/views/alerts/builder/projectProvider'))}
|
|
>
|
|
<IndexRedirect
|
|
to={
|
|
forCustomerDomain
|
|
? '/alerts/rules/'
|
|
: '/organizations/:orgId/alerts/rules/'
|
|
}
|
|
/>
|
|
<Route
|
|
path=":ruleId/"
|
|
component={make(() => import('sentry/views/alerts/edit'))}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
<Route
|
|
path="wizard/"
|
|
component={make(() => import('sentry/views/alerts/builder/projectProvider'))}
|
|
>
|
|
<IndexRoute component={make(() => import('sentry/views/alerts/wizard'))} />
|
|
</Route>
|
|
<Route
|
|
path="new/"
|
|
component={make(() => import('sentry/views/alerts/builder/projectProvider'))}
|
|
>
|
|
<IndexRedirect
|
|
to={
|
|
forCustomerDomain
|
|
? '/alerts/wizard/'
|
|
: '/organizations/:orgId/alerts/wizard/'
|
|
}
|
|
/>
|
|
<Route
|
|
path=":alertType/"
|
|
component={make(() => import('sentry/views/alerts/create'))}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path=":alertId/"
|
|
component={make(() => import('sentry/views/alerts/incidentRedirect'))}
|
|
/>
|
|
<Route
|
|
path=":projectId/"
|
|
component={make(() => import('sentry/views/alerts/builder/projectProvider'))}
|
|
>
|
|
<Route
|
|
path="new/"
|
|
component={make(() => import('sentry/views/alerts/create'))}
|
|
/>
|
|
<Route
|
|
path="wizard/"
|
|
component={make(() => import('sentry/views/alerts/wizard'))}
|
|
/>
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
};
|
|
const alertRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/alerts/"
|
|
component={withDomainRequired(make(() => import('sentry/views/alerts')))}
|
|
key="orgless-alerts-route"
|
|
>
|
|
{alertChildRoutes({forCustomerDomain: true})}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/alerts/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/alerts')))}
|
|
key="org-alerts"
|
|
>
|
|
{alertChildRoutes({forCustomerDomain: false})}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const cronsChildRoutes = (
|
|
<Fragment>
|
|
<IndexRoute component={make(() => import('sentry/views/monitors/overview'))} />
|
|
<Route
|
|
path="create/"
|
|
component={make(() => import('sentry/views/monitors/create'))}
|
|
/>
|
|
<Route
|
|
path=":monitorSlug/"
|
|
component={make(() => import('sentry/views/monitors/details'))}
|
|
/>
|
|
<Route
|
|
path=":monitorSlug/edit/"
|
|
component={make(() => import('sentry/views/monitors/edit'))}
|
|
/>
|
|
</Fragment>
|
|
);
|
|
const cronsRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/crons/"
|
|
component={withDomainRequired(make(() => import('sentry/views/monitors')))}
|
|
key="orgless-monitors-route"
|
|
>
|
|
{cronsChildRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/crons/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/monitors')))}
|
|
key="org-monitors"
|
|
>
|
|
{cronsChildRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const replayChildRoutes = (
|
|
<Fragment>
|
|
<IndexRoute component={make(() => import('sentry/views/replays/list'))} />
|
|
<Route
|
|
path="selectors/"
|
|
component={make(
|
|
() => import('sentry/views/replays/deadRageClick/deadRageClickList')
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":replaySlug/"
|
|
component={make(() => import('sentry/views/replays/details'))}
|
|
/>
|
|
</Fragment>
|
|
);
|
|
const replayRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/replays/"
|
|
component={withDomainRequired(make(() => import('sentry/views/replays/index')))}
|
|
key="orgless-replays-route"
|
|
>
|
|
{replayChildRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/replays/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/replays/index')))}
|
|
key="org-replays"
|
|
>
|
|
{replayChildRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const releasesChildRoutes = ({forCustomerDomain}: {forCustomerDomain: boolean}) => {
|
|
return (
|
|
<Fragment>
|
|
<IndexRoute component={make(() => import('sentry/views/releases/list'))} />
|
|
<Route
|
|
path=":release/"
|
|
component={make(() => import('sentry/views/releases/detail'))}
|
|
>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/releases/detail/overview'))}
|
|
/>
|
|
<Route
|
|
path="commits/"
|
|
component={make(
|
|
() => import('sentry/views/releases/detail/commitsAndFiles/commits')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="files-changed/"
|
|
component={make(
|
|
() => import('sentry/views/releases/detail/commitsAndFiles/filesChanged')
|
|
)}
|
|
/>
|
|
{forCustomerDomain ? null : (
|
|
<Fragment>
|
|
<Redirect
|
|
from="new-events/"
|
|
to="/organizations/:orgId/releases/:release/"
|
|
/>
|
|
<Redirect
|
|
from="all-events/"
|
|
to="/organizations/:orgId/releases/:release/"
|
|
/>
|
|
</Fragment>
|
|
)}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
};
|
|
const releasesRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/releases/"
|
|
component={withDomainRequired(NoOp)}
|
|
key="orgless-releases-route"
|
|
>
|
|
{releasesChildRoutes({forCustomerDomain: true})}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/releases/"
|
|
component={withDomainRedirect(NoOp)}
|
|
key="org-releases"
|
|
>
|
|
{releasesChildRoutes({forCustomerDomain: false})}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
const releaseThresholdRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/release-thresholds/"
|
|
component={withDomainRequired(NoOp)}
|
|
key="orgless-release-thresholds-route"
|
|
>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/releases/thresholdsList'))}
|
|
/>
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/release-thresholds/"
|
|
component={withDomainRedirect(NoOp)}
|
|
key="org-release-thresholds"
|
|
>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/releases/thresholdsList'))}
|
|
/>
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const activityRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/activity/"
|
|
component={withDomainRequired(
|
|
make(() => import('sentry/views/organizationActivity'))
|
|
)}
|
|
key="orgless-activity-route"
|
|
/>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/activity/"
|
|
component={withDomainRedirect(
|
|
make(() => import('sentry/views/organizationActivity'))
|
|
)}
|
|
key="org-activity"
|
|
/>
|
|
</Fragment>
|
|
);
|
|
|
|
const statsChildRoutes = ({forCustomerDomain}: {forCustomerDomain: boolean}) => {
|
|
return (
|
|
<Fragment>
|
|
<IndexRoute component={make(() => import('sentry/views/organizationStats'))} />
|
|
<Route
|
|
path="issues/"
|
|
component={make(() => import('sentry/views/organizationStats/teamInsights'))}
|
|
>
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/organizationStats/teamInsights/issues')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="health/"
|
|
component={make(() => import('sentry/views/organizationStats/teamInsights'))}
|
|
>
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/organizationStats/teamInsights/health')
|
|
)}
|
|
/>
|
|
</Route>
|
|
{forCustomerDomain ? null : (
|
|
<Redirect from="team/" to="/organizations/:orgId/stats/issues/" />
|
|
)}
|
|
</Fragment>
|
|
);
|
|
};
|
|
const statsRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/stats/"
|
|
component={withDomainRequired(NoOp)}
|
|
key="orgless-stats-route"
|
|
>
|
|
{statsChildRoutes({forCustomerDomain: true})}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/stats/"
|
|
component={withDomainRedirect(NoOp)}
|
|
key="org-stats"
|
|
>
|
|
{statsChildRoutes({forCustomerDomain: false})}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
// 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
|
|
const discoverChildRoutes = (
|
|
<Fragment>
|
|
<IndexRedirect to="queries/" />
|
|
<Route
|
|
path="homepage/"
|
|
component={make(() => import('sentry/views/discover/homepage'))}
|
|
/>
|
|
<Route
|
|
path="queries/"
|
|
component={make(() => import('sentry/views/discover/landing'))}
|
|
/>
|
|
<Route
|
|
path="results/"
|
|
component={make(() => import('sentry/views/discover/results'))}
|
|
/>
|
|
<Route
|
|
path=":eventSlug/"
|
|
component={make(() => import('sentry/views/discover/eventDetails'))}
|
|
/>
|
|
</Fragment>
|
|
);
|
|
const discoverRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/discover/"
|
|
component={withDomainRequired(make(() => import('sentry/views/discover')))}
|
|
key="orgless-discover-route"
|
|
>
|
|
{discoverChildRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/discover/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/discover')))}
|
|
key="org-discover-route"
|
|
>
|
|
{discoverChildRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const performanceChildRoutes = (
|
|
<Fragment>
|
|
<IndexRoute component={make(() => import('sentry/views/performance/content'))} />
|
|
<Route
|
|
path="trends/"
|
|
component={make(() => import('sentry/views/performance/trends'))}
|
|
/>
|
|
<Route path="database/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/performance/database/databaseLandingPage')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="spans/span/:groupId/"
|
|
component={make(
|
|
() => import('sentry/views/performance/database/databaseSpanSummaryPage')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route path="browser/">
|
|
<Route path="interactions/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/performance/browser/interactionsLandingPage')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="summary/"
|
|
component={make(
|
|
() => import('sentry/views/performance/browser/interactionSummary/index')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route path="pageloads/">
|
|
<IndexRoute
|
|
component={make(
|
|
() =>
|
|
import('sentry/views/performance/browser/webVitals/webVitalsLandingPage')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="overview/"
|
|
component={make(
|
|
() => import('sentry/views/performance/browser/webVitals/pageOverview')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route path="resources/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/performance/browser/resources/index')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="spans/span/:groupId/"
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/performance/browser/resources/resourceSummaryPage/index'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
<Route path="mobile/">
|
|
<Route path="screens/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/starfish/modules/mobile/pageload')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="spans/"
|
|
component={make(
|
|
() => import('sentry/views/starfish/views/screens/screenLoadSpans')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route path="app-startup/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/starfish/modules/mobile/appStartup')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="spans/"
|
|
component={make(
|
|
() => import('sentry/views/starfish/views/appStartup/screenSummary')
|
|
)}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
<Route path="summary/">
|
|
<IndexRoute
|
|
component={make(
|
|
() =>
|
|
import('sentry/views/performance/transactionSummary/transactionOverview')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="replays/"
|
|
component={make(
|
|
() => import('sentry/views/performance/transactionSummary/transactionReplays')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="vitals/"
|
|
component={make(
|
|
() => import('sentry/views/performance/transactionSummary/transactionVitals')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="tags/"
|
|
component={make(
|
|
() => import('sentry/views/performance/transactionSummary/transactionTags')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="events/"
|
|
component={make(
|
|
() => import('sentry/views/performance/transactionSummary/transactionEvents')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="anomalies/"
|
|
component={make(
|
|
() =>
|
|
import('sentry/views/performance/transactionSummary/transactionAnomalies')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="profiles/"
|
|
component={make(
|
|
() =>
|
|
import('sentry/views/performance/transactionSummary/transactionProfiles')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="aggregateWaterfall/"
|
|
component={make(
|
|
() =>
|
|
import('sentry/views/performance/transactionSummary/aggregateSpanWaterfall')
|
|
)}
|
|
/>
|
|
<Route path="spans/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/performance/transactionSummary/transactionSpans')
|
|
)}
|
|
/>
|
|
<Route
|
|
path=":spanSlug/"
|
|
component={make(
|
|
() =>
|
|
import(
|
|
'sentry/views/performance/transactionSummary/transactionSpans/spanDetails'
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
</Route>
|
|
<Route
|
|
path="vitaldetail/"
|
|
component={make(() => import('sentry/views/performance/vitalDetail'))}
|
|
/>
|
|
<Route
|
|
path="trace/:traceSlug/"
|
|
component={make(() => import('sentry/views/performance/traceDetails'))}
|
|
/>
|
|
<Route
|
|
path=":eventSlug/"
|
|
component={make(() => import('sentry/views/performance/transactionDetails'))}
|
|
/>
|
|
</Fragment>
|
|
);
|
|
const performanceRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/performance/"
|
|
component={withDomainRequired(make(() => import('sentry/views/performance')))}
|
|
key="orgless-performance-route"
|
|
>
|
|
{performanceChildRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/performance/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/performance')))}
|
|
key="org-performance"
|
|
>
|
|
{performanceChildRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const starfishChildRoutes = (
|
|
<Fragment>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/starfish/views/webServiceView'))}
|
|
/>
|
|
<Route path="endpoint-overview/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/starfish/views/webServiceView/endpointOverview')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="span/:groupId/"
|
|
component={make(() => import('sentry/views/starfish/views/spanSummaryPage'))}
|
|
/>
|
|
</Route>
|
|
<Redirect from="database/" to="/performance/database" />
|
|
<Route path="appStartup/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/starfish/modules/mobile/appStartup')
|
|
)}
|
|
/>
|
|
<Route
|
|
path="spans/"
|
|
component={make(
|
|
() => import('sentry/views/starfish/views/appStartup/screenSummary')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route path="responsiveness/">
|
|
<IndexRoute
|
|
component={make(
|
|
() => import('sentry/views/starfish/modules/mobile/responsiveness')
|
|
)}
|
|
/>
|
|
</Route>
|
|
<Route path="spans/">
|
|
<IndexRoute component={make(() => import('sentry/views/starfish/views/spans'))} />
|
|
<Route
|
|
path="span/:groupId/"
|
|
component={make(() => import('sentry/views/starfish/views/spanSummaryPage'))}
|
|
/>
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
const starfishRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/starfish/"
|
|
component={withDomainRequired(make(() => import('sentry/views/starfish')))}
|
|
key="orgless-starfish-route"
|
|
>
|
|
{starfishChildRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/starfish/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/starfish/')))}
|
|
key="org-starfish"
|
|
>
|
|
{starfishChildRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const userFeedbackRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/user-feedback/"
|
|
component={withDomainRequired(make(() => import('sentry/views/userFeedback')))}
|
|
key="orgless-user-feedback-route"
|
|
/>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/user-feedback/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/userFeedback')))}
|
|
key="org-user-feedback"
|
|
/>
|
|
</Fragment>
|
|
);
|
|
|
|
const feedbackChildRoutes = (
|
|
<Fragment>
|
|
<IndexRoute
|
|
component={make(() => import('sentry/views/feedback/feedbackListPage'))}
|
|
/>
|
|
</Fragment>
|
|
);
|
|
const feedbackv2Routes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/feedback/"
|
|
component={withDomainRequired(
|
|
make(() => import('sentry/views/feedback/index'))
|
|
)}
|
|
key="orgless-feedback-list-route"
|
|
>
|
|
{feedbackChildRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/feedback/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/feedback/index')))}
|
|
key="org-feedback-list-route"
|
|
>
|
|
{feedbackChildRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const issueListRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/issues/(searches/:searchId/)"
|
|
component={withDomainRequired(errorHandler(IssueListContainer))}
|
|
key="orgless-issues-route"
|
|
>
|
|
<IndexRoute component={errorHandler(IssueListOverview)} />
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/issues/(searches/:searchId/)"
|
|
component={withDomainRedirect(errorHandler(IssueListContainer))}
|
|
key="org-issues"
|
|
>
|
|
<Redirect from="/organizations/:orgId/" to="/organizations/:orgId/issues/" />
|
|
<IndexRoute component={errorHandler(IssueListOverview)} />
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
// Once org issues is complete, these routes can be nested under
|
|
// /organizations/:orgId/issues
|
|
const issueTabs = ({forCustomerDomain}: {forCustomerDomain: boolean}) => {
|
|
const hoc = forCustomerDomain ? withDomainRequired : x => x;
|
|
return (
|
|
<Fragment>
|
|
<IndexRoute
|
|
component={hoc(
|
|
make(() => import('sentry/views/issueDetails/groupEventDetails'))
|
|
)}
|
|
/>
|
|
<Route
|
|
path={TabPaths[Tab.REPLAYS]}
|
|
component={hoc(make(() => import('sentry/views/issueDetails/groupReplays')))}
|
|
/>
|
|
<Route
|
|
path={TabPaths[Tab.ACTIVITY]}
|
|
component={hoc(make(() => import('sentry/views/issueDetails/groupActivity')))}
|
|
/>
|
|
<Route
|
|
path={TabPaths[Tab.EVENTS]}
|
|
component={hoc(make(() => import('sentry/views/issueDetails/groupEvents')))}
|
|
/>
|
|
<Route
|
|
path={TabPaths[Tab.TAGS]}
|
|
component={hoc(make(() => import('sentry/views/issueDetails/groupTags')))}
|
|
/>
|
|
<Route
|
|
path={`${TabPaths[Tab.TAGS]}:tagKey/`}
|
|
component={make(() => import('sentry/views/issueDetails/groupTagValues'))}
|
|
/>
|
|
<Route
|
|
path={TabPaths[Tab.USER_FEEDBACK]}
|
|
component={hoc(
|
|
make(() => import('sentry/views/issueDetails/groupUserFeedback'))
|
|
)}
|
|
/>
|
|
<Route
|
|
path={TabPaths[Tab.ATTACHMENTS]}
|
|
component={hoc(
|
|
make(() => import('sentry/views/issueDetails/groupEventAttachments'))
|
|
)}
|
|
/>
|
|
<Route
|
|
path={TabPaths[Tab.SIMILAR_ISSUES]}
|
|
component={hoc(
|
|
make(() => import('sentry/views/issueDetails/groupSimilarIssues'))
|
|
)}
|
|
/>
|
|
<Route
|
|
path={TabPaths[Tab.MERGED]}
|
|
component={hoc(make(() => import('sentry/views/issueDetails/groupMerged')))}
|
|
/>
|
|
</Fragment>
|
|
);
|
|
};
|
|
const issueDetailsChildRoutes = ({forCustomerDomain}: {forCustomerDomain: boolean}) => (
|
|
<Fragment>
|
|
{issueTabs({forCustomerDomain})}
|
|
<Route path={`${TabPaths[Tab.EVENTS]}:eventId/`}>
|
|
{issueTabs({forCustomerDomain})}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
const issueDetailsRoutes = (
|
|
<Fragment>
|
|
<Route
|
|
path="/organizations/:orgId/issues/:groupId/"
|
|
component={withDomainRedirect(
|
|
make(() => import('sentry/views/issueDetails/groupDetails'))
|
|
)}
|
|
key="org-issues-group-id"
|
|
>
|
|
{issueDetailsChildRoutes({forCustomerDomain: false})}
|
|
</Route>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/issues/:groupId/"
|
|
component={withDomainRequired(
|
|
make(() => import('sentry/views/issueDetails/groupDetails'))
|
|
)}
|
|
key="orgless-issues-group-id-route"
|
|
>
|
|
{issueDetailsChildRoutes({forCustomerDomain: true})}
|
|
</Route>
|
|
)}
|
|
</Fragment>
|
|
);
|
|
|
|
// These are the "manage" pages. For sentry.io, these are _different_ from
|
|
// the SaaS admin routes in getsentry.
|
|
const adminManageRoutes = (
|
|
<Route
|
|
path="/manage/"
|
|
component={make(() => import('sentry/views/admin/adminLayout'))}
|
|
>
|
|
<IndexRoute component={make(() => import('sentry/views/admin/adminOverview'))} />
|
|
<Route
|
|
path="buffer/"
|
|
component={make(() => import('sentry/views/admin/adminBuffer'))}
|
|
/>
|
|
<Route
|
|
path="relays/"
|
|
component={make(() => import('sentry/views/admin/adminRelays'))}
|
|
/>
|
|
<Route
|
|
path="organizations/"
|
|
component={make(() => import('sentry/views/admin/adminOrganizations'))}
|
|
/>
|
|
<Route
|
|
path="projects/"
|
|
component={make(() => import('sentry/views/admin/adminProjects'))}
|
|
/>
|
|
<Route
|
|
path="queue/"
|
|
component={make(() => import('sentry/views/admin/adminQueue'))}
|
|
/>
|
|
<Route
|
|
path="quotas/"
|
|
component={make(() => import('sentry/views/admin/adminQuotas'))}
|
|
/>
|
|
<Route
|
|
path="settings/"
|
|
component={make(() => import('sentry/views/admin/adminSettings'))}
|
|
/>
|
|
<Route path="users/">
|
|
<IndexRoute component={make(() => import('sentry/views/admin/adminUsers'))} />
|
|
<Route
|
|
path=":id"
|
|
component={make(() => import('sentry/views/admin/adminUserEdit'))}
|
|
/>
|
|
</Route>
|
|
<Route
|
|
path="status/mail/"
|
|
component={make(() => import('sentry/views/admin/adminMail'))}
|
|
/>
|
|
<Route
|
|
path="status/environment/"
|
|
component={make(() => import('sentry/views/admin/adminEnvironment'))}
|
|
/>
|
|
<Route
|
|
path="status/packages/"
|
|
component={make(() => import('sentry/views/admin/adminPackages'))}
|
|
/>
|
|
<Route
|
|
path="status/warnings/"
|
|
component={make(() => import('sentry/views/admin/adminWarnings'))}
|
|
/>
|
|
</Route>
|
|
);
|
|
|
|
// XXX(epurkhiser): This should probably go away. It's not totally clear to
|
|
// me why we need the OrganizationRoot root container.
|
|
const legacyOrganizationRootRoutes = (
|
|
<Route component={errorHandler(OrganizationRoot)}>
|
|
<Redirect from="/organizations/:orgId/teams/new/" to="/settings/:orgId/teams/" />
|
|
<Route path="/organizations/:orgId/">
|
|
{hook('routes:organization')}
|
|
<Redirect from="/organizations/:orgId/teams/" to="/settings/:orgId/teams/" />
|
|
<Redirect
|
|
from="/organizations/:orgId/teams/your-teams/"
|
|
to="/settings/:orgId/teams/"
|
|
/>
|
|
<Redirect
|
|
from="/organizations/:orgId/teams/all-teams/"
|
|
to="/settings/:orgId/teams/"
|
|
/>
|
|
<Redirect
|
|
from="/organizations/:orgId/teams/:teamId/"
|
|
to="/settings/:orgId/teams/:teamId/"
|
|
/>
|
|
<Redirect
|
|
from="/organizations/:orgId/teams/:teamId/members/"
|
|
to="/settings/:orgId/teams/:teamId/members/"
|
|
/>
|
|
<Redirect
|
|
from="/organizations/:orgId/teams/:teamId/projects/"
|
|
to="/settings/:orgId/teams/:teamId/projects/"
|
|
/>
|
|
<Redirect
|
|
from="/organizations/:orgId/teams/:teamId/settings/"
|
|
to="/settings/:orgId/teams/:teamId/settings/"
|
|
/>
|
|
<Redirect from="/organizations/:orgId/settings/" to="/settings/:orgId/" />
|
|
<Redirect
|
|
from="/organizations/:orgId/api-keys/"
|
|
to="/settings/:orgId/api-keys/"
|
|
/>
|
|
<Redirect
|
|
from="/organizations/:orgId/api-keys/:apiKey/"
|
|
to="/settings/:orgId/api-keys/:apiKey/"
|
|
/>
|
|
<Redirect from="/organizations/:orgId/members/" to="/settings/:orgId/members/" />
|
|
<Redirect
|
|
from="/organizations/:orgId/members/:memberId/"
|
|
to="/settings/:orgId/members/:memberId/"
|
|
/>
|
|
<Redirect
|
|
from="/organizations/:orgId/rate-limits/"
|
|
to="/settings/:orgId/rate-limits/"
|
|
/>
|
|
<Redirect from="/organizations/:orgId/repos/" to="/settings/:orgId/repos/" />
|
|
</Route>
|
|
</Route>
|
|
);
|
|
|
|
const gettingStartedRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Fragment>
|
|
<Redirect
|
|
from="/getting-started/:projectId/"
|
|
to="/projects/:projectId/getting-started/"
|
|
/>
|
|
<Redirect
|
|
from="/getting-started/:projectId/:platform/"
|
|
to="/projects/:projectId/getting-started/"
|
|
/>
|
|
</Fragment>
|
|
)}
|
|
<Redirect
|
|
from="/:orgId/:projectId/getting-started/"
|
|
to="/organizations/:orgId/projects/:projectId/getting-started/"
|
|
/>
|
|
<Redirect
|
|
from="/:orgId/:projectId/getting-started/:platform/"
|
|
to="/organizations/:orgId/projects/:projectId/getting-started/"
|
|
/>
|
|
</Fragment>
|
|
);
|
|
|
|
const profilingChildRoutes = (
|
|
<Fragment>
|
|
<IndexRoute component={make(() => import('sentry/views/profiling/content'))} />
|
|
<Route
|
|
path="summary/:projectId/"
|
|
component={make(() => import('sentry/views/profiling/profileSummary'))}
|
|
/>
|
|
<Route
|
|
path="profile/:projectId/differential-flamegraph/"
|
|
component={make(() => import('sentry/views/profiling/differentialFlamegraph'))}
|
|
/>
|
|
<Route
|
|
path="profile/:projectId/:eventId/"
|
|
component={make(() => import('sentry/views/profiling/profilesProvider'))}
|
|
>
|
|
<Route
|
|
path="flamegraph/"
|
|
component={make(() => import('sentry/views/profiling/profileFlamechart'))}
|
|
/>
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
const profilingRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/profiling/"
|
|
component={withDomainRequired(make(() => import('sentry/views/profiling')))}
|
|
key="orgless-profiling-route"
|
|
>
|
|
{profilingChildRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/profiling/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/profiling')))}
|
|
key="org-profiling"
|
|
>
|
|
{profilingChildRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
const ddmChildRoutes = (
|
|
<Fragment>
|
|
<IndexRoute component={make(() => import('sentry/views/ddm/ddm'))} />
|
|
</Fragment>
|
|
);
|
|
|
|
const ddmRoutes = (
|
|
<Fragment>
|
|
{USING_CUSTOMER_DOMAIN && (
|
|
<Route
|
|
path="/ddm/"
|
|
component={withDomainRequired(make(() => import('sentry/views/ddm')))}
|
|
key="orgless-ddm-route"
|
|
>
|
|
{ddmChildRoutes}
|
|
</Route>
|
|
)}
|
|
<Route
|
|
path="/organizations/:orgId/ddm/"
|
|
component={withDomainRedirect(make(() => import('sentry/views/ddm')))}
|
|
key="org-ddm"
|
|
>
|
|
{ddmChildRoutes}
|
|
</Route>
|
|
</Fragment>
|
|
);
|
|
|
|
// Support for deprecated URLs (pre-Sentry 10). We just redirect users to new
|
|
// canonical URLs.
|
|
//
|
|
// XXX(epurkhiser): Can these be moved over to the legacyOrgRedirects routes,
|
|
// or do these need to be nested into the OrganizationLayout tree?
|
|
const legacyOrgRedirects = (
|
|
<Route path="/:orgId/:projectId/">
|
|
<IndexRoute
|
|
component={errorHandler(
|
|
redirectDeprecatedProjectRoute(
|
|
({orgId, projectId}) => `/organizations/${orgId}/issues/?project=${projectId}`
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path="issues/"
|
|
component={errorHandler(
|
|
redirectDeprecatedProjectRoute(
|
|
({orgId, projectId}) => `/organizations/${orgId}/issues/?project=${projectId}`
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path="dashboard/"
|
|
component={errorHandler(
|
|
redirectDeprecatedProjectRoute(
|
|
({orgId, projectId}) =>
|
|
`/organizations/${orgId}/dashboards/?project=${projectId}`
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path="user-feedback/"
|
|
component={errorHandler(
|
|
redirectDeprecatedProjectRoute(
|
|
({orgId, projectId}) =>
|
|
`/organizations/${orgId}/user-feedback/?project=${projectId}`
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path="releases/"
|
|
component={errorHandler(
|
|
redirectDeprecatedProjectRoute(
|
|
({orgId, projectId}) =>
|
|
`/organizations/${orgId}/releases/?project=${projectId}`
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path="releases/:version/"
|
|
component={errorHandler(
|
|
redirectDeprecatedProjectRoute(
|
|
({orgId, projectId, router}) =>
|
|
`/organizations/${orgId}/releases/${router.params.version}/?project=${projectId}`
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path="releases/:version/new-events/"
|
|
component={errorHandler(
|
|
redirectDeprecatedProjectRoute(
|
|
({orgId, projectId, router}) =>
|
|
`/organizations/${orgId}/releases/${router.params.version}/new-events/?project=${projectId}`
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path="releases/:version/all-events/"
|
|
component={errorHandler(
|
|
redirectDeprecatedProjectRoute(
|
|
({orgId, projectId, router}) =>
|
|
`/organizations/${orgId}/releases/${router.params.version}/all-events/?project=${projectId}`
|
|
)
|
|
)}
|
|
/>
|
|
<Route
|
|
path="releases/:version/commits/"
|
|
component={errorHandler(
|
|
redirectDeprecatedProjectRoute(
|
|
({orgId, projectId, router}) =>
|
|
`/organizations/${orgId}/releases/${router.params.version}/commits/?project=${projectId}`
|
|
)
|
|
)}
|
|
/>
|
|
</Route>
|
|
);
|
|
|
|
const organizationRoutes = (
|
|
<Route component={errorHandler(OrganizationLayout)}>
|
|
{settingsRoutes}
|
|
{projectsRoutes}
|
|
{dashboardRoutes}
|
|
{userFeedbackRoutes}
|
|
{feedbackv2Routes}
|
|
{issueListRoutes}
|
|
{issueDetailsRoutes}
|
|
{alertRoutes}
|
|
{cronsRoutes}
|
|
{replayRoutes}
|
|
{releasesRoutes}
|
|
{releaseThresholdRoutes}
|
|
{activityRoutes}
|
|
{statsRoutes}
|
|
{discoverRoutes}
|
|
{performanceRoutes}
|
|
{starfishRoutes}
|
|
{profilingRoutes}
|
|
{ddmRoutes}
|
|
{gettingStartedRoutes}
|
|
{adminManageRoutes}
|
|
{legacyOrganizationRootRoutes}
|
|
{legacyOrgRedirects}
|
|
</Route>
|
|
);
|
|
|
|
const legacyRedirectRoutes = (
|
|
<Route path="/:orgId/">
|
|
<IndexRedirect to="/organizations/:orgId/" />
|
|
<Route path=":projectId/settings/">
|
|
<Redirect from="teams/" to="/settings/:orgId/projects/:projectId/teams/" />
|
|
<Redirect from="alerts/" to="/settings/:orgId/projects/:projectId/alerts/" />
|
|
<Redirect
|
|
from="alerts/rules/"
|
|
to="/settings/:orgId/projects/:projectId/alerts/rules/"
|
|
/>
|
|
<Redirect
|
|
from="alerts/rules/new/"
|
|
to="/settings/:orgId/projects/:projectId/alerts/rules/new/"
|
|
/>
|
|
<Redirect
|
|
from="alerts/rules/:ruleId/"
|
|
to="/settings/:orgId/projects/:projectId/alerts/rules/:ruleId/"
|
|
/>
|
|
<Redirect
|
|
from="environments/"
|
|
to="/settings/:orgId/projects/:projectId/environments/"
|
|
/>
|
|
<Redirect
|
|
from="environments/hidden/"
|
|
to="/settings/:orgId/projects/:projectId/environments/hidden/"
|
|
/>
|
|
<Redirect from="tags/" to="/settings/projects/:orgId/projects/:projectId/tags/" />
|
|
<Redirect
|
|
from="issue-tracking/"
|
|
to="/settings/:orgId/projects/:projectId/issue-tracking/"
|
|
/>
|
|
<Redirect
|
|
from="release-tracking/"
|
|
to="/settings/:orgId/projects/:projectId/release-tracking/"
|
|
/>
|
|
<Redirect
|
|
from="ownership/"
|
|
to="/settings/:orgId/projects/:projectId/ownership/"
|
|
/>
|
|
<Redirect
|
|
from="data-forwarding/"
|
|
to="/settings/:orgId/projects/:projectId/data-forwarding/"
|
|
/>
|
|
<Redirect
|
|
from="debug-symbols/"
|
|
to="/settings/:orgId/projects/:projectId/debug-symbols/"
|
|
/>
|
|
<Redirect
|
|
from="processing-issues/"
|
|
to="/settings/:orgId/projects/:projectId/processing-issues/"
|
|
/>
|
|
<Redirect from="filters/" to="/settings/:orgId/projects/:projectId/filters/" />
|
|
<Redirect from="hooks/" to="/settings/:orgId/projects/:projectId/hooks/" />
|
|
<Redirect from="keys/" to="/settings/:orgId/projects/:projectId/keys/" />
|
|
<Redirect
|
|
from="keys/:keyId/"
|
|
to="/settings/:orgId/projects/:projectId/keys/:keyId/"
|
|
/>
|
|
<Redirect
|
|
from="user-feedback/"
|
|
to="/settings/:orgId/projects/:projectId/user-feedback/"
|
|
/>
|
|
<Redirect
|
|
from="security-headers/"
|
|
to="/settings/:orgId/projects/:projectId/security-headers/"
|
|
/>
|
|
<Redirect
|
|
from="security-headers/csp/"
|
|
to="/settings/:orgId/projects/:projectId/security-headers/csp/"
|
|
/>
|
|
<Redirect
|
|
from="security-headers/expect-ct/"
|
|
to="/settings/:orgId/projects/:projectId/security-headers/expect-ct/"
|
|
/>
|
|
<Redirect
|
|
from="security-headers/hpkp/"
|
|
to="/settings/:orgId/projects/:projectId/security-headers/hpkp/"
|
|
/>
|
|
<Redirect from="plugins/" to="/settings/:orgId/projects/:projectId/plugins/" />
|
|
<Redirect
|
|
from="plugins/:pluginId/"
|
|
to="/settings/:orgId/projects/:projectId/plugins/:pluginId/"
|
|
/>
|
|
<Redirect
|
|
from="integrations/:providerKey/"
|
|
to="/settings/:orgId/projects/:projectId/integrations/:providerKey/"
|
|
/>
|
|
</Route>
|
|
<Redirect from=":projectId/group/:groupId/" to="issues/:groupId/" />
|
|
<Redirect
|
|
from=":projectId/issues/:groupId/"
|
|
to="/organizations/:orgId/issues/:groupId/"
|
|
/>
|
|
<Redirect
|
|
from=":projectId/issues/:groupId/events/"
|
|
to="/organizations/:orgId/issues/:groupId/events/"
|
|
/>
|
|
<Redirect
|
|
from=":projectId/issues/:groupId/events/:eventId/"
|
|
to="/organizations/:orgId/issues/:groupId/events/:eventId/"
|
|
/>
|
|
<Redirect
|
|
from=":projectId/issues/:groupId/tags/"
|
|
to="/organizations/:orgId/issues/:groupId/tags/"
|
|
/>
|
|
<Redirect
|
|
from=":projectId/issues/:groupId/tags/:tagKey/"
|
|
to="/organizations/:orgId/issues/:groupId/tags/:tagKey/"
|
|
/>
|
|
<Redirect
|
|
from=":projectId/issues/:groupId/feedback/"
|
|
to="/organizations/:orgId/issues/:groupId/feedback/"
|
|
/>
|
|
<Redirect
|
|
from=":projectId/issues/:groupId/similar/"
|
|
to="/organizations/:orgId/issues/:groupId/similar/"
|
|
/>
|
|
<Redirect
|
|
from=":projectId/issues/:groupId/merged/"
|
|
to="/organizations/:orgId/issues/:groupId/merged/"
|
|
/>
|
|
<Route
|
|
path=":projectId/events/:eventId/"
|
|
component={errorHandler(ProjectEventRedirect)}
|
|
/>
|
|
</Route>
|
|
);
|
|
|
|
const appRoutes = (
|
|
<Route>
|
|
{experimentalSpaRoutes}
|
|
<Route path="/" component={errorHandler(App)}>
|
|
{rootRoutes}
|
|
{organizationRoutes}
|
|
{legacyRedirectRoutes}
|
|
<Route path="*" component={errorHandler(RouteNotFound)} />
|
|
</Route>
|
|
</Route>
|
|
);
|
|
|
|
return appRoutes;
|
|
}
|
|
|
|
// We load routes both when initializing the SDK (for routing integrations) and
|
|
// when the app renders Main. Memoize to avoid rebuilding the route tree.
|
|
export const routes = memoize(buildRoutes);
|
|
|
|
// Exported for use in tests.
|
|
export {buildRoutes};
|
|
|
|
function NoOp({children}: {children: JSX.Element}) {
|
|
return children;
|
|
}
|