Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {ConfigStore} from 'sentry/stores/configStore';
import type {Config} from 'sentry/types/system';
import {getSettingsFieldSections} from 'sentry/views/settings/settingsCommandPaletteActions';

describe('getSettingsFieldSections', () => {
let configState: Config;

beforeEach(() => {
configState = ConfigStore.getState();
});

afterEach(() => {
ConfigStore.loadInitialData(configState);
});

it('includes the org slug in org-level route paths when not on a customer domain', () => {
// No customerDomain set — normalizeUrl is a no-op, so the slug should be substituted.
ConfigStore.set('customerDomain', null);

const sections = getSettingsFieldSections('doogi');

const allPathnames = sections.flatMap(section =>
section.fields.map(field => field.to.pathname)
);
const orgPaths = allPathnames.filter(
p => p.startsWith('/settings/') && !p.startsWith('/settings/account/')
);

// Every org-level path must include the org slug and must NOT contain the literal `:orgId`.
for (const pathname of orgPaths) {
expect(pathname).toContain('doogi');
expect(pathname).not.toContain(':orgId');
}
});

it('does not include the org slug in org-level route paths when on a customer domain', () => {
// With customerDomain set, normalizeUrl strips the orgId segment from the
// template before the slug is substituted, so the resulting path should NOT
// contain the real slug (the org is identified by the subdomain instead).
ConfigStore.loadInitialData({
...configState,
customerDomain: {
subdomain: 'doogi',
organizationUrl: 'https://doogi.sentry.io',
sentryUrl: 'https://sentry.io',
},
});

const sections = getSettingsFieldSections('doogi');

const allPathnames = sections.flatMap(section =>
section.fields.map(field => field.to.pathname)
);
const orgPaths = allPathnames.filter(
p => p.startsWith('/settings/') && !p.startsWith('/settings/account/')
);

// Org paths must not include the slug and must not contain `:orgId`.
for (const pathname of orgPaths) {
expect(pathname).not.toContain('doogi');
expect(pathname).not.toContain(':orgId');
}
});
});
15 changes: 13 additions & 2 deletions static/app/views/settings/settingsCommandPaletteActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {CommandPaletteSlot} from 'sentry/components/commandPalette/ui/commandPal
import {IconLock, IconMail, IconSettings, IconSubscribed, IconUser} from 'sentry/icons';
import {t} from 'sentry/locale';
import {replaceRouterParams} from 'sentry/utils/replaceRouterParams';
import {normalizeUrl} from 'sentry/utils/url/normalizeUrl';
import {useOrganization} from 'sentry/utils/useOrganization';
import {FORM_FIELD_REGISTRY} from 'sentry/views/settings/fieldRegistry.generated';
import {getUserOrgNavigationConfiguration} from 'sentry/views/settings/organization/userOrgNavigationConfiguration';
Expand All @@ -26,7 +27,17 @@ function normalizeRouteForLookup(route: string): string {
}

function resolveRoutePath(route: string, orgSlug: string): string {
return replaceRouterParams(normalizeRouteForLookup(route), {orgId: orgSlug});
// Apply normalizeUrl to the route template *before* substituting the real org
// slug. On customer-domain sessions, normalizeUrl strips the `:orgId` segment
// from the template (e.g. `/settings/:orgId/feature-flags/…` →
// `/settings/feature-flags/…`), so replaceRouterParams becomes a no-op and
// the stored URL is already canonical. Without this, the command palette's
// own normalizeUrl call (commandPalette.tsx) would strip a real slug that had
// already been substituted, producing a broken path such as
// `/settings/feature-flags/…` where `feature-flags` would be mis-matched as
// `:orgId` by the router.
const normalizedRoute = normalizeUrl(normalizeRouteForLookup(route));
return replaceRouterParams(normalizedRoute, {orgId: orgSlug});
}

function titleFromRoute(route: string): string {
Expand Down Expand Up @@ -75,7 +86,7 @@ type SettingsFieldSection = {

type FormFieldDefinition = (typeof FORM_FIELD_REGISTRY)[string];

function getSettingsFieldSections(orgSlug: string): SettingsFieldSection[] {
export function getSettingsFieldSections(orgSlug: string): SettingsFieldSection[] {
const routeTitleMap = new Map<string, string>();
for (const section of getUserOrgNavigationConfiguration()) {
for (const item of section.items) {
Expand Down
Loading