diff --git a/static/app/views/navigation/index.desktop.spec.tsx b/static/app/views/navigation/index.desktop.spec.tsx
index 414d53ea7b90de..612752eb989283 100644
--- a/static/app/views/navigation/index.desktop.spec.tsx
+++ b/static/app/views/navigation/index.desktop.spec.tsx
@@ -353,7 +353,6 @@ describe('desktop navigation', () => {
[`${ORG}/insights/ai-agents/`, 'Insights', 'Agents'],
[`${ORG}/insights/mcp/`, 'Insights', 'MCP'],
[`${ORG}/monitors/crons/?insightsRedirect=true`, 'Monitors', 'Crons'],
- [`${ORG}/insights/projects/`, 'Insights', 'All Projects'],
// Monitors
[`${ORG}/monitors/`, 'Monitors', 'All Monitors'],
[`${ORG}/monitors/my-monitors/`, 'Monitors', 'My Monitors'],
diff --git a/static/app/views/navigation/primaryNavigationContext.tsx b/static/app/views/navigation/primaryNavigationContext.tsx
index 378dcdf69aa0e5..5b5b2baf688f61 100644
--- a/static/app/views/navigation/primaryNavigationContext.tsx
+++ b/static/app/views/navigation/primaryNavigationContext.tsx
@@ -13,6 +13,7 @@ const PRIMARY_NAVIGATION_GROUP_CONFIG = {
explore: ['explore'],
dashboards: ['dashboards', 'dashboard'],
insights: ['insights'],
+ projects: ['projects'], // No primary nav button — accessed via logo nav. Needed for secondary nav routing.
monitors: ['monitors'],
settings: ['settings'],
prevent: ['prevent'],
diff --git a/static/app/views/navigation/secondary/content.tsx b/static/app/views/navigation/secondary/content.tsx
index 16ddf8ae18cec1..30f4995fb3284d 100644
--- a/static/app/views/navigation/secondary/content.tsx
+++ b/static/app/views/navigation/secondary/content.tsx
@@ -8,6 +8,7 @@ import {ExploreSecondaryNavigation} from 'sentry/views/navigation/secondary/sect
import {InsightsSecondaryNavigation} from 'sentry/views/navigation/secondary/sections/insights/insightsSecondaryNavigation';
import {IssuesSecondaryNavigation} from 'sentry/views/navigation/secondary/sections/issues/issuesSecondaryNavigation';
import {MonitorsSecondaryNavigation} from 'sentry/views/navigation/secondary/sections/monitors/monitorsSecondaryNavigation';
+import {ProjectsSecondaryNavigation} from 'sentry/views/navigation/secondary/sections/projects/projectsSecondaryNavigation';
import {SettingsSecondaryNavigation} from 'sentry/views/navigation/secondary/sections/settings/settingsSecondaryNavigation';
export function SecondaryNavigationContent(): ReactNode {
@@ -21,6 +22,8 @@ export function SecondaryNavigationContent(): ReactNode {
return ;
case 'explore':
return ;
+ case 'projects':
+ return ;
case 'monitors':
return ;
case 'prevent':
diff --git a/static/app/views/navigation/secondary/sections/insights/insightsSecondaryNavigation.tsx b/static/app/views/navigation/secondary/sections/insights/insightsSecondaryNavigation.tsx
index fa8bd2d91cea01..077bf900a9cf03 100644
--- a/static/app/views/navigation/secondary/sections/insights/insightsSecondaryNavigation.tsx
+++ b/static/app/views/navigation/secondary/sections/insights/insightsSecondaryNavigation.tsx
@@ -1,10 +1,8 @@
-import {Fragment, useMemo} from 'react';
-import partition from 'lodash/partition';
+import {Fragment} from 'react';
import Feature from 'sentry/components/acl/feature';
import {t} from 'sentry/locale';
import {useOrganization} from 'sentry/utils/useOrganization';
-import {useProjects} from 'sentry/utils/useProjects';
import {useUser} from 'sentry/utils/useUser';
import {makeMonitorBasePathname} from 'sentry/views/detectors/pathnames';
import {
@@ -29,23 +27,13 @@ import {
} from 'sentry/views/insights/pages/mobile/settings';
import {DOMAIN_VIEW_BASE_URL} from 'sentry/views/insights/pages/settings';
import {SecondaryNavigation} from 'sentry/views/navigation/secondary/components';
+import {ProjectsNavigationItems} from 'sentry/views/navigation/secondary/sections/projects/starredProjectsList';
export function InsightsSecondaryNavigation() {
const user = useUser();
const organization = useOrganization();
const baseUrl = `/organizations/${organization.slug}/${DOMAIN_VIEW_BASE_URL}`;
- const {projects} = useProjects();
-
- const [starredProjects, nonStarredProjects] = useMemo(() => {
- return partition(projects, project => project.isBookmarked);
- }, [projects]);
-
- const displayStarredProjects = starredProjects.length > 0;
- const projectsToDisplay = displayStarredProjects
- ? starredProjects.slice(0, 8)
- : nonStarredProjects.filter(project => project.isMember).slice(0, 8);
-
const shouldRedirectToMonitors =
organization.features.includes('workflow-engine-ui') && !user?.isStaff;
@@ -133,49 +121,15 @@ export function InsightsSecondaryNavigation() {
-
-
-
-
-
- {t('All Projects')}
-
-
-
-
- {projectsToDisplay.length > 0 ? (
+ {!organization.features.includes('workflow-engine-ui') && (
-
-
- {projectsToDisplay.map(project => (
-
-
- }
- analyticsItemName="insights_project_starred"
- >
- {project.slug}
-
-
- ))}
-
-
+
- ) : null}
+ )}
);
diff --git a/static/app/views/navigation/secondary/sections/projects/projectsSecondaryNavigation.tsx b/static/app/views/navigation/secondary/sections/projects/projectsSecondaryNavigation.tsx
new file mode 100644
index 00000000000000..f6be8ee179576d
--- /dev/null
+++ b/static/app/views/navigation/secondary/sections/projects/projectsSecondaryNavigation.tsx
@@ -0,0 +1,16 @@
+import {Fragment} from 'react';
+
+import {t} from 'sentry/locale';
+import {SecondaryNavigation} from 'sentry/views/navigation/secondary/components';
+import {ProjectsNavigationItems} from 'sentry/views/navigation/secondary/sections/projects/starredProjectsList';
+
+export function ProjectsSecondaryNavigation() {
+ return (
+
+ {t('Projects')}
+
+
+
+
+ );
+}
diff --git a/static/app/views/navigation/secondary/sections/projects/starredProjectsList.tsx b/static/app/views/navigation/secondary/sections/projects/starredProjectsList.tsx
new file mode 100644
index 00000000000000..5659b4f1527a28
--- /dev/null
+++ b/static/app/views/navigation/secondary/sections/projects/starredProjectsList.tsx
@@ -0,0 +1,80 @@
+import {Fragment, useMemo} from 'react';
+import partition from 'lodash/partition';
+
+import {t} from 'sentry/locale';
+import {useOrganization} from 'sentry/utils/useOrganization';
+import {useProjects} from 'sentry/utils/useProjects';
+import {SecondaryNavigation} from 'sentry/views/navigation/secondary/components';
+import {makeProjectsPathname} from 'sentry/views/projects/pathname';
+
+interface ProjectsNavigationItemsProps {
+ allProjectsAnalyticsItemName?: string;
+ starredAnalyticsItemName?: string;
+}
+
+export function ProjectsNavigationItems({
+ allProjectsAnalyticsItemName = 'projects_all',
+ starredAnalyticsItemName = 'project_starred',
+}: ProjectsNavigationItemsProps) {
+ const organization = useOrganization();
+ const {projects} = useProjects();
+
+ const [starredProjects, nonStarredProjects] = useMemo(() => {
+ return partition(projects, project => project.isBookmarked);
+ }, [projects]);
+
+ const displayStarredProjects = starredProjects.length > 0;
+ const projectsToDisplay = displayStarredProjects
+ ? starredProjects.slice(0, 8)
+ : nonStarredProjects.filter(project => project.isMember).slice(0, 8);
+
+ return (
+
+
+
+
+
+ {t('All Projects')}
+
+
+
+
+ {projectsToDisplay.length > 0 ? (
+
+
+
+
+ {projectsToDisplay.map(project => (
+
+
+ }
+ analyticsItemName={starredAnalyticsItemName}
+ >
+ {project.slug}
+
+
+ ))}
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/static/app/views/projects/index.tsx b/static/app/views/projects/index.tsx
index c6d637f1509828..0ff20630db1201 100644
--- a/static/app/views/projects/index.tsx
+++ b/static/app/views/projects/index.tsx
@@ -1,13 +1,26 @@
import {Outlet} from 'react-router-dom';
import {Redirect} from 'sentry/components/redirect';
+import {useOrganization} from 'sentry/utils/useOrganization';
import {useRedirectNavigationV2Routes} from 'sentry/views/navigation/useRedirectNavigationV2Routes';
export default function Projects() {
- const redirectPath = useRedirectNavigationV2Routes({
+ const organization = useOrganization();
+ // Both hooks are called unconditionally (React rules of hooks), but only one
+ // can match at a time since the current URL can't start with both prefixes.
+ // We select which result to act on based on the feature flag.
+ const forwardRedirect = useRedirectNavigationV2Routes({
oldPathPrefix: '/projects/',
newPathPrefix: '/insights/projects/',
});
+ const reverseRedirect = useRedirectNavigationV2Routes({
+ oldPathPrefix: '/insights/projects/',
+ newPathPrefix: '/projects/',
+ });
+
+ const redirectPath = organization.features.includes('workflow-engine-ui')
+ ? reverseRedirect
+ : forwardRedirect;
if (redirectPath) {
return ;
diff --git a/static/app/views/projects/pathname.tsx b/static/app/views/projects/pathname.tsx
index 6ed6d1bb685a20..fd7ef740b62ebd 100644
--- a/static/app/views/projects/pathname.tsx
+++ b/static/app/views/projects/pathname.tsx
@@ -1,8 +1,6 @@
import type {Organization} from 'sentry/types/organization';
import {normalizeUrl} from 'sentry/utils/url/normalizeUrl';
-const PROJECTS_BASE_PATHNAME = 'insights/projects';
-
export function makeProjectsPathname({
path,
organization,
@@ -10,7 +8,8 @@ export function makeProjectsPathname({
organization: Organization;
path: '/' | `/${string}/`;
}) {
- return normalizeUrl(
- `/organizations/${organization.slug}/${PROJECTS_BASE_PATHNAME}${path}`
- );
+ const base = organization.features.includes('workflow-engine-ui')
+ ? 'projects'
+ : 'insights/projects';
+ return normalizeUrl(`/organizations/${organization.slug}/${base}${path}`);
}