Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Google Analytics #617

Merged
merged 8 commits into from Nov 17, 2017
Merged

Conversation

@gregtatum
Copy link
Member

@gregtatum gregtatum commented Oct 17, 2017

#615 needs to land first.

I opted to go for pageviews on the tabs, as then we can easily gather timing information through the GA reports. I also added instrumentation to timeCode, so that we can get that information and analyze it over a broader audience.

I tried to break out the changes into different commits in case people wanted to look at them individually for feedback.

@gregtatum gregtatum requested review from digitarald, julienw and mstange Oct 17, 2017
@codecov
Copy link

@codecov codecov bot commented Oct 17, 2017

Codecov Report

Merging #617 into master will decrease coverage by 0.11%.
The diff coverage is 41.79%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #617      +/-   ##
==========================================
- Coverage   62.81%   62.69%   -0.12%     
==========================================
  Files         130      131       +1     
  Lines        7296     7345      +49     
  Branches     1616     1623       +7     
==========================================
+ Hits         4583     4605      +22     
- Misses       2390     2411      +21     
- Partials      323      329       +6
Impacted Files Coverage Δ
src/components/app/UrlManager.js 0% <0%> (ø) ⬆️
src/components/app/ProfileSharing.js 35.89% <0%> (-1.95%) ⬇️
...omponents/header/ProfileThreadHeaderContextMenu.js 0% <0%> (ø) ⬆️
src/index.js 0% <0%> (ø) ⬆️
src/actions/app.js 0% <0%> (ø) ⬆️
src/actions/profile-view.js 76.81% <100%> (+8.18%) ⬆️
src/utils/time-code.js 33.33% <33.33%> (-6.67%) ⬇️
src/utils/analytics.js 75% <75%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 4d6b910...ed319e3. Read the comment docs.

@gregtatum
Copy link
Member Author

@gregtatum gregtatum commented Oct 26, 2017

The legal bug for permission to analytics is approved. If anyone has thoughts on my implementation please leave them here. Otherwise, @julienw I would appreciate a code review on this so we can started getting some data about our usage patterns

Copy link
Contributor

@julienw julienw left a comment

There are a few needed changes but I think overall this looks good :)

Thanks !

timingLabel?: string,
|};

type GoogleAnalytics = ('send', GAEvent | GAPageView | GATiming) => {};

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

Any reason you declare all this here and not in a separate types file ? Like src/types/analytics.js ?
Then you can import it here yo use it below.

@@ -25,7 +25,8 @@ new WebpackDevServer(webpack(config), {
'self'
'sha256-eRTCQnd2fhPykpATDzCv4gdVk/EOdDq+6yzFXaWgGEw='
'sha256-AdiT28wTL5FNaRVHWQVFC0ic3E20Gu4/PiC9xukS9+E='
https://api-ssl.bitly.com;
https://api-ssl.bitly.com
https://www.google-analytics.com;

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

Not sure this is needed given we load it in perf.html only ? Though it can be useful while testing.

This comment has been minimized.

@gregtatum

gregtatum Nov 1, 2017
Author Member

Maybe not technically, but it does make it easier to verify they are the same.

// Only include on perf-html.io
window.location.host.includes('perf-html.io') &&
// Observe do not track.
!(navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack)

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

doNotTrack values are not boolean. You want to check if the value is "1", maybe something like:

const doNotTrack = (navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack) === '1';
if (!doNotTrack) { ... }

This comment has been minimized.

@gregtatum

gregtatum Nov 1, 2017
Author Member

Yikes, what an API. Fixing it.

(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','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-35433268-81', 'auto');

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

wondering if this shouldn't be hidden from our source, and instead we should have a conf file for building on the server ?

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

I assume this is our ID.

This comment has been minimized.

@gregtatum

gregtatum Nov 1, 2017
Author Member

This is our ID, although it's always publicly visible. I'm not sure it's worth the complexity of adding environment keys to our build step yet.

@@ -6,6 +6,7 @@
<head>
<meta charset="utf-8">
<title>perf.html</title>
<script src='analytics.js'></script>

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

Should you add it in webpack's OfflinePlugin configuration too ?

This comment has been minimized.

@gregtatum

gregtatum Nov 1, 2017
Author Member

Done.

This comment has been minimized.

@julienw

julienw Nov 14, 2017
Contributor

Actually, you need /analytics.js instead of analytics.js

This comment has been minimized.

@julienw

julienw Nov 14, 2017
Contributor

And you need to copy it to dist.

ga('send', {
hitType: 'event',
eventCategory: 'profile',
eventAction: 'change hide platform details',

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

not sure this is the right action string ;) "change implementation filter" would be more appropriate, right ?

This comment has been minimized.

@gregtatum

gregtatum Nov 1, 2017
Author Member

Nice catch, thanks!

eventCategory: 'profile upload',
eventAction: 'failed',
});
}

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

I'd create an action (I mean, an action that wouldn't dispatch a redux action) to handle ga calls.

