| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290 |
- // @ts-nocheck
- 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<ChartIntervalKey>(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<ServiceKey, keyof UsageApiCounts> = {
- 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 (
- <div className="space-y-6">
- <div className="flex flex-row justify-between items-center gap-x-2">
- <div className="flex items-start gap-2 heading-section text-foreground-light">
- <span className="text-foreground">{totalRequests.toLocaleString()}</span>
- <span>Total Requests</span>
- </div>
- <ChartIntervalDropdown
- value={interval}
- onChange={(interval) => setInterval(interval as ChartIntervalKey)}
- organizationSlug={organization?.slug}
- dropdownAlign="end"
- tooltipSide="left"
- />
- </div>
- <Row maxColumns={4} minWidth={280}>
- {enabledServices.map((s) => (
- <Card key={s.key} className="mb-0 md:mb-0 h-full flex flex-col h-64">
- <CardHeader className="flex flex-row items-end justify-between gap-2 space-y-0 pb-0 border-b-0">
- <div className="flex items-center gap-2">
- <div className="flex flex-col">
- <CardTitle className="text-xs font-mono uppercase text-foreground-light">
- {s.href ? (
- <Link
- href={s.href}
- onClick={() => {
- 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}
- </Link>
- ) : (
- s.title
- )}
- </CardTitle>
- <span className="text-foreground text-xl">{(s.total || 0).toLocaleString()}</span>
- </div>
- </div>
- </CardHeader>
- <CardContent className="p-card flex-1 h-full overflow-hidden">
- <Loading isFullHeight active={isLoading}>
- <LogsBarChart
- isFullHeight
- data={s.data}
- DateTimeFormat={datetimeFormat}
- onBarClick={handleBarClick(s.route, s.key)}
- hideZeroValues={true}
- chartConfig={{
- error_count: {
- label: 'Errors',
- },
- warning_count: {
- label: 'Warnings',
- },
- ok_count: {
- label: 'Requests',
- },
- }}
- EmptyState={
- <NoDataPlaceholder
- size="small"
- message="No data for selected period"
- isFullHeight
- />
- }
- />
- </Loading>
- </CardContent>
- </Card>
- ))}
- </Row>
- </div>
- )
- }
|