Files
supabase/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx
kemal.earth 7927865669 feat(studio): auth overview usage section (#39207)
* feat: add button to all reports and basic layout

* feat: add last 24 hours indicator

* feat: add active users tile

* feat: working first row of usage stats

* feat: add basic report charts for sign in and sign up

* fix: change prev number to difference from before

* feat: pass time range to view all reports link

* fix: get static numbers to show

* fix: type error

* fix: exclude admin users from active users

* fix: revert change as number stopped working

* feat: testing latest sign ups table

* chore: remove table redoing

* feat: testing with renderers

* feat: revert back to reportchartv2

* feat: make sure sign up and sign in charts are bar by default

* fix: class name for default difference num

* chore: bit of clean up

* chore: tidy up constants file

* chore: remove unused auth renderers

* feat: single query instead of multiple tiny ones

* fix: query fetching

* feat: simplify how the query does averages

* nit: small css bits
2025-10-08 08:52:25 +01:00

205 lines
6.1 KiB
TypeScript

import {
ScaffoldSection,
ScaffoldSectionTitle,
ScaffoldSectionContent,
} from 'components/layouts/Scaffold'
import { Card, CardContent, cn } from 'ui'
import Link from 'next/link'
import { useParams } from 'common'
import { ChevronRight, Loader2 } from 'lucide-react'
import { Reports } from 'icons'
import {
getChangeSign,
getChangeColor,
fetchAllAuthMetrics,
processAllAuthMetrics,
calculatePercentageChange,
} from './OverviewUsage.constants'
import { useQuery } from '@tanstack/react-query'
import { useMemo } from 'react'
import { ReportChartV2 } from 'components/interfaces/Reports/v2/ReportChartV2'
import { createAuthReportConfig } from 'data/reports/v2/auth.config'
import dayjs from 'dayjs'
const StatCard = ({
title,
current,
previous,
loading,
suffix = '',
}: {
title: string
current: number
previous: number
loading: boolean
suffix?: string
}) => {
const changeColor = getChangeColor(previous)
const changeSign = getChangeSign(previous)
const formattedCurrent = suffix === 'ms' ? current.toFixed(2) : current
return (
<Card>
<CardContent
className={cn(
'flex flex-col my-1 gap-1',
loading && 'opacity-50 items-center justify-center min-h-[108px]'
)}
>
{loading ? (
<Loader2 className="size-5 animate-spin text-foreground-light" />
) : (
<>
<h4 className="text-sm text-foreground-lighter font-normal mb-0 truncate">{title}</h4>
<p className="text-xl">{`${formattedCurrent}${suffix}`}</p>
<p className={cn('text-sm text-foreground-lighter', changeColor)}>
{`${changeSign}${previous.toFixed(1)}%`}
</p>
</>
)}
</CardContent>
</Card>
)
}
export const OverviewUsage = () => {
const { ref } = useParams()
const { data: currentData, isLoading: currentLoading } = useQuery({
queryKey: ['auth-metrics', ref, 'current'],
queryFn: () => fetchAllAuthMetrics(ref as string, 'current'),
enabled: !!ref,
})
const { data: previousData, isLoading: previousLoading } = useQuery({
queryKey: ['auth-metrics', ref, 'previous'],
queryFn: () => fetchAllAuthMetrics(ref as string, 'previous'),
enabled: !!ref,
})
const metrics = processAllAuthMetrics(currentData?.result || [], previousData?.result || [])
const isLoading = currentLoading || previousLoading
const activeUsersChange = calculatePercentageChange(
metrics.current.activeUsers,
metrics.previous.activeUsers
)
const passwordResetChange = calculatePercentageChange(
metrics.current.passwordResets,
metrics.previous.passwordResets
)
const signInLatencyChange = calculatePercentageChange(
metrics.current.signInLatency,
metrics.previous.signInLatency
)
const signUpLatencyChange = calculatePercentageChange(
metrics.current.signUpLatency,
metrics.previous.signUpLatency
)
const endDate = dayjs().toISOString()
const startDate = dayjs().subtract(24, 'hour').toISOString()
const signUpChartConfig = useMemo(() => {
const config = createAuthReportConfig({
projectRef: ref as string,
startDate,
endDate,
interval: '1h',
filters: { status_code: null },
})
const chart = config.find((c) => c.id === 'signups')
if (chart) {
return { ...chart, defaultChartStyle: 'bar' }
}
return chart
}, [ref, startDate, endDate])
const signInChartConfig = useMemo(() => {
const config = createAuthReportConfig({
projectRef: ref as string,
startDate,
endDate,
interval: '1h',
filters: { status_code: null },
})
const chart = config.find((c) => c.id === 'sign-in-attempts')
if (chart) {
return { ...chart, defaultChartStyle: 'bar' }
}
return chart
}, [ref, startDate, endDate])
const updateDateRange = (from: string, to: string) => {
console.log('Date range update:', from, to)
}
return (
<ScaffoldSection isFullWidth>
<div className="flex items-center justify-between mb-4">
<ScaffoldSectionTitle>Usage</ScaffoldSectionTitle>
<Link
href={`/project/${ref}/reports/auth?its=${startDate}&ite=${endDate}&isHelper=true&helperText=Last+24+hours`}
className="text-sm text-link inline-flex items-center gap-x-1.5"
>
<Reports size={14} />
<span>View all reports</span>
<ChevronRight size={14} />
</Link>
</div>
<ScaffoldSectionContent className="gap-4">
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
<StatCard
title="Active Users"
current={metrics.current.activeUsers}
previous={activeUsersChange}
loading={isLoading}
/>
<StatCard
title="Password Reset Requests"
current={metrics.current.passwordResets}
previous={passwordResetChange}
loading={isLoading}
/>
<StatCard
title="Sign up Latency"
current={Number(metrics.current.signUpLatency.toFixed(2))}
previous={signUpLatencyChange}
loading={isLoading}
suffix="ms"
/>
<StatCard
title="Sign in Latency"
current={Number(metrics.current.signInLatency.toFixed(2))}
previous={signInLatencyChange}
loading={isLoading}
suffix="ms"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{signUpChartConfig && (
<ReportChartV2
report={signUpChartConfig}
projectRef={ref as string}
startDate={startDate}
endDate={endDate}
interval="1h"
updateDateRange={updateDateRange}
/>
)}
{signInChartConfig && (
<ReportChartV2
report={signInChartConfig}
projectRef={ref as string}
startDate={startDate}
endDate={endDate}
interval="1h"
updateDateRange={updateDateRange}
/>
)}
</div>
</ScaffoldSectionContent>
</ScaffoldSection>
)
}