import dayjs from 'dayjs' import { ChevronDown } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' import { useMemo, useState } from 'react' import { useParams } from 'common' import NoDataPlaceholder from 'components/ui/Charts/NoDataPlaceholder' import { InlineLink } from 'components/ui/InlineLink' import { useSendEventMutation } from 'data/telemetry/send-event-mutation' import { useCurrentOrgPlan } from 'hooks/misc/useCurrentOrgPlan' import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import type { ChartIntervals } from 'types' import { Button, Card, CardContent, CardHeader, CardTitle, DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, Loading, Tooltip, TooltipContent, TooltipTrigger, cn, } from 'ui' import { Row } from 'ui-patterns' import { LogsBarChart } from 'ui-patterns/LogsBarChart' import { useServiceStats } from './ProjectUsageSection.utils' import type { StatsLike } from './ProjectUsageSection.utils' import type { LogsBarChartDatum } from './ProjectUsage.metrics' import { sumTotal, sumWarnings, sumErrors, computeSuccessAndNonSuccessRates, computeChangePercent, formatDelta, } from './ProjectUsage.metrics' const LOG_RETENTION = { free: 1, pro: 7, team: 28, enterprise: 90, platform: 1 } const CHART_INTERVALS: ChartIntervals[] = [ { key: '1hr', label: 'Last 60 minutes', startValue: 1, startUnit: 'hour', format: 'MMM D, h:mma', availableIn: ['free', 'pro', 'team', 'enterprise', 'platform'], }, { key: '1day', label: 'Last 24 hours', startValue: 24, startUnit: 'hour', format: 'MMM D, ha', availableIn: ['free', 'pro', 'team', 'enterprise', 'platform'], }, { key: '7day', label: 'Last 7 days', startValue: 7, startUnit: 'day', format: 'MMM D, ha', availableIn: ['pro', 'team', 'enterprise'], }, ] type ChartIntervalKey = '1hr' | '1day' | '7day' type ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime' type ServiceEntry = { key: ServiceKey title: string href?: string route: string enabled: boolean } type ServiceComputed = ServiceEntry & { data: LogsBarChartDatum[] total: number warn: number err: number stats: StatsLike } export const ProjectUsageSection = () => { const router = useRouter() const { ref: projectRef } = useParams() const { data: organization } = useSelectedOrganizationQuery() const { mutate: sendEvent } = useSendEventMutation() const { projectAuthAll: authEnabled, projectStorageAll: storageEnabled } = useIsFeatureEnabled([ 'project_auth:all', 'project_storage:all', ]) const { plan } = useCurrentOrgPlan() const DEFAULT_INTERVAL: ChartIntervalKey = plan?.id === 'free' ? '1hr' : '1day' const [interval, setInterval] = useState(DEFAULT_INTERVAL) 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 statsByService = useServiceStats(projectRef!, interval) const serviceBase: ServiceEntry[] = useMemo( () => [ { key: 'db', title: 'Database requests', href: `/project/${projectRef}/editor`, route: '/logs/postgres-logs', enabled: true, }, { key: 'functions', title: 'Functions requests', route: '/logs/edge-functions-logs', enabled: true, }, { key: 'auth', title: 'Auth requests', href: `/project/${projectRef}/auth/users`, route: '/logs/auth-logs', enabled: authEnabled, }, { key: 'storage', title: 'Storage requests', href: `/project/${projectRef}/storage/buckets`, route: '/logs/storage-logs', enabled: storageEnabled, }, { key: 'realtime', title: 'Realtime requests', route: '/logs/realtime-logs', enabled: true, }, ], [projectRef, authEnabled, storageEnabled] ) const services: ServiceComputed[] = useMemo( () => serviceBase.map((s) => { const currentStats = statsByService[s.key].current const data = currentStats.eventChartData const total = sumTotal(data) const warn = sumWarnings(data) const err = sumErrors(data) return { ...s, stats: currentStats, data, total, warn, err } }), [serviceBase, statsByService] ) const isLoading = services.some((s) => s.stats.isLoading) const handleBarClick = (logRoute: string, serviceKey: ServiceKey) => (datum: any) => { if (!datum?.timestamp) return const datumTimestamp = dayjs(datum.timestamp).toISOString() const start = dayjs(datumTimestamp).subtract(1, 'minute').toISOString() const end = dayjs(datumTimestamp).add(1, 'minute').toISOString() const queryParams = new URLSearchParams({ iso_timestamp_start: start, iso_timestamp_end: end, }) router.push(`/project/${projectRef}${logRoute}?${queryParams.toString()}`) if (projectRef && organization?.slug) { sendEvent({ action: 'home_project_usage_chart_clicked', properties: { service_type: serviceKey, bar_timestamp: datum.timestamp, }, groups: { project: projectRef, organization: organization.slug, }, }) } } const enabledServices = services.filter((s) => s.enabled) const totalRequests = enabledServices.reduce((sum, s) => sum + (s.total || 0), 0) const totalErrors = enabledServices.reduce((sum, s) => sum + (s.err || 0), 0) const totalWarnings = enabledServices.reduce((sum, s) => sum + (s.warn || 0), 0) const { successRate, nonSuccessRate } = computeSuccessAndNonSuccessRates( totalRequests, totalWarnings, totalErrors ) const prevServiceTotals = useMemo( () => serviceBase.map((s) => { const previousStats = statsByService[s.key].previous const data = previousStats.eventChartData return { enabled: s.enabled, total: sumTotal(data), } }), [serviceBase, statsByService] ) const enabledPrev = prevServiceTotals.filter((s) => s.enabled) const prevTotalRequests = enabledPrev.reduce((sum, s) => sum + (s.total || 0), 0) const totalRequestsChangePct = computeChangePercent(totalRequests, prevTotalRequests) const totalDeltaClass = totalRequestsChangePct >= 0 ? 'text-brand-link' : 'text-destructive' const nonSuccessClass = nonSuccessRate > 0 ? 'text-destructive' : 'text-brand-link' return (
{totalRequests.toLocaleString()} Total Requests {formatDelta(totalRequestsChangePct)}
{successRate.toFixed(1)}% Success Rate {formatDelta(nonSuccessRate)}
setInterval(interval as ChartIntervalKey)} > {CHART_INTERVALS.map((i) => { const disabled = !i.availableIn?.includes(plan?.id || 'free') if (disabled) { const retentionDuration = LOG_RETENTION[plan?.id ?? 'free'] return ( {i.label}

{plan?.name} plan only includes up to {retentionDuration} day {retentionDuration > 1 ? 's' : ''} of log retention

Upgrade your plan {' '} to increase log retention and view statistics for the{' '} {i.label.toLowerCase()}

) } else { return ( {i.label} ) } })}
{enabledServices.map((s) => (
{s.href ? ( { if (projectRef && organization?.slug) { sendEvent({ action: 'home_project_usage_service_clicked', properties: { service_type: s.key, total_requests: s.total || 0, error_count: s.err || 0, }, groups: { project: projectRef, organization: organization.slug, }, }) } }} > {s.title} ) : ( s.title )}
{(s.total || 0).toLocaleString()}
Warn
{(s.warn || 0).toLocaleString()}
Err
{(s.err || 0).toLocaleString()}
} /> ))}
) }