Skip to content

Commit 9b64e0a

Browse files
authored
Refactor error handling and reporting: remove user display name from error context, add username to error email payload, and enhance EmailTemplate to display username. Update ErrorEmailProps type to require username. (#575) (#576)
1 parent bcf2302 commit 9b64e0a

File tree

7 files changed

+103
-53
lines changed

7 files changed

+103
-53
lines changed

apps/roam/src/components/canvas/Tldraw.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -513,7 +513,6 @@ const TldrawCanvas = ({ title }: { title: string }) => {
513513
type: "Tldraw Error",
514514
context: {
515515
title: title,
516-
user: getCurrentUserDisplayName(),
517516
lastActions: lastActionsRef.current,
518517
},
519518
}).catch(() => {});

apps/roam/src/components/canvas/useRoamStore.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ export const useRoamStore = ({
153153
type: errorMessage,
154154
context: {
155155
pageUid,
156-
user: getCurrentUserDisplayName(),
157156
snapshotSize,
158157
...(snapshotSize < 10000 ? { initialSnapshot } : {}),
159158
},

apps/roam/src/components/settings/AdminPanel.tsx

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
import migrateRelations from "~/utils/migrateRelations";
2929
import { countReifiedRelations } from "~/utils/createReifiedBlock";
3030
import { DGSupabaseClient } from "@repo/database/lib/client";
31+
import sendErrorEmail from "~/utils/sendErrorEmail";
3132

3233
const NodeRow = ({ node }: { node: PConceptFull }) => {
3334
return (
@@ -326,24 +327,40 @@ const FeatureFlagsTab = (): React.ReactElement => {
326327
getSetting("use-reified-relations"),
327328
);
328329
return (
329-
<Checkbox
330-
defaultChecked={useReifiedRelations}
331-
onChange={(e) => {
332-
const target = e.target as HTMLInputElement;
333-
setUseReifiedRelations(target.checked);
334-
setSetting("use-reified-relations", target.checked);
335-
}}
336-
labelElement={
337-
<>
338-
Reified Relation Triples
339-
<Description
340-
description={
341-
"When ON, relations are read/written as reifiedRelationUid in [[roam/js/discourse-graph/relations]]."
342-
}
343-
/>
344-
</>
345-
}
346-
/>
330+
<div className="flex flex-col gap-4 p-4">
331+
<Checkbox
332+
defaultChecked={useReifiedRelations}
333+
onChange={(e) => {
334+
const target = e.target as HTMLInputElement;
335+
setUseReifiedRelations(target.checked);
336+
setSetting("use-reified-relations", target.checked);
337+
}}
338+
labelElement={
339+
<>
340+
Reified Relation Triples
341+
<Description
342+
description={
343+
"When ON, relations are read/written as reifiedRelationUid in [[roam/js/discourse-graph/relations]]."
344+
}
345+
/>
346+
</>
347+
}
348+
/>
349+
350+
<Button
351+
className="w-96"
352+
icon="send-message"
353+
onClick={() => {
354+
console.log("sending error email");
355+
sendErrorEmail({
356+
error: new Error("test"),
357+
type: "Test",
358+
}).catch(() => {});
359+
}}
360+
>
361+
Send Error Email
362+
</Button>
363+
</div>
347364
);
348365
};
349366

apps/roam/src/utils/sendErrorEmail.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { getNodeEnv } from "roamjs-components/util/env";
22
import { ErrorEmailProps } from "@repo/types";
33
import { getVersionWithDate } from "~/utils/getVersion";
4+
import getCurrentUserDisplayName from "roamjs-components/queries/getCurrentUserDisplayName";
45

56
const sendErrorEmail = async ({
67
error,
@@ -16,21 +17,25 @@ const sendErrorEmail = async ({
1617
? "http://localhost:3000/api/errors"
1718
: "https://discoursegraphs.com/api/errors";
1819
const { version, buildDate } = getVersionWithDate();
20+
const username = getCurrentUserDisplayName();
21+
1922
const payload: ErrorEmailProps = {
2023
errorMessage: error.message,
2124
errorStack: error.stack || "",
2225
type,
2326
app: "Roam",
2427
graphName: window.roamAlphaAPI?.graph?.name || "unknown",
25-
version,
26-
buildDate,
28+
version: version || "-",
29+
buildDate: buildDate || "-",
30+
username: username || "unknown",
2731
context,
2832
};
2933

3034
try {
3135
const response = await fetch(url, {
3236
method: "POST",
3337
headers: {
38+
// eslint-disable-next-line @typescript-eslint/naming-convention
3439
"Content-Type": "application/json",
3540
},
3641
body: JSON.stringify(payload),
@@ -41,7 +46,8 @@ const sendErrorEmail = async ({
4146
console.error(`Failed to send error email: ${errorMessage}`);
4247
}
4348
} catch (err) {
44-
console.error(`Error sending request: ${err}`);
49+
const errorMessage = err instanceof Error ? err.message : String(err);
50+
console.error(`Error sending request: ${errorMessage}`);
4551
}
4652
};
4753

apps/website/app/api/errors/EmailTemplate.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
import { ErrorEmailProps } from "@repo/types";
22
import React from "react";
33

4+
const ErrorField = ({ label, value }: { label: string; value: string }) => (
5+
<div>
6+
<span style={{ fontWeight: "bold", minWidth: "100px" }}>{label}:</span>{" "}
7+
{value}
8+
</div>
9+
);
410
// TODO: use react.email
511
export const EmailTemplate = ({
612
errorMessage,
@@ -10,17 +16,18 @@ export const EmailTemplate = ({
1016
graphName,
1117
version,
1218
buildDate,
19+
username,
1320
context,
1421
}: ErrorEmailProps) => {
1522
return (
1623
<div>
1724
<h1>Error Report</h1>
18-
19-
<span>Type: {type}</span>
20-
<span>App: {app}</span>
21-
<span>Graph Name: {graphName}</span>
22-
{version && <span>Version: {version}</span>}
23-
{buildDate && <span>Build Date: {buildDate}</span>}
25+
<ErrorField label="Type" value={type} />
26+
<ErrorField label="App" value={app} />
27+
<ErrorField label="Graph Name" value={graphName} />
28+
<ErrorField label="Username" value={username} />
29+
<ErrorField label="Version" value={version} />
30+
<ErrorField label="Build Date" value={buildDate} />
2431

2532
<div>
2633
<h2>Error Details</h2>

apps/website/app/api/errors/route.tsx

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,46 @@ const allowedOrigins = ["https://roamresearch.com", "http://localhost:3000"];
77

88
const resend = new Resend(process.env.RESEND_API_KEY);
99

10-
export async function POST(request: Request) {
11-
try {
12-
const origin = request.headers.get("origin");
13-
const isAllowedOrigin = origin && allowedOrigins.includes(origin);
10+
const createCorsResponse = (
11+
data: unknown,
12+
status: number,
13+
origin: string | null,
14+
): Response => {
15+
const response = NextResponse.json(data, { status });
16+
const isAllowedOrigin = origin && allowedOrigins.includes(origin);
17+
18+
if (isAllowedOrigin) {
19+
response.headers.set("Access-Control-Allow-Origin", origin);
20+
response.headers.set("Access-Control-Allow-Methods", "POST, OPTIONS");
21+
response.headers.set("Access-Control-Allow-Headers", "Content-Type");
22+
}
1423

15-
const body = await request.json();
24+
return response;
25+
};
26+
27+
export const POST = async (request: Request) => {
28+
const origin = request.headers.get("origin");
29+
30+
try {
31+
const body = (await request.json()) as ErrorEmailProps;
1632
const {
1733
errorMessage,
1834
errorStack,
1935
type,
2036
app,
2137
graphName,
38+
username,
39+
version,
40+
buildDate,
2241
context = {},
23-
} = body as ErrorEmailProps;
42+
} = body;
2443

2544
if (!errorMessage) {
26-
return Response.json({ error: "Missing error message" }, { status: 400 });
45+
return createCorsResponse(
46+
{ error: "Missing error message" },
47+
400,
48+
origin,
49+
);
2750
}
2851

2952
const { data, error: resendError } = await resend.emails.send({
@@ -37,32 +60,30 @@ export async function POST(request: Request) {
3760
type,
3861
app,
3962
graphName,
63+
// ?: Why is username assignment unsafe and graphName,version,buildDate are not?
64+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
65+
username,
66+
version,
67+
buildDate,
4068
context,
4169
}),
4270
});
4371

4472
if (resendError) {
45-
return Response.json({ error: resendError }, { status: 500 });
46-
}
47-
48-
const response = NextResponse.json({ success: true, data });
49-
50-
if (isAllowedOrigin) {
51-
response.headers.set("Access-Control-Allow-Origin", origin);
52-
response.headers.set("Access-Control-Allow-Methods", "POST, OPTIONS");
53-
response.headers.set("Access-Control-Allow-Headers", "Content-Type");
73+
return createCorsResponse({ error: resendError }, 500, origin);
5474
}
5575

56-
return response;
76+
return createCorsResponse({ success: true, data }, 200, origin);
5777
} catch (error) {
58-
return Response.json(
78+
return createCorsResponse(
5979
{ error: error instanceof Error ? error.message : "Unknown error" },
60-
{ status: 500 },
80+
500,
81+
origin,
6182
);
6283
}
63-
}
84+
};
6485

65-
export async function OPTIONS(request: Request) {
86+
export const OPTIONS = (request: Request) => {
6687
const origin = request.headers.get("origin");
6788

6889
const isAllowedOrigin = origin && allowedOrigins.includes(origin);
@@ -78,4 +99,4 @@ export async function OPTIONS(request: Request) {
7899
}
79100

80101
return response;
81-
}
102+
};

packages/types/index.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ export type ErrorEmailProps = {
44
type: string; // To identify the type of error, eg "Export Dialog Failed"
55
app: "Roam" | "Obsidian";
66
graphName: string;
7-
version?: string;
8-
buildDate?: string;
7+
version: string;
8+
buildDate: string;
9+
username: string;
910
context?: Record<string, unknown>;
1011
};

0 commit comments

Comments
 (0)