From a0c3d5bd082533a3905a7eaca205053635f8d98a Mon Sep 17 00:00:00 2001 From: Stuart Taylor Date: Wed, 7 Mar 2018 15:04:42 +0000 Subject: [PATCH 1/3] feat(opbeat): Enable opbeat-react for frontend performance tracking --- .babelrc | 1 + client/index.js | 22 ++++++++++- gulpfile.js | 1 + opbeat-config.js | 5 +++ package-lock.json | 81 ++++++++++++++++++++++++++++++++++++++- package.json | 2 + server/middlewares/csp.js | 3 +- 7 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 opbeat-config.js diff --git a/.babelrc b/.babelrc index e4b307e90b..49378940ef 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,7 @@ { "presets": [ "es2015", "react", "stage-0" ], "plugins": [ + "add-react-displayname", "transform-runtime", "babel-plugin-add-module-exports", "lodash", diff --git a/client/index.js b/client/index.js index 3d59d7c821..f2cf0ed844 100644 --- a/client/index.js +++ b/client/index.js @@ -1,3 +1,5 @@ +import initOpbeat from 'opbeat-react'; +import { createOpbeatMiddleware } from 'opbeat-react/redux'; import Rx from 'rx'; import debug from 'debug'; import { render } from 'redux-epic'; @@ -17,6 +19,22 @@ import { saveToColdStorage } from './cold-reload'; +import opBeat from '../opbeat-config'; + +const localhostRE = /^(localhost|127.|0.)/; +const enableOpbeat = !localhostRE.test(window.location.hostname); + +if (enableOpbeat && opBeat) { + const { orgId, appId } = opBeat; + if (!orgId || !appId) { + console.error('OpBeat credentials not found in ~/opbeat-config'); + } + initOpbeat({ + orgId, + appId + }); +} + const isDev = Rx.config.longStackSupport = debug.enabled('fcc:*'); const log = debug('fcc:client'); const hotReloadTimeout = 2000; @@ -41,6 +59,7 @@ const epicOptions = { history: _history }; + const DOMContainer = document.getElementById('fcc'); const defaultState = isColdStored() ? getColdStorage() : @@ -60,7 +79,8 @@ createApp({ defaultState, epics, epicOptions, - enhancers: isDev && devToolsExtension && [ devToolsExtension() ] + enhancers: isDev && devToolsExtension && [ devToolsExtension() ], + middlewares: enableOpbeat && [ createOpbeatMiddleware() ] }) .doOnNext(({ store }) => { if (module.hot && typeof module.hot.accept === 'function') { diff --git a/gulpfile.js b/gulpfile.js index 51997e317d..2f91190f20 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -107,6 +107,7 @@ const paths = { ], vendorMain: [ + resolve('opbeat-react'), resolve('jquery', '.js', '.min.js'), resolve('bootstrap', 'npm.js', 'bootstrap.min.js'), resolve('d3', '.js', '.min.js'), diff --git a/opbeat-config.js b/opbeat-config.js new file mode 100644 index 0000000000..6d90bd553e --- /dev/null +++ b/opbeat-config.js @@ -0,0 +1,5 @@ +export default { + // replace these values with your app specific credentials + orgId: '', + appId: '' +}; diff --git a/package-lock.json b/package-lock.json index beb66c890d..58c9f2d347 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1119,6 +1119,11 @@ "integrity": "sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU=", "dev": true }, + "babel-plugin-add-react-displayname": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz", + "integrity": "sha1-M51M3be2X9YtHfnbn+BN4TQSK9U=" + }, "babel-plugin-check-es2015-constants": { "version": "6.22.0", "resolved": "https://registry.npmjs.org/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz", @@ -4765,6 +4770,14 @@ "is-arrayish": "0.2.1" } }, + "error-stack-parser": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-1.3.6.tgz", + "integrity": "sha1-4Oc7k+QXE40c18C3RrGkoUhUwpI=", + "requires": { + "stackframe": "0.3.1" + } + }, "es-abstract": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz", @@ -10589,6 +10602,11 @@ } } }, + "loglevel": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz", + "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=" + }, "loglevelnext": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/loglevelnext/-/loglevelnext-1.0.2.tgz", @@ -12896,6 +12914,27 @@ "fast-safe-stringify": "1.2.0" } }, + "opbeat-js-core": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/opbeat-js-core/-/opbeat-js-core-0.3.2.tgz", + "integrity": "sha1-/hIMzCF0j4gz2lMLZAspvOX98iU=", + "requires": { + "error-stack-parser": "1.3.6", + "loglevel": "1.6.1", + "stack-generator": "1.1.0", + "url-parse": "1.2.0" + } + }, + "opbeat-react": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/opbeat-react/-/opbeat-react-3.4.0.tgz", + "integrity": "sha1-ivOLnjIq3JBQltk7aCJ8hBdddf0=", + "requires": { + "loglevel": "1.6.1", + "opbeat-js-core": "0.3.2", + "opbeat-zone": "0.8.12-prm" + } + }, "opbeat-release-tracker": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/opbeat-release-tracker/-/opbeat-release-tracker-1.1.1.tgz", @@ -12905,6 +12944,11 @@ "opbeat-http-client": "1.2.2" } }, + "opbeat-zone": { + "version": "0.8.12-prm", + "resolved": "https://registry.npmjs.org/opbeat-zone/-/opbeat-zone-0.8.12-prm.tgz", + "integrity": "sha1-76aLeSlygV+mghn5sKmS6qWb5hY=" + }, "open": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", @@ -13776,6 +13820,11 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "querystringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", + "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=" + }, "raf": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.0.tgz", @@ -14655,8 +14704,7 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "reselect": { "version": "3.0.1", @@ -16156,6 +16204,26 @@ "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.6.tgz", "integrity": "sha1-kQ9dKu17Ugxud3SZwfMuE5/eyxA=" }, + "stack-generator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-1.1.0.tgz", + "integrity": "sha1-NvapIHUabBD0maE8Msu19RoLiyU=", + "requires": { + "stackframe": "1.0.4" + }, + "dependencies": { + "stackframe": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.0.4.tgz", + "integrity": "sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw==" + } + } + }, + "stackframe": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-0.3.1.tgz", + "integrity": "sha1-M6qE8Rd6VUjIk1Uzy/6zQgl19aQ=" + }, "stackman": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/stackman/-/stackman-2.0.1.tgz", @@ -17798,6 +17866,15 @@ "integrity": "sha1-wHJ1aWetJLi1nldBVRyqx49QuLc=", "dev": true }, + "url-parse": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz", + "integrity": "sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==", + "requires": { + "querystringify": "1.0.0", + "requires-port": "1.0.0" + } + }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", diff --git a/package.json b/package.json index 52781b29bd..fdccfc1124 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "dependencies": { "accepts": "^1.3.0", "babel-core": "^6.18.1", + "babel-plugin-add-react-displayname": "0.0.5", "babel-preset-es2015": "^6.3.13", "babel-preset-react": "^6.3.13", "babel-register": "^6.3.0", @@ -97,6 +98,7 @@ "normalize-url": "^1.3.1", "normalizr": "2.2.1", "opbeat": "^4.14.0", + "opbeat-react": "^3.4.0", "passport": "^0.4.0", "passport-facebook": "^2.0.0", "passport-github": "^1.0.0", diff --git a/server/middlewares/csp.js b/server/middlewares/csp.js index 0464b336e1..868a0c6bc3 100644 --- a/server/middlewares/csp.js +++ b/server/middlewares/csp.js @@ -2,7 +2,8 @@ import helmet from 'helmet'; let trusted = [ "'self'", - 'https://search.freecodecamp.org' + 'https://search.freecodecamp.org', + 'https://*.opbeat.com' ]; const host = process.env.HOST || 'localhost'; From fa59bcfd7b0b8cebab84c39a995f600add1c4d64 Mon Sep 17 00:00:00 2001 From: Stuart Taylor Date: Fri, 9 Mar 2018 15:59:45 +0000 Subject: [PATCH 2/3] fix(credentials): Pass credentials through DefinePlugin --- client/index.js | 14 ++++++-------- opbeat-config.js | 5 ----- sample.env | 3 +++ webpack.config.js | 4 +++- 4 files changed, 12 insertions(+), 14 deletions(-) delete mode 100644 opbeat-config.js diff --git a/client/index.js b/client/index.js index f2cf0ed844..f09d2cbbe9 100644 --- a/client/index.js +++ b/client/index.js @@ -1,3 +1,4 @@ +/* global __OPBEAT__ORG_ID __OPBEAT__APP_ID */ import initOpbeat from 'opbeat-react'; import { createOpbeatMiddleware } from 'opbeat-react/redux'; import Rx from 'rx'; @@ -19,19 +20,16 @@ import { saveToColdStorage } from './cold-reload'; -import opBeat from '../opbeat-config'; - const localhostRE = /^(localhost|127.|0.)/; const enableOpbeat = !localhostRE.test(window.location.hostname); -if (enableOpbeat && opBeat) { - const { orgId, appId } = opBeat; - if (!orgId || !appId) { - console.error('OpBeat credentials not found in ~/opbeat-config'); +if (enableOpbeat) { + if (!__OPBEAT__ORG_ID || !__OPBEAT__APP_ID) { + console.error('OpBeat credentials not found in .env'); } initOpbeat({ - orgId, - appId + orgId: __OPBEAT__ORG_ID, + appId: __OPBEAT__APP_ID }); } diff --git a/opbeat-config.js b/opbeat-config.js deleted file mode 100644 index 6d90bd553e..0000000000 --- a/opbeat-config.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - // replace these values with your app specific credentials - orgId: '', - appId: '' -}; diff --git a/sample.env b/sample.env index 3486cd5d14..2d4f82a7ad 100644 --- a/sample.env +++ b/sample.env @@ -1,5 +1,8 @@ MONGOHQ_URL='mongodb://localhost:27017/freecodecamp' +OPBEAT_FRONTEND_ORG_ID=stuff +OPBEAT_FRONTEND_APP_ID=stuff + FACEBOOK_ID=stuff FACEBOOK_SECRET=stuff diff --git a/webpack.config.js b/webpack.config.js index af0e03f015..d66c49ef5d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -55,7 +55,9 @@ module.exports = { 'process.env': { NODE_ENV: JSON.stringify(__DEV__ ? 'development' : 'production') }, - __DEVTOOLS__: !__DEV__ + __DEVTOOLS__: !__DEV__, + __OPBEAT__ORG_ID: JSON.stringify(process.env.OPBEAT_FRONTEND_ORG_ID), + __OPBEAT__APP_ID: JSON.stringify(process.env.OPBEAT_FRONTEND_APP_ID) }), // Use browser version of visionmedia-debug new webpack.NormalModuleReplacementPlugin( From 73b8ba96afb85817cde55b35d0e00e93a4db5dd2 Mon Sep 17 00:00:00 2001 From: Stuart Taylor Date: Tue, 13 Mar 2018 22:56:44 +0000 Subject: [PATCH 3/3] fix(env): Use NODE_ENV to enable/disable opbeat frontend --- client/index.js | 10 +++++++--- webpack.config.js | 8 ++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/client/index.js b/client/index.js index f09d2cbbe9..dd375a9c92 100644 --- a/client/index.js +++ b/client/index.js @@ -1,4 +1,3 @@ -/* global __OPBEAT__ORG_ID __OPBEAT__APP_ID */ import initOpbeat from 'opbeat-react'; import { createOpbeatMiddleware } from 'opbeat-react/redux'; import Rx from 'rx'; @@ -20,8 +19,13 @@ import { saveToColdStorage } from './cold-reload'; -const localhostRE = /^(localhost|127.|0.)/; -const enableOpbeat = !localhostRE.test(window.location.hostname); +const { + __OPBEAT__ORG_ID, + __OPBEAT__APP_ID, + NODE_ENV +} = process.env; + +const enableOpbeat = NODE_ENV !== 'development'; if (enableOpbeat) { if (!__OPBEAT__ORG_ID || !__OPBEAT__APP_ID) { diff --git a/webpack.config.js b/webpack.config.js index d66c49ef5d..fe134849ba 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -53,11 +53,11 @@ module.exports = { plugins: [ new webpack.DefinePlugin({ 'process.env': { - NODE_ENV: JSON.stringify(__DEV__ ? 'development' : 'production') + NODE_ENV: JSON.stringify(__DEV__ ? 'development' : 'production'), + __OPBEAT__ORG_ID: JSON.stringify(process.env.OPBEAT_FRONTEND_ORG_ID), + __OPBEAT__APP_ID: JSON.stringify(process.env.OPBEAT_FRONTEND_APP_ID) }, - __DEVTOOLS__: !__DEV__, - __OPBEAT__ORG_ID: JSON.stringify(process.env.OPBEAT_FRONTEND_ORG_ID), - __OPBEAT__APP_ID: JSON.stringify(process.env.OPBEAT_FRONTEND_APP_ID) + __DEVTOOLS__: !__DEV__ }), // Use browser version of visionmedia-debug new webpack.NormalModuleReplacementPlugin(