mirror of
https://github.com/supabase/supabase.git
synced 2026-06-20 18:26:00 +08:00
When the dashboard hits a DB connection timeout, users currently see a
raw error message with no
path forward. This PR adds an inline troubleshooting system that detects
known error types and
surfaces contextual next steps — restart the DB, read the docs, or debug
with AI.
## Changes
- New ErrorDisplay component (packages/ui-patterns) — styled error card
with a title, monospace error
block, optional troubleshooting slot, and a "Contact support" link that
always renders. Accepts
typed supportFormParams to pre-fill the support form.
- Error classification in handleError (data/fetchers.ts) — on every API
error, the message is tested
against ERROR_PATTERNS. If matched, handleError throws a typed subclass
(ConnectionTimeoutError
extends ResponseError) instead of a plain ResponseError. Stack traces
now show the exact error
class. All existing instanceof ResponseError checks continue to work.
- ErrorMatcher component — reads errorType from the thrown class
instance, does an O(1) lookup into
ERROR_MAPPINGS, and renders the matching troubleshooting accordion as
children of ErrorDisplay.
Falls back to plain ErrorDisplay for unclassified errors.
- Connection timeout mapping — first error type wired up, with three
troubleshooting steps: restart
the database, link to the docs, and "Debug with AI" (opens the AI
assistant sidebar with a
pre-filled prompt).
- Telemetry — three new typed events track when the troubleshooter is
shown, when accordion steps are
toggled, and which CTAs are clicked.
## Adding a new error type
1. Add a class to types/api-errors.ts
2. Add { pattern, ErrorClass } to data/error-patterns.ts
3. Create a troubleshooting component in errorMappings/
4. Add an entry to error-mappings.tsx
180 lines
4.9 KiB
TypeScript
180 lines
4.9 KiB
TypeScript
'use client'
|
|
|
|
import { AiAssistantDropdown } from 'components/ui/AiAssistantDropdown'
|
|
import { useTrack } from 'lib/telemetry/track'
|
|
import { ExternalLink } from 'lucide-react'
|
|
import { useState } from 'react'
|
|
import {
|
|
AccordionContent_Shadcn_ as AccordionContent,
|
|
AccordionItem_Shadcn_ as AccordionItem,
|
|
AccordionTrigger_Shadcn_ as AccordionTrigger,
|
|
Button,
|
|
} from 'ui'
|
|
|
|
import { RestartProjectDialog } from './RestartProjectDialog'
|
|
|
|
interface StepTriggerProps {
|
|
number: number
|
|
title: string
|
|
}
|
|
|
|
function StepTrigger({ number, title }: StepTriggerProps) {
|
|
return (
|
|
<AccordionTrigger className="py-3 hover:no-underline">
|
|
<div className="flex items-center gap-2.5">
|
|
<span className="flex-shrink-0 w-6 h-6 border border-button-hover text-foreground font-mono tabular-nums bg-button rounded-md text-xs font-medium flex items-center justify-center">
|
|
{number}
|
|
</span>
|
|
<span className="text-sm font-medium text-foreground text-left">{title}</span>
|
|
</div>
|
|
</AccordionTrigger>
|
|
)
|
|
}
|
|
|
|
interface RestartDatabaseTroubleshootingSectionProps {
|
|
number: number
|
|
errorType: string
|
|
/** Override the restart handler. If not provided, opens the restart dialog internally. */
|
|
onRestartProject?: () => void
|
|
}
|
|
|
|
export function RestartDatabaseTroubleshootingSection({
|
|
number,
|
|
errorType,
|
|
onRestartProject,
|
|
}: RestartDatabaseTroubleshootingSectionProps) {
|
|
const track = useTrack()
|
|
const [showDialog, setShowDialog] = useState(false)
|
|
|
|
const handleClick = () => {
|
|
track('inline_error_troubleshooter_action_clicked', {
|
|
errorType,
|
|
ctaType: 'restart_db',
|
|
})
|
|
if (onRestartProject) {
|
|
onRestartProject()
|
|
} else {
|
|
setShowDialog(true)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<AccordionItem
|
|
value={`step-${number}`}
|
|
className="border-b border-default last:border-b-0 px-3 py-2"
|
|
>
|
|
<StepTrigger number={number} title="Try restarting your project" />
|
|
<AccordionContent className="pt-1">
|
|
<div className="px-2">
|
|
<p className="text-sm text-foreground-light mb-3">
|
|
Restarting your project can help resolve timeout errors or stale connections.
|
|
</p>
|
|
<Button type="default" size="tiny" onClick={handleClick}>
|
|
Restart project
|
|
</Button>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
|
|
<RestartProjectDialog
|
|
visible={showDialog}
|
|
onClose={() => setShowDialog(false)}
|
|
restartType="database"
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
|
|
interface TroubleshootingGuideSectionProps {
|
|
number: number
|
|
errorType: string
|
|
href: string
|
|
title?: string
|
|
description?: string
|
|
}
|
|
|
|
export function TroubleshootingGuideSection({
|
|
number,
|
|
errorType,
|
|
href,
|
|
title = 'Try our troubleshooting guide',
|
|
description,
|
|
}: TroubleshootingGuideSectionProps) {
|
|
const track = useTrack()
|
|
|
|
return (
|
|
<AccordionItem
|
|
value={`step-${number}`}
|
|
className="border-b border-default last:border-b-0 px-3 py-2"
|
|
>
|
|
<StepTrigger number={number} title={title} />
|
|
<AccordionContent className="pt-1">
|
|
<div className="px-2">
|
|
{description && <p className="text-sm text-foreground-light mb-3">{description}</p>}
|
|
<Button
|
|
asChild
|
|
type="default"
|
|
size="tiny"
|
|
onClick={() =>
|
|
track('inline_error_troubleshooter_action_clicked', {
|
|
errorType,
|
|
ctaType: 'troubleshooting_guide',
|
|
})
|
|
}
|
|
iconRight={<ExternalLink />}
|
|
>
|
|
<a href={href} target="_blank" rel="noopener noreferrer">
|
|
View troubleshooting guide
|
|
</a>
|
|
</Button>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
)
|
|
}
|
|
|
|
interface FixWithAITroubleshootingSectionProps {
|
|
number: number
|
|
errorType: string
|
|
description?: string
|
|
onDebugWithAI?: (prompt: string) => void
|
|
buildPrompt: () => string
|
|
}
|
|
|
|
export function FixWithAITroubleshootingSection({
|
|
number,
|
|
errorType,
|
|
description = 'Let our AI assistant help diagnose and suggest solutions.',
|
|
onDebugWithAI,
|
|
buildPrompt,
|
|
}: FixWithAITroubleshootingSectionProps) {
|
|
const track = useTrack()
|
|
|
|
return (
|
|
<AccordionItem
|
|
value={`step-${number}`}
|
|
className="border-b border-default last:border-b-0 px-3 py-2"
|
|
>
|
|
<StepTrigger number={number} title="Debug with AI" />
|
|
<AccordionContent className="pt-1">
|
|
<div className="px-2">
|
|
<p className="text-sm text-foreground-light mb-3">{description}</p>
|
|
<AiAssistantDropdown
|
|
label="Debug with AI"
|
|
buildPrompt={buildPrompt}
|
|
onOpenAssistant={() => {
|
|
track('inline_error_troubleshooter_action_clicked', {
|
|
errorType,
|
|
ctaType: 'ask_ai',
|
|
})
|
|
onDebugWithAI?.(buildPrompt())
|
|
}}
|
|
size="tiny"
|
|
/>
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
)
|
|
}
|