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 ;