Clean up and refactor side wide shared JS code (#4387)

* cleanup and refactor side wide shared JS code

* clean up Google Analytics related code

* clean up 'dnt.js' code by merging it into 'common/google-analytics.js'

* forgot to uncomment a line of code

* move 'react-ga-proxy.js' to 'common' dir

* split code/modules further

* updates

* Update source/js/common/inject-react/join-us.js

Co-Authored-By: Pomax <pomax@nihongoresources.com>

* Update source/js/buyers-guide/analytics-events.js

Co-Authored-By: Pomax <pomax@nihongoresources.com>

Co-authored-by: Pomax <pomax@nihongoresources.com>
This commit is contained in:
Mavis Ou 2020-03-26 11:26:25 -07:00 коммит произвёл GitHub
Родитель 7cbfa4fa85
Коммит 07d2aa0a56
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
20 изменённых файлов: 201 добавлений и 192 удалений

Просмотреть файл

@ -1,65 +0,0 @@
export default {
initialize: function(gaIdentifier = undefined) {
if (!gaIdentifier) {
console.warn(`No GA identifier found: skipping bootstrap step`);
}
var _dntStatus = navigator.doNotTrack || navigator.msDoNotTrack;
var fxMatch = navigator.userAgent.match(/Firefox\/(\d+)/);
var ie10Match = navigator.userAgent.match(/MSIE 10/i);
var w8Match = navigator.appVersion.match(/Windows NT 6.2/);
if (fxMatch && Number(fxMatch[1]) < 32) {
_dntStatus = `Unspecified`;
} else if (ie10Match && w8Match) {
_dntStatus = `Unspecified`;
} else {
_dntStatus =
{
"0": `Disabled`,
"1": `Enabled`
}[_dntStatus] || `Unspecified`;
}
if (_dntStatus !== `Enabled`) {
(function(i, s, o, g, r, a, m) {
i[`GoogleAnalyticsObject`] = r;
(i[r] =
i[r] ||
function() {
(i[r].q = i[r].q || []).push(arguments);
}),
(i[r].l = 1 * new Date());
(a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m);
})(
window,
document,
`script`,
`https://www.google-analytics.com/analytics.js`,
`ga`
);
ga(`create`, gaIdentifier, `auto`);
ga(`send`, `pageview`);
}
},
sendGAEvent(category = ``, action = ``, label = ``) {
if (!window.ga) {
return;
}
window.ga(`send`, category, `navigation`, action, label);
window.ga(
`send`,
`event`,
`navigation`,
`page footer cta`,
document.querySelectorAll(`.cms h1`).length > 0
? `${document.querySelectorAll(`.cms h1`)[0].innerText} - footer cta`
: ``
);
}
};

Просмотреть файл

@ -1,5 +1,4 @@
import ReactGA from "../react-ga-proxy";
import DNT from "../dnt.js";
import { ReactGA, GoogleAnalytics } from "../common";
function getQuerySelectorEvents(pageTitle, productName) {
return {
@ -127,7 +126,7 @@ function setupElementGA(element, eventData) {
const ProductGA = {
init: () => {
if (!DNT.allowTracking) {
if (GoogleAnalytics.doNotTrack) {
// explicit check on DNT left in, to prevent
// a whole heap of code from executing.
return;

Просмотреть файл

@ -1,13 +1,17 @@
import React from "react";
import ReactDOM from "react-dom";
import ReactGA from "../react-ga-proxy.js";
import Storage from "../storage.js";
import {
bindCommonEventHandlers,
GoogleAnalytics,
initializePrimaryNav,
injectCommonReactComponents,
ReactGA
} from "../common";
import primaryNav from "./components/primary-nav/primary-nav.js";
import navNewsletter from "../nav-newsletter.js";
import CreepVote from "./components/creep-vote/creep-vote.jsx";
import Creepometer from "./components/creepometer/creepometer.jsx";
import JoinUs from "../components/join/join.jsx";
import copyToClipboard from "../../js/copy-to-clipboard.js";
import HomepageSlider from "./homepage-c-slider.js";
@ -55,25 +59,14 @@ let main = {
networkSiteURL = `https://${env.HEROKU_APP_NAME}.herokuapp.com`;
}
// TODO: this should probably tap into the analytics.js file that
// we use in main.js so that we only have one place where top level
// changes to how analytics works need to be made.
const gaMeta = document.querySelector(`meta[name="ga-identifier"]`);
if (gaMeta) {
let gaIdentifier = gaMeta.getAttribute(`content`);
ReactGA.initialize(gaIdentifier); // UA-87658599-6 by default
ReactGA.pageview(window.location.pathname);
AnalyticsEvents.init();
} else {
console.warn(`No GA identifier found: skipping bootstrap step`);
}
GoogleAnalytics.init();
AnalyticsEvents.init();
this.enableCopyLinks();
this.injectReactComponents();
primaryNav.init();
navNewsletter.init(networkSiteURL, csrfToken);
bindCommonEventHandlers();
initializePrimaryNav(networkSiteURL, csrfToken, primaryNav);
if (document.getElementById(`view-home`)) {
HomepageSlider.init();
@ -120,6 +113,8 @@ let main = {
// Embed various React components based on the existence of containers within the current page
injectReactComponents() {
injectCommonReactComponents(apps, networkSiteURL, csrfToken);
document.querySelectorAll(`.creep-vote-target`).forEach(element => {
let csrf = element.querySelector(`input[name=csrfmiddlewaretoken]`);
let productName = element.dataset.productName;
@ -170,24 +165,6 @@ let main = {
})
);
});
document.querySelectorAll(`.join-us`).forEach(element => {
const props = element.dataset;
const sid = props.signupId || 0;
props.apiUrl = `${networkSiteURL}/api/campaign/signups/${sid}/`;
props.csrfToken = props.csrfToken || csrfToken;
props.isHidden = false;
apps.push(
new Promise(resolve => {
ReactDOM.render(
<JoinUs {...props} whenLoaded={() => resolve()} />,
element
);
})
);
});
}
};

Просмотреть файл

@ -1,5 +1,5 @@
import React from "react";
import ReactGA from "../../../react-ga-proxy";
import { ReactGA } from "../../../common";
import copyToClipboard from "../../../../js/copy-to-clipboard.js";
const SocialShareLink = props => {

Просмотреть файл

@ -0,0 +1,53 @@
import { ReactGA } from "./react-ga-proxy.js";
/**
* Check browser's "do not track" setting
* @return {Boolean} if browser's "do not track" setting is on
*/
const checkDoNotTrack = () => {
let _dntStatus = navigator.doNotTrack || navigator.msDoNotTrack,
fxMatch = navigator.userAgent.match(/Firefox\/(\d+)/),
ie10Match = navigator.userAgent.match(/MSIE 10/i),
w8Match = navigator.appVersion.match(/Windows NT 6.2/);
if (fxMatch && Number(fxMatch[1]) < 32) {
_dntStatus = `Unspecified`;
} else if (ie10Match && w8Match) {
_dntStatus = `Unspecified`;
} else {
_dntStatus =
{ "0": `Disabled`, "1": `Enabled` }[_dntStatus] || `Unspecified`;
}
return _dntStatus === `Enabled`;
};
const DO_NOT_TRACK = checkDoNotTrack();
/**
* Initialize Google Analytics and tracking pageviews
*/
const init = () => {
const gaMeta = document.querySelector(`meta[name="ga-identifier"]`);
if (!gaMeta) return;
let gaIdentifier = gaMeta.getAttribute(`content`);
if (!gaIdentifier) {
console.warn(`No GA identifier found: skipping bootstrap step`);
}
if (!DO_NOT_TRACK) {
ReactGA.initialize(gaIdentifier);
ReactGA.pageview(window.location.pathname);
}
};
/**
* Object that includes analytics related configs and functions
*/
export const GoogleAnalytics = {
doNotTrack: DO_NOT_TRACK,
init: init
};

10
source/js/common/index.js Normal file
Просмотреть файл

@ -0,0 +1,10 @@
/**
* The following modules are the common modules which need to be called
* on all pages on the main Foundation site, apps, and microsites.
* e.g., Foundation site, PNI, MozFest etc
*/
export { bindCommonEventHandlers } from "./template-js-handles";
export { GoogleAnalytics } from "./google-analytics.js";
export { initializePrimaryNav } from "./initialize-primary-nav.js";
export { injectCommonReactComponents } from "./inject-react";
export { ReactGA } from "./react-ga-proxy.js";

Просмотреть файл

@ -0,0 +1,12 @@
import navNewsletter from "../nav-newsletter.js";
/**
* Initiate primary nav scripts
* @param {String} siteUrl Foundation site base URL
* @param {String} csrfToken CSRF Token
* @param {Object} primaryNavModule primary nav module to initiate
*/
export const initializePrimaryNav = (siteUrl, csrfToken, primaryNavModule) => {
primaryNavModule.init();
navNewsletter.init(siteUrl, csrfToken);
};

Просмотреть файл

@ -0,0 +1,11 @@
import injectJoinUs from "./join-us.js";
/**
* Inject React components
* @param {Array} apps The existing array we are using to to track all ReactDOM.render calls
* @param {String} siteUrl Foundation site base URL
* @param {String} csrfToken CSRF Token
*/
export const injectCommonReactComponents = (apps, siteUrl, csrfToken) => {
injectJoinUs(apps, siteUrl, csrfToken);
};

Просмотреть файл

@ -0,0 +1,30 @@
import React from "react";
import ReactDOM from "react-dom";
import JoinUs from "../../components/join/join.jsx";
/**
* Inject newsletter signup forms
* @param {Array} apps The existing array we are using to to track all ReactDOM.render calls
* @param {String} siteUrl Foundation site base URL
* @param {String} csrfToken CSRF Token
*/
export default (apps, siteUrl, csrfToken) => {
// excluding `.join-us.on-nav` because it's taken care of by nav-newsletter.js
document.querySelectorAll(`.join-us:not(.on-nav)`).forEach(element => {
const props = element.dataset;
const sid = props.signupId || 0;
props.apiUrl = `${siteUrl}/api/campaign/signups/${sid}/`;
props.csrfToken = props.csrfToken || csrfToken;
props.isHidden = false;
apps.push(
new Promise(resolve => {
ReactDOM.render(
<JoinUs {...props} whenLoaded={() => resolve()} />,
element
);
})
);
});
};

14
source/js/common/react-ga-proxy.js поставляемый Normal file
Просмотреть файл

@ -0,0 +1,14 @@
import ReactGAModule from "react-ga";
import { GoogleAnalytics } from "./google-analytics.js";
// A no-operation object with the same API surface
// as ReactGA, for when tracking is not appreciated:
const noop = {
initialize: () => {},
pageview: () => {},
event: () => {}
};
const TrackingObject = GoogleAnalytics.doNotTrack ? noop : ReactGAModule;
export { TrackingObject as ReactGA };

Просмотреть файл

@ -0,0 +1,18 @@
import { ReactGA } from "../react-ga-proxy.js";
/**
* Bind click handler to #donate-footer-btn
*/
export default () => {
let donateFooterBtn = document.getElementById(`donate-footer-btn`);
if (donateFooterBtn) {
donateFooterBtn.addEventListener(`click`, () => {
ReactGA.event({
category: `donate`,
action: `donate button tap`,
label: `${document.title} footer`
});
});
}
};

Просмотреть файл

@ -0,0 +1,8 @@
import bindFooterDonateButtonHandler from "./footer-donate-button";
/**
* Bind event handlers
*/
export const bindCommonEventHandlers = () => {
bindFooterDonateButtonHandler();
};

Просмотреть файл

@ -1,5 +1,5 @@
import React from "react";
import ReactGA from "react-ga";
import { ReactGA } from "../../common";
import ReactDOM from "react-dom";
import classNames from "classnames";
import CountrySelect from "../petition/country-select.jsx";
@ -452,7 +452,7 @@ export default class JoinUs extends React.Component {
!this.state.apiSubmitted &&
!this.privacy.checked &&
!this.isFlowForm() && (
<span class="form-error-glyph privacy-error d-flex" />
<span className="form-error-glyph privacy-error d-flex" />
)}
</label>
</div>
@ -515,7 +515,7 @@ export default class JoinUs extends React.Component {
{this.renderSubmitButton()}
{this.isFlowForm() && (
<button
class="btn btn-primary btn-dismiss flex-1"
className="btn btn-primary btn-dismiss flex-1"
onClick={() => this.props.handleSignUp(false)}
type="button"
>

Просмотреть файл

@ -1,5 +1,5 @@
import React from "react";
import ReactGA from "react-ga";
import { ReactGA } from "../../common";
import classNames from "classnames";
import DonationModal from "./donation-modal.jsx";
import FloatingLabelInput from "./floating-label-input.jsx";

Просмотреть файл

@ -1,18 +0,0 @@
let _dntStatus = navigator.doNotTrack || navigator.msDoNotTrack,
fxMatch = navigator.userAgent.match(/Firefox\/(\d+)/),
ie10Match = navigator.userAgent.match(/MSIE 10/i),
w8Match = navigator.appVersion.match(/Windows NT 6.2/);
if (fxMatch && Number(fxMatch[1]) < 32) {
_dntStatus = `Unspecified`;
} else if (ie10Match && w8Match) {
_dntStatus = `Unspecified`;
} else {
_dntStatus = { "0": `Disabled`, "1": `Enabled` }[_dntStatus] || `Unspecified`;
}
const DNT = {
allowTracking: _dntStatus !== `Enabled`
};
export default DNT;

Просмотреть файл

@ -1,12 +1,16 @@
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "React" }] */
import React from "react";
import ReactGA from "react-ga";
import ReactDOM from "react-dom";
import Analytics from "./analytics.js";
import * as Sentry from "@sentry/browser";
import {
bindCommonEventHandlers,
GoogleAnalytics,
initializePrimaryNav,
injectCommonReactComponents,
ReactGA
} from "./common";
import JoinUs from "./components/join/join.jsx";
import Petition from "./components/petition/petition.jsx";
import MultipageNavMobile from "./components/multipage-nav-mobile/multipage-nav-mobile.jsx";
import News from "./components/news/news.jsx";
@ -14,7 +18,6 @@ import PulseProjectList from "./components/pulse-project-list/pulse-project-list
import ShareButtonGroup from "./components/share-button-group/share-button-group.jsx";
import primaryNav from "./primary-nav.js";
import navNewsletter from "./nav-newsletter.js";
import bindMozFestGA from "./mozfest-ga.js";
import bindMozFestEventHandlers from "./mozfest-event-handlers";
import youTubeRegretsTunnel from "./youtube-regrets.js";
@ -60,11 +63,7 @@ let main = {
networkSiteURL = `https://${env.HEROKU_APP_NAME}.herokuapp.com`;
}
const gaMeta = document.querySelector(`meta[name="ga-identifier"]`);
if (gaMeta) {
let gaIdentifier = gaMeta.getAttribute(`content`);
Analytics.initialize(gaIdentifier);
}
GoogleAnalytics.init();
this.injectReactComponents();
this.bindGlobalHandlers();
@ -205,8 +204,7 @@ let main = {
// Call once to get scroll position on initial page load.
onScroll();
primaryNav.init();
navNewsletter.init(networkSiteURL, csrfToken);
initializePrimaryNav(networkSiteURL, csrfToken, primaryNav);
youTubeRegretsTunnel.init();
// Extra tracking
@ -222,31 +220,26 @@ let main = {
});
}
let donateFooterBtn = document.getElementById(`donate-footer-btn`);
if (donateFooterBtn) {
donateFooterBtn.addEventListener(`click`, () => {
ReactGA.event({
category: `donate`,
action: `donate button tap`,
label: `${document.title} footer`
});
});
}
bindCommonEventHandlers();
},
bindGAEventTrackers() {
let seeMorePage = document.querySelector(`#see-more-modular-page`);
if (seeMorePage) {
let label = ``;
let pageHeader = document.querySelector(`.cms h1`);
if (pageHeader) {
label = `${pageHeader.innerText} - footer cta`;
}
seeMorePage.addEventListener(`click`, () => {
let label = ``;
let pageHeader = document.querySelector(`.cms h1`);
if (pageHeader) {
label = `${pageHeader.innerText} - footer cta`;
}
Analytics.sendGAEvent(`navigation`, `page footer cta`, label);
ReactGA.event({
category: `navigation`,
action: `page footer cta`,
label: label
});
});
}
@ -271,25 +264,7 @@ let main = {
// Embed various React components based on the existence of containers within the current page
injectReactComponents() {
// Embed additional instances of the Join Us box that don't need an API exposed (eg: Homepage)
document.querySelectorAll(`.join-us:not(.on-nav)`).forEach(element => {
var props = element.dataset;
props.apiUrl = `${networkSiteURL}/api/campaign/signups/${props.signupId ||
0}/`;
props.csrfToken = props.csrfToken || csrfToken;
props.isHidden = false;
apps.push(
new Promise(resolve => {
ReactDOM.render(
<JoinUs {...props} whenLoaded={() => resolve()} />,
element
);
})
);
});
injectCommonReactComponents(apps, networkSiteURL, csrfToken);
// petition elements
var subscribed = false;

Просмотреть файл

@ -1,4 +1,4 @@
import ReactGA from "./react-ga-proxy.js";
import { ReactGA } from "./common";
const bindMozfestGAEventTrackers = () => {
let homeWatchVideoButton = document.querySelector(

Просмотреть файл

@ -1,5 +1,4 @@
import React from "react";
import ReactGA from "react-ga";
import ReactDOM from "react-dom";
import JoinUs from "./components/join/join.jsx";

Просмотреть файл

@ -1,4 +1,4 @@
import ReactGA from "react-ga";
import { ReactGA } from "./common";
import navNewsletter from "./nav-newsletter.js";
let primaryNav = {

14
source/js/react-ga-proxy.js поставляемый
Просмотреть файл

@ -1,14 +0,0 @@
import DNT from "./dnt.js";
import ReactGA from "react-ga";
// A no-operation object with the same API surface
// as ReactGA, for when tracking is not appreciated:
const noop = {
initialize: () => {},
pageview: () => {},
event: () => {}
};
const TrackingObject = DNT.allowTracking ? ReactGA : noop;
export default TrackingObject;