Skip to content

Commit

Permalink
feat: adds telemetry
Browse files Browse the repository at this point in the history
  • Loading branch information
Ken Howard committed Nov 21, 2018
1 parent 3c9c0d7 commit 9b91159
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 10 deletions.
7 changes: 7 additions & 0 deletions __mocks__/vscode-extension-telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// tslint:disable:no-any no-unsafe-any
const mockedImplementation = {
sendTelemetryEvent: jest.fn()
};

// tslint:disable-next-line:no-default-export
export default jest.fn(() => mockedImplementation);
3 changes: 3 additions & 0 deletions __mocks__/vscode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ module.exports = {
env: {
language: 'en-US'
},
extensions: {
getExtension: jest.fn(() => '1.0.0')
},
window: {
showErrorMessage: jest.fn(),
showInformationMessage: jest.fn(),
Expand Down
36 changes: 36 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
},
"dependencies": {
"@octokit/rest": "^15.17.0",
"tmp": "0.0.33"
"tmp": "0.0.33",
"vscode-extension-telemetry": "^0.1.0"
}
}
21 changes: 15 additions & 6 deletions src/commands/gists.commands.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { commands, window, workspace } from 'vscode';

import { getGist, getGists, updateGist } from '../gists';
import { insights } from '../insights';
import { logger } from '../logger';
import { extractTextDocumentDetails, filesSync, notify } from '../utils';

Expand All @@ -27,11 +28,11 @@ const _getGist = async (id: string): Promise<Gist | void> => {
}
};

const _openDocument = (file: string): Thenable<void> =>
workspace
.openTextDocument(file)
.then(window.showTextDocument)
.then(() => commands.executeCommand('workbench.action.keepEditor'));
const _openDocument = async (file: string): Promise<void> => {
const doc = await workspace.openTextDocument(file);
await window.showTextDocument(doc);
commands.executeCommand('workbench.action.keepEditor');
};

const openCodeBlock = async (): Promise<void> => {
let gistName = '';
Expand Down Expand Up @@ -60,7 +61,12 @@ const openCodeBlock = async (): Promise<void> => {
logger.info('Code Block Found');
const filePaths = filesSync(codeBlock.id, codeBlock.files);

filePaths.forEach(_openDocument);
// await is not available not available in forEach
for (const filePath of filePaths) {
await _openDocument(filePath);
}

insights.track('open', undefined, { fileCount: codeBlock.fileCount });
}
} catch (err) {
const error: Error = err as Error;
Expand All @@ -70,6 +76,7 @@ const openCodeBlock = async (): Promise<void> => {
`Could Not Open Gist ${gistName}`,
`Reason: ${error.message}`
);
insights.exception('openCodeBlock', { messsage: error.message });
}
}
};
Expand All @@ -83,6 +90,7 @@ const updateCodeBlock = async (doc: GistTextDocument): Promise<void> => {
logger.info(`Saving "${filename}"`);
await updateGist(id, filename, content);
notify.info(`Saved "${filename}"`);
insights.track('save');
} else {
logger.info(`"${filename}" Not a Gist`);
}
Expand All @@ -92,6 +100,7 @@ const updateCodeBlock = async (doc: GistTextDocument): Promise<void> => {
if (error && error.message === 'Not Found') {
notify.error(`Could Not Save ${file}`, `Reason: ${error.message}`);
}
insights.exception('updateCodeBlock', { messsage: error.message });
}
};

Expand Down
8 changes: 7 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
// tslint:disable:no-magic-numbers
import { Buffer } from 'buffer';

export const DEBUG = process.env.DEBUG === 'true';
export const GISTS_PER_PAGE = 9999;
export const LOGGER_LEVEL = 3;
export const TELEMETRY_COHORT_RANGE = [0, 10];
export const TELEMETRY_COHORT_RANGE = [0, 25];
export const TELEMETRY_WRITE_KEY = Buffer.from(
'OTgzNDBjN2UtOTExOS00OGM3LWI2OWMtOTY3NTE3MTZiOTg4',
'base64'
).toString();
export const TMP_DIRECTORY_PREFIX = 'vscode_gist';
7 changes: 5 additions & 2 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import * as vscode from 'vscode';

import { openCodeBlock, updateCodeBlock } from './commands';
import { DEBUG } from './constants';
import { insights } from './insights';
import { Levels, logger } from './logger';

