diff --git a/.github/workflows/sentry-production.yml b/.github/workflows/sentry-production.yml new file mode 100644 index 00000000..8b979728 --- /dev/null +++ b/.github/workflows/sentry-production.yml @@ -0,0 +1,41 @@ +name: New Sentry Production Release + +on: + push: + branches: + - master + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1.0.0 + + - name: Setup Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: 12.x + + - name: Install and build + run: | + npm ci + npm run build:app + env: + CI: true + + - name: Install Sentry + run: | + curl -sL https://sentry.io/get-cli/ | bash + + - name: Create new Sentry release + run: | + export SENTRY_RELEASE=$(sentry-cli releases propose-version) + sentry-cli releases new $SENTRY_RELEASE --project $SENTRY_PROJECT + sentry-cli releases set-commits --auto $SENTRY_RELEASE + sentry-cli releases files $SENTRY_RELEASE upload-sourcemaps --no-rewrite ./build/static/js/ --url-prefix "~/static/js" + sentry-cli releases finalize $SENTRY_RELEASE + sentry-cli releases deploys $SENTRY_RELEASE new -e production + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} diff --git a/.github/workflows/sentry-staging.yml b/.github/workflows/sentry-staging.yml new file mode 100644 index 00000000..42f7cbda --- /dev/null +++ b/.github/workflows/sentry-staging.yml @@ -0,0 +1,41 @@ +name: New Sentry Staging Release + +on: + pull_request: + branches: + - master + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1.0.0 + + - name: Setup Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: 12.x + + - name: Install and build + run: | + npm ci + npm run build:app + env: + CI: true + + - name: Install Sentry + run: | + curl -sL https://sentry.io/get-cli/ | bash + + - name: Create new Sentry release + run: | + export SENTRY_RELEASE=$(sentry-cli releases propose-version) + sentry-cli releases new $SENTRY_RELEASE --project $SENTRY_PROJECT + sentry-cli releases set-commits --auto $SENTRY_RELEASE + sentry-cli releases files $SENTRY_RELEASE upload-sourcemaps --no-rewrite ./build/static/js/ --url-prefix "~/static/js" + sentry-cli releases finalize $SENTRY_RELEASE + sentry-cli releases deploys $SENTRY_RELEASE new -e staging + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + SENTRY_ORG: ${{ secrets.SENTRY_ORG }} + SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} diff --git a/package-lock.json b/package-lock.json index aa5ea01c..2f6b9e0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1341,6 +1341,63 @@ "any-observable": "^0.3.0" } }, + "@sentry/browser": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.15.4.tgz", + "integrity": "sha512-l/auT1HtZM3KxjCGQHYO/K51ygnlcuOrM+7Ga8gUUbU9ZXDYw6jRi0+Af9aqXKmdDw1naNxr7OCSy6NBrLWVZw==", + "requires": { + "@sentry/core": "5.15.4", + "@sentry/types": "5.15.4", + "@sentry/utils": "5.15.4", + "tslib": "^1.9.3" + } + }, + "@sentry/core": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.15.4.tgz", + "integrity": "sha512-9KP4NM4SqfV5NixpvAymC7Nvp36Zj4dU2fowmxiq7OIbzTxGXDhwuN/t0Uh8xiqlkpkQqSECZ1OjSFXrBldetQ==", + "requires": { + "@sentry/hub": "5.15.4", + "@sentry/minimal": "5.15.4", + "@sentry/types": "5.15.4", + "@sentry/utils": "5.15.4", + "tslib": "^1.9.3" + } + }, + "@sentry/hub": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.15.4.tgz", + "integrity": "sha512-1XJ1SVqadkbUT4zLS0TVIVl99si7oHizLmghR8LMFl5wOkGEgehHSoOydQkIAX2C7sJmaF5TZ47ORBHgkqclUg==", + "requires": { + "@sentry/types": "5.15.4", + "@sentry/utils": "5.15.4", + "tslib": "^1.9.3" + } + }, + "@sentry/minimal": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.15.4.tgz", + "integrity": "sha512-GL4GZ3drS9ge+wmxkHBAMEwulaE7DMvAEfKQPDAjg2p3MfcCMhAYfuY4jJByAC9rg9OwBGGehz7UmhWMFjE0tw==", + "requires": { + "@sentry/hub": "5.15.4", + "@sentry/types": "5.15.4", + "tslib": "^1.9.3" + } + }, + "@sentry/types": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.15.4.tgz", + "integrity": "sha512-quPHPpeAuwID48HLPmqBiyXE3xEiZLZ5D3CEbU3c3YuvvAg8qmfOOTI6z4Z3Eedi7flvYpnx3n7N3dXIEz30Eg==" + }, + "@sentry/utils": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.15.4.tgz", + "integrity": "sha512-lO8SLBjrUDGADl0LOkd55R5oL510d/1SaI08/IBHZCxCUwI4TiYo5EPECq8mrj3XGfgCyq9osw33bymRlIDuSQ==", + "requires": { + "@sentry/types": "5.15.4", + "tslib": "^1.9.3" + } + }, "@svgr/babel-plugin-add-jsx-attribute": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz", @@ -5219,14 +5276,6 @@ "is-arrayish": "^0.2.1" } }, - "error-stack-parser": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.6.tgz", - "integrity": "sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ==", - "requires": { - "stackframe": "^1.1.1" - } - }, "es-abstract": { "version": "1.17.4", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", @@ -14357,50 +14406,11 @@ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==" }, - "stack-generator": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.5.tgz", - "integrity": "sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q==", - "requires": { - "stackframe": "^1.1.1" - } - }, "stack-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==" }, - "stackframe": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.1.1.tgz", - "integrity": "sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ==" - }, - "stacktrace-gps": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz", - "integrity": "sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg==", - "requires": { - "source-map": "0.5.6", - "stackframe": "^1.1.1" - }, - "dependencies": { - "source-map": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", - "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=" - } - } - }, - "stacktrace-js": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stacktrace-js/-/stacktrace-js-2.0.2.tgz", - "integrity": "sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==", - "requires": { - "error-stack-parser": "^2.0.6", - "stack-generator": "^2.0.5", - "stacktrace-gps": "^3.0.4" - } - }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", diff --git a/package.json b/package.json index c0cd8a69..14e111cf 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ ] }, "dependencies": { + "@sentry/browser": "5.15.4", "browser-nativefs": "0.4.0", "i18next-browser-languagedetector": "4.0.2", "nanoid": "2.1.11", @@ -26,8 +27,7 @@ "react-dom": "16.13.1", "react-scripts": "3.4.1", "roughjs": "4.0.4", - "socket.io-client": "2.3.0", - "stacktrace-js": "2.0.2" + "socket.io-client": "2.3.0" }, "devDependencies": { "@testing-library/jest-dom": "5.3.0", @@ -100,7 +100,7 @@ "scripts": { "build": "npm run build:app && npm run build:zip", "build-node": "node ./scripts/build-node.js", - "build:app": "react-scripts build", + "build:app": "REACT_APP_GIT_SHA=$NOW_GITHUB_COMMIT_SHA react-scripts build", "build:zip": "node ./scripts/build-version.js", "eject": "react-scripts eject", "fix": "npm run fix:other && npm run fix:code", diff --git a/src/bug-issue-template.js b/src/bug-issue-template.js index 9dfe1eaf..bc048864 100644 --- a/src/bug-issue-template.js +++ b/src/bug-issue-template.js @@ -1,13 +1,11 @@ -export default ` -### Stack trace - -\`\`\` -// paste stack trace here -\`\`\` - +export default (sentryErrorId) => ` ### Scene content \`\`\` -// paste scene content here (if it doesn't contain sensitive data) +Paste scene content here \`\`\` + +### Sentry Error ID + +${sentryErrorId} `; diff --git a/src/components/TopErrorBoundary.tsx b/src/components/TopErrorBoundary.tsx index b715dd3a..a0741036 100644 --- a/src/components/TopErrorBoundary.tsx +++ b/src/components/TopErrorBoundary.tsx @@ -1,11 +1,11 @@ import React from "react"; +import * as Sentry from "@sentry/browser"; import { resetCursor } from "../utils"; import { t } from "../i18n"; interface TopErrorBoundaryState { - unresolvedError: Error[] | null; hasError: boolean; - stack: string; + sentryEventId: string; localStorage: string; } @@ -14,9 +14,8 @@ export class TopErrorBoundary extends React.Component< TopErrorBoundaryState > { state: TopErrorBoundaryState = { - unresolvedError: null, hasError: false, - stack: "", + sentryEventId: "", localStorage: "", }; @@ -24,7 +23,7 @@ export class TopErrorBoundary extends React.Component< return this.state.hasError ? this.errorSplash() : this.props.children; } - componentDidCatch(error: Error) { + componentDidCatch(error: Error, errorInfo: any) { resetCursor(); const _localStorage: any = {}; for (const [key, value] of Object.entries({ ...localStorage })) { @@ -34,39 +33,17 @@ export class TopErrorBoundary extends React.Component< _localStorage[key] = value; } } - this.setState((state) => ({ - hasError: true, - unresolvedError: state.unresolvedError - ? state.unresolvedError.concat(error) - : [error], - localStorage: JSON.stringify(_localStorage), - })); - } - async componentDidUpdate() { - if (this.state.unresolvedError !== null) { - let stack = ""; - for (const error of this.state.unresolvedError) { - if (stack) { - stack += `\n\n================\n\n`; - } - stack += `${error.message}:\n\n`; - try { - const StackTrace = await import("stacktrace-js"); - stack += (await StackTrace.fromError(error)).join("\n"); - } catch (error) { - console.error(error); - stack += error.stack || ""; - } - } + Sentry.withScope((scope) => { + scope.setExtras(errorInfo); + const eventId = Sentry.captureException(error); this.setState((state) => ({ - unresolvedError: null, - stack: `${ - state.stack ? `${state.stack}\n\n================\n\n${stack}` : stack - }`, + hasError: true, + sentryEventId: eventId, + localStorage: JSON.stringify(_localStorage), })); - } + }); } private selectTextArea(event: React.MouseEvent) { @@ -79,10 +56,8 @@ export class TopErrorBoundary extends React.Component< private async createGithubIssue() { let body = ""; try { - const templateStr = (await import("../bug-issue-template")).default; - if (typeof templateStr === "string") { - body = encodeURIComponent(templateStr); - } + const templateStrFn = (await import("../bug-issue-template")).default; + body = encodeURIComponent(templateStrFn(this.state.sentryEventId)); } catch (error) { console.error(error); } @@ -124,26 +99,20 @@ export class TopErrorBoundary extends React.Component<
+
+ {t("errorSplash.trackedToSentry_pre")} + {this.state.sentryEventId} + {t("errorSplash.trackedToSentry_post")} +
{t("errorSplash.openIssueMessage_pre")} - {t("errorSplash.openIssueMessage_post")}
- -