From 14added7ebdf0db5f5cfc4fd07ebe32e4e839531 Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 10:56:57 +0200 Subject: [PATCH 01/12] feat: introduce telemetry - client and events definition --- package-lock.json | 157 +++++++++++++++++++++++++++++++++++++++-- package.json | 7 ++ src/constants.ts | 2 + src/utils/globals.ts | 4 ++ src/utils/telemetry.ts | 65 +++++++++++++++++ 5 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 src/utils/telemetry.ts diff --git a/package-lock.json b/package-lock.json index f123c3d..0d0f339 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@monokle/synchronizer": "^0.2.0", "@monokle/validation": "^0.25.2", + "analytics-node": "^6.2.0", + "node-machine-id": "^1.1.12", "uuid": "^9.0.0", "yaml": "^2.3.1" }, @@ -970,6 +972,15 @@ } } }, + "node_modules/@segment/loosely-validate-event": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", + "integrity": "sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==", + "dependencies": { + "component-type": "^1.2.1", + "join-component": "^1.1.0" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", @@ -1512,6 +1523,32 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/analytics-node": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/analytics-node/-/analytics-node-6.2.0.tgz", + "integrity": "sha512-NLU4tCHlWt0tzEaFQL7NIoWhq2KmQSmz0JvyS2lYn6fc4fEjTMSabhJUx8H1r5995FX8fE3rZ15uIHU6u+ovlQ==", + "dependencies": { + "@segment/loosely-validate-event": "^2.0.0", + "axios": "^0.27.2", + "axios-retry": "3.2.0", + "lodash.isstring": "^4.0.1", + "md5": "^2.2.1", + "ms": "^2.0.0", + "remove-trailing-slash": "^0.1.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/analytics-node/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -1582,8 +1619,37 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/axios-retry": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.2.0.tgz", + "integrity": "sha512-RK2cLMgIsAQBDhlIsJR5dOhODPigvel18XUv1dDXW+4k1FzebyfRk+C+orot6WPZOYFKSfhLwHPwVmTVOODQ5w==", + "dependencies": { + "is-retry-allowed": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } }, "node_modules/balanced-match": { "version": "1.0.2", @@ -1849,6 +1915,14 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -1921,7 +1995,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -1929,6 +2002,11 @@ "node": ">= 0.8" } }, + "node_modules/component-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.1.tgz", + "integrity": "sha512-Kgy+2+Uwr75vAi6ChWXgHuLvd+QLD7ssgpaRq2zCvt80ptvAfMc/hijcJxXkBa2wMlEZcJvC2H8Ubo+A9ATHIg==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2115,6 +2193,14 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -2177,7 +2263,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -2828,6 +2913,25 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -3306,6 +3410,11 @@ "node": ">=8" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3363,6 +3472,14 @@ "node": ">=8" } }, + "node_modules/is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -3450,6 +3567,11 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/join-component": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz", + "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==" + }, "node_modules/jose": { "version": "4.14.4", "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", @@ -3554,6 +3676,11 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3637,6 +3764,16 @@ "semver": "bin/semver.js" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3699,7 +3836,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -3708,7 +3844,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -3991,6 +4126,11 @@ } } }, + "node_modules/node-machine-id": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", + "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4452,6 +4592,11 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, + "node_modules/remove-trailing-slash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz", + "integrity": "sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index 452cd90..1144c4c 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,11 @@ "default": null, "description": "Overwrite Monokle Cloud URL which is used to authenticate and fetch policies from. Useful when running on-premise Monokle Cloud." }, + "monokle.telemetryEnabled": { + "type": "boolean", + "default": true, + "description": "Whenever anonymous telemetry is enabled. It will be also disabled automatically when VSC telemetry is disabled globally." + }, "monokle.enabled": { "type": "boolean", "default": true, @@ -152,6 +157,8 @@ "dependencies": { "@monokle/synchronizer": "^0.2.0", "@monokle/validation": "^0.25.2", + "analytics-node": "^6.2.0", + "node-machine-id": "^1.1.12", "uuid": "^9.0.0", "yaml": "^2.3.1" }, diff --git a/src/constants.ts b/src/constants.ts index 17cf186..6179f9a 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -13,10 +13,12 @@ export const SETTINGS = { ENABLED: 'enabled', CONFIGURATION_PATH: 'configurationPath', VERBOSE: 'verbose', + TELEMETRY_ENABLED: 'telemetryEnabled', OVERWRITE_REMOTE_POLICY_URL: 'overwriteRemotePolicyUrl', ENABLED_PATH: 'monokle.enabled', CONFIGURATION_PATH_PATH: 'monokle.configurationPath', VERBOSE_PATH: 'monokle.verbose', + TELEMETRY_ENABLED_PATH: 'monokle.telemetryEnabled', OVERWRITE_REMOTE_POLICY_URL_PATH: 'monokle.overwriteRemotePolicyUrl', }; diff --git a/src/utils/globals.ts b/src/utils/globals.ts index 00cb691..4ba1181 100644 --- a/src/utils/globals.ts +++ b/src/utils/globals.ts @@ -43,6 +43,10 @@ class Globals { return workspace.getConfiguration(SETTINGS.NAMESPACE).get(SETTINGS.VERBOSE); } + get telemetryEnabled() { + return workspace.getConfiguration(SETTINGS.NAMESPACE).get(SETTINGS.VERBOSE); + } + get user(): Awaited>['user'] { if (!this._authenticator) { throw new Error('Authenticator not initialized for globals.'); diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts new file mode 100644 index 0000000..ca95384 --- /dev/null +++ b/src/utils/telemetry.ts @@ -0,0 +1,65 @@ +import {machineIdSync} from 'node-machine-id'; +import Analytics from 'analytics-node'; +import {env} from 'vscode'; +import logger from './logger'; +import globals from './globals'; + +let client: Analytics | undefined; + +const getSegmentClient = () => { + if (!env.isTelemetryEnabled || !globals.telemetryEnabled ) { + client = undefined; + return undefined; + } + + if (!client) { + enableSegment(); + } + + return client; +}; + +const enableSegment = () => { + if (process.env.SEGMENT_API_KEY) { + logger.log('Enabled Segment'); + client = new Analytics({writeKey: process.env.SEGMENT_API_KEY, flushAt: 1, errorHandler: logger.error}); + } +}; + +const machineId: string = machineIdSync(); + +export const trackEvent = (eventName: TEvent, payload?: EventMap[TEvent]) => { + const segmentClient = getSegmentClient(); + + logger.log('Track event', machineId, eventName, payload); + + segmentClient?.track({ + event: eventName, + properties: payload, + userId: machineId, + }); +}; + +export type EventStatus = 'started' | 'success' | 'failure' | 'cancelled'; +export type BaseEvent = {status: EventStatus, error?: string}; + +export type Event = keyof EventMap; +export type EventMap = { + 'ext/installed': BaseEvent & {appVersion: string; deviceOS: string}; + 'ext/session': BaseEvent & {appVersion: string}; + 'ext/session_end': BaseEvent & {timeSpent: number}; + 'command/login': BaseEvent & {method?: string}; + 'command/logout': BaseEvent; + 'command/validate': BaseEvent & {configurationType: string}; + 'command/show_panel': BaseEvent; + 'command/show_configuration': BaseEvent & {configurationType: string}; + 'command/bootstrap_configuration': BaseEvent; + 'command/synchronize': BaseEvent; + 'config/change': BaseEvent & {name: string; value: string}; + // When new folder is added/removed to/from VSC workspace. + 'workspace/change': BaseEvent & {rootCount: number}; + // When validation is completed for a workspace, usually triggered by file modification. + 'workspace/validated': BaseEvent; + // When policy is synced from Monokle Cloud. + 'policy/sync': BaseEvent; +}; From 264aba4aa1aae8b3ee48740415b99d680bbefa0c Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 11:25:19 +0200 Subject: [PATCH 02/12] feat: track commands with telemetry --- src/commands/bootstrap-configuration.ts | 46 +++++++++++++++++++----- src/commands/login.ts | 47 +++++++++++++++++++++++-- src/commands/logout.ts | 20 +++++++++++ src/commands/show-configuration.ts | 25 +++++++++++-- src/commands/show-panel.ts | 11 +++++- src/commands/synchronize.ts | 15 ++++++++ src/commands/validate.ts | 10 ++++++ src/utils/telemetry.ts | 8 ++--- 8 files changed, 163 insertions(+), 19 deletions(-) diff --git a/src/commands/bootstrap-configuration.ts b/src/commands/bootstrap-configuration.ts index 234a65e..af852a7 100644 --- a/src/commands/bootstrap-configuration.ts +++ b/src/commands/bootstrap-configuration.ts @@ -3,6 +3,7 @@ import { canRun } from '../utils/commands'; import { getWorkspaceConfig, getWorkspaceFolders } from '../utils/workspace'; import { createDefaultConfigFile } from '../utils/validation'; import { raiseInfo, raiseWarning } from '../utils/errors'; +import { trackEvent } from '../utils/telemetry'; import logger from '../utils/logger'; import type { Folder } from '../utils/workspace'; @@ -17,6 +18,10 @@ export function getBootstrapConfigurationCommand() { return null; } + trackEvent('command/bootstrap_configuration', { + status: 'started', + }); + const folders = getWorkspaceFolders(); if (!folders.length) { @@ -29,27 +34,46 @@ export function getBootstrapConfigurationCommand() { const currentConfig = await getWorkspaceConfig(folder); if (currentConfig.type === 'file') { raiseInfo(`Local '${currentConfig.fileName}' configuration file already exists, opening it.`); - return currentConfig.path; + return { + path: currentConfig.path, + type: currentConfig.type, + }; } if (currentConfig.type === 'config') { raiseWarning(`Shared '${currentConfig.path}' configuration file already exists, opening it.`); - return currentConfig.path; + return { + path: currentConfig.path, + type: currentConfig.type, + }; } if (currentConfig.type === 'remote') { raiseWarning(`Remote '${currentConfig.fileName}' configuration file already exists, opening it.`); - return currentConfig.path; + return { + path: currentConfig.path, + type: currentConfig.type, + }; } const configPath = (await createDefaultConfigFile(folder.uri.fsPath)).fsPath; - return configPath; + return { + path: configPath, + type: 'default', + }; }; if (folders.length === 1) { - const configPath = await generateConfig(folders[0]); - return await commands.executeCommand('vscode.open', Uri.file(configPath)); + const configData = await generateConfig(folders[0]); + await commands.executeCommand('vscode.open', Uri.file(configData.path)); + + trackEvent('command/bootstrap_configuration', { + status: 'success', + configurationType: configData.type, + }); + + return null; } const quickPick = window.createQuickPick(); @@ -63,13 +87,19 @@ export function getBootstrapConfigurationCommand() { // error } - const configPath = await generateConfig(selectedFolder); + const configData = await generateConfig(selectedFolder); quickPick.hide(); - await commands.executeCommand('vscode.open', Uri.file(configPath)); + await commands.executeCommand('vscode.open', Uri.file(configData.path)); + + trackEvent('command/bootstrap_configuration', { + status: 'success', + configurationType: configData.type, + }); } }); + quickPick.onDidHide(() => quickPick.dispose()); quickPick.show(); diff --git a/src/commands/login.ts b/src/commands/login.ts index 755b7dc..6a6bc42 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -2,6 +2,7 @@ import { window, env, Uri } from 'vscode'; import { canRun } from '../utils/commands'; import { raiseError, raiseInfo } from '../utils/errors'; import { COMMAND_NAMES } from '../constants'; +import { trackEvent } from '../utils/telemetry'; import logger from '../utils/logger'; import type { RuntimeContext } from '../utils/runtime-context'; @@ -11,23 +12,37 @@ const AUTH_METHOD_LABELS = { }; export function getLoginCommand(context: RuntimeContext) { - return async () => { if (!canRun()) { return; } + trackEvent('command/login', { + status: 'started', + }); + const authenticator = context.authenticator; if (authenticator.user.isAuthenticated) { raiseInfo(`You are already logged in. Please logout first with '${COMMAND_NAMES.LOGIN}'.`); + + trackEvent('command/login', { + status: 'cancelled', + error: 'User already logged in.' + }); + return; } const method = await pickLoginMethod(authenticator.methods); if (!method) { - return; + trackEvent('command/login', { + status: 'cancelled', + error: 'No login method selected.' + }); + + return; } try { @@ -57,19 +72,45 @@ export function getLoginCommand(context: RuntimeContext) { prompt: 'You can create account on https://app.monokle.com.', }); + if (!accessToken) { + trackEvent('command/login', { + status: 'cancelled', + method, + error: 'No access token provided.' + }); + + return; + } + loginRequest = await authenticator.login(method, accessToken); } if (!loginRequest) { - return; + trackEvent('command/login', { + status: 'cancelled', + method, + }); + + return; } const user = await loginRequest.onDone; raiseInfo(`You are now logged in as ${user.email}.`); + + trackEvent('command/login', { + status: 'success', + method, + }); } catch (err) { logger.error(err); raiseError(`Failed to login to Monokle Cloud. Please try again. Error: ${err.message}`); + + trackEvent('command/login', { + status: 'failure', + method, + error: err.message, + }); } }; } diff --git a/src/commands/logout.ts b/src/commands/logout.ts index d709b2a..26bcff5 100644 --- a/src/commands/logout.ts +++ b/src/commands/logout.ts @@ -1,6 +1,7 @@ import { canRun } from '../utils/commands'; import { raiseError, raiseInfo } from '../utils/errors'; import { COMMAND_NAMES } from '../constants'; +import { trackEvent } from '../utils/telemetry'; import logger from '../utils/logger'; import type { RuntimeContext } from '../utils/runtime-context'; @@ -10,19 +11,38 @@ export function getLogoutCommand(context: RuntimeContext) { return; } + trackEvent('command/logout', { + status: 'started', + }); + const authenticator = context.authenticator; if (!authenticator.user.isAuthenticated) { raiseInfo(`You are already logged out. You can login with '${COMMAND_NAMES.LOGOUT}' command.`); + + trackEvent('command/logout', { + status: 'cancelled', + error: 'User already logged out.' + }); + return; } try { await authenticator.logout(); raiseInfo('You have been successfully logged out.'); + + trackEvent('command/logout', { + status: 'success', + }); } catch (err) { logger.error(err); raiseError(`Failed to logout from Monokle Cloud. Please try again. Error: ${err.message}`); + + trackEvent('command/logout', { + status: 'failure', + error: err.message, + }); } }; } diff --git a/src/commands/show-configuration.ts b/src/commands/show-configuration.ts index 0bf63db..4db08fd 100644 --- a/src/commands/show-configuration.ts +++ b/src/commands/show-configuration.ts @@ -2,6 +2,7 @@ import { Uri, commands, window } from 'vscode'; import { canRun } from '../utils/commands'; import { getWorkspaceConfig, getWorkspaceFolders } from '../utils/workspace'; import { createTemporaryConfigFile } from '../utils/validation'; +import { trackEvent } from '../utils/telemetry'; import type { Folder } from '../utils/workspace'; type FolderItem = { @@ -15,6 +16,10 @@ export function getShowConfigurationCommand() { return null; } + trackEvent('command/show_configuration', { + status: 'started', + }); + const folders = getWorkspaceFolders(); if (!folders.length) { @@ -27,11 +32,20 @@ export function getShowConfigurationCommand() { Uri.file(config.path) : await createTemporaryConfigFile(config.config, config.owner); - return commands.executeCommand('vscode.open', configUri); + await commands.executeCommand('vscode.open', configUri); + + return config.type; }; if (folders.length === 1) { - return await showConfig(folders[0]); + const configType = await showConfig(folders[0]); + + trackEvent('command/show_configuration', { + status: 'success', + configurationType: configType, + }); + + return; } const quickPick = window.createQuickPick(); @@ -47,7 +61,12 @@ export function getShowConfigurationCommand() { quickPick.hide(); - await showConfig(selectedFolder); + const configType = await showConfig(selectedFolder); + + trackEvent('command/show_configuration', { + status: 'success', + configurationType: configType, + }); } }); quickPick.onDidHide(() => quickPick.dispose()); diff --git a/src/commands/show-panel.ts b/src/commands/show-panel.ts index f8bbb98..0c468c1 100644 --- a/src/commands/show-panel.ts +++ b/src/commands/show-panel.ts @@ -1,5 +1,6 @@ import { commands, extensions } from 'vscode'; import { canRun } from '../utils/commands'; +import { trackEvent } from '../utils/telemetry'; export function getShowPanelCommand() { return async () => { @@ -7,12 +8,20 @@ export function getShowPanelCommand() { return null; } + trackEvent('command/show_panel', { + status: 'started' + }); + const sarifExtension = extensions.getExtension('MS-SarifVSCode.sarif-viewer'); if (!sarifExtension.isActive) { await sarifExtension.activate(); } - return commands.executeCommand('sarif.showPanel'); + await commands.executeCommand('sarif.showPanel'); + + trackEvent('command/show_panel', { + status: 'success' + }); }; } \ No newline at end of file diff --git a/src/commands/synchronize.ts b/src/commands/synchronize.ts index 1d33e30..e9b5d83 100644 --- a/src/commands/synchronize.ts +++ b/src/commands/synchronize.ts @@ -2,6 +2,7 @@ import { commands } from 'vscode'; import { canRun } from '../utils/commands'; import { COMMANDS, COMMAND_NAMES } from '../constants'; import { raiseWarning } from '../utils/errors'; +import { trackEvent } from '../utils/telemetry'; import globals from '../utils/globals'; import type { RuntimeContext } from '../utils/runtime-context'; @@ -11,13 +12,27 @@ export function getSynchronizeCommand(context: RuntimeContext) { return null; } + trackEvent('command/synchronize', { + status: 'started' + }); + if (!globals.user.isAuthenticated) { raiseWarning(`You are not authenticated, cannot synchronize policies. Run ${COMMAND_NAMES.LOGIN} to authenticate first.}`); + + trackEvent('command/synchronize', { + status: 'cancelled', + error: 'User not authenticated.' + }); + return null; } await context.policyPuller.refresh(); + trackEvent('command/synchronize', { + status: 'success' + }); + return commands.executeCommand(COMMANDS.VALIDATE); }; } \ No newline at end of file diff --git a/src/commands/validate.ts b/src/commands/validate.ts index ae6acdc..ce58682 100644 --- a/src/commands/validate.ts +++ b/src/commands/validate.ts @@ -1,6 +1,7 @@ import { validateFolder } from '../utils/validation'; import { getWorkspaceFolders } from '../utils/workspace'; import { canRun } from '../utils/commands'; +import { trackEvent } from '../utils/telemetry'; import type { RuntimeContext } from '../utils/runtime-context'; export function getValidateCommand(context: RuntimeContext) { @@ -9,6 +10,10 @@ export function getValidateCommand(context: RuntimeContext) { return; } + trackEvent('command/validate', { + status: 'started', + }); + context.isValidating = true; const roots = getWorkspaceFolders(); @@ -19,6 +24,11 @@ export function getValidateCommand(context: RuntimeContext) { context.isValidating = false; + trackEvent('command/validate', { + status: 'success', + rootCount: roots.length, + }); + return context.sarifWatcher.replace(resultFiles); }; } diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts index ca95384..d65030b 100644 --- a/src/utils/telemetry.ts +++ b/src/utils/telemetry.ts @@ -50,16 +50,16 @@ export type EventMap = { 'ext/session_end': BaseEvent & {timeSpent: number}; 'command/login': BaseEvent & {method?: string}; 'command/logout': BaseEvent; - 'command/validate': BaseEvent & {configurationType: string}; + 'command/validate': BaseEvent & {rootCount?: number}; 'command/show_panel': BaseEvent; - 'command/show_configuration': BaseEvent & {configurationType: string}; - 'command/bootstrap_configuration': BaseEvent; + 'command/show_configuration': BaseEvent & {configurationType?: string}; + 'command/bootstrap_configuration': BaseEvent & {configurationType?: string}; 'command/synchronize': BaseEvent; 'config/change': BaseEvent & {name: string; value: string}; // When new folder is added/removed to/from VSC workspace. 'workspace/change': BaseEvent & {rootCount: number}; // When validation is completed for a workspace, usually triggered by file modification. - 'workspace/validated': BaseEvent; + 'workspace/validate': BaseEvent & {configurationType: string, isValidConfiguration: boolean}; // When policy is synced from Monokle Cloud. 'policy/sync': BaseEvent; }; From 3045bcae3e2d7263f37fb7d12f96c3595901ffc1 Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 11:38:59 +0200 Subject: [PATCH 03/12] fix: migrate to latest Segment client SDK --- package-lock.json | 262 +++++++++++++++++++---------------------- package.json | 2 +- src/utils/telemetry.ts | 4 +- 3 files changed, 122 insertions(+), 146 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d0f339..6d49e8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@monokle/synchronizer": "^0.2.0", "@monokle/validation": "^0.25.2", - "analytics-node": "^6.2.0", + "@segment/analytics-node": "^1.1.0", "node-machine-id": "^1.1.12", "uuid": "^9.0.0", "yaml": "^2.3.1" @@ -797,6 +797,25 @@ "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" }, + "node_modules/@lukeed/csprng": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", + "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@lukeed/uuid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@lukeed/uuid/-/uuid-2.0.1.tgz", + "integrity": "sha512-qC72D4+CDdjGqJvkFMMEAtancHUQ7/d/tAiHf64z8MopFDmcrtbcJuerDtFceuAfQJ2pDSfCKCtbqoGBNnwg0w==", + "dependencies": { + "@lukeed/csprng": "^1.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@monokle/synchronizer": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@monokle/synchronizer/-/synchronizer-0.2.0.tgz", @@ -972,15 +991,41 @@ } } }, - "node_modules/@segment/loosely-validate-event": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@segment/loosely-validate-event/-/loosely-validate-event-2.0.0.tgz", - "integrity": "sha512-ZMCSfztDBqwotkl848ODgVcAmN4OItEWDCkshcKz0/W6gGSQayuuCtWV/MlodFivAZD793d6UgANd6wCXUfrIw==", + "node_modules/@segment/analytics-core": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-core/-/analytics-core-1.3.0.tgz", + "integrity": "sha512-ujScWZH49NK1hYlp2/EMw45nOPEh+pmTydAnR6gSkRNucZD4fuinvpPL03rmFCw8ibaMuKLAdgPJfQ0gkLKZ5A==", "dependencies": { - "component-type": "^1.2.1", - "join-component": "^1.1.0" + "@lukeed/uuid": "^2.0.0", + "dset": "^3.1.2", + "tslib": "^2.4.1" } }, + "node_modules/@segment/analytics-core/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/@segment/analytics-node": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@segment/analytics-node/-/analytics-node-1.1.0.tgz", + "integrity": "sha512-q8MPpvQJgMUSIRmQXficA33ZmLQ6s5YqUMr623xJhhfp/TGkkgfpcdMk+qSniZVIm8JuQRXm8Mbh922LG3goBQ==", + "dependencies": { + "@lukeed/uuid": "^2.0.0", + "@segment/analytics-core": "1.3.0", + "buffer": "^6.0.3", + "node-fetch": "^2.6.7", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@segment/analytics-node/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", @@ -1523,32 +1568,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/analytics-node": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/analytics-node/-/analytics-node-6.2.0.tgz", - "integrity": "sha512-NLU4tCHlWt0tzEaFQL7NIoWhq2KmQSmz0JvyS2lYn6fc4fEjTMSabhJUx8H1r5995FX8fE3rZ15uIHU6u+ovlQ==", - "dependencies": { - "@segment/loosely-validate-event": "^2.0.0", - "axios": "^0.27.2", - "axios-retry": "3.2.0", - "lodash.isstring": "^4.0.1", - "md5": "^2.2.1", - "ms": "^2.0.0", - "remove-trailing-slash": "^0.1.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/analytics-node/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -1619,37 +1638,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", - "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", - "dependencies": { - "follow-redirects": "^1.14.9", - "form-data": "^4.0.0" - } - }, - "node_modules/axios-retry": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.2.0.tgz", - "integrity": "sha512-RK2cLMgIsAQBDhlIsJR5dOhODPigvel18XUv1dDXW+4k1FzebyfRk+C+orot6WPZOYFKSfhLwHPwVmTVOODQ5w==", - "dependencies": { - "is-retry-allowed": "^1.1.0" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/balanced-match": { "version": "1.0.2", @@ -1657,6 +1647,25 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1733,6 +1742,29 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1915,14 +1947,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "engines": { - "node": "*" - } - }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -1995,6 +2019,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2002,11 +2027,6 @@ "node": ">= 0.8" } }, - "node_modules/component-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-type/-/component-type-1.2.1.tgz", - "integrity": "sha512-Kgy+2+Uwr75vAi6ChWXgHuLvd+QLD7ssgpaRq2zCvt80ptvAfMc/hijcJxXkBa2wMlEZcJvC2H8Ubo+A9ATHIg==" - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2193,14 +2213,6 @@ "node": ">= 8" } }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "engines": { - "node": "*" - } - }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -2263,6 +2275,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -2337,7 +2350,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==", - "dev": true, "engines": { "node": ">=4" } @@ -2913,25 +2925,6 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/foreground-child": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", @@ -3333,6 +3326,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -3410,11 +3422,6 @@ "node": ">=8" } }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3472,14 +3479,6 @@ "node": ">=8" } }, - "node_modules/is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", @@ -3567,11 +3566,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/join-component": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/join-component/-/join-component-1.1.0.tgz", - "integrity": "sha512-bF7vcQxbODoGK1imE2P9GS9aw4zD0Sd+Hni68IMZLj7zRnquH7dXUmMw9hDI5S/Jzt7q+IyTXN0rSg2GI0IKhQ==" - }, "node_modules/jose": { "version": "4.14.4", "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", @@ -3676,11 +3670,6 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3764,16 +3753,6 @@ "semver": "bin/semver.js" } }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3836,6 +3815,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -3844,6 +3824,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -4592,11 +4573,6 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "dev": true }, - "node_modules/remove-trailing-slash": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz", - "integrity": "sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", diff --git a/package.json b/package.json index 1144c4c..e26716d 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,7 @@ "dependencies": { "@monokle/synchronizer": "^0.2.0", "@monokle/validation": "^0.25.2", - "analytics-node": "^6.2.0", + "@segment/analytics-node": "^1.1.0", "node-machine-id": "^1.1.12", "uuid": "^9.0.0", "yaml": "^2.3.1" diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts index d65030b..7504425 100644 --- a/src/utils/telemetry.ts +++ b/src/utils/telemetry.ts @@ -1,5 +1,5 @@ import {machineIdSync} from 'node-machine-id'; -import Analytics from 'analytics-node'; +import {Analytics} from '@segment/analytics-node'; import {env} from 'vscode'; import logger from './logger'; import globals from './globals'; @@ -22,7 +22,7 @@ const getSegmentClient = () => { const enableSegment = () => { if (process.env.SEGMENT_API_KEY) { logger.log('Enabled Segment'); - client = new Analytics({writeKey: process.env.SEGMENT_API_KEY, flushAt: 1, errorHandler: logger.error}); + client = new Analytics({writeKey: process.env.SEGMENT_API_KEY}); } }; From 03f84dad3ab9189eff3ad779481544ff1fff1f26 Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 12:16:50 +0200 Subject: [PATCH 04/12] feat: track background tasks and changes with telemetry --- src/extension.ts | 41 ++++++++++++++++++++++++++++++- src/utils/policy-puller.ts | 16 ++++++++++++ src/utils/telemetry.ts | 10 ++++++-- src/utils/validation.ts | 50 ++++++++++++++++++++++++++++++++++++-- 4 files changed, 112 insertions(+), 5 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 7fbb1e9..d72e0aa 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,6 +15,7 @@ import { getTooltipContentDefault } from './utils/tooltip'; import { getLogoutCommand } from './commands/logout'; import { getAuthenticator } from './utils/authentication'; import { getSynchronizer } from './utils/synchronization'; +import { trackEvent } from './utils/telemetry'; import logger from './utils/logger'; import globals from './utils/globals'; import type { ExtensionContext } from 'vscode'; @@ -62,6 +63,13 @@ export async function activate(context: ExtensionContext): Promise { const configurationWatcher = workspace.onDidChangeConfiguration(async (event) => { if (event.affectsConfiguration(SETTINGS.ENABLED_PATH)) { const enabled = globals.enabled; + + trackEvent('config/change', { + status: 'success', + name: SETTINGS.ENABLED, + value: String(enabled), + }); + if (enabled) { await runtimeContext.policyPuller.refresh(); await commands.executeCommand(COMMANDS.VALIDATE); @@ -72,20 +80,51 @@ export async function activate(context: ExtensionContext): Promise { } if (event.affectsConfiguration(SETTINGS.CONFIGURATION_PATH_PATH)) { - commands.executeCommand(COMMANDS.VALIDATE); + trackEvent('config/change', { + status: 'success', + name: SETTINGS.CONFIGURATION_PATH, + value: 'redacted', // Can include sensitive data. + }); + + await commands.executeCommand(COMMANDS.VALIDATE); } if (event.affectsConfiguration(SETTINGS.VERBOSE_PATH)) { + trackEvent('config/change', { + status: 'success', + name: SETTINGS.VERBOSE, + value: String(globals.verbose), + }); + logger.debug = globals.verbose; } if (event.affectsConfiguration(SETTINGS.OVERWRITE_REMOTE_POLICY_URL_PATH)) { + trackEvent('config/change', { + status: 'success', + name: SETTINGS.OVERWRITE_REMOTE_POLICY_URL, + value: 'redacted', // Can include sensitive data. + }); + await runtimeContext.policyPuller.refresh(); await commands.executeCommand(COMMANDS.VALIDATE); } + + if (event.affectsConfiguration(SETTINGS.TELEMETRY_ENABLED_PATH)) { + trackEvent('config/change', { + status: 'success', + name: SETTINGS.TELEMETRY_ENABLED, + value: String(globals.telemetryEnabled), + }); + } }); const workspaceWatcher = workspace.onDidChangeWorkspaceFolders(async () => { + trackEvent('workspace/change', { + status: 'success', + rootCount: workspace.workspaceFolders?.length ?? 0, + }); + await runtimeContext.policyPuller.refresh(); await commands.executeCommand(COMMANDS.VALIDATE); await commands.executeCommand(COMMANDS.WATCH); diff --git a/src/utils/policy-puller.ts b/src/utils/policy-puller.ts index a297909..a68b506 100644 --- a/src/utils/policy-puller.ts +++ b/src/utils/policy-puller.ts @@ -1,6 +1,7 @@ import { rm } from 'fs/promises'; import { getWorkspaceFolders } from './workspace'; import { getSynchronizer } from './synchronization'; +import { trackEvent } from './telemetry'; import logger from './logger'; import globals from './globals'; import type { Folder } from './workspace'; @@ -65,10 +66,19 @@ export class PolicyPuller { private async fetchPolicyFiles(roots: Folder[]) { for (const folder of roots) { + + trackEvent('policy/synchronize', { + status: 'started', + }); + try { const policy = await this._synchronizer.synchronize(folder.uri.fsPath, globals.user.token); logger.log('fetchPolicyFiles', policy); globals.setFolderStatus(folder); + + trackEvent('policy/synchronize', { + status: 'success', + }); } catch (error) { const errorDetails = this.getErrorDetails(error); @@ -83,6 +93,12 @@ export class PolicyPuller { } globals.setFolderStatus(folder, errorDetails.message); + + trackEvent('policy/synchronize', { + status: 'failure', + errorCode: errorDetails.type, + error: error.msg, + }); } } diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts index 7504425..860a1d7 100644 --- a/src/utils/telemetry.ts +++ b/src/utils/telemetry.ts @@ -59,7 +59,13 @@ export type EventMap = { // When new folder is added/removed to/from VSC workspace. 'workspace/change': BaseEvent & {rootCount: number}; // When validation is completed for a workspace, usually triggered by file modification. - 'workspace/validate': BaseEvent & {configurationType: string, isValidConfiguration: boolean}; + 'workspace/validate': BaseEvent & { + resourceCount?: number, + configurationType?: string, + isValidConfiguration?: boolean, + validationWarnings?: number, + validationErrors?: number, + }; // When policy is synced from Monokle Cloud. - 'policy/sync': BaseEvent; + 'policy/synchronize': BaseEvent & {errorCode?: string}; }; diff --git a/src/utils/validation.ts b/src/utils/validation.ts index ce9c945..8a7b23b 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -3,9 +3,10 @@ import { join, normalize } from 'path'; import { platform } from 'os'; import { Uri } from 'vscode'; import { Document } from 'yaml'; -import { getWorkspaceConfig, getWorkspaceResources } from './workspace'; +import { getWorkspaceConfig, getWorkspaceResources, WorkspaceFolderConfig } from './workspace'; import { VALIDATION_FILE_SUFFIX, DEFAULT_CONFIG_FILE_NAME, TMP_POLICY_FILE_SUFFIX } from '../constants'; import { getInvalidConfigError } from './errors'; +import { trackEvent } from './telemetry'; import logger from '../utils/logger'; import globals from './globals'; import type { Folder } from './workspace'; @@ -58,9 +59,18 @@ export async function getValidator(validatorId: string, config?: any) { } export async function validateFolder(root: Folder): Promise { + trackEvent('workspace/validate', { + status: 'started', + }); + const resources = await getWorkspaceResources(root); if(!resources.length) { + trackEvent('workspace/validate', { + status: 'cancelled', + resourceCount: 0, + }); + return null; } @@ -71,6 +81,15 @@ export async function validateFolder(root: Folder): Promise { // For remote config, error is already send from policy puller. globals.setFolderStatus(root, getInvalidConfigError(workspaceConfig)); } + + trackEvent('workspace/validate', { + status: 'failure', + resourceCount: resources.length, + configurationType: workspaceConfig.type, + isValidConfiguration: false, + error: 'Invalid configuration', + }); + return null; } @@ -102,7 +121,11 @@ export async function validateFolder(root: Folder): Promise { globals.setFolderStatus(root); - return Uri.file(resultsFilePath); + const resultFilePath = Uri.file(resultsFilePath); + + sendSuccessValidationTelemetry(resources.length, workspaceConfig, result); + + return resultFilePath; } export async function getValidationResult(fileName: string) { @@ -223,6 +246,29 @@ async function getConfigurableValidator() { }; } +function sendSuccessValidationTelemetry(resourceCount: number, workspaceConfig: WorkspaceFolderConfig, validationResult: any) { + const results = validationResult?.runs?.length ? validationResult.runs[0].results : []; + + let errors = 0; + let warnings = 0; + results.forEach(result => { + if (result.level === 'warning') { + warnings++; + } else if (result.level === 'error') { + errors++; + } + }); + + trackEvent('workspace/validate', { + status: 'success', + resourceCount, + configurationType: workspaceConfig.type, + isValidConfiguration: workspaceConfig.isValid, + validationWarnings: warnings, + validationErrors: errors, + }); +} + // For some reason (according to specs? to be checked) SARIF extension doesn't like // valid Windows paths, which are "C:\path\to\file.yaml". It expects them to have // unix like separators, so "C:/path/to/file.yaml". From d42b031522bd8fa8493a2942761acc15a97c1c5b Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 12:22:53 +0200 Subject: [PATCH 05/12] fix: make sure segment client is closed correctly --- src/extension.ts | 4 +++- src/utils/telemetry.ts | 12 ++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index d72e0aa..55c7186 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,7 +15,7 @@ import { getTooltipContentDefault } from './utils/tooltip'; import { getLogoutCommand } from './commands/logout'; import { getAuthenticator } from './utils/authentication'; import { getSynchronizer } from './utils/synchronization'; -import { trackEvent } from './utils/telemetry'; +import { closeClient, trackEvent } from './utils/telemetry'; import logger from './utils/logger'; import globals from './utils/globals'; import type { ExtensionContext } from 'vscode'; @@ -192,6 +192,8 @@ export async function activate(context: ExtensionContext): Promise { export async function deactivate() { logger.log('Deactivating extension...'); + await closeClient(); + if (runtimeContext) { runtimeContext.dispose(); runtimeContext.authenticator.removeAllListeners(); diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts index 860a1d7..b951c4f 100644 --- a/src/utils/telemetry.ts +++ b/src/utils/telemetry.ts @@ -8,7 +8,7 @@ let client: Analytics | undefined; const getSegmentClient = () => { if (!env.isTelemetryEnabled || !globals.telemetryEnabled ) { - client = undefined; + closeClient(); return undefined; } @@ -28,7 +28,7 @@ const enableSegment = () => { const machineId: string = machineIdSync(); -export const trackEvent = (eventName: TEvent, payload?: EventMap[TEvent]) => { +export function trackEvent(eventName: TEvent, payload?: EventMap[TEvent]) { const segmentClient = getSegmentClient(); logger.log('Track event', machineId, eventName, payload); @@ -40,6 +40,14 @@ export const trackEvent = (eventName: TEvent, payload?: Ev }); }; +export async function closeClient() { + if (client) { + logger.log('Close Segment client'); + await client.closeAndFlush(); + client = undefined; + } +} + export type EventStatus = 'started' | 'success' | 'failure' | 'cancelled'; export type BaseEvent = {status: EventStatus, error?: string}; From 9f73666489fdb64683ca19bf957c9e134555b9ae Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 15:00:48 +0200 Subject: [PATCH 06/12] feat: add install and session events --- src/extension.ts | 13 ++++- src/utils/telemetry-client.ts | 29 ++++++++++ src/utils/telemetry.ts | 106 ++++++++++++++++++++++++---------- 3 files changed, 115 insertions(+), 33 deletions(-) create mode 100644 src/utils/telemetry-client.ts diff --git a/src/extension.ts b/src/extension.ts index 55c7186..7c38f8a 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,7 +15,7 @@ import { getTooltipContentDefault } from './utils/tooltip'; import { getLogoutCommand } from './commands/logout'; import { getAuthenticator } from './utils/authentication'; import { getSynchronizer } from './utils/synchronization'; -import { closeClient, trackEvent } from './utils/telemetry'; +import { trackEvent, initTelemetry, closeTelemetry } from './utils/telemetry'; import logger from './utils/logger'; import globals from './utils/globals'; import type { ExtensionContext } from 'vscode'; @@ -71,10 +71,12 @@ export async function activate(context: ExtensionContext): Promise { }); if (enabled) { + await initTelemetry(); await runtimeContext.policyPuller.refresh(); await commands.executeCommand(COMMANDS.VALIDATE); await commands.executeCommand(COMMANDS.WATCH); } else { + await closeTelemetry(); await runtimeContext.dispose(); } } @@ -116,6 +118,12 @@ export async function activate(context: ExtensionContext): Promise { name: SETTINGS.TELEMETRY_ENABLED, value: String(globals.telemetryEnabled), }); + + if (globals.telemetryEnabled) { + await initTelemetry(); + } else { + await closeTelemetry(); + } } }); @@ -180,6 +188,7 @@ export async function activate(context: ExtensionContext): Promise { return; } + await initTelemetry(); await runtimeContext.policyPuller.refresh(); await commands.executeCommand(COMMANDS.VALIDATE); await commands.executeCommand(COMMANDS.WATCH); @@ -192,7 +201,7 @@ export async function activate(context: ExtensionContext): Promise { export async function deactivate() { logger.log('Deactivating extension...'); - await closeClient(); + await closeTelemetry(); if (runtimeContext) { runtimeContext.dispose(); diff --git a/src/utils/telemetry-client.ts b/src/utils/telemetry-client.ts new file mode 100644 index 0000000..13b27b9 --- /dev/null +++ b/src/utils/telemetry-client.ts @@ -0,0 +1,29 @@ +import {Analytics} from '@segment/analytics-node'; +import {env} from 'vscode'; +import logger from './logger'; +import globals from './globals'; + +let client: Analytics | undefined; + +export function getSegmentClient() { + if (!env.isTelemetryEnabled || !globals.telemetryEnabled ) { + return undefined; + } + + if (!client) { + if (process.env.SEGMENT_API_KEY) { + logger.log('Enabled Segment'); + client = new Analytics({writeKey: process.env.SEGMENT_API_KEY}); + } + } + + return client; +} + +export async function closeSegmentClient() { + if (client) { + logger.log('Close Segment client'); + await client.closeAndFlush(); + client = undefined; + } +} diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts index b951c4f..4b2c416 100644 --- a/src/utils/telemetry.ts +++ b/src/utils/telemetry.ts @@ -1,32 +1,59 @@ -import {machineIdSync} from 'node-machine-id'; -import {Analytics} from '@segment/analytics-node'; -import {env} from 'vscode'; +import os from 'os'; +import { machineIdSync } from 'node-machine-id'; +import { join, normalize } from 'path'; +import { mkdir, writeFile, readFile } from 'fs/promises'; +import { closeSegmentClient, getSegmentClient } from './telemetry-client'; import logger from './logger'; import globals from './globals'; +import { extensions } from 'vscode'; -let client: Analytics | undefined; +let sessionTimeStart : number; -const getSegmentClient = () => { - if (!env.isTelemetryEnabled || !globals.telemetryEnabled ) { - closeClient(); - return undefined; - } +const machineId: string = machineIdSync(); - if (!client) { - enableSegment(); - } +// Should be called on extension start or when extension or telemetry gets enabled. +export async function initTelemetry() { + sessionTimeStart = Date.now(); - return client; -}; + const storedMachineId = await readMachineId(); + if (!storedMachineId) { + await saveMachineId(machineId); + + const segmentClient = getSegmentClient(); + + logger.log('Identify user', machineId); + + segmentClient?.identify({ + userId: machineId, + }); -const enableSegment = () => { - if (process.env.SEGMENT_API_KEY) { - logger.log('Enabled Segment'); - client = new Analytics({writeKey: process.env.SEGMENT_API_KEY}); + trackEvent('ext/installed', { + status: 'success', + appVersion: getAppVersion(), + deviceOS: os.platform(), + }); } -}; -const machineId: string = machineIdSync(); + trackEvent('ext/session', { + status: 'success', + appVersion: getAppVersion(), + startTimeMs: sessionTimeStart, + }); +} + +// Should be called on extension stop, when extension gets disabled or when telemetry gets disabled. +export async function closeTelemetry() { + const sessionTimeEnd = Date.now(); + trackEvent('ext/session_end', { + status: 'success', + endTimeMs: sessionTimeEnd, + timeSpentSec: Math.ceil((sessionTimeEnd - sessionTimeStart) / 1000), + }); + + sessionTimeStart = 0; + + return closeSegmentClient(); +} export function trackEvent(eventName: TEvent, payload?: EventMap[TEvent]) { const segmentClient = getSegmentClient(); @@ -38,14 +65,6 @@ export function trackEvent(eventName: TEvent, payload?: Ev properties: payload, userId: machineId, }); -}; - -export async function closeClient() { - if (client) { - logger.log('Close Segment client'); - await client.closeAndFlush(); - client = undefined; - } } export type EventStatus = 'started' | 'success' | 'failure' | 'cancelled'; @@ -53,9 +72,9 @@ export type BaseEvent = {status: EventStatus, error?: string}; export type Event = keyof EventMap; export type EventMap = { - 'ext/installed': BaseEvent & {appVersion: string; deviceOS: string}; - 'ext/session': BaseEvent & {appVersion: string}; - 'ext/session_end': BaseEvent & {timeSpent: number}; + 'ext/installed': BaseEvent & {appVersion: string; deviceOS: NodeJS.Platform}; + 'ext/session': BaseEvent & {appVersion: string, startTimeMs: number}; + 'ext/session_end': BaseEvent & {endTimeMs: number, timeSpentSec: number}; 'command/login': BaseEvent & {method?: string}; 'command/logout': BaseEvent; 'command/validate': BaseEvent & {rootCount?: number}; @@ -77,3 +96,28 @@ export type EventMap = { // When policy is synced from Monokle Cloud. 'policy/synchronize': BaseEvent & {errorCode?: string}; }; + +function getAppVersion(): string { + return extensions.getExtension('kubeshop.monokle')?.packageJSON?.version ?? 'unknown'; +} + +async function readMachineId(): Promise { + try { + const filePath = normalize(join(globals.storagePath, `machine.id`)); + const machineId = await readFile(filePath); + return machineId.toString().trim(); + } catch (e) { + logger.error('Failed to read machine id', e); + return null; + } +} + +async function saveMachineId(machineId: string) { + try { + await mkdir(globals.storagePath, { recursive: true }); + const filePath = normalize(join(globals.storagePath, `machine.id`)); + await writeFile(filePath, machineId); + } catch (e) { + logger.error('Failed to save machine id', e); + } +} From 1ebc3681ef9ffe73a9010d911bc4eafb3fd37334 Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 15:43:02 +0200 Subject: [PATCH 07/12] fix: create proper telemetry setup --- CONTRIBUTING.md | 2 ++ src/config.ts | 1 + src/utils/telemetry-client.ts | 5 +++-- src/utils/telemetry.ts | 17 ++++++++--------- 4 files changed, 14 insertions(+), 11 deletions(-) create mode 100644 src/config.ts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3bdabaa..8d90b79 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,8 @@ And then using _Install from VSIX_ option in VSC. Releasing requires [`@vscode/vsce`](https://www.npmjs.com/package/@vscode/vsce) package installed. +**IMPORTANT**: To keep telemetry working, before running any `vsce` command, please update `SEGMENT_API_KEY` to correct value for a time of builidng the extension (DO NOT COMMIT THOUGH!). + After that, run: ```bash diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..0eea38b --- /dev/null +++ b/src/config.ts @@ -0,0 +1 @@ +export const SEGMENT_API_KEY = ''; diff --git a/src/utils/telemetry-client.ts b/src/utils/telemetry-client.ts index 13b27b9..ce56ff0 100644 --- a/src/utils/telemetry-client.ts +++ b/src/utils/telemetry-client.ts @@ -1,5 +1,6 @@ import {Analytics} from '@segment/analytics-node'; import {env} from 'vscode'; +import {SEGMENT_API_KEY} from '../config'; import logger from './logger'; import globals from './globals'; @@ -11,9 +12,9 @@ export function getSegmentClient() { } if (!client) { - if (process.env.SEGMENT_API_KEY) { + if (SEGMENT_API_KEY) { logger.log('Enabled Segment'); - client = new Analytics({writeKey: process.env.SEGMENT_API_KEY}); + client = new Analytics({writeKey: SEGMENT_API_KEY}); } } diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts index 4b2c416..a674791 100644 --- a/src/utils/telemetry.ts +++ b/src/utils/telemetry.ts @@ -1,7 +1,7 @@ import os from 'os'; -import { machineIdSync } from 'node-machine-id'; import { join, normalize } from 'path'; import { mkdir, writeFile, readFile } from 'fs/promises'; +import { env } from 'vscode'; import { closeSegmentClient, getSegmentClient } from './telemetry-client'; import logger from './logger'; import globals from './globals'; @@ -9,22 +9,20 @@ import { extensions } from 'vscode'; let sessionTimeStart : number; -const machineId: string = machineIdSync(); - // Should be called on extension start or when extension or telemetry gets enabled. export async function initTelemetry() { sessionTimeStart = Date.now(); const storedMachineId = await readMachineId(); if (!storedMachineId) { - await saveMachineId(machineId); + await saveMachineId(env.machineId); const segmentClient = getSegmentClient(); - logger.log('Identify user', machineId); + logger.log('Identify user', env.machineId); segmentClient?.identify({ - userId: machineId, + userId: env.machineId, }); trackEvent('ext/installed', { @@ -57,13 +55,14 @@ export async function closeTelemetry() { export function trackEvent(eventName: TEvent, payload?: EventMap[TEvent]) { const segmentClient = getSegmentClient(); + const eventPayload = { ...payload, sessionId: env.sessionId }; - logger.log('Track event', machineId, eventName, payload); + logger.log('Track event', env.machineId, eventName, eventPayload); segmentClient?.track({ event: eventName, - properties: payload, - userId: machineId, + properties: eventPayload, + userId: env.machineId, }); } From 623118562b2d9197d562613eaa9d3d0abdd4695e Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 16:38:13 +0200 Subject: [PATCH 08/12] fix: remove unused deps --- package-lock.json | 6 ------ package.json | 1 - 2 files changed, 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6d49e8f..10e6335 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "@monokle/synchronizer": "^0.2.0", "@monokle/validation": "^0.25.2", "@segment/analytics-node": "^1.1.0", - "node-machine-id": "^1.1.12", "uuid": "^9.0.0", "yaml": "^2.3.1" }, @@ -4107,11 +4106,6 @@ } } }, - "node_modules/node-machine-id": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/node-machine-id/-/node-machine-id-1.1.12.tgz", - "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==" - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", diff --git a/package.json b/package.json index e26716d..ad9464f 100644 --- a/package.json +++ b/package.json @@ -158,7 +158,6 @@ "@monokle/synchronizer": "^0.2.0", "@monokle/validation": "^0.25.2", "@segment/analytics-node": "^1.1.0", - "node-machine-id": "^1.1.12", "uuid": "^9.0.0", "yaml": "^2.3.1" }, From 255a67497d9b735c72bb7ed2e1eb0af01f9b5b1c Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 16:49:07 +0200 Subject: [PATCH 09/12] chore: update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index be85ece..3fbc1d5 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ This extension contributes the following settings: * `monokle.configurationPath` - Set path to validation configuration file. * `monokle.verbose` - Log runtime info to VSC Developer Console. * `monokle.overwriteRemotePolicyUrl` - Overwrite default Monokle Cloud URL to fetch policies from. +* `monokle.telemetryEnabled` - Enable anonymous telemetry. ## Dependencies From 2db8d5ec9b2187186b5a2edad41ff62203e826d5 Mon Sep 17 00:00:00 2001 From: f1ames Date: Mon, 21 Aug 2023 16:57:38 +0200 Subject: [PATCH 10/12] chore: apply minor fixes --- CONTRIBUTING.md | 2 +- src/commands/show-panel.ts | 2 +- src/utils/telemetry.ts | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8d90b79..d8adb2c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,7 +42,7 @@ And then using _Install from VSIX_ option in VSC. Releasing requires [`@vscode/vsce`](https://www.npmjs.com/package/@vscode/vsce) package installed. -**IMPORTANT**: To keep telemetry working, before running any `vsce` command, please update `SEGMENT_API_KEY` to correct value for a time of builidng the extension (DO NOT COMMIT THOUGH!). +**IMPORTANT**: To keep telemetry working, before running any `vsce` command, please update `SEGMENT_API_KEY` in `src/config.ts` to correct value for a time of building the extension (DO NOT COMMIT THOUGH!). After that, run: diff --git a/src/commands/show-panel.ts b/src/commands/show-panel.ts index 0c468c1..a871ca3 100644 --- a/src/commands/show-panel.ts +++ b/src/commands/show-panel.ts @@ -24,4 +24,4 @@ export function getShowPanelCommand() { status: 'success' }); }; -} \ No newline at end of file +} diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts index a674791..12416fa 100644 --- a/src/utils/telemetry.ts +++ b/src/utils/telemetry.ts @@ -57,9 +57,13 @@ export function trackEvent(eventName: TEvent, payload?: Ev const segmentClient = getSegmentClient(); const eventPayload = { ...payload, sessionId: env.sessionId }; + if (!segmentClient) { + return; + } + logger.log('Track event', env.machineId, eventName, eventPayload); - segmentClient?.track({ + segmentClient.track({ event: eventName, properties: eventPayload, userId: env.machineId, From 992fc15ce37a2c680b98a446ac92d063b5d23892 Mon Sep 17 00:00:00 2001 From: f1ames Date: Tue, 22 Aug 2023 12:51:42 +0200 Subject: [PATCH 11/12] fix: fix failing platform data request --- src/utils/telemetry.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/telemetry.ts b/src/utils/telemetry.ts index 12416fa..c72b193 100644 --- a/src/utils/telemetry.ts +++ b/src/utils/telemetry.ts @@ -1,4 +1,4 @@ -import os from 'os'; +import { platform } from 'node:os'; import { join, normalize } from 'path'; import { mkdir, writeFile, readFile } from 'fs/promises'; import { env } from 'vscode'; @@ -28,7 +28,7 @@ export async function initTelemetry() { trackEvent('ext/installed', { status: 'success', appVersion: getAppVersion(), - deviceOS: os.platform(), + deviceOS: platform(), }); } From abf31c1a06c4976d5dfd0fe6b497518e34b3287b Mon Sep 17 00:00:00 2001 From: f1ames Date: Tue, 22 Aug 2023 12:55:08 +0200 Subject: [PATCH 12/12] test: adjust cc threshold --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ad9464f..1b7bb0f 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,7 @@ "test": "concurrently -s command-1 -k \"npm run test:server\" \"node ./out/test/run-test.js\"", "test:cc": "concurrently -s command-1 -k \"npm run test:server\" \"c8 node ./out/test/run-test.js\"", "test:server": "node ./out/test/run-server.js", - "test:ensure-coverage": "npx c8 check-coverage --lines 70 --functions 80 --branches 80 --statements 70" + "test:ensure-coverage": "npx c8 check-coverage --lines 70 --functions 80 --branches 75 --statements 70" }, "devDependencies": { "@graphql-tools/mock": "^9.0.0",