Skip to content

Commit

Permalink
[CORL-1293] Sentry Error Integration (#3109)
Browse files Browse the repository at this point in the history
* feat: initial server impl

* feat: transitioned to vanilla apollo-server-express

* fix: disable APQ

* feat: client side error reporting support

* fix: updated snapshots

* feat: add domain tags to requests

* fix: remove email from error payload

* fix: fixed based on review comments

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
wyattjoh and kodiakhq[bot] committed Aug 20, 2020
1 parent aa3273f commit 15f7c10
Show file tree
Hide file tree
Showing 63 changed files with 1,484 additions and 753 deletions.
484 changes: 339 additions & 145 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Expand Up @@ -67,10 +67,11 @@
"@hapi/joi": "^17.1.1",
"@metascraper/helpers": "^5.11.6",
"@rudderstack/rudder-sdk-node": "0.0.2",
"@sentry/node": "^5.21.1",
"abort-controller": "^3.0.0",
"akismet-api": "^5.0.0",
"apollo-server-core": "^2.14.4",
"apollo-server-express": "^2.14.2",
"apollo-server-express": "^2.16.1",
"apollo-server-plugin-base": "^0.9.1",
"archiver": "^3.1.1",
"basic-auth": "^2.0.1",
"bcryptjs": "^2.4.3",
Expand Down Expand Up @@ -155,6 +156,7 @@
"@coralproject/rte": "^1.2.3",
"@fluent/react": "^0.11.1",
"@intervolga/optimize-cssnano-plugin": "^1.0.6",
"@sentry/react": "^5.21.1",
"@types/archiver": "^3.1.0",
"@types/basic-auth": "^1.1.3",
"@types/bcryptjs": "^2.4.2",
Expand Down
7 changes: 7 additions & 0 deletions src/core/client/account/index.tsx
Expand Up @@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
import injectConditionalPolyfills from "coral-framework/helpers/injectConditionalPolyfills";
import potentiallyInjectAxe from "coral-framework/helpers/potentiallyInjectAxe";
import { createManaged } from "coral-framework/lib/bootstrap";
import { createReporter } from "coral-framework/lib/errors/reporter";

import App from "./App";
import { initLocalState } from "./local";
Expand All @@ -13,12 +14,18 @@ import localesData from "./locales";
import "coral-ui/theme/stream.css";

async function main() {
// Configure and load the error reporter.
const reporter = createReporter();

await injectConditionalPolyfills();

// Potentially inject react-axe for runtime a11y checks.
await potentiallyInjectAxe();

const ManagedCoralContextProvider = await createManaged({
initLocalState,
localesData,
reporter,
});

const Index: FunctionComponent = () => (
Expand Down
8 changes: 8 additions & 0 deletions src/core/client/admin/index.tsx
Expand Up @@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
import injectConditionalPolyfills from "coral-framework/helpers/injectConditionalPolyfills";
import potentiallyInjectAxe from "coral-framework/helpers/potentiallyInjectAxe";
import { createManaged } from "coral-framework/lib/bootstrap";
import { createReporter } from "coral-framework/lib/errors/reporter";

import App from "./App";
import Head from "./Head";
Expand All @@ -14,12 +15,19 @@ import localesData from "./locales";
import "coral-ui/theme/admin.css";

async function main() {
// Configure and load the error reporter with the boundary element.
const reporter = createReporter({ reporterFeedbackPrompt: true });

// Load any polyfills that are required.
await injectConditionalPolyfills();

// Potentially inject react-axe for runtime a11y checks.
await potentiallyInjectAxe();

const ManagedCoralContextProvider = await createManaged({
initLocalState,
localesData,
reporter,
});

const Index: FunctionComponent = () => (
Expand Down
40 changes: 38 additions & 2 deletions src/core/client/admin/routes/AuthCheck/AuthCheckRoute.tsx
Expand Up @@ -5,6 +5,7 @@ import { graphql } from "react-relay";
import { SetRedirectPathMutation } from "coral-admin/mutations";
import { AbilityType, can } from "coral-admin/permissions";
import { roleIsAtLeast } from "coral-framework/helpers";
import { CoralContext, withContext } from "coral-framework/lib/bootstrap";
import { MutationProp, withMutation } from "coral-framework/lib/relay";
import { withRouteConfig } from "coral-framework/lib/router";
import { GQLUSER_ROLE } from "coral-framework/schema";
Expand All @@ -15,6 +16,7 @@ import NetworkError from "./NetworkError";
import RestrictedContainer from "./RestrictedContainer";

interface Props {
reporter: CoralContext["reporter"];
match: Match;
router: Router;
setRedirectPath: MutationProp<typeof SetRedirectPathMutation>;
Expand Down Expand Up @@ -88,17 +90,45 @@ function createAuthCheckRoute(check: CheckParams) {
props.router.replace("/admin/login");
}

public componentDidUpdate(prevProps: Props) {
// Whenever the viewer changes on this component, update the user on the
// reporter.
if (!this.props.reporter) {
return;
}

// Pull the viewer out of the next props (`this.props`) and the current
// props (`prevProps`) to compare them.
const next = this.props.data?.viewer || null;
const curr = prevProps.data?.viewer || null;

// If the next is different than current, then...
if (next !== curr) {
// If they are both provided then if the id of the viewer didn't change,
// then the user hasn't changed (at least, it hasn't changed enough to
// require another update to the reporter).
if (next && curr && next.id === curr.id) {
return;
}

this.props.reporter.setUser(next);
}
}

public render() {
if (this.props.error) {
return <NetworkError />;
}

if (!this.props.data || this.shouldRedirectTo()) {
return null;
}

if (this.hasAccess()) {
return this.props.children;
}
return <RestrictedContainer viewer={this.props.data.viewer!} />;

return <RestrictedContainer viewer={this.props.data.viewer} />;
}
}

Expand All @@ -107,6 +137,7 @@ function createAuthCheckRoute(check: CheckParams) {
query AuthCheckRouteQuery {
viewer {
...RestrictedContainer_viewer
id
username
email
profiles {
Expand All @@ -129,7 +160,12 @@ function createAuthCheckRoute(check: CheckParams) {
}
}
`,
})(withRouter(withMutation(SetRedirectPathMutation)(AuthCheckRoute)));
})(
withContext(({ reporter }) => ({ reporter }))(
withRouter(withMutation(SetRedirectPathMutation)(AuthCheckRoute))
)
);

return enhanced;
}

Expand Down
24 changes: 15 additions & 9 deletions src/core/client/admin/routes/AuthCheck/Restricted.tsx
Expand Up @@ -8,12 +8,11 @@ import { Button, Flex, HorizontalGutter, Icon } from "coral-ui/components/v2";
import styles from "./Restricted.css";

interface Props {
username: string;
username: string | null;
onSignInAs: React.MouseEventHandler;
}

const SignIn: FunctionComponent<Props> = ({ username, onSignInAs }) => {
const Username = () => <div className={styles.username}>{username}</div>;
return (
<AuthBox
title={
Expand All @@ -37,13 +36,20 @@ const SignIn: FunctionComponent<Props> = ({ username, onSignInAs }) => {
</div>
</Localized>
</div>
<div>
<Localized id="restricted-signedInAs" Username={<Username />}>
<div className={styles.copy}>
{"You are signed in as: <Username></Username>"}
</div>
</Localized>
</div>
{username && (
<div>
<Localized
id="restricted-signedInAs"
strong={<div className={styles.username} />}
$username={username}
>
<div className={styles.copy}>
You are signed in as:{" "}
<div className={styles.username}>{username}</div>
</div>
</Localized>
</div>
)}
<Flex justifyContent="center">
<Localized id="restricted-signInWithADifferentAccount">
<Button
Expand Down
Expand Up @@ -19,7 +19,7 @@ import { RestrictedContainer_viewer as ViewerData } from "coral-admin/__generate
import Restricted from "./Restricted";

interface Props {
viewer: ViewerData;
viewer: ViewerData | null;
error?: Error | null;
signOut: SignOutMutation;
setRedirectPath: MutationProp<typeof SetRedirectPathMutation>;
Expand All @@ -44,7 +44,7 @@ class RestrictedContainer extends Component<Props> {

return (
<Restricted
username={this.props.viewer.username!}
username={this.props.viewer.username}
onSignInAs={this.handleSignInAs}
/>
);
Expand Down
Expand Up @@ -42,13 +42,24 @@ exports[`renders correctly 1`] = `
</div>
<div>
<Localized
Username={<Username />}
$username="User"
id="restricted-signedInAs"
strong={
<div
className="Restricted-username"
/>
}
>
<div
className="Restricted-copy"
>
You are signed in as: &lt;Username&gt;&lt;/Username&gt;
You are signed in as:
<div
className="Restricted-username"
>
User
</div>
</div>
</Localized>
</div>
Expand Down
7 changes: 7 additions & 0 deletions src/core/client/auth/index.tsx
Expand Up @@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
import injectConditionalPolyfills from "coral-framework/helpers/injectConditionalPolyfills";
import potentiallyInjectAxe from "coral-framework/helpers/potentiallyInjectAxe";
import { createManaged } from "coral-framework/lib/bootstrap";
import { createReporter } from "coral-framework/lib/errors/reporter";

import App from "./App";
import { initLocalState } from "./local";
Expand All @@ -13,12 +14,18 @@ import localesData from "./locales";
import "coral-ui/theme/stream.css";

async function main() {
// Configure and load the error reporter.
const reporter = createReporter();

await injectConditionalPolyfills();

// Potentially inject react-axe for runtime a11y checks.
await potentiallyInjectAxe();

const ManagedCoralContextProvider = await createManaged({
initLocalState,
localesData,
reporter,
});

const Index: FunctionComponent = () => (
Expand Down
Expand Up @@ -37,5 +37,6 @@ export default async function injectConditionalPolyfills() {
if (!browser.supports.cssVariables) {
pending.push(polyfillCSSVars());
}

await Promise.all(pending);
}
6 changes: 6 additions & 0 deletions src/core/client/framework/lib/bootstrap/CoralContext.tsx
Expand Up @@ -9,6 +9,7 @@ import { Environment } from "relay-runtime";

import { LanguageCode } from "coral-common/helpers/i18n";
import { BrowserInfo } from "coral-framework/lib/browserInfo";
import { ErrorReporter } from "coral-framework/lib/errors";
import { PostMessageService } from "coral-framework/lib/postMessage";
import { RestClient } from "coral-framework/lib/rest";
import { PromisifiedStorage } from "coral-framework/lib/storage";
Expand Down Expand Up @@ -73,6 +74,11 @@ export interface CoralContext {

/** Controls router transitions (for tests) */
transitionControl?: TransitionControlData;

/**
* reporter is the designated ErrorReporter for this application.
*/
reporter?: ErrorReporter;
}

export const CoralReactContext = React.createContext<CoralContext>({} as any);
Expand Down
21 changes: 20 additions & 1 deletion src/core/client/framework/lib/bootstrap/createManaged.tsx
Expand Up @@ -10,6 +10,7 @@ import { v1 as uuid } from "uuid";
import { LanguageCode } from "coral-common/helpers/i18n";
import polyfillIntlLocale from "coral-framework/helpers/polyfillIntlLocale";
import { getBrowserInfo } from "coral-framework/lib/browserInfo";
import { ErrorReporter } from "coral-framework/lib/errors";
import { RestClient } from "coral-framework/lib/rest";
import {
createLocalStorage,
Expand Down Expand Up @@ -60,6 +61,11 @@ interface CreateContextArguments {

/** Supports emitting and listening to events. */
eventEmitter?: EventEmitter2;

/**
* reporter is the designated ErrorReporter for this application.
*/
reporter?: ErrorReporter;
}

/** websocketURL points to our live graphql server */
Expand Down Expand Up @@ -226,9 +232,20 @@ function createManagedCoralContextProvider(
};

public render() {
// If the boundary is available from the reporter (also, if it's
// available) then use it to wrap the lower children for any error that
// happens.

// NOTE: (wyattjoh) there should be another way to do this better...
const Boundary = this.state.context.reporter?.ErrorBoundary;

return (
<CoralContextProvider value={this.state.context}>
{this.props.children}
{Boundary ? (
<Boundary>{this.props.children}</Boundary>
) : (
this.props.children
)}
{this.state.context.pym && <SendPymReady />}
</CoralContextProvider>
);
Expand Down Expand Up @@ -271,6 +288,7 @@ export default async function createManaged({
initLocalState = noop,
localesData,
pym,
reporter,
eventEmitter = new EventEmitter2({ wildcard: true, maxListeners: 20 }),
}: CreateContextArguments): Promise<ComponentType> {
// Listen for outside clicks.
Expand Down Expand Up @@ -337,6 +355,7 @@ export default async function createManaged({
timeagoFormatter,
pym,
eventEmitter,
reporter,
registerClickFarAway,
rest: createRestClient(clientID, accessTokenProvider),
postMessage: new PostMessageService(),
Expand Down
1 change: 1 addition & 0 deletions src/core/client/framework/lib/errors/index.ts
@@ -1,3 +1,4 @@
export { default as UnknownServerError } from "./unknownServerError";
export { default as InvalidRequestError } from "./invalidRequestError";
export { default as ModerationNudgeError } from "./moderationNudgeError";
export * from "./reporter";

0 comments on commit 15f7c10

Please sign in to comment.