-
Notifications
You must be signed in to change notification settings - Fork 12
Fix: show friendly error when backend is unreachable #291
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a5971e4
93eeb06
28452dc
dcdefc9
fddf2c2
e6868c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -90,11 +90,48 @@ type ConfirmAction = | |
| | { type: "bulk_move"; itemIds: number[]; targetTrackId: number } | ||
| | { type: "clear_track_memory"; trackId: number } | ||
|
|
||
| type UpstreamErrorBody = { | ||
| detail?: string | ||
| error?: string | ||
| } | ||
|
|
||
| function toFriendlyErrorMessage(status: number, rawText: string): string | null { | ||
| if (!rawText) return null | ||
|
|
||
| let parsed: UpstreamErrorBody | null = null | ||
| try { | ||
| parsed = JSON.parse(rawText) as UpstreamErrorBody | ||
| } catch { | ||
| parsed = null | ||
| } | ||
|
|
||
| const detail = parsed && typeof parsed.detail === "string" ? parsed.detail : undefined | ||
|
|
||
| if (detail && (detail.includes("Upstream API unreachable") || detail.includes("Upstream API timed out"))) { | ||
| if (detail.includes("timed out")) { | ||
| return "Unable to connect to service (request timed out). Please ensure the backend is running. Please try again." | ||
| } | ||
| return "Unable to connect to service. Please ensure the backend is running." | ||
| } | ||
|
|
||
| // Fallback: surface backend-provided detail when available | ||
| if (detail) return detail | ||
|
|
||
| return null | ||
| } | ||
|
|
||
| async function fetchJson<T>(url: string, init?: RequestInit): Promise<T> { | ||
| const res = await fetch(url, init) | ||
| if (!res.ok) { | ||
| const text = await res.text().catch(() => "") | ||
| throw new Error(`${res.status} ${res.statusText} ${text}`.trim()) | ||
| const friendly = toFriendlyErrorMessage(res.status, text) | ||
| if (friendly) { | ||
| throw new Error(friendly) | ||
| } | ||
| const statusLabel = `${res.status} ${res.statusText}`.trim() | ||
| const base = statusLabel || "Request failed" | ||
| const message = text ? `${base} ${text}`.trim() : base | ||
| throw new Error(message) | ||
| } | ||
| return res.json() as Promise<T> | ||
| } | ||
|
|
@@ -219,7 +256,7 @@ export default function ResearchDashboard() { | |
|
|
||
| useEffect(() => { | ||
| setError(null) | ||
| refreshTracks().catch((e) => setError(String(e))) | ||
| refreshTracks().catch((e) => setError(e instanceof Error ? e.message : String(e))) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The pattern For example: const getErrorMessage = (error: unknown): string => {
return error instanceof Error ? error.message : String(error);
};This can then be used as |
||
| // eslint-disable-next-line react-hooks/exhaustive-deps | ||
| }, []) | ||
|
|
||
|
|
@@ -272,7 +309,7 @@ export default function ResearchDashboard() { | |
| await refreshTracks() | ||
| } | ||
| } catch (e) { | ||
| setError(String(e)) | ||
| setError(e instanceof Error ? e.message : String(e)) | ||
| } finally { | ||
| setLoading(false) | ||
| } | ||
|
|
@@ -297,7 +334,7 @@ export default function ResearchDashboard() { | |
| setSuggestText("") | ||
| await refreshInbox(activeTrackId) | ||
| } catch (e) { | ||
| setError(String(e)) | ||
| setError(e instanceof Error ? e.message : String(e)) | ||
| } finally { | ||
| setLoading(false) | ||
| } | ||
|
|
@@ -320,7 +357,7 @@ export default function ResearchDashboard() { | |
| }) | ||
| await refreshInbox(activeTrackId) | ||
| } catch (e) { | ||
| setError(String(e)) | ||
| setError(e instanceof Error ? e.message : String(e)) | ||
| } finally { | ||
| setLoading(false) | ||
| } | ||
|
|
@@ -344,7 +381,7 @@ export default function ResearchDashboard() { | |
| }) | ||
| await refreshInbox(activeTrackId) | ||
| } catch (e) { | ||
| setError(String(e)) | ||
| setError(e instanceof Error ? e.message : String(e)) | ||
| } finally { | ||
| setLoading(false) | ||
| } | ||
|
|
@@ -383,7 +420,7 @@ export default function ResearchDashboard() { | |
| const activeId = await refreshTracks() | ||
| await refreshInbox(activeId) | ||
| } catch (e) { | ||
| setError(String(e)) | ||
| setError(e instanceof Error ? e.message : String(e)) | ||
| } finally { | ||
| setLoading(false) | ||
| } | ||
|
|
@@ -421,7 +458,7 @@ export default function ResearchDashboard() { | |
| }) | ||
| await buildContext(false) | ||
| } catch (e) { | ||
| setError(String(e)) | ||
| setError(e instanceof Error ? e.message : String(e)) | ||
| } finally { | ||
| setLoading(false) | ||
| } | ||
|
|
@@ -437,7 +474,7 @@ export default function ResearchDashboard() { | |
| ) | ||
| await refreshInbox(trackId) | ||
| } catch (e) { | ||
| setError(String(e)) | ||
| setError(e instanceof Error ? e.message : String(e)) | ||
| } finally { | ||
| setLoading(false) | ||
| } | ||
|
|
@@ -936,7 +973,7 @@ export default function ResearchDashboard() { | |
| /> | ||
| <Button | ||
| variant="outline" | ||
| onClick={() => refreshEval().catch((e) => setError(String(e)))} | ||
| onClick={() => refreshEval().catch((e) => setError(e instanceof Error ? e.message : String(e)))} | ||
| disabled={loading} | ||
| > | ||
| Refresh | ||
|
|
@@ -952,7 +989,7 @@ export default function ResearchDashboard() { | |
| </div> | ||
|
|
||
| {!evalSummary ? ( | ||
| <Button variant="secondary" onClick={() => refreshEval().catch((e) => setError(String(e)))} disabled={loading}> | ||
| <Button variant="secondary" onClick={() => refreshEval().catch((e) => setError(e instanceof Error ? e.message : String(e)))} disabled={loading}> | ||
| Load Summary | ||
| </Button> | ||
| ) : ( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block of code introduces helpful error handling, but it has a couple of issues:
UpstreamErrorBodytype,toFriendlyErrorMessagefunction, andfetchJsonfunction are duplicated inResearchDiscoveryPage.tsxandResearchPageNew.tsx. This should be extracted to a shared utility file (e.g., inweb/src/lib/) to adhere to the DRY principle and improve maintainability.statusparameter intoFriendlyErrorMessage(line 98) is not used. It should be removed from both the function definition and its call site on line 127 to keep the code clean.