From ac9707b89e0b59f1b4486805b2d304c7fd689fd3 Mon Sep 17 00:00:00 2001 From: Konstantin Wohlwend Date: Sat, 18 Apr 2026 19:08:52 -0700 Subject: [PATCH] Update metrics endpoint to no longer trigger global error boundary on failure --- .../(overview)/charts-section-with-data.tsx | 193 ------------------ .../(overview)/globe-section-with-data.tsx | 32 ++- .../(overview)/metrics-loading.tsx | 49 ++++- .../[projectId]/(overview)/metrics-page.tsx | 15 +- .../[projectId]/users/page-client.tsx | 25 ++- 5 files changed, 106 insertions(+), 208 deletions(-) delete mode 100644 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx deleted file mode 100644 index ff0e78c69..000000000 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx +++ /dev/null @@ -1,193 +0,0 @@ -'use client'; - -import { useRouter } from "@/components/router"; -import { UserAvatar } from '@stackframe/stack'; -import { fromNow } from '@stackframe/stack-shared/dist/utils/dates'; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, - Typography, -} from "@/components/ui"; -import { stackAppInternalsSymbol } from "@/lib/stack-app-internals"; -import { useAdminApp, useProjectId } from '../use-admin-app'; -import { DonutChartDisplay, LineChartDisplay, LineChartDisplayConfig } from './line-chart'; - -const dailySignUpsConfig = { - name: 'Daily Sign-ups', - description: 'User registration over the last 30 days', - chart: { - activity: { - label: "Activity", - theme: { - light: "hsl(221, 83%, 53%)", // Bright blue for light mode - dark: "hsl(217, 91%, 60%)", // Lighter blue for dark mode - }, - }, - } -} satisfies LineChartDisplayConfig; - -const dauConfig = { - name: 'Daily Active Users', - description: 'Number of unique users that were active over the last 30 days', - chart: { - activity: { - label: "Activity", - theme: { - light: "hsl(142, 76%, 36%)", // Bright green for light mode - dark: "hsl(142, 71%, 45%)", // Lighter green for dark mode - }, - }, - } -} satisfies LineChartDisplayConfig; - -export function ChartsSectionWithData({ includeAnonymous }: { includeAnonymous: boolean }) { - const adminApp = useAdminApp(); - const projectId = useProjectId(); - const router = useRouter(); - const data = (adminApp as any)[stackAppInternalsSymbol].useMetrics(includeAnonymous); - - return ( -
- {/* Charts Grid */} -
- - -
- - {/* Activity Grid */} -
- {/* Recent Sign Ups - 2/3 width */} - - -
- - Activity - - - Recent Sign Ups - - - Users who signed up most recently. - -
-
- - {data.recently_registered.length === 0 ? ( -
- - No recent sign ups - -
- ) : ( -
- {data.recently_registered.map((user: any) => ( - - ))} -
- )} -
-
- - {/* Auth Methods Donut */} - -
- - {/* Recently Active - Full Width Grid */} - - -
- - Activity - - - Recently Active - - - Users who were active most recently. - -
-
- - {data.recently_active.length === 0 ? ( -
- - No recent activity - -
- ) : ( -
- {data.recently_active.map((user: any) => ( - - ))} -
- )} -
-
-
- ); -} diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe-section-with-data.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe-section-with-data.tsx index a7b867b29..5f488badb 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe-section-with-data.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe-section-with-data.tsx @@ -1,17 +1,37 @@ 'use client'; -import { ErrorBoundary } from '@sentry/nextjs'; import { stackAppInternalsSymbol } from "@/lib/stack-app-internals"; +import { captureError } from "@stackframe/stack-shared/dist/utils/errors"; +import { ErrorBoundary } from "next/dist/client/components/error-boundary"; import { useAdminApp } from '../use-admin-app'; import { GlobeSection } from './globe'; -export function GlobeSectionWithData({ includeAnonymous }: { includeAnonymous: boolean }) { - const adminApp = useAdminApp(); - const data = (adminApp as any)[stackAppInternalsSymbol].useMetrics(includeAnonymous); +const capturedGlobeErrors = new WeakSet(); +function captureGlobeErrorOnce(error: Error) { + if (capturedGlobeErrors.has(error)) { + return; + } + capturedGlobeErrors.add(error); + captureError("metrics-globe-error-boundary", error); +} + +export function GlobeSectionWithData({ includeAnonymous }: { includeAnonymous: boolean }) { return ( - Error initializing globe visualization. Please try updating your browser or enabling WebGL.}> - + + ); } + +function GlobeErrorComponent(props: { error: Error }) { + captureGlobeErrorOnce(props.error); + return
Error initializing globe visualization. Please try updating your browser or enabling WebGL.
; +} + +function GlobeSectionWithMetrics({ includeAnonymous }: { includeAnonymous: boolean }) { + const adminApp = useAdminApp(); + const data = (adminApp as any)[stackAppInternalsSymbol].useMetrics(includeAnonymous); + + return ; +} diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-loading.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-loading.tsx index 21d31b434..960f91a45 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-loading.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-loading.tsx @@ -1,7 +1,18 @@ 'use client'; -import { Card, CardContent, cn } from '@/components/ui'; -import { CircleNotchIcon } from '@phosphor-icons/react'; +import { Button, Card, CardContent, cn } from '@/components/ui'; +import { captureError } from '@stackframe/stack-shared/dist/utils/errors'; +import { ArrowClockwiseIcon, CircleNotchIcon, WarningCircleIcon } from '@phosphor-icons/react'; + +const capturedMetricsErrors = new WeakSet(); + +function captureMetricsErrorOnce(error: Error) { + if (capturedMetricsErrors.has(error)) { + return; + } + capturedMetricsErrors.add(error); + captureError("metrics-page-error-boundary", error); +} export function MetricsLoadingFallback({ className }: { className?: string }) { return ( @@ -16,3 +27,37 @@ export function MetricsLoadingFallback({ className }: { className?: string }) { ); } + +export function MetricsErrorFallback({ + error, + onRetryAction, + className, +}: { + error: Error, + onRetryAction?: () => void, + className?: string, +}) { + captureMetricsErrorOnce(error); + + const errorMessage = error.message.trim().length > 0 + ? error.message + : "An unexpected error occurred while loading project metrics."; + + return ( + + + +
+

Failed to load metrics

+

{errorMessage}

+
+ {onRetryAction != null ? ( + + ) : null} +
+
+ ); +} diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx index 50f6be81b..6dd654de5 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx @@ -18,6 +18,7 @@ import { useUser } from "@stackframe/stack"; import { ALL_APPS } from "@stackframe/stack-shared/dist/apps/apps-config"; import { typedEntries } from "@stackframe/stack-shared/dist/utils/objects"; import { stringCompare } from "@stackframe/stack-shared/dist/utils/strings"; +import { ErrorBoundary } from "next/dist/client/components/error-boundary"; import { Suspense, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, type ElementType } from "react"; import { PageLayout } from "../page-layout"; import { useAdminApp, useProjectId } from "../use-admin-app"; @@ -42,7 +43,7 @@ import { VisitorsHoverChart, VisitorsHoverDataPoint, } from "./line-chart"; -import { MetricsLoadingFallback } from "./metrics-loading"; +import { MetricsErrorFallback, MetricsLoadingFallback } from "./metrics-loading"; const dailySignUpsConfig: LineChartDisplayConfig = { name: 'Daily Sign-Ups', @@ -932,13 +933,19 @@ export default function MetricsPage(props: { toSetup: () => void }) { fullBleed wrapHeaderInCard > - }> - - + + }> + + + ); } +function MetricsErrorComponent(props: { error: Error, reset?: () => void }) { + return ; +} + function MetricsContent({ includeAnonymous, timeRange, diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx index ced4e6e87..cce06df4e 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx @@ -6,12 +6,24 @@ import { StyledLink } from "@/components/link"; import { Alert, Button, SimpleTooltip, Skeleton } from "@/components/ui"; import { UserDialog } from "@/components/user-dialog"; import { stackAppInternalsSymbol } from "@/lib/stack-app-internals"; +import { captureError } from "@stackframe/stack-shared/dist/utils/errors"; import { ArrowsClockwiseIcon, DownloadSimpleIcon } from "@phosphor-icons/react"; +import { ErrorBoundary } from "next/dist/client/components/error-boundary"; import { Suspense, useState } from "react"; import { AppEnabledGuard } from "../app-enabled-guard"; import { PageLayout } from "../page-layout"; import { useAdminApp } from "../use-admin-app"; +const capturedUsersMetricsErrors = new WeakSet(); + +function captureUsersMetricsErrorOnce(error: Error) { + if (capturedUsersMetricsErrors.has(error)) { + return; + } + capturedUsersMetricsErrors.add(error); + captureError("users-total-metrics-error-boundary", error); +} + function TotalUsersDisplay() { const stackAdminApp = useAdminApp(); const metrics = (stackAdminApp as any)[stackAppInternalsSymbol].useMetrics(false); @@ -38,6 +50,11 @@ function TotalUsersDisplay() { ); } +function TotalUsersErrorComponent(props: { error: Error }) { + captureUsersMetricsErrorOnce(props.error); + return <>Unavailable; +} + export default function PageClient() { const stackAdminApp = useAdminApp(); const firstUser = (stackAdminApp as any).useUsers({ limit: 1 }); @@ -60,9 +77,11 @@ export default function PageClient() { title="Users" description={<> Total:{" "} - Calculating}> - - + + Calculating}> + + + } actions={