mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 11:55:05 +03:00
bring back old charts
This commit is contained in:
1 parent
87408ed9d9
commit
9384fbf90b
1 file changed
+77
-76
@@ -7,26 +7,26 @@ import { Card, CardContent, CardHeader, CardTitle, Loading } from 'ui'
|
||||
import { Row } from 'ui-patterns'
|
||||
import { LogsBarChart } from 'ui-patterns/LogsBarChart'
|
||||
|
||||
import { normalizeChartBuckets } from './ChartDataTransform.utils'
|
||||
import type { LogsBarChartDatum } from './ProjectUsage.metrics'
|
||||
import {
|
||||
computeSuccessAndNonSuccessRates,
|
||||
sumErrors,
|
||||
sumTotal,
|
||||
sumWarnings,
|
||||
} from './ProjectUsage.metrics'
|
||||
import { useServiceHealthMetrics } from '@/components/interfaces/Observability/useServiceHealthMetrics'
|
||||
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 ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime'
|
||||
|
||||
type ServiceEntry = {
|
||||
key: ServiceKey
|
||||
@@ -39,6 +39,8 @@ type ServiceEntry = {
|
||||
type ServiceComputed = ServiceEntry & {
|
||||
data: LogsBarChartDatum[]
|
||||
total: number
|
||||
warn: number
|
||||
err: number
|
||||
isLoading: boolean
|
||||
error: unknown | null
|
||||
}
|
||||
@@ -70,34 +72,11 @@ export const ProjectUsageSection = () => {
|
||||
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 {
|
||||
services: healthServices,
|
||||
isLoading: isHealthLoading,
|
||||
endDate,
|
||||
} = useServiceHealthMetrics(projectRef as string, interval, 0)
|
||||
|
||||
const serviceBase: ServiceEntry[] = useMemo(
|
||||
() => [
|
||||
@@ -108,6 +87,12 @@ export const ProjectUsageSection = () => {
|
||||
route: '/logs/postgres-logs',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
key: 'functions',
|
||||
title: 'Functions requests',
|
||||
route: '/logs/edge-functions-logs',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
key: 'auth',
|
||||
title: 'Auth requests',
|
||||
@@ -135,51 +120,35 @@ export const ProjectUsageSection = () => {
|
||||
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)
|
||||
const healthData = healthServices[s.key]
|
||||
const data = normalizeChartBuckets(healthData.eventChartData, interval, new Date(endDate))
|
||||
const total = sumTotal(data)
|
||||
const warn = sumWarnings(data)
|
||||
const err = sumErrors(data)
|
||||
|
||||
return {
|
||||
...s,
|
||||
data: transformedData,
|
||||
data,
|
||||
total,
|
||||
isLoading,
|
||||
error: null,
|
||||
warn,
|
||||
err,
|
||||
isLoading: healthData.isLoading,
|
||||
error: healthData.error,
|
||||
}
|
||||
}),
|
||||
[serviceBase, filledCharts, isLoading]
|
||||
[serviceBase, healthServices, interval, endDate]
|
||||
)
|
||||
|
||||
const isLoading = isHealthLoading
|
||||
|
||||
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,
|
||||
})
|
||||
const unit = interval === '1hr' ? 'minute' : interval === '1day' ? 'hour' : 'day'
|
||||
const start = dayjs(datum.timestamp).toISOString()
|
||||
const end = dayjs(datum.timestamp).add(1, unit).toISOString()
|
||||
const queryParams = new URLSearchParams({ its: start, ite: end })
|
||||
|
||||
router.push(`/project/${projectRef}${logRoute}?${queryParams.toString()}`)
|
||||
|
||||
@@ -200,13 +169,28 @@ export const ProjectUsageSection = () => {
|
||||
|
||||
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 } = computeSuccessAndNonSuccessRates(
|
||||
totalRequests,
|
||||
totalWarnings,
|
||||
totalErrors
|
||||
)
|
||||
|
||||
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 className="flex flex-col md:flex-row md:items-center md:gap-6">
|
||||
<div className="flex items-start gap-2 heading-section text-foreground-light">
|
||||
<span className="text-foreground">{totalRequests.toLocaleString()}</span>
|
||||
<span>Total Requests</span>
|
||||
</div>
|
||||
<div className="flex items-start gap-2 heading-section text-foreground-light">
|
||||
<span className="text-foreground">
|
||||
{successRate === 100 ? '100' : successRate.toFixed(1)}%
|
||||
</span>
|
||||
<span>Success Rate</span>
|
||||
</div>
|
||||
</div>
|
||||
<ChartIntervalDropdown
|
||||
value={interval}
|
||||
@@ -216,13 +200,13 @@ export const ProjectUsageSection = () => {
|
||||
tooltipSide="left"
|
||||
/>
|
||||
</div>
|
||||
<Row maxColumns={4} minWidth={280}>
|
||||
<Row maxColumns={3} 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">
|
||||
<CardTitle className="text-foreground-light">
|
||||
{s.href ? (
|
||||
<Link
|
||||
href={s.href}
|
||||
@@ -233,6 +217,7 @@ export const ProjectUsageSection = () => {
|
||||
properties: {
|
||||
service_type: s.key,
|
||||
total_requests: s.total || 0,
|
||||
error_count: s.err || 0,
|
||||
},
|
||||
groups: {
|
||||
project: projectRef,
|
||||
@@ -251,6 +236,22 @@ export const ProjectUsageSection = () => {
|
||||
<span className="text-foreground text-xl">{(s.total || 0).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-end gap-4 text-foreground-light">
|
||||
<div className="flex flex-col items-end">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1.5 h-1.5 bg-warning rounded-full" />
|
||||
<span className="heading-meta">Warn</span>
|
||||
</div>
|
||||
<span className="text-foreground text-xl">{(s.warn || 0).toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-end">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-1.5 h-1.5 bg-destructive rounded-full" />
|
||||
<span className="heading-meta">Err</span>
|
||||
</div>
|
||||
<span className="text-foreground text-xl">{(s.err || 0).toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-card flex-1 h-full overflow-hidden">
|
||||
<Loading isFullHeight active={isLoading}>
|
||||
|
||||
Reference in new issue
Block a user