mirror of
https://github.com/supabase/supabase.git
synced 2026-09-22 13:37:53 +08:00
b0e31be89a
Removes Studio code that nothing imports, as reported by knip. First PR in a stack of three: this one is pure deletions, #49720 removes the unused dependencies, #49721 upgrades knip and adds the CI gate so this doesn't accumulate again. Every file was verified with a repo-wide grep for its basename, exported symbols, and string/dynamic imports before deletion — none are reachable via `next/dynamic`, a barrel file, or a config. **Removed:** - `Billing/Usage/UsageWarningAlerts/{CPU,RAM,DiskIOBandwidth}Warnings.tsx` (whole directory) - `DataWarehouse/FormFooterChangeBadge.tsx` (whole directory) - `Database/Replication/ReplicationDiagram/EmptyReplicationDiagram.tsx` - `Integrations/Vercel/OrganizationPicker.tsx` - `QueryInsights/QueryInsightsTable/QueryInsightsTableRow.tsx` - `hooks/misc/useTrackExperimentExposure.ts` - `data/ai/{parse-client-code,sql-policy}-mutation.ts`, `data/misc/parse-query-mutation.ts`, `data/database/table-check-rls-mutation.ts` - `data/notifications/notifications-v2-{archive-all-mutation,summary-query}.ts` + their two now-unused keys in `notifications/keys.ts` (`listV2` kept) - `data/platform-apps/platform-app-{update,signing-key-delete}-mutation.ts` - `DateTimeFormats.DATE_ONLY` and the unused `Notebooks.{MarkdownCell,LogCell,ChartConfig}` types **Changed:** - `ReportPadding` no longer has a duplicate default export; its 9 default importers (observability pages) now use the named export Not removed: `CONSTRAINT_TYPE`'s unused members mirror the closed set of `pg_constraint.contype` values, so they're documentation rather than dead code — suppressed narrowly in #49721's knip config instead. ## To test - `pnpm --filter studio run typecheck` and `lint:ratchet` pass - Observability pages (`/project/[ref]/observability/*`) still render with padding — they're the only code touched, via the `ReportPadding` import change - Notifications popover still loads and marks-as-read (the removed keys weren't used for invalidation) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **Removed Features** - Removed CPU, memory, and disk usage warning alerts. - Removed the Vercel organization picker and empty replication diagram. - Removed query insights row actions and several SQL assistance tools. - Removed notification summary and archive-all capabilities. - Removed platform app update and signing-key deletion actions. - Removed the form change-count badge and experiment exposure tracking. - **Refactor** - Updated observability reports to use the revised report layout export. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Alaister Young <10985857+alaister@users.noreply.github.com>
249 lines
9.2 KiB
TypeScript
249 lines
9.2 KiB
TypeScript
import { useQueryClient } from '@tanstack/react-query'
|
|
import { useParams } from 'common'
|
|
import dayjs from 'dayjs'
|
|
import { RefreshCw } from 'lucide-react'
|
|
import { useRouter } from 'next/router'
|
|
import { useCallback, useMemo, useState } from 'react'
|
|
import { Badge, Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui'
|
|
|
|
import { useUnifiedLogsPreview } from '../App/FeaturePreview/FeaturePreviewContext'
|
|
import { DatabaseInfrastructureSection } from './DatabaseInfrastructureSection'
|
|
import { OBSERVABILITY_DOCS_HREFS } from './Observability.constants'
|
|
import { useObservabilityOverviewData } from './ObservabilityOverview.utils'
|
|
import { ObservabilityOverviewFooter } from './ObservabilityOverviewFooter'
|
|
import { ServiceHealthTable } from './ServiceHealthTable'
|
|
import { useSlowQueriesCount } from './useSlowQueriesCount'
|
|
import ReportHeader from '@/components/interfaces/Reports/ReportHeader'
|
|
import { ReportPadding } from '@/components/interfaces/Reports/ReportPadding'
|
|
import {
|
|
buildUnifiedLogsUrl,
|
|
type UnifiedLogType,
|
|
} from '@/components/interfaces/UnifiedLogs/UnifiedLogs.utils'
|
|
import { DocsButton } from '@/components/ui/DocsButton'
|
|
import { ChartIntervalDropdown } from '@/components/ui/Logs/ChartIntervalDropdown'
|
|
import { CHART_INTERVALS } from '@/components/ui/Logs/logs.utils'
|
|
import { ShortcutTooltip } from '@/components/ui/ShortcutTooltip'
|
|
import { useIsDataApiEnabled } from '@/hooks/misc/useIsDataApiEnabled'
|
|
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
|
|
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
|
import { SHORTCUT_IDS } from '@/state/shortcuts/registry'
|
|
import { useShortcut } from '@/state/shortcuts/useShortcut'
|
|
|
|
const REPORT_TITLE = 'Overview'
|
|
|
|
type ChartIntervalKey = '1hr' | '1day' | '7day'
|
|
|
|
export const ObservabilityOverview = () => {
|
|
const router = useRouter()
|
|
const { ref: projectRef } = useParams()
|
|
const { data: organization } = useSelectedOrganizationQuery()
|
|
const queryClient = useQueryClient()
|
|
|
|
const { isEnabled: isUnifiedLogsEnabled } = useUnifiedLogsPreview()
|
|
const { projectStorageAll: storageSupported } = useIsFeatureEnabled(['project_storage:all'])
|
|
const { isEnabled: isDataApiEnabled } = useIsDataApiEnabled({ projectRef })
|
|
|
|
const DEFAULT_INTERVAL: ChartIntervalKey = '1day'
|
|
const [interval, setInterval] = useState<ChartIntervalKey>(DEFAULT_INTERVAL)
|
|
const [refreshKey, setRefreshKey] = useState(0)
|
|
const [showIntervalDropdown, setShowIntervalDropdown] = useState(false)
|
|
|
|
const selectedInterval = CHART_INTERVALS.find((i) => i.key === interval) || CHART_INTERVALS[1]
|
|
|
|
const { datetimeFormat } = useMemo(() => {
|
|
const format = selectedInterval.format || 'MMM D, ha'
|
|
return { datetimeFormat: format }
|
|
}, [selectedInterval])
|
|
|
|
const overviewData = useObservabilityOverviewData(projectRef!, interval, refreshKey)
|
|
|
|
const { slowQueriesCount, isLoading: slowQueriesLoading } = useSlowQueriesCount(
|
|
projectRef,
|
|
refreshKey
|
|
)
|
|
|
|
const handleRefresh = useCallback(() => {
|
|
setRefreshKey((prev) => prev + 1)
|
|
queryClient.invalidateQueries({ queryKey: ['projects', projectRef, 'service-health'] })
|
|
queryClient.invalidateQueries({ queryKey: ['project-metrics'] })
|
|
queryClient.invalidateQueries({ queryKey: ['infra-monitoring'] })
|
|
queryClient.invalidateQueries({ queryKey: ['max-connections'] })
|
|
}, [queryClient, projectRef])
|
|
|
|
useShortcut(SHORTCUT_IDS.OBSERVABILITY_REFRESH, handleRefresh)
|
|
useShortcut(SHORTCUT_IDS.OBSERVABILITY_TOGGLE_DATE_PICKER, () => {
|
|
setShowIntervalDropdown((open) => !open)
|
|
})
|
|
|
|
const getLogsUrl = useCallback(
|
|
(logType: UnifiedLogType, legacyLogsUrl: string) =>
|
|
isUnifiedLogsEnabled
|
|
? buildUnifiedLogsUrl({ projectRef: projectRef!, logType })
|
|
: `/project/${projectRef}${legacyLogsUrl}`,
|
|
[projectRef, isUnifiedLogsEnabled]
|
|
)
|
|
|
|
const serviceBase = useMemo(
|
|
() => [
|
|
{
|
|
key: 'data_api' as const,
|
|
name: 'API Gateway',
|
|
reportUrl: undefined,
|
|
logType: 'edge' as const,
|
|
logsUrl: getLogsUrl('edge', '/logs/edge-logs'),
|
|
enabled: isDataApiEnabled,
|
|
hasReport: false,
|
|
},
|
|
{
|
|
key: 'db' as const,
|
|
name: 'Database',
|
|
reportUrl: `/project/${projectRef}/observability/database`,
|
|
logType: 'postgres' as const,
|
|
logsUrl: getLogsUrl('postgres', '/logs/postgres-logs'),
|
|
enabled: true,
|
|
hasReport: true,
|
|
},
|
|
{
|
|
key: 'postgrest' as const,
|
|
name: 'PostgREST',
|
|
reportUrl: `/project/${projectRef}/observability/postgrest`,
|
|
logType: 'postgrest' as const,
|
|
logsUrl: getLogsUrl('postgrest', '/logs/postgrest-logs'),
|
|
enabled: true,
|
|
hasReport: true,
|
|
},
|
|
{
|
|
key: 'auth' as const,
|
|
name: 'Auth',
|
|
reportUrl: `/project/${projectRef}/observability/auth`,
|
|
logType: 'auth' as const,
|
|
logsUrl: getLogsUrl('auth', '/logs/auth-logs'),
|
|
enabled: true,
|
|
hasReport: true,
|
|
},
|
|
{
|
|
key: 'functions' as const,
|
|
name: 'Edge Functions',
|
|
reportUrl: `/project/${projectRef}/observability/edge-functions`,
|
|
logType: 'edge function' as const,
|
|
logsUrl: getLogsUrl('edge function', '/logs/edge-functions-logs'),
|
|
enabled: true,
|
|
hasReport: true,
|
|
},
|
|
{
|
|
key: 'storage' as const,
|
|
name: 'Storage',
|
|
reportUrl: `/project/${projectRef}/observability/storage`,
|
|
logType: 'storage' as const,
|
|
logsUrl: getLogsUrl('storage', '/logs/storage-logs'),
|
|
enabled: storageSupported,
|
|
hasReport: true,
|
|
},
|
|
{
|
|
key: 'realtime' as const,
|
|
name: 'Realtime',
|
|
reportUrl: `/project/${projectRef}/observability/realtime`,
|
|
logType: 'realtime' as const,
|
|
logsUrl: getLogsUrl('realtime', '/logs/realtime-logs'),
|
|
enabled: true,
|
|
hasReport: true,
|
|
},
|
|
],
|
|
[projectRef, storageSupported, isDataApiEnabled, getLogsUrl]
|
|
)
|
|
|
|
const enabledServices = serviceBase.filter((s) => s.enabled)
|
|
|
|
const dbServiceData = overviewData.services.db
|
|
|
|
// Navigate to the log view scoped to the clicked bar's bucket window
|
|
const handleBarClick = useCallback(
|
|
(service: { logType: UnifiedLogType; logsUrl: string }) => (datum: any) => {
|
|
if (!datum?.timestamp) return
|
|
|
|
// datum.timestamp is already the UTC-truncated bucket boundary from timestamp_trunc(),
|
|
// so use it directly to avoid local-timezone startOf() misalignment (e.g. UTC+5:30).
|
|
const unit = interval === '1hr' ? 'minute' : 'hour'
|
|
const start = datum.timestamp
|
|
const end = dayjs.utc(datum.timestamp).add(1, unit).toISOString()
|
|
|
|
if (isUnifiedLogsEnabled) {
|
|
router.push(
|
|
buildUnifiedLogsUrl({ projectRef: projectRef!, logType: service.logType, start, end })
|
|
)
|
|
} else {
|
|
const queryParams = new URLSearchParams({ its: start, ite: end })
|
|
const separator = service.logsUrl.includes('?') ? '&' : '?'
|
|
router.push(`${service.logsUrl}${separator}${queryParams.toString()}`)
|
|
}
|
|
},
|
|
[router, interval, isUnifiedLogsEnabled, projectRef]
|
|
)
|
|
|
|
return (
|
|
<ReportPadding>
|
|
<div className="flex flex-row justify-between items-center">
|
|
<div className="flex items-center gap-3">
|
|
<ReportHeader title={REPORT_TITLE} />
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<Badge variant="warning">Beta</Badge>
|
|
</TooltipTrigger>
|
|
<TooltipContent>
|
|
<p>This page is subject to change</p>
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<DocsButton href={OBSERVABILITY_DOCS_HREFS.overview} topic={REPORT_TITLE} />
|
|
<ShortcutTooltip
|
|
shortcutId={SHORTCUT_IDS.OBSERVABILITY_REFRESH}
|
|
label="Refresh report"
|
|
side="bottom"
|
|
>
|
|
<Button variant="outline" icon={<RefreshCw size={14} />} onClick={handleRefresh}>
|
|
Refresh
|
|
</Button>
|
|
</ShortcutTooltip>
|
|
<ChartIntervalDropdown
|
|
value={interval}
|
|
onChange={(interval) => setInterval(interval as ChartIntervalKey)}
|
|
organizationSlug={organization?.slug}
|
|
dropdownAlign="end"
|
|
tooltipSide="left"
|
|
open={showIntervalDropdown}
|
|
onOpenChange={setShowIntervalDropdown}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-12 mt-8">
|
|
<DatabaseInfrastructureSection
|
|
interval={interval}
|
|
refreshKey={refreshKey}
|
|
dbErrorRate={dbServiceData.errorRate}
|
|
isLoading={dbServiceData.isLoading}
|
|
slowQueriesCount={slowQueriesCount}
|
|
slowQueriesLoading={slowQueriesLoading}
|
|
/>
|
|
|
|
<ServiceHealthTable
|
|
services={enabledServices.map((service) => ({
|
|
key: service.key,
|
|
name: service.name,
|
|
description: '',
|
|
reportUrl: service.hasReport ? service.reportUrl : undefined,
|
|
logType: service.logType,
|
|
logsUrl: service.logsUrl,
|
|
}))}
|
|
serviceData={overviewData.services}
|
|
onBarClick={handleBarClick}
|
|
datetimeFormat={datetimeFormat}
|
|
/>
|
|
</div>
|
|
|
|
<ObservabilityOverviewFooter />
|
|
</ReportPadding>
|
|
)
|
|
}
|