From a4d7bc6231eb9ab7b2f8d0b9dbbda6772c42bb91 Mon Sep 17 00:00:00 2001 From: "kemal.earth" <606977+kemaldotearth@users.noreply.github.com> Date: Fri, 10 Oct 2025 20:52:15 +0100 Subject: [PATCH] feat(studio): auth overview monitoring section (#39327) * feat: begin monitoring with compute metrics * chore: simplify monitoring constants * chore: something got added * feat: add database connections chart * make it success rate instead of error rate * add new metrics * hide monitoring * rm unused * rm * rm --------- Co-authored-by: Jordi Enric --- .../Auth/Overview/OverviewMonitoring.tsx | 16 -- .../Auth/Overview/OverviewUsage.constants.ts | 60 ++++++-- .../Auth/Overview/OverviewUsage.tsx | 143 +++++++----------- .../pages/project/[ref]/auth/overview.tsx | 4 +- 4 files changed, 98 insertions(+), 125 deletions(-) delete mode 100644 apps/studio/components/interfaces/Auth/Overview/OverviewMonitoring.tsx diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewMonitoring.tsx b/apps/studio/components/interfaces/Auth/Overview/OverviewMonitoring.tsx deleted file mode 100644 index 098ff90beec..00000000000 --- a/apps/studio/components/interfaces/Auth/Overview/OverviewMonitoring.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { ScaffoldSectionTitle, ScaffoldSection } from 'components/layouts/Scaffold' -import { Card } from 'ui' - -export const OverviewMonitoring = () => { - return ( - - Monitoring -
- - - - -
-
- ) -} diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.constants.ts b/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.constants.ts index caad4e20030..0e858125dfa 100644 --- a/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.constants.ts +++ b/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.constants.ts @@ -43,16 +43,39 @@ export const AUTH_COMBINED_QUERY = () => ` select 'signUpCount' as metric, cast(count(case when action = 'user_signedup' then 1 else null end) as float64) from base - + union all - select 'signInLatency' as metric, - coalesce(round(avg(case when action = 'login' then duration_ns else null end) / 1000000, 2), 0) - from base - + select 'apiTotalRequests' as metric, + cast(count(*) as float64) as value + from edge_logs + cross join unnest(metadata) as m + cross join unnest(m.request) as request + cross join unnest(m.response) as response + cross join unnest(response.headers) as h + where path like '%auth/v1%' + union all - select 'signUpLatency' as metric, - coalesce(round(avg(case when action = 'user_signedup' then duration_ns else null end) / 1000000, 2), 0) - from base + select 'apiErrorRequests' as metric, + cast(count(*) as float64) as value + from edge_logs + cross join unnest(metadata) as m + cross join unnest(m.request) as request + cross join unnest(m.response) as response + cross join unnest(response.headers) as h + where path like '%auth/v1%' + and response.status_code >= 400 and response.status_code <= 599 + + union all + select 'authErrorRequests' as metric, + cast(count(*) as float64) as value + from edge_logs + cross join unnest(metadata) as m + cross join unnest(m.request) as request + cross join unnest(m.response) as response + cross join unnest(response.headers) as h + where path like '%auth/v1%' + and response.status_code >= 400 and response.status_code <= 599 + and h.x_sb_error_code is not null ` export const fetchAllAuthMetrics = async (projectRef: string, period: 'current' | 'previous') => { @@ -66,22 +89,31 @@ export const fetchAllAuthMetrics = async (projectRef: string, period: 'current' export const processAllAuthMetrics = (currentData: any[], previousData: any[]) => { const processData = (data: any[]) => { if (!data || !Array.isArray(data)) { - return { activeUsers: 0, passwordResets: 0, signInLatency: 0, signUpLatency: 0 } + return { activeUsers: 0, signUps: 0, apiErrorRate: 0, authErrorRate: 0 } } const result = data.reduce( (acc, row) => { const { metric, value } = row if (metric === 'activeUsers') acc.activeUsers = value || 0 - if (metric === 'passwordResetRequests') acc.passwordResets = value || 0 - if (metric === 'signInLatency') acc.signInLatency = value || 0 - if (metric === 'signUpLatency') acc.signUpLatency = value || 0 + if (metric === 'signUpCount') acc.signUps = value || 0 + if (metric === 'apiTotalRequests') acc._apiTotal = value || 0 + if (metric === 'apiErrorRequests') acc._apiErrors = value || 0 + if (metric === 'authErrorRequests') acc._authErrors = value || 0 return acc }, - { activeUsers: 0, passwordResets: 0, signInLatency: 0, signUpLatency: 0 } + { activeUsers: 0, signUps: 0, _apiTotal: 0, _apiErrors: 0, _authErrors: 0 } as any ) - return result + const apiErrorRate = result._apiTotal > 0 ? (result._apiErrors / result._apiTotal) * 100 : 0 + const authErrorRate = result._apiTotal > 0 ? (result._authErrors / result._apiTotal) * 100 : 0 + + return { + activeUsers: result.activeUsers, + signUps: result.signUps, + apiErrorRate, + authErrorRate, + } } return { diff --git a/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx b/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx index dac8b7b29ab..2ddc6452fa8 100644 --- a/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx +++ b/apps/studio/components/interfaces/Auth/Overview/OverviewUsage.tsx @@ -9,17 +9,14 @@ 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' +import { ArrowUpIcon, ArrowDownIcon } from 'lucide-react' const StatCard = ({ title, @@ -27,19 +24,32 @@ const StatCard = ({ previous, loading, suffix = '', + invert = false, + href, }: { title: string current: number previous: number loading: boolean suffix?: string + invert?: boolean + href?: string }) => { - const changeColor = getChangeColor(previous) - const changeSign = getChangeSign(previous) - const formattedCurrent = suffix === 'ms' ? current.toFixed(2) : current + const isZeroChange = previous === 0 + const changeColor = isZeroChange + ? 'text-foreground-lighter' + : invert + ? previous >= 0 + ? 'text-destructive' + : 'text-brand' + : getChangeColor(previous) + const formattedCurrent = + suffix === 'ms' ? current.toFixed(2) : suffix === '%' ? current.toFixed(1) : Math.round(current) + const ArrowIcon = previous >= 0 ? ArrowUpIcon : ArrowDownIcon + const signChar = previous > 0 ? '+' : previous < 0 ? '-' : '' - return ( - + const Inner = ( +

{title}

-

{`${formattedCurrent}${suffix}`}

-

- {`${changeSign}${previous.toFixed(1)}%`} -

+

{`${formattedCurrent}${suffix}`}

+
+ {!isZeroChange && } + {`${signChar}${Math.abs(previous).toFixed(1)}%`} +
)}
) + + return href ? {Inner} : Inner } export const OverviewUsage = () => { @@ -84,55 +97,13 @@ export const OverviewUsage = () => { 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 signUpsChange = calculatePercentageChange(metrics.current.signUps, metrics.previous.signUps) 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) - } + // No charts on overview; keep date range for link only return ( @@ -150,54 +121,42 @@ export const OverviewUsage = () => {
-
- {signUpChartConfig && ( - - )} - {signInChartConfig && ( - - )} -
) diff --git a/apps/studio/pages/project/[ref]/auth/overview.tsx b/apps/studio/pages/project/[ref]/auth/overview.tsx index 86f8f300fe1..0a42fba0eb9 100644 --- a/apps/studio/pages/project/[ref]/auth/overview.tsx +++ b/apps/studio/pages/project/[ref]/auth/overview.tsx @@ -5,7 +5,6 @@ import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import { PageLayout } from 'components/layouts/PageLayout/PageLayout' import { DocsButton } from 'components/ui/DocsButton' import { DOCS_URL } from 'lib/constants' -import { OverviewMonitoring } from 'components/interfaces/Auth/Overview/OverviewMonitoring' import { OverviewUsage } from 'components/interfaces/Auth/Overview/OverviewUsage' import { OverviewLearnMore } from 'components/interfaces/Auth/Overview/OverviewLearnMore' import { useRouter } from 'next/router' @@ -32,7 +31,6 @@ const AuthOverview: NextPageWithLayout = () => { return (
-
@@ -48,7 +46,7 @@ AuthOverview.getLayout = (page) => ( secondaryActions={
- All reports Last 24 hours + Last 24 hours