export function activate(_: vscode.ExtensionContext): void {
const debug = process.env.DEBUG === 'true';
logger.setLevel(debug ? Levels.DEBUG : Levels.ERROR);
logger.setLevel(DEBUG ? Levels.DEBUG : Levels.ERROR);

logger.debug('extension activated');
vscode.commands.registerCommand('extension.openCodeBlock', openCodeBlock);
vscode.workspace.onDidSaveTextDocument(updateCodeBlock);

insights.track('activated');

vscode.commands.registerCommand(
'extension.openFavoriteCodeBlock',
(): void => {
Expand Down
43 changes: 43 additions & 0 deletions src/insights/__tests__/telemetry-service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// tslint:disable:no-any no-unsafe-any no-require-imports ordered-imports
import TelemetryReporter from 'vscode-extension-telemetry';

jest.genMockFromModule<TelemetryReporter>('vscode-extension-telemetry');
jest.mock('vscode-extension-telemetry');

const sendTelemetryEvent = jest.fn();

(TelemetryReporter as any).mockImplementation(
(): any => ({ sendTelemetryEvent })
);

import { telemetry } from '../telemetry-service';

describe('Telemetry Service Tests', () => {
beforeEach(() => {
(telemetry as any).enabled = true;
});
afterEach(() => {
(telemetry as any).enabled = true;
jest.resetAllMocks();
});
describe('#exception', () => {
test('should send telemetry event to app insights', () => {
telemetry.exception('telemetry-test', { message: 'testing' });
expect(sendTelemetryEvent).toHaveBeenCalledWith(
'exception',
{ context: 'telemetry-test', message: 'testing' },
{}
);
});
});
describe('#track', () => {
test('should send telemetry event to app insights', () => {
telemetry.track('telemetry-test', { message: 'testing' });
expect(sendTelemetryEvent).toHaveBeenCalledWith(
'telemetry-test',
{ message: 'testing' },
{}
);
});
});
});
3 changes: 3 additions & 0 deletions src/insights/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { telemetry as insights } from './telemetry-service';

export { insights };
87 changes: 87 additions & 0 deletions src/insights/telemetry-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { extensions } from 'vscode';
import TelemetryReporter from 'vscode-extension-telemetry';

import {
DEBUG,
TELEMETRY_COHORT_RANGE,
TELEMETRY_WRITE_KEY
} from '../constants';

interface Extension {
packageJSON: { version: string };
}

const extensionId = 'kenhowardpdx.vscode-gist';
const extension = extensions.getExtension(extensionId) as Extension;
const extensionVersion =
(extension && extension.packageJSON && extension.packageJSON.version) || '';

const telemetryCohortMax = 100;
const telemetryCohortMin = 1;
const telemetryCohort =
Math.floor(Math.random() * telemetryCohortMax) + telemetryCohortMin;

const enabled = ((): boolean => {
if (DEBUG) {
return true;
} else if (
telemetryCohort >= TELEMETRY_COHORT_RANGE[0] &&
telemetryCohort <= TELEMETRY_COHORT_RANGE[1]
) {
return true;
}

return false;
})();

class TelemetryService {
public static getInstance = (): TelemetryService =>
// TODO: permanently disable the semicolon rule
// tslint:disable-next-line:semicolon
TelemetryService.instance
? TelemetryService.instance
: new TelemetryService()

private static readonly instance?: TelemetryService;

private readonly enabled = enabled;
private reporter: TelemetryReporter;

private constructor() {
this.reporter = new TelemetryReporter(
extensionId,
extensionVersion,
TELEMETRY_WRITE_KEY
);
}

public configure(id: string, version: string, key: string): void {
this.reporter = new TelemetryReporter(id, version, key);
}

public exception(
context: string,
properties?: { [x: string]: string },
measurements?: { [x: string]: number }
): void {
this.track('exception', { ...properties, context }, { ...measurements });
}

public track(
eventName: string,
properties?: { [x: string]: string },
measurements?: { [x: string]: number }
): void {
if (!this.enabled) {
return;
}

this.reporter.sendTelemetryEvent(
eventName,
{ ...properties },
{ ...measurements }
);
}
}

export const telemetry = TelemetryService.getInstance();

0 comments on commit 9b91159

Please sign in to comment.