import { PermissionAction } from '@supabase/shared-types/out/constants' import { useFlag, useParams } from 'common' import { Plus } from 'lucide-react' import { useRouter } from 'next/router' import { parseAsBoolean, useQueryState } from 'nuqs' import { useMemo, useState } from 'react' import { toast } from 'sonner' import { Menu } from 'ui' import { InnerSideBarEmptyPanel } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader' import { generateObservabilityMenuItems } from './ObservabilityMenu.utils' import { ObservabilityMenuItem } from './ObservabilityMenuItem' import { useSupamonitorStatus } from '@/components/interfaces/QueryPerformance/hooks/useSupamonitorStatus' import { CreateReportModal } from '@/components/interfaces/Reports/CreateReportModal' import { UpdateCustomReportModal } from '@/components/interfaces/Reports/UpdateModal' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import { ProductMenu } from '@/components/ui/ProductMenu' import { ProductMenuShortcuts } from '@/components/ui/ProductMenu/ProductMenuShortcuts' import { useContentDeleteMutation } from '@/data/content/content-delete-mutation' import { Content, ContentBase, useContentQuery } from '@/data/content/content-query' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { IS_PLATFORM } from '@/lib/constants' import { useProfile } from '@/lib/profile' import { SHORTCUT_IDS } from '@/state/shortcuts/registry' import { useShortcut } from '@/state/shortcuts/useShortcut' import type { Dashboards } from '@/types' const ObservabilityMenu = () => { const router = useRouter() const { profile } = useProfile() const { ref, id } = useParams() const pageKey = (id || router.pathname.split('/')[4] || 'observability') as string const showOverview = useFlag('observabilityOverview') const { isSupamonitorEnabled } = useSupamonitorStatus() const storageSupported = useIsFeatureEnabled('project_storage:all') const { can: canCreateCustomReport } = useAsyncCheckPermissions( PermissionAction.CREATE, 'user_content', { resource: { type: 'report', owner_id: profile?.id }, subject: { id: profile?.id }, } ) // Preserve date range query parameters when navigating const preservedQueryParams = useMemo(() => { const { its, ite, isHelper, helperText } = router.query const params = new URLSearchParams() if (its && typeof its === 'string') params.set('its', its) if (ite && typeof ite === 'string') params.set('ite', ite) if (isHelper && typeof isHelper === 'string') params.set('isHelper', isHelper) if (helperText && typeof helperText === 'string') params.set('helperText', helperText) const queryString = params.toString() return queryString ? `?${queryString}` : '' }, [router.query]) const { data: content, isPending: isLoading } = useContentQuery({ projectRef: ref, type: 'report', }) const { mutateAsync: deleteReport } = useContentDeleteMutation({ // Toasts are driven by toast.promise in onConfirmDeleteReport. This no-op keeps the hook // from showing its own default error toast, while its optimistic rollback still runs. onError: () => {}, }) const [deleteModalOpen, setDeleteModalOpen] = useState(false) const [showNewReportModal, setShowNewReportModal] = useQueryState( 'newReport', parseAsBoolean.withDefault(false).withOptions({ history: 'push', clearOnDefault: true }) ) const [selectedReportToDelete, setSelectedReportToDelete] = useState() const [selectedReportToUpdate, setSelectedReportToUpdate] = useState() const onConfirmDeleteReport = () => { if (ref === undefined) return console.error('Project ref is required') if (selectedReportToDelete?.id === undefined) return console.error('Report ID is required') const reportId = selectedReportToDelete.id const isViewingDeletedReport = id === reportId setDeleteModalOpen(false) const deletion = deleteReport({ projectRef: ref, ids: [reportId] }) toast.promise(deletion, { loading: 'Deleting report...', success: 'Report deleted', error: (err) => `Failed to delete report: ${err?.message ?? 'Unknown error'}`, }) // Only navigate away when the open report is the one deleted, and only after it // succeeds so a failed delete (which rolls the cache back) doesn't strand the route. deletion .then(() => { if (isViewingDeletedReport) router.push(`/project/${ref}/observability`) }) .catch(() => { // Error is already surfaced by toast.promise; keep the user on the current route. }) } function isReportContent(c: Content): c is ContentBase & { type: 'report' content: Dashboards.Content } { return c.type === 'report' } function getReportMenuItems() { if (!content) return [] const reports = content?.content.filter(isReportContent) const sortedReports = reports?.sort((a, b) => { if (a.name < b.name) { return -1 } if (a.name > b.name) { return 1 } return 0 }) const reportMenuItems = sortedReports.map((r, idx) => ({ id: r.id, name: r.name, description: r.description || '', key: r.id || idx + '-report', url: `/project/${ref}/observability/${r.id}${preservedQueryParams}`, hasDropdownActions: true, report: r, })) return reportMenuItems } const reportMenuItems = getReportMenuItems() const menuItems = generateObservabilityMenuItems({ ref, preservedQueryParams, showOverview, isSupamonitorEnabled, storageSupported, isPlatform: IS_PLATFORM, }) useShortcut( SHORTCUT_IDS.OBSERVABILITY_NEW_REPORT, () => { setShowNewReportModal(true) }, { enabled: IS_PLATFORM && canCreateCustomReport } ) return (
{isLoading ? (
) : (
({ ...item, items: item.items.map((subItem) => ({ ...subItem, items: [] })), }))} /> {IS_PLATFORM && ( <>
Custom Reports {reportMenuItems.length > 0 && ( } disabled={!canCreateCustomReport} className="flex items-center justify-center h-6 w-6 absolute top-0 -right-1" onClick={() => { setShowNewReportModal(true) }} tooltip={{ content: { side: 'bottom', text: !canCreateCustomReport ? 'You need additional permissions to create custom reports' : undefined, }, }} /> )} } /> {reportMenuItems.length > 0 && reportMenuItems.map((item) => ( { setSelectedReportToUpdate(item.report) }} onSelectDelete={() => { setSelectedReportToDelete(item.report) setDeleteModalOpen(true) }} /> ))} {reportMenuItems.length === 0 ? (
} disabled={!canCreateCustomReport} onClick={() => { setShowNewReportModal(true) }} tooltip={{ content: { side: 'bottom', text: !canCreateCustomReport ? 'You need additional permissions to create custom reports' : undefined, }, }} > New custom report } />
) : null}
)} setSelectedReportToUpdate(undefined)} selectedReport={selectedReportToUpdate} initialValues={{ name: selectedReportToUpdate?.name || '', description: selectedReportToUpdate?.description || '', }} /> setDeleteModalOpen(false)} onConfirm={onConfirmDeleteReport} >

Are you sure you want to delete '{selectedReportToDelete?.name}'?

setShowNewReportModal(false)} afterSubmit={() => setShowNewReportModal(false)} />
)}
) } export default ObservabilityMenu