Kostas Bariotis 5 лет назад
Родитель
Сommit
4ecbbab7da

+ 41 - 0
.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 }}

+ 41 - 0
.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 }}

+ 57 - 47
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",

+ 3 - 3
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",

+ 5 - 7
src/bug-issue-template.js

@@ -1,13 +1,11 @@
-export default `
-### Stack trace
+export default (sentryErrorId) => `
+### Scene content
 
 \`\`\`
-// paste stack trace here
+Paste scene content here
 \`\`\`
 
-### Scene content
+### Sentry Error ID
 
-\`\`\`
-// paste scene content here (if it doesn't contain sensitive data)
-\`\`\`
+${sentryErrorId}
 `;

+ 19 - 50
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<HTMLTextAreaElement>) {
@@ -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);
     }
@@ -125,25 +100,19 @@ export class TopErrorBoundary extends React.Component<
           </div>
           <div>
             <div className="ErrorSplash-paragraph">
+              {t("errorSplash.trackedToSentry_pre")}
+              {this.state.sentryEventId}
+              {t("errorSplash.trackedToSentry_post")}
+            </div>
+            <div className="ErrorSplash-paragraph">
               {t("errorSplash.openIssueMessage_pre")}
-              <button onClick={this.createGithubIssue}>
+              <button onClick={() => this.createGithubIssue()}>
                 {t("errorSplash.openIssueMessage_button")}
               </button>
               {t("errorSplash.openIssueMessage_post")}
             </div>
             <div className="ErrorSplash-paragraph">
               <div className="ErrorSplash-details">
-                <label>{t("errorSplash.errorStack")}</label>
-                <textarea
-                  rows={10}
-                  onPointerDown={this.selectTextArea}
-                  readOnly={true}
-                  value={
-                    this.state.unresolvedError
-                      ? t("errorSplash.errorStack_loading")
-                      : this.state.stack
-                  }
-                />
                 <label>{t("errorSplash.sceneContent")}</label>
                 <textarea
                   rows={5}

+ 19 - 0
src/index.tsx

@@ -1,10 +1,29 @@
 import React from "react";
 import ReactDOM from "react-dom";
+import * as Sentry from "@sentry/browser";
 import { TopErrorBoundary } from "./components/TopErrorBoundary";
 import { IsMobileProvider } from "./is-mobile";
 import { App } from "./components/App";
 import "./styles.scss";
 
+const SentyEnvHostnameMap: { [key: string]: string } = {
+  "excalidraw.com": "production",
+  "now.sh": "staging",
+};
+
+const onlineEnv = Object.keys(SentyEnvHostnameMap).find(
+  (item) => window.location.hostname.indexOf(item) >= 0,
+);
+
+Sentry.init({
+  // Disable Sentry locally to avoid noise
+  dsn: onlineEnv
+    ? "https://7bfc596a5bf945eda6b660d3015a5460@sentry.io/5179260"
+    : undefined,
+  environment: onlineEnv ? SentyEnvHostnameMap[onlineEnv] : undefined,
+  release: process.env.REACT_APP_GIT_SHA,
+});
+
 // Block pinch-zooming on iOS outside of the content area
 document.addEventListener(
   "touchmove",

+ 4 - 4
src/locales/en.json

@@ -105,11 +105,11 @@
     "clearCanvasMessage": "If reloading doesn't work, try ",
     "clearCanvasMessage_button": "clearing the canvas.",
     "clearCanvasCaveat": " This will result in loss of work ",
-    "openIssueMessage_pre": "Before doing so, we'd appreciate if you opened an issue on our ",
+    "trackedToSentry_pre": "The error with identifier ",
+    "trackedToSentry_post": " was tracked on our system.",
+    "openIssueMessage_pre": "We were very cautious not to include your scene information on the error. If your scene is not private, please consider following up on our ",
     "openIssueMessage_button": "bug tracker.",
-    "openIssueMessage_post": " Please include the following error stack trace (and if it's not private, also the scene content):",
-    "errorStack": "Error stack trace:",
-    "errorStack_loading": "Loading data. please wait...",
+    "openIssueMessage_post": " Please include information below by copying and pasting into the GitHub issue.",
     "sceneContent": "Scene content:"
   },
   "roomDialog": {