Merge pull request #48 from Bouncey/feat/GA

Add google analytics
This commit is contained in:
Stuart Taylor
2018-05-18 19:07:32 +01:00
committed by Mrugesh Mohapatra
parent 54a9f70574
commit f90bb69cde
13 changed files with 155 additions and 27 deletions

View File

@ -5,7 +5,8 @@ module.exports = {
// '.module.css' https://regex101.com/r/VzwrKH/4
'^(?!.*\\.module\\.css$).*\\.css$': '<rootDir>/src/__mocks__/styleMock.js',
// CSS Modules - match files that end with 'module.css'
'\\.module\\.css$': 'identity-obj-proxy'
'\\.module\\.css$': 'identity-obj-proxy',
analytics: '<rootDir>/src/__mocks__/analyticsMock.js'
},
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/.cache/'],
globals: {

View File

@ -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",

View File

@ -0,0 +1,3 @@
export default {
event: () => {}
};

View File

@ -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()
);
}

View File

@ -0,0 +1,5 @@
import ReactGA from 'react-ga';
ReactGA.initialize('UA-55446531-10');
export default ReactGA;

View File

@ -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 => (
<li className='map-challenge-title' key={challenge.dashedName}>
<Link onClick={this.handleChallengeClick} to={challenge.fields.slug}>
<Link
onClick={this.handleChallengeClick(challenge.fields.slug)}
to={challenge.fields.slug}
>
{challenge.title}
</Link>
</li>

View File

@ -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 } } }) => (
<Fragment>
<Helmet
meta={[
{ name: 'description', content: 'Sample' },
{ name: 'keywords', content: 'sample, something' }
]}
/>
<Header />
<div className='app-wrapper'>
<main>{children()}</main>
</div>
<MapModal
nodes={edges
.map(({ node }) => node)
.filter(({ isPrivate }) => !isPrivate)}
/>
</Fragment>
);
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 (
<Fragment>
<Helmet
meta={[
{
name: 'description',
content:
'Learn to code with free online courses, programming ' +
'projects, and interview preparation for developer jobs.'
},
{ name: 'keywords', content: metaKeywords.join(', ') }
]}
/>
<Header />
<div className='app-wrapper'>
<main>{children()}</main>
</div>
<MapModal
nodes={edges
.map(({ node }) => node)
.filter(({ isPrivate }) => !isPrivate)}
/>
</Fragment>
);
}
}
Layout.propTypes = {
children: PropTypes.func,

View File

@ -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: {

View File

@ -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 });

View File

@ -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 (
<Modal
animation={false}

View File

@ -4,6 +4,7 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Button, Modal } from 'react-bootstrap';
import ga from '../../../analytics';
import { createQuestion, closeModal, isHelpModalOpenSelector } from '../redux';
const mapStateToProps = state => ({ 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 (
<Modal onHide={closeHelpModal} show={isOpen}>
<Modal.Header

View File

@ -5,6 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { Button, Modal } from 'react-bootstrap';
import ga from '../../../analytics';
import { isResetModalOpenSelector, closeModal, resetChallenge } from '../redux';
import './reset-modal.css';
@ -30,6 +31,9 @@ function withActions(...fns) {
}
function ResetModal({ reset, close, isOpen }) {
if (isOpen) {
ga.modalview('/reset-modal');
}
return (
<Modal
animation={false}

View File

@ -8544,6 +8544,13 @@ react-error-overlay@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-3.0.0.tgz#c2bc8f4d91f1375b3dad6d75265d51cd5eeaf655"
react-ga@^2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/react-ga/-/react-ga-2.5.2.tgz#1574b26e30ed668e4e74735527314393b22c55a9"
optionalDependencies:
prop-types "^15.6.0"
react "^15.6.2 || ^16.0"
react-helmet@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.0.tgz#a81811df21313a6d55c5f058c4aeba5d6f3d97a7"
@ -8717,6 +8724,15 @@ react@^15.6.0:
object-assign "^4.1.0"
prop-types "^15.5.10"
"react@^15.6.2 || ^16.0":
version "16.3.2"
resolved "https://registry.yarnpkg.com/react/-/react-16.3.2.tgz#fdc8420398533a1e58872f59091b272ce2f91ea9"
dependencies:
fbjs "^0.8.16"
loose-envify "^1.1.0"
object-assign "^4.1.1"
prop-types "^15.6.0"
react@^16.0.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-16.2.0.tgz#a31bd2dab89bff65d42134fa187f24d054c273ba"