Skip to content

Commit 35f9696

Browse files
authored
Merge pull request #899 from webhintio/telemetry
Add 28-Day Active Telemetry
2 parents 6d3c8b9 + 47b4cfb commit 35f9696

File tree

3 files changed

+120
-1
lines changed

3 files changed

+120
-1
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"@hint/formatter-html": "^4.1.13",
44
"@hint/utils": "^7.0.1",
55
"@hint/utils-i18n": "^1.0.0",
6+
"@hint/utils-telemetry": "^1.0.2",
67
"algoliasearch": "^4.0.0",
78
"applicationinsights": "^1.6.0",
89
"body-parser": "^1.19.0",
@@ -72,7 +73,7 @@
7273
"node": ">=10.0.0"
7374
},
7475
"hexo": {
75-
"version": "4.0.0"
76+
"version": "4.2.0"
7677
},
7778
"private": true,
7879
"scripts": {

src/webhint-theme/layout/scan.ejs

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@
6363
<script src="/js/partials/utils.js"></script>
6464
<!-- endbuild -->
6565
66+
<!-- build:js /static/scripts/telemetry.js -->
67+
<script src="/js/telemetry/telemetry.js"></script>
68+
<!-- endbuild -->
69+
6670
<% if (result.isFinish) { %>
6771
<!-- build:js /static/scripts/scanner-finish.js -->
6872
<script src="/js/scan/scanner-common.js"></script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
"use strict";
2+
(function () {
3+
const activityKey = 'webhint-activity';
4+
const productKey = 'webhint-online-scanner';
5+
const storage = window.localStorage;
6+
const telemetryApiEndpoint = 'https://webhint-telemetry.azurewebsites.net/api/log';
7+
let sendTimeout = null;
8+
let telemetryQueue = [];
9+
let options = {
10+
batchDelay: 15000,
11+
defaultProperties: {},
12+
};
13+
14+
const post = async (url, data) => {
15+
const response = await fetch(url, {
16+
method: 'POST',
17+
headers: {
18+
'Content-Type': 'application/json'
19+
},
20+
body: data
21+
});
22+
return response;
23+
}
24+
25+
const sendTelemetry = async () => {
26+
if (sendTimeout) {
27+
clearTimeout(sendTimeout);
28+
sendTimeout = null;
29+
}
30+
const data = JSON.stringify({
31+
product: productKey,
32+
data: telemetryQueue
33+
});
34+
35+
telemetryQueue = [];
36+
try {
37+
post(telemetryApiEndpoint, data)
38+
.then(response => {
39+
if (response.status !== 200) {
40+
console.warn('Failed to send telemetry: ', status);
41+
}
42+
});
43+
}
44+
catch (err) {
45+
console.warn('Failed to send telemetry: ', err);
46+
}
47+
};
48+
49+
const track = async (type, data) => {
50+
telemetryQueue.push(
51+
{
52+
name: data.name,
53+
properties: Object.assign(Object.assign({}, options.defaultProperties), data.properties),
54+
ver: 2,
55+
type: `${type}Data`
56+
}
57+
);
58+
if (!options.batchDelay) {
59+
await sendTelemetry();
60+
}
61+
else if (!sendTimeout) {
62+
sendTimeout = setTimeout(sendTelemetry, options.batchDelay);
63+
}
64+
};
65+
66+
const trackEvent = async (name, properties) => {
67+
await track('Event', { name, properties });
68+
};
69+
70+
const getISODateString = () => {
71+
const date = new Date(Date.now());
72+
date.setUTCHours(0);
73+
date.setUTCMinutes(0);
74+
date.setUTCSeconds(0);
75+
date.setUTCMilliseconds(0);
76+
return date.toISOString();
77+
};
78+
const getDaysBetweenUpdates = (currentUpdate, lastUpdated) => {
79+
if (!lastUpdated) {
80+
return 1;
81+
}
82+
const deltaMS = new Date(currentUpdate).getTime() - new Date(lastUpdated).getTime();
83+
return deltaMS / 1000 / 60 / 60 / 24;
84+
};
85+
const initializeActivity = () => {
86+
const lastUpdated = '';
87+
const last28Days = ''.padEnd(28, '0');
88+
return { last28Days, lastUpdated };
89+
};
90+
const getUpdatedActivity = (previousActivity) => {
91+
const activity = JSON.parse(previousActivity) || initializeActivity();
92+
const currentUpdate = getISODateString();
93+
const delta = getDaysBetweenUpdates(currentUpdate, activity.lastUpdated);
94+
if (delta < 1) {
95+
return null;
96+
}
97+
activity.last28Days = '1'.padEnd(Math.min(delta, 28), '0') + activity.last28Days.slice(0, -delta);
98+
activity.lastUpdated = currentUpdate;
99+
return activity;
100+
};
101+
102+
/**
103+
* Report once per UTC day that a user is active (has run a scan).
104+
* Data includes `last28Days` (e.g. `"1001100110011001100110011001"`)
105+
* and `lastUpdated` (e.g. `"2019-10-04T00:00:00.000Z"`).
106+
*/
107+
// Don't count a user as active if telemetry is disabled.
108+
109+
const activity = getUpdatedActivity(storage.getItem(activityKey));
110+
if (activity) {
111+
storage.setItem(activityKey, JSON.stringify(activity));
112+
trackEvent('online-activity', activity);
113+
}
114+
}());

0 commit comments

Comments
 (0)