Skip to content
This repository was archived by the owner on Feb 28, 2024. It is now read-only.

Commit 4ee68c5

Browse files
committed
Integrate bugsnag for frontend error detection
1 parent a917b95 commit 4ee68c5

File tree

10 files changed

+399
-16
lines changed

10 files changed

+399
-16
lines changed

frontend/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# API configuration
22
REACT_APP_API_BASE_URL=http://localhost.localdomain:8000
33

4+
# The BugSnag API key
5+
REACT_APP_BUGSNAG_API_KEY=abcdefghijklmnopqrstuvwxyz
6+
47
# Google Maps API key for address selection
58
REACT_APP_GOOGLE_MAPS_API_KEY=abcdefghijklmnopqrstuvwxyz
69

frontend/.env.production

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Re-map Cloudflare Pages environment variables
2+
REACT_APP_BUGSNAG_VERSION=${CF_PAGES_COMMIT_SHA}
3+
REACT_APP_BUGSNAG_BRANCH=${CF_PAGES_BRANCH}

frontend/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
# misc
1515
.DS_Store
16-
.env.local
16+
.env.production.local
1717
.env.development.local
1818
.env.test.local
1919
.env.production.local

frontend/package.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6+
"@bugsnag/js": "^7.20.2",
7+
"@bugsnag/plugin-react": "^7.19.0",
68
"@headlessui/react": "^1.7.14",
79
"@heroicons/react": "^2.0.17",
810
"@reduxjs/toolkit": "^1.9.5",
@@ -32,6 +34,7 @@
3234
"yup": "^1.1.1"
3335
},
3436
"devDependencies": {
37+
"@bugsnag/source-maps": "^2.3.1",
3538
"@types/crypto-js": "^4.1.1",
3639
"@types/google.maps": "^3.52.5",
3740
"@types/luxon": "^3.3.0",
@@ -57,8 +60,10 @@
5760
"build": "react-scripts build",
5861
"test": "react-scripts test",
5962
"eject": "react-scripts eject",
60-
"analyze": "source-map-explorer 'build/static/js/*.js'",
61-
"lint": "eslint --fix ."
63+
"source-map:upload": "node scripts/upload-source-maps.mjs",
64+
"source-map:analyze": "source-map-explorer 'build/static/js/*.js'",
65+
"lint": "eslint --fix .",
66+
"production": "yarn build && yarn source-map:upload"
6267
},
6368
"engines": {
6469
"node": "^18"
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { browser } from '@bugsnag/source-maps';
2+
3+
const API_KEY = process.env.REACT_APP_BUGSNAG_API_KEY;
4+
const VERSION = process.env.CF_PAGES_COMMIT_SHA || 'dev';
5+
const BASE_DOMAIN = process.env.CF_PAGES_URL || 'https://localhost.localdomain:3000';
6+
7+
console.log(BASE_DOMAIN);
8+
9+
async function main() {
10+
if (API_KEY === undefined) {
11+
console.error('Missing REACT_APP_BUGSNAG_API_KEY environment variable');
12+
process.exit(1);
13+
}
14+
15+
await browser.uploadMultiple({
16+
apiKey: API_KEY,
17+
appVersion: VERSION,
18+
overwrite: true,
19+
directory: './build/static/js',
20+
baseUrl: `${BASE_DOMAIN}/static/js`,
21+
});
22+
}
23+
24+
main();

frontend/src/ErrorHandler.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import Bugsnag from '@bugsnag/js';
2+
import BugsnagPluginReact from '@bugsnag/plugin-react';
3+
import React, { FC, ReactNode } from 'react';
4+
5+
const API_KEY = process.env.REACT_APP_BUGSNAG_API_KEY;
6+
const VERSION = process.env.REACT_APP_BUGSNAG_VERSION || 'dev';
7+
8+
const BRANCH = process.env.REACT_APP_BUGSNAG_BRANCH || 'dev';
9+
const STAGE = BRANCH === 'main' ? 'production' : BRANCH;
10+
11+
console.log(VERSION);
12+
console.log(BRANCH);
13+
console.log(STAGE);
14+
15+
interface Props {
16+
children: ReactNode;
17+
}
18+
19+
let ErrorHandler: FC<Props>;
20+
21+
if (API_KEY === undefined) {
22+
// eslint-disable-next-line react/display-name
23+
ErrorHandler = ({ children }: Props): JSX.Element => <>{children}</>;
24+
} else {
25+
Bugsnag.start({
26+
apiKey: API_KEY,
27+
appVersion: VERSION,
28+
releaseStage: STAGE,
29+
plugins: [new BugsnagPluginReact()],
30+
});
31+
32+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
33+
const ErrorBoundary = Bugsnag.getPlugin('react')!.createErrorBoundary(React);
34+
35+
// eslint-disable-next-line react/display-name
36+
ErrorHandler = ({ children }: Props): JSX.Element => <ErrorBoundary>{children}</ErrorBoundary>;
37+
}
38+
39+
export default ErrorHandler;

frontend/src/index.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,23 @@ import 'flatpickr/dist/flatpickr.min.css';
88
import './index.css';
99

1010
import App from './App';
11+
import ErrorHandler from './ErrorHandler';
1112
import Loading from './Loading';
1213
import { store } from './store';
1314

1415
const container = document.getElementById('root');
1516
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
1617
createRoot(container!).render(
1718
<React.StrictMode>
18-
<Provider store={store}>
19-
<BrowserRouter>
20-
<Toaster position="top-right" toastOptions={{ duration: 2500 }} />
21-
<Suspense fallback={<Loading />}>
22-
<App />
23-
</Suspense>
24-
</BrowserRouter>
25-
</Provider>
19+
<ErrorHandler>
20+
<Provider store={store}>
21+
<BrowserRouter>
22+
<Toaster position="top-right" toastOptions={{ duration: 2500 }} />
23+
<Suspense fallback={<Loading />}>
24+
<App />
25+
</Suspense>
26+
</BrowserRouter>
27+
</Provider>
28+
</ErrorHandler>
2629
</React.StrictMode>,
2730
);

frontend/src/participants/pages/statuses/Pending.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Bugsnag from '@bugsnag/js';
12
import React from 'react';
23

34
import Link from '../../../components/Link';
@@ -19,6 +20,7 @@ const Pending = (): JSX.Element => (
1920
Twitter
2021
</Link>{' '}
2122
to keep up-to-date with the latest information.
23+
<button onClick={() => Bugsnag.notify(new Error('test error'))}>Error</button>
2224
</Status>
2325
);
2426

frontend/src/store/authentication.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import Bugsnag from '@bugsnag/js';
12
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
23

34
import { AuthenticationStatus, Participant, Provider, ProviderWithClientSecret, ReducedProvider } from './types';
@@ -37,6 +38,23 @@ const api = createApi({
3738
// User endpoints
3839
currentUser: builder.query<Me, void>({
3940
query: () => ({ url: '/auth/me' }),
41+
transformResponse: (response: Me) => {
42+
// Add the user context if able
43+
if (response.email) {
44+
Bugsnag.setUser(undefined, response.email, undefined);
45+
Bugsnag.addMetadata('authentication', { status: response.status, email: response.email });
46+
}
47+
if (response.participant) {
48+
Bugsnag.setUser(
49+
response.participant.id.toString(),
50+
response.participant.email,
51+
`${response.participant.first_name} ${response.participant.last_name}`,
52+
);
53+
Bugsnag.addMetadata('authentication', { status: response.status, ...response.participant });
54+
}
55+
56+
return response;
57+
},
4058
providesTags: () => [Tag.Profile],
4159
}),
4260
completeProfile: builder.mutation<Participant, ProfileCreate>({

0 commit comments

Comments
 (0)