Skip to content
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

[CORL-1293] Sentry Error Integration #3109

Merged
merged 9 commits into from Aug 20, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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>
cvle marked this conversation as resolved.
Show resolved Hide resolved
</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";