From bebcf3b519b5465cabaeb2496944b5caaedcaa8d Mon Sep 17 00:00:00 2001 From: Ryan Luker Date: Sun, 9 Apr 2017 15:37:57 -0700 Subject: [PATCH] Metrics and config cleanup (#44) * update readme to have useful link for open bugs * add request for metrics * update typescript version * cleanup and add metrics component * cleanup tslint * cleanup interfaces * add metrics and config integration * increment versions * use DI instead * add tests cleanup stricts * fix lintings * add platform --- README.md | 2 +- package.json | 450 ++++++++++++++++++++-------------------- src/config.ts | 35 ++-- src/extension.ts | 13 +- src/gutters.ts | 26 ++- src/indicators.ts | 25 +-- src/lcov.ts | 13 +- src/reporter.ts | 44 ++++ src/wrappers/request.ts | 10 + src/wrappers/uuid.ts | 7 + test/config.test.ts | 3 - test/gutters.test.ts | 28 ++- test/indicators.test.ts | 2 - test/lcov.test.ts | 3 - test/reporter.test.ts | 61 ++++++ 15 files changed, 432 insertions(+), 290 deletions(-) create mode 100644 src/reporter.ts create mode 100644 src/wrappers/request.ts create mode 100644 src/wrappers/uuid.ts create mode 100644 test/reporter.test.ts diff --git a/README.md b/README.md index a5c711b..e723c25 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Some examples for the highlight colour are as follows: https://developer.mozilla.org/en/docs/Web/CSS/background-color#Syntax ## Known Issues -- can only reliably handle one lcov file per workspace [#35](https://github.com/ryanluker/vscode-coverage-gutters/issues/35) +### [Open Bugs](https://github.com/ryanluker/vscode-coverage-gutters/issues?q=is%3Aopen+is%3Aissue+label%3Abug) ## Release Notes ### [Changelog](https://github.com/ryanluker/vscode-coverage-gutters/releases) diff --git a/package.json b/package.json index 13a4706..1d8e1f3 100644 --- a/package.json +++ b/package.json @@ -1,229 +1,233 @@ { - "name": "vscode-coverage-gutters", - "displayName": "Coverage Gutters", - "description": "Display test coverage generated by lcov - works with many languages", - "version": "0.3.0", - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/ryanluker/vscode-coverage-gutters" - }, - "icon": "images/icon.svg", - "galleryBanner": { - "color": "#24381b", - "theme": "dark" - }, - "bugs": "https://github.com/ryanluker/vscode-coverage-gutters/issues", - "publisher": "ryanluker", - "engines": { - "vscode": "^1.9.0" + "name": "vscode-coverage-gutters", + "displayName": "Coverage Gutters", + "description": "Display test coverage generated by lcov - works with many languages", + "version": "0.4.0", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/ryanluker/vscode-coverage-gutters" + }, + "icon": "images/icon.svg", + "galleryBanner": { + "color": "#24381b", + "theme": "dark" + }, + "bugs": "https://github.com/ryanluker/vscode-coverage-gutters/issues", + "publisher": "ryanluker", + "engines": { + "vscode": "^1.9.0" + }, + "categories": [ + "Other" + ], + "keywords": [ + "gutters", + "code coverage", + "lcov", + "vscode", + "extension" + ], + "main": "./out/src/extension", + "contributes": { + "configuration": { + "title": "coverage-gutters", + "properties": { + "coverage-gutters.lcovname": { + "type": "string", + "default": "lcov.info", + "description": "name of your lcov file" + }, + "coverage-gutters.altSfCompare": { + "type": "boolean", + "default": false, + "description": "uses a relative method of comparing lcov source file paths" + }, + "coverage-gutters.highlightlight": { + "type": "string", + "default": "rgba(166, 220, 142, 0.75)", + "description": "light themed highlight for code coverage" + }, + "coverage-gutters.highlightdark": { + "type": "string", + "default": "rgba(36, 56, 27, 0.75)", + "description": "dark themed highlight for code coverage" + }, + "coverage-gutters.partialHighlightLight": { + "type": "string", + "default": "rgba(220, 213, 143, 0.75)", + "description": "light theme partial highlight for code coverage" + }, + "coverage-gutters.partialHighlightDark": { + "type": "string", + "default": "rgba(57, 50, 27, 0.75)", + "description": "dark theme partial highlight for code coverage" + }, + "coverage-gutters.noHighlightLight": { + "type": "string", + "default": "rgba(220, 143, 143, 0.75)", + "description": "light theme partial highlight for code coverage" + }, + "coverage-gutters.noHighlightDark": { + "type": "string", + "default": "rgba(57, 27, 27, 0.75)", + "description": "dark theme partial highlight for code coverage" + }, + "coverage-gutters.gutterIconPathLight": { + "type": "string", + "default": "./images/gutter-icon-light.svg", + "description": "path to an icon (svg, png, etc) for displaying in the gutter for full coverage" + }, + "coverage-gutters.gutterIconPathDark": { + "type": "string", + "default": "./images/gutter-icon-dark.svg", + "description": "path to an icon (svg, png, etc) for displaying in the gutter for full coverage" + }, + "coverage-gutters.partialGutterIconPathLight": { + "type": "string", + "default": "./images/partial-gutter-icon-light.svg", + "description": "path to an icon (svg, png, etc) for displaying in the gutter for partial coverage" + }, + "coverage-gutters.partialGutterIconPathDark": { + "type": "string", + "default": "./images/partial-gutter-icon-dark.svg", + "description": "path to an icon (svg, png, etc) for displaying in the gutter for partial coverage" + }, + "coverage-gutters.noGutterIconPathLight": { + "type": "string", + "default": "./images/no-gutter-icon-light.svg", + "description": "path to an icon (svg, png, etc) for displaying in the gutter for no coverage" + }, + "coverage-gutters.noGutterIconPathDark": { + "type": "string", + "default": "./images/no-gutter-icon-dark.svg", + "description": "path to an icon (svg, png, etc) for displaying in the gutter for no coverage" + }, + "coverage-gutters.showLineCoverage": { + "type": "boolean", + "default": true, + "description": "show or hide the line coverage" + }, + "coverage-gutters.showRulerCoverage": { + "type": "boolean", + "default": true, + "description": "show or hide the ruler coverage" + }, + "coverage-gutters.showGutterCoverage": { + "type": "boolean", + "default": true, + "description": "show or hide the gutter coverage" + }, + "coverage-gutters.customizable.menus-editor-context-displayCoverage-enabled": { + "type": "boolean", + "default": true, + "description": "enable or disable the displayCoverage command in the editor/context menu" + }, + "coverage-gutters.customizable.menus-editor-context-watchLcovFile-enabled": { + "type": "boolean", + "default": true, + "description": "enable or disable the watchLcovFile command in the editor/context menu" + }, + "coverage-gutters.customizable.menus-editor-context-removeCoverage-enabled": { + "type": "boolean", + "default": true, + "description": "enable or disable the removeCoverage command in the editor/context menu" + }, + "coverage-gutters.customizable.keybindings-displayCoverage-enabled": { + "type": "boolean", + "default": true, + "description": "enable or disable the keybinding shortcut for enabling coverage on the active file" + }, + "coverage-gutters.customizable.keybindings-watchLcovFile-enabled": { + "type": "boolean", + "default": true, + "description": "enable or disable the keybinding shortcut for watching the lcov file" + }, + "coverage-gutters.customizable.keybindings-removeCoverage-enabled": { + "type": "boolean", + "default": true, + "description": "enable or disable the keybinding shortcut for removing the coverage on the active file" + } + } }, - "categories": [ - "Other" + "commands": [ + { + "command": "extension.displayCoverage", + "title": "Coverage Gutters: Display File Coverage" + }, + { + "command": "extension.watchLcovFile", + "title": "Coverage Gutters: Watch Lcov and Render Changes" + }, + { + "command": "extension.removeCoverage", + "title": "Coverage Gutters: Remove File Coverage" + } ], - "keywords": [ - "gutters", - "code coverage", - "lcov", - "vscode", - "extension" + "keybindings": [ + { + "command": "extension.displayCoverage", + "key": "ctrl+shift+7", + "mac": "shift+cmd+7", + "when": "config.coverage-gutters.customizable.keybindings-displayCoverage-enabled" + }, + { + "command": "extension.watchLcovFile", + "key": "ctrl+shift+8", + "mac": "shift+cmd+8", + "when": "config.coverage-gutters.customizable.keybindings-watchLcovFile-enabled" + }, + { + "command": "extension.removeCoverage", + "key": "ctrl+shift+9", + "mac": "shift+cmd+9", + "when": "config.coverage-gutters.customizable.keybindings-removeCoverage-enabled" + } ], - "main": "./out/src/extension", - "contributes": { - "configuration": { - "title": "coverage-gutters", - "properties": { - "coverage-gutters.lcovname": { - "type": "string", - "default": "lcov.info", - "description": "name of your lcov file" - }, - "coverage-gutters.altSfCompare": { - "type": "boolean", - "default": false, - "description": "uses a relative method of comparing lcov source file paths" - }, - "coverage-gutters.highlightlight": { - "type": "string", - "default": "rgba(166, 220, 142, 0.75)", - "description": "light themed highlight for code coverage" - }, - "coverage-gutters.highlightdark": { - "type": "string", - "default": "rgba(36, 56, 27, 0.75)", - "description": "dark themed highlight for code coverage" - }, - "coverage-gutters.partialHighlightLight": { - "type": "string", - "default": "rgba(220, 213, 143, 0.75)", - "description": "light theme partial highlight for code coverage" - }, - "coverage-gutters.partialHighlightDark": { - "type": "string", - "default": "rgba(57, 50, 27, 0.75)", - "description": "dark theme partial highlight for code coverage" - }, - "coverage-gutters.noHighlightLight": { - "type": "string", - "default": "rgba(220, 143, 143, 0.75)", - "description": "light theme partial highlight for code coverage" - }, - "coverage-gutters.noHighlightDark": { - "type": "string", - "default": "rgba(57, 27, 27, 0.75)", - "description": "dark theme partial highlight for code coverage" - }, - "coverage-gutters.gutterIconPathLight": { - "type": "string", - "default": "./images/gutter-icon-light.svg", - "description": "path to an icon (svg, png, etc) for displaying in the gutter for full coverage" - }, - "coverage-gutters.gutterIconPathDark": { - "type": "string", - "default": "./images/gutter-icon-dark.svg", - "description": "path to an icon (svg, png, etc) for displaying in the gutter for full coverage" - }, - "coverage-gutters.partialGutterIconPathLight": { - "type": "string", - "default": "./images/partial-gutter-icon-light.svg", - "description": "path to an icon (svg, png, etc) for displaying in the gutter for partial coverage" - }, - "coverage-gutters.partialGutterIconPathDark": { - "type": "string", - "default": "./images/partial-gutter-icon-dark.svg", - "description": "path to an icon (svg, png, etc) for displaying in the gutter for partial coverage" - }, - "coverage-gutters.noGutterIconPathLight": { - "type": "string", - "default": "./images/no-gutter-icon-light.svg", - "description": "path to an icon (svg, png, etc) for displaying in the gutter for no coverage" - }, - "coverage-gutters.noGutterIconPathDark": { - "type": "string", - "default": "./images/no-gutter-icon-dark.svg", - "description": "path to an icon (svg, png, etc) for displaying in the gutter for no coverage" - }, - "coverage-gutters.showLineCoverage": { - "type": "boolean", - "default": true, - "description": "show or hide the line coverage" - }, - "coverage-gutters.showRulerCoverage": { - "type": "boolean", - "default": true, - "description": "show or hide the ruler coverage" - }, - "coverage-gutters.showGutterCoverage": { - "type": "boolean", - "default": true, - "description": "show or hide the gutter coverage" - }, - "coverage-gutters.customizable.menus-editor-context-displayCoverage-enabled": { - "type": "boolean", - "default": true, - "description": "enable or disable the displayCoverage command in the editor/context menu" - }, - "coverage-gutters.customizable.menus-editor-context-watchLcovFile-enabled": { - "type": "boolean", - "default": true, - "description": "enable or disable the watchLcovFile command in the editor/context menu" - }, - "coverage-gutters.customizable.menus-editor-context-removeCoverage-enabled": { - "type": "boolean", - "default": true, - "description": "enable or disable the removeCoverage command in the editor/context menu" - }, - "coverage-gutters.customizable.keybindings-displayCoverage-enabled": { - "type": "boolean", - "default": true, - "description": "enable or disable the keybinding shortcut for enabling coverage on the active file" - }, - "coverage-gutters.customizable.keybindings-watchLcovFile-enabled": { - "type": "boolean", - "default": true, - "description": "enable or disable the keybinding shortcut for watching the lcov file" - }, - "coverage-gutters.customizable.keybindings-removeCoverage-enabled": { - "type": "boolean", - "default": true, - "description": "enable or disable the keybinding shortcut for removing the coverage on the active file" - } - } - }, - "commands": [ - { - "command": "extension.displayCoverage", - "title": "Coverage Gutters: Display File Coverage" - }, - { - "command": "extension.watchLcovFile", - "title": "Coverage Gutters: Watch Lcov and Render Changes" - }, - { - "command": "extension.removeCoverage", - "title": "Coverage Gutters: Remove File Coverage" - } - ], - "keybindings":[ - { - "command": "extension.displayCoverage", - "key": "ctrl+shift+7", - "mac": "shift+cmd+7", - "when": "config.coverage-gutters.customizable.keybindings-displayCoverage-enabled" - }, - { - "command": "extension.watchLcovFile", - "key": "ctrl+shift+8", - "mac": "shift+cmd+8", - "when": "config.coverage-gutters.customizable.keybindings-watchLcovFile-enabled" - }, - { - "command": "extension.removeCoverage", - "key": "ctrl+shift+9", - "mac": "shift+cmd+9", - "when": "config.coverage-gutters.customizable.keybindings-removeCoverage-enabled" - } - ], - "menus": { - "editor/context": [ - { - "when": "config.coverage-gutters.customizable.menus-editor-context-displayCoverage-enabled", - "command": "extension.displayCoverage", - "group": "Coverage-Gutters@1" - }, - { - "when": "config.coverage-gutters.customizable.menus-editor-context-watchLcovFile-enabled", - "command": "extension.watchLcovFile", - "group": "Coverage-Gutters@2" - }, - { - "when": "config.coverage-gutters.customizable.menus-editor-context-removeCoverage-enabled", - "command": "extension.removeCoverage", - "group": "Coverage-Gutters@3" - } - ] + "menus": { + "editor/context": [ + { + "when": "config.coverage-gutters.customizable.menus-editor-context-displayCoverage-enabled", + "command": "extension.displayCoverage", + "group": "Coverage-Gutters@1" + }, + { + "when": "config.coverage-gutters.customizable.menus-editor-context-watchLcovFile-enabled", + "command": "extension.watchLcovFile", + "group": "Coverage-Gutters@2" + }, + { + "when": "config.coverage-gutters.customizable.menus-editor-context-removeCoverage-enabled", + "command": "extension.removeCoverage", + "group": "Coverage-Gutters@3" } - }, - "activationEvents": [ - "*" - ], - "scripts": { - "lint": "tslint './src/**/*.ts' './test/**/*.ts'", - "test": "node ./node_modules/vscode/bin/test", - "test-ci": "npm run lint && npm test --silent", - "vscode:prepublish": "tsc -p ./", - "compile": "tsc -watch -p ./", - "postinstall": "node ./node_modules/vscode/bin/install" - }, - "devDependencies": { - "typescript": "^2.0.3", - "vscode": "^1.0.0", - "mocha": "^2.3.3", - "tslint": "^4.5.1", - "@types/node": "^6.0.40", - "@types/mocha": "^2.2.32" - }, - "dependencies": { - "lcov-parse": "0.0.10" + ] } -} \ No newline at end of file + }, + "activationEvents": [ + "*" + ], + "scripts": { + "lint": "tslint './src/**/*.ts' './test/**/*.ts'", + "test": "node ./node_modules/vscode/bin/test", + "test-ci": "npm run lint && npm test --silent", + "vscode:prepublish": "tsc -p ./", + "compile": "tsc -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install" + }, + "devDependencies": { + "typescript": "^2.2.2", + "vscode": "^1.1.0", + "mocha": "^2.3.3", + "tslint": "^5.0.0", + "@types/node": "^6.0.40", + "@types/mocha": "^2.2.32", + "@types/request": "0.0.42", + "@types/uuid": "2.0.29" + }, + "dependencies": { + "lcov-parse": "0.0.10", + "request": "2.81.0", + "uuid": "3.0.1" + } +} diff --git a/src/config.ts b/src/config.ts index d5db5e8..664eefb 100644 --- a/src/config.ts +++ b/src/config.ts @@ -3,25 +3,23 @@ import { ExtensionContext, OverviewRulerLane, TextEditorDecorationType, + WorkspaceConfiguration, } from "vscode"; +import {Reporter} from "./reporter"; import {InterfaceVscode} from "./wrappers/vscode"; -export type ConfigStore = { +export interface IConfigStore { lcovFileName: string; fullCoverageDecorationType: TextEditorDecorationType; partialCoverageDecorationType: TextEditorDecorationType; noCoverageDecorationType: TextEditorDecorationType; altSfCompare: boolean; -}; - -export interface InterfaceConfig { - get(): ConfigStore; - setup(): ConfigStore; } -export class Config implements InterfaceConfig { +export class Config { private vscode: InterfaceVscode; private context: ExtensionContext; + private reporter: Reporter; private lcovFileName: string; private fullCoverageDecorationType: TextEditorDecorationType; @@ -29,12 +27,13 @@ export class Config implements InterfaceConfig { private noCoverageDecorationType: TextEditorDecorationType; private altSfCompare: boolean; - constructor(vscode: InterfaceVscode, context: ExtensionContext) { + constructor(vscode: InterfaceVscode, context: ExtensionContext, reporter: Reporter) { this.vscode = vscode; this.context = context; + this.reporter = reporter; } - public get(): ConfigStore { + public get(): IConfigStore { return { altSfCompare: this.altSfCompare, fullCoverageDecorationType: this.fullCoverageDecorationType, @@ -44,19 +43,23 @@ export class Config implements InterfaceConfig { }; } - public setup(): ConfigStore { - // Customizable UI configurations + public setup(): IConfigStore { const rootCustomConfig = this.vscode.getConfiguration("coverage-gutters.customizable"); + this.sendConfigMetrics(rootCustomConfig, "customConfig"); + + // Customizable UI configurations const configsCustom = Object.keys(rootCustomConfig); - for (let element of configsCustom) { + for (const element of configsCustom) { this.vscode.executeCommand( "setContext", "config.coverage-gutters.customizable." + element, rootCustomConfig.get(element)); } - // Basic configurations const rootConfig = this.vscode.getConfiguration("coverage-gutters"); + this.sendConfigMetrics(rootConfig, "config"); + + // Basic configurations this.lcovFileName = rootConfig.get("lcovname") as string; this.altSfCompare = rootConfig.get("altSfCompare") as boolean; @@ -130,4 +133,10 @@ export class Config implements InterfaceConfig { return this.get(); } + + private sendConfigMetrics(config: WorkspaceConfiguration, category: string) { + Object.keys(config).forEach((configElement) => { + this.reporter.sendEvent(category, configElement, config.get(configElement) as string); + }); + } } diff --git a/src/extension.ts b/src/extension.ts index ef45ae8..f5225f1 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,18 +1,23 @@ import * as vscode from "vscode"; import {Gutters} from "./gutters"; +import {Reporter} from "./reporter"; +import {Request} from "./wrappers/request"; +import {Uuid} from "./wrappers/uuid"; export function activate(context: vscode.ExtensionContext) { - let gutters = new Gutters(context); + const enableMetrics = vscode.workspace.getConfiguration("telemetry").get("enableTelemetry") as boolean; + const reporter = new Reporter(new Request(), new Uuid(), enableMetrics); + const gutters = new Gutters(context, reporter); - let display = vscode.commands.registerCommand("extension.displayCoverage", () => { + const display = vscode.commands.registerCommand("extension.displayCoverage", () => { gutters.displayCoverageForActiveFile(); }); - let watchLcovFile = vscode.commands.registerCommand("extension.watchLcovFile", () => { + const watchLcovFile = vscode.commands.registerCommand("extension.watchLcovFile", () => { gutters.watchLcovFile(); }); - let remove = vscode.commands.registerCommand("extension.removeCoverage", () => { + const remove = vscode.commands.registerCommand("extension.removeCoverage", () => { gutters.removeCoverageForActiveFile(); }); diff --git a/src/gutters.ts b/src/gutters.ts index bff79d2..85c0957 100644 --- a/src/gutters.ts +++ b/src/gutters.ts @@ -4,30 +4,35 @@ import { TextEditor, window, } from "vscode"; + import {Fs} from "./wrappers/fs"; import {LcovParse} from "./wrappers/lcov-parse"; import {Vscode} from "./wrappers/vscode"; -import {Config, ConfigStore} from "./config"; -import {Indicators, InterfaceIndicators} from "./indicators"; -import {InterfaceLcov, Lcov} from "./lcov"; +import {Config, IConfigStore} from "./config"; +import {Indicators} from "./indicators"; +import {Lcov} from "./lcov"; +import {Reporter} from "./reporter"; const vscodeImpl = new Vscode(); const fsImpl = new Fs(); const parseImpl = new LcovParse(); export class Gutters { - private configStore: ConfigStore; + private configStore: IConfigStore; private fileWatcher: FileSystemWatcher; - private lcov: InterfaceLcov; - private indicators: InterfaceIndicators; + private lcov: Lcov; + private indicators: Indicators; + private reporter: Reporter; private textEditors: TextEditor[]; - constructor(context: ExtensionContext) { - this.configStore = new Config(vscodeImpl, context).setup(); + constructor(context: ExtensionContext, reporter: Reporter) { + this.configStore = new Config(vscodeImpl, context, reporter).setup(); this.lcov = new Lcov(this.configStore, vscodeImpl, fsImpl); this.indicators = new Indicators(parseImpl, vscodeImpl, this.configStore); + this.reporter = reporter; this.textEditors = []; + this.reporter.sendEvent("user", "start"); } public async displayCoverageForActiveFile() { @@ -36,6 +41,7 @@ export class Gutters { try { const lcovPath = await this.lcov.find(); await this.loadAndRenderCoverage(textEditor, lcovPath); + this.reporter.sendEvent("user", "display-coverage"); } catch (error) { this.handleError(error); } @@ -60,6 +66,7 @@ export class Gutters { } }); }); + this.reporter.sendEvent("user", "watch-lcov"); } catch (error) { this.handleError(error); } @@ -69,11 +76,13 @@ export class Gutters { const activeEditor = window.activeTextEditor; this.removeTextEditorFromCache(activeEditor); this.removeDecorationsForTextEditor(activeEditor); + this.reporter.sendEvent("user", "remove-coverage"); } public dispose() { this.fileWatcher.dispose(); this.textEditors.forEach(this.removeDecorationsForTextEditor); + this.reporter.sendEvent("cleanup", "dispose"); } public getTextEditors(): TextEditor[] { @@ -83,6 +92,7 @@ export class Gutters { private handleError(error: Error) { const message = error.message ? error.message : error; window.showErrorMessage(message.toString()); + this.reporter.sendEvent("error", message.toString()); } private addTextEditorToCache(editor: TextEditor) { diff --git a/src/indicators.ts b/src/indicators.ts index 74d680c..f6752b2 100644 --- a/src/indicators.ts +++ b/src/indicators.ts @@ -1,30 +1,25 @@ -import {ConfigStore} from "./config"; +import {IConfigStore} from "./config"; import {InterfaceLcovParse} from "./wrappers/lcov-parse"; import {InterfaceVscode} from "./wrappers/vscode"; import {LcovSection} from "lcov-parse"; import {Range, TextEditor} from "vscode"; -export interface InterfaceIndicators { - renderToTextEditor(lines: LcovSection, textEditor: TextEditor): Promise; - extract(lcovFile: string, file: string): Promise; -} - -export type CoverageLines = { +export interface ICoverageLines { full: Range[]; partial: Range[]; none: Range[]; -}; +} -export class Indicators implements InterfaceIndicators { +export class Indicators { private parse: InterfaceLcovParse; private vscode: InterfaceVscode; - private configStore: ConfigStore; + private configStore: IConfigStore; constructor( parse: InterfaceLcovParse, vscode: InterfaceVscode, - configStore: ConfigStore, + configStore: IConfigStore, ) { this.parse = parse; this.vscode = vscode; @@ -33,7 +28,7 @@ export class Indicators implements InterfaceIndicators { public renderToTextEditor(section: LcovSection, textEditor: TextEditor): Promise { return new Promise((resolve, reject) => { - let coverageLines: CoverageLines = { + const coverageLines: ICoverageLines = { full: [], none: [], partial: [], @@ -50,7 +45,7 @@ export class Indicators implements InterfaceIndicators { return new Promise((resolve, reject) => { this.parse.source(lcovFile, (err, data) => { if (err) { return reject(err); } - let section = data.find((lcovSection) => { + const section = data.find((lcovSection) => { return this.compareFilePaths(lcovSection.file, file); }); @@ -60,7 +55,7 @@ export class Indicators implements InterfaceIndicators { }); } - private setDecorationsForEditor(editor: TextEditor, coverage: CoverageLines) { + private setDecorationsForEditor(editor: TextEditor, coverage: ICoverageLines) { // remove coverage first to prevent graphical conflicts editor.setDecorations(this.configStore.fullCoverageDecorationType, []); editor.setDecorations(this.configStore.noCoverageDecorationType, []); @@ -71,7 +66,7 @@ export class Indicators implements InterfaceIndicators { editor.setDecorations(this.configStore.partialCoverageDecorationType, coverage.partial); } - private filterCoverage(section: LcovSection, coverageLines: CoverageLines): CoverageLines { + private filterCoverage(section: LcovSection, coverageLines: ICoverageLines): ICoverageLines { section.lines.details.forEach((detail) => { const lineRange = new Range(detail.line - 1, 0, detail.line - 1, 0); if (detail.hit > 0) { diff --git a/src/lcov.ts b/src/lcov.ts index 82ef2d2..0c5d6e5 100644 --- a/src/lcov.ts +++ b/src/lcov.ts @@ -1,19 +1,14 @@ -import {ConfigStore} from "./config"; +import {IConfigStore} from "./config"; import {InterfaceFs} from "./wrappers/fs"; import {InterfaceVscode} from "./wrappers/vscode"; -export interface InterfaceLcov { - find(): Promise; - load(lcovPath: string): Promise; -} - -export class Lcov implements InterfaceLcov { - private configStore: ConfigStore; +export class Lcov { + private configStore: IConfigStore; private vscode: InterfaceVscode; private fs: InterfaceFs; constructor( - configStore: ConfigStore, + configStore: IConfigStore, vscode: InterfaceVscode, fs: InterfaceFs, ) { diff --git a/src/reporter.ts b/src/reporter.ts new file mode 100644 index 0000000..416dd9c --- /dev/null +++ b/src/reporter.ts @@ -0,0 +1,44 @@ +import {platform} from "os"; +import {Request} from "./wrappers/request"; +import {Uuid} from "./wrappers/uuid"; + +const PLATFORM = platform(); +const GA_TRACKING_ID = ""; // add before a release; +const EXT_NAME = "vscode-coverage-gutters"; +const EXT_VERSION = "0.4.0"; + +export class Reporter { + private readonly cid: string; + private readonly enableMetrics: boolean; + private readonly request: Request; + + constructor(request: Request, uuid: Uuid, enableMetrics: boolean) { + this.request = request; + this.cid = uuid.get(); + this.enableMetrics = enableMetrics; + } + + public sendEvent( + category: string, + action: string, + label?: string, + value?: number, + ) { + if (!this.enableMetrics) { return; } + const data = { + an: EXT_NAME, + av: EXT_VERSION, + cid: this.cid, + ea: action, + ec: category, + el: label, + ev: value, + t: "event", + tid: GA_TRACKING_ID, + ua: PLATFORM, + v: "1", + }; + + return this.request.post("https://www.google-analytics.com/collect", { form: data }); + } +} diff --git a/src/wrappers/request.ts b/src/wrappers/request.ts new file mode 100644 index 0000000..f716dc2 --- /dev/null +++ b/src/wrappers/request.ts @@ -0,0 +1,10 @@ +import {post} from "request"; + +export interface IOptions { form?: object; } + +export class Request { + public post(uri: string, options?: IOptions): void { + post(uri, options); + return; + } +} diff --git a/src/wrappers/uuid.ts b/src/wrappers/uuid.ts new file mode 100644 index 0000000..4c49ee7 --- /dev/null +++ b/src/wrappers/uuid.ts @@ -0,0 +1,7 @@ +import {v4} from "uuid"; + +export class Uuid { + public get(): string { + return v4(); + } +} diff --git a/test/config.test.ts b/test/config.test.ts index 08bf924..04af4b0 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -1,7 +1,4 @@ -"use strict"; - import * as assert from "assert"; - import * as vscode from "vscode"; import {Config} from "../src/config"; diff --git a/test/gutters.test.ts b/test/gutters.test.ts index 40bc905..66f7ba7 100644 --- a/test/gutters.test.ts +++ b/test/gutters.test.ts @@ -1,21 +1,25 @@ -"use strict"; - import * as assert from "assert"; - import * as vscode from "vscode"; import {Gutters} from "../src/gutters"; +import {Reporter} from "../src/reporter"; suite("Gutters Tests", function() { test("Should setup gutters based on config values with no errors", function(done) { this.timeout(12000); try { - let ctx: vscode.ExtensionContext = { + const ctx: vscode.ExtensionContext = { asAbsolutePath() { return "test"; }, subscriptions: [], - }; - const gutters = new Gutters(ctx); + } as any; + const reporter: Reporter = { + sendEvent() { + return; + }, + } as any; + + const gutters = new Gutters(ctx, reporter); assert.equal(gutters.getTextEditors().length, 0); return done(); } catch (e) { @@ -25,13 +29,19 @@ suite("Gutters Tests", function() { test("Should remove the activeEditor from the textEditors array", async function() { this.timeout(12000); - let ctx: vscode.ExtensionContext = { + const ctx: vscode.ExtensionContext = { asAbsolutePath() { return "test"; }, subscriptions: [], - }; - const gutters = new Gutters(ctx); + } as any; + const reporter: Reporter = { + sendEvent() { + return; + }, + } as any; + + const gutters = new Gutters(ctx, reporter); await gutters.displayCoverageForActiveFile(); assert.equal(gutters.getTextEditors().length, 1); gutters.removeCoverageForActiveFile(); diff --git a/test/indicators.test.ts b/test/indicators.test.ts index 918aca4..f2dc42e 100644 --- a/test/indicators.test.ts +++ b/test/indicators.test.ts @@ -1,5 +1,3 @@ -"use strict"; - import * as assert from "assert"; import {BranchDetail, LcovSection} from "lcov-parse"; import {TextEditor} from "vscode"; diff --git a/test/lcov.test.ts b/test/lcov.test.ts index c91a540..ac445fa 100644 --- a/test/lcov.test.ts +++ b/test/lcov.test.ts @@ -1,7 +1,4 @@ -"use strict"; - import * as assert from "assert"; - import {Lcov} from "../src/lcov"; import {Fs} from "../src/wrappers/fs"; import {Vscode} from "../src/wrappers/vscode"; diff --git a/test/reporter.test.ts b/test/reporter.test.ts new file mode 100644 index 0000000..20d87ab --- /dev/null +++ b/test/reporter.test.ts @@ -0,0 +1,61 @@ +import * as assert from "assert"; +import {Reporter} from "../src/reporter"; +import {IOptions} from "../src/wrappers/request"; + +suite("Reporter Tests", function() { + test("Should not report metrics if enabledMetrics false", function() { + const fakeRequest = { + post(uri: string, options?: IOptions) { + assert.equal(1, 2); + return; + }, + }; + + const fakeUuid = { + get() { + return "fakeuuidhere"; + }, + }; + + const reporter = new Reporter(fakeRequest, fakeUuid, false); + reporter.sendEvent("test", "action"); + }); + + test("Should send metrics if enabledMetrics is true", function() { + const fakeRequest = { + post(uri: string, options?: IOptions) { + // tslint:disable-next-line:no-string-literal + assert.equal(options.form["ec"], "test"); + return; + }, + }; + + const fakeUuid = { + get() { + return "fakeuuidhere"; + }, + }; + + const reporter = new Reporter(fakeRequest, fakeUuid, true); + reporter.sendEvent("test", "action"); + }); + + test("GA tracking id should not be set in code", function() { + const fakeRequest = { + post(uri: string, options?: IOptions) { + // tslint:disable-next-line:no-string-literal + assert.equal(options.form["tid"], ""); + return; + }, + }; + + const fakeUuid = { + get() { + return "fakeuuidhere"; + }, + }; + + const reporter = new Reporter(fakeRequest, fakeUuid, true); + reporter.sendEvent("test", "action"); + }); +});