From 383055a2de546e84ad9787420aa94ec16f66675a Mon Sep 17 00:00:00 2001 From: Jakub Majorek Date: Fri, 22 Jan 2021 15:05:26 +0100 Subject: [PATCH] SALEOR-2064 Add generic error tracker with Sentry adapter (#956) * Add generic error tracker with Sentry extension * Add Sentry webpack plugin * Update variable names and README * Update deploy-staging template * Update changelog --- .github/workflows/deploy-staging.yaml | 7 +- CHANGELOG.md | 1 + README.md | 41 +++++- package-lock.json | 120 ++++++++++++++++++ package.json | 5 +- src/auth/AuthProvider.tsx | 22 +++- src/components/AppLayout/AppLayout.tsx | 7 +- src/components/ErrorPage/ErrorPage.tsx | 11 +- src/containers/AppState/reducer.ts | 20 ++- src/containers/AppState/state.ts | 5 +- src/index.tsx | 12 +- src/services/errorTracking/adapters/Sentry.ts | 33 +++++ src/services/errorTracking/adapters/index.ts | 1 + src/services/errorTracking/index.ts | 11 ++ .../errorTracking/trackerFactory.test.ts | 83 ++++++++++++ src/services/errorTracking/trackerFactory.ts | 52 ++++++++ src/services/errorTracking/types.ts | 15 +++ webpack.config.js | 28 +++- 18 files changed, 451 insertions(+), 23 deletions(-) create mode 100644 src/services/errorTracking/adapters/Sentry.ts create mode 100644 src/services/errorTracking/adapters/index.ts create mode 100644 src/services/errorTracking/index.ts create mode 100644 src/services/errorTracking/trackerFactory.test.ts create mode 100644 src/services/errorTracking/trackerFactory.ts create mode 100644 src/services/errorTracking/types.ts diff --git a/.github/workflows/deploy-staging.yaml b/.github/workflows/deploy-staging.yaml index b3f52ea67..f38df4db9 100644 --- a/.github/workflows/deploy-staging.yaml +++ b/.github/workflows/deploy-staging.yaml @@ -11,6 +11,12 @@ jobs: API_URI: https://master.staging.saleor.cloud/graphql/ APP_MOUNT_URI: /dashboard/ STATIC_URL: /dashboard/static/ + SENTRY_ORG: sentry + SENTRY_PROJECT: dashboard + SENTRY_URL_PREFIX: "~/static" + ENVIRONMENT: master-staging + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} steps: - uses: actions/checkout@v2 - name: Package @@ -41,4 +47,3 @@ jobs: aws s3 sync build/dashboard s3://${{ secrets.AWS_STAGING_DEPLOYMENT_BUCKET }}/saleor-master-staging/static/ aws s3 cp build/dashboard/index.html s3://${{ secrets.AWS_STAGING_DEPLOYMENT_BUCKET }}/saleor-master-staging/ aws cloudfront create-invalidation --distribution-id ${{ secrets.AWS_STAGING_CF_DIST_ID }} --paths "/dashboard*" - diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b0927617..e3deeb342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ All notable, unreleased changes to this project will be documented in this file. - Add reference attributes - #917 by @orzechdev - Add product reference attributes - #948 by @orzechdev - Drop descriptionJson and contentJson fields - #950 by @jwm0 +- Add error tracking with Sentry adapter - #956 by @jwm0 # 2.11.1 diff --git a/README.md b/README.md index 1bdccfccd..4cda35bd8 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ $ npm i ### Configuration -There are two environment variables available for configuration: +There following environment variables are available for configuration: - `API_URI` (required) - URI of a running instance of Saleor GraphQL API. If you are running Saleor locally with the default settings, set `API_URI` to: `http://localhost:8000/graphql/`. @@ -88,4 +88,43 @@ To build the application bundle run: $ npm run build ``` +### Error Tracking + +Saleor Dashboard is using a generic error tracking wrapper function that takes care of the most popular use cases: + +- initializing the tracker +- capturing exceptions and (optionally) displaying the event id +- setting basic user data (this is opt-in and disabled by default) + +By default it ships with a Sentry adapter but any kind of error tracking software can be used by creating a custom adapter (using Sentry and TS types as an example). + +Example: + +```javascript +// src/services/errorTracking/index.ts + +import { CustomAdapter } from "./adapters/"; + +const errorTracker = ErrorTrackerFactory(CustomAdapter(config)); +``` + +##### Usage with Sentry adapter: + +Sentry is used as the default tracker so no changes in code are necessary and the configuration is done via environment variables. + +The following environment variables are available: + +``` +# Required +SENTRY_DSN= + +# Optional +# https://docs.sentry.io/product/cli/configuration/ +SENTRY_AUTH_TOKEN= +SENTRY_ORG= +SENTRY_PROJECT= +SENTRY_URL_PREFIX= +ENVIRONMENT= +``` + #### Crafted with ❤️ by [Mirumee Software](https://mirumee.com) diff --git a/package-lock.json b/package-lock.json index 032ae5a10..c07e8cbbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3153,6 +3153,120 @@ "any-observable": "^0.3.0" } }, + "@sentry/browser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.0.0.tgz", + "integrity": "sha512-R4+MHb5FyVZCz3EVnaquvT1mwOM2MWP4gBqjYEADY5m0XWoHiJf0skFkWt8iEKJanzGbhl4PMb9gHuJj6YfVLw==", + "requires": { + "@sentry/core": "6.0.0", + "@sentry/types": "6.0.0", + "@sentry/utils": "6.0.0", + "tslib": "^1.9.3" + } + }, + "@sentry/cli": { + "version": "1.61.0", + "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-1.61.0.tgz", + "integrity": "sha512-pHEhqP1bB4sdO7N5ow/IkRBrPbKT9HZRinq4PhTVIvmG+NW4VVuVZ6k4tlbp+JXmzMcUc/iXynVkTL7zJIlTQw==", + "dev": true, + "requires": { + "https-proxy-agent": "^5.0.0", + "mkdirp": "^0.5.5", + "node-fetch": "^2.6.0", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dev": true, + "requires": { + "agent-base": "6", + "debug": "4" + } + } + } + }, + "@sentry/core": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.0.0.tgz", + "integrity": "sha512-afAiOachs/WfGWc9LsJBFnJMhqQVENyzfSMnf7sLRvxPAw8n7IrXY0R09MKmG0SlAnTKN2pWoQFzFF+J3NuHBA==", + "requires": { + "@sentry/hub": "6.0.0", + "@sentry/minimal": "6.0.0", + "@sentry/types": "6.0.0", + "@sentry/utils": "6.0.0", + "tslib": "^1.9.3" + } + }, + "@sentry/hub": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.0.0.tgz", + "integrity": "sha512-s8IsW6LvEH7ACnniQcxxb/9uEyjmoQ/TAoryTJN2qyPzzrHTw8NCyMuJvK+8ivUvRViz5AvtuOFf8AJlh9lzeA==", + "requires": { + "@sentry/types": "6.0.0", + "@sentry/utils": "6.0.0", + "tslib": "^1.9.3" + } + }, + "@sentry/minimal": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.0.0.tgz", + "integrity": "sha512-daYdEzTr+ERMwViu6RpWHOfk0oZrSNqdx+7bejTqmFHqO4pt+9ZrMiw3vinL+MWQcKXwD95uXBz6O/ryrVdPtg==", + "requires": { + "@sentry/hub": "6.0.0", + "@sentry/types": "6.0.0", + "tslib": "^1.9.3" + } + }, + "@sentry/react": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sentry/react/-/react-6.0.0.tgz", + "integrity": "sha512-GYX110NSodd8wGUbnyxemndTijM+U7dI/WjFSPOyJdLB2hzzPjJ9kUqtuobT/JlGzbWE2278WysAuySne6bUGw==", + "requires": { + "@sentry/browser": "6.0.0", + "@sentry/minimal": "6.0.0", + "@sentry/types": "6.0.0", + "@sentry/utils": "6.0.0", + "hoist-non-react-statics": "^3.3.2", + "tslib": "^1.9.3" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + } + } + } + }, + "@sentry/types": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.0.0.tgz", + "integrity": "sha512-yueRSRGPCahuju/UMdtOt8LIIncbpwLINQd9Q8E4OXtoPpMHR6Oun8sMKCPd+Wq3piI5yRDzKkGCl+sH7mHVrA==" + }, + "@sentry/utils": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.0.0.tgz", + "integrity": "sha512-dMMWOT69bQ4CF1R33dOnXIOyiHRWsUAON3nFVljV1JNNTDA69YwaF9f5FIT0DKpO4qhgTlElsm8WgHI9prAVEQ==", + "requires": { + "@sentry/types": "6.0.0", + "tslib": "^1.9.3" + } + }, + "@sentry/webpack-plugin": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/@sentry/webpack-plugin/-/webpack-plugin-1.14.0.tgz", + "integrity": "sha512-1cS99mnHqASYtMlHi2J107p6x3lfC5NmLOgA0iI6avaaFes8RTQMlW8YT2CyrvhtQod1bViPZOlh3NOVC8vnOA==", + "dev": true, + "requires": { + "@sentry/cli": "^1.58.0" + } + }, "@sindresorhus/fnv1a": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@sindresorhus/fnv1a/-/fnv1a-1.2.0.tgz", @@ -18614,6 +18728,12 @@ "ipaddr.js": "1.9.0" } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", diff --git a/package.json b/package.json index 69bbb607d..88b73b4a1 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@material-ui/icons": "^4.5.1", "@material-ui/styles": "^4.5.2", "@saleor/macaw-ui": "^0.1.1-9", + "@sentry/react": "^6.0.0", "apollo": "^2.21.2", "apollo-cache-inmemory": "^1.6.5", "apollo-client": "^2.6.8", @@ -92,6 +93,7 @@ "@pollyjs/adapter-node-http": "^5.0.0", "@pollyjs/core": "^5.0.0", "@pollyjs/persister-fs": "^5.0.0", + "@sentry/webpack-plugin": "^1.14.0", "@storybook/addon-storyshots": "^5.2.8", "@storybook/react": "^5.1.9", "@testing-library/react-hooks": "^1.1.0", @@ -226,6 +228,7 @@ "test:e2e:dev": "start-server-and-test start http://localhost:9000 cy:open", "test": "jest src/", "transpile-messages": "node scripts/transpile-tx.js", - "lint": "npx eslint \"src/**/*.@(tsx|ts|jsx|js)\" --fix ; npx prettier --check \"src/**/*.@(tsx|ts|jsx|js)\" --write" + "lint": "npx eslint \"src/**/*.@(tsx|ts|jsx|js)\" --fix ; npx prettier --check \"src/**/*.@(tsx|ts|jsx|js)\" --write", + "postbuild": "rimraf ./build/**/*.js.map" } } diff --git a/src/auth/AuthProvider.tsx b/src/auth/AuthProvider.tsx index 37b75a5b9..7cfe3a77a 100644 --- a/src/auth/AuthProvider.tsx +++ b/src/auth/AuthProvider.tsx @@ -4,6 +4,7 @@ import { User } from "@saleor/fragments/types/User"; import useNotifier from "@saleor/hooks/useNotifier"; import { commonMessages } from "@saleor/intl"; import { getMutationStatus } from "@saleor/misc"; +import errorTracker from "@saleor/services/errorTracking"; import { isSupported as isCredentialsManagementAPISupported, login as loginWithCredentialsManagementAPI, @@ -52,13 +53,22 @@ export function useAuthProvider( }, []); useEffect(() => { - if (userContext && !userContext.isStaff) { - logout(); - notify({ - status: "error", - text: intl.formatMessage(commonMessages.unauthorizedDashboardAccess), - title: intl.formatMessage(commonMessages.insufficientPermissions) + if (userContext) { + const { id, email, firstName, lastName } = userContext; + errorTracker.setUserData({ + email, + id, + username: `${firstName} ${lastName}` }); + + if (!userContext.isStaff) { + logout(); + notify({ + status: "error", + text: intl.formatMessage(commonMessages.unauthorizedDashboardAccess), + title: intl.formatMessage(commonMessages.insufficientPermissions) + }); + } } }, [userContext]); diff --git a/src/components/AppLayout/AppLayout.tsx b/src/components/AppLayout/AppLayout.tsx index 4be74a99f..11ee9ce45 100644 --- a/src/components/AppLayout/AppLayout.tsx +++ b/src/components/AppLayout/AppLayout.tsx @@ -239,8 +239,11 @@ const AppLayout: React.FC = ({ children }) => {
{appState.error - ? appState.error === "unhandled" && ( - + ? appState.error.type === "unhandled" && ( + ) : children}
diff --git a/src/components/ErrorPage/ErrorPage.tsx b/src/components/ErrorPage/ErrorPage.tsx index 856230375..b6447ba8b 100644 --- a/src/components/ErrorPage/ErrorPage.tsx +++ b/src/components/ErrorPage/ErrorPage.tsx @@ -7,6 +7,7 @@ import SVG from "react-inlinesvg"; import { FormattedMessage } from "react-intl"; export interface ErrorPageProps { + id?: string | null; onBack: () => void; } @@ -31,6 +32,9 @@ const useStyles = makeStyles( margin: "0 auto", width: 830 }, + errorId: { + marginTop: theme.spacing(3) + }, innerContainer: { [theme.breakpoints.down("sm")]: { order: 1, @@ -58,7 +62,7 @@ const useStyles = makeStyles( ); const ErrorPage: React.FC = props => { - const { onBack } = props; + const { onBack, id } = props; const classes = useStyles(props); @@ -79,6 +83,11 @@ const ErrorPage: React.FC = props => { + {!!id && ( + + Error ID: {id} + + )}