Skip to content

Commit

Permalink
Support client-side error-logging with Rollbar (#1264)
Browse files Browse the repository at this point in the history
- adds `@react/rollbar and rollbar` as dependencies
   for the client app
- adjusts related constants so that they can be shared
   between client and server
- adds support of client-side error-logging and use
   router-base contexts
- not in the code changes, but created a Rollbar access
   tokens for client side with `post_client_item` scope
- adds the above env var to docker-compose for client
   app and to the GitHub actions CI steps
  • Loading branch information
aaronxsu authored Sep 27, 2022
1 parent 8c347af commit 22e7cb4
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 44 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
run: ./scripts/cibuild
env:
NODE_ENV: test
REACT_APP_ROLLBAR_CLIENT_ACCESS_TOKEN: ${{ secrets.REACT_APP_ROLLBAR_CLIENT_ACCESS_TOKEN }}

- uses: aws-actions/configure-aws-credentials@v1
with:
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Enable SSM to EC2 Hosts [#1259](https://github.com/PublicMapping/districtbuilder/pull/1259)
- Enabled SSM to EC2 Hosts [#1259](https://github.com/PublicMapping/districtbuilder/pull/1259)
- Added support for client-side error-logging with Rollbar [#1264](https://github.com/PublicMapping/districtbuilder/pull/1264)

### Changed

Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ services:
environment:
- BASE_URL=${BASE_URL:-http://server:3005}
- PORT=3003
- REACT_APP_ROLLBAR_CLIENT_ACCESS_TOKEN
volumes:
- ./:/home/node/app
- ./src/server/dist/server/src/static:/home/node/app/build
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@emotion/styled": "11.6.0",
"@mdx-js/react": "1.6.22",
"@redux-beacon/google-tag-manager": "1.0.1",
"@rollbar/react": "^0.11.1",
"@szhsin/react-menu": "^1.9.1",
"@theme-ui/color": "0.13.1",
"@tippyjs/react": "^4.1.0",
Expand Down Expand Up @@ -66,6 +67,7 @@
"redux-beacon": "2.1.0",
"redux-devtools-extension": "^2.13.8",
"redux-loop": "5.0.1",
"rollbar": "^2.25.2",
"theme-ui": "0.13.1",
"timeago-react": "3.0.1",
"ts.data.json": "0.3.0",
Expand Down
4 changes: 4 additions & 0 deletions scripts/cibuild
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,21 @@ if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
if [ "${1:-}" = "--help" ]; then
usage
else
echo "Running update script to build and package client app and server"
GIT_COMMIT="${GIT_COMMIT}" CI=1 ./scripts/update

echo "Building images"
# Build tagged container images
GIT_COMMIT="${GIT_COMMIT}" docker-compose \
-f docker-compose.yml \
-f docker-compose.ci.yml \
build server manage

echo "Stopping containers"
# Stop all docker containers so Github actions uses less memory
docker stop districtbuilder_server_1 districtbuilder_database_1

echo "Running tests"
CI=1 GIT_COMMIT="${GIT_COMMIT}" \
./scripts/test
fi
Expand Down
142 changes: 107 additions & 35 deletions src/client/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React from "react";
import * as H from "history";
import { BrowserRouter as Router, Redirect, Route, RouteProps, Switch } from "react-router-dom";
import { Provider, RollbarContext } from "@rollbar/react";
import Rollbar from "rollbar";
import { ThemeProvider } from "theme-ui";

import { getJWT, jwtIsExpired } from "./jwt";
Expand All @@ -24,6 +26,7 @@ import StartProjectScreen from "./screens/StartProjectScreen";
import PublishedMapsListScreen from "./screens/PublishedMapsListScreen";
import { PushReplaceHistory, QueryParamProvider } from "use-query-params";
import { createBrowserHistory } from "history";
import { DEBUG } from "../shared/constants";

const PrivateRoute = ({ children, ...props }: { children: React.ReactNode } & RouteProps) => {
const savedJWT = getJWT();
Expand All @@ -42,6 +45,25 @@ const PrivateRoute = ({ children, ...props }: { children: React.ReactNode } & Ro
);
};

const environment = window.location.href.includes("staging") ? "staging" : "production";

const rollbarConfig: Rollbar.Configuration = {
accessToken: process.env.REACT_APP_ROLLBAR_CLIENT_ACCESS_TOKEN || "",
captureUncaught: true,
captureUnhandledRejections: true,
enabled: !DEBUG,
nodeSourceMaps: true,
payload: {
environment,
client: {
javascript: {
code_version: "1.18.2",
source_map_enabled: true
}
}
}
};

const history = createBrowserHistory();
const pushReplaceHistory: PushReplaceHistory = {
push: (location: Location): void => {
Expand All @@ -53,47 +75,97 @@ const pushReplaceHistory: PushReplaceHistory = {
location: window.location
};

const App = () => (
<ThemeProvider theme={theme}>
<Toast />
<Router>
<QueryParamProvider history={pushReplaceHistory}>
<Switch>
<PrivateRoute path="/" exact={true}>
const Routes = () => (
<Router>
<QueryParamProvider history={pushReplaceHistory}>
<Switch>
<PrivateRoute path="/" exact={true}>
<RollbarContext context="home">
<HomeScreen />
</PrivateRoute>
<Route path="/o/:organizationSlug" exact={true} component={OrganizationScreen} />
<PrivateRoute path="/o/:organizationSlug/admin" exact={true}>
</RollbarContext>
</PrivateRoute>
<Route path="/o/:organizationSlug" exact={true}>
<RollbarContext context="organization">
<OrganizationScreen />
</RollbarContext>
</Route>
<PrivateRoute path="/o/:organizationSlug/admin" exact={true}>
<RollbarContext context="organization-admin">
<OrganizationAdminScreen />
</PrivateRoute>
<Route path="/projects/:projectId" exact={true} component={ProjectScreen} />
<Route path="/login" exact={true} component={LoginScreen} />
<Route path="/maps" exact={true} component={PublishedMapsListScreen} />
<Route path="/register" exact={true} component={RegistrationScreen} />
<Route path="/forgot-password" exact={true} component={ForgotPasswordScreen} />
<Route path="/activate/:token" exact={true} component={ActivateAccountScreen} />
<Route
path="/activate/:token/:organizationSlug"
exact={true}
component={ActivateAccountScreen}
/>
<Route path="/password-reset/:token" exact={true} component={ResetPasswordScreen} />
<PrivateRoute path="/create-project" exact={true}>
</RollbarContext>
</PrivateRoute>
<Route path="/projects/:projectId" exact={true}>
<RollbarContext context="project">
<ProjectScreen />
</RollbarContext>
</Route>
<Route path="/login" exact={true}>
<RollbarContext context="login">
<LoginScreen />
</RollbarContext>
</Route>
<Route path="/maps" exact={true}>
<RollbarContext context="published-map-list">
<PublishedMapsListScreen />
</RollbarContext>
</Route>
<Route path="/register" exact={true}>
<RollbarContext context="register">
<RegistrationScreen />
</RollbarContext>
</Route>
<Route path="/forgot-password" exact={true}>
<RollbarContext context="forgot-password">
<ForgotPasswordScreen />
</RollbarContext>
</Route>
<Route path="/activate/:token" exact={true}>
<RollbarContext context="activate-account">
<ActivateAccountScreen />
</RollbarContext>
</Route>
<Route path="/activate/:token/:organizationSlug" exact={true}>
<RollbarContext context="activate-account-organization">
<ActivateAccountScreen />
</RollbarContext>
</Route>
<Route path="/password-reset/:token" exact={true}>
<RollbarContext context="reset-password">
<ResetPasswordScreen />
</RollbarContext>
</Route>
<PrivateRoute path="/create-project" exact={true}>
<RollbarContext context="create-project">
<CreateProjectScreen />
</PrivateRoute>
<PrivateRoute path="/start-project" exact={true}>
</RollbarContext>
</PrivateRoute>
<PrivateRoute path="/start-project" exact={true}>
<RollbarContext context="start-project">
<StartProjectScreen />
</PrivateRoute>
<PrivateRoute path="/import-project" exact={true}>
</RollbarContext>
</PrivateRoute>
<PrivateRoute path="/import-project" exact={true}>
<RollbarContext context="import-project">
<ImportProjectScreen />
</PrivateRoute>
<PrivateRoute path="/user-account" exact={true}>
</RollbarContext>
</PrivateRoute>
<PrivateRoute path="/user-account" exact={true}>
<RollbarContext context="user-account">
<UserAccountScreen />
</PrivateRoute>
</Switch>
</QueryParamProvider>
</Router>
</ThemeProvider>
</RollbarContext>
</PrivateRoute>
</Switch>
</QueryParamProvider>
</Router>
);

const App = () => (
<Provider config={rollbarConfig}>
<ThemeProvider theme={theme}>
<Toast />
<Routes />
</ThemeProvider>
</Provider>
);

export default App;
2 changes: 1 addition & 1 deletion src/server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { SES } from "aws-sdk";
import * as SESTransport from "nodemailer/lib/ses-transport";
import * as StreamTransport from "nodemailer/lib/stream-transport";

import { DEBUG } from "./common/constants";
import { DEBUG } from "../../shared/constants";
import { AuthModule } from "./auth/auth.module";
import { HealthCheckModule } from "./healthcheck/healthcheck.module";
import { OrganizationsModule } from "./organizations/organizations.module";
Expand Down
2 changes: 1 addition & 1 deletion src/server/src/commands/reindex.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Logger } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "../app.module";
import { DEBUG } from "../common/constants";
import { DEBUG } from "../../../shared/constants";
import { Connection } from "typeorm";

async function bootstrap(): Promise<void> {
Expand Down
2 changes: 1 addition & 1 deletion src/server/src/commands/set-topology-size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import S3 from "aws-sdk/clients/s3";
import Queue from "promise-queue";

import { AppModule } from "../app.module";
import { DEBUG } from "../common/constants";
import { DEBUG } from "../../../shared/constants";
import { formatBytes, getTopology, getTopologyLayerSize } from "../common/functions";
import { RegionConfigsModule } from "../region-configs/region-configs.module";
import { RegionConfigsService } from "../region-configs/services/region-configs.service";
Expand Down
7 changes: 4 additions & 3 deletions src/server/src/common/constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os from "os";
import { ENVIRONMENT } from "../../../shared/constants";

export const ENVIRONMENT = process.env.NODE_ENV || "Development";
export const DEBUG = ENVIRONMENT === "Development";
export const BCRYPT_SALT_ROUNDS = 10;
export const EMAIL_VERIFICATION_TOKEN_LENGTH = 20;
export const DEFAULT_FROM_EMAIL =
Expand All @@ -10,4 +9,6 @@ export const DEFAULT_FROM_EMAIL =
// One worker/CPU works well on AWS using memory-optimized units
// For dev & CI that's not the case, so just use a fixed amount
export const NUM_WORKERS =
ENVIRONMENT === "Development" || ENVIRONMENT === "test" ? 3 : os.cpus().length;
ENVIRONMENT.toUpperCase() === "DEVELOPMENT" || ENVIRONMENT.toUpperCase() === "TEST"
? 3
: os.cpus().length;
2 changes: 1 addition & 1 deletion src/server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Reflector } from "@nestjs/core";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { BadRequestExceptionFilter } from "./common/bad-request-exception.filter";
import { DEBUG } from "./common/constants";
import { DEBUG } from "../../shared/constants";
import * as bodyParser from "body-parser";
import { DistrictsModule } from "./districts/districts.module";
import { TopologyService } from "./districts/services/topology.service";
Expand Down
2 changes: 1 addition & 1 deletion src/server/src/rollbar/rollbar.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { join } from "path";
import { Injectable } from "@nestjs/common";
import Rollbar from "rollbar";
import { DEBUG, ENVIRONMENT } from "../common/constants";
import { DEBUG, ENVIRONMENT } from "../../../shared/constants";

@Injectable()
export class RollbarService extends Rollbar {
Expand Down
3 changes: 3 additions & 0 deletions src/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,6 @@ export const MAX_IMPORT_ERRORS = 1_000;

export const PLANSCORE_POLL_MAX_TRIES = 40;
export const PLANSCORE_POLL_MS = 6000;

export const ENVIRONMENT = process.env.NODE_ENV || "development";
export const DEBUG = ENVIRONMENT.toUpperCase() === "DEVELOPMENT";
Loading

0 comments on commit 22e7cb4

Please sign in to comment.