or at least a local function.

This comment has been minimized.

@gregtatum

gregtatum Nov 1, 2017
Author Member

I'm curious if the reason you want me to create local functions is the if check. Would you be ok with me stubbing out the analytics function for everywhere? That way we can fire it regardless. The interface itself is just a function call with some parameters, so I don't feel the need to abstract it away further.

updateUrlState,
urlSetupDone,
show404,
}

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

👍
it always disturbed me


// Some portion of users will have timing information sent. Limit this further to
// only send a few labels per user.
const ga = self.ga;

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

yet another way of getting the ga function ? :)

This comment has been minimized.

@gregtatum

gregtatum Oct 27, 2017
Author Member

window doesn't exist in workers.

This comment has been minimized.

@julienw

julienw Oct 28, 2017
Contributor

Ok, makes sense !

// Some portion of users will have timing information sent. Limit this further to
// only send a few labels per user.
const ga = self.ga;
if (ga && !(_timingsPerLabel[label] > MAX_TIMINGS_PER_LABEL)) {

This comment has been minimized.

@julienw

julienw Oct 27, 2017
Contributor

I think you want _timingsPerLabel[label] < MAX_TIMINGS_PER_LABEL (I mean, remove the ! of course, and use < instead of the current <= because otherwise it happens once too many)

This comment has been minimized.

@gregtatum

gregtatum Nov 1, 2017
Author Member

Thanks, not sure why I had it that way.

Copy link
Contributor

@digitarald digitarald left a comment

Some general comments and one point on how we track page views

(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','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-35433268-81', 'auto');

This comment has been minimized.

@digitarald

digitarald Oct 30, 2017
Contributor

to be more specific, auto could be also perf-html.io to tracking on other domains; this way the check can be removed from the top-level if

This comment has been minimized.

@gregtatum

gregtatum Nov 2, 2017
Author Member

I personally like the top level if, since it won't even ping google while I'm working locally, and have spotty internet.

m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-35433268-81', 'auto');
ga('send', 'pageview');

This comment has been minimized.

@digitarald

digitarald Oct 30, 2017
Contributor

This should probably not send the current URL but the selectedTab from the profile viewer; otherwise the metrics will be a mix of paths and selected tabs

This comment has been minimized.

This comment has been minimized.

@gregtatum

gregtatum Nov 1, 2017
Author Member

Yes, that was a mistake leaving that in there, thanks for catching it!

ga('send', {
hitType: 'event',
eventCategory: 'profile',
eventAction: 'toggle thread',

This comment has been minimized.

@digitarald

digitarald Oct 30, 2017
Contributor

Could we use hide for the action and the type (content, main, compositor, etc) for the label? This way we know which threads most users hide (if users actually hide threads, might need to be more visible) and might pro-actively show less of them

const dataSource = getDataSource(getState());
ga('send', {
hitType: 'pageview',
page: dataSource === 'none' ? 'home' : getSelectedTab(getState()),

This comment has been minimized.

@digitarald

digitarald Oct 30, 2017
Contributor

What would be interesting here where the user came from. First idea that comes to mind would be adding an event to when the profile is loaded either from whatever mechanism is triggered.

This comment has been minimized.

@gregtatum

gregtatum Nov 1, 2017
Author Member

      ga('send', {
        hitType: 'event',
        eventCategory: 'datasource',
        eventAction: dataSource,
      });
@gregtatum gregtatum force-pushed the gregtatum:google-analytics branch 2 times, most recently from a7534a5 to 0b5afa6 Nov 2, 2017
Copy link
Contributor

@julienw julienw left a comment

Hey, thanks for the changes, this is much more readable !

There are 2 subtle bugs left:

  • one related to the doNotTrack flag
  • another with the MAX_TIMINGS_PER_LABEL logic
  • please manually type ga in the sendAnalytics function.
// Only include on perf-html.io
window.location.host.includes('perf-html.io') &&
// Observe do not track.
(navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack) === '1'

This comment has been minimized.

@julienw

julienw Nov 6, 2017
Contributor

don't you want the opposite ? ;)
It's '1' if the user actually specified "I don't want to be tracked" ;)

That's why my suggested code was:

const doNotTrack = (navigator.doNotTrack || window.doNotTrack || navigator.msDoNotTrack) === '1';
if (!doNotTrack) { ... }

(note the ! :) )


// Some portion of users will have timing information sent. Limit this further to
// only send a few labels per user.
if (_timingsPerLabel[label] <= MAX_TIMINGS_PER_LABEL) {

This comment has been minimized.

@julienw

julienw Nov 6, 2017
Contributor

When _timingsPerLabel[label] is undefined (the first time then), I think the result of this comparison is always false.

I think you need to set it to a temporary const before the condition, something like this:

const sentTimings = _timingsPerLabel[label] || 0;
if (sentTimings < MAX_TIMINGS_PER_LABEL) {
  _timingsPerLabel[label] = sentTimings + 1;
  ...
}

Also note the operator < instead of <=. Otherwise I think it's sent (MAX_TIMINGS_PER_LABEL + 1) times.

This comment has been minimized.

@gregtatum

gregtatum Nov 10, 2017
Author Member

Thanks for catching this.

export type GoogleAnalytics = ('send', GAPayload) => {};

export function sendAnalytics(payload: GAPayload) {
const { ga } = self;

This comment has been minimized.

@julienw

julienw Nov 6, 2017
Contributor

I think there is something wrong after latest Flow upgrade, the typing for window properties seems to be broken.
In the mean time, could you type ga manually here ?

This comment has been minimized.

@gregtatum

gregtatum Nov 10, 2017
Author Member

This must be because self is not equivalent to window.

@gregtatum gregtatum force-pushed the gregtatum:google-analytics branch from 0b5afa6 to 902f850 Nov 10, 2017
@gregtatum
Copy link
Member Author

@gregtatum gregtatum commented Nov 10, 2017

@julienw this is ready for re-review.

@julienw julienw force-pushed the gregtatum:google-analytics branch from 902f850 to 2f89cad Nov 14, 2017
@@ -6,6 +6,7 @@
<head>
<meta charset="utf-8">
<title>perf.html</title>
<script src='analytics.js'></script>

This comment has been minimized.

@julienw

julienw Nov 14, 2017
Contributor

Actually, you need /analytics.js instead of analytics.js

@@ -6,6 +6,7 @@
<head>
<meta charset="utf-8">
<title>perf.html</title>
<script src='analytics.js'></script>

This comment has been minimized.

@julienw

julienw Nov 14, 2017
Contributor

And you need to copy it to dist.

@@ -9,7 +9,7 @@
(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','//www.google-analytics.com/analytics.js','ga');
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

This comment has been minimized.

@julienw

julienw Nov 14, 2017
Contributor

without this change, CSP blocks the "http" version in the dev environment. 2 possible fixes: either this, or adding the "http" version to the CSP header -- which redirects to https anyway. So I think it's better to always ask for the https version, in this case.

@@ -6,7 +6,7 @@
<head>
<meta charset="utf-8">
<title>perf.html</title>
<script src='analytics.js'></script>
<script src='/analytics.js'></script>
@julienw
Copy link
Contributor

@julienw julienw commented Nov 14, 2017

Note I think we should do the actions from #473 before landing @gregtatum @digitarald. Or at least before deploying.

@julienw
Copy link
Contributor

@julienw julienw commented Nov 17, 2017

@gregtatum if you're OK with my last commit, please merge :)

@gregtatum gregtatum merged commit 04bc38f into firefox-devtools:master Nov 17, 2017
2 of 4 checks passed
2 of 4 checks passed
@codecov
codecov/patch 41.79% of diff hit (target 62.81%)
Details
@codecov
codecov/project 62.69% (-0.12%) compared to 4d6b910
Details
ci/circleci Your tests passed on CircleCI!
Details
continuous-integration/appveyor/pr AppVeyor build succeeded
Details
@gregtatum
Copy link
Member Author

@gregtatum gregtatum commented Nov 17, 2017

The review is still in the state "changes requested" but I'm using my admin privilege to go ahead and merge based on your written approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

3 participants