Browse Source

feat(nav): Merge account and organization nav items (#85017)

Currently the nav items in the sidebar will change depending on whether
you are in account or org settings. This change uses a shared config so
that you can easily switch between account and org settings without the
nav changing on you.
Malachi Willey 3 weeks ago
parent
commit
3289456077

+ 10 - 7
static/app/components/search/sources/routeSource.tsx

@@ -9,17 +9,20 @@ import {createFuzzySearch} from 'sentry/utils/fuzzySearch';
 import replaceRouterParams from 'sentry/utils/replaceRouterParams';
 import withLatestContext from 'sentry/utils/withLatestContext';
 import accountSettingsNavigation from 'sentry/views/settings/account/navigationConfiguration';
-import organizationSettingsNavigation from 'sentry/views/settings/organization/navigationConfiguration';
+import {getOrganizationNavigationConfiguration} from 'sentry/views/settings/organization/navigationConfiguration';
 import projectSettingsNavigation from 'sentry/views/settings/project/navigationConfiguration';
-import type {NavigationItem} from 'sentry/views/settings/types';
+import type {NavigationItem, NavigationSection} from 'sentry/views/settings/types';
 
 import type {ChildProps, ResultItem} from './types';
 import {strGetFn} from './utils';
 
-type Config =
-  | typeof accountSettingsNavigation
-  | typeof organizationSettingsNavigation
-  | typeof projectSettingsNavigation;
+type ConfigParams = {
+  debugFilesNeedsReview?: boolean;
+  organization?: Organization;
+  project?: Project;
+};
+
+type Config = ((params: ConfigParams) => NavigationSection[]) | NavigationSection[];
 
 // XXX(epurkhiser): We use the context in mapFunc to handle both producing the
 // NavigationSection list AND filtering out items in the sections that should
@@ -121,7 +124,7 @@ class RouteSource extends Component<Props, State> {
     const searchMap: NavigationItem[] = [
       mapFunc(accountSettingsNavigation, context),
       mapFunc(projectSettingsNavigation, context),
-      mapFunc(organizationSettingsNavigation, context),
+      mapFunc(getOrganizationNavigationConfiguration, context),
       mapFunc(this.getHookConfigs(), context),
     ].flat(2);
 

+ 1 - 1
static/app/views/settings/account/accountSettingsLayout.tsx

@@ -10,7 +10,7 @@ import SettingsLayout from 'sentry/views/settings/components/settingsLayout';
 
 type Props = React.ComponentProps<typeof SettingsLayout> & {
   api: Client;
-  organization: Organization;
+  organization?: Organization;
 };
 
 class AccountSettingsLayout extends Component<Props> {

+ 3 - 1
static/app/views/settings/account/accountSettingsNavigation.tsx

@@ -3,7 +3,7 @@ import getConfiguration from 'sentry/views/settings/account/navigationConfigurat
 import SettingsNavigation from 'sentry/views/settings/components/settingsNavigation';
 
 type Props = {
-  organization: Organization;
+  organization?: Organization;
 };
 
 function AccountSettingsNavigation({organization}: Props) {
@@ -11,6 +11,8 @@ function AccountSettingsNavigation({organization}: Props) {
     <SettingsNavigation
       organization={organization}
       navigationObjects={getConfiguration({organization})}
+      features={new Set(organization?.features)}
+      access={new Set(organization?.access)}
     />
   );
 }

+ 7 - 0
static/app/views/settings/account/navigationConfiguration.tsx

@@ -1,6 +1,7 @@
 import {t} from 'sentry/locale';
 import HookStore from 'sentry/stores/hookStore';
 import type {Organization} from 'sentry/types/organization';
+import {getUserOrgNavigationConfiguration} from 'sentry/views/settings/organization/userOrgNavigationConfiguration';
 import type {NavigationSection} from 'sentry/views/settings/types';
 
 const pathPrefix = '/settings/account';
@@ -10,6 +11,12 @@ type ConfigParams = {
 };
 
 function getConfiguration({organization}: ConfigParams): NavigationSection[] {
+  const hasNavigationV2 = organization?.features.includes('navigation-sidebar-v2');
+
+  if (organization && hasNavigationV2) {
+    return getUserOrgNavigationConfiguration({organization});
+  }
+
   return [
     {
       name: t('Account'),

+ 165 - 149
static/app/views/settings/organization/navigationConfiguration.tsx

@@ -1,158 +1,174 @@
 import FeatureBadge from 'sentry/components/badge/featureBadge';
 import {t} from 'sentry/locale';
+import type {Organization} from 'sentry/types/organization';
 import {hasDynamicSamplingCustomFeature} from 'sentry/utils/dynamicSampling/features';
+import {getUserOrgNavigationConfiguration} from 'sentry/views/settings/organization/userOrgNavigationConfiguration';
 import type {NavigationSection} from 'sentry/views/settings/types';
 
 const organizationSettingsPathPrefix = '/settings/:orgId';
 const userSettingsPathPrefix = '/settings/account';
 
-const organizationNavigation: NavigationSection[] = [
-  {
-    name: t('User Settings'),
-    items: [
-      {
-        path: `${userSettingsPathPrefix}/`,
-        title: t('General Settings'),
-        description: t('Configure general settings for your account'),
-        id: 'user-settings',
-      },
-    ],
-  },
-  {
-    name: t('Organization'),
-    items: [
-      {
-        path: `${organizationSettingsPathPrefix}/`,
-        title: t('General Settings'),
-        index: true,
-        description: t('Configure general settings for an organization'),
-        id: 'general',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/projects/`,
-        title: t('Projects'),
-        description: t("View and manage an organization's projects"),
-        id: 'projects',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/teams/`,
-        title: t('Teams'),
-        description: t("Manage an organization's teams"),
-        id: 'teams',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/members/`,
-        title: t('Members'),
-        description: t('Manage user membership for an organization'),
-        id: 'members',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/security-and-privacy/`,
-        title: t('Security & Privacy'),
-        description: t(
-          'Configuration related to dealing with sensitive data and other security settings. (Data Scrubbing, Data Privacy, Data Scrubbing)'
-        ),
-        id: 'security-and-privacy',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/auth/`,
-        title: t('Auth'),
-        description: t('Configure single sign-on'),
-        id: 'sso',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/api-keys/`,
-        title: t('API Keys'),
-        show: ({access, features}) =>
-          features!.has('api-keys') && access!.has('org:admin'),
-        id: 'api-keys',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/audit-log/`,
-        title: t('Audit Log'),
-        description: t('View the audit log for an organization'),
-        id: 'audit-log',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/rate-limits/`,
-        title: t('Rate Limits'),
-        show: ({features}) => features!.has('legacy-rate-limits'),
-        description: t('Configure rate limits for all projects in the organization'),
-        id: 'rate-limits',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/relay/`,
-        title: t('Relay'),
-        description: t('Manage relays connected to the organization'),
-        id: 'relay',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/repos/`,
-        title: t('Repositories'),
-        description: t('Manage repositories connected to the organization'),
-        id: 'repos',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/integrations/`,
-        title: t('Integrations'),
-        description: t(
-          'Manage organization-level integrations, including: Slack, Github, Bitbucket, Jira, and Azure DevOps'
-        ),
-        id: 'integrations',
-        recordAnalytics: true,
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/early-features/`,
-        title: t('Early Features'),
-        description: t('Manage early access features'),
-        badge: () => <FeatureBadge type="new" />,
-        show: ({isSelfHosted}) => isSelfHosted || false,
-        id: 'early-features',
-        recordAnalytics: true,
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/dynamic-sampling/`,
-        title: t('Dynamic Sampling'),
-        description: t('Manage your sampling rate'),
-        badge: () => 'alpha',
-        show: ({organization}) =>
-          !!organization && hasDynamicSamplingCustomFeature(organization),
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/feature-flags/`,
-        title: t('Feature Flags'),
-        description: t('Set up your provider webhooks'),
-        badge: () => 'beta',
-        show: ({organization}) =>
-          !!organization && organization.features.includes('feature-flag-ui'),
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/stats/`,
-        title: t('Stats & Usage'),
-        description: t('View organization stats and usage'),
-        id: 'stats',
-        show: ({organization}) =>
-          organization?.features.includes('navigation-sidebar-v2') ?? false,
-      },
-    ],
-  },
-  {
-    name: t('Developer Settings'),
-    items: [
-      {
-        path: `${organizationSettingsPathPrefix}/auth-tokens/`,
-        title: t('Auth Tokens'),
-        description: t('Manage organization auth tokens'),
-        id: 'auth-tokens',
-      },
-      {
-        path: `${organizationSettingsPathPrefix}/developer-settings/`,
-        title: t('Custom Integrations'),
-        description: t('Manage custom integrations'),
-        id: 'developer-settings',
-      },
-    ],
-  },
-];
+type ConfigParams = {
+  organization?: Organization;
+};
 
-export default organizationNavigation;
+export function getOrganizationNavigationConfiguration({
+  organization: incomingOrganization,
+}: ConfigParams): NavigationSection[] {
+  const hasNavigationV2 = incomingOrganization?.features.includes(
+    'navigation-sidebar-v2'
+  );
+
+  if (incomingOrganization && hasNavigationV2) {
+    return getUserOrgNavigationConfiguration({organization: incomingOrganization});
+  }
+
+  return [
+    {
+      name: t('User Settings'),
+      items: [
+        {
+          path: `${userSettingsPathPrefix}/`,
+          title: t('General Settings'),
+          description: t('Configure general settings for your account'),
+          id: 'user-settings',
+        },
+      ],
+    },
+    {
+      name: t('Organization'),
+      items: [
+        {
+          path: `${organizationSettingsPathPrefix}/`,
+          title: t('General Settings'),
+          index: true,
+          description: t('Configure general settings for an organization'),
+          id: 'general',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/projects/`,
+          title: t('Projects'),
+          description: t("View and manage an organization's projects"),
+          id: 'projects',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/teams/`,
+          title: t('Teams'),
+          description: t("Manage an organization's teams"),
+          id: 'teams',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/members/`,
+          title: t('Members'),
+          description: t('Manage user membership for an organization'),
+          id: 'members',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/security-and-privacy/`,
+          title: t('Security & Privacy'),
+          description: t(
+            'Configuration related to dealing with sensitive data and other security settings. (Data Scrubbing, Data Privacy, Data Scrubbing)'
+          ),
+          id: 'security-and-privacy',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/auth/`,
+          title: t('Auth'),
+          description: t('Configure single sign-on'),
+          id: 'sso',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/api-keys/`,
+          title: t('API Keys'),
+          show: ({access, features}) =>
+            features!.has('api-keys') && access!.has('org:admin'),
+          id: 'api-keys',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/audit-log/`,
+          title: t('Audit Log'),
+          description: t('View the audit log for an organization'),
+          id: 'audit-log',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/rate-limits/`,
+          title: t('Rate Limits'),
+          show: ({features}) => features!.has('legacy-rate-limits'),
+          description: t('Configure rate limits for all projects in the organization'),
+          id: 'rate-limits',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/relay/`,
+          title: t('Relay'),
+          description: t('Manage relays connected to the organization'),
+          id: 'relay',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/repos/`,
+          title: t('Repositories'),
+          description: t('Manage repositories connected to the organization'),
+          id: 'repos',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/integrations/`,
+          title: t('Integrations'),
+          description: t(
+            'Manage organization-level integrations, including: Slack, Github, Bitbucket, Jira, and Azure DevOps'
+          ),
+          id: 'integrations',
+          recordAnalytics: true,
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/early-features/`,
+          title: t('Early Features'),
+          description: t('Manage early access features'),
+          badge: () => <FeatureBadge type="new" />,
+          show: ({isSelfHosted}) => isSelfHosted || false,
+          id: 'early-features',
+          recordAnalytics: true,
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/dynamic-sampling/`,
+          title: t('Dynamic Sampling'),
+          description: t('Manage your sampling rate'),
+          badge: () => 'alpha',
+          show: ({organization}) =>
+            !!organization && hasDynamicSamplingCustomFeature(organization),
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/feature-flags/`,
+          title: t('Feature Flags'),
+          description: t('Set up your provider webhooks'),
+          badge: () => 'beta',
+          show: ({organization}) =>
+            !!organization && organization.features.includes('feature-flag-ui'),
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/stats/`,
+          title: t('Stats & Usage'),
+          description: t('View organization stats and usage'),
+          id: 'stats',
+          show: ({organization}) =>
+            organization?.features.includes('navigation-sidebar-v2') ?? false,
+        },
+      ],
+    },
+    {
+      name: t('Developer Settings'),
+      items: [
+        {
+          path: `${organizationSettingsPathPrefix}/auth-tokens/`,
+          title: t('Auth Tokens'),
+          description: t('Manage organization auth tokens'),
+          id: 'auth-tokens',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/developer-settings/`,
+          title: t('Custom Integrations'),
+          description: t('Manage custom integrations'),
+          id: 'developer-settings',
+        },
+      ],
+    },
+  ];
+}

+ 2 - 2
static/app/views/settings/organization/organizationSettingsNavigation.tsx

@@ -6,7 +6,7 @@ import type {HookName, Hooks} from 'sentry/types/hooks';
 import type {Organization} from 'sentry/types/organization';
 import withOrganization from 'sentry/utils/withOrganization';
 import SettingsNavigation from 'sentry/views/settings/components/settingsNavigation';
-import navigationConfiguration from 'sentry/views/settings/organization/navigationConfiguration';
+import {getOrganizationNavigationConfiguration} from 'sentry/views/settings/organization/navigationConfiguration';
 import type {NavigationSection} from 'sentry/views/settings/types';
 
 type Props = {
@@ -81,7 +81,7 @@ class OrganizationSettingsNavigation extends Component<Props, State> {
 
     return (
       <SettingsNavigation
-        navigationObjects={navigationConfiguration}
+        navigationObjects={getOrganizationNavigationConfiguration({organization})}
         access={access}
         features={features}
         organization={organization}

+ 228 - 0
static/app/views/settings/organization/userOrgNavigationConfiguration.tsx

@@ -0,0 +1,228 @@
+import FeatureBadge from 'sentry/components/badge/featureBadge';
+import {t} from 'sentry/locale';
+import HookStore from 'sentry/stores/hookStore';
+import type {Organization} from 'sentry/types/organization';
+import {hasDynamicSamplingCustomFeature} from 'sentry/utils/dynamicSampling/features';
+import type {NavigationSection} from 'sentry/views/settings/types';
+
+const organizationSettingsPathPrefix = '/settings/:orgId';
+const userSettingsPathPrefix = '/settings/account';
+
+export function getUserOrgNavigationConfiguration({
+  organization: incomingOrganization,
+}: {
+  organization: Organization;
+}): NavigationSection[] {
+  return [
+    {
+      name: t('Account'),
+      items: [
+        {
+          path: `${userSettingsPathPrefix}/details/`,
+          title: t('Account Details'),
+          description: t(
+            'Change your account details and preferences (e.g. timezone/clock, avatar, language)'
+          ),
+        },
+        {
+          path: `${userSettingsPathPrefix}/security/`,
+          title: t('Security'),
+          description: t('Change your account password and/or two factor authentication'),
+        },
+        {
+          path: `${userSettingsPathPrefix}/notifications/`,
+          title: t('Notifications'),
+          description: t('Configure what email notifications to receive'),
+        },
+        {
+          path: `${userSettingsPathPrefix}/emails/`,
+          title: t('Email Addresses'),
+          description: t(
+            'Add or remove secondary emails, change your primary email, verify your emails'
+          ),
+        },
+        {
+          path: `${userSettingsPathPrefix}/subscriptions/`,
+          title: t('Subscriptions'),
+          description: t(
+            'Change Sentry marketing subscriptions you are subscribed to (GDPR)'
+          ),
+        },
+        {
+          path: `${userSettingsPathPrefix}/authorizations/`,
+          title: t('Authorized Applications'),
+          description: t(
+            'Manage third-party applications that have access to your Sentry account'
+          ),
+        },
+        {
+          path: `${userSettingsPathPrefix}/identities/`,
+          title: t('Identities'),
+          description: t(
+            'Manage your third-party identities that are associated to Sentry'
+          ),
+        },
+        {
+          path: `${userSettingsPathPrefix}/close-account/`,
+          title: t('Close Account'),
+          description: t('Permanently close your Sentry account'),
+        },
+      ],
+    },
+    {
+      name: t('Organization'),
+      items: [
+        {
+          path: `${organizationSettingsPathPrefix}/`,
+          title: t('General Settings'),
+          index: true,
+          description: t('Configure general settings for an organization'),
+          id: 'general',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/projects/`,
+          title: t('Projects'),
+          description: t("View and manage an organization's projects"),
+          id: 'projects',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/teams/`,
+          title: t('Teams'),
+          description: t("Manage an organization's teams"),
+          id: 'teams',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/members/`,
+          title: t('Members'),
+          description: t('Manage user membership for an organization'),
+          id: 'members',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/security-and-privacy/`,
+          title: t('Security & Privacy'),
+          description: t(
+            'Configuration related to dealing with sensitive data and other security settings. (Data Scrubbing, Data Privacy, Data Scrubbing)'
+          ),
+          id: 'security-and-privacy',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/auth/`,
+          title: t('Auth'),
+          description: t('Configure single sign-on'),
+          id: 'sso',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/api-keys/`,
+          title: t('API Keys'),
+          show: ({access, features}) =>
+            (features?.has('api-keys') && access?.has('org:admin')) ?? false,
+          id: 'api-keys',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/audit-log/`,
+          title: t('Audit Log'),
+          description: t('View the audit log for an organization'),
+          id: 'audit-log',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/rate-limits/`,
+          title: t('Rate Limits'),
+          show: ({features}) => features?.has('legacy-rate-limits') ?? false,
+          description: t('Configure rate limits for all projects in the organization'),
+          id: 'rate-limits',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/relay/`,
+          title: t('Relay'),
+          description: t('Manage relays connected to the organization'),
+          id: 'relay',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/repos/`,
+          title: t('Repositories'),
+          description: t('Manage repositories connected to the organization'),
+          id: 'repos',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/integrations/`,
+          title: t('Integrations'),
+          description: t(
+            'Manage organization-level integrations, including: Slack, Github, Bitbucket, Jira, and Azure DevOps'
+          ),
+          id: 'integrations',
+          recordAnalytics: true,
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/early-features/`,
+          title: t('Early Features'),
+          description: t('Manage early access features'),
+          badge: () => <FeatureBadge type="new" />,
+          show: ({isSelfHosted}) => isSelfHosted || false,
+          id: 'early-features',
+          recordAnalytics: true,
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/dynamic-sampling/`,
+          title: t('Dynamic Sampling'),
+          description: t('Manage your sampling rate'),
+          badge: () => 'alpha',
+          show: ({organization}) =>
+            !!organization && hasDynamicSamplingCustomFeature(organization),
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/feature-flags/`,
+          title: t('Feature Flags'),
+          description: t('Set up your provider webhooks'),
+          badge: () => 'beta',
+          show: ({organization}) =>
+            !!organization && organization.features.includes('feature-flag-ui'),
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/stats/`,
+          title: t('Stats & Usage'),
+          description: t('View organization stats and usage'),
+          id: 'stats',
+          show: ({organization}) =>
+            organization?.features.includes('navigation-sidebar-v2') ?? false,
+        },
+      ],
+    },
+    {
+      name: t('Developer Settings'),
+      items: [
+        {
+          path: `${organizationSettingsPathPrefix}/auth-tokens/`,
+          title: t('Auth Tokens'),
+          description: t('Manage organization auth tokens'),
+          id: 'auth-tokens',
+        },
+        {
+          path: `${organizationSettingsPathPrefix}/developer-settings/`,
+          title: t('Custom Integrations'),
+          description: t('Manage custom integrations'),
+          id: 'developer-settings',
+        },
+      ],
+    },
+    {
+      name: t('API'),
+      items: [
+        {
+          path: `${userSettingsPathPrefix}/api/applications/`,
+          title: t('Applications'),
+          description: t('Add and configure OAuth2 applications'),
+        },
+        {
+          path: `${userSettingsPathPrefix}/api/auth-tokens/`,
+          title: t('User Auth Tokens'),
+          description: t(
+            "Authentication tokens allow you to perform actions against the Sentry API on behalf of your account. They're the easiest way to get started using the API."
+          ),
+        },
+        ...HookStore.get('settings:api-navigation-config').flatMap(cb =>
+          cb(incomingOrganization)
+        ),
+      ],
+    },
+  ];
+}