From f90bb69cde61c45b67610820911d37e329375e22 Mon Sep 17 00:00:00 2001 From: Stuart Taylor Date: Fri, 18 May 2018 19:07:32 +0100 Subject: [PATCH] Merge pull request #48 from Bouncey/feat/GA Add google analytics --- packages/learn/jest.config.js | 3 +- packages/learn/package.json | 1 + packages/learn/src/__mocks__/analyticsMock.js | 3 + .../learn/src/analytics/analytics-epic.js | 15 +++ packages/learn/src/analytics/index.js | 5 + .../src/components/Map/components/Block.js | 20 +++- packages/learn/src/layouts/index.js | 102 ++++++++++++++---- packages/learn/src/redux/store.js | 3 +- .../src/templates/Challenges/classic/Show.js | 2 +- .../Challenges/components/CompletionModal.js | 4 + .../Challenges/components/HelpModal.js | 4 + .../Challenges/components/ResetModal.js | 4 + packages/learn/yarn.lock | 16 +++ 13 files changed, 155 insertions(+), 27 deletions(-) create mode 100644 packages/learn/src/__mocks__/analyticsMock.js create mode 100644 packages/learn/src/analytics/analytics-epic.js create mode 100644 packages/learn/src/analytics/index.js diff --git a/packages/learn/jest.config.js b/packages/learn/jest.config.js index 813e1c7457..d1e351075e 100644 --- a/packages/learn/jest.config.js +++ b/packages/learn/jest.config.js @@ -5,7 +5,8 @@ module.exports = { // '.module.css' https://regex101.com/r/VzwrKH/4 '^(?!.*\\.module\\.css$).*\\.css$': '/src/__mocks__/styleMock.js', // CSS Modules - match files that end with 'module.css' - '\\.module\\.css$': 'identity-obj-proxy' + '\\.module\\.css$': 'identity-obj-proxy', + analytics: '/src/__mocks__/analyticsMock.js' }, testPathIgnorePatterns: ['/node_modules/', '/.cache/'], globals: { diff --git a/packages/learn/package.json b/packages/learn/package.json index 2d8e0a6076..bdc5ce2efb 100644 --- a/packages/learn/package.json +++ b/packages/learn/package.json @@ -35,6 +35,7 @@ "react": "16", "react-bootstrap": "^0.32.1", "react-dom": "16", + "react-ga": "^2.5.2", "react-helmet": "^5.2.0", "react-monaco-editor": "^0.14.1", "react-redux": "^5.0.7", diff --git a/packages/learn/src/__mocks__/analyticsMock.js b/packages/learn/src/__mocks__/analyticsMock.js new file mode 100644 index 0000000000..efefaa3c93 --- /dev/null +++ b/packages/learn/src/__mocks__/analyticsMock.js @@ -0,0 +1,3 @@ +export default { + event: () => {} +}; diff --git a/packages/learn/src/analytics/analytics-epic.js b/packages/learn/src/analytics/analytics-epic.js new file mode 100644 index 0000000000..073b13fa6e --- /dev/null +++ b/packages/learn/src/analytics/analytics-epic.js @@ -0,0 +1,15 @@ +import { tap, ignoreElements } from 'rxjs/operators'; + +import ga from './'; + +export default function analyticsEpic(action$) { + return action$.pipe( + tap(({ type }) => { + ga.event({ + category: 'Redux Action', + action: type + }); + }), + ignoreElements() + ); +} diff --git a/packages/learn/src/analytics/index.js b/packages/learn/src/analytics/index.js new file mode 100644 index 0000000000..8c917a9466 --- /dev/null +++ b/packages/learn/src/analytics/index.js @@ -0,0 +1,5 @@ +import ReactGA from 'react-ga'; + +ReactGA.initialize('UA-55446531-10'); + +export default ReactGA; diff --git a/packages/learn/src/components/Map/components/Block.js b/packages/learn/src/components/Map/components/Block.js index b5405ea8b6..cc29dc8fcb 100644 --- a/packages/learn/src/components/Map/components/Block.js +++ b/packages/learn/src/components/Map/components/Block.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import Link, { navigateTo } from 'gatsby-link'; +import ga from '../../../analytics'; import { makeExpandedBlockSelector, toggleBlock } from '../redux'; import { toggleMapModal } from '../../../redux/app'; import Caret from '../../icons/Caret'; @@ -44,18 +45,31 @@ export class Block extends PureComponent { .slice(0, -1) .join('/'); toggleBlock(blockDashedName); + ga.event({ + category: 'Map Block Click', + action: blockDashedName + }); return navigateTo(blockPath); } - handleChallengeClick() { - this.props.toggleMapModal(); + handleChallengeClick(slug) { + return () => { + this.props.toggleMapModal(); + return ga.event({ + category: 'Map Challenge Click', + action: slug + }); + }; } renderChallenges(challenges) { // TODO: Split this into a Challenge Component and add tests return challenges.map(challenge => (
  • - + {challenge.title}
  • diff --git a/packages/learn/src/layouts/index.js b/packages/learn/src/layouts/index.js index 7684b144bb..a547083ee5 100644 --- a/packages/learn/src/layouts/index.js +++ b/packages/learn/src/layouts/index.js @@ -1,10 +1,11 @@ /* global graphql */ -import React, { Fragment } from 'react'; +import React, { Fragment, PureComponent } from 'react'; import PropTypes from 'prop-types'; import Helmet from 'react-helmet'; -import { AllChallengeNode } from '../redux/propTypes'; +import ga from '../analytics'; +import { AllChallengeNode } from '../redux/propTypes'; import Header from '../components/Header'; import MapModal from '../components/MapModal'; @@ -12,25 +13,84 @@ import './global.css'; import 'react-reflex/styles.css'; import './layout.css'; -const Layout = ({ children, data: { allChallengeNode: { edges } } }) => ( - - -
    -
    -
    {children()}
    -
    - node) - .filter(({ isPrivate }) => !isPrivate)} - /> - -); +const metaKeywords = [ + 'javascript', + 'js', + 'website', + 'web', + 'development', + 'free', + 'code', + 'camp', + 'course', + 'courses', + 'html', + 'css', + 'react', + 'redux', + 'api', + 'front', + 'back', + 'end', + 'learn', + 'tutorial', + 'programming' +]; + +class Layout extends PureComponent { + state = { + location: '' + }; + componentDidMount() { + const url = window.location.pathname + window.location.search; + ga.pageview(url); + /* eslint-disable react/no-did-mount-set-state */ + // this is for local location tracking only, no re-rendering required + this.setState(state => ({ + ...state, + location: url + })); + } + componentDidUpdate() { + const url = window.location.pathname + window.location.search; + if (url !== this.state.location) { + ga.pageview(url); + /* eslint-disable react/no-did-update-set-state */ + // this is for local location tracking only, no re-rendering required + this.setState(state => ({ + ...state, + location: url + })); + } + } + render() { + const { children, data: { allChallengeNode: { edges } } } = this.props; + return ( + + +
    +
    +
    {children()}
    +
    + node) + .filter(({ isPrivate }) => !isPrivate)} + /> + + ); + } +} Layout.propTypes = { children: PropTypes.func, diff --git a/packages/learn/src/redux/store.js b/packages/learn/src/redux/store.js index ef826221c1..a437d514c1 100644 --- a/packages/learn/src/redux/store.js +++ b/packages/learn/src/redux/store.js @@ -8,6 +8,7 @@ import { routerReducer as router, routerMiddleware } from 'react-router-redux'; import { reducer as formReducer } from 'redux-form'; +import analyticsEpic from '../analytics/analytics-epic'; import { reducer as app, epics as appEpics } from './app'; import { reducer as challenge, @@ -23,7 +24,7 @@ const rootReducer = combineReducers({ router }); -const rootEpic = combineEpics(...appEpics, ...challengeEpics); +const rootEpic = combineEpics(analyticsEpic, ...appEpics, ...challengeEpics); const epicMiddleware = createEpicMiddleware(rootEpic, { dependencies: { diff --git a/packages/learn/src/templates/Challenges/classic/Show.js b/packages/learn/src/templates/Challenges/classic/Show.js index 4a123ce7c1..63e04a16b9 100644 --- a/packages/learn/src/templates/Challenges/classic/Show.js +++ b/packages/learn/src/templates/Challenges/classic/Show.js @@ -115,8 +115,8 @@ class ShowClassic extends PureComponent { }, pathContext: { challengeMeta } } = this.props; - updateSuccessMessage(randomCompliment()); if (prevTitle !== currentTitle) { + updateSuccessMessage(randomCompliment()); createFiles(files); initTests(tests); updateChallengeMeta({ ...challengeMeta, title: currentTitle }); diff --git a/packages/learn/src/templates/Challenges/components/CompletionModal.js b/packages/learn/src/templates/Challenges/components/CompletionModal.js index 7dc0d97e9c..d1afd002c3 100644 --- a/packages/learn/src/templates/Challenges/components/CompletionModal.js +++ b/packages/learn/src/templates/Challenges/components/CompletionModal.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { Button, Modal } from 'react-bootstrap'; +import ga from '../../../analytics'; import GreenPass from './icons/GreenPass'; import './completion-modal.css'; @@ -57,6 +58,9 @@ export class CompletionModal extends PureComponent { handleKeypress, message } = this.props; + if (isOpen) { + ga.modalview('/completion-modal'); + } return ( ({ isOpen: isHelpModalOpenSelector(state) }); @@ -26,6 +27,9 @@ const RSA = export class HelpModal extends PureComponent { render() { const { isOpen, closeHelpModal, createQuestion } = this.props; + if (isOpen) { + ga.modalview('/help-modal'); + } return (