Skip to content
Permalink
Browse files

New: Add 28-day retention telemetry

Also migrate to use shared webhint telemetry utilities.
  • Loading branch information
antross authored and molant committed Nov 8, 2019
1 parent 4f9446f commit 618e13aaf7e3ce863a3b539ee1e2f78743bcae93
@@ -20,7 +20,7 @@
],
"timeout": "1m"
},
"bundleSize": 80000,
"bundleSize": 95000,
"categories": [
"Linters"
],
@@ -46,18 +46,18 @@
}
}
},
"dependencies": {
"@hint/utils-types": "^1.0.0"
},
"description": "Run webhint in Visual Studio Code.",
"devDependencies": {
"@hint/utils-telemetry": "^1.0.0",
"@hint/utils-types": "^1.0.0",
"@types/node": "^12.7.5",
"@types/proxyquire": "^1.3.28",
"@types/sinon": "^7.5.0",
"@types/vscode": "^1.39.0",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.12.0",
"ava": "^2.4.0",
"configstore": "^5.0.0",
"cpx": "^1.5.0",
"eslint": "^6.5.1",
"eslint-plugin-import": "^2.18.2",
@@ -1,7 +1,7 @@
import * as https from 'https';
import { createConnection, ProposedFeatures, TextDocuments } from 'vscode-languageserver';
import { initTelemetry, updateTelemetry } from '@hint/utils-telemetry';

import { initTelemetry, updateTelemetry } from './utils/app-insights';
import { trackClose, trackSave, trackOptIn, TelemetryState } from './utils/analytics';
import { Analyzer } from './utils/analyze';
import * as notifications from './utils/notifications';
@@ -1,4 +1,12 @@
import { trackEvent } from './app-insights';
import * as Configstore from 'configstore';

import {
determineHintStatus,
enabled,
getUpdatedActivity,
ProblemCountMap,
trackEvent
} from '@hint/utils-telemetry';

export type ResultData = {
hints: import('hint').IHintConstructor[];
@@ -7,20 +15,8 @@ export type ResultData = {

export type TelemetryState = 'ask' | 'disabled' | 'enabled';

const enum HintStatus {
passed = 'passed',
failed = 'failed',
fixed = 'fixed',
fixing = 'fixing'
}

type HintStatusMap = {
[hintKey: string]: HintStatus;
};

type ProblemCountMap = {
[hintId: string]: number;
};
const activityKey = 'webhint-activity';
const config = new Configstore('vscode-webhint');

// Remember per-document results for analytics.
const prevProblems = new Map<string, ProblemCountMap>();
@@ -29,30 +25,6 @@ const languageIds = new Map<string, string>();
const lastSaveTimes = new Map<string, number>();
const twoMinutes = 1000 * 60 * 2;

const determineHintStatus = (prev: ProblemCountMap, next: ProblemCountMap, languageId: string) => {
const status: HintStatusMap = {};

for (const id of Object.keys(next)) {
const hintKey = `hint-${id}`;
const prevCount = prev[id] || 0;
const nextCount = next[id] || 0;

if (nextCount) {
if (prevCount > nextCount) {
status[hintKey] = HintStatus.fixing;
} else {
status[hintKey] = HintStatus.failed;
}
} else if (prevCount > 0) {
status[hintKey] = HintStatus.fixed;
} else {
status[hintKey] = HintStatus.passed;
}
}

return { languageId, ...status };
};

const toTrackedResult = (data: ResultData) => {
const result: ProblemCountMap = {};
const ids = data.hints.map((hint) => {
@@ -68,8 +40,30 @@ const toTrackedResult = (data: ResultData) => {
return result;
};

const trackOpen = (result: ProblemCountMap, languageId: string) => {
trackEvent('vscode-open', determineHintStatus({}, result, languageId));
/**
* Report once per UTC day that a user is active (has opened a recognized file).
* Data includes `last28Days` (e.g. `"1001100110011001100110011001"`)
* and `lastUpdated` (e.g. `"2019-10-04T00:00:00.000Z"`).
*/
const trackActive = (s: Configstore) => {
// Don't count a user as active if telemetry is disabled.
if (!enabled()) {
return;
}

const activity = getUpdatedActivity(s.get(activityKey));

if (activity) {
s.set(activityKey, activity);
trackEvent('vscode-activity', activity);
}
};

const trackOpen = (result: ProblemCountMap, languageId: string, s: Configstore) => {
const status = determineHintStatus({}, result);

trackEvent('vscode-open', { ...status, languageId });
trackActive(s);
};

export const trackOptIn = (telemetryEnabled: TelemetryState, everEnabledTelemetry: boolean) => {
@@ -85,7 +79,7 @@ export const trackClose = (uri: string) => {
lastSaveTimes.delete(uri);
};

export const trackResult = (uri: string, languageId: string, result: ResultData) => {
export const trackResult = (uri: string, languageId: string, result: ResultData, s = config) => {
const problems = toTrackedResult(result);

languageIds.set(uri, languageId);
@@ -94,7 +88,7 @@ export const trackResult = (uri: string, languageId: string, result: ResultData)
nextProblems.set(uri, problems);
} else {
prevProblems.set(uri, problems);
trackOpen(problems, languageId);
trackOpen(problems, languageId, s);
}
};

@@ -116,7 +110,9 @@ export const trackSave = (uri: string, languageId: string) => {
prevProblems.set(uri, next);
nextProblems.delete(uri);

trackEvent('vscode-save', determineHintStatus(prev, next, languageId));
const status = determineHintStatus(prev, next);

trackEvent('vscode-save', { ...status, languageId });

lastSaveTimes.set(uri, now);
};

This file was deleted.

@@ -5,16 +5,38 @@ import { Problem } from '@hint/utils-types';
import { IHintConstructor } from 'hint';

const stubContext = () => {
const stubs = { './app-insights': {} as typeof import('../../src/utils/app-insights') };
let _enabled = false;
const config = {
get: () => {},
set: () => {}
};
const stubs = {
'@hint/utils-telemetry': {
enabled() {
return _enabled;
},
initTelemetry(opts) {
_enabled = opts.enabled || false;
},
updateTelemetry(enabled) {
_enabled = enabled;
}
} as typeof import('@hint/utils-telemetry'),
configstore: class {
public constructor() {
return config;
}
}
};
const module = proxyquire('../../src/utils/analytics', stubs) as typeof import('../../src/utils/analytics');

return { module, stubs };
return { config, module, stubs };
};

test('It tracks when telemetry is first enabled', (t) => {
const sandbox = sinon.createSandbox();
const { module, stubs } = stubContext();
const trackEventSpy = sandbox.spy(stubs['./app-insights'], 'trackEvent');
const trackEventSpy = sandbox.spy(stubs['@hint/utils-telemetry'], 'trackEvent');

module.trackOptIn('ask', true);
t.true(trackEventSpy.notCalled);
@@ -41,7 +63,7 @@ test('It tracks when telemetry is first enabled', (t) => {
test('It tracks the first result for each document when opened', (t) => {
const sandbox = sinon.createSandbox();
const { module, stubs } = stubContext();
const trackEventSpy = sandbox.spy(stubs['./app-insights'], 'trackEvent');
const trackEventSpy = sandbox.spy(stubs['@hint/utils-telemetry'], 'trackEvent');

// First result should be tracked.
module.trackResult('test.html', 'html', {
@@ -90,7 +112,7 @@ test('It tracks the first result for each document when opened', (t) => {
test('It tracks the delta between the first and last results on save', (t) => {
const sandbox = sinon.createSandbox();
const { module, stubs } = stubContext();
const trackEventSpy = sandbox.spy(stubs['./app-insights'], 'trackEvent');
const trackEventSpy = sandbox.spy(stubs['@hint/utils-telemetry'], 'trackEvent');

// First result should be tracked.
module.trackResult('test.html', 'html', {
@@ -160,7 +182,7 @@ test('It tracks the delta between the first and last results on save', (t) => {
test('It tracks results again for a document when re-opened', (t) => {
const sandbox = sinon.createSandbox();
const { module, stubs } = stubContext();
const trackEventSpy = sandbox.spy(stubs['./app-insights'], 'trackEvent');
const trackEventSpy = sandbox.spy(stubs['@hint/utils-telemetry'], 'trackEvent');

// First result should be tracked.
module.trackResult('test.html', 'html', {

0 comments on commit 618e13a

Please sign in to comment.
You can’t perform that action at this time.