diff --git a/apps/studio/components/layouts/Navigation/NavigationBar/NavigationBar.utils.test.tsx b/apps/studio/components/layouts/Navigation/NavigationBar/NavigationBar.utils.test.tsx new file mode 100644 index 0000000000..4518c37011 --- /dev/null +++ b/apps/studio/components/layouts/Navigation/NavigationBar/NavigationBar.utils.test.tsx @@ -0,0 +1,208 @@ +import type { Project } from 'data/projects/project-detail-query' +import { describe, expect, it } from 'vitest' + +import { + generateOtherRoutes, + generateProductRoutes, + generateSettingsRoutes, + generateToolRoutes, +} from './NavigationBar.utils' + +const REF = 'test-project-ref' + +const activeProject = { status: 'ACTIVE_HEALTHY' } as Project +const buildingProject = { status: 'COMING_UP' } as Project +const inactiveProject = { status: 'INACTIVE' } as Project + +const keys = (routes: { key: string }[]) => routes.map((r) => r.key) + +describe('generateToolRoutes', () => { + it('always returns Table Editor and SQL Editor', () => { + const routes = generateToolRoutes(REF, activeProject) + expect(keys(routes)).toEqual(['editor', 'sql']) + }) + + it('marks routes as disabled when project is not active', () => { + const routes = generateToolRoutes(REF, inactiveProject) + expect(routes.every((r) => r.disabled)).toBe(true) + }) + + it('points links to the building URL when project is building', () => { + const routes = generateToolRoutes(REF, buildingProject) + expect(routes.every((r) => r.link === `/project/${REF}`)).toBe(true) + }) + + it('returns links as false when ref is undefined', () => { + const routes = generateToolRoutes(undefined, activeProject) + expect(routes.every((r) => r.link === undefined)).toBe(true) + }) +}) + +describe('generateProductRoutes', () => { + it('includes all product routes when all features are enabled', () => { + const routes = generateProductRoutes(REF, activeProject, { + auth: true, + storage: true, + edgeFunctions: true, + realtime: true, + }) + expect(keys(routes)).toEqual(['database', 'auth', 'storage', 'functions', 'realtime']) + }) + + it('includes all product routes by default (features default to true)', () => { + const routes = generateProductRoutes(REF, activeProject) + expect(keys(routes)).toEqual(['database', 'auth', 'storage', 'functions', 'realtime']) + }) + + it('excludes auth when auth feature is disabled', () => { + const routes = generateProductRoutes(REF, activeProject, { auth: false }) + expect(keys(routes)).not.toContain('auth') + + expect(keys(routes)).toContain('database') + expect(keys(routes)).toContain('storage') + }) + + it('excludes storage when storage feature is disabled', () => { + const routes = generateProductRoutes(REF, activeProject, { storage: false }) + expect(keys(routes)).not.toContain('storage') + }) + + it('excludes edge functions when edgeFunctions feature is disabled', () => { + const routes = generateProductRoutes(REF, activeProject, { edgeFunctions: false }) + expect(keys(routes)).not.toContain('functions') + }) + + it('excludes realtime when realtime feature is disabled', () => { + const routes = generateProductRoutes(REF, activeProject, { realtime: false }) + expect(keys(routes)).not.toContain('realtime') + }) + + it('links auth to overview page when authOverviewPage is enabled', () => { + const routes = generateProductRoutes(REF, activeProject, { authOverviewPage: true }) + const authRoute = routes.find((r) => r.key === 'auth') + expect(authRoute?.link).toBe(`/project/${REF}/auth/overview`) + }) + + it('links auth to users page by default', () => { + const routes = generateProductRoutes(REF, activeProject) + const authRoute = routes.find((r) => r.key === 'auth') + expect(authRoute?.link).toBe(`/project/${REF}/auth/users`) + }) + + it('always includes database even when all optional features are disabled', () => { + const routes = generateProductRoutes(REF, activeProject, { + auth: false, + storage: false, + edgeFunctions: false, + realtime: false, + }) + expect(keys(routes)).toEqual(['database']) + }) +}) + +describe('generateOtherRoutes', () => { + it('always includes advisors, logs, and integrations', () => { + const routes = generateOtherRoutes(REF, activeProject, { isPlatform: true }) + expect(keys(routes)).toContain('advisors') + expect(keys(routes)).toContain('logs') + expect(keys(routes)).toContain('integrations') + }) + + it('includes observability on platform when reports are enabled', () => { + const routes = generateOtherRoutes(REF, activeProject, { + isPlatform: true, + showReports: true, + }) + expect(keys(routes)).toContain('observability') + }) + + it('excludes observability on platform when reports are disabled', () => { + const routes = generateOtherRoutes(REF, activeProject, { + isPlatform: true, + showReports: false, + }) + expect(keys(routes)).not.toContain('observability') + }) + + it('excludes observability in self-hosted mode even when reports are enabled', () => { + const routes = generateOtherRoutes(REF, activeProject, { + isPlatform: false, + showReports: true, + }) + expect(keys(routes)).not.toContain('observability') + }) + + it('excludes observability in self-hosted mode when reports are disabled', () => { + const routes = generateOtherRoutes(REF, activeProject, { + isPlatform: false, + showReports: false, + }) + expect(keys(routes)).not.toContain('observability') + }) + + it('includes API Docs when apiDocsSidePanel is enabled', () => { + const routes = generateOtherRoutes(REF, activeProject, { + isPlatform: true, + apiDocsSidePanel: true, + }) + expect(keys(routes)).toContain('api') + }) + + it('excludes API Docs when apiDocsSidePanel is disabled', () => { + const routes = generateOtherRoutes(REF, activeProject, { + isPlatform: true, + apiDocsSidePanel: false, + }) + expect(keys(routes)).not.toContain('api') + }) + + it('links logs to unified logs page when unifiedLogs is enabled', () => { + const routes = generateOtherRoutes(REF, activeProject, { + isPlatform: true, + unifiedLogs: true, + }) + const logsRoute = routes.find((r) => r.key === 'logs') + expect(logsRoute?.link).toBe(`/project/${REF}/logs`) + }) + + it('links logs to explorer page by default', () => { + const routes = generateOtherRoutes(REF, activeProject, { isPlatform: true }) + const logsRoute = routes.find((r) => r.key === 'logs') + expect(logsRoute?.link).toBe(`/project/${REF}/logs/explorer`) + }) + + it('points links to building URL when project is building', () => { + const routes = generateOtherRoutes(REF, buildingProject, { + isPlatform: true, + showReports: true, + }) + const observabilityRoute = routes.find((r) => r.key === 'observability') + expect(observabilityRoute?.link).toBe(`/project/${REF}`) + }) + + it('marks routes as disabled when project is not active', () => { + const routes = generateOtherRoutes(REF, inactiveProject, { isPlatform: true }) + const advisorsRoute = routes.find((r) => r.key === 'advisors') + expect(advisorsRoute?.disabled).toBe(true) + }) +}) + +describe('generateSettingsRoutes', () => { + it('links to general settings on platform', () => { + const routes = generateSettingsRoutes(REF, { isPlatform: true }) + const settingsRoute = routes.find((r) => r.key === 'settings') + expect(settingsRoute?.link).toBe(`/project/${REF}/settings/general`) + }) + + it('links to log-drains settings in self-hosted mode', () => { + const routes = generateSettingsRoutes(REF, { isPlatform: false }) + const settingsRoute = routes.find((r) => r.key === 'settings') + expect(settingsRoute?.link).toBe(`/project/${REF}/settings/log-drains`) + }) + + it('returns a link as false when ref is undefined', () => { + const routes = generateSettingsRoutes(undefined, { isPlatform: true }) + const settingsRoute = routes.find((r) => r.key === 'settings') + expect(settingsRoute?.link).toBe(undefined) + }) +}) diff --git a/apps/studio/components/layouts/Navigation/NavigationBar/NavigationBar.utils.tsx b/apps/studio/components/layouts/Navigation/NavigationBar/NavigationBar.utils.tsx index 92d2d10145..798d65e20d 100644 --- a/apps/studio/components/layouts/Navigation/NavigationBar/NavigationBar.utils.tsx +++ b/apps/studio/components/layouts/Navigation/NavigationBar/NavigationBar.utils.tsx @@ -7,10 +7,43 @@ import { Auth, Database, EdgeFunctions, Realtime, SqlEditor, Storage, TableEdito import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' import { Blocks, FileText, Lightbulb, List, Settings, Telescope } from 'lucide-react' -export const generateToolRoutes = (ref?: string, project?: Project, features?: {}): Route[] => { - const isProjectActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY - const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP - const buildingUrl = `/project/${ref}` +interface RouteContext { + ref?: string + isProjectActive: boolean + isProjectBuilding: boolean + buildingUrl: string +} + +interface ProductFeatures { + auth?: boolean + edgeFunctions?: boolean + storage?: boolean + realtime?: boolean + authOverviewPage?: boolean +} + +interface OtherFeatures { + isPlatform?: boolean + unifiedLogs?: boolean + showReports?: boolean + apiDocsSidePanel?: boolean +} + +interface SettingsFeatures { + isPlatform?: boolean +} + +function getRouteContext(ref?: string, project?: Project): RouteContext { + return { + ref, + isProjectActive: project?.status === PROJECT_STATUS.ACTIVE_HEALTHY, + isProjectBuilding: project?.status === PROJECT_STATUS.COMING_UP, + buildingUrl: `/project/${ref}`, + } +} + +export const generateToolRoutes = (ref?: string, project?: Project): Route[] => { + const { isProjectActive, isProjectBuilding, buildingUrl } = getRouteContext(ref, project) return [ { @@ -34,17 +67,9 @@ export const generateToolRoutes = (ref?: string, project?: Project, features?: { export const generateProductRoutes = ( ref?: string, project?: Project, - features?: { - auth?: boolean - edgeFunctions?: boolean - storage?: boolean - realtime?: boolean - authOverviewPage?: boolean - } + features?: ProductFeatures ): Route[] => { - const isProjectActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY - const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP - const buildingUrl = `/project/${ref}` + const { isProjectActive, isProjectBuilding, buildingUrl } = getRouteContext(ref, project) const authEnabled = features?.auth ?? true const edgeFunctionsEnabled = features?.edgeFunctions ?? true @@ -122,16 +147,14 @@ export const generateProductRoutes = ( export const generateOtherRoutes = ( ref?: string, project?: Project, - features?: { unifiedLogs?: boolean; showReports?: boolean; apiDocsSidePanel?: boolean } + features?: OtherFeatures ): Route[] => { - const isProjectActive = project?.status === PROJECT_STATUS.ACTIVE_HEALTHY - const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP - const buildingUrl = `/project/${ref}` + const { isProjectActive, isProjectBuilding, buildingUrl } = getRouteContext(ref, project) - const { unifiedLogs, showReports, apiDocsSidePanel } = features ?? {} - const unifiedLogsEnabled = unifiedLogs ?? false - const reportsEnabled = showReports ?? true - const apiDocsSidePanelEnabled = apiDocsSidePanel ?? false + const isPlatform = features?.isPlatform ?? IS_PLATFORM + const unifiedLogsEnabled = features?.unifiedLogs ?? false + const reportsEnabled = features?.showReports ?? true + const apiDocsSidePanelEnabled = features?.apiDocsSidePanel ?? false return [ { @@ -142,18 +165,16 @@ export const generateOtherRoutes = ( link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/advisors/security`), }, // Observability is only available on the platform, not for self-hosted/CLI - ...(IS_PLATFORM - ? reportsEnabled - ? [ - { - key: 'observability', - label: 'Observability', - disabled: !isProjectActive, - icon: , - link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/observability`), - }, - ] - : [] + ...(isPlatform && reportsEnabled + ? [ + { + key: 'observability', + label: 'Observability', + disabled: !isProjectActive, + icon: , + link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/observability`), + }, + ] : []), { key: 'logs', @@ -185,7 +206,9 @@ export const generateOtherRoutes = ( ] } -export const generateSettingsRoutes = (ref?: string): Route[] => { +export const generateSettingsRoutes = (ref?: string, features?: SettingsFeatures): Route[] => { + const isPlatform = features?.isPlatform ?? IS_PLATFORM + return [ { key: 'settings', @@ -193,7 +216,7 @@ export const generateSettingsRoutes = (ref?: string): Route[] => { icon: , link: ref && - (IS_PLATFORM ? `/project/${ref}/settings/general` : `/project/${ref}/settings/log-drains`), + (isPlatform ? `/project/${ref}/settings/general` : `/project/${ref}/settings/log-drains`), disabled: false, }, ] diff --git a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.tsx b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.tsx index 96cb647613..2a31a162dd 100644 --- a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.tsx +++ b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.tsx @@ -20,9 +20,10 @@ import { cn, Menu } from 'ui' import { InnerSideBarEmptyPanel } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' -import { useSupamonitorStatus } from '@/components/interfaces/QueryPerformance/hooks/useSupamonitorStatus' +import { generateObservabilityMenuItems } from './ObservabilityMenu.utils' import { ObservabilityMenuItem } from './ObservabilityMenuItem' +import { useSupamonitorStatus } from '@/components/interfaces/QueryPerformance/hooks/useSupamonitorStatus' const ObservabilityMenu = () => { const router = useRouter() @@ -124,87 +125,14 @@ const ObservabilityMenu = () => { const reportMenuItems = getReportMenuItems() - const menuItems = [ - { - title: 'GENERAL', - key: 'general-section', - items: [ - ...(showOverview - ? [ - { - name: 'Overview', - key: 'observability', - url: `/project/${ref}/observability${preservedQueryParams}`, - }, - ] - : []), - ...(isSupamonitorEnabled - ? [ - { - name: 'Query Insights', - key: 'query-insights', - url: `/project/${ref}/observability/query-insights${preservedQueryParams}`, - }, - ] - : [ - { - name: 'Query Performance', - key: 'query-performance', - url: `/project/${ref}/observability/query-performance${preservedQueryParams}`, - }, - ]), - ...(IS_PLATFORM - ? [ - { - name: 'API Gateway', - key: 'api-overview', - url: `/project/${ref}/observability/api-overview${preservedQueryParams}`, - }, - ] - : []), - ], - }, - { - title: 'PRODUCT', - key: 'product-section', - items: [ - { - name: 'Database', - key: 'database', - url: `/project/${ref}/observability/database${preservedQueryParams}`, - }, - { - name: 'Data API', - key: 'postgrest', - url: `/project/${ref}/observability/postgrest${preservedQueryParams}`, - }, - { - name: 'Auth', - key: 'auth', - url: `/project/${ref}/observability/auth${preservedQueryParams}`, - }, - { - name: 'Edge Functions', - key: 'edge-functions', - url: `/project/${ref}/observability/edge-functions${preservedQueryParams}`, - }, - ...(storageSupported - ? [ - { - name: 'Storage', - key: 'storage', - url: `/project/${ref}/observability/storage${preservedQueryParams}`, - }, - ] - : []), - { - name: 'Realtime', - key: 'realtime', - url: `/project/${ref}/observability/realtime${preservedQueryParams}`, - }, - ], - }, - ] + const menuItems = generateObservabilityMenuItems({ + ref, + preservedQueryParams, + showOverview, + isSupamonitorEnabled, + storageSupported, + isPlatform: IS_PLATFORM, + }) return ( diff --git a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.test.tsx b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.test.tsx new file mode 100644 index 0000000000..40211e8b59 --- /dev/null +++ b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.test.tsx @@ -0,0 +1,280 @@ +import { describe, expect, it } from 'vitest' + +import { + generateObservabilityMenuItems, + type ObservabilityMenuSection, +} from './ObservabilityMenu.utils' + +const REF = 'test-project-ref' +const QUERY_PARAMS = '' + +const sectionTitles = (sections: ObservabilityMenuSection[]) => sections.map((s) => s.title) + +const itemKeys = (section: ObservabilityMenuSection | undefined) => + section?.items.map((i) => i.key) ?? [] + +const findSection = (sections: ObservabilityMenuSection[], title: string) => + sections.find((s) => s.title === title) + +describe('generateObservabilityMenuItems - PRODUCT section', () => { + it('includes PRODUCT section on platform', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: true, + }) + + expect(sectionTitles(menu)).toContain('PRODUCT') + const productSection = findSection(menu, 'PRODUCT') + expect(itemKeys(productSection)).toEqual([ + 'database', + 'postgrest', + 'auth', + 'edge-functions', + 'storage', + 'realtime', + ]) + }) + + it('excludes PRODUCT section in self-hosted mode', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: false, + }) + + expect(sectionTitles(menu)).not.toContain('PRODUCT') + expect(menu.length).toBe(1) // Only GENERAL section + }) + + it('excludes Storage from PRODUCT when storageSupported is false', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: false, + isPlatform: true, + }) + + const productSection = findSection(menu, 'PRODUCT') + expect(itemKeys(productSection)).not.toContain('storage') + expect(itemKeys(productSection)).toEqual([ + 'database', + 'postgrest', + 'auth', + 'edge-functions', + 'realtime', + ]) + }) + + it('includes Storage in PRODUCT when storageSupported is true', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: true, + }) + + const productSection = findSection(menu, 'PRODUCT') + expect(itemKeys(productSection)).toContain('storage') + }) +}) + +describe('generateObservabilityMenuItems - GENERAL section', () => { + it('always includes GENERAL section', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: false, + }) + + expect(sectionTitles(menu)).toContain('GENERAL') + }) + + it('includes Query Performance when supamonitor is disabled', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: false, + }) + + const generalSection = findSection(menu, 'GENERAL') + expect(itemKeys(generalSection)).toContain('query-performance') + expect(itemKeys(generalSection)).not.toContain('query-insights') + }) + + it('includes Query Insights when supamonitor is enabled', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: true, + storageSupported: true, + isPlatform: false, + }) + + const generalSection = findSection(menu, 'GENERAL') + expect(itemKeys(generalSection)).toContain('query-insights') + expect(itemKeys(generalSection)).not.toContain('query-performance') + }) + + it('includes Overview when showOverview is true', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: true, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: false, + }) + + const generalSection = findSection(menu, 'GENERAL') + expect(itemKeys(generalSection)).toContain('observability') + }) + + it('excludes Overview when showOverview is false', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: false, + }) + + const generalSection = findSection(menu, 'GENERAL') + expect(itemKeys(generalSection)).not.toContain('observability') + }) + + it('includes API Gateway on platform', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: true, + }) + + const generalSection = findSection(menu, 'GENERAL') + expect(itemKeys(generalSection)).toContain('api-overview') + }) + + it('excludes API Gateway in self-hosted mode', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: false, + }) + + const generalSection = findSection(menu, 'GENERAL') + expect(itemKeys(generalSection)).not.toContain('api-overview') + }) +}) + +describe('generateObservabilityMenuItems - URL construction', () => { + it('constructs correct URLs with preserved query params', () => { + const params = '?its=2024-01-01&ite=2024-01-31' + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: params, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: true, + }) + + const generalSection = findSection(menu, 'GENERAL') + const queryPerfItem = generalSection?.items.find((i) => i.key === 'query-performance') + expect(queryPerfItem?.url).toBe(`/project/${REF}/observability/query-performance${params}`) + + const productSection = findSection(menu, 'PRODUCT') + const databaseItem = productSection?.items.find((i) => i.key === 'database') + expect(databaseItem?.url).toBe(`/project/${REF}/observability/database${params}`) + }) + + it('handles undefined ref', () => { + const menu = generateObservabilityMenuItems({ + ref: undefined, + preservedQueryParams: QUERY_PARAMS, + showOverview: false, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: true, + }) + + const generalSection = findSection(menu, 'GENERAL') + const queryPerfItem = generalSection?.items.find((i) => i.key === 'query-performance') + expect(queryPerfItem?.url).toBe('/project/undefined/observability/query-performance') + }) +}) + +describe('generateObservabilityMenuItems - complete structure', () => { + it('returns only GENERAL in self-hosted with all standard items', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: true, + isSupamonitorEnabled: false, + storageSupported: true, + isPlatform: false, + }) + + expect(menu.length).toBe(1) + expect(menu[0].title).toBe('GENERAL') + expect(itemKeys(menu[0])).toEqual([ + 'observability', // Overview + 'query-performance', + // API Gateway excluded + ]) + }) + + it('returns GENERAL and PRODUCT on platform with all items', () => { + const menu = generateObservabilityMenuItems({ + ref: REF, + preservedQueryParams: QUERY_PARAMS, + showOverview: true, + isSupamonitorEnabled: true, + storageSupported: true, + isPlatform: true, + }) + + expect(menu.length).toBe(2) + expect(sectionTitles(menu)).toEqual(['GENERAL', 'PRODUCT']) + + const generalSection = findSection(menu, 'GENERAL') + expect(itemKeys(generalSection)).toEqual([ + 'observability', // Overview + 'query-insights', // Supamonitor enabled + 'api-overview', // Platform only + ]) + + const productSection = findSection(menu, 'PRODUCT') + expect(itemKeys(productSection)).toEqual([ + 'database', + 'postgrest', + 'auth', + 'edge-functions', + 'storage', + 'realtime', + ]) + }) +}) diff --git a/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.tsx b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.tsx new file mode 100644 index 0000000000..2a814cedec --- /dev/null +++ b/apps/studio/components/layouts/ObservabilityLayout/ObservabilityMenu.utils.tsx @@ -0,0 +1,126 @@ +import { IS_PLATFORM } from 'lib/constants' + +interface ObservabilityMenuItem { + name: string + key: string + url: string +} + +export interface ObservabilityMenuSection { + title: string + key: string + items: ObservabilityMenuItem[] +} + +interface GenerateObservabilityMenuOptions { + ref: string | undefined + preservedQueryParams: string + showOverview: boolean + isSupamonitorEnabled: boolean + storageSupported: boolean + isPlatform?: boolean +} + +export function generateObservabilityMenuItems( + options: GenerateObservabilityMenuOptions +): ObservabilityMenuSection[] { + const { + ref, + preservedQueryParams, + showOverview, + isSupamonitorEnabled, + storageSupported, + isPlatform = IS_PLATFORM, + } = options + + const generalItems: ObservabilityMenuItem[] = [ + ...(showOverview + ? [ + { + name: 'Overview', + key: 'observability', + url: `/project/${ref}/observability${preservedQueryParams}`, + }, + ] + : []), + ...(isSupamonitorEnabled + ? [ + { + name: 'Query Insights', + key: 'query-insights', + url: `/project/${ref}/observability/query-insights${preservedQueryParams}`, + }, + ] + : [ + { + name: 'Query Performance', + key: 'query-performance', + url: `/project/${ref}/observability/query-performance${preservedQueryParams}`, + }, + ]), + ...(isPlatform + ? [ + { + name: 'API Gateway', + key: 'api-overview', + url: `/project/${ref}/observability/api-overview${preservedQueryParams}`, + }, + ] + : []), + ] + + const productItems: ObservabilityMenuItem[] = [ + { + name: 'Database', + key: 'database', + url: `/project/${ref}/observability/database${preservedQueryParams}`, + }, + { + name: 'Data API', + key: 'postgrest', + url: `/project/${ref}/observability/postgrest${preservedQueryParams}`, + }, + { + name: 'Auth', + key: 'auth', + url: `/project/${ref}/observability/auth${preservedQueryParams}`, + }, + { + name: 'Edge Functions', + key: 'edge-functions', + url: `/project/${ref}/observability/edge-functions${preservedQueryParams}`, + }, + ...(storageSupported + ? [ + { + name: 'Storage', + key: 'storage', + url: `/project/${ref}/observability/storage${preservedQueryParams}`, + }, + ] + : []), + { + name: 'Realtime', + key: 'realtime', + url: `/project/${ref}/observability/realtime${preservedQueryParams}`, + }, + ] + + const sections: ObservabilityMenuSection[] = [ + { + title: 'GENERAL', + key: 'general-section', + items: generalItems, + }, + ] + + if (isPlatform) { + sections.push({ + title: 'PRODUCT', + key: 'product-section', + items: productItems, + }) + } + + return sections +}