import { useQuery } from '@tanstack/react-query' import { useParams } from 'common' import dayjs from 'dayjs' import { BarChart2, ChevronRight, ExternalLink, Telescope } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' import { AiIconAnimation, Button, Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { StatusCode } from 'ui-patterns' import { Chart, ChartActions, ChartCard, ChartContent, ChartEmptyState, ChartHeader, ChartLoadingState, ChartMetric, ChartTitle, } from 'ui-patterns/Chart' import { PageSection, PageSectionContent, PageSectionMeta, PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' import { AuthErrorCodeRow, fetchTopAuthErrorCodes, fetchTopResponseErrors, ResponseErrorRow, } from './OverviewErrors.constants' import { OverviewTable } from './OverviewTable' import { AuthMetricsResponse, calculatePercentageChange, getApiSuccessRates, getAuthSuccessRates, getMetricValues, } from './OverviewUsage.constants' import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider' import AlertError from '@/components/ui/AlertError' import { ErrorCodeTooltip } from '@/components/ui/ErrorCodeTooltip/ErrorCodeTooltip' import { Service } from '@/data/graphql/graphql' import { useAiAssistantStateSnapshot } from '@/state/ai-assistant-state' import { useSidebarManagerSnapshot } from '@/state/sidebar-manager-state' const StatCard = ({ title, current, previous, loading, suffix = '', href, tooltip, }: { title: string current: number previous: number loading: boolean suffix?: string invert?: boolean href?: string tooltip?: string }) => { const router = useRouter() const formattedCurrent = suffix === 'ms' ? current.toFixed(2) : suffix === '%' ? current.toFixed(1) : Math.round(current).toLocaleString() // const signChar = previous > 0 ? '+' : previous < 0 ? '-' : '' const actions = [ { label: 'Go to Auth Report', icon: , onClick: href ? () => router.push(href) : undefined, }, ] return ( ) } const LogsLink = ({ href }: { href: string }) => ( Go to Logs ) function isResponseErrorRow(row: unknown): row is ResponseErrorRow { if (!row || typeof row !== 'object') return false const r = row as Record return ( typeof r.method === 'string' && typeof r.path === 'string' && typeof r.status_code === 'number' && typeof r.count === 'number' ) } function isAuthErrorCodeRow(row: unknown): row is AuthErrorCodeRow { if (!row || typeof row !== 'object') return false const r = row as Record return typeof r.error_code === 'string' && typeof r.count === 'number' } interface OverviewMetricsProps { metrics?: AuthMetricsResponse isLoading: boolean error: unknown } export const OverviewMetrics = ({ metrics, isLoading, error }: OverviewMetricsProps) => { const { ref } = useParams() const endDate = dayjs().toISOString() const startDate = dayjs().subtract(24, 'hour').toISOString() const aiSnap = useAiAssistantStateSnapshot() const { openSidebar } = useSidebarManagerSnapshot() const { current: activeUsersCurrent, previous: activeUsersPrevious } = getMetricValues( metrics, 'activeUsers' ) const { current: signUpsCurrent, previous: signUpsPrevious } = getMetricValues( metrics, 'signUpCount' ) const activeUsersChange = calculatePercentageChange(activeUsersCurrent, activeUsersPrevious) const signUpsChange = calculatePercentageChange(signUpsCurrent, signUpsPrevious) const { current: apiSuccessRateCurrent, previous: apiSuccessRatePrevious } = getApiSuccessRates(metrics) const { current: authSuccessRateCurrent, previous: authSuccessRatePrevious } = getAuthSuccessRates(metrics) const apiSuccessRateChange = calculatePercentageChange( apiSuccessRateCurrent, apiSuccessRatePrevious ) const authSuccessRateChange = calculatePercentageChange( authSuccessRateCurrent, authSuccessRatePrevious ) const { data: respErrData, isPending: isLoadingResp } = useQuery({ queryKey: ['auth-overview', ref, 'top-response-errors'], queryFn: () => fetchTopResponseErrors(ref as string), enabled: !!ref, }) const { data: codeErrData, isPending: isLoadingCodes } = useQuery({ queryKey: ['auth-overview', ref, 'top-auth-error-codes'], queryFn: () => fetchTopAuthErrorCodes(ref as string), enabled: !!ref, }) const responseErrors: ResponseErrorRow[] = Array.isArray(respErrData?.result) ? (respErrData?.result as unknown[]).filter(isResponseErrorRow) : [] const errorCodes: AuthErrorCodeRow[] = Array.isArray(codeErrData?.result) ? (codeErrData?.result as unknown[]).filter(isAuthErrorCodeRow) : [] const errorCodesActions = [ { label: 'Ask Assistant about Error Codes', icon: , onClick: () => { openSidebar(SIDEBAR_KEYS.AI_ASSISTANT) aiSnap.newChat({ name: 'Auth Help', initialInput: `Can you explain to me what the authentication error codes mean?`, }) }, }, ] return ( <> {!!error && ( )}
Usage Go to observability
Monitoring
Auth API Errors } title="No data to show" description="It may take up to 24 hours for data to refresh" />
} loadingState={
} > isLoading={isLoadingResp} data={responseErrors} columns={[ { key: 'request', header: 'Request', className: 'w-auto pr-0!', render: (row) => { return }, }, { key: 'path', header: 'Path', className: 'w-full', render: (row) => ( {row.path} ), }, { key: 'count', header: 'Count', className: 'text-right shrink-0 ml-auto justify-end', render: (row) => (
{row.count}
), }, ]} /> Auth Server Errors } title="No data to show" description="It may take up to 24 hours for data to refresh" /> } loadingState={
} > isLoading={isLoadingCodes} data={errorCodes} columns={[ { key: 'error_code', header: 'Error code', className: 'w-full', render: (row) => ( {row.error_code} ), }, { key: 'count', header: 'Count', className: 'text-right', render: (row) => (
{row.count}
), }, ]} />
) }