fix(analytics): stop UMAMI_NOT_CONFIGURED returning 409 — caused dev server hangs
Root cause of recurring dev server hangs:
/api/v1/website-analytics threw CodedError('UMAMI_NOT_CONFIGURED') which
rendered as HTTP 409. React Query default-retries on 4xx (we set retry=1
globally), so every page render fired the umami queries → 409 →
retry → 409. Each request queried system_settings to resolve umami
credentials. Six analytics widgets on the /website-analytics page +
two on the dashboard glance tile × 2 (initial + retry) = 16 system_settings
queries on first paint. Combined with React Query refetching on mount,
the postgres pool (max=20) saturated and the server appeared hung.
Fix: return 200 with `{ data: null, notConfigured: true }` instead of
4xx. Not-configured is a steady empty state, not a transient error —
no retry loop. Updated WebsiteGlanceTile (hides itself) and
WebsiteAnalyticsShell (renders configure-umami CTA) to check the new
notConfigured flag.
Also includes from in-flight work: package.json dev script binds
0.0.0.0 so iPhone on LAN can reach the dev server, and BrandedAuthShell
uses fixed/inset-0 + flex to lock the login surface to the viewport so
iOS Safari doesn't rubber-band-scroll the card.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,10 +25,13 @@ import type {
|
||||
interface MetricResponse<T> {
|
||||
metric: string;
|
||||
range: DateRange;
|
||||
data: T;
|
||||
/** Surfaced when Umami isn't configured for the port - UI can render a
|
||||
* "set up Umami" empty state instead of a generic loading spinner. */
|
||||
error?: string;
|
||||
data: T | null;
|
||||
/** True when Umami isn't configured for the port. Surfaced as a 200
|
||||
* response (not 4xx) so React Query doesn't infinitely retry — that
|
||||
* retry loop previously saturated the postgres pool and hung the dev
|
||||
* server. UI consumers should check this flag and render the
|
||||
* "set up Umami" empty state. */
|
||||
notConfigured?: boolean;
|
||||
}
|
||||
|
||||
function rangeToQuery(range: DateRange): string {
|
||||
|
||||
@@ -45,11 +45,11 @@ export function WebsiteAnalyticsShell() {
|
||||
const topReferrers = useUmamiTopReferrers(range);
|
||||
const topCountries = useUmamiTopCountries(range);
|
||||
|
||||
// Any of the queries returning `error: 'umami_not_configured'` means we
|
||||
// need to prompt the operator to set credentials. Single empty state
|
||||
// covers all widgets so the page doesn't show six loading spinners that
|
||||
// never resolve.
|
||||
const notConfigured = stats.data?.error === 'umami_not_configured';
|
||||
// API surfaces `notConfigured: true` on a 200 response (not 4xx) so
|
||||
// React Query doesn't infinite-retry — that retry loop saturated the
|
||||
// postgres pool and stalled the dev server. Single empty state covers
|
||||
// all widgets so the page doesn't show six loading spinners forever.
|
||||
const notConfigured = stats.data?.notConfigured === true;
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
|
||||
Reference in New Issue
Block a user