From 556c625c978b77293d26170b0f9f9fdfdc74f000 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 09:28:22 +0000 Subject: [PATCH 1/6] feat(settings): Align cmd+k, page titles and copy with settings-v3 With settings-v3 on, cmd+k still showed the old 'General Settings' and 'Account Details' labels and listed account pages next to org pages. Browser tab titles also did not match the new 'General' headers. Cmd+K now uses the v3 navigation config and groups account pages under Account Settings. Tab titles, the replays settings link, the nav LLM context hint and a JSDoc follow the v3 scopes. Without the flag nothing changes. Co-Authored-By: Priscila Andre de Oliveira <29228205+priscilawebdev@users.noreply.github.com> --- .../ui/commandPaletteGlobalActions.spec.tsx | 75 +++++++++++++++++++ .../ui/commandPaletteGlobalActions.tsx | 69 ++++++++++++++--- .../replays/table/deleteReplays.tsx | 5 +- static/app/views/navigation/navigation.tsx | 17 +++-- .../primary/organizationDropdown.tsx | 3 +- .../views/settings/account/accountDetails.tsx | 2 +- .../components/settingsNavigation.tsx | 16 +--- .../organizationGeneralSettings/index.tsx | 5 +- ...jectSettingsCommandPaletteActions.spec.tsx | 30 ++++++++ .../projectSettingsCommandPaletteActions.tsx | 11 ++- .../settings/projectGeneralSettings/index.tsx | 5 +- .../settingsCommandPaletteActions.tsx | 47 ++++++++++-- static/app/views/settings/useSettingsScope.ts | 15 ++++ 13 files changed, 255 insertions(+), 45 deletions(-) diff --git a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.spec.tsx b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.spec.tsx index 6dd69a154c8a..a19627da541e 100644 --- a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.spec.tsx +++ b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.spec.tsx @@ -526,6 +526,81 @@ describe('GlobalCommandPaletteActions - search recall', () => { }); }); +describe('GlobalCommandPaletteActions - settings-v3', () => { + function renderPalette(organization: ReturnType) { + for (const url of [ + 'group-search-views/starred/', + 'dashboards/starred/', + 'dashboards/', + 'explore/all-queries/', + 'members/', + 'teams/', + 'projects/', + ]) { + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/${url}`, + body: [], + }); + } + + render( + + + + + , + { + organization, + initialRouterConfig: { + location: {pathname: `/organizations/${organization.slug}/issues/`}, + }, + } + ); + } + + it('lists account pages under Account Settings', async () => { + renderPalette(OrganizationFixture({features: ['settings-v3']})); + + const input = await screen.findByRole('textbox', {name: 'Search commands'}); + await userEvent.type(input, 'Personal Tokens'); + + expect( + await screen.findByRole('option', {name: /Account Settings.*Personal Tokens/}) + ).toBeInTheDocument(); + expect( + screen.queryByRole('option', {name: /Org Settings.*Personal Tokens/}) + ).not.toBeInTheDocument(); + }); + + it('titles the organization index page "General"', async () => { + renderPalette(OrganizationFixture({features: ['settings-v3']})); + + const input = await screen.findByRole('textbox', {name: 'Search commands'}); + await userEvent.type(input, 'General'); + + expect( + await screen.findByRole('option', {name: /Org Settings.*General/}) + ).toBeInTheDocument(); + expect( + screen.queryByRole('option', {name: /General Settings/}) + ).not.toBeInTheDocument(); + }); + + it('keeps account pages under Settings without the flag', async () => { + renderPalette(OrganizationFixture({features: []})); + + const input = await screen.findByRole('textbox', {name: 'Search commands'}); + await userEvent.type(input, 'Personal Tokens'); + + expect( + await screen.findByRole('option', {name: /Settings.*Personal Tokens/}) + ).toBeInTheDocument(); + expect( + screen.queryByRole('option', {name: /Account Settings/}) + ).not.toBeInTheDocument(); + }); +}); + describe('GlobalCommandPaletteActions - Seer XRay Mode gating', () => { function mockOrgApis(organization: ReturnType) { MockApiClient.addMockResponse({ diff --git a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx index 42b4248693cd..8c718c8d11a3 100644 --- a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx +++ b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx @@ -103,10 +103,19 @@ import { toggleXRayMode, useXRayModeEnabled, } from 'sentry/views/seerExplorer/xray/xrayModeStore'; -import {getUserOrgNavigationConfiguration} from 'sentry/views/settings/organization/userOrgNavigationConfiguration'; +import { + getUserNavigationConfiguration, + getUserOrgNavigationConfiguration, +} from 'sentry/views/settings/organization/userOrgNavigationConfiguration'; import {getNavigationConfiguration} from 'sentry/views/settings/project/navigationConfiguration'; import {PROJECT_SETTINGS_ICONS} from 'sentry/views/settings/project/projectSettingsCommandPaletteActions'; -import type {NavigationGroupProps, NavigationItem} from 'sentry/views/settings/types'; +import type { + NavigationGroupProps, + NavigationItem, + NavigationSection, +} from 'sentry/views/settings/types'; +import {getSettingsScopeTitle} from 'sentry/views/settings/useSettingsScope'; +import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; /** * Returns true if a settings navigation item should be shown in cmd+k. @@ -311,6 +320,7 @@ export function GlobalCommandPaletteActions() { const {projects} = useProjects(); const {organizations} = useLegacyStore(OrganizationsStore); const sentryConfig = useLegacyStore(ConfigStore); + const settingsV3 = useSettingsV3(); const params = useParams(); const location = useLocation(); const {mutateAsync: mutateUserOptions} = useMutateUserOptions(); @@ -349,6 +359,7 @@ export function GlobalCommandPaletteActions() { }; return getNavigationConfiguration({ organization, + settingsV3, }) .flatMap(section => section.items.filter(navItem => @@ -356,23 +367,35 @@ export function GlobalCommandPaletteActions() { ) ) .sort((a, b) => a.title.localeCompare(b.title)); - }, [organization]); + }, [organization, settingsV3]); - const visibleOrgSettingsNavItems = useMemo(() => { + const {visibleOrgSettingsNavItems, visibleAccountSettingsNavItems} = useMemo(() => { const context: Omit = { access: new Set(organization.access), features: new Set(organization.features), isSelfHosted: sentryConfig.isSelfHosted, organization, }; - return getUserOrgNavigationConfiguration() - .flatMap(section => - section.items.filter(navItem => - isNavItemVisible(navItem, {...context, ...section}) + const getVisibleItems = (sections: NavigationSection[]) => + sections + .flatMap(section => + section.items.filter(navItem => + isNavItemVisible(navItem, {...context, ...section}) + ) ) - ) - .sort((a, b) => a.title.localeCompare(b.title)); - }, [organization, sentryConfig.isSelfHosted]); + .sort((a, b) => a.title.localeCompare(b.title)); + + return { + visibleOrgSettingsNavItems: getVisibleItems( + getUserOrgNavigationConfiguration({settingsV3}) + ), + // With settings-v3, account pages leave the organization settings for + // their own scope, so they get their own group here too. + visibleAccountSettingsNavItems: settingsV3 + ? getVisibleItems(getUserNavigationConfiguration({settingsV3})) + : [], + }; + }, [organization, sentryConfig.isSelfHosted, settingsV3]); const prefix = `/organizations/${organization.slug}`; const hasInsightsRollout = organization.features.includes( @@ -621,7 +644,13 @@ export function GlobalCommandPaletteActions() { /> - }} limit={4}> + , + }} + limit={4} + > {visibleOrgSettingsNavItems.map(item => ( + {settingsV3 && ( + }} + limit={4} + > + {visibleAccountSettingsNavItems.map(item => ( + + ))} + + )} + }} to={makeProjectsPathname({path: '/', organization})} diff --git a/static/app/components/replays/table/deleteReplays.tsx b/static/app/components/replays/table/deleteReplays.tsx index d6a8fda57fb1..c35cbbd24232 100644 --- a/static/app/components/replays/table/deleteReplays.tsx +++ b/static/app/components/replays/table/deleteReplays.tsx @@ -37,6 +37,8 @@ import {useOrganization} from 'sentry/utils/useOrganization'; import {useProjectFromId} from 'sentry/utils/useProjectFromId'; import {useProjects} from 'sentry/utils/useProjects'; import type {ReplayListRecord} from 'sentry/views/explore/replays/types'; +import {getSettingsScopeTitle} from 'sentry/views/settings/useSettingsScope'; +import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; const REPLAY_COLUMNS: TableColumnConfig[] = [ {key: 'replay', width: '1fr'}, @@ -265,6 +267,7 @@ function ReplayPreviewTable({ function Title({children, project}: {children: React.ReactNode; project: Project}) { const organization = useOrganization(); + const settingsV3 = useSettingsV3(); const settingsPath = `/settings/${organization.slug}/projects/${project.slug}/replays/?replaySettingsTab=bulk-delete`; return ( @@ -276,7 +279,7 @@ function Title({children, project}: {children: React.ReactNode; project: Project link: ( {`Settings > ${project.slug} > Replays`} + >{`${settingsV3 ? getSettingsScopeTitle('project') : 'Settings'} > ${project.slug} > Replays`} ), })}

diff --git a/static/app/views/navigation/navigation.tsx b/static/app/views/navigation/navigation.tsx index 8712f6c9e846..57f2a7e873c7 100644 --- a/static/app/views/navigation/navigation.tsx +++ b/static/app/views/navigation/navigation.tsx @@ -69,6 +69,7 @@ import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; function NavigationImpl() { const organization = useOrganization({allowNull: true}); const isSettingsNavigation = useSettingsNavigation(); + const settingsV3 = useSettingsV3(); const collapsedNavigation = useCollapsedNavigation(); const {view} = useSecondaryNavigation(); @@ -84,12 +85,16 @@ function NavigationImpl() { const isCollapsed = currentStepId === null ? view !== 'expanded' : false; useLLMContext({ - contextHint: - "The org's left-hand navigation sidebar — a primary icon rail (Issues, " + - 'Explore, Dashboards, Insights, Monitors, Settings, ...) plus a secondary ' + - 'panel for whichever group is active. Child nodes carry each active ' + - "secondary panel's own detail (starred views/queries/dashboards, issue " + - 'counts, settings categories, etc).', + contextHint: isSettingsNavigation + ? "The settings navigation sidebar — a primary icon rail with a 'Back to app' " + + 'button and the Org, Project and Account settings scopes, plus a secondary ' + + 'panel listing the settings pages of the active scope. Child nodes carry ' + + "the secondary panel's own detail." + : "The org's left-hand navigation sidebar — a primary icon rail (Issues, " + + `Explore, Dashboards, Insights, Monitors${settingsV3 ? '' : ', Settings'}, ...) ` + + 'plus a secondary panel for whichever group is active. Child nodes carry ' + + "each active secondary panel's own detail (starred views/queries/dashboards, " + + 'issue counts, settings categories, etc).', activeGroup, isCollapsed, }); diff --git a/static/app/views/navigation/primary/organizationDropdown.tsx b/static/app/views/navigation/primary/organizationDropdown.tsx index 015ae2efbbe9..015a99a5280b 100644 --- a/static/app/views/navigation/primary/organizationDropdown.tsx +++ b/static/app/views/navigation/primary/organizationDropdown.tsx @@ -57,7 +57,8 @@ import {NavigationMenuHeader} from './navigationMenuHeader'; interface OrganizationDropdownProps { /** - * When true, hides settings, projects, members, teams, and billing links for the current organization. + * When true, hides the links into the current organization: settings, projects, + * members, teams, and billing, or with settings-v3, settings and project settings. */ hideCurrentOrganizationLinks?: boolean; } diff --git a/static/app/views/settings/account/accountDetails.tsx b/static/app/views/settings/account/accountDetails.tsx index 0351a6a79080..b1bee6bc34f5 100644 --- a/static/app/views/settings/account/accountDetails.tsx +++ b/static/app/views/settings/account/accountDetails.tsx @@ -166,7 +166,7 @@ function AccountDetails() { return ( - + diff --git a/static/app/views/settings/components/settingsNavigation.tsx b/static/app/views/settings/components/settingsNavigation.tsx index dc29b6854552..06f17ebd6895 100644 --- a/static/app/views/settings/components/settingsNavigation.tsx +++ b/static/app/views/settings/components/settingsNavigation.tsx @@ -12,7 +12,6 @@ import {Text} from '@sentry/scraps/text'; import {ErrorBoundary} from 'sentry/components/errorBoundary'; import {t} from 'sentry/locale'; import {replaceRouterParams} from 'sentry/utils/replaceRouterParams'; -import {unreachable} from 'sentry/utils/unreachable'; import {normalizeUrl} from 'sentry/utils/url/normalizeUrl'; import {useCanCreateProject} from 'sentry/utils/useCanCreateProject'; import {useLocation} from 'sentry/utils/useLocation'; @@ -26,8 +25,8 @@ import {SettingsNavigationGroup} from 'sentry/views/settings/components/settings import type {NavigationProps, NavigationSection} from 'sentry/views/settings/types'; import {useSettingsProjectSwitchTemplate} from 'sentry/views/settings/useSettingsProjectSwitchTemplate'; import { + getSettingsScopeTitle, useSettingsScope, - type SettingsScope, } from 'sentry/views/settings/useSettingsScope'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; @@ -46,19 +45,6 @@ type Props = NavigationProps & { navigationObjects: NavigationSection[]; }; -function getSettingsScopeTitle(scope: SettingsScope): string { - switch (scope) { - case 'account': - return t('Account Settings'); - case 'project': - return t('Project Settings'); - case 'org': - return t('Org Settings'); - default: - return unreachable(scope); - } -} - export function SettingsNavigation({ navigationObjects, hookConfigs, diff --git a/static/app/views/settings/organizationGeneralSettings/index.tsx b/static/app/views/settings/organizationGeneralSettings/index.tsx index ee2ebeb370c1..43bbbd55b7af 100644 --- a/static/app/views/settings/organizationGeneralSettings/index.tsx +++ b/static/app/views/settings/organizationGeneralSettings/index.tsx @@ -97,7 +97,10 @@ export default function OrganizationGeneralSettings() { return ( - +
{ ]) ); }); + + it('titles the index item "General" with settings-v3', () => { + const organization = OrganizationFixture({slug: 'acme'}); + const project = DetailedProjectFixture({slug: 'frontend'}); + + const sections = getProjectSettingsCommandPaletteSections({ + organization, + project, + settingsV3: true, + }); + + expect(sections).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + label: 'frontend', + items: expect.arrayContaining([ + expect.objectContaining({ + label: 'General', + items: expect.arrayContaining([ + expect.objectContaining({ + display: expect.objectContaining({label: 'General'}), + to: '/settings/acme/projects/frontend/', + }), + ]), + }), + ]), + }), + ]) + ); + }); }); diff --git a/static/app/views/settings/project/projectSettingsCommandPaletteActions.tsx b/static/app/views/settings/project/projectSettingsCommandPaletteActions.tsx index 28b1aa0b0b46..1c4142182193 100644 --- a/static/app/views/settings/project/projectSettingsCommandPaletteActions.tsx +++ b/static/app/views/settings/project/projectSettingsCommandPaletteActions.tsx @@ -25,6 +25,7 @@ import type {Project} from 'sentry/types/project'; import {replaceRouterParams} from 'sentry/utils/replaceRouterParams'; import {getNavigationConfiguration} from 'sentry/views/settings/project/navigationConfiguration'; import type {NavigationGroupProps, NavigationItem} from 'sentry/views/settings/types'; +import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; type ProjectSettingsCommandPaletteEntry = { display: { @@ -86,9 +87,11 @@ function shouldShowItem( export function getProjectSettingsCommandPaletteSections({ organization, project, + settingsV3, }: { organization: Organization; project: Project; + settingsV3?: boolean; }): ProjectSettingsCommandPaletteSection[] { const context = { access: new Set(organization.access), @@ -107,6 +110,7 @@ export function getProjectSettingsCommandPaletteSections({ debugFilesNeedsReview: false, organization, project, + settingsV3, }) .map(section => { const label = @@ -184,7 +188,12 @@ export function ProjectSettingsCommandPaletteActions({ organization: Organization; project: Project; }) { - const sections = getProjectSettingsCommandPaletteSections({organization, project}); + const settingsV3 = useSettingsV3(); + const sections = getProjectSettingsCommandPaletteSections({ + organization, + project, + settingsV3, + }); return ( diff --git a/static/app/views/settings/projectGeneralSettings/index.tsx b/static/app/views/settings/projectGeneralSettings/index.tsx index f23adbd5d949..413b46879e76 100644 --- a/static/app/views/settings/projectGeneralSettings/index.tsx +++ b/static/app/views/settings/projectGeneralSettings/index.tsx @@ -678,7 +678,10 @@ export function ProjectGeneralSettings({project, onChangeSlug}: Props) { return (
- + diff --git a/static/app/views/settings/settingsCommandPaletteActions.tsx b/static/app/views/settings/settingsCommandPaletteActions.tsx index cd0bfdd5f662..05d0c352c3dc 100644 --- a/static/app/views/settings/settingsCommandPaletteActions.tsx +++ b/static/app/views/settings/settingsCommandPaletteActions.tsx @@ -11,7 +11,15 @@ import {t} from 'sentry/locale'; import {replaceRouterParams} from 'sentry/utils/replaceRouterParams'; import {useOrganization} from 'sentry/utils/useOrganization'; import {FORM_FIELD_REGISTRY} from 'sentry/views/settings/fieldRegistry.generated'; -import {getUserOrgNavigationConfiguration} from 'sentry/views/settings/organization/userOrgNavigationConfiguration'; +import { + getUserNavigationConfiguration, + getUserOrgNavigationConfiguration, +} from 'sentry/views/settings/organization/userOrgNavigationConfiguration'; +import { + getSettingsScopeTitle, + type SettingsScope, +} from 'sentry/views/settings/useSettingsScope'; +import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; const ROUTE_ICONS: Record = { '/settings/account/details/': , @@ -79,13 +87,39 @@ type SettingsFieldSection = { type FormFieldDefinition = (typeof FORM_FIELD_REGISTRY)[string]; -function getSettingsFieldSections(orgSlug: string): SettingsFieldSection[] { +// With settings-v3, the org and account index pages are both titled "General", +// so their field sections are named after their scope to tell them apart. +const SETTINGS_V3_INDEX_SCOPES: Record = { + '/settings/:orgId/': 'org', + '/settings/account/details/': 'account', +}; + +function getRouteTitleMap(settingsV3: boolean): Map { + const sections = settingsV3 + ? [ + ...getUserOrgNavigationConfiguration({settingsV3}), + ...getUserNavigationConfiguration({settingsV3}), + ] + : getUserOrgNavigationConfiguration(); + const routeTitleMap = new Map(); - for (const section of getUserOrgNavigationConfiguration()) { + for (const section of sections) { for (const item of section.items) { - routeTitleMap.set(item.path, item.title); + const scope = settingsV3 ? SETTINGS_V3_INDEX_SCOPES[item.path] : undefined; + routeTitleMap.set( + item.path, + scope ? `${getSettingsScopeTitle(scope)} › ${item.title}` : item.title + ); } } + return routeTitleMap; +} + +function getSettingsFieldSections( + orgSlug: string, + settingsV3: boolean +): SettingsFieldSection[] { + const routeTitleMap = getRouteTitleMap(settingsV3); const groups = new Map>(); for (const field of Object.values(FORM_FIELD_REGISTRY)) { @@ -140,9 +174,10 @@ function getSettingsFieldSections(orgSlug: string): SettingsFieldSection[] { export function SettingsCommandPaletteActions() { const organization = useOrganization({allowNull: true}); + const settingsV3 = useSettingsV3(); const sections = useMemo( - () => (organization ? getSettingsFieldSections(organization.slug) : []), - [organization] + () => (organization ? getSettingsFieldSections(organization.slug, settingsV3) : []), + [organization, settingsV3] ); if (sections.length === 0) { diff --git a/static/app/views/settings/useSettingsScope.ts b/static/app/views/settings/useSettingsScope.ts index d6e569547045..5a645faed5c4 100644 --- a/static/app/views/settings/useSettingsScope.ts +++ b/static/app/views/settings/useSettingsScope.ts @@ -1,9 +1,24 @@ import {matchPath} from 'react-router'; +import {t} from 'sentry/locale'; +import {unreachable} from 'sentry/utils/unreachable'; import {useLocation} from 'sentry/utils/useLocation'; export type SettingsScope = 'account' | 'org' | 'project'; +export function getSettingsScopeTitle(scope: SettingsScope): string { + switch (scope) { + case 'account': + return t('Account Settings'); + case 'project': + return t('Project Settings'); + case 'org': + return t('Org Settings'); + default: + return unreachable(scope); + } +} + /** * Which settings scope the current route belongs to. Derived from the pathname * only, so the primary rail, the settings header and the secondary sidebar From b981e12d419a31445c5b76e51f87be3a4cf7e782 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Tue, 3 Nov 2026 10:27:32 +0000 Subject: [PATCH 2/6] feat(settings): Give org and account General a cmd+k icon in v3 Project Settings > General already had a gear icon, but the org and account General entries had none. Under settings-v3 all three now match. Co-Authored-By: Priscila Andre de Oliveira <29228205+priscilawebdev@users.noreply.github.com> --- .../ui/commandPaletteGlobalActions.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx index 8c718c8d11a3..abdbc8e8ef1b 100644 --- a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx +++ b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx @@ -157,6 +157,13 @@ const ORG_SETTINGS_ICONS: Record = { '/settings/account/notifications/': , }; +// With settings-v3, the org and account index pages are titled "General" like +// the project one, so they get the same icon to read as the same page type. +const SETTINGS_V3_GENERAL_ICONS: Record = { + '/settings/:orgId/': , + '/settings/account/details/': , +}; + const helpSearch = new SentryGlobalSearch(['docs', 'develop']); const EVENT_ID_PATTERN = /^(?:[A-Fa-f0-9]{32}|[A-Fa-f0-9]{8}(?:-[A-Fa-f0-9]{4}){3}-[A-Fa-f0-9]{12})$/; @@ -397,6 +404,10 @@ export function GlobalCommandPaletteActions() { }; }, [organization, sentryConfig.isSelfHosted, settingsV3]); + const getSettingsNavItemIcon = (path: string) => + ORG_SETTINGS_ICONS[path] ?? + (settingsV3 ? SETTINGS_V3_GENERAL_ICONS[path] : undefined); + const prefix = `/organizations/${organization.slug}`; const hasInsightsRollout = organization.features.includes( 'insights-to-dashboards-ui-rollout' @@ -654,7 +665,7 @@ export function GlobalCommandPaletteActions() { {visibleOrgSettingsNavItems.map(item => ( @@ -670,7 +681,7 @@ export function GlobalCommandPaletteActions() { {visibleAccountSettingsNavItems.map(item => ( From ebd51dbd1e9221eccf43402d0953d74f8692751b Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Tue, 3 Nov 2026 12:31:27 +0100 Subject: [PATCH 3/6] ref(settings): Address review feedback on settings-v3 consistency - Title the general pages' browser tabs "Org Settings", "Account Settings" and "Project Settings" with settings-v3, so the account tab is no longer a bare "General". - Share the settings-v3 General icons between the global cmd+k actions and the settings field sections, so the account General page no longer shows two different icons. - Prefix field section titles with their scope only when the title is used by more than one scope, derived from the navigation config instead of a hard-coded path list. - Move getSettingsScopeTitle and SettingsScope out of the useSettingsScope hook module into settingsScope.ts. - Reuse the existing org API mocks in the cmd+k spec and add a spec for the field sections. --- .../ui/commandPaletteGlobalActions.spec.tsx | 85 ++++++++----------- .../ui/commandPaletteGlobalActions.tsx | 10 +-- .../replays/table/deleteReplays.tsx | 2 +- .../primary/organizationDropdown.tsx | 5 +- .../views/settings/account/accountDetails.tsx | 5 +- .../components/settingsNavigation.tsx | 6 +- .../organizationGeneralSettings/index.tsx | 3 +- .../settings/projectGeneralSettings/index.tsx | 5 +- .../settingsCommandPaletteActions.spec.tsx | 49 +++++++++++ .../settingsCommandPaletteActions.tsx | 72 +++++++++++----- static/app/views/settings/settingsScope.ts | 17 ++++ static/app/views/settings/useSettingsScope.ts | 18 +--- 12 files changed, 170 insertions(+), 107 deletions(-) create mode 100644 static/app/views/settings/settingsCommandPaletteActions.spec.tsx create mode 100644 static/app/views/settings/settingsScope.ts diff --git a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.spec.tsx b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.spec.tsx index a19627da541e..b2588b0eed29 100644 --- a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.spec.tsx +++ b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.spec.tsx @@ -526,22 +526,44 @@ describe('GlobalCommandPaletteActions - search recall', () => { }); }); +function mockOrgApis(organization: ReturnType) { + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/group-search-views/starred/`, + body: [], + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/dashboards/starred/`, + body: [], + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/dashboards/`, + body: [], + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/explore/all-queries/`, + body: [], + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/users/`, + body: [], + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/members/`, + body: [], + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/teams/`, + body: [], + }); + MockApiClient.addMockResponse({ + url: `/organizations/${organization.slug}/projects/`, + body: [], + }); +} + describe('GlobalCommandPaletteActions - settings-v3', () => { function renderPalette(organization: ReturnType) { - for (const url of [ - 'group-search-views/starred/', - 'dashboards/starred/', - 'dashboards/', - 'explore/all-queries/', - 'members/', - 'teams/', - 'projects/', - ]) { - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/${url}`, - body: [], - }); - } + mockOrgApis(organization); render( @@ -602,41 +624,6 @@ describe('GlobalCommandPaletteActions - settings-v3', () => { }); describe('GlobalCommandPaletteActions - Seer XRay Mode gating', () => { - function mockOrgApis(organization: ReturnType) { - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/group-search-views/starred/`, - body: [], - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/dashboards/starred/`, - body: [], - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/dashboards/`, - body: [], - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/explore/all-queries/`, - body: [], - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/users/`, - body: [], - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/members/`, - body: [], - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/teams/`, - body: [], - }); - MockApiClient.addMockResponse({ - url: `/organizations/${organization.slug}/projects/`, - body: [], - }); - } - function renderPalette(organization: ReturnType) { render( diff --git a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx index abdbc8e8ef1b..e113237ada48 100644 --- a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx +++ b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx @@ -109,12 +109,13 @@ import { } from 'sentry/views/settings/organization/userOrgNavigationConfiguration'; import {getNavigationConfiguration} from 'sentry/views/settings/project/navigationConfiguration'; import {PROJECT_SETTINGS_ICONS} from 'sentry/views/settings/project/projectSettingsCommandPaletteActions'; +import {SETTINGS_V3_GENERAL_ICONS} from 'sentry/views/settings/settingsCommandPaletteActions'; +import {getSettingsScopeTitle} from 'sentry/views/settings/settingsScope'; import type { NavigationGroupProps, NavigationItem, NavigationSection, } from 'sentry/views/settings/types'; -import {getSettingsScopeTitle} from 'sentry/views/settings/useSettingsScope'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; /** @@ -157,13 +158,6 @@ const ORG_SETTINGS_ICONS: Record = { '/settings/account/notifications/': , }; -// With settings-v3, the org and account index pages are titled "General" like -// the project one, so they get the same icon to read as the same page type. -const SETTINGS_V3_GENERAL_ICONS: Record = { - '/settings/:orgId/': , - '/settings/account/details/': , -}; - const helpSearch = new SentryGlobalSearch(['docs', 'develop']); const EVENT_ID_PATTERN = /^(?:[A-Fa-f0-9]{32}|[A-Fa-f0-9]{8}(?:-[A-Fa-f0-9]{4}){3}-[A-Fa-f0-9]{12})$/; diff --git a/static/app/components/replays/table/deleteReplays.tsx b/static/app/components/replays/table/deleteReplays.tsx index c35cbbd24232..1010dfd1d68c 100644 --- a/static/app/components/replays/table/deleteReplays.tsx +++ b/static/app/components/replays/table/deleteReplays.tsx @@ -37,7 +37,7 @@ import {useOrganization} from 'sentry/utils/useOrganization'; import {useProjectFromId} from 'sentry/utils/useProjectFromId'; import {useProjects} from 'sentry/utils/useProjects'; import type {ReplayListRecord} from 'sentry/views/explore/replays/types'; -import {getSettingsScopeTitle} from 'sentry/views/settings/useSettingsScope'; +import {getSettingsScopeTitle} from 'sentry/views/settings/settingsScope'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; const REPLAY_COLUMNS: TableColumnConfig[] = [ diff --git a/static/app/views/navigation/primary/organizationDropdown.tsx b/static/app/views/navigation/primary/organizationDropdown.tsx index 015a99a5280b..b1d6b41cfa3b 100644 --- a/static/app/views/navigation/primary/organizationDropdown.tsx +++ b/static/app/views/navigation/primary/organizationDropdown.tsx @@ -57,8 +57,9 @@ import {NavigationMenuHeader} from './navigationMenuHeader'; interface OrganizationDropdownProps { /** - * When true, hides the links into the current organization: settings, projects, - * members, teams, and billing, or with settings-v3, settings and project settings. + * When true, hides the links into the current organization. Without + * settings-v3, these are settings, projects, members, teams, and billing. + * With settings-v3, these are settings and project settings. */ hideCurrentOrganizationLinks?: boolean; } diff --git a/static/app/views/settings/account/accountDetails.tsx b/static/app/views/settings/account/accountDetails.tsx index b1bee6bc34f5..6cb50a79ef59 100644 --- a/static/app/views/settings/account/accountDetails.tsx +++ b/static/app/views/settings/account/accountDetails.tsx @@ -18,6 +18,7 @@ import {getApiUrl} from 'sentry/utils/api/getApiUrl'; import {fetchMutation, setApiQueryData, useApiQuery} from 'sentry/utils/queryClient'; import {removeBodyTheme} from 'sentry/utils/removeBodyTheme'; import {SettingsPageHeader} from 'sentry/views/settings/components/settingsPageHeader'; +import {getSettingsScopeTitle} from 'sentry/views/settings/settingsScope'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; // The avatar endpoint ("/users/me/avatar/") returns a User-like type without `options` and other properties that are present in User @@ -166,7 +167,9 @@ function AccountDetails() { return ( - + diff --git a/static/app/views/settings/components/settingsNavigation.tsx b/static/app/views/settings/components/settingsNavigation.tsx index 06f17ebd6895..f2236e5c53d7 100644 --- a/static/app/views/settings/components/settingsNavigation.tsx +++ b/static/app/views/settings/components/settingsNavigation.tsx @@ -22,12 +22,10 @@ import {useProjects} from 'sentry/utils/useProjects'; import {SecondaryNavigation} from 'sentry/views/navigation/secondary/components'; import {makeProjectsPathname} from 'sentry/views/projects/pathname'; import {SettingsNavigationGroup} from 'sentry/views/settings/components/settingsNavigationGroup'; +import {getSettingsScopeTitle} from 'sentry/views/settings/settingsScope'; import type {NavigationProps, NavigationSection} from 'sentry/views/settings/types'; import {useSettingsProjectSwitchTemplate} from 'sentry/views/settings/useSettingsProjectSwitchTemplate'; -import { - getSettingsScopeTitle, - useSettingsScope, -} from 'sentry/views/settings/useSettingsScope'; +import {useSettingsScope} from 'sentry/views/settings/useSettingsScope'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; type Props = NavigationProps & { diff --git a/static/app/views/settings/organizationGeneralSettings/index.tsx b/static/app/views/settings/organizationGeneralSettings/index.tsx index 43bbbd55b7af..3996c4fbea32 100644 --- a/static/app/views/settings/organizationGeneralSettings/index.tsx +++ b/static/app/views/settings/organizationGeneralSettings/index.tsx @@ -26,6 +26,7 @@ import {SettingsPageHeader} from 'sentry/views/settings/components/settingsPageH import {TextBlock} from 'sentry/views/settings/components/text/textBlock'; import {OrganizationPermissionAlert} from 'sentry/views/settings/organization/organizationPermissionAlert'; import {defaultEnableSeerFeaturesValue} from 'sentry/views/settings/organizationGeneralSettings/aiFeatureSettings'; +import {getSettingsScopeTitle} from 'sentry/views/settings/settingsScope'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; import {OrganizationSettingsForm} from './organizationSettingsForm'; @@ -98,7 +99,7 @@ export default function OrganizationGeneralSettings() { return (
diff --git a/static/app/views/settings/projectGeneralSettings/index.tsx b/static/app/views/settings/projectGeneralSettings/index.tsx index 413b46879e76..f23adbd5d949 100644 --- a/static/app/views/settings/projectGeneralSettings/index.tsx +++ b/static/app/views/settings/projectGeneralSettings/index.tsx @@ -678,10 +678,7 @@ export function ProjectGeneralSettings({project, onChangeSlug}: Props) { return (
- + diff --git a/static/app/views/settings/settingsCommandPaletteActions.spec.tsx b/static/app/views/settings/settingsCommandPaletteActions.spec.tsx new file mode 100644 index 000000000000..01f44e27024e --- /dev/null +++ b/static/app/views/settings/settingsCommandPaletteActions.spec.tsx @@ -0,0 +1,49 @@ +import {IconSettings} from '@sentry/icons/settings'; +import {IconUser} from '@sentry/icons/user'; + +import {getSettingsFieldSections} from 'sentry/views/settings/settingsCommandPaletteActions'; + +function getSection(sections: ReturnType, key: string) { + const section = sections.find(s => s.key === key); + if (!section) { + throw new Error(`No field section for ${key}`); + } + return section; +} + +describe('getSettingsFieldSections', () => { + it('names shared titles after their scope with settings-v3', () => { + const sections = getSettingsFieldSections('org-slug', true); + + expect(getSection(sections, '/settings/:orgId/').title).toBe( + 'Org Settings › General' + ); + expect(getSection(sections, '/settings/account/details/').title).toBe( + 'Account Settings › General' + ); + // Titles unique to one scope are left alone. + expect(getSection(sections, '/settings/account/notifications/').title).toBe( + 'Notifications' + ); + }); + + it('keeps the original titles without settings-v3', () => { + const sections = getSettingsFieldSections('org-slug', false); + + expect(getSection(sections, '/settings/:orgId/').title).toBe('General Settings'); + expect(getSection(sections, '/settings/account/details/').title).toBe( + 'Account Details' + ); + }); + + it('gives both General pages the settings icon with settings-v3', () => { + const v3Sections = getSettingsFieldSections('org-slug', true); + expect(getSection(v3Sections, '/settings/:orgId/').icon).toEqual(); + expect(getSection(v3Sections, '/settings/account/details/').icon).toEqual( + + ); + + const sections = getSettingsFieldSections('org-slug', false); + expect(getSection(sections, '/settings/account/details/').icon).toEqual(); + }); +}); diff --git a/static/app/views/settings/settingsCommandPaletteActions.tsx b/static/app/views/settings/settingsCommandPaletteActions.tsx index 05d0c352c3dc..247b4f6ab9f4 100644 --- a/static/app/views/settings/settingsCommandPaletteActions.tsx +++ b/static/app/views/settings/settingsCommandPaletteActions.tsx @@ -18,7 +18,8 @@ import { import { getSettingsScopeTitle, type SettingsScope, -} from 'sentry/views/settings/useSettingsScope'; +} from 'sentry/views/settings/settingsScope'; +import type {NavigationSection} from 'sentry/views/settings/types'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; const ROUTE_ICONS: Record = { @@ -30,6 +31,22 @@ const ROUTE_ICONS: Record = { '/settings/:orgId/security-and-privacy/': , }; +/** + * With settings-v3, the org and account index pages are both titled "General", + * like the project one, so they share its icon to read as the same page type. + * Shared with the global cmd+k settings actions so both lists agree. + */ +export const SETTINGS_V3_GENERAL_ICONS: Record = { + '/settings/:orgId/': , + '/settings/account/details/': , +}; + +function getRouteIcon(route: string, settingsV3: boolean): ReactNode { + return ( + (settingsV3 ? SETTINGS_V3_GENERAL_ICONS[route] : undefined) ?? ROUTE_ICONS[route] + ); +} + function normalizeRouteForLookup(route: string): string { if (route === '/settings/organization/') { return '/settings/:orgId/'; @@ -87,35 +104,50 @@ type SettingsFieldSection = { type FormFieldDefinition = (typeof FORM_FIELD_REGISTRY)[string]; -// With settings-v3, the org and account index pages are both titled "General", -// so their field sections are named after their scope to tell them apart. -const SETTINGS_V3_INDEX_SCOPES: Record = { - '/settings/:orgId/': 'org', - '/settings/account/details/': 'account', -}; +function getNavItems(sections: NavigationSection[]) { + return sections.flatMap(section => section.items); +} function getRouteTitleMap(settingsV3: boolean): Map { - const sections = settingsV3 - ? [ - ...getUserOrgNavigationConfiguration({settingsV3}), - ...getUserNavigationConfiguration({settingsV3}), - ] - : getUserOrgNavigationConfiguration(); - const routeTitleMap = new Map(); - for (const section of sections) { - for (const item of section.items) { - const scope = settingsV3 ? SETTINGS_V3_INDEX_SCOPES[item.path] : undefined; + + if (!settingsV3) { + for (const item of getNavItems(getUserOrgNavigationConfiguration())) { + routeTitleMap.set(item.path, item.title); + } + return routeTitleMap; + } + + // With settings-v3, org and account pages live in separate scopes and can + // share a title (both index pages are "General"). Titles used by more than + // one scope are prefixed with the scope's name to tell them apart. + const scopedItems: Array<[SettingsScope, ReturnType]> = [ + ['org', getNavItems(getUserOrgNavigationConfiguration({settingsV3}))], + ['account', getNavItems(getUserNavigationConfiguration({settingsV3}))], + ]; + + const scopesByTitle = new Map>(); + for (const [scope, items] of scopedItems) { + for (const item of items) { + const scopes = scopesByTitle.get(item.title) ?? new Set(); + scopes.add(scope); + scopesByTitle.set(item.title, scopes); + } + } + + for (const [scope, items] of scopedItems) { + for (const item of items) { + const isShared = (scopesByTitle.get(item.title)?.size ?? 0) > 1; routeTitleMap.set( item.path, - scope ? `${getSettingsScopeTitle(scope)} › ${item.title}` : item.title + isShared ? `${getSettingsScopeTitle(scope)} › ${item.title}` : item.title ); } } return routeTitleMap; } -function getSettingsFieldSections( +export function getSettingsFieldSections( orgSlug: string, settingsV3: boolean ): SettingsFieldSection[] { @@ -147,7 +179,7 @@ function getSettingsFieldSections( return { key: route, title, - icon: ROUTE_ICONS[route], + icon: getRouteIcon(route, settingsV3), fields: Array.from(fieldMap.values()) .filter( (f): f is FormFieldDefinition & {label: string} => diff --git a/static/app/views/settings/settingsScope.ts b/static/app/views/settings/settingsScope.ts new file mode 100644 index 000000000000..ebdbba166a6f --- /dev/null +++ b/static/app/views/settings/settingsScope.ts @@ -0,0 +1,17 @@ +import {t} from 'sentry/locale'; +import {unreachable} from 'sentry/utils/unreachable'; + +export type SettingsScope = 'account' | 'org' | 'project'; + +export function getSettingsScopeTitle(scope: SettingsScope): string { + switch (scope) { + case 'account': + return t('Account Settings'); + case 'project': + return t('Project Settings'); + case 'org': + return t('Org Settings'); + default: + return unreachable(scope); + } +} diff --git a/static/app/views/settings/useSettingsScope.ts b/static/app/views/settings/useSettingsScope.ts index 5a645faed5c4..9c080b9bbd13 100644 --- a/static/app/views/settings/useSettingsScope.ts +++ b/static/app/views/settings/useSettingsScope.ts @@ -1,23 +1,7 @@ import {matchPath} from 'react-router'; -import {t} from 'sentry/locale'; -import {unreachable} from 'sentry/utils/unreachable'; import {useLocation} from 'sentry/utils/useLocation'; - -export type SettingsScope = 'account' | 'org' | 'project'; - -export function getSettingsScopeTitle(scope: SettingsScope): string { - switch (scope) { - case 'account': - return t('Account Settings'); - case 'project': - return t('Project Settings'); - case 'org': - return t('Org Settings'); - default: - return unreachable(scope); - } -} +import type {SettingsScope} from 'sentry/views/settings/settingsScope'; /** * Which settings scope the current route belongs to. Derived from the pathname From 7ba062688c6f50bf77e9176ba081c237c0507df5 Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Tue, 3 Nov 2026 12:45:55 +0100 Subject: [PATCH 4/6] ref(settings): Share settings scope icons and titles Add getSettingsScopeIcon next to getSettingsScopeTitle so each settings scope has one title and one icon. The settings navigation rail and the cmd+k Org, Project and Account settings groups now use it, so the org scope shows the same building icon in both instead of a gear in cmd+k. Also use getSettingsScopeTitle for the remaining "Project Settings" labels in the settings-v3 surfaces: the project General tab title, the organization dropdown's project section and tour, and the cmd+k group. --- .../ui/commandPaletteGlobalActions.tsx | 17 ++++++-- static/app/views/navigation/navigation.tsx | 23 +++++------ .../primary/organizationDropdown.tsx | 5 ++- .../settings/projectGeneralSettings/index.tsx | 6 ++- static/app/views/settings/settingsScope.ts | 17 -------- static/app/views/settings/settingsScope.tsx | 39 +++++++++++++++++++ 6 files changed, 69 insertions(+), 38 deletions(-) delete mode 100644 static/app/views/settings/settingsScope.ts create mode 100644 static/app/views/settings/settingsScope.tsx diff --git a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx index e113237ada48..688db6fb24d8 100644 --- a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx +++ b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx @@ -110,7 +110,10 @@ import { import {getNavigationConfiguration} from 'sentry/views/settings/project/navigationConfiguration'; import {PROJECT_SETTINGS_ICONS} from 'sentry/views/settings/project/projectSettingsCommandPaletteActions'; import {SETTINGS_V3_GENERAL_ICONS} from 'sentry/views/settings/settingsCommandPaletteActions'; -import {getSettingsScopeTitle} from 'sentry/views/settings/settingsScope'; +import { + getSettingsScopeIcon, + getSettingsScopeTitle, +} from 'sentry/views/settings/settingsScope'; import type { NavigationGroupProps, NavigationItem, @@ -652,7 +655,7 @@ export function GlobalCommandPaletteActions() { , + icon: settingsV3 ? getSettingsScopeIcon('org') : , }} limit={4} > @@ -669,7 +672,10 @@ export function GlobalCommandPaletteActions() { {settingsV3 && ( }} + display={{ + label: getSettingsScopeTitle('account'), + icon: getSettingsScopeIcon('account'), + }} limit={4} > {visibleAccountSettingsNavItems.map(item => ( @@ -754,7 +760,10 @@ export function GlobalCommandPaletteActions() { )} }} + display={{ + label: getSettingsScopeTitle('project'), + icon: settingsV3 ? getSettingsScopeIcon('project') : , + }} limit={4} > {currentProjects.map(project => ( diff --git a/static/app/views/navigation/navigation.tsx b/static/app/views/navigation/navigation.tsx index 57f2a7e873c7..dddfa98f2572 100644 --- a/static/app/views/navigation/navigation.tsx +++ b/static/app/views/navigation/navigation.tsx @@ -1,17 +1,14 @@ import {Fragment, type PropsWithChildren, type RefObject, useMemo, useRef} from 'react'; import {mergeProps} from '@react-aria/utils'; import {IconArrow} from '@sentry/icons/arrow'; -import {IconBuilding} from '@sentry/icons/building'; import {IconCompass} from '@sentry/icons/compass'; import {IconDashboard} from '@sentry/icons/dashboard'; import {IconGraph} from '@sentry/icons/graph'; import {IconIssues} from '@sentry/icons/issues'; import {IconMail} from '@sentry/icons/mail'; -import {IconProject} from '@sentry/icons/project'; import {IconSeer} from '@sentry/icons/seer'; import {IconSettings} from '@sentry/icons/settings'; import {IconSiren} from '@sentry/icons/siren'; -import {IconUser} from '@sentry/icons/user'; import {motion, type MotionProps} from 'framer-motion'; import {LinkButton} from '@sentry/scraps/button'; @@ -63,6 +60,10 @@ import { getAutomationsPath, useAutomationsEnabled, } from 'sentry/views/seerWorkflows/utils'; +import { + getSettingsScopeIcon, + type SettingsScope, +} from 'sentry/views/settings/settingsScope'; import {useSettingsScope} from 'sentry/views/settings/useSettingsScope'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; @@ -212,23 +213,17 @@ export function SettingsPrimaryNavigationItems({ const {scope: activeScope, projectSlug: activeProjectSlug} = useSettingsScope(); const projectSlug = activeProjectSlug ?? projects[0]?.slug; const projectSettingsPath = `/settings/${organizationSlug}/projects/`; - const items = [ - { - key: 'org', - label: t('Org'), - to: `/settings/${organizationSlug}/`, - icon: , - }, + const items: Array<{key: SettingsScope; label: string; to: string}> = [ + {key: 'org', label: t('Org'), to: `/settings/${organizationSlug}/`}, { key: 'project', label: t('Project'), to: projectSlug ? `${projectSettingsPath}${projectSlug}/` : projectSettingsPath, - icon: , }, - {key: 'account', label: t('Account'), to: '/settings/account/', icon: }, + {key: 'account', label: t('Account'), to: '/settings/account/'}, ]; - return items.map(({key, label, to, icon}) => ( + return items.map(({key, label, to}) => ( - {icon} + {getSettingsScopeIcon(key)} )); diff --git a/static/app/views/navigation/primary/organizationDropdown.tsx b/static/app/views/navigation/primary/organizationDropdown.tsx index b1d6b41cfa3b..f5565db23885 100644 --- a/static/app/views/navigation/primary/organizationDropdown.tsx +++ b/static/app/views/navigation/primary/organizationDropdown.tsx @@ -51,6 +51,7 @@ import { useSettingsNavigationTour, } from 'sentry/views/navigation/settingsNavigationTour'; import {makeProjectsPathname} from 'sentry/views/projects/pathname'; +import {getSettingsScopeTitle} from 'sentry/views/settings/settingsScope'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; import {NavigationMenuHeader} from './navigationMenuHeader'; @@ -162,7 +163,7 @@ export function OrganizationDropdown(props: OrganizationDropdownProps) { returnFocusRef={triggerRef} title={ hasNoProjects - ? t('Project Settings') + ? getSettingsScopeTitle('project') : t('Quick settings-access for your important Projects') } description={ @@ -247,7 +248,7 @@ export function OrganizationDropdown(props: OrganizationDropdownProps) { }, { key: 'project-settings', - label: t('Project Settings'), + label: getSettingsScopeTitle('project'), ref: projectsTourProps.ref, hidden: props.hideCurrentOrganizationLinks, children: [ diff --git a/static/app/views/settings/projectGeneralSettings/index.tsx b/static/app/views/settings/projectGeneralSettings/index.tsx index f23adbd5d949..5235c737faf9 100644 --- a/static/app/views/settings/projectGeneralSettings/index.tsx +++ b/static/app/views/settings/projectGeneralSettings/index.tsx @@ -64,6 +64,7 @@ import {TextBlock} from 'sentry/views/settings/components/text/textBlock'; import {ProjectPermissionAlert} from 'sentry/views/settings/project/projectPermissionAlert'; import {useProjectSettingsOutlet} from 'sentry/views/settings/project/projectSettingsLayout'; import {ConnectedRepositoriesPanel} from 'sentry/views/settings/projectGeneralSettings/connectedRepositoriesPanel'; +import {getSettingsScopeTitle} from 'sentry/views/settings/settingsScope'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; type Props = { @@ -678,7 +679,10 @@ export function ProjectGeneralSettings({project, onChangeSlug}: Props) { return (
- + diff --git a/static/app/views/settings/settingsScope.ts b/static/app/views/settings/settingsScope.ts deleted file mode 100644 index ebdbba166a6f..000000000000 --- a/static/app/views/settings/settingsScope.ts +++ /dev/null @@ -1,17 +0,0 @@ -import {t} from 'sentry/locale'; -import {unreachable} from 'sentry/utils/unreachable'; - -export type SettingsScope = 'account' | 'org' | 'project'; - -export function getSettingsScopeTitle(scope: SettingsScope): string { - switch (scope) { - case 'account': - return t('Account Settings'); - case 'project': - return t('Project Settings'); - case 'org': - return t('Org Settings'); - default: - return unreachable(scope); - } -} diff --git a/static/app/views/settings/settingsScope.tsx b/static/app/views/settings/settingsScope.tsx new file mode 100644 index 000000000000..d252f174f6a0 --- /dev/null +++ b/static/app/views/settings/settingsScope.tsx @@ -0,0 +1,39 @@ +import type {ReactNode} from 'react'; +import {IconBuilding} from '@sentry/icons/building'; +import {IconProject} from '@sentry/icons/project'; +import {IconUser} from '@sentry/icons/user'; + +import {t} from 'sentry/locale'; +import {unreachable} from 'sentry/utils/unreachable'; + +export type SettingsScope = 'account' | 'org' | 'project'; + +export function getSettingsScopeTitle(scope: SettingsScope): string { + switch (scope) { + case 'account': + return t('Account Settings'); + case 'project': + return t('Project Settings'); + case 'org': + return t('Org Settings'); + default: + return unreachable(scope); + } +} + +/** + * The icon that stands for a settings scope wherever the scope is listed, e.g. + * the settings navigation rail and the cmd+k settings groups. + */ +export function getSettingsScopeIcon(scope: SettingsScope): ReactNode { + switch (scope) { + case 'account': + return ; + case 'project': + return ; + case 'org': + return ; + default: + return unreachable(scope); + } +} From 8939cf2e842036cda6b327d84af965f1bdc3679b Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Tue, 3 Nov 2026 12:52:33 +0100 Subject: [PATCH 5/6] ref(settings): Declare settings page icons in the navigation config The command palette kept two path-to-icon maps for settings pages, one for the settings pages list and one for the settings field sections, plus a settings-v3 override shared between them because the two maps disagreed. Give NavigationItem an optional icon and set it next to the path and title in the org and account navigation config. Both command palette lists already read from that config, so they now use item.icon and the three maps are gone. The settings-v3 case for the account General page becomes a plain ternary in the config, like its title. Pages now show the same icon in both lists, so a few pages gain an icon in one of them (e.g. Security and Email Addresses in the pages list). --- .../ui/commandPaletteGlobalActions.tsx | 26 +------- .../userOrgNavigationConfiguration.tsx | 31 ++++++++++ .../settingsCommandPaletteActions.tsx | 60 ++++++------------- static/app/views/settings/types.tsx | 4 ++ 4 files changed, 56 insertions(+), 65 deletions(-) diff --git a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx index 688db6fb24d8..fb477d27ec28 100644 --- a/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx +++ b/static/app/components/commandPalette/ui/commandPaletteGlobalActions.tsx @@ -12,19 +12,16 @@ import {IconGithub} from '@sentry/icons/github'; import {IconGraph} from '@sentry/icons/graph'; import {IconGroup} from '@sentry/icons/group'; import {IconIssues} from '@sentry/icons/issues'; -import {IconLink} from '@sentry/icons/link'; import {IconList} from '@sentry/icons/list'; import {IconLock} from '@sentry/icons/lock'; import {IconOpen} from '@sentry/icons/open'; import {IconPlay} from '@sentry/icons/play'; -import {IconRepository} from '@sentry/icons/repository'; import {IconSearch} from '@sentry/icons/search'; import {IconSeer} from '@sentry/icons/seer'; import {IconSettings} from '@sentry/icons/settings'; import {IconSiren} from '@sentry/icons/siren'; import {IconStar} from '@sentry/icons/star'; import {IconSubscribed} from '@sentry/icons/subscribed'; -import {IconTerminal} from '@sentry/icons/terminal'; import {IconUser} from '@sentry/icons/user'; import * as Sentry from '@sentry/react'; import {skipToken, useMutation, useQuery} from '@tanstack/react-query'; @@ -109,7 +106,6 @@ import { } from 'sentry/views/settings/organization/userOrgNavigationConfiguration'; import {getNavigationConfiguration} from 'sentry/views/settings/project/navigationConfiguration'; import {PROJECT_SETTINGS_ICONS} from 'sentry/views/settings/project/projectSettingsCommandPaletteActions'; -import {SETTINGS_V3_GENERAL_ICONS} from 'sentry/views/settings/settingsCommandPaletteActions'; import { getSettingsScopeIcon, getSettingsScopeTitle, @@ -147,20 +143,6 @@ const DSN_ICONS: React.ReactElement[] = [ , ]; -const ORG_SETTINGS_ICONS: Record = { - '/settings/:orgId/api-keys/': , - '/settings/:orgId/auth-tokens/': , - '/settings/:orgId/feature-flags/': , - '/settings/:orgId/projects/': , - '/settings/:orgId/integrations/': , - '/settings/:orgId/mcp-cli/': , - '/settings/:orgId/members/': , - '/settings/:orgId/repos/': , - '/settings/:orgId/seer/': , - '/settings/:orgId/teams/': , - '/settings/account/notifications/': , -}; - const helpSearch = new SentryGlobalSearch(['docs', 'develop']); const EVENT_ID_PATTERN = /^(?:[A-Fa-f0-9]{32}|[A-Fa-f0-9]{8}(?:-[A-Fa-f0-9]{4}){3}-[A-Fa-f0-9]{12})$/; @@ -401,10 +383,6 @@ export function GlobalCommandPaletteActions() { }; }, [organization, sentryConfig.isSelfHosted, settingsV3]); - const getSettingsNavItemIcon = (path: string) => - ORG_SETTINGS_ICONS[path] ?? - (settingsV3 ? SETTINGS_V3_GENERAL_ICONS[path] : undefined); - const prefix = `/organizations/${organization.slug}`; const hasInsightsRollout = organization.features.includes( 'insights-to-dashboards-ui-rollout' @@ -662,7 +640,7 @@ export function GlobalCommandPaletteActions() { {visibleOrgSettingsNavItems.map(item => ( @@ -681,7 +659,7 @@ export function GlobalCommandPaletteActions() { {visibleAccountSettingsNavItems.map(item => ( diff --git a/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx b/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx index b9333024d9cb..a18fa4cae67f 100644 --- a/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx +++ b/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx @@ -1,3 +1,16 @@ +import {IconAllProjects} from '@sentry/icons/allProjects'; +import {IconFlag} from '@sentry/icons/flag'; +import {IconGroup} from '@sentry/icons/group'; +import {IconLink} from '@sentry/icons/link'; +import {IconLock} from '@sentry/icons/lock'; +import {IconMail} from '@sentry/icons/mail'; +import {IconRepository} from '@sentry/icons/repository'; +import {IconSeer} from '@sentry/icons/seer'; +import {IconSettings} from '@sentry/icons/settings'; +import {IconSubscribed} from '@sentry/icons/subscribed'; +import {IconTerminal} from '@sentry/icons/terminal'; +import {IconUser} from '@sentry/icons/user'; + import {FeatureBadge} from '@sentry/scraps/badge'; import {t} from 'sentry/locale'; @@ -53,6 +66,9 @@ function getAccountNavigationConfiguration({ items: [ { path: `${userSettingsPathPrefix}/details/`, + // With settings-v3 this is the account's "General" page, so it gets + // the same icon as the org and project General pages. + icon: settingsV3 ? : , title: settingsV3 ? t('General') : t('Account Details'), keywords: [t('user settings'), t('account settings')], description: t( @@ -61,17 +77,20 @@ function getAccountNavigationConfiguration({ }, { path: `${userSettingsPathPrefix}/security/`, + icon: , title: t('Security'), description: t('Change your account password and/or two factor authentication'), }, { path: `${userSettingsPathPrefix}/notifications/`, + icon: , title: t('Notifications'), keywords: [t('weekly report')], description: t('Configure what email notifications to receive'), }, { path: `${userSettingsPathPrefix}/emails/`, + icon: , title: t('Email Addresses'), description: t( 'Add or remove secondary emails, change your primary email, verify your emails' @@ -119,6 +138,7 @@ function getOrganizationNavigationConfiguration({ items: [ { path: `${organizationSettingsPathPrefix}/`, + icon: , title: settingsV3 ? t('General') : t('General Settings'), keywords: [t('slug'), t('org slug'), t('organization slug')], index: true, @@ -134,24 +154,28 @@ function getOrganizationNavigationConfiguration({ }, { path: `${organizationSettingsPathPrefix}/projects/`, + icon: , title: t('Projects'), description: t("View and manage an organization's projects"), id: 'projects', }, { path: `${organizationSettingsPathPrefix}/teams/`, + icon: , title: t('Teams'), description: t("Manage an organization's teams"), id: 'teams', }, { path: `${organizationSettingsPathPrefix}/members/`, + icon: , title: t('Members'), description: t('Manage user membership for an organization'), id: 'members', }, { path: `${organizationSettingsPathPrefix}/security-and-privacy/`, + icon: , title: t('Security & Privacy'), keywords: [ t('data scrubbing'), @@ -174,6 +198,7 @@ function getOrganizationNavigationConfiguration({ }, { path: `${organizationSettingsPathPrefix}/api-keys/`, + icon: , title: t('API Keys'), show: false, // deprecated: hide from settings nav and cmd+k (SEC-551) id: 'api-keys', @@ -216,6 +241,7 @@ function getOrganizationNavigationConfiguration({ }, { path: `${organizationSettingsPathPrefix}/feature-flags/`, + icon: , title: t('Feature Flags'), description: t('Set up feature flag integrations'), }, @@ -235,6 +261,7 @@ function getOrganizationNavigationConfiguration({ items: [ { path: `${organizationSettingsPathPrefix}/seer/`, + icon: , title: t('Issue Scans & Fixes'), description: t("Manage Seer's automated issue analysis across your projects"), id: 'seer-autofix-legacy', @@ -284,12 +311,14 @@ function getOrganizationNavigationConfiguration({ items: [ { path: `${organizationSettingsPathPrefix}/mcp-cli/`, + icon: , title: t('MCP & CLI'), description: t('Connect to Sentry via MCP server or the Sentry CLI'), id: 'mcp-cli', }, { path: `${organizationSettingsPathPrefix}/integrations/`, + icon: , title: t('Integrations'), keywords: [ t('slack'), @@ -324,6 +353,7 @@ function getOrganizationNavigationConfiguration({ }, { path: `${organizationSettingsPathPrefix}/repos/`, + icon: , title: t('Repositories'), description: t('Manage repositories connected to the organization'), id: 'repos', @@ -388,6 +418,7 @@ function getDeveloperNavigationConfiguration({ if (organization) { items.unshift({ path: `${organizationSettingsPathPrefix}/auth-tokens/`, + icon: , title: t('Organization Tokens'), keywords: [ t('auth'), diff --git a/static/app/views/settings/settingsCommandPaletteActions.tsx b/static/app/views/settings/settingsCommandPaletteActions.tsx index 247b4f6ab9f4..1b9dd1bc9312 100644 --- a/static/app/views/settings/settingsCommandPaletteActions.tsx +++ b/static/app/views/settings/settingsCommandPaletteActions.tsx @@ -1,9 +1,5 @@ import {useMemo, type ReactNode} from 'react'; -import {IconLock} from '@sentry/icons/lock'; -import {IconMail} from '@sentry/icons/mail'; import {IconSettings} from '@sentry/icons/settings'; -import {IconSubscribed} from '@sentry/icons/subscribed'; -import {IconUser} from '@sentry/icons/user'; import {CMDKAction} from 'sentry/components/commandPalette/ui/cmdk'; import {CommandPaletteSlot} from 'sentry/components/commandPalette/ui/commandPaletteSlot'; @@ -22,31 +18,6 @@ import { import type {NavigationSection} from 'sentry/views/settings/types'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; -const ROUTE_ICONS: Record = { - '/settings/account/details/': , - '/settings/account/security/': , - '/settings/account/notifications/': , - '/settings/account/emails/': , - '/settings/:orgId/': , - '/settings/:orgId/security-and-privacy/': , -}; - -/** - * With settings-v3, the org and account index pages are both titled "General", - * like the project one, so they share its icon to read as the same page type. - * Shared with the global cmd+k settings actions so both lists agree. - */ -export const SETTINGS_V3_GENERAL_ICONS: Record = { - '/settings/:orgId/': , - '/settings/account/details/': , -}; - -function getRouteIcon(route: string, settingsV3: boolean): ReactNode { - return ( - (settingsV3 ? SETTINGS_V3_GENERAL_ICONS[route] : undefined) ?? ROUTE_ICONS[route] - ); -} - function normalizeRouteForLookup(route: string): string { if (route === '/settings/organization/') { return '/settings/:orgId/'; @@ -108,14 +79,20 @@ function getNavItems(sections: NavigationSection[]) { return sections.flatMap(section => section.items); } -function getRouteTitleMap(settingsV3: boolean): Map { - const routeTitleMap = new Map(); +type RouteDisplay = {title: string; icon?: ReactNode}; + +/** + * Title and icon of each settings page, keyed by route, taken from the + * settings navigation configuration. + */ +function getRouteDisplayMap(settingsV3: boolean): Map { + const routeDisplayMap = new Map(); if (!settingsV3) { for (const item of getNavItems(getUserOrgNavigationConfiguration())) { - routeTitleMap.set(item.path, item.title); + routeDisplayMap.set(item.path, {title: item.title, icon: item.icon}); } - return routeTitleMap; + return routeDisplayMap; } // With settings-v3, org and account pages live in separate scopes and can @@ -138,20 +115,20 @@ function getRouteTitleMap(settingsV3: boolean): Map { for (const [scope, items] of scopedItems) { for (const item of items) { const isShared = (scopesByTitle.get(item.title)?.size ?? 0) > 1; - routeTitleMap.set( - item.path, - isShared ? `${getSettingsScopeTitle(scope)} › ${item.title}` : item.title - ); + routeDisplayMap.set(item.path, { + title: isShared ? `${getSettingsScopeTitle(scope)} › ${item.title}` : item.title, + icon: item.icon, + }); } } - return routeTitleMap; + return routeDisplayMap; } export function getSettingsFieldSections( orgSlug: string, settingsV3: boolean ): SettingsFieldSection[] { - const routeTitleMap = getRouteTitleMap(settingsV3); + const routeDisplayMap = getRouteDisplayMap(settingsV3); const groups = new Map>(); for (const field of Object.values(FORM_FIELD_REGISTRY)) { @@ -173,13 +150,14 @@ export function getSettingsFieldSections( return Array.from(groups.entries()) .map(([route, fieldMap]): SettingsFieldSection => { - const title = routeTitleMap.get(route) ?? titleFromRoute(route); + const routeDisplay = routeDisplayMap.get(route); + const title = routeDisplay?.title ?? titleFromRoute(route); const resolvedPath = resolveRoutePath(route, orgSlug); return { key: route, title, - icon: getRouteIcon(route, settingsV3), + icon: routeDisplay?.icon, fields: Array.from(fieldMap.values()) .filter( (f): f is FormFieldDefinition & {label: string} => diff --git a/static/app/views/settings/types.tsx b/static/app/views/settings/types.tsx index 0462733d61ba..803e94bf62f4 100644 --- a/static/app/views/settings/types.tsx +++ b/static/app/views/settings/types.tsx @@ -33,6 +33,10 @@ export type NavigationItem = { * The description of the settings section. This will be used in search. */ description?: string; + /** + * Icon shown next to the page in the command palette. + */ + icon?: ReactElement; /** * A string identifier for the navigation item. May be used in hooks to * augment navigation items. From 736520a619541961a8e3867b4dc01c6db1238fa8 Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Tue, 3 Nov 2026 13:11:36 +0100 Subject: [PATCH 6/6] ref(settings): Give General settings pages their scope icon The org, project and account General pages are the home page of their settings scope, so give them that scope's icon from the settings navigation rail (building, project and user) instead of a gear. The icon no longer depends on settings-v3. --- .../userOrgNavigationConfiguration.tsx | 8 +++---- .../projectSettingsCommandPaletteActions.tsx | 4 ++-- .../settingsCommandPaletteActions.spec.tsx | 22 +++++++++---------- static/app/views/settings/settingsScope.tsx | 4 ++-- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx b/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx index a18fa4cae67f..cf199dcd969d 100644 --- a/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx +++ b/static/app/views/settings/organization/userOrgNavigationConfiguration.tsx @@ -6,7 +6,6 @@ import {IconLock} from '@sentry/icons/lock'; import {IconMail} from '@sentry/icons/mail'; import {IconRepository} from '@sentry/icons/repository'; import {IconSeer} from '@sentry/icons/seer'; -import {IconSettings} from '@sentry/icons/settings'; import {IconSubscribed} from '@sentry/icons/subscribed'; import {IconTerminal} from '@sentry/icons/terminal'; import {IconUser} from '@sentry/icons/user'; @@ -16,6 +15,7 @@ import {FeatureBadge} from '@sentry/scraps/badge'; import {t} from 'sentry/locale'; import {hasDynamicSamplingCustomFeature} from 'sentry/utils/dynamicSampling/features'; import {showNewSeer} from 'sentry/utils/seer/showNewSeer'; +import {getSettingsScopeIcon} from 'sentry/views/settings/settingsScope'; import type {NavigationItem, NavigationSection} from 'sentry/views/settings/types'; const organizationSettingsPathPrefix = '/settings/:orgId'; @@ -66,9 +66,7 @@ function getAccountNavigationConfiguration({ items: [ { path: `${userSettingsPathPrefix}/details/`, - // With settings-v3 this is the account's "General" page, so it gets - // the same icon as the org and project General pages. - icon: settingsV3 ? : , + icon: getSettingsScopeIcon('account'), title: settingsV3 ? t('General') : t('Account Details'), keywords: [t('user settings'), t('account settings')], description: t( @@ -138,7 +136,7 @@ function getOrganizationNavigationConfiguration({ items: [ { path: `${organizationSettingsPathPrefix}/`, - icon: , + icon: getSettingsScopeIcon('org'), title: settingsV3 ? t('General') : t('General Settings'), keywords: [t('slug'), t('org slug'), t('organization slug')], index: true, diff --git a/static/app/views/settings/project/projectSettingsCommandPaletteActions.tsx b/static/app/views/settings/project/projectSettingsCommandPaletteActions.tsx index 1c4142182193..086af9c1d7c7 100644 --- a/static/app/views/settings/project/projectSettingsCommandPaletteActions.tsx +++ b/static/app/views/settings/project/projectSettingsCommandPaletteActions.tsx @@ -10,7 +10,6 @@ import {IconPlay} from '@sentry/icons/play'; import {IconProject} from '@sentry/icons/project'; import {IconReleases} from '@sentry/icons/releases'; import {IconSeer} from '@sentry/icons/seer'; -import {IconSettings} from '@sentry/icons/settings'; import {IconSiren} from '@sentry/icons/siren'; import {IconStack} from '@sentry/icons/stack'; import {IconTag} from '@sentry/icons/tag'; @@ -24,6 +23,7 @@ import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {replaceRouterParams} from 'sentry/utils/replaceRouterParams'; import {getNavigationConfiguration} from 'sentry/views/settings/project/navigationConfiguration'; +import {getSettingsScopeIcon} from 'sentry/views/settings/settingsScope'; import type {NavigationGroupProps, NavigationItem} from 'sentry/views/settings/types'; import {useSettingsV3} from 'sentry/views/settings/useSettingsV3'; @@ -56,7 +56,7 @@ type ProjectSettingsCommandPaletteSection = { }; export const PROJECT_SETTINGS_ICONS: Record = { - '': , + '': getSettingsScopeIcon('project'), 'alerts/': , 'debug-symbols/': , 'environments/': , diff --git a/static/app/views/settings/settingsCommandPaletteActions.spec.tsx b/static/app/views/settings/settingsCommandPaletteActions.spec.tsx index 01f44e27024e..5e68808d7740 100644 --- a/static/app/views/settings/settingsCommandPaletteActions.spec.tsx +++ b/static/app/views/settings/settingsCommandPaletteActions.spec.tsx @@ -1,4 +1,4 @@ -import {IconSettings} from '@sentry/icons/settings'; +import {IconBuilding} from '@sentry/icons/building'; import {IconUser} from '@sentry/icons/user'; import {getSettingsFieldSections} from 'sentry/views/settings/settingsCommandPaletteActions'; @@ -36,14 +36,14 @@ describe('getSettingsFieldSections', () => { ); }); - it('gives both General pages the settings icon with settings-v3', () => { - const v3Sections = getSettingsFieldSections('org-slug', true); - expect(getSection(v3Sections, '/settings/:orgId/').icon).toEqual(); - expect(getSection(v3Sections, '/settings/account/details/').icon).toEqual( - - ); - - const sections = getSettingsFieldSections('org-slug', false); - expect(getSection(sections, '/settings/account/details/').icon).toEqual(); - }); + it.each([true, false])( + 'gives the General pages their scope icon (settings-v3: %s)', + settingsV3 => { + const sections = getSettingsFieldSections('org-slug', settingsV3); + expect(getSection(sections, '/settings/:orgId/').icon).toEqual(); + expect(getSection(sections, '/settings/account/details/').icon).toEqual( + + ); + } + ); }); diff --git a/static/app/views/settings/settingsScope.tsx b/static/app/views/settings/settingsScope.tsx index d252f174f6a0..6246f10ba08a 100644 --- a/static/app/views/settings/settingsScope.tsx +++ b/static/app/views/settings/settingsScope.tsx @@ -1,4 +1,4 @@ -import type {ReactNode} from 'react'; +import type {ReactElement} from 'react'; import {IconBuilding} from '@sentry/icons/building'; import {IconProject} from '@sentry/icons/project'; import {IconUser} from '@sentry/icons/user'; @@ -25,7 +25,7 @@ export function getSettingsScopeTitle(scope: SettingsScope): string { * The icon that stands for a settings scope wherever the scope is listed, e.g. * the settings navigation rail and the cmd+k settings groups. */ -export function getSettingsScopeIcon(scope: SettingsScope): ReactNode { +export function getSettingsScopeIcon(scope: SettingsScope): ReactElement { switch (scope) { case 'account': return ;