ruvector/studio/components/interfaces/HomeNew/ProjectUsageSection.utils.ts
rUv 814f595995 feat(studio): Add complete RuVector Studio application
Major additions:
- Complete Next.js studio application with 1600+ components
- Docker support (Dockerfile.combined, docker-compose.yml)
- GCP deployment documentation and benchmarks
- SQL benchmark scripts for performance testing
- Sentry integration for monitoring
- Comprehensive test suite and mocks

Studio features:
- Dashboard and admin interfaces
- Data visualization components
- Authentication and user management
- API integration with RuVector backend
- Static data and public assets

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 23:04:48 +00:00

121 lines
3.6 KiB
TypeScript

import { useProjectMetricsQuery } from 'data/analytics/project-metrics-query'
import type { ProjectMetricsRow } from 'data/analytics/project-metrics-query'
type ServiceKey = 'db' | 'functions' | 'auth' | 'storage' | 'realtime'
export type StatsLike = {
error: unknown | null
isLoading: boolean
eventChartData: Array<{
timestamp: string
ok_count: number
warning_count: number
error_count: number
}>
refresh: () => void
}
type ServiceStatsMap = Record<
ServiceKey,
{
current: StatsLike
previous: StatsLike
}
>
/**
* Transform backend project metrics into a UI-friendly structure with consistent
* loading/error/refresh state per service.
*
* Why this exists
* - Backend returns flat rows: one record per (time_window, service, bucket_ts).
* - UI needs per-service objects with two series (current/previous) to drive 5 cards and compute per-service totals.
* - Charts expect ISO string timestamps; backend gives TIMESTAMP (coming to client as microseconds). We convert to ISO.
* - We also need stable sorting and consistent empty arrays when a series has no points.
* - We attach loading/error/refresh per service to keep UI simple.
*/
export const toServiceStatsMap = (args: {
data?: ProjectMetricsRow[]
isLoading: boolean
error?: unknown
onRefresh: () => void
}): ServiceStatsMap => {
const { data, isLoading, error, onRefresh } = args
const base = {
error: error ?? null,
isLoading,
refresh: () => {
onRefresh()
},
}
const empty: StatsLike = { ...base, eventChartData: [] }
const grouped: Record<
ServiceKey,
{ current: StatsLike['eventChartData']; previous: StatsLike['eventChartData'] }
> = {
db: { current: [], previous: [] },
functions: { current: [], previous: [] },
auth: { current: [], previous: [] },
storage: { current: [], previous: [] },
realtime: { current: [], previous: [] },
}
const toIso = (microseconds: number) => new Date(microseconds / 1000).toISOString()
for (const r of data ?? []) {
const bucket = grouped[r.service as ServiceKey]
const target = r.time_window === 'current' ? bucket.current : bucket.previous
target.push({
timestamp: toIso(r.timestamp),
ok_count: r.ok_count,
warning_count: r.warning_count,
error_count: r.error_count,
})
}
const byTime = (a: { timestamp: string }, b: { timestamp: string }) =>
Date.parse(a.timestamp) - Date.parse(b.timestamp)
for (const key of Object.keys(grouped) as ServiceKey[]) {
grouped[key].current.sort(byTime)
grouped[key].previous.sort(byTime)
}
const toStats = (rows: StatsLike['eventChartData'] | undefined): StatsLike =>
rows ? { ...base, eventChartData: rows } : empty
return {
db: { current: toStats(grouped.db.current), previous: toStats(grouped.db.previous) },
functions: {
current: toStats(grouped.functions.current),
previous: toStats(grouped.functions.previous),
},
auth: { current: toStats(grouped.auth.current), previous: toStats(grouped.auth.previous) },
storage: {
current: toStats(grouped.storage.current),
previous: toStats(grouped.storage.previous),
},
realtime: {
current: toStats(grouped.realtime.current),
previous: toStats(grouped.realtime.previous),
},
}
}
export const useServiceStats = (
projectRef: string,
interval: '1hr' | '1day' | '7day'
): ServiceStatsMap => {
const { data, isLoading, error, refetch } = useProjectMetricsQuery({ projectRef, interval })
return toServiceStatsMap({
data,
isLoading,
error,
onRefresh: () => {
void refetch()
},
})
}