From a33b8aa3a16e43ffd79e69e9c2433a5937cab004 Mon Sep 17 00:00:00 2001 From: Sorin Sbarnea Date: Fri, 6 Jan 2023 16:31:51 +0000 Subject: [PATCH] Enable prettier formatting - Uses default prettier settings --- .github/workflows/ci.yaml | 37 +-- .pre-commit-config.yaml | 16 + .vscode/extensions.json | 3 + .vscode/settings.json | 11 + README.md | 90 +++--- USAGE_DATA.md | 57 ++-- package.json | 2 + src/che/cheIdManager.ts | 61 ++-- src/config/telemetry-config.json | 20 +- src/gitpod/gitpodIdManager.ts | 47 ++- src/index.ts | 12 +- src/interfaces/cacheService.ts | 16 +- src/interfaces/envVar.ts | 4 +- src/interfaces/environment.ts | 96 +++--- src/interfaces/idManager.ts | 12 +- src/interfaces/redhatService.ts | 20 +- src/interfaces/settings.ts | 4 +- src/interfaces/telemetry.ts | 56 ++-- src/node/platform.ts | 143 +++++---- src/services/AnalyticsEvent.ts | 14 +- src/services/FileSystemStorageService.ts | 74 ++--- src/services/configuration.ts | 181 +++++------ src/services/configurationManager.ts | 210 +++++++------ src/services/fileSystemCacheService.ts | 43 +-- src/services/fileSystemIdManager.ts | 10 +- src/services/idManagerFactory.ts | 28 +- src/services/reporter.ts | 41 +-- src/services/telemetryServiceBuilder.ts | 173 ++++++----- src/services/telemetryServiceImpl.ts | 54 ++-- src/tests/config/telemetry-config.json | 42 +-- src/tests/gitpod/gitpodIdManager.test.ts | 54 ++-- src/tests/services/configuration.test.ts | 263 ++++++++-------- .../services/configurationManager.test.ts | 285 +++++++++--------- .../services/fileSystemCacheService.test.ts | 55 ++-- src/tests/utils/events.test.ts | 186 ++++++------ src/tests/utils/geolocation.test.ts | 22 +- src/utils/events.ts | 64 ++-- src/utils/extensions.ts | 14 +- src/utils/geolocation.ts | 17 +- src/utils/hashcode.ts | 35 ++- src/utils/logger.ts | 7 +- src/utils/segmentInitializer.ts | 23 +- src/utils/telemetryEventQueue.ts | 2 +- src/utils/uuid.ts | 32 +- src/vscode/constants.ts | 12 +- src/vscode/redhatService.ts | 117 ++++--- src/vscode/settings.ts | 30 +- test/index.ts | 5 +- tsconfig.json | 10 +- webpack.config.ts | 22 +- 50 files changed, 1508 insertions(+), 1324 deletions(-) create mode 100644 .pre-commit-config.yaml create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3ff5f16..a22b574 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -21,25 +21,28 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - # Set up Node - - name: Use Node 14 - uses: actions/setup-node@v1 - with: - node-version: 14 - registry-url: 'https://registry.npmjs.org' + # Set up Node + - name: Use Node 14 + uses: actions/setup-node@v1 + with: + node-version: 14 + registry-url: "https://registry.npmjs.org" - # Run install dependencies - - name: Install dependencies - run: npm i + # Run install dependencies + - name: Install dependencies + run: npm i - # Build - - name: Build - run: npm run prepublish + - name: Lint + run: npm run lint - - name: Test - run: npm test + # Build + - name: Build + run: npm run prepublish - - name: Test packaging - run: npm run package + - name: Test + run: npm test + + - name: Test packaging + run: npm run package diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e5a4cc8 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +--- +ci: + autoupdate_commit_msg: "chore: pre-commit autoupdate" + autoupdate_schedule: monthly + autofix_commit_msg: | + chore: auto fixes from pre-commit.com hooks + + for more information, see https://pre-commit.ci +minimum_pre_commit_version: 2.9.0 # types_or +repos: + - repo: https://github.com/pre-commit/mirrors-prettier + # keep it before markdownlint and eslint + rev: "v3.0.0-alpha.4" + hooks: + - id: prettier + types_or: ["markdown", "json", "ts"] diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c83e263 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["esbenp.prettier-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..fe2c4d6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "editor.formatOnSave": true, + "[typescript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "files.exclude": { + ".nyc_output": true, + "coverage": true, + "lib": true + } +} diff --git a/README.md b/README.md index f23a380..98a5b3f 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,16 @@ It's also possible to opt-in later, by setting the `redhat.telemetry.enabled` us From File > Preferences > Settings (On macOS: Code > Preferences > Settings), search for telemetry, and check the `Redhat > Telemetry : Enabled` setting. This will enable sending all telemetry events from Red Hat extensions going forward. - ## How to disable telemetry reporting? -If you want to stop sending usage data to Red Hat, you can set the `redhat.telemetry.enabled` user setting to `false`. + +If you want to stop sending usage data to Red Hat, you can set the `redhat.telemetry.enabled` user setting to `false`. From File > Preferences > Settings (On macOS: Code > Preferences > Settings), search for telemetry, and uncheck the `Redhat > Telemetry : Enabled` setting. This will silence all telemetry events from Red Hat extensions going forward. Additionally, and starting from version 0.5.0, this module abides by Visual Studio Code's telemetry level: if `telemetry.telemetryLevel` is set to `off`, then no telemetry events will be sent to Red Hat, even if `redhat.telemetry.enabled` is set to `true`. If `telemetry.telemetryLevel` is set to `error` or `crash`, only events containing an `error` or `errors` property will be sent to Red Hat. # Remote configuration + Starting from version 0.5.0, Red Hat Telemetry can be remotely configured. Once every 12h (or whatever is remotely configured), [telemetry-config.json](src/config/telemetry-config.json) will be downloaded to, depending on your platform: - **Windows** `%APPDATA%\Code\User\globalStorage\vscode-redhat-telemetry\cache\telemetry-config.json` @@ -31,35 +32,35 @@ Starting from version 0.5.0, Red Hat Telemetry can be remotely configured. Once This allows Red Hat extensions to limit the events to be sent, by including or excluding certain events, by name or containing properties, or by limiting the ratio of users sending data. eg.: + - 50% of `redhat.vscode-hypothetical` users only, to report error events, excluding stackoverflows: ```json { - "*": { - "enabled":"all", // supports "all", "error", "crash", "off" - "refresh": "12h", - "includes": [ - { - "name" : "*" - } - ] - }, - "redhat.vscode-hypothetical": { - "enabled": "error", - "ratio": "0.5", - "excludes": [ - { - "property": "error", - "value": "*stackoverflow*" - } - ] - } + "*": { + "enabled": "all", // supports "all", "error", "crash", "off" + "refresh": "12h", + "includes": [ + { + "name": "*" + } + ] + }, + "redhat.vscode-hypothetical": { + "enabled": "error", + "ratio": "0.5", + "excludes": [ + { + "property": "error", + "value": "*stackoverflow*" + } + ] + } } ``` Extension configuration inherits and overrides the `*` configuration. - # How to use this library ## Add the `@redhat-developer/vscode-redhat-telemetry` dependency @@ -69,7 +70,9 @@ In order to install [`@redhat-developer/vscode-redhat-telemetry`](https://github ``` npm i @redhat-developer/vscode-redhat-telemetry ``` + ## Contribute the `redhat.telemetry.enabled` preference + Unless your extension already depends on a telemetry-enabled Red Hat extension, it needs to declare the `redhat.telemetry.enabled` preference in its package.json, like: ``` @@ -89,7 +92,9 @@ Unless your extension already depends on a telemetry-enabled Red Hat extension, } } ``` + ## [Optional] Add a custom segment key in package.json file + By default, extensions will send their data to https://app.segment.com/redhat-devtools/sources/vscode/. In development mode, the data is sent to https://app.segment.com/redhat-devtools/sources/vs_code_tests/. - You can specify custom segment keys in your package.json, to connect and push usage data to https://segment.com/ @@ -102,13 +107,14 @@ By default, extensions will send their data to https://app.segment.com/redhat-de ## Add the below code to your `extension.ts` Get a reference to the RedHatService instance from your VS Code extension's `activate` method in `extension.ts`: + ```typescript import { getRedHatService, TelemetryService } from "@redhat-developer/vscode-redhat-telemetry"; let telemetryService: TelemetryService = null; export async function activate(context: ExtensionContext) { - const redhatService = await getRedHatService(context); + const redhatService = await getRedHatService(context); telemetryService = await redhatService.getTelemetryService(); telemetryService.sendStartupEvent(); ... @@ -126,7 +132,7 @@ if (telemetryService) { name: "Test Event", type: "track", // optional type (track is the default) properties: { // optional custom properties - foo: "bar", + foo: "bar", } }; telemetryService.send(event); @@ -134,28 +140,32 @@ if (telemetryService) { ``` To access the anonymous Red Hat UUID for the current user: + ```typescript -const redhatUuid = await (await redhatService.getIdManager()).getRedHatUUID(); +const redhatUuid = await(await redhatService.getIdManager()).getRedHatUUID(); ``` Once your extension is deactivated, a shutdown event, including the session duration, will automatically be sent on its behalf. However, shutdown event delivery is not guaranteed, in case VS Code is faster to exit than to send those last events. All event properties are automatically sanitized to anonymize all paths (best effort) and references to the username. - ## Publicly document your data collection Once telemetry is in place, you need to document the extent of the telemetry collection performed by your extension. -* add a USAGE_DATA.md page to your extension's repository, listing the type of data being collected by your extension. -* add a `Data and Telemetry` paragraph at the end of your extension's README file: -> `The ***** extension collects anonymous [usage data](USAGE_DATA.md) and sends it to Red Hat servers to help improve our products and services. Read our [privacy statement](https://developers.redhat.com/article/tool-data-collection) to learn more. This extension respects the `redhat.telemetry.enabled` setting which you can learn more about at https://github.com/redhat-developer/vscode-redhat-telemetry#how-to-disable-telemetry-reporting` -* add a reference to your telemetry documentation page to this repository's own [USAGE_DATA.md](https://github.com/redhat-developer/vscode-redhat-telemetry/blob/HEAD/USAGE_DATA.md#other-extensions). +- add a USAGE_DATA.md page to your extension's repository, listing the type of data being collected by your extension. +- add a `Data and Telemetry` paragraph at the end of your extension's README file: + + > `The ***** extension collects anonymous [usage data](USAGE_DATA.md) and sends it to Red Hat servers to help improve our products and services. Read our [privacy statement](https://developers.redhat.com/article/tool-data-collection) to learn more. This extension respects the `redhat.telemetry.enabled` setting which you can learn more about at https://github.com/redhat-developer/vscode-redhat-telemetry#how-to-disable-telemetry-reporting` + +- add a reference to your telemetry documentation page to this repository's own [USAGE_DATA.md](https://github.com/redhat-developer/vscode-redhat-telemetry/blob/HEAD/USAGE_DATA.md#other-extensions). ### Checking telemetry during development + In your `.vscode/launch.json`: + - set the `VSCODE_REDHAT_TELEMETRY_DEBUG` environment variable to `true`, to log telemetry events in the console -- set the `REDHAT_TELEMETRY_REMOTE_CONFIG_URL` environment variable to the URL of a remote configuration file, if you need to test remote configuration +- set the `REDHAT_TELEMETRY_REMOTE_CONFIG_URL` environment variable to the URL of a remote configuration file, if you need to test remote configuration ```json { @@ -176,21 +186,22 @@ In your `.vscode/launch.json`: }, ``` - # How to use from a VS Code webview -From a VS Code webview, since you can not rely on accessing the filesystem, you need to instanciate the `TelemetryService` from a `TelemetryServiceBuilder`, providing browser-specific implementations of services for collecting data. + +From a VS Code webview, since you can not rely on accessing the filesystem, you need to instanciate the `TelemetryService` from a `TelemetryServiceBuilder`, providing browser-specific implementations of services for collecting data. To get a reference to the TelemetryService instance for your VS Code extension: + ```typescript import { TelemetryServiceBuilder, TelemetryService, TelemetrySettings, Environment, IdManager } from "@redhat-developer/vscode-redhat-telemetry"; ... -const packageJson: any = ...; // an object defining `{publisher:string, name:string, version:string, segmentWriteKey:string}` +const packageJson: any = ...; // an object defining `{publisher:string, name:string, version:string, segmentWriteKey:string}` const idManager: IdManager = ...; // a service returning Red Hat anonymous UUID const environment: Environment = ...; // an object containing environment specific data (OS, locale...) const settings:TelemetrySettings = ...; // an object checking whether telemetry collection is enabled const telemetryService: TelemetryService = new TelemetryServiceBuilder(packageJson) - .setIdManager(idManager) - .setEnvironment(environment) + .setIdManager(idManager) + .setEnvironment(environment) .setSettings(settings) ... let event = { @@ -204,14 +215,19 @@ const REDHAT_UUID = idManager.getRedHatUUID(); ``` # Build -In a terminal, execute: + +In a terminal, execute: + ``` npm i ``` + to install the dependencies, then: + ``` npm run prepublish ``` + to build the library # Information on data transmission during development diff --git a/USAGE_DATA.md b/USAGE_DATA.md index f11cd4d..38185bc 100644 --- a/USAGE_DATA.md +++ b/USAGE_DATA.md @@ -1,41 +1,44 @@ ## Usage data being collected by Red Hat Extensions + Only anonymous data is being collected by Red Hat extensions using `vscode-redhat-telemetry` facilities. The IP address of telemetry requests is not even stored on Red Hat servers. All telemetry events are automatically sanitized to anonymize all paths (best effort) and references to the username. ### Common data + Telemetry requests may contain: -* a random anonymous user id (UUID v4), that is stored locally on `~/.redhat/anonymousId` -* the client name (VS Code, VSCodium, Eclipse Che...) and its version -* the type of client (Desktop vs Web) -* the name and version of the extension sending the event (eg. `fabric8-analytics.fabric8-analytics-vscode-extension`) -* whether the extension runs remotely or not (eg. in WSL) -* the OS name and version (and distribution name, in case of Linux) -* the user locale (eg. en_US) -* the user timezone -* the country id ( as determined by the current timezone) +- a random anonymous user id (UUID v4), that is stored locally on `~/.redhat/anonymousId` +- the client name (VS Code, VSCodium, Eclipse Che...) and its version +- the type of client (Desktop vs Web) +- the name and version of the extension sending the event (eg. `fabric8-analytics.fabric8-analytics-vscode-extension`) +- whether the extension runs remotely or not (eg. in WSL) +- the OS name and version (and distribution name, in case of Linux) +- the user locale (eg. en_US) +- the user timezone +- the country id ( as determined by the current timezone) Common events are reported: -* when extension is started -* when extension is shutdown - - duration of the session +- when extension is started +- when extension is shutdown + - duration of the session ### Other extensions + Red Hat extensions' specific telemetry collection details can be found there: -* [Dependency Analytics](https://github.com/fabric8-analytics/fabric8-analytics-vscode-extension/blob/master/Telemetry.md) -* [OpenShift Connector](https://github.com/redhat-developer/vscode-openshift-tools/blob/master/USAGE_DATA.md) -* [Project Initializer](https://github.com/redhat-developer/vscode-project-initializer/blob/master/USAGE_DATA.md) -* [Quarkus](https://github.com/redhat-developer/vscode-quarkus/blob/master/USAGE_DATA.md) -* [Red Hat Authentication](https://github.com/redhat-developer/vscode-redhat-account/blob/main/USAGE_DATA.md) -* [Red Hat OpenShift Application Services](https://github.com/redhat-developer/vscode-rhoas/blob/main/USAGE_DATA.md) -* [Remote Server Protocol](https://github.com/redhat-developer/vscode-rsp-ui/blob/master/USAGE_DATA.md) -* [Tekton Pipelines](https://github.com/redhat-developer/vscode-tekton/blob/master/USAGE_DATA.md) -* [Tooling for Apache Camel K](https://github.com/camel-tooling/vscode-camelk/blob/main/USAGE_DATA.md) -* [Language Support for Apache Camel](https://github.com/camel-tooling/camel-lsp-client-vscode/blob/main/USAGE_DATA.md) -* [Debug Adapter for Apache Camel](https://github.com/camel-tooling/camel-dap-client-vscode/blob/main/USAGE_DATA.md) -* [Tools for MicroProfile](https://github.com/redhat-developer/vscode-microprofile/blob/master/USAGE_DATA.md) -* [XML](https://github.com/redhat-developer/vscode-xml/blob/master/USAGE_DATA.md) -* [YAML](https://github.com/redhat-developer/vscode-yaml/blob/main/USAGE_DATA.md) -* [Ansible](https://github.com/ansible/vscode-ansible/blob/main/USAGE_DATA.md) +- [Dependency Analytics](https://github.com/fabric8-analytics/fabric8-analytics-vscode-extension/blob/master/Telemetry.md) +- [OpenShift Connector](https://github.com/redhat-developer/vscode-openshift-tools/blob/master/USAGE_DATA.md) +- [Project Initializer](https://github.com/redhat-developer/vscode-project-initializer/blob/master/USAGE_DATA.md) +- [Quarkus](https://github.com/redhat-developer/vscode-quarkus/blob/master/USAGE_DATA.md) +- [Red Hat Authentication](https://github.com/redhat-developer/vscode-redhat-account/blob/main/USAGE_DATA.md) +- [Red Hat OpenShift Application Services](https://github.com/redhat-developer/vscode-rhoas/blob/main/USAGE_DATA.md) +- [Remote Server Protocol](https://github.com/redhat-developer/vscode-rsp-ui/blob/master/USAGE_DATA.md) +- [Tekton Pipelines](https://github.com/redhat-developer/vscode-tekton/blob/master/USAGE_DATA.md) +- [Tooling for Apache Camel K](https://github.com/camel-tooling/vscode-camelk/blob/main/USAGE_DATA.md) +- [Language Support for Apache Camel](https://github.com/camel-tooling/camel-lsp-client-vscode/blob/main/USAGE_DATA.md) +- [Debug Adapter for Apache Camel](https://github.com/camel-tooling/camel-dap-client-vscode/blob/main/USAGE_DATA.md) +- [Tools for MicroProfile](https://github.com/redhat-developer/vscode-microprofile/blob/master/USAGE_DATA.md) +- [XML](https://github.com/redhat-developer/vscode-xml/blob/master/USAGE_DATA.md) +- [YAML](https://github.com/redhat-developer/vscode-yaml/blob/main/USAGE_DATA.md) +- [Ansible](https://github.com/ansible/vscode-ansible/blob/main/USAGE_DATA.md) diff --git a/package.json b/package.json index 780802a..25432d6 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "copy-files": "copyfiles -u 1 src/config/* src/tests/config/* lib/", "compile": "tsc -p .", "build": "npm run clean && npm run copy-files && npm run compile", + "lint": "python3 -m pre_commit run -a", + "prepare": "python3 -m pip install --user pre-commit", "prepublish": "npm run build", "coverage": "nyc -r lcov -e .ts -x \"*.ts\" npm run test", "test": "mocha -r ts-node/register --ui tdd \"src/tests/**/*.test.ts\"", diff --git a/src/che/cheIdManager.ts b/src/che/cheIdManager.ts index bd4a7da..573bfdd 100644 --- a/src/che/cheIdManager.ts +++ b/src/che/cheIdManager.ts @@ -1,39 +1,38 @@ -import { extensions } from 'vscode'; -import { IdManager } from '../interfaces/idManager'; -import { UUID } from '../utils/uuid'; +import { extensions } from "vscode"; +import { IdManager } from "../interfaces/idManager"; +import { UUID } from "../utils/uuid"; let userId: string; export class CheIdManager implements IdManager { - async getRedHatUUID(): Promise { - if (!userId) { - userId = await this.loadRedHatUUID(); - } - return userId; + async getRedHatUUID(): Promise { + if (!userId) { + userId = await this.loadRedHatUUID(); } + return userId; + } - async loadRedHatUUID(): Promise { - try { - console.log('Reading user id from @eclipse-che.ext-plugin'); - const che = extensions.getExtension('@eclipse-che.ext-plugin'); - if (che) { - console.log('Found Che API'); - // grab user - const user = await che.exports.user?.getCurrentUser(); - - if (user.id) { - console.log(`Found Che user id ${user.id}`); - return user.id; - } - console.log('No Che user id'); + async loadRedHatUUID(): Promise { + try { + console.log("Reading user id from @eclipse-che.ext-plugin"); + const che = extensions.getExtension("@eclipse-che.ext-plugin"); + if (che) { + console.log("Found Che API"); + // grab user + const user = await che.exports.user?.getCurrentUser(); - } else { - console.log('No @eclipse-che.ext-plugin'); - } - } catch (error) { - console.log('Failed to get user id from Che', error); + if (user.id) { + console.log(`Found Che user id ${user.id}`); + return user.id; } - //fall back to generating a random UUID - console.log('fall back to generating a random UUID'); - return UUID.getRedHatUUID(); + console.log("No Che user id"); + } else { + console.log("No @eclipse-che.ext-plugin"); + } + } catch (error) { + console.log("Failed to get user id from Che", error); } -} \ No newline at end of file + //fall back to generating a random UUID + console.log("fall back to generating a random UUID"); + return UUID.getRedHatUUID(); + } +} diff --git a/src/config/telemetry-config.json b/src/config/telemetry-config.json index 2b7c0d2..69d44b5 100644 --- a/src/config/telemetry-config.json +++ b/src/config/telemetry-config.json @@ -1,11 +1,11 @@ { - "*": { - "enabled":"all", - "refresh": "12h", - "includes": [ - { - "name" : "*" - } - ] - } -} \ No newline at end of file + "*": { + "enabled": "all", + "refresh": "12h", + "includes": [ + { + "name": "*" + } + ] + } +} diff --git a/src/gitpod/gitpodIdManager.ts b/src/gitpod/gitpodIdManager.ts index 1d4622d..a70f363 100644 --- a/src/gitpod/gitpodIdManager.ts +++ b/src/gitpod/gitpodIdManager.ts @@ -5,31 +5,30 @@ import { UUID } from "../utils/uuid"; let userId: string; export class GitpodIdManager implements IdManager { - async getRedHatUUID(): Promise { - if (!userId) { - userId = this.loadRedHatUUID(); - } - return userId; + async getRedHatUUID(): Promise { + if (!userId) { + userId = this.loadRedHatUUID(); } + return userId; + } - loadRedHatUUID(redhatDir?: string): string { - try { - const email = env.GITPOD_GIT_USER_EMAIL; - if (email) { - userId = UUID.generateUUID(email); - const anonymousIdFile = UUID.getAnonymousIdFile(redhatDir); - const existingId = UUID.readFile(anonymousIdFile); - if (existingId !== userId) { - UUID.writeFile(anonymousIdFile, userId); - } - return userId; - } - } catch (error) { - console.log('Failed to get user id from Gitpod', error); + loadRedHatUUID(redhatDir?: string): string { + try { + const email = env.GITPOD_GIT_USER_EMAIL; + if (email) { + userId = UUID.generateUUID(email); + const anonymousIdFile = UUID.getAnonymousIdFile(redhatDir); + const existingId = UUID.readFile(anonymousIdFile); + if (existingId !== userId) { + UUID.writeFile(anonymousIdFile, userId); } - //fall back to generating a random UUID - console.log('fall back to generating a random UUID'); - return UUID.getRedHatUUID(); + return userId; + } + } catch (error) { + console.log("Failed to get user id from Gitpod", error); } - -} \ No newline at end of file + //fall back to generating a random UUID + console.log("fall back to generating a random UUID"); + return UUID.getRedHatUUID(); + } +} diff --git a/src/index.ts b/src/index.ts index 7e19605..657af9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,16 @@ -import { getRedHatService} from "./vscode/redhatService"; +import { getRedHatService } from "./vscode/redhatService"; import { TelemetryEvent, TelemetryService } from "./interfaces/telemetry"; import { IdManager } from "./interfaces/idManager"; import { Environment } from "./interfaces/environment"; import { TelemetryServiceBuilder } from "./services/telemetryServiceBuilder"; import { TelemetrySettings } from "./interfaces/settings"; -export { getRedHatService, TelemetryEvent, TelemetryService, TelemetrySettings, TelemetryServiceBuilder, IdManager, Environment }; +export { + getRedHatService, + TelemetryEvent, + TelemetryService, + TelemetrySettings, + TelemetryServiceBuilder, + IdManager, + Environment, +}; diff --git a/src/interfaces/cacheService.ts b/src/interfaces/cacheService.ts index 8e64980..a3cce9f 100644 --- a/src/interfaces/cacheService.ts +++ b/src/interfaces/cacheService.ts @@ -1,11 +1,11 @@ /** - * Cache Service + * Cache Service */ - export interface CacheService { - /** - * Returns the value in cache for the given key. - */ - get(key: string):Promise; +export interface CacheService { + /** + * Returns the value in cache for the given key. + */ + get(key: string): Promise; - put(key: string, value: string):Promise; -} \ No newline at end of file + put(key: string, value: string): Promise; +} diff --git a/src/interfaces/envVar.ts b/src/interfaces/envVar.ts index d65a607..6116d61 100644 --- a/src/interfaces/envVar.ts +++ b/src/interfaces/envVar.ts @@ -1,6 +1,6 @@ export interface Dict { [key: string]: T | undefined; } -let env = (typeof process !== 'undefined')?process.env : {} as Dict; +let env = typeof process !== "undefined" ? process.env : ({} as Dict); -export default env; \ No newline at end of file +export default env; diff --git a/src/interfaces/environment.ts b/src/interfaces/environment.ts index ea15dd5..60ba186 100644 --- a/src/interfaces/environment.ts +++ b/src/interfaces/environment.ts @@ -2,71 +2,71 @@ * Container object holding environment specific data, used to enrich telemetry events. */ export interface Environment { - /** - * The extension from which Telemetry events are sent. - */ - extension: Application, + /** + * The extension from which Telemetry events are sent. + */ + extension: Application; - /** - * The client application from which Telemetry events are sent . - */ - application: Client, + /** + * The client application from which Telemetry events are sent . + */ + application: Client; - /** - * The platform (or OS) from from which Telemetry events are sent. - */ - platform: Platform, - /** - * User timezone, eg. 'Europe/Paris' - */ - timezone?: string, + /** + * The platform (or OS) from from which Telemetry events are sent. + */ + platform: Platform; + /** + * User timezone, eg. 'Europe/Paris' + */ + timezone?: string; - /** - * The user locale, eg. 'en-US' - */ - locale?: string, + /** + * The user locale, eg. 'en-US' + */ + locale?: string; - /** - * The user's ISO country code, eg. 'CA' for Canada - */ - country?: string, + /** + * The user's ISO country code, eg. 'CA' for Canada + */ + country?: string; - /** - * Username (used as basis for stripping PII from data) - */ - username?: string + /** + * Username (used as basis for stripping PII from data) + */ + username?: string; } /** * The client application or extension from which Telemetry events are sent. */ export interface Client extends Application { - /** - * UI Kind (Web / ) - */ - uiKind?: string, - /** - * Runs remotely (eg. in wsl)? - */ - remote?: boolean + /** + * UI Kind (Web / ) + */ + uiKind?: string; + /** + * Runs remotely (eg. in wsl)? + */ + remote?: boolean; } export interface Application { - /** - * Client name - */ - name: string, - /** - * Client version - */ - version: string + /** + * Client name + */ + name: string; + /** + * Client version + */ + version: string; } /** * The platform (or OS) from which Telemetry events are sent. */ export interface Platform { - name: string, - distribution?: string, - version?: string -} \ No newline at end of file + name: string; + distribution?: string; + version?: string; +} diff --git a/src/interfaces/idManager.ts b/src/interfaces/idManager.ts index ac04faf..5a722e8 100644 --- a/src/interfaces/idManager.ts +++ b/src/interfaces/idManager.ts @@ -1,9 +1,9 @@ /** - * Service providing the Red Hat anonymous user id. + * Service providing the Red Hat anonymous user id. */ export interface IdManager { - /** - * Returns the Red Hat' anonymous user id. - */ - getRedHatUUID():Promise; -} \ No newline at end of file + /** + * Returns the Red Hat' anonymous user id. + */ + getRedHatUUID(): Promise; +} diff --git a/src/interfaces/redhatService.ts b/src/interfaces/redhatService.ts index 3cc5cb0..0db56f1 100644 --- a/src/interfaces/redhatService.ts +++ b/src/interfaces/redhatService.ts @@ -3,13 +3,13 @@ import { IdManager, TelemetryService } from ".."; /** * Umbrella for Red Hat services. */ - export interface RedHatService { - /** - * Returns a Telemetry service - */ - getTelemetryService():Promise; - /** - * Returns the Red Hat Id manager - */ - getIdManager():Promise; -} \ No newline at end of file +export interface RedHatService { + /** + * Returns a Telemetry service + */ + getTelemetryService(): Promise; + /** + * Returns the Red Hat Id manager + */ + getIdManager(): Promise; +} diff --git a/src/interfaces/settings.ts b/src/interfaces/settings.ts index 515cacb..5639ac8 100644 --- a/src/interfaces/settings.ts +++ b/src/interfaces/settings.ts @@ -5,7 +5,7 @@ export interface TelemetrySettings { /** * Returns `true` if Telemetry is enabled. */ - isTelemetryEnabled() : boolean; + isTelemetryEnabled(): boolean; /** * Returns `true` if Telemetry is configured (enabled or not). */ @@ -15,4 +15,4 @@ export interface TelemetrySettings { * Returns the telemetry level: value can be either "off", "all", "error" or "crash" */ getTelemetryLevel(): string | undefined; -} \ No newline at end of file +} diff --git a/src/interfaces/telemetry.ts b/src/interfaces/telemetry.ts index 1738e13..6645775 100644 --- a/src/interfaces/telemetry.ts +++ b/src/interfaces/telemetry.ts @@ -2,40 +2,40 @@ * Telemetry Event */ export interface TelemetryEvent { - type?: string; // type of telemetry event such as : identify, track, page, etc. - name: string; - properties?: any; - measures?: any; - traits?: any; - context?: any; + type?: string; // type of telemetry event such as : identify, track, page, etc. + name: string; + properties?: any; + measures?: any; + traits?: any; + context?: any; } /** * Service for sending Telemetry events */ export interface TelemetryService { - /** - * Sends a `startup` Telemetry event - */ - sendStartupEvent(): Promise; - - /** - * Sends the Telemetry event - */ - send(event: TelemetryEvent): Promise; - - /** - * Sends a `shutdown` Telemetry event - */ - sendShutdownEvent(): Promise; + /** + * Sends a `startup` Telemetry event + */ + sendStartupEvent(): Promise; - /** - * Flushes the service's Telemetry events queue - */ - flushQueue(): Promise; + /** + * Sends the Telemetry event + */ + send(event: TelemetryEvent): Promise; - /** - * Dispose this service - */ - dispose(): Promise; + /** + * Sends a `shutdown` Telemetry event + */ + sendShutdownEvent(): Promise; + + /** + * Flushes the service's Telemetry events queue + */ + flushQueue(): Promise; + + /** + * Dispose this service + */ + dispose(): Promise; } diff --git a/src/node/platform.ts b/src/node/platform.ts index 49e6389..74c1479 100644 --- a/src/node/platform.ts +++ b/src/node/platform.ts @@ -1,91 +1,90 @@ -import os from 'os'; -import osLocale from 'os-locale'; -import getos from 'getos'; -import { LinuxOs } from 'getos'; -import { Environment } from '..'; -import { env as vscodeEnv , UIKind, version} from 'vscode'; -import { promisify } from 'util'; +import os from "os"; +import osLocale from "os-locale"; +import getos from "getos"; +import { LinuxOs } from "getos"; +import { Environment } from ".."; +import { env as vscodeEnv, UIKind, version } from "vscode"; +import { promisify } from "util"; -import { getCountry } from '../utils/geolocation'; -import env from '../interfaces/envVar'; +import { getCountry } from "../utils/geolocation"; +import env from "../interfaces/envVar"; export const PLATFORM = getPlatform(); export const DISTRO = getDistribution(); export const PLATFORM_VERSION = os.release(); export const TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone; -export const LOCALE = osLocale.sync().replace('_', '-'); +export const LOCALE = osLocale.sync().replace("_", "-"); export const COUNTRY = getCountry(TIMEZONE); export const UI_KIND = getUIKind(); export const USERNAME = getUsername(); - function getPlatform(): string { - const platform: string = os.platform(); - if (platform.startsWith('win')) { - return 'Windows'; - } - if (platform.startsWith('darwin')) { - return 'Mac'; - } - return platform.charAt(0).toUpperCase() + platform.slice(1); + const platform: string = os.platform(); + if (platform.startsWith("win")) { + return "Windows"; + } + if (platform.startsWith("darwin")) { + return "Mac"; + } + return platform.charAt(0).toUpperCase() + platform.slice(1); } -async function getDistribution(): Promise { - if (os.platform() === 'linux') { - const platform = await promisify(getos)() as LinuxOs; - return platform.dist; - } - return undefined; +async function getDistribution(): Promise { + if (os.platform() === "linux") { + const platform = (await promisify(getos)()) as LinuxOs; + return platform.dist; + } + return undefined; } -export async function getEnvironment(extensionId: string, extensionVersion:string): Promise { - return { - extension: { - name:extensionId, - version:extensionVersion, - }, - application: { - name: vscodeEnv.appName, - version: version, - uiKind: UI_KIND, - remote: vscodeEnv.remoteName !== undefined - }, - platform:{ - name:PLATFORM, - version:PLATFORM_VERSION, - distribution: await DISTRO - }, - timezone:TIMEZONE, - locale:LOCALE, - country: COUNTRY, - username: USERNAME - }; +export async function getEnvironment( + extensionId: string, + extensionVersion: string, +): Promise { + return { + extension: { + name: extensionId, + version: extensionVersion, + }, + application: { + name: vscodeEnv.appName, + version: version, + uiKind: UI_KIND, + remote: vscodeEnv.remoteName !== undefined, + }, + platform: { + name: PLATFORM, + version: PLATFORM_VERSION, + distribution: await DISTRO, + }, + timezone: TIMEZONE, + locale: LOCALE, + country: COUNTRY, + username: USERNAME, + }; } -function getUIKind():string { - switch (vscodeEnv.uiKind) { - case UIKind.Desktop: - return 'Desktop'; - case UIKind.Web: - return 'Web'; - default: - return 'Unknown'; - } +function getUIKind(): string { + switch (vscodeEnv.uiKind) { + case UIKind.Desktop: + return "Desktop"; + case UIKind.Web: + return "Web"; + default: + return "Unknown"; + } } function getUsername(): string | undefined { - - let username = ( - env.SUDO_USER || - env.C9_USER /* Cloud9 */ || - env.LOGNAME || - env.USER || - env.LNAME || - env.USERNAME - ); - if (!username) { - try { - username = os.userInfo().username; - } catch (_) {} - } - return username; + let username = + env.SUDO_USER || + env.C9_USER /* Cloud9 */ || + env.LOGNAME || + env.USER || + env.LNAME || + env.USERNAME; + if (!username) { + try { + username = os.userInfo().username; + } catch (_) {} + } + return username; } - diff --git a/src/services/AnalyticsEvent.ts b/src/services/AnalyticsEvent.ts index 47f40e7..02b7eeb 100644 --- a/src/services/AnalyticsEvent.ts +++ b/src/services/AnalyticsEvent.ts @@ -1,10 +1,10 @@ import { TelemetryEvent } from "../interfaces/telemetry"; export interface AnalyticsEvent { - userId: string; - event: string; - properties?: any; - measures?: any; - traits?: any; - context?: any; -} \ No newline at end of file + userId: string; + event: string; + properties?: any; + measures?: any; + traits?: any; + context?: any; +} diff --git a/src/services/FileSystemStorageService.ts b/src/services/FileSystemStorageService.ts index c6c84ab..b6d51f9 100644 --- a/src/services/FileSystemStorageService.ts +++ b/src/services/FileSystemStorageService.ts @@ -1,44 +1,44 @@ -import * as fs from 'fs'; +import * as fs from "fs"; import path from "path"; -export class FileSystemStorageService { //implements StorageService { - - private storagePath: string; +export class FileSystemStorageService { + //implements StorageService { - constructor(storagePath: string) { - this.storagePath = storagePath; - if (!fs.existsSync(storagePath)) { - fs.mkdirSync(storagePath, { recursive: true }); - } - } + private storagePath: string; - public readFromFile(fileName: string): Promise { - const filePath = path.resolve(this.storagePath, fileName); - return new Promise((resolve, reject) => { - if (!fs.existsSync(filePath)) { - resolve(undefined); - return; - } - fs.readFile(filePath, 'utf8', (err, data) => { - if (err) { - resolve(undefined); - return; - } - resolve(data); - }); - }); + constructor(storagePath: string) { + this.storagePath = storagePath; + if (!fs.existsSync(storagePath)) { + fs.mkdirSync(storagePath, { recursive: true }); } + } - public writeToFile(filename: string, content: string): Promise { - const filePath = path.resolve(this.storagePath, filename); - return new Promise((resolve, _reject) => { - fs.writeFile(filePath, content, (err) => { - if (err) { - resolve(false); - } - resolve(true); - }); - }); - } + public readFromFile(fileName: string): Promise { + const filePath = path.resolve(this.storagePath, fileName); + return new Promise((resolve, reject) => { + if (!fs.existsSync(filePath)) { + resolve(undefined); + return; + } + fs.readFile(filePath, "utf8", (err, data) => { + if (err) { + resolve(undefined); + return; + } + resolve(data); + }); + }); + } -} \ No newline at end of file + public writeToFile(filename: string, content: string): Promise { + const filePath = path.resolve(this.storagePath, filename); + return new Promise((resolve, _reject) => { + fs.writeFile(filePath, content, (err) => { + if (err) { + resolve(false); + } + resolve(true); + }); + }); + } +} diff --git a/src/services/configuration.ts b/src/services/configuration.ts index 994526e..273bf32 100644 --- a/src/services/configuration.ts +++ b/src/services/configuration.ts @@ -4,121 +4,124 @@ import { numValue } from "../utils/hashcode"; import { AnalyticsEvent } from "./AnalyticsEvent"; interface EventNamePattern { - name: string; + name: string; } interface PropertyPattern { - property: string; - value: string; + property: string; + value: string; } type EventPattern = EventNamePattern | PropertyPattern; export class Configuration { + json: any; - json: any; + constructor(json: any) { + this.json = json; + } - constructor(json: any) { - this.json = json; - } - - public isEnabled(): boolean { - return (this.json?.enabled) === undefined || "off" !== (this.json?.enabled) - } - - public canSend(event: AnalyticsEvent): boolean { - if (!this.isEnabled()) { - return false; - } - if ( ["error","crash"].includes(this.json?.enabled) && !isError(event)) { - return false; - } - - const ratio = this.getRatio(); - if (ratio < 1.0) { - const userNumValue = numValue(event.userId); - if (userNumValue > ratio) { - return false; - } - } + public isEnabled(): boolean { + return this.json?.enabled === undefined || "off" !== this.json?.enabled; + } - const isIncluded = this.isIncluded(event) && !this.isExcluded(event); - return isIncluded; + public canSend(event: AnalyticsEvent): boolean { + if (!this.isEnabled()) { + return false; } - - isIncluded(event: AnalyticsEvent): boolean { - const includes = this.getIncludePatterns(); - if (includes.length) { - return isEventMatching(event, includes); - } - return true; + if (["error", "crash"].includes(this.json?.enabled) && !isError(event)) { + return false; } - isExcluded(event: AnalyticsEvent): boolean { - const excludes = this.getExcludePatterns(); - if (excludes.length) { - return isEventMatching(event, excludes); - } + const ratio = this.getRatio(); + if (ratio < 1.0) { + const userNumValue = numValue(event.userId); + if (userNumValue > ratio) { return false; + } } - getIncludePatterns(): EventPattern[] { - if (this.json?.includes) { - return this.json.includes as EventPattern[]; - } - return []; - } + const isIncluded = this.isIncluded(event) && !this.isExcluded(event); + return isIncluded; + } - getExcludePatterns(): EventPattern[] { - if (this.json.excludes) { - return this.json.excludes as EventPattern[]; - } - return []; + isIncluded(event: AnalyticsEvent): boolean { + const includes = this.getIncludePatterns(); + if (includes.length) { + return isEventMatching(event, includes); } + return true; + } - getRatio(): number { - if (this.json.ratio) { - try { - return parseFloat(this.json.ratio); + isExcluded(event: AnalyticsEvent): boolean { + const excludes = this.getExcludePatterns(); + if (excludes.length) { + return isEventMatching(event, excludes); + } + return false; + } - } catch(e) { - // ignore - } - } - return 1.0; + getIncludePatterns(): EventPattern[] { + if (this.json?.includes) { + return this.json.includes as EventPattern[]; } + return []; + } + getExcludePatterns(): EventPattern[] { + if (this.json.excludes) { + return this.json.excludes as EventPattern[]; + } + return []; + } + + getRatio(): number { + if (this.json.ratio) { + try { + return parseFloat(this.json.ratio); + } catch (e) { + // ignore + } + } + return 1.0; + } } -function isEventMatching(event: AnalyticsEvent, patterns:EventPattern[]):boolean { - if (!patterns || !patterns.length) { - return false; - } - const match = patterns.find(evtPtn => { - if (isPropertyPattern(evtPtn)) { - const props = event.properties; - if (props) { - const value = props[evtPtn.property]; - const propertyPattern = evtPtn.value; - if (value && minimatch(value, propertyPattern)) { - return true; - } - } - } else { - const eventNamePattern = evtPtn.name; - if (eventNamePattern && event.event && minimatch(event.event, eventNamePattern)) { - return true; - } +function isEventMatching( + event: AnalyticsEvent, + patterns: EventPattern[], +): boolean { + if (!patterns || !patterns.length) { + return false; + } + const match = patterns.find((evtPtn) => { + if (isPropertyPattern(evtPtn)) { + const props = event.properties; + if (props) { + const value = props[evtPtn.property]; + const propertyPattern = evtPtn.value; + if (value && minimatch(value, propertyPattern)) { + return true; } - return false; - }); - return !!match; + } + } else { + const eventNamePattern = evtPtn.name; + if ( + eventNamePattern && + event.event && + minimatch(event.event, eventNamePattern) + ) { + return true; + } + } + return false; + }); + return !!match; } - function isPropertyPattern(event: EventPattern): event is PropertyPattern { - if ((event as PropertyPattern).property) { - return true - } - return false -} \ No newline at end of file + if ((event as PropertyPattern).property) { + return true; + } + return false; +} diff --git a/src/services/configurationManager.ts b/src/services/configurationManager.ts index 92874b8..f2c2c8f 100644 --- a/src/services/configurationManager.ts +++ b/src/services/configurationManager.ts @@ -1,131 +1,143 @@ import { Configuration } from "./configuration"; -import axios from 'axios'; +import axios from "axios"; import { FileSystemStorageService } from "./FileSystemStorageService"; import env from "../interfaces/envVar"; -export const DEFAULT_CONFIG_URL = 'https://raw.githubusercontent.com/redhat-developer/vscode-redhat-telemetry/main/src/config/telemetry-config.json'; +export const DEFAULT_CONFIG_URL = + "https://raw.githubusercontent.com/redhat-developer/vscode-redhat-telemetry/main/src/config/telemetry-config.json"; export const TELEMETRY_CONFIG = "telemetry-config.json"; export class ConfigurationManager { - public static REMOTE_CONFIG_KEY = 'REDHAT_TELEMETRY_REMOTE_CONFIG_URL'; - public static TEST_CONFIG_KEY = 'REDHAT_TELEMETRY_TEST_CONFIG_KEY'; + public static REMOTE_CONFIG_KEY = "REDHAT_TELEMETRY_REMOTE_CONFIG_URL"; + public static TEST_CONFIG_KEY = "REDHAT_TELEMETRY_TEST_CONFIG_KEY"; - constructor(private extensionId: string, private storageService: FileSystemStorageService){} + constructor( + private extensionId: string, + private storageService: FileSystemStorageService, + ) {} - private extensionConfig: Promise|undefined; + private extensionConfig: Promise | undefined; - public async refresh(): Promise { - const remoteConfig = await this.fetchRemoteConfiguration(); - if (remoteConfig) { - remoteConfig['timestamp'] = new Date().getTime(); - await this.saveLocalConfiguration(remoteConfig); - } - return remoteConfig; + public async refresh(): Promise { + const remoteConfig = await this.fetchRemoteConfiguration(); + if (remoteConfig) { + remoteConfig["timestamp"] = new Date().getTime(); + await this.saveLocalConfiguration(remoteConfig); } + return remoteConfig; + } - public async getExtensionConfiguration(): Promise { - let extensionConfig = this.extensionConfig; - if (extensionConfig) { - if (!isStale(await extensionConfig)) { - return extensionConfig; - } - this.extensionConfig = undefined; - } - console.log("Loading json config for "+this.extensionId); - this.extensionConfig = this.loadConfiguration(this.extensionId); - return this.extensionConfig; + public async getExtensionConfiguration(): Promise { + let extensionConfig = this.extensionConfig; + if (extensionConfig) { + if (!isStale(await extensionConfig)) { + return extensionConfig; + } + this.extensionConfig = undefined; } + console.log("Loading json config for " + this.extensionId); + this.extensionConfig = this.loadConfiguration(this.extensionId); + return this.extensionConfig; + } - private async loadConfiguration(extensionId: string): Promise { - let localConfig: any; - try { - localConfig = await this.getLocalConfiguration(); - if (isStale(localConfig)) { - localConfig = await this.refresh(); - } - } catch(e: any) { - console.error(`Failed to load local configuration: ${e?.message}`); - } - let fullConfig:any; - if (localConfig) { - fullConfig = localConfig; - } else { - fullConfig = await this.getEmbeddedConfiguration(); - } - const json = getExtensionConfig(fullConfig, extensionId); - return new Configuration(json); + private async loadConfiguration(extensionId: string): Promise { + let localConfig: any; + try { + localConfig = await this.getLocalConfiguration(); + if (isStale(localConfig)) { + localConfig = await this.refresh(); + } + } catch (e: any) { + console.error(`Failed to load local configuration: ${e?.message}`); } - async saveLocalConfiguration(fullConfig: any): Promise { - try { - return this.storageService.writeToFile(TELEMETRY_CONFIG, JSON.stringify(fullConfig, null, 2)); - } catch (e) { - console.error(`Error saving configuration locally: ${e}`); - } - return false; + let fullConfig: any; + if (localConfig) { + fullConfig = localConfig; + } else { + fullConfig = await this.getEmbeddedConfiguration(); } - - public async fetchRemoteConfiguration(uri?: string): Promise { - let telemetryUri = ( uri )? uri: env[ConfigurationManager.REMOTE_CONFIG_KEY]; - if (!telemetryUri) { - telemetryUri = DEFAULT_CONFIG_URL; - } - console.log(`Updating vscode-redhat-telemetry configuration from ${telemetryUri}`); - const response = await axios.get(telemetryUri); - try { - return response?.data; - } catch (e) { - console.error(`Failed to parse:\n`+response?.data+'\n'+e); - } - return undefined; + const json = getExtensionConfig(fullConfig, extensionId); + return new Configuration(json); + } + async saveLocalConfiguration(fullConfig: any): Promise { + try { + return this.storageService.writeToFile( + TELEMETRY_CONFIG, + JSON.stringify(fullConfig, null, 2), + ); + } catch (e) { + console.error(`Error saving configuration locally: ${e}`); } + return false; + } - public async getLocalConfiguration(): Promise { - const content = await this.storageService.readFromFile(TELEMETRY_CONFIG); - if (content) { - return JSON.parse(content); - } - return undefined; + public async fetchRemoteConfiguration(uri?: string): Promise { + let telemetryUri = uri ? uri : env[ConfigurationManager.REMOTE_CONFIG_KEY]; + if (!telemetryUri) { + telemetryUri = DEFAULT_CONFIG_URL; + } + console.log( + `Updating vscode-redhat-telemetry configuration from ${telemetryUri}`, + ); + const response = await axios.get(telemetryUri); + try { + return response?.data; + } catch (e) { + console.error(`Failed to parse:\n` + response?.data + "\n" + e); } + return undefined; + } - public async getEmbeddedConfiguration(): Promise { - const testConfig = env[ConfigurationManager.TEST_CONFIG_KEY]; - if (testConfig) { - return require('../tests/config/telemetry-config.json');// hard-coded to keep webpack happy - } - return require('../config/telemetry-config.json'); + public async getLocalConfiguration(): Promise { + const content = await this.storageService.readFromFile(TELEMETRY_CONFIG); + if (content) { + return JSON.parse(content); } + return undefined; + } + public async getEmbeddedConfiguration(): Promise { + const testConfig = env[ConfigurationManager.TEST_CONFIG_KEY]; + if (testConfig) { + return require("../tests/config/telemetry-config.json"); // hard-coded to keep webpack happy + } + return require("../config/telemetry-config.json"); + } } const refreshPattern = /\d+/g; const REFRESH_PERIOD = 6; const HOUR_IN_MILLISEC = 60 * 60 * 1000; function isStale(configOrJson: any): boolean { - if (!configOrJson) { - return true; + if (!configOrJson) { + return true; + } + let config: any; + if (configOrJson instanceof Configuration) { + config = configOrJson.json; + } else { + config = configOrJson; + } + const timestamp = config.timestamp ? config.timestamp : 0; + let period = REFRESH_PERIOD; + if (config.refresh) { + const res = (config.refresh as string).match(refreshPattern); + if (res && res.length) { + period = parseInt(res[0]); } - let config: any; - if (configOrJson instanceof Configuration) { - config = configOrJson.json; - } else { - config = configOrJson; - } - const timestamp = config.timestamp? config.timestamp : 0; - let period = REFRESH_PERIOD; - if (config.refresh) { - const res = (config.refresh as string).match(refreshPattern); - if (res && res.length) { - period = parseInt(res[0]); - } - } - let elapsed = new Date().getTime() - timestamp; - return (elapsed > period * HOUR_IN_MILLISEC); + } + let elapsed = new Date().getTime() - timestamp; + return elapsed > period * HOUR_IN_MILLISEC; } function getExtensionConfig(fullConfig: any, extensionId: string) { - const extensionConfig = Object.assign({}, fullConfig['*'], fullConfig[extensionId]); - if (fullConfig.timestamp) { - extensionConfig['timestamp'] = fullConfig.timestamp; - } - return extensionConfig; -} \ No newline at end of file + const extensionConfig = Object.assign( + {}, + fullConfig["*"], + fullConfig[extensionId], + ); + if (fullConfig.timestamp) { + extensionConfig["timestamp"] = fullConfig.timestamp; + } + return extensionConfig; +} diff --git a/src/services/fileSystemCacheService.ts b/src/services/fileSystemCacheService.ts index 599d98e..f3ac910 100644 --- a/src/services/fileSystemCacheService.ts +++ b/src/services/fileSystemCacheService.ts @@ -1,29 +1,30 @@ import { CacheService } from "../interfaces/cacheService"; import { FileSystemStorageService } from "./FileSystemStorageService"; -export class FileSystemCacheService extends FileSystemStorageService implements CacheService { - - private memCache = new Map(); +export class FileSystemCacheService + extends FileSystemStorageService + implements CacheService +{ + private memCache = new Map(); - constructor(storagePath: string) { - super(storagePath); - } + constructor(storagePath: string) { + super(storagePath); + } - async get(key: string): Promise { - if (this.memCache.has(key)) { - return Promise.resolve(this.memCache.get(key)); - } - const value = await this.readFromFile(`${key}.txt`); - if (value) { - this.memCache.set(key, value); - } - return value; + async get(key: string): Promise { + if (this.memCache.has(key)) { + return Promise.resolve(this.memCache.get(key)); } - - async put(key: string, value: string): Promise { - this.memCache.set(key, value); - await this.writeToFile(`${key}.txt`, value); - return true; + const value = await this.readFromFile(`${key}.txt`); + if (value) { + this.memCache.set(key, value); } + return value; + } -} \ No newline at end of file + async put(key: string, value: string): Promise { + this.memCache.set(key, value); + await this.writeToFile(`${key}.txt`, value); + return true; + } +} diff --git a/src/services/fileSystemIdManager.ts b/src/services/fileSystemIdManager.ts index b149082..3c2386e 100644 --- a/src/services/fileSystemIdManager.ts +++ b/src/services/fileSystemIdManager.ts @@ -2,10 +2,10 @@ import { IdManager } from "../interfaces/idManager"; import { UUID } from "../utils/uuid"; /** - * Service providing the Red Hat anonymous user id, read/stored from the `~/.redhat/anonymousId` file. + * Service providing the Red Hat anonymous user id, read/stored from the `~/.redhat/anonymousId` file. */ export class FileSystemIdManager implements IdManager { - async getRedHatUUID(): Promise { - return UUID.getRedHatUUID(); - } -} \ No newline at end of file + async getRedHatUUID(): Promise { + return UUID.getRedHatUUID(); + } +} diff --git a/src/services/idManagerFactory.ts b/src/services/idManagerFactory.ts index f42c512..1118912 100644 --- a/src/services/idManagerFactory.ts +++ b/src/services/idManagerFactory.ts @@ -1,18 +1,16 @@ -import { IdManager } from '../interfaces/idManager'; -import { CheIdManager } from '../che/cheIdManager'; -import { FileSystemIdManager } from './fileSystemIdManager'; -import { GitpodIdManager } from '../gitpod/gitpodIdManager'; -import env from '../interfaces/envVar'; +import { IdManager } from "../interfaces/idManager"; +import { CheIdManager } from "../che/cheIdManager"; +import { FileSystemIdManager } from "./fileSystemIdManager"; +import { GitpodIdManager } from "../gitpod/gitpodIdManager"; +import env from "../interfaces/envVar"; export namespace IdManagerFactory { - - export function getIdManager(): IdManager { - if (env['CHE_WORKSPACE_ID']) { - return new CheIdManager(); - } else if (env['GITPOD_GIT_USER_EMAIL']) { - return new GitpodIdManager(); - } - return new FileSystemIdManager(); + export function getIdManager(): IdManager { + if (env["CHE_WORKSPACE_ID"]) { + return new CheIdManager(); + } else if (env["GITPOD_GIT_USER_EMAIL"]) { + return new GitpodIdManager(); } - -} \ No newline at end of file + return new FileSystemIdManager(); + } +} diff --git a/src/services/reporter.ts b/src/services/reporter.ts index fb6f8a7..eb4d51d 100644 --- a/src/services/reporter.ts +++ b/src/services/reporter.ts @@ -1,48 +1,55 @@ -import Analytics from 'analytics-node'; -import { sha1 } from 'object-hash'; -import { CacheService } from '../interfaces/cacheService'; -import { Logger } from '../utils/logger'; -import { AnalyticsEvent } from './AnalyticsEvent'; +import Analytics from "analytics-node"; +import { sha1 } from "object-hash"; +import { CacheService } from "../interfaces/cacheService"; +import { Logger } from "../utils/logger"; +import { AnalyticsEvent } from "./AnalyticsEvent"; /** * Sends Telemetry events to a segment.io backend */ export class Reporter { + constructor( + private analytics?: Analytics, + private cacheService?: CacheService, + ) {} - constructor(private analytics?: Analytics, private cacheService?: CacheService) { - } - - public async report(event: AnalyticsEvent, type: string = 'track'): Promise { + public async report( + event: AnalyticsEvent, + type: string = "track", + ): Promise { if (!this.analytics) { return; } const payloadString = JSON.stringify(event); switch (type) { - case 'identify': + case "identify": //Avoid identifying the user several times, until some data has changed. const hash = sha1(payloadString); - const cached = await this.cacheService?.get('identify'); + const cached = await this.cacheService?.get("identify"); if (hash === cached) { - Logger.log(`Skipping 'identify' event! Already sent:\n${payloadString}`); + Logger.log( + `Skipping 'identify' event! Already sent:\n${payloadString}`, + ); return; } Logger.log(`Sending 'identify' event with\n${payloadString}`); this.analytics?.identify(event); - this.cacheService?.put('identify', hash); + this.cacheService?.put("identify", hash); break; - case 'track': + case "track": Logger.log(`Sending 'track' event with\n${payloadString}`); this.analytics?.track(event); break; - case 'page': + case "page": Logger.log(`Sending 'page' event with\n${payloadString}`); this.analytics?.page(event); break; default: - Logger.log(`Skipping unsupported (yet?) '${type}' event with\n${payloadString}`); + Logger.log( + `Skipping unsupported (yet?) '${type}' event with\n${payloadString}`, + ); break; } - } public async flush(): Promise { diff --git a/src/services/telemetryServiceBuilder.ts b/src/services/telemetryServiceBuilder.ts index 1dae45e..5d7345c 100644 --- a/src/services/telemetryServiceBuilder.ts +++ b/src/services/telemetryServiceBuilder.ts @@ -1,96 +1,105 @@ -import { Environment } from '../interfaces/environment'; -import { IdManager } from '../interfaces/idManager'; -import { Reporter } from './reporter'; -import { TelemetryService } from '../interfaces/telemetry'; -import { TelemetryServiceImpl } from './telemetryServiceImpl'; -import { TelemetryEventQueue } from '../utils/telemetryEventQueue'; -import { TelemetrySettings } from '../interfaces/settings'; -import { SegmentInitializer } from '../utils/segmentInitializer'; -import { FileSystemIdManager } from './fileSystemIdManager'; -import { getExtensionId } from '../utils/extensions'; -import { CacheService } from '../interfaces/cacheService'; -import { ConfigurationManager } from './configurationManager'; +import { Environment } from "../interfaces/environment"; +import { IdManager } from "../interfaces/idManager"; +import { Reporter } from "./reporter"; +import { TelemetryService } from "../interfaces/telemetry"; +import { TelemetryServiceImpl } from "./telemetryServiceImpl"; +import { TelemetryEventQueue } from "../utils/telemetryEventQueue"; +import { TelemetrySettings } from "../interfaces/settings"; +import { SegmentInitializer } from "../utils/segmentInitializer"; +import { FileSystemIdManager } from "./fileSystemIdManager"; +import { getExtensionId } from "../utils/extensions"; +import { CacheService } from "../interfaces/cacheService"; +import { ConfigurationManager } from "./configurationManager"; /** * `TelemetryService` builder */ export class TelemetryServiceBuilder { - private packageJson: any; - private settings?: TelemetrySettings; - private idManager?: IdManager; - private environment?: Environment; - private cacheService?: CacheService; - private configurationManager?: ConfigurationManager; - - constructor(packageJson?:any) { - this.packageJson = packageJson; - } - - public setPackageJson(packageJson: any): TelemetryServiceBuilder { - this.packageJson = packageJson; - return this; - } + private packageJson: any; + private settings?: TelemetrySettings; + private idManager?: IdManager; + private environment?: Environment; + private cacheService?: CacheService; + private configurationManager?: ConfigurationManager; - public setSettings(settings: TelemetrySettings): TelemetryServiceBuilder { - this.settings = settings; - return this; - } + constructor(packageJson?: any) { + this.packageJson = packageJson; + } - public setIdManager(idManager: IdManager): TelemetryServiceBuilder { - this.idManager = idManager; - return this; - } + public setPackageJson(packageJson: any): TelemetryServiceBuilder { + this.packageJson = packageJson; + return this; + } - public setEnvironment(environment: Environment): TelemetryServiceBuilder { - this.environment = environment; - return this; - } + public setSettings(settings: TelemetrySettings): TelemetryServiceBuilder { + this.settings = settings; + return this; + } - public setConfigurationManager(configManager: ConfigurationManager): TelemetryServiceBuilder { - this.configurationManager = configManager; - return this; - } + public setIdManager(idManager: IdManager): TelemetryServiceBuilder { + this.idManager = idManager; + return this; + } - public setCacheService(cacheService: CacheService): TelemetryServiceBuilder { - this.cacheService = cacheService; - return this; - } + public setEnvironment(environment: Environment): TelemetryServiceBuilder { + this.environment = environment; + return this; + } + + public setConfigurationManager( + configManager: ConfigurationManager, + ): TelemetryServiceBuilder { + this.configurationManager = configManager; + return this; + } + + public setCacheService(cacheService: CacheService): TelemetryServiceBuilder { + this.cacheService = cacheService; + return this; + } - public async build(): Promise { - this.validate(); - const analytics = SegmentInitializer.initialize(this.packageJson); - if (!this.idManager) { - this.idManager = new FileSystemIdManager(); - } - if (!this.environment) { - this.environment = { - extension: { - name: getExtensionId(this.packageJson), - version: this.packageJson.version - }, - application: { - name: 'Unknown', - version: '-' - }, - platform:{ - name:'Unknown', - version:'-' - } - }; - } - const reporter = new Reporter(analytics, this.cacheService); - const queue = this.settings!.isTelemetryConfigured() - ? undefined - : new TelemetryEventQueue(); - return new TelemetryServiceImpl(reporter, queue, this.settings!, this.idManager, this.environment!, this.configurationManager); + public async build(): Promise { + this.validate(); + const analytics = SegmentInitializer.initialize(this.packageJson); + if (!this.idManager) { + this.idManager = new FileSystemIdManager(); } + if (!this.environment) { + this.environment = { + extension: { + name: getExtensionId(this.packageJson), + version: this.packageJson.version, + }, + application: { + name: "Unknown", + version: "-", + }, + platform: { + name: "Unknown", + version: "-", + }, + }; + } + const reporter = new Reporter(analytics, this.cacheService); + const queue = this.settings!.isTelemetryConfigured() + ? undefined + : new TelemetryEventQueue(); + return new TelemetryServiceImpl( + reporter, + queue, + this.settings!, + this.idManager, + this.environment!, + this.configurationManager, + ); + } - private validate() { - if (!this.packageJson) { - throw new Error('packageJson is not set'); - } - if (!this.environment) { - throw new Error('Environment is not set'); - } + private validate() { + if (!this.packageJson) { + throw new Error("packageJson is not set"); + } + if (!this.environment) { + throw new Error("Environment is not set"); } -} \ No newline at end of file + } +} diff --git a/src/services/telemetryServiceImpl.ts b/src/services/telemetryServiceImpl.ts index 0eab2e0..13cc829 100644 --- a/src/services/telemetryServiceImpl.ts +++ b/src/services/telemetryServiceImpl.ts @@ -1,13 +1,13 @@ -import { Reporter } from './reporter'; -import { Logger } from '../utils/logger'; -import { TelemetrySettings } from '../interfaces/settings'; -import { TelemetryEventQueue } from '../utils/telemetryEventQueue'; -import { TelemetryService, TelemetryEvent } from '../interfaces/telemetry'; -import { CacheService } from '../interfaces/cacheService'; -import { ConfigurationManager } from './configurationManager'; -import { IdManager } from '../interfaces/idManager'; -import { Environment } from '../interfaces/environment'; -import { enhance, isError } from '../utils/events'; +import { Reporter } from "./reporter"; +import { Logger } from "../utils/logger"; +import { TelemetrySettings } from "../interfaces/settings"; +import { TelemetryEventQueue } from "../utils/telemetryEventQueue"; +import { TelemetryService, TelemetryEvent } from "../interfaces/telemetry"; +import { CacheService } from "../interfaces/cacheService"; +import { ConfigurationManager } from "./configurationManager"; +import { IdManager } from "../interfaces/idManager"; +import { Environment } from "../interfaces/environment"; +import { enhance, isError } from "../utils/events"; /** * Implementation of a `TelemetryService` @@ -15,12 +15,14 @@ import { enhance, isError } from '../utils/events'; export class TelemetryServiceImpl implements TelemetryService { private startTime: number; - constructor(private reporter: Reporter, - private queue: TelemetryEventQueue | undefined, - private settings: TelemetrySettings, - private idManager: IdManager, - private environment: Environment, - private configurationManager?: ConfigurationManager) { + constructor( + private reporter: Reporter, + private queue: TelemetryEventQueue | undefined, + private settings: TelemetrySettings, + private idManager: IdManager, + private environment: Environment, + private configurationManager?: ConfigurationManager, + ) { this.startTime = this.getCurrentTimeInSeconds(); } @@ -43,19 +45,22 @@ export class TelemetryServiceImpl implements TelemetryService { public async sendStartupEvent(): Promise { this.startTime = this.getCurrentTimeInSeconds(); - return this.send({ name: 'startup' }); + return this.send({ name: "startup" }); } public async sendShutdownEvent(): Promise { - return this.send({ name: 'shutdown', properties: { - //Sends session duration in seconds - session_duration: this.getCurrentTimeInSeconds() - this.startTime - } }); + return this.send({ + name: "shutdown", + properties: { + //Sends session duration in seconds + session_duration: this.getCurrentTimeInSeconds() - this.startTime, + }, + }); } private async sendEvent(event: TelemetryEvent): Promise { //Check against VS Code settings const level = this.settings.getTelemetryLevel(); - if (level && ["error","crash"].includes(level) && !isError(event)) { + if (level && ["error", "crash"].includes(level) && !isError(event)) { return; } @@ -66,7 +71,7 @@ export class TelemetryServiceImpl implements TelemetryService { properties: event.properties, measures: event.measures, traits: event.traits, - context: event.context + context: event.context, }; //Check against Extension configuration @@ -95,9 +100,8 @@ export class TelemetryServiceImpl implements TelemetryService { return this.reporter.flush(); } - private getCurrentTimeInSeconds(): number { const now = Date.now(); - return Math.floor(now/1000); + return Math.floor(now / 1000); } } diff --git a/src/tests/config/telemetry-config.json b/src/tests/config/telemetry-config.json index 8569b0c..9d60acb 100644 --- a/src/tests/config/telemetry-config.json +++ b/src/tests/config/telemetry-config.json @@ -1,22 +1,22 @@ { - "*": { - "enabled":"all", - "refresh": "2h", - "ratio": "1", - "includes": [ - { - "name" : "*" - } - ] - }, - "redhat.vscode-hypothetical": { - "enabled": "errors", - "ratio": "0.5", - "excludes": [ - { - "property": "error", - "value": "*stackoverflow*" - } - ] - } -} \ No newline at end of file + "*": { + "enabled": "all", + "refresh": "2h", + "ratio": "1", + "includes": [ + { + "name": "*" + } + ] + }, + "redhat.vscode-hypothetical": { + "enabled": "errors", + "ratio": "0.5", + "excludes": [ + { + "property": "error", + "value": "*stackoverflow*" + } + ] + } +} diff --git a/src/tests/gitpod/gitpodIdManager.test.ts b/src/tests/gitpod/gitpodIdManager.test.ts index 1ba3b4e..b4a4960 100644 --- a/src/tests/gitpod/gitpodIdManager.test.ts +++ b/src/tests/gitpod/gitpodIdManager.test.ts @@ -1,32 +1,32 @@ -import * as assert from 'assert'; -import mock from 'mock-fs'; -import { GitpodIdManager } from '../../gitpod/gitpodIdManager'; -import env from '../../interfaces/envVar'; -import { UUID } from '../../utils/uuid'; +import * as assert from "assert"; +import mock from "mock-fs"; +import { GitpodIdManager } from "../../gitpod/gitpodIdManager"; +import env from "../../interfaces/envVar"; +import { UUID } from "../../utils/uuid"; const redhatDir = `${process.cwd()}/.redhat/`; -suite('Test gitpod id manager', () => { - setup(() => { - mock({ - '.redhat': { - 'anonymousId': 'some-uuid' - } - }); +suite("Test gitpod id manager", () => { + setup(() => { + mock({ + ".redhat": { + anonymousId: "some-uuid", + }, }); - teardown(() => { - mock.restore(); - }); - test('Should generate Red Hat UUID from GITPOD_GIT_USER_EMAIL env', async () => { - env.GITPOD_GIT_USER_EMAIL = 'some.user@company.com'; - console.log(env.GITPOD_GIT_USER_EMAIL); - const gitpod = new GitpodIdManager(); - const id = gitpod.loadRedHatUUID(redhatDir); - const expectedId = '465b7cd6-0f77-5fc8-97ed-7b6342df109f'; - assert.strictEqual(id, expectedId); + }); + teardown(() => { + mock.restore(); + }); + test("Should generate Red Hat UUID from GITPOD_GIT_USER_EMAIL env", async () => { + env.GITPOD_GIT_USER_EMAIL = "some.user@company.com"; + console.log(env.GITPOD_GIT_USER_EMAIL); + const gitpod = new GitpodIdManager(); + const id = gitpod.loadRedHatUUID(redhatDir); + const expectedId = "465b7cd6-0f77-5fc8-97ed-7b6342df109f"; + assert.strictEqual(id, expectedId); - //Check anonymousId file was updated - const anonymousId = UUID.readFile(UUID.getAnonymousIdFile(redhatDir)); - assert.strictEqual(anonymousId, id); - }); -}); \ No newline at end of file + //Check anonymousId file was updated + const anonymousId = UUID.readFile(UUID.getAnonymousIdFile(redhatDir)); + assert.strictEqual(anonymousId, id); + }); +}); diff --git a/src/tests/services/configuration.test.ts b/src/tests/services/configuration.test.ts index 544ed5d..783c4d1 100644 --- a/src/tests/services/configuration.test.ts +++ b/src/tests/services/configuration.test.ts @@ -1,123 +1,132 @@ import assert from "assert"; import { AnalyticsEvent } from "../../services/AnalyticsEvent"; import { Configuration } from "../../services/configuration"; -import { v4 } from 'uuid'; +import { v4 } from "uuid"; import { hashCode, numValue } from "../../utils/hashcode"; -suite('Test configurations', () => { +suite("Test configurations", () => { + const all = { + enabled: "all", + includes: [ + { + name: "*", + }, + ], + }; - const all = { - "enabled": "all", - "includes": [ - { - "name": "*" - } - ] - }; + const identify = { + enabled: "all", + includes: [ + { + name: "identify", + }, + ], + }; - const identify = { - "enabled": "all", - "includes": [ - { - "name": "identify" - } - ] - }; + const off = { + enabled: "off", + includes: [ + { + name: "*", + }, + ], + }; - const off = { - "enabled": "off", - "includes": [ - { - "name": "*" - } - ] - }; + const errors = { + enabled: "error", + excludes: [ + { + property: "error", + value: "*stackoverflow*", + }, + ], + }; - const errors = { - "enabled": "error", - "excludes": [ - { - "property": "error", - "value": "*stackoverflow*" - } - ] - } + const ratioed = { + ratio: "0.3", + }; - const ratioed = { - "ratio":"0.3" - }; + test("Should allow all events", async () => { + const config = new Configuration(all); + let event = { event: "something" } as AnalyticsEvent; + assert.ok(config.canSend(event) === true); + }); + test("Should not allow any events", async () => { + const config = new Configuration(off); + let event = { event: "something" } as AnalyticsEvent; + assert.ok(config.canSend(event) === false); + }); - test('Should allow all events', async () => { - const config = new Configuration(all); - let event = { event: "something" } as AnalyticsEvent; - assert.ok(config.canSend(event) === true); - }); + test("Should filter events by name", async () => { + const config = new Configuration(identify); + let event = { + event: "identify", + } as AnalyticsEvent; + assert.ok(config.canSend(event) === true); + event = { + event: "startup", + } as AnalyticsEvent; + assert.ok(config.canSend(event) === false); + }); - test('Should not allow any events', async () => { - const config = new Configuration(off); - let event = { event: "something" } as AnalyticsEvent; - assert.ok(config.canSend(event) === false); - }); + test("Should only allow errors", async () => { + const config = new Configuration(errors); + let event = { + event: "startup", + } as AnalyticsEvent; + assert.ok( + config.canSend(event) === false, + `${event.event} shouldn't be sent`, + ); + event = { + event: "failed-analysis", + properties: { + error: "Ohoh, an error occurred!", + }, + } as AnalyticsEvent; + assert.ok(config.canSend(event) === true, `${event.event} should be sent`); + event = { + event: "crash-analysis", + properties: { + error: "Bla bla stackoverflow bla", + }, + } as AnalyticsEvent; + assert.ok( + config.canSend(event) === false, + `${event.event} shouldn't be sent`, + ); + }); - test('Should filter events by name', async () => { - const config = new Configuration(identify); - let event = { - event: "identify" - } as AnalyticsEvent; - assert.ok(config.canSend(event) === true); - event = { - event: "startup" - } as AnalyticsEvent; - assert.ok(config.canSend(event) === false); - }); + test("Should only allow errors", async () => { + const config = new Configuration(errors); + let event = { + event: "startup", + } as AnalyticsEvent; + assert.ok( + config.canSend(event) === false, + `${event.event} shouldn't be sent`, + ); + event = { + event: "failed-analysis", + properties: { + error: "Ohoh, an error occurred!", + }, + } as AnalyticsEvent; + assert.ok(config.canSend(event) === true, `${event.event} should be sent`); + event = { + event: "crash-analysis", + properties: { + error: "Bla bla stackoverflow bla", + }, + } as AnalyticsEvent; + assert.ok( + config.canSend(event) === false, + `${event.event} shouldn't be sent`, + ); + }); - test('Should only allow errors', async () => { - const config = new Configuration(errors); - let event = { - event: "startup" - } as AnalyticsEvent; - assert.ok(config.canSend(event) === false, `${event.event} shouldn't be sent`); - event = { - event: "failed-analysis", - properties: { - "error": "Ohoh, an error occurred!" - } - } as AnalyticsEvent; - assert.ok(config.canSend(event) === true, `${event.event} should be sent`); - event = { - event: "crash-analysis", - properties: { - "error": "Bla bla stackoverflow bla" - } - } as AnalyticsEvent; - assert.ok(config.canSend(event) === false, `${event.event} shouldn't be sent`); - }); - - test('Should only allow errors', async () => { - const config = new Configuration(errors); - let event = { - event: "startup" - } as AnalyticsEvent; - assert.ok(config.canSend(event) === false, `${event.event} shouldn't be sent`); - event = { - event: "failed-analysis", - properties: { - "error": "Ohoh, an error occurred!" - } - } as AnalyticsEvent; - assert.ok(config.canSend(event) === true, `${event.event} should be sent`); - event = { - event: "crash-analysis", - properties: { - "error": "Bla bla stackoverflow bla" - } - } as AnalyticsEvent; - assert.ok(config.canSend(event) === false, `${event.event} shouldn't be sent`); - }); - - - test('Should apply ratio on userId', async () => { - /* + test("Should apply ratio on userId", async () => { + /* d0b7ac12-caa0-4253-8087-788ff0b1c293 hashcode:-1654400659 numvalue:0.59 8668869d-a068-412b-9e59-4fec9dc0483a hashcode:-1782924593 numvalue:0.93 8b7fe10d-bb9d-434c-afed-4fb03f3b626e hashcode:1373002981 numvalue:0.81 @@ -130,21 +139,27 @@ suite('Test configurations', () => { cd304b68-3512-4af5-8991-377479bfede6 hashcode:-449137339 numvalue:0.39 */ - const config = new Configuration(ratioed); - let event = { - userId: "d0b7ac12-caa0-4253-8087-788ff0b1c293", //numvalue:0.59 > 0.3 - event: "startup" - } as AnalyticsEvent; - assert.ok(config.canSend(event) === false, `${event.event} shouldn't be sent`); - event = { - userId: "533629ec-091b-474b-95e6-3aa0eef3e940",//numvalue:0.22 < 0.3 - event: "startup", - } as AnalyticsEvent; - assert.ok(config.canSend(event) === true, `${event.event} should be sent`); - event = { - userId: "cd304b68-3512-4af5-8991-377479bfede6",//numvalue:0.39 > 0.3 - event: "startup", - } as AnalyticsEvent; - assert.ok(config.canSend(event) === false, `${event.event} shouldn't be sent`); - }); -}); \ No newline at end of file + const config = new Configuration(ratioed); + let event = { + userId: "d0b7ac12-caa0-4253-8087-788ff0b1c293", //numvalue:0.59 > 0.3 + event: "startup", + } as AnalyticsEvent; + assert.ok( + config.canSend(event) === false, + `${event.event} shouldn't be sent`, + ); + event = { + userId: "533629ec-091b-474b-95e6-3aa0eef3e940", //numvalue:0.22 < 0.3 + event: "startup", + } as AnalyticsEvent; + assert.ok(config.canSend(event) === true, `${event.event} should be sent`); + event = { + userId: "cd304b68-3512-4af5-8991-377479bfede6", //numvalue:0.39 > 0.3 + event: "startup", + } as AnalyticsEvent; + assert.ok( + config.canSend(event) === false, + `${event.event} shouldn't be sent`, + ); + }); +}); diff --git a/src/tests/services/configurationManager.test.ts b/src/tests/services/configurationManager.test.ts index 7fc0e58..6ceca76 100644 --- a/src/tests/services/configurationManager.test.ts +++ b/src/tests/services/configurationManager.test.ts @@ -1,158 +1,173 @@ import assert from "assert"; -import axios from 'axios'; -import mockFS from 'mock-fs'; -import * as fs from 'fs'; -import MockAdapter from 'axios-mock-adapter'; +import axios from "axios"; +import mockFS from "mock-fs"; +import * as fs from "fs"; +import MockAdapter from "axios-mock-adapter"; import { afterEach, beforeEach } from "mocha"; -import { ConfigurationManager, DEFAULT_CONFIG_URL, TELEMETRY_CONFIG } from "../../services/configurationManager"; +import { + ConfigurationManager, + DEFAULT_CONFIG_URL, + TELEMETRY_CONFIG, +} from "../../services/configurationManager"; import { FileSystemStorageService } from "../../services/FileSystemStorageService"; import path from "path"; import env from "../../interfaces/envVar"; -suite('Test configuration manager', () => { +suite("Test configuration manager", () => { + let mockAxios: MockAdapter; + let configurationManager: ConfigurationManager; - let mockAxios: MockAdapter; - let configurationManager: ConfigurationManager; - - const cacheDir = `${process.cwd()}/extension/cache`; + const cacheDir = `${process.cwd()}/extension/cache`; - const storageService = new FileSystemStorageService(cacheDir); + const storageService = new FileSystemStorageService(cacheDir); - const remoteConfig = { - "*": { - "enabled":"all", - "refresh": "3h", - "ratio": "1", - "includes": [ - { - "name" : "*" - } - ] + const remoteConfig = { + "*": { + enabled: "all", + refresh: "3h", + ratio: "1", + includes: [ + { + name: "*", }, - "redhat.vscode-yaml":{ - "enabled": "errors", - "ratio": "0.5", - "excludes": [ - { - "property": "error", - "value": "*stackoverflow*" - } - ] + ], + }, + "redhat.vscode-yaml": { + enabled: "errors", + ratio: "0.5", + excludes: [ + { + property: "error", + value: "*stackoverflow*", }, - "redhat.vscode-hypothetical": { - "enabled": "off" - } - } + ], + }, + "redhat.vscode-hypothetical": { + enabled: "off", + }, + }; - beforeEach(() => { - mockFS({ - 'extension/cache': {} - }); - configurationManager = new ConfigurationManager('redhat.vscode-hypothetical', storageService); - mockAxios = new MockAdapter(axios); - mockAxios.onGet(DEFAULT_CONFIG_URL).replyOnce(200, remoteConfig); + beforeEach(() => { + mockFS({ + "extension/cache": {}, }); + configurationManager = new ConfigurationManager( + "redhat.vscode-hypothetical", + storageService, + ); + mockAxios = new MockAdapter(axios); + mockAxios.onGet(DEFAULT_CONFIG_URL).replyOnce(200, remoteConfig); + }); - afterEach(() => { - env[ConfigurationManager.TEST_CONFIG_KEY] = undefined; - mockAxios.reset(); - mockAxios.restore(); - mockFS.restore(); - }); + afterEach(() => { + env[ConfigurationManager.TEST_CONFIG_KEY] = undefined; + mockAxios.reset(); + mockAxios.restore(); + mockFS.restore(); + }); - test('Should download remote config', async () => { - const json = await configurationManager.fetchRemoteConfiguration(); - assert.deepStrictEqual(json, remoteConfig); - }); + test("Should download remote config", async () => { + const json = await configurationManager.fetchRemoteConfiguration(); + assert.deepStrictEqual(json, remoteConfig); + }); - test('Should update stale config', async ()=> { - const origTimestamp = '12345678'; - mockFS.restore(); - mockFS({ - 'extension/cache': { - 'telemetry-config.json': '{'+ - '"*": {'+ - '"enabled":"errors",'+ - '"timestamp" : "12345678",'+ - '"refresh": "12h"'+ - '}'+ - '}' - } - }); - const config = await configurationManager.getExtensionConfiguration(); - const referenceTimestamp = config.json.timestamp; - assert.notStrictEqual(referenceTimestamp, origTimestamp); - assert.strictEqual(config.json.enabled, 'off'); - - const configPath = path.join(cacheDir, TELEMETRY_CONFIG); - const jsonConfig = JSON.parse(fs.readFileSync(configPath, { encoding: 'utf8' })); - assert.strictEqual(jsonConfig.timestamp, referenceTimestamp); + test("Should update stale config", async () => { + const origTimestamp = "12345678"; + mockFS.restore(); + mockFS({ + "extension/cache": { + "telemetry-config.json": + "{" + + '"*": {' + + '"enabled":"errors",' + + '"timestamp" : "12345678",' + + '"refresh": "12h"' + + "}" + + "}", + }, }); + const config = await configurationManager.getExtensionConfiguration(); + const referenceTimestamp = config.json.timestamp; + assert.notStrictEqual(referenceTimestamp, origTimestamp); + assert.strictEqual(config.json.enabled, "off"); + + const configPath = path.join(cacheDir, TELEMETRY_CONFIG); + const jsonConfig = JSON.parse( + fs.readFileSync(configPath, { encoding: "utf8" }), + ); + assert.strictEqual(jsonConfig.timestamp, referenceTimestamp); + }); + + test("Should store remote content locally", async () => { + const filePath = path.join(cacheDir, TELEMETRY_CONFIG); + assert.ok(!fs.existsSync(filePath), `${TELEMETRY_CONFIG} should not exist`); - test('Should store remote content locally', async ()=> { - const filePath = path.join(cacheDir, TELEMETRY_CONFIG); - assert.ok(!fs.existsSync(filePath), `${TELEMETRY_CONFIG} should not exist`); + const config1 = await configurationManager.getExtensionConfiguration(); + assert.ok(fs.existsSync(filePath), `${TELEMETRY_CONFIG} should exist`); + const referenceTimestamp = config1.json.timestamp; - const config1 = await configurationManager.getExtensionConfiguration(); - assert.ok(fs.existsSync(filePath), `${TELEMETRY_CONFIG} should exist`); - const referenceTimestamp = config1.json.timestamp; - - //No http request was made here - configurationManager = new ConfigurationManager('redhat.vscode-other', storageService); - const config = await configurationManager.getExtensionConfiguration(); - assert.strictEqual(config.json.timestamp, referenceTimestamp);//Same timestamp - delete config.json['timestamp']; - assert.deepStrictEqual(config.json ,{ - "refresh": "3h", - "includes": [ - { - "name" : "*" - } - ], - "enabled": "all", - "ratio": "1" - }); + //No http request was made here + configurationManager = new ConfigurationManager( + "redhat.vscode-other", + storageService, + ); + const config = await configurationManager.getExtensionConfiguration(); + assert.strictEqual(config.json.timestamp, referenceTimestamp); //Same timestamp + delete config.json["timestamp"]; + assert.deepStrictEqual(config.json, { + refresh: "3h", + includes: [ + { + name: "*", + }, + ], + enabled: "all", + ratio: "1", }); + }); - test('Should inherit config', async () => { - configurationManager = new ConfigurationManager('random-vscode', storageService); - const config = await configurationManager.getExtensionConfiguration(); - assert.ok(config.json.timestamp); - delete config.json['timestamp']; - assert.deepStrictEqual(config.json ,{ - "enabled":"all", - "refresh": "3h", - "ratio": "1", - "includes": [ - { - "name" : "*" - } - ] - },); + test("Should inherit config", async () => { + configurationManager = new ConfigurationManager( + "random-vscode", + storageService, + ); + const config = await configurationManager.getExtensionConfiguration(); + assert.ok(config.json.timestamp); + delete config.json["timestamp"]; + assert.deepStrictEqual(config.json, { + enabled: "all", + refresh: "3h", + ratio: "1", + includes: [ + { + name: "*", + }, + ], }); - - test('Should read embedded config', async () => { - mockFS.restore(); - env[ConfigurationManager.TEST_CONFIG_KEY] = 'true'; - mockAxios.reset(); - mockAxios.onGet(DEFAULT_CONFIG_URL).reply(404); - - const config = await configurationManager.getExtensionConfiguration(); - assert.deepStrictEqual(config.json ,{ - "refresh": "2h", - "includes": [ - { - "name" : "*" - } - ], - "enabled": "errors", - "ratio": "0.5", - "excludes": [ - { - "property": "error", - "value": "*stackoverflow*" - } - ] - }); + }); + + test("Should read embedded config", async () => { + mockFS.restore(); + env[ConfigurationManager.TEST_CONFIG_KEY] = "true"; + mockAxios.reset(); + mockAxios.onGet(DEFAULT_CONFIG_URL).reply(404); + + const config = await configurationManager.getExtensionConfiguration(); + assert.deepStrictEqual(config.json, { + refresh: "2h", + includes: [ + { + name: "*", + }, + ], + enabled: "errors", + ratio: "0.5", + excludes: [ + { + property: "error", + value: "*stackoverflow*", + }, + ], }); -}); \ No newline at end of file + }); +}); diff --git a/src/tests/services/fileSystemCacheService.test.ts b/src/tests/services/fileSystemCacheService.test.ts index 08263bd..e39cd17 100644 --- a/src/tests/services/fileSystemCacheService.test.ts +++ b/src/tests/services/fileSystemCacheService.test.ts @@ -1,19 +1,19 @@ -import mockFS from 'mock-fs'; -import * as fs from 'fs'; +import mockFS from "mock-fs"; +import * as fs from "fs"; import path from "path"; -import * as assert from 'assert'; -import { FileSystemCacheService } from '../../services/fileSystemCacheService'; +import * as assert from "assert"; +import { FileSystemCacheService } from "../../services/fileSystemCacheService"; let cacheService: FileSystemCacheService; const cacheDir = `${process.cwd()}/extension/cache`; -suite('Test cache service', () => { +suite("Test cache service", () => { setup(() => { mockFS({ - 'extension/cache': { - 'identity.txt': 'hash' - } + "extension/cache": { + "identity.txt": "hash", + }, }); cacheService = new FileSystemCacheService(cacheDir); @@ -21,31 +21,30 @@ suite('Test cache service', () => { teardown(() => { mockFS.restore(); }); - test('Should create cache directory recursively', async () => { - const cacheDir = `${process.cwd()}/extensions/cache/` + new Date().getTime(); + test("Should create cache directory recursively", async () => { + const cacheDir = + `${process.cwd()}/extensions/cache/` + new Date().getTime(); cacheService = new FileSystemCacheService(cacheDir); - const filePath = path.join(cacheDir, 'something.txt'); - await cacheService.put('something', 'hash'); - assert.ok(fs.existsSync(filePath), 'something.txt should exist'); - + const filePath = path.join(cacheDir, "something.txt"); + await cacheService.put("something", "hash"); + assert.ok(fs.existsSync(filePath), "something.txt should exist"); }); - test('Should read data from FS', async () => { - let data = await cacheService.get('identity'); - assert.strictEqual(data, 'hash'); - const filePath = path.join(cacheDir, 'identity.txt'); + test("Should read data from FS", async () => { + let data = await cacheService.get("identity"); + assert.strictEqual(data, "hash"); + const filePath = path.join(cacheDir, "identity.txt"); // Delete underlying file and check data is read from memory from now on fs.unlinkSync(filePath); - data = await cacheService.get('identity'); - assert.strictEqual(data, 'hash'); - assert.ok(!fs.existsSync(filePath), 'identity.txt should not exist'); - + data = await cacheService.get("identity"); + assert.strictEqual(data, "hash"); + assert.ok(!fs.existsSync(filePath), "identity.txt should not exist"); }); - test('Should write data to FS', async () => { - const filePath = path.join(cacheDir, 'something.txt'); - assert.ok(!fs.existsSync(filePath), 'something.txt should not exist'); - await cacheService.put('something', 'hash'); - assert.ok(fs.existsSync(filePath), 'something.txt should exist'); + test("Should write data to FS", async () => { + const filePath = path.join(cacheDir, "something.txt"); + assert.ok(!fs.existsSync(filePath), "something.txt should not exist"); + await cacheService.put("something", "hash"); + assert.ok(fs.existsSync(filePath), "something.txt should exist"); }); -}); \ No newline at end of file +}); diff --git a/src/tests/utils/events.test.ts b/src/tests/utils/events.test.ts index 9ed39e8..067b268 100644 --- a/src/tests/utils/events.test.ts +++ b/src/tests/utils/events.test.ts @@ -1,99 +1,111 @@ -import * as utils from '../../utils/events'; -import * as assert from 'assert'; -import { Environment } from '../../interfaces/environment'; -import { TelemetryEvent } from '../..'; +import * as utils from "../../utils/events"; +import * as assert from "assert"; +import { Environment } from "../../interfaces/environment"; +import { TelemetryEvent } from "../.."; const env: Environment = { - application: { - name:'SuperCode', - version:'6.6.6' - }, - extension: { - name: 'my-ext', - version: '1.2.3' - }, - username:'Fred', - platform: { - name: 'DeathStar II' - }, -} + application: { + name: "SuperCode", + version: "6.6.6", + }, + extension: { + name: "my-ext", + version: "1.2.3", + }, + username: "Fred", + platform: { + name: "DeathStar II", + }, +}; -suite('Test events enhancements', () => { - test('should inject environment data', async () => { - const event: TelemetryEvent = { - name:'Something', - properties: { - foo: 'bar', - } - } +suite("Test events enhancements", () => { + test("should inject environment data", async () => { + const event: TelemetryEvent = { + name: "Something", + properties: { + foo: "bar", + }, + }; - const betterEvent = utils.enhance(event, env); - assert.strictEqual(betterEvent.properties.app_name, 'SuperCode'); - assert.strictEqual(betterEvent.properties.app_version, '6.6.6'); - assert.strictEqual(betterEvent.properties.extension_name, 'my-ext'); - assert.strictEqual(betterEvent.properties.extension_version, '1.2.3'); - assert.strictEqual(betterEvent.properties.foo, 'bar'); - assert.strictEqual(betterEvent.context.ip, '0.0.0.0'); + const betterEvent = utils.enhance(event, env); + assert.strictEqual(betterEvent.properties.app_name, "SuperCode"); + assert.strictEqual(betterEvent.properties.app_version, "6.6.6"); + assert.strictEqual(betterEvent.properties.extension_name, "my-ext"); + assert.strictEqual(betterEvent.properties.extension_version, "1.2.3"); + assert.strictEqual(betterEvent.properties.foo, "bar"); + assert.strictEqual(betterEvent.context.ip, "0.0.0.0"); + }); - }); - - test('should anonymize data', async () => { - const event: TelemetryEvent = { - name:'Something', - properties: { - foo: 'Fred is Fred', - qty: 10, - active: false, - bar: 'That c:\\Fred\\bar looks like a path', - error: 'An error occurred in /Users/Fred/foo/bar.txt! But we\'re fine', - multiline: 'That url file://Fred/bar.txt is gone!\nNot that c:\\user\\bar though', - obj: { - q: 'Who is Fred?', - a: 'Fred who?' - } - } - } + test("should anonymize data", async () => { + const event: TelemetryEvent = { + name: "Something", + properties: { + foo: "Fred is Fred", + qty: 10, + active: false, + bar: "That c:\\Fred\\bar looks like a path", + error: "An error occurred in /Users/Fred/foo/bar.txt! But we're fine", + multiline: + "That url file://Fred/bar.txt is gone!\nNot that c:\\user\\bar though", + obj: { + q: "Who is Fred?", + a: "Fred who?", + }, + }, + }; - const betterEvent = utils.enhance(event, env); + const betterEvent = utils.enhance(event, env); - assert.strictEqual(betterEvent.properties.qty, 10); - assert.strictEqual(betterEvent.properties.active, false); - assert.strictEqual(betterEvent.properties.foo, '_username_ is _username_'); - assert.strictEqual(betterEvent.properties.bar, 'That c:\\_username_\\bar looks like a path'); - assert.strictEqual(betterEvent.properties.error, 'An error occurred in /Users/_username_/foo/bar.txt! But we\'re fine'); - assert.strictEqual(betterEvent.properties.multiline, 'That url file://_username_/bar.txt is gone!\nNot that c:\\user\\bar though'); - assert.strictEqual(betterEvent.properties.obj.q, 'Who is _username_?'); - assert.strictEqual(betterEvent.properties.obj.a, '_username_ who?'); - }); + assert.strictEqual(betterEvent.properties.qty, 10); + assert.strictEqual(betterEvent.properties.active, false); + assert.strictEqual(betterEvent.properties.foo, "_username_ is _username_"); + assert.strictEqual( + betterEvent.properties.bar, + "That c:\\_username_\\bar looks like a path", + ); + assert.strictEqual( + betterEvent.properties.error, + "An error occurred in /Users/_username_/foo/bar.txt! But we're fine", + ); + assert.strictEqual( + betterEvent.properties.multiline, + "That url file://_username_/bar.txt is gone!\nNot that c:\\user\\bar though", + ); + assert.strictEqual(betterEvent.properties.obj.q, "Who is _username_?"); + assert.strictEqual(betterEvent.properties.obj.a, "_username_ who?"); + }); - test('should not anonymize special usernames', async () => { - utils.IGNORED_USERS.forEach((user) => { - const cheEnv: Environment = { - application: { - name:'SuperCode', - version:'6.6.6' - }, - extension: { - name: 'my-ext', - version: '1.2.3' - }, - username: user, - platform: { - name: 'DeathStar II' - }, - } + test("should not anonymize special usernames", async () => { + utils.IGNORED_USERS.forEach((user) => { + const cheEnv: Environment = { + application: { + name: "SuperCode", + version: "6.6.6", + }, + extension: { + name: "my-ext", + version: "1.2.3", + }, + username: user, + platform: { + name: "DeathStar II", + }, + }; - const event: TelemetryEvent = { - name:'Something', - properties: { - foo: 'user likes theia', - multiline: 'That gitpod \nusername is woke', - } - } + const event: TelemetryEvent = { + name: "Something", + properties: { + foo: "user likes theia", + multiline: "That gitpod \nusername is woke", + }, + }; - const betterEvent = utils.enhance(event, cheEnv); - assert.strictEqual(betterEvent.properties.foo, event.properties.foo); - assert.strictEqual(betterEvent.properties.multiline, event.properties.multiline); - }); + const betterEvent = utils.enhance(event, cheEnv); + assert.strictEqual(betterEvent.properties.foo, event.properties.foo); + assert.strictEqual( + betterEvent.properties.multiline, + event.properties.multiline, + ); }); + }); }); diff --git a/src/tests/utils/geolocation.test.ts b/src/tests/utils/geolocation.test.ts index ba3df91..f8fb5de 100644 --- a/src/tests/utils/geolocation.test.ts +++ b/src/tests/utils/geolocation.test.ts @@ -1,12 +1,12 @@ -import * as assert from 'assert'; -import { getCountry } from '../../utils/geolocation'; +import * as assert from "assert"; +import { getCountry } from "../../utils/geolocation"; -suite('Test get country from timezone', () => { - test('known country', async () => { - assert.strictEqual('FR', getCountry("Europe/Paris")); - }); - test('unknown country', async () => { - assert.strictEqual('ZZ', getCountry("")); - assert.strictEqual('ZZ', getCountry("Groland/Groville")); - }); -}); \ No newline at end of file +suite("Test get country from timezone", () => { + test("known country", async () => { + assert.strictEqual("FR", getCountry("Europe/Paris")); + }); + test("unknown country", async () => { + assert.strictEqual("ZZ", getCountry("")); + assert.strictEqual("ZZ", getCountry("Groland/Groville")); + }); +}); diff --git a/src/utils/events.ts b/src/utils/events.ts index fb3caba..4cbdb59 100644 --- a/src/utils/events.ts +++ b/src/utils/events.ts @@ -1,5 +1,5 @@ -import { Environment } from '../interfaces/environment'; -import { TelemetryEvent } from '../interfaces/telemetry'; +import { Environment } from "../interfaces/environment"; +import { TelemetryEvent } from "../interfaces/telemetry"; /** * Enhances a `TelemetryEvent` by injecting environmental data to its properties and context @@ -92,14 +92,19 @@ import { TelemetryEvent } from '../interfaces/telemetry'; * @param event the event to enhance * @param environment the environment data to inject the event with */ -export const IGNORED_USERS = ['user', 'gitpod', 'theia'] +export const IGNORED_USERS = ["user", "gitpod", "theia"]; -export function enhance(event: TelemetryEvent, environment: Environment): TelemetryEvent { +export function enhance( + event: TelemetryEvent, + environment: Environment, +): TelemetryEvent { //Inject Client name and version, Extension id and version, and timezone to the event properties - const properties = event.properties ? sanitize(event.properties, environment) : {}; - if (!(event.type) || event.type == 'track') { - properties.extension_name = environment.extension.name - properties.extension_version = environment.extension.version + const properties = event.properties + ? sanitize(event.properties, environment) + : {}; + if (!event.type || event.type == "track") { + properties.extension_name = environment.extension.name; + properties.extension_version = environment.extension.version; properties.app_name = environment.application.name; properties.app_version = environment.application.version; if (environment.application.uiKind) { @@ -111,7 +116,7 @@ export function enhance(event: TelemetryEvent, environment: Environment): Teleme } const traits = event.traits ? sanitize(event.traits, environment) : {}; - if (event.type == 'identify') { + if (event.type == "identify") { //All those traits should be handled by Woopra in the context block, but are not. Meh. traits.timezone = environment.timezone; traits.os_name = environment.platform.name; @@ -123,10 +128,10 @@ export function enhance(event: TelemetryEvent, environment: Environment): Teleme //Inject Platform specific data in segment's context, so it can be recognized by the end destination // XXX Currently, Woopra ignores app, os, locale and timezone const context = event.context ? event.context : {}; - context.ip = '0.0.0.0'; + context.ip = "0.0.0.0"; context.app = { name: environment.application.name, - version: environment.application.version + version: environment.application.version, }; context.os = { name: environment.platform.name, @@ -136,7 +141,7 @@ export function enhance(event: TelemetryEvent, environment: Environment): Teleme context.location = { // This is inaccurate in some cases (user uses a different locale than from his actual country), // but still provides an interesting metric in most cases. - country: environment.country + country: environment.country, }; context.timezone = environment.timezone; @@ -146,16 +151,20 @@ export function enhance(event: TelemetryEvent, environment: Environment): Teleme properties: properties, measures: event.measures, traits: traits, - context: context + context: context, }; return enhancedEvent; } -function sanitize(properties: any, environment: Environment) : any { - const sanitized:any = {}; - let usernameRegexp: RegExp|undefined; - if (environment.username && environment.username.length > 3 && !IGNORED_USERS.includes(environment.username)) { - usernameRegexp = new RegExp(environment.username, 'g'); +function sanitize(properties: any, environment: Environment): any { + const sanitized: any = {}; + let usernameRegexp: RegExp | undefined; + if ( + environment.username && + environment.username.length > 3 && + !IGNORED_USERS.includes(environment.username) + ) { + usernameRegexp = new RegExp(environment.username, "g"); } for (const p in properties) { const rawProperty = properties[p]; @@ -164,14 +173,17 @@ function sanitize(properties: any, environment: Environment) : any { continue; } const isObj = isObject(rawProperty); - let sanitizedProperty = isObj? JSON.stringify(rawProperty) : rawProperty; + let sanitizedProperty = isObj ? JSON.stringify(rawProperty) : rawProperty; - sanitizedProperty = (sanitizedProperty as string).replace(usernameRegexp,'_username_'); + sanitizedProperty = (sanitizedProperty as string).replace( + usernameRegexp, + "_username_", + ); if (isObj) { //let's try to deserialize into a sanitized object try { sanitizedProperty = JSON.parse(sanitizedProperty); - } catch(e) { + } catch (e) { //We messed up, we'll return the sanitized string instead } } @@ -180,14 +192,16 @@ function sanitize(properties: any, environment: Environment) : any { return sanitized; } -function isObject(test:any):boolean { +function isObject(test: any): boolean { return test === Object(test); } -export function isError(event:any):boolean { +export function isError(event: any): boolean { return event.properties?.error || event.properties?.errors; } -function isNonStringPrimitive(test:any) { - return typeof test !== "string" && !(test instanceof String) && !isObject(test); +function isNonStringPrimitive(test: any) { + return ( + typeof test !== "string" && !(test instanceof String) && !isObject(test) + ); } diff --git a/src/utils/extensions.ts b/src/utils/extensions.ts index 7c27b3d..20fb9cf 100644 --- a/src/utils/extensions.ts +++ b/src/utils/extensions.ts @@ -1,13 +1,13 @@ import path from "path"; -import * as fs from 'fs'; +import * as fs from "fs"; export function getExtensionId(packageJson: any): string { - return `${packageJson.publisher}.${packageJson.name}`; + return `${packageJson.publisher}.${packageJson.name}`; } export function loadPackageJson(extensionPath: string): any { - const packageJsonPath = path.resolve(extensionPath, 'package.json') - const rawdata = fs.readFileSync(packageJsonPath, { encoding: 'utf8' }); - const packageJson = JSON.parse(rawdata); - return packageJson; -} \ No newline at end of file + const packageJsonPath = path.resolve(extensionPath, "package.json"); + const rawdata = fs.readFileSync(packageJsonPath, { encoding: "utf8" }); + const packageJson = JSON.parse(rawdata); + return packageJson; +} diff --git a/src/utils/geolocation.ts b/src/utils/geolocation.ts index 6a72563..a38f25a 100644 --- a/src/utils/geolocation.ts +++ b/src/utils/geolocation.ts @@ -1,10 +1,9 @@ - -import { getTimezone } from 'countries-and-timezones'; +import { getTimezone } from "countries-and-timezones"; export function getCountry(timezone: string): string { - const tz = getTimezone(timezone); - if (tz && tz?.country) { - return tz.country; - } - //Probably UTC timezone - return 'ZZ'; //Unknown country -} \ No newline at end of file + const tz = getTimezone(timezone); + if (tz && tz?.country) { + return tz.country; + } + //Probably UTC timezone + return "ZZ"; //Unknown country +} diff --git a/src/utils/hashcode.ts b/src/utils/hashcode.ts index 0bad3f8..e989069 100644 --- a/src/utils/hashcode.ts +++ b/src/utils/hashcode.ts @@ -1,25 +1,24 @@ //See https://stackoverflow.com/a/8076436/753170 -export function hashCode(value: string) : number { - let hash = 0; - for (let i = 0; i < value.length; i++) { - const code = value.charCodeAt(i); - hash = ((hash<<5)-hash)+code; - hash = hash & hash; // Convert to 32bit integer - } - return hash; +export function hashCode(value: string): number { + let hash = 0; + for (let i = 0; i < value.length; i++) { + const code = value.charCodeAt(i); + hash = (hash << 5) - hash + code; + hash = hash & hash; // Convert to 32bit integer + } + return hash; } - const cache = new Map(); -export function numValue(value: string) : number { - let num = cache.get(value); - if (num) { - return num; - } - const hash = Math.abs(hashCode(value)).toString(); - const x = Math.min(2, hash.length); - num = parseFloat(hash.substring(hash.length - x))/100; - cache.set(value, num); +export function numValue(value: string): number { + let num = cache.get(value); + if (num) { return num; + } + const hash = Math.abs(hashCode(value)).toString(); + const x = Math.min(2, hash.length); + num = parseFloat(hash.substring(hash.length - x)) / 100; + cache.set(value, num); + return num; } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index db8dd84..8c48bd8 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,13 +1,12 @@ import envVars from "../interfaces/envVar"; -export let doLog: boolean = envVars.VSCODE_REDHAT_TELEMETRY_DEBUG === 'true'; +export let doLog: boolean = envVars.VSCODE_REDHAT_TELEMETRY_DEBUG === "true"; // This exists only for testing purposes. Could delete later. -const VERSION = require('../../package.json').version; - +const VERSION = require("../../package.json").version; export namespace Logger { - export let extId = 'unknown'; + export let extId = "unknown"; export function log(s: number | string | boolean | undefined): void { if (doLog) { console.log(`vscode-redhat-telemetry ${VERSION} (${extId}): ${s}`); diff --git a/src/utils/segmentInitializer.ts b/src/utils/segmentInitializer.ts index e9c1462..048f29b 100644 --- a/src/utils/segmentInitializer.ts +++ b/src/utils/segmentInitializer.ts @@ -1,19 +1,18 @@ -import { Logger } from './logger'; -import Analytics from 'analytics-node'; -import { getExtensionId } from './extensions'; +import { Logger } from "./logger"; +import Analytics from "analytics-node"; +import { getExtensionId } from "./extensions"; const IS_DEBUG = startedInDebugMode(); let DEFAULT_SEGMENT_KEY: string | undefined; export namespace SegmentInitializer { - export function initialize(clientPackageJson: any): Analytics | undefined { let segmentWriteKey = getSegmentKey(clientPackageJson); if (!segmentWriteKey) { //Using the default key if (!DEFAULT_SEGMENT_KEY) { - const defaultPackageJson = require('../../package.json'); + const defaultPackageJson = require("../../package.json"); DEFAULT_SEGMENT_KEY = getSegmentKey(defaultPackageJson); } segmentWriteKey = DEFAULT_SEGMENT_KEY; @@ -31,7 +30,9 @@ export namespace SegmentInitializer { }); return analytics; } else { - Logger.log('Missing segmentWriteKey from package.json OR package.json in vscode-commons'); + Logger.log( + "Missing segmentWriteKey from package.json OR package.json in vscode-commons", + ); return undefined; } } @@ -44,20 +45,22 @@ function startedInDebugMode(): boolean { // exported for tests function hasDebugFlag(args: string[]): boolean { - return args + return ( + args && // See https://nodejs.org/en/docs/guides/debugging-getting-started/ - && args.some(arg => /^--inspect/.test(arg) || /^--debug/.test(arg)); + args.some((arg) => /^--inspect/.test(arg) || /^--debug/.test(arg)) + ); } function getSegmentKey(packageJson: any): string | undefined { const extensionId = getExtensionId(packageJson); - let keyKey = 'segmentWriteKeyDebug'; + let keyKey = "segmentWriteKeyDebug"; try { let clientSegmentKey: string | undefined = undefined; if (IS_DEBUG) { clientSegmentKey = packageJson[keyKey]; } else { - keyKey = 'segmentWriteKey'; + keyKey = "segmentWriteKey"; clientSegmentKey = packageJson[keyKey]; } if (clientSegmentKey) { diff --git a/src/utils/telemetryEventQueue.ts b/src/utils/telemetryEventQueue.ts index b20e53f..b335561 100644 --- a/src/utils/telemetryEventQueue.ts +++ b/src/utils/telemetryEventQueue.ts @@ -1,4 +1,4 @@ -import { TelemetryEvent } from '../interfaces/telemetry'; +import { TelemetryEvent } from "../interfaces/telemetry"; export const MAX_QUEUE_SIZE = 35; diff --git a/src/utils/uuid.ts b/src/utils/uuid.ts index 4bbee84..6fdaaff 100644 --- a/src/utils/uuid.ts +++ b/src/utils/uuid.ts @@ -1,11 +1,11 @@ -import * as os from 'os'; -import * as fs from 'fs'; -import * as path from 'path'; -import { v4, v5 } from 'uuid'; -import { Logger } from './logger'; +import * as os from "os"; +import * as fs from "fs"; +import * as path from "path"; +import { v4, v5 } from "uuid"; +import { Logger } from "./logger"; let REDHAT_ANONYMOUS_UUID: string | undefined; -const REDHAT_NAMESPACE_UUID = '44662bc6-c388-4e0e-a652-53bda6f35923'; +const REDHAT_NAMESPACE_UUID = "44662bc6-c388-4e0e-a652-53bda6f35923"; export namespace UUID { export function getRedHatUUID(redhatDir?: string): string { @@ -18,13 +18,15 @@ export namespace UUID { if (REDHAT_ANONYMOUS_UUID) { Logger.log(`loaded Red Hat UUID: ${REDHAT_ANONYMOUS_UUID}`); } else { - Logger.log('No Red Hat UUID found'); + Logger.log("No Red Hat UUID found"); REDHAT_ANONYMOUS_UUID = v4(); writeFile(redhatUUIDFilePath, REDHAT_ANONYMOUS_UUID); - Logger.log(`Written Red Hat UUID: ${REDHAT_ANONYMOUS_UUID} to ${redhatUUIDFilePath}`); + Logger.log( + `Written Red Hat UUID: ${REDHAT_ANONYMOUS_UUID} to ${redhatUUIDFilePath}`, + ); } } catch (e: any) { - Logger.log('Failed to access Red Hat UUID: ' + e?.message); + Logger.log("Failed to access Red Hat UUID: " + e?.message); } return REDHAT_ANONYMOUS_UUID!; } @@ -32,15 +34,15 @@ export namespace UUID { export function getAnonymousIdFile(redhatDir?: string): string { const homedir = os.homedir(); if (!redhatDir) { - redhatDir = path.join(homedir, '.redhat'); + redhatDir = path.join(homedir, ".redhat"); } - return path.join(redhatDir, 'anonymousId'); + return path.join(redhatDir, "anonymousId"); } - export function readFile(filePath: string): string|undefined { + export function readFile(filePath: string): string | undefined { let content: string | undefined; if (fs.existsSync(filePath)) { - content = fs.readFileSync(filePath, { encoding: 'utf8' }); + content = fs.readFileSync(filePath, { encoding: "utf8" }); if (content) { content = content.trim(); } @@ -48,12 +50,12 @@ export namespace UUID { return content; } - export function writeFile(filePath: string, content: string){ + export function writeFile(filePath: string, content: string) { const parentDir = path.dirname(filePath); if (!fs.existsSync(parentDir)) { fs.mkdirSync(parentDir, { recursive: true }); } - fs.writeFileSync(filePath, content, { encoding: 'utf8' }); + fs.writeFileSync(filePath, content, { encoding: "utf8" }); } export function generateUUID(source: string): string { diff --git a/src/vscode/constants.ts b/src/vscode/constants.ts index 3d5f665..538c343 100644 --- a/src/vscode/constants.ts +++ b/src/vscode/constants.ts @@ -2,12 +2,12 @@ OPT_IN_STATUS === "true" if user responded on the notification pop-up and undefined if the user has not responded or closed the notification pop-up */ -export const OPT_IN_STATUS_KEY = 'redhat.telemetry.optInRequested'; +export const OPT_IN_STATUS_KEY = "redhat.telemetry.optInRequested"; export const PRIVACY_STATEMENT_URL: string = - 'https://developers.redhat.com/article/tool-data-collection'; + "https://developers.redhat.com/article/tool-data-collection"; export const OPT_OUT_INSTRUCTIONS_URL: string = - 'https://github.com/redhat-developer/vscode-redhat-telemetry#how-to-disable-telemetry-reporting'; -export const CONFIG_KEY = 'redhat.telemetry'; + "https://github.com/redhat-developer/vscode-redhat-telemetry#how-to-disable-telemetry-reporting"; +export const CONFIG_KEY = "redhat.telemetry"; -export const DEFAULT_SEGMENT_KEY = 'MXM7iv13sVaCGqOhnQEGLZxhfy6hecYh'; -export const DEFAULT_SEGMENT_DEBUG_KEY ='eKBn0xqKQcQJVhUOW0vdQtNQiK791OLa'; +export const DEFAULT_SEGMENT_KEY = "MXM7iv13sVaCGqOhnQEGLZxhfy6hecYh"; +export const DEFAULT_SEGMENT_DEBUG_KEY = "eKBn0xqKQcQJVhUOW0vdQtNQiK791OLa"; diff --git a/src/vscode/redhatService.ts b/src/vscode/redhatService.ts index e98e5e4..15479b5 100644 --- a/src/vscode/redhatService.ts +++ b/src/vscode/redhatService.ts @@ -1,17 +1,28 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import { ConfigurationChangeEvent, Disposable, env, ExtensionContext, window, workspace } from "vscode"; +import * as fs from "fs"; +import * as path from "path"; +import { + ConfigurationChangeEvent, + Disposable, + env, + ExtensionContext, + window, + workspace, +} from "vscode"; import { TelemetryService, TelemetryServiceBuilder } from ".."; import { RedHatService } from "../interfaces/redhatService"; -import { ConfigurationManager } from '../services/configurationManager'; -import { FileSystemCacheService } from '../services/fileSystemCacheService'; +import { ConfigurationManager } from "../services/configurationManager"; +import { FileSystemCacheService } from "../services/fileSystemCacheService"; import { IdManagerFactory } from "../services/idManagerFactory"; -import { getExtensionId, loadPackageJson } from '../utils/extensions'; +import { getExtensionId, loadPackageJson } from "../utils/extensions"; import { Logger } from "../utils/logger"; import { getEnvironment } from "../node/platform"; -import { DEFAULT_SEGMENT_DEBUG_KEY, DEFAULT_SEGMENT_KEY, OPT_OUT_INSTRUCTIONS_URL, PRIVACY_STATEMENT_URL } from './constants'; -import { didUserDisableTelemetry, VSCodeSettings } from './settings'; - +import { + DEFAULT_SEGMENT_DEBUG_KEY, + DEFAULT_SEGMENT_KEY, + OPT_OUT_INSTRUCTIONS_URL, + PRIVACY_STATEMENT_URL, +} from "./constants"; +import { didUserDisableTelemetry, VSCodeSettings } from "./settings"; const RETRY_OPTIN_DELAY_IN_MS = 24 * 60 * 60 * 1000; // 24h @@ -20,24 +31,28 @@ const RETRY_OPTIN_DELAY_IN_MS = 24 * 60 * 60 * 1000; // 24h * - A preference listener enables/disables telemetry based on changes to `redhat.telemetry.enabled` * - If `redhat.telemetry.enabled` is not set, a popup requesting telemetry opt-in will be displayed * - when the extension is deactivated, a telemetry shutdown event will be emitted (if telemetry is enabled) - * + * * @param context the extension's context * @returns a Promise of RedHatService */ -export async function getRedHatService(context: ExtensionContext): Promise { +export async function getRedHatService( + context: ExtensionContext, +): Promise { const extensionInfo = getExtension(context); const extensionId = extensionInfo.id; Logger.extId = extensionId; const packageJson = getPackageJson(extensionInfo); const settings = new VSCodeSettings(); const idManager = IdManagerFactory.getIdManager(); - const cachePath = path.resolve(getTelemetryWorkingDir(context), 'cache'); + const cachePath = path.resolve(getTelemetryWorkingDir(context), "cache"); const cacheService = new FileSystemCacheService(cachePath); const builder = new TelemetryServiceBuilder(packageJson) .setSettings(settings) .setIdManager(idManager) .setCacheService(cacheService) - .setConfigurationManager(new ConfigurationManager(extensionId, cacheService)) + .setConfigurationManager( + new ConfigurationManager(extensionId, cacheService), + ) .setEnvironment(await getEnvironment(extensionId, packageJson.version)); const telemetryService = await builder.build(); @@ -45,36 +60,45 @@ export async function getRedHatService(context: ExtensionContext): Promise Promise.resolve(telemetryService), - getIdManager: () => Promise.resolve(idManager) - } + getIdManager: () => Promise.resolve(idManager), + }; } -function onDidChangeTelemetryEnabled(telemetryService: TelemetryService): Disposable { +function onDidChangeTelemetryEnabled( + telemetryService: TelemetryService, +): Disposable { return workspace.onDidChangeConfiguration( //as soon as user changed the redhat.telemetry setting, we consider //opt-in (or out) has been set, so whichever the choice is, we flush the queue (e: ConfigurationChangeEvent) => { - if (e.affectsConfiguration("redhat.telemetry") || e.affectsConfiguration("telemetry")) { + if ( + e.affectsConfiguration("redhat.telemetry") || + e.affectsConfiguration("telemetry") + ) { telemetryService.flushQueue(); } - } + }, ); } -async function openTelemetryOptInDialogIfNeeded(context: ExtensionContext, extensionId:string, settings: VSCodeSettings) { +async function openTelemetryOptInDialogIfNeeded( + context: ExtensionContext, + extensionId: string, + settings: VSCodeSettings, +) { if (settings.isTelemetryConfigured() || didUserDisableTelemetry()) { return; } @@ -82,13 +106,16 @@ async function openTelemetryOptInDialogIfNeeded(context: ExtensionContext, exten let popupInfo: PopupInfo | undefined; const parentDir = getTelemetryWorkingDir(context); - const optinPopupInfo = path.resolve(parentDir, 'redhat.optin.json'); + const optinPopupInfo = path.resolve(parentDir, "redhat.optin.json"); if (fs.existsSync(optinPopupInfo)) { - const rawdata = fs.readFileSync(optinPopupInfo, { encoding: 'utf8' }); + const rawdata = fs.readFileSync(optinPopupInfo, { encoding: "utf8" }); popupInfo = JSON.parse(rawdata); } if (popupInfo) { - if (popupInfo.sessionId !== env.sessionId || popupInfo.owner !== extensionId) { + if ( + popupInfo.sessionId !== env.sessionId || + popupInfo.owner !== extensionId + ) { //someone else is showing the popup, bail. return; } @@ -96,14 +123,16 @@ async function openTelemetryOptInDialogIfNeeded(context: ExtensionContext, exten popupInfo = { owner: extensionId, sessionId: env.sessionId, - time: new Date().getTime() //for troubleshooting purposes - } + time: new Date().getTime(), //for troubleshooting purposes + }; if (!fs.existsSync(parentDir)) { fs.mkdirSync(parentDir, { recursive: true }); } fs.writeFileSync(optinPopupInfo, JSON.stringify(popupInfo)); context.subscriptions.push({ - dispose: () => { safeCleanup(optinPopupInfo); } + dispose: () => { + safeCleanup(optinPopupInfo); + }, }); } @@ -111,16 +140,21 @@ async function openTelemetryOptInDialogIfNeeded(context: ExtensionContext, exten Read our [privacy statement](${PRIVACY_STATEMENT_URL}?from=${extensionId}) and learn how to [opt out](${OPT_OUT_INSTRUCTIONS_URL}?from=${extensionId}).`; - const retryOptin = setTimeout(openTelemetryOptInDialogIfNeeded, RETRY_OPTIN_DELAY_IN_MS, context, settings); + const retryOptin = setTimeout( + openTelemetryOptInDialogIfNeeded, + RETRY_OPTIN_DELAY_IN_MS, + context, + settings, + ); let selection: string | undefined; try { - selection = await window.showInformationMessage(message, 'Accept', 'Deny'); + selection = await window.showInformationMessage(message, "Accept", "Deny"); if (!selection) { //close was chosen. Ask next time. return; } clearTimeout(retryOptin); - settings.updateTelemetryEnabledConfig(selection === 'Accept'); + settings.updateTelemetryEnabledConfig(selection === "Accept"); } finally { if (selection) { safeCleanup(optinPopupInfo); @@ -129,11 +163,11 @@ async function openTelemetryOptInDialogIfNeeded(context: ExtensionContext, exten } interface ExtensionInfo { - id:string, - packageJSON: any + id: string; + packageJSON: any; } -function getExtension(context: ExtensionContext): ExtensionInfo { +function getExtension(context: ExtensionContext): ExtensionInfo { if (context.extension) { return context.extension; } @@ -141,7 +175,7 @@ function getExtension(context: ExtensionContext): ExtensionInfo { const packageJson = loadPackageJson(context.extensionPath); const info = { id: getExtensionId(packageJson), - packageJSON: packageJson + packageJSON: packageJson, }; return info; } @@ -158,7 +192,7 @@ function getPackageJson(extension: ExtensionInfo): any { } interface PopupInfo { - owner: string, + owner: string; sessionId: string; time: number; } @@ -166,7 +200,7 @@ interface PopupInfo { function safeCleanup(filePath: string) { try { fs.unlinkSync(filePath); - } catch (err : any) { + } catch (err: any) { Logger.log(err); } Logger.log(`Deleted ${filePath}`); @@ -177,11 +211,14 @@ function shutdownHook(telemetryService: TelemetryService): Disposable { dispose: async () => { await telemetryService.sendShutdownEvent(); await telemetryService.dispose(); - } + }, }; } function getTelemetryWorkingDir(context: ExtensionContext): string { - return path.resolve(context.globalStorageUri.fsPath, '..', 'vscode-redhat-telemetry'); + return path.resolve( + context.globalStorageUri.fsPath, + "..", + "vscode-redhat-telemetry", + ); } - diff --git a/src/vscode/settings.ts b/src/vscode/settings.ts index 2198bf6..65a6012 100644 --- a/src/vscode/settings.ts +++ b/src/vscode/settings.ts @@ -1,16 +1,20 @@ -import { env, workspace, WorkspaceConfiguration } from 'vscode'; -import { TelemetrySettings } from '../interfaces/settings'; -import { CONFIG_KEY } from './constants'; +import { env, workspace, WorkspaceConfiguration } from "vscode"; +import { TelemetrySettings } from "../interfaces/settings"; +import { CONFIG_KEY } from "./constants"; export class VSCodeSettings implements TelemetrySettings { isTelemetryEnabled(): boolean { - return this.getTelemetryLevel() !== 'off' && getTelemetryConfiguration().get('enabled', false); + return ( + this.getTelemetryLevel() !== "off" && + getTelemetryConfiguration().get("enabled", false) + ); } getTelemetryLevel(): string { //Respecting old vscode telemetry settings https://github.com/microsoft/vscode/blob/f09c4124a229b4149984e1c2da46f35b873d23fa/src/vs/platform/telemetry/common/telemetryUtils.ts#L131 - if (workspace.getConfiguration().get("telemetry.enableTelemetry") == false - || workspace.getConfiguration().get("telemetry.enableCrashReporter") == false + if ( + workspace.getConfiguration().get("telemetry.enableTelemetry") == false || + workspace.getConfiguration().get("telemetry.enableCrashReporter") == false ) { return "off"; } @@ -18,15 +22,14 @@ export class VSCodeSettings implements TelemetrySettings { } isTelemetryConfigured(): boolean { - return isPreferenceOverridden(CONFIG_KEY + '.enabled'); + return isPreferenceOverridden(CONFIG_KEY + ".enabled"); } updateTelemetryEnabledConfig(value: boolean): Thenable { - return getTelemetryConfiguration().update('enabled', value, true); + return getTelemetryConfiguration().update("enabled", value, true); } } - export function getTelemetryConfiguration(): WorkspaceConfiguration { return workspace.getConfiguration(CONFIG_KEY); } @@ -48,7 +51,10 @@ export function didUserDisableTelemetry(): boolean { return false; } //Telemetry is not enabled, but it might not be the user's choice. - //i.e. could be the App's default setting (VS Codium), or - //then the user only asked for reporting errors/crashes, in which case we can do the same. - return isPreferenceOverridden("telemetry.telemetryLevel") && workspace.getConfiguration().get("telemetry.telemetryLevel") === "off"; + //i.e. could be the App's default setting (VS Codium), or + //then the user only asked for reporting errors/crashes, in which case we can do the same. + return ( + isPreferenceOverridden("telemetry.telemetryLevel") && + workspace.getConfiguration().get("telemetry.telemetryLevel") === "off" + ); } diff --git a/test/index.ts b/test/index.ts index 941048f..4b49d42 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1,6 +1,3 @@ -import { - getRedHatService, - TelemetryService, - } from "../lib"; +import { getRedHatService, TelemetryService } from "../lib"; console.log("Hello World"); diff --git a/tsconfig.json b/tsconfig.json index 6432767..af2fbd9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,9 +6,7 @@ "declaration": true, "declarationMap": true, "sourceMap": true, - "lib": [ - "es6" - ], + "lib": ["es6"], "allowJs": true, "outDir": "./lib", "strict": true, @@ -16,7 +14,5 @@ "esModuleInterop": true, "resolveJsonModule": true }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} diff --git a/webpack.config.ts b/webpack.config.ts index ee00eb1..79db9ae 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -1,30 +1,28 @@ -import * as path from 'path'; -import * as webpack from 'webpack'; -const WarningsToErrorsPlugin = require('warnings-to-errors-webpack-plugin'); +import * as path from "path"; +import * as webpack from "webpack"; +const WarningsToErrorsPlugin = require("warnings-to-errors-webpack-plugin"); // in case you run into any typescript error when configuring `devServer` // import 'webpack-dev-server'; const config: webpack.Configuration = { - mode: 'production', - entry: './test/index.ts', + mode: "production", + entry: "./test/index.ts", externals: { - "vscode": "commonjs vscode", + vscode: "commonjs vscode", }, target: "node", // vscode extensions run in a Node.js-context node: { __dirname: false, // leave the __dirname-behavior intact }, output: { - path: path.resolve(__dirname, 'dist'), - filename: 'foo.bundle.js', + path: path.resolve(__dirname, "dist"), + filename: "foo.bundle.js", }, - plugins: [ - new WarningsToErrorsPlugin(), - ], + plugins: [new WarningsToErrorsPlugin()], stats: { errorDetails: true, - } + }, }; export default config;