Skip to content

Commit 0fa8dc4

Browse files
microbit-josephinemicrobit-carlos
authored andcommitted
Move metrics from S3 to Google analytics (bbcmicrobit#94)
1 parent a8ac81d commit 0fa8dc4

File tree

3 files changed

+253
-300
lines changed

3 files changed

+253
-300
lines changed

editor.html

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,22 @@
5959
Nicholas and Damien.
6060
-->
6161
<head>
62+
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-137582950-2"></script>
63+
<script>
64+
if (location.hostname !== "localhost" && location.hostname !== "127.0.0.1" &&
65+
location.hostname !== "") {
66+
window.dataLayer = window.dataLayer || [];
67+
function gtag() {
68+
dataLayer.push(arguments);
69+
}
70+
gtag('js', new Date());
71+
var options = {
72+
anonymize_ip: true,
73+
cookie_prefix: "mb"
74+
};
75+
gtag('config', 'UA-137582950-2', options);
76+
}
77+
</script>
6278
<meta charset="utf-8">
6379
<title>Python editor</title>
6480
<meta name="viewport" content="width=device-width,initial-scale=1">

metrics.js

Lines changed: 127 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
var defaultScript = "";
55

66
window.addEventListener("load", function() {
7-
sendMetric("/page-load");
87
measureViewport();
8+
// Retained so we can compare with GA.
9+
sendLegacyPageViewMetric();
10+
911
// Capture the default script loaded in the editor
1012
defaultScript = EDITOR.getCode();
1113

@@ -27,214 +29,202 @@ function attachActionListeners() {
2729
$(".action").on("click", actionClickListener);
2830
}
2931

30-
function sendMetric(slug) {
31-
slug = slug.replace(/,/g, '-');
32-
// Do not send the metrics if running locally during development
33-
if ((location.hostname === "localhost" || location.hostname === "127.0.0.1" ||
34-
location.hostname === "") &&
35-
// Check this is not Puppeteer, the tests need to intercept the sent requests
36-
(typeof navigator !== "undefined" && !navigator.webdriver)) {
37-
console.log("metric: " + slug);
38-
} else {
32+
function sendLegacyPageViewMetric() {
33+
if (location.hostname !== "localhost" && location.hostname !== "127.0.0.1" &&
34+
location.hostname !== "") {
3935
$.ajax({
4036
type: "GET",
41-
url: "https://metrics.microbit.org/pyeditor-" + EDITOR_VERSION + slug,
37+
url: "https://metrics.microbit.org/pyeditor-" + EDITOR_VERSION + "/page-load",
4238
complete: function(res) {
4339
// Do nothing
4440
}
4541
});
4642
}
43+
else {
44+
console.log("metric: pageview");
45+
}
46+
}
47+
48+
function sendEvent(action, label, value) {
49+
// Do not send the metrics if running locally during development
50+
if (location.hostname === "localhost" || location.hostname === "127.0.0.1" ||
51+
location.hostname === "") {
52+
console.log("metric: " + action + " " + label + " " + value);
53+
} else {
54+
gtag('event', action, {
55+
event_category: 'Python Editor ' + EDITOR_VERSION,
56+
event_label: label,
57+
value: value
58+
});
59+
}
4760
}
4861

4962
function measureViewport(){
50-
var widthRange = [[0, 480], [481, 890], [891,1024], [1025, 1280], [1281, 10000]];
51-
var viewportWidth = $(window).width();
63+
var widthRange = [[0, 480], [481, 890], [891,1024], [1025, 1280], [1281, 10000]];
64+
var viewportWidth = $(window).width();
5265

53-
var bucket = widthRange.filter(function(a) {
54-
if (viewportWidth >= a[0] && viewportWidth <= a[1]) return a;
55-
});
66+
var bucket = widthRange.filter(function(a) {
67+
if (viewportWidth >= a[0] && viewportWidth <= a[1]) return a;
68+
});
69+
var label = bucket.toString().replace(/,/g, '-');
5670

57-
var slug = "/width/" + bucket[0].toString();
58-
sendMetric(slug);
71+
sendEvent('viewport', label, 1);
5972
}
6073

6174
function trackLines() {
6275
var range = [[0, 20], [21, 50], [51, 100], [101, 200], [201, 500], [501, 1000], [1001, 1000000]];
6376
var currentCode = EDITOR.getCode();
64-
var slug = "/lines/";
6577

6678
if (currentCode == defaultScript) {
67-
slug += "default-script";
79+
var label = 'default';
6880
} else {
69-
var lines = currentCode.split(/\r\n|\r|\n/).length;
70-
var bucket = range.filter(function(a) {
71-
if (lines >= a[0] && lines <= a[1]) return a;
72-
});
73-
slug += bucket[0].toString();
81+
var lines = currentCode.split(/\r\n|\r|\n/).length;
82+
var bucket = range.filter(function(a) {
83+
if (lines >= a[0] && lines <= a[1]) return a;
84+
});
85+
var label = bucket.toString().replace(/,/g, '-');
7486
}
7587

76-
sendMetric(slug);
88+
sendEvent('lines', label, 1);
7789
}
7890

7991
function trackFiles() {
8092
var range = [[11, 15], [16, 20], [21, 25], [26, 1000]];
81-
var files;
82-
83-
try {
84-
// Will always be at least 1 due to main.py
85-
files = micropythonFs.ls().length;
86-
}
87-
catch(e) {
88-
// If the filesystem is not present assume one file (main.py)
89-
sendMetric("/files/1");
90-
return;
91-
}
93+
var files = micropythonFs.ls().length;
9294

95+
var label = files.toString();
9396
if (files > 10) {
9497
var bucket = range.filter(function(a) {
9598
if (files >= a[0] && files <= a[1]) return a;
9699
});
97-
var slug = "/files/" + bucket[0].toString();
98-
sendMetric(slug);
99-
}
100-
else {
101-
var slug = "/files/" + files.toString();
102-
sendMetric(slug);
100+
label = bucket.toString().replace(/,/g, '-');
103101
}
102+
103+
sendEvent('files', label , 1);
104+
}
105+
106+
/**
107+
* Returns an analytics label for the file extension.
108+
* "none" is used when there is no extension.
109+
*/
110+
function fileExtension(file) {
111+
var lowerName = file.name.toLowerCase();
112+
var ext = (/[.]/.exec(lowerName)) ? /[^.]+$/.exec(lowerName) : ["none"];
113+
return ext[0];
104114
}
105115

106116
// Dropping into editor
107117
$('#editor').on('drop', function (e) {
108118
var file = e.originalEvent.dataTransfer.files[0];
109-
var ext = (/[.]/.exec(file.name)) ? /[^.]+$/.exec(file.name) : ["none"];
110-
111-
switch(ext[0]) {
112-
case "py":
113-
sendMetric("/drop/py");
114-
break;
115-
case "hex":
116-
sendMetric("/drop/hex");
117-
break;
118-
default:
119-
sendMetric("/drop/error/invalid");
119+
var label = fileExtension(file);
120+
if ((label === 'py') || (label==='hex')) {
121+
sendEvent('load', 'drop-editor-' + label, 1);
122+
} else {
123+
sendEvent('load', 'error-drop-editor-type-' + label, 1);
120124
}
121125
});
122126

123127
// Dropping into load area
124128
document.addEventListener('load-drop', function (e) {
125129
var file = e.detail;
126-
var ext = (/[.]/.exec(file.name)) ? /[^.]+$/.exec(file.name) : ["none"];
127-
128-
switch(ext[0]) {
129-
case "py":
130-
sendMetric("/drop/py");
131-
break;
132-
case "hex":
133-
sendMetric("/drop/hex");
134-
break;
135-
default:
136-
sendMetric("/drop/error/invalid");
130+
var label = fileExtension(file);
131+
if ((label === 'py') || (label==='hex')) {
132+
sendEvent('load', 'drop-load-' + label, 1);
133+
} else {
134+
sendEvent('load', 'error-drop-load-type-' + label, 1);
137135
}
138136
});
139137

140138
// Uploading a file to the editor via Load/Save modal
141139
document.addEventListener('file-upload', function (e) {
142140
var files = e.detail;
143141
if (files.length === 1) {
144-
var f = files[0];
145-
var ext = (/[.]/.exec(f.name)) ? /[^.]+$/.exec(f.name) : null;
146-
switch(ext[0]) {
147-
case "py":
148-
sendMetric("/file-upload/py");
149-
break;
150-
case "hex":
151-
sendMetric("/file-upload/hex");
152-
break;
153-
default:
154-
sendMetric("/file-upload/error/invalid");
155-
}
142+
var label = fileExtension(files[0]);
143+
if ((label === 'py') || (label==='hex')) {
144+
sendEvent('load', 'file-upload-' + label, 1);
145+
} else {
146+
sendEvent('load', 'error-file-upload-type-' + label, 1);
147+
}
156148
} else {
157-
sendMetric("/file-upload/error/multiple-files");
149+
sendEvent('load', 'error-file-upload-multiple', 1);
158150
}
159151
});
160152

161-
// WebUSB Error
153+
// WebUSB flash time and errors
162154
document.addEventListener('webusb', function (e) {
163155
var details = e.detail;
164-
165-
// Put flash time into brackets
166-
if( details["event-type"] == "flash-time" ) {
167-
156+
if (details["event-type"] == "flash-time" ) {
157+
var flashAction = 'WebUSB-time';
168158
var flashTime = details["message"];
169-
var timeBracket;
170-
159+
var flashLabel = 'unknown';
171160
if (flashTime < 2000) {
172-
timeBracket = "0-2";
173-
}
174-
else if (flashTime <= 4000) {
175-
timeBracket = "2-4";
176-
}
177-
else if (flashTime <= 6000) {
178-
timeBracket = "4-6";
179-
}
180-
else if (flashTime <= 10000) {
181-
timeBracket = "6-10";
182-
}
183-
else if (flashTime <= 20000) {
184-
timeBracket = "10-20";
161+
flashLabel = "0-2";
162+
} else if (flashTime <= 4000) {
163+
flashLabel = "2-4";
164+
} else if (flashTime <= 6000) {
165+
flashLabel = "4-6";
166+
} else if (flashTime <= 10000) {
167+
flashLabel = "6-10";
168+
} else if (flashTime <= 20000) {
169+
flashLabel = "10-20";
170+
} else if (flashTime <= 30000) {
171+
flashLabel = "20-30";
172+
} else if (flashTime <= 60000) {
173+
flashLabel = "30-60";
174+
} else if (flashTime <= 120000) {
175+
flashLabel = "60-120";
176+
} else {
177+
flashLabel = "120+";
185178
}
186-
else if (flashTime <= 30000) {
187-
timeBracket = "20-30";
188-
}
189-
else if (flashTime <= 60000) {
190-
timeBracket = "30-60";
191-
}
192-
else if (flashTime <= 120000) {
193-
timeBracket = "60-120";
194-
}
195-
else {
196-
timeBracket = "120+";
197-
}
198-
199-
// Set message to time bracket
200-
details["message"] = timeBracket;
201-
202-
}
203-
204-
sendMetric('/webusb/' + details["flash-type"] + '/' + details["event-type"] + "/" + details["message"]);
179+
var flashValue = 1;
180+
} else if (details["event-type"] == "info" ) {
181+
var flashAction = 'WebUSB-info';
182+
var flashLabel = details["message"];
183+
var flashValue = 1;
184+
} else if (details["event-type"] == "error" ) {
185+
var flashAction = 'WebUSB-error';
186+
// TODO: At the moment details["flash-type"] only indicates the flash
187+
// option selected and doesn't distinguish full flash fall-back
188+
// so we won't include it for now, but that could be changed
189+
var flashLabel = details["message"];
190+
var flashValue = 1;
191+
} else {
192+
var flashAction = 'WebUSB-error';
193+
var flashLabel = "unknown-event/" + details["event-type"] + "/" + details["message"];
194+
var flashValue = 1;
195+
}
196+
sendEvent(flashAction, flashLabel, flashValue);
205197
});
206198

199+
// Any click on an element with the "action" class is captured here
207200
function actionClickListener(e) {
208-
var slug;
209-
if(e.target) {
210-
slug = "/action/" + $(e.target).closest(".action")[0].id;
211-
} else {
212-
slug = "/action/";
201+
var actionId = 'unknown';
202+
if (e.target) {
203+
actionId = $(e.target).closest(".action")[0].id;
204+
actionId = actionId.replace("command-", "");
213205
}
214206

215-
slug = slug.replace("command-", "");
216-
217-
if (slug.match(/_save/)) {
218-
slug = "/action/fs-file-save";
207+
if (actionId.match(/_save/)) {
208+
actionId = "file-save";
219209
}
220-
else if (slug.match(/_remove/)) {
221-
slug = "/action/fs-file-remove";
210+
else if (actionId.match(/_remove/)) {
211+
actionId = "file-remove";
222212
}
223-
else if (slug.match(/flashing-overlay-download/)) {
224-
slug = "/action/partial-flashing/error-link-download";
213+
else if (actionId.match(/flashing-overlay-download/)) {
214+
actionId = "webusb/error-modal/download-hex";
225215
}
226-
else if (slug.match(/flashing-overlay-troubleshoot/)) {
227-
slug = "/action/partial-flashing/error-link-troubleshoot";
216+
else if (actionId.match(/flashing-overlay-troubleshoot/)) {
217+
actionId = "webusb/error-modal/troubleshoot";
228218
}
229219

230-
switch(slug) {
231-
case "/action/flash":
232-
case "/action/download":
220+
switch(actionId) {
221+
case "flash":
222+
case "download":
233223
trackFiles();
234224
trackLines();
235225
/* Intentional fall-through */
236226
default:
237-
sendMetric(slug);
238-
break;
227+
sendEvent('click', actionId, 1);
228+
break;
239229
}
240230
}

0 commit comments

Comments
 (0)