import { useParams } from 'common' import dayjs from 'dayjs' import Link from 'next/link' import { useRouter } from 'next/router' import { useEffect, useMemo, useState } from 'react' import { Card, CardContent, CardHeader, CardTitle, Loading } from 'ui' import { Row } from 'ui-patterns' import { LogsBarChart } from 'ui-patterns/LogsBarChart' import NoDataPlaceholder from '@/components/ui/Charts/NoDataPlaceholder' import { ChartIntervalDropdown } from '@/components/ui/Logs/ChartIntervalDropdown' import { CHART_INTERVALS } from '@/components/ui/Logs/logs.utils' import { UsageApiCounts, useProjectLogStatsQuery } from '@/data/analytics/project-log-stats-query' import { useSendEventMutation } from '@/data/telemetry/send-event-mutation' import { useFillTimeseriesSorted } from '@/hooks/analytics/useFillTimeseriesSorted' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' type LogsBarChartDatum = { timestamp: string error_count: number ok_count: number warning_count: number } type ChartIntervalKey = '1hr' | '1day' | '7day' type ServiceKey = 'db' | 'auth' | 'storage' | 'realtime' type ServiceEntry = { key: ServiceKey title: string href?: string route: string enabled: boolean } type ServiceComputed = ServiceEntry & { data: LogsBarChartDatum[] total: number isLoading: boolean error: unknown | null } 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 { getEntitlementMax } = useCheckEntitlements('log.retention_days') const retentionDays = getEntitlementMax() const DEFAULT_INTERVAL: ChartIntervalKey = retentionDays !== undefined && retentionDays < 7 ? '1hr' : '1day' const [interval, setInterval] = useState(DEFAULT_INTERVAL) useEffect(() => { setInterval(retentionDays !== undefined && retentionDays < 7 ? '1hr' : '1day') }, [retentionDays]) 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]) // Use V1 data fetching const { data: logStatsData, isPending: isLoading } = useProjectLogStatsQuery({ projectRef, interval, }) // Calculate date range for gap filling const startDateLocal = dayjs().subtract( selectedInterval.startValue, selectedInterval.startUnit as dayjs.ManipulateType ) const endDateLocal = dayjs() // Fill gaps in timeseries data const { data: filledCharts } = useFillTimeseriesSorted({ data: logStatsData?.result ?? [], timestampKey: 'timestamp', valueKey: [ 'total_auth_requests', 'total_rest_requests', 'total_storage_requests', 'total_realtime_requests', ], defaultValue: 0, startDate: startDateLocal.toISOString(), endDate: endDateLocal.toISOString(), minPointsToFill: 5, }) const serviceBase: ServiceEntry[] = useMemo( () => [ { key: 'db', title: 'Database requests', href: `/project/${projectRef}/editor`, route: '/logs/postgres-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) => { // Map service keys to V1 data field names const dataKeyMap: Record = { db: 'total_rest_requests', auth: 'total_auth_requests', storage: 'total_storage_requests', realtime: 'total_realtime_requests', } const dataKey = dataKeyMap[s.key] // Transform V1 data to LogsBarChart format // Since V1 doesn't have error/warning breakdown, we show everything as "ok" const transformedData: LogsBarChartDatum[] = (filledCharts || []).map((item) => ({ timestamp: item.timestamp, error_count: 0, warning_count: 0, ok_count: Number(item[dataKey]) || 0, })) // Calculate total from filled data const total = transformedData.reduce((sum, item) => sum + item.ok_count, 0) return { ...s, data: transformedData, total, isLoading, error: null, } }), [serviceBase, filledCharts, isLoading] ) const handleBarClick = (logRoute: string, serviceKey: ServiceKey) => (datum: LogsBarChartDatum) => { 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) return (
{totalRequests.toLocaleString()} Total Requests
setInterval(interval as ChartIntervalKey)} organizationSlug={organization?.slug} dropdownAlign="end" tooltipSide="left" />
{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, }, groups: { project: projectRef, organization: organization.slug, }, }) } }} > {s.title} ) : ( s.title )} {(s.total || 0).toLocaleString()}
} />
))}
) }