diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 460f7ed3..e9236ec6 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -38,6 +38,10 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install pnpm modules run: pnpm install + - name: Create a .env file in ide-extension package + run: | + touch packages/ide-extension/.env + echo KHE_TELEMETRY_INSTRUMENTATION_KEY=${{ secrets.INSTRUMENTATION_KEY }} >> packages/ide-extension/.env - name: Run build run: pnpm run build - name: Run linting diff --git a/.vscode/launch.json b/.vscode/launch.json index be87fd9d..9855320e 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,17 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": "Knowledge Hub extension - Launch Knowledge Hub extension", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": ["--log=debug", "--extensionDevelopmentPath=${workspaceFolder}/packages/ide-extension/"], - "outFiles": ["${workspaceFolder}/pacakges/ide-extension/dist/**/*.js"], - "preLaunchTask": "extension-watch", - "postDebugTask": "Terminate All Tasks" - }, - { - "name": "Knowledge Hub extension - webapp: Run Current Jest File", + "name": "core: Run current jest file", "type": "node", "request": "launch", "program": "${workspaceFolder}/node_modules/jest/bin/jest", @@ -25,10 +15,20 @@ "windows": { "program": "${workspaceFolder}/node_modules/jest/bin/jest" }, - "cwd": "${workspaceFolder}/packages/webapp" + "cwd": "${workspaceFolder}/packages/core" }, { - "name": "Knowledge Hub extension - ide-extension: Run Current Jest File", + "name": "ide-extension: Launch extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--log=debug", "--extensionDevelopmentPath=${workspaceFolder}/packages/ide-extension/"], + "outFiles": ["${workspaceFolder}/pacakges/ide-extension/dist/**/*.js"], + "preLaunchTask": "extension-watch", + "postDebugTask": "Terminate All Tasks" + }, + { + "name": "ide-extension: Run current jest file", "type": "node", "request": "launch", "program": "${workspaceFolder}/node_modules/jest/bin/jest", @@ -41,7 +41,7 @@ "cwd": "${workspaceFolder}/packages/ide-extension" }, { - "name": "Knowledge Hub extension - core: Run Current Jest File", + "name": "webapp: Run current jest file", "type": "node", "request": "launch", "program": "${workspaceFolder}/node_modules/jest/bin/jest", @@ -51,7 +51,7 @@ "windows": { "program": "${workspaceFolder}/node_modules/jest/bin/jest" }, - "cwd": "${workspaceFolder}/packages/core" + "cwd": "${workspaceFolder}/packages/webapp" } ] } diff --git a/packages/ide-extension/esbuild.js b/packages/ide-extension/esbuild.js index 2a3ab6f8..68664c19 100644 --- a/packages/ide-extension/esbuild.js +++ b/packages/ide-extension/esbuild.js @@ -20,7 +20,7 @@ const buildConfig = { '.svg': 'file' }, platform: 'node', - target: 'node12.22', + target: 'node16.1', external: [ 'vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.js.org/configuration/externals/ ], diff --git a/packages/ide-extension/package.json b/packages/ide-extension/package.json index 1e181982..ede1e2ff 100644 --- a/packages/ide-extension/package.json +++ b/packages/ide-extension/package.json @@ -44,6 +44,19 @@ "onCommand:sap.ux.knowledgeHub.openKnowledgeHub" ], "contributes": { + "configuration": { + "id": "sap.ux.knowledgeHub", + "type": "object", + "title": "%configuration.title%", + "properties": { + "sap.ux.knowledgeHub.telemetryEnabled": { + "type": "boolean", + "description": "%configuration.telemetryEnabled%", + "default": true, + "scope": "window" + } + } + }, "commands": [ { "command": "sap.ux.knowledgeHub.openKnowledgeHub", @@ -55,15 +68,19 @@ "devDependencies": { "@sap/knowledge-hub-extension-core": "workspace:*", "@sap/knowledge-hub-extension-types": "workspace:*", + "@types/uuid": "9.0.1", "@types/vscode": "1.39.0", + "@vscode/vsce": "2.19.0", + "applicationinsights": "2.5.1", + "axios": "1.3.4", + "dotenv": "16.0.3", "esbuild": "0.15.12", "esbuild-plugin-copy": "2.1.0", "i18next": "22.4.15", "jsdom": "21.1.0", - "vsce": "2.15.0", - "vscode-uri": "3.0.7", - "axios": "1.3.4", - "qs": "6.11.1" + "qs": "6.11.1", + "uuid": "9.0.0", + "vscode-uri": "3.0.7" }, "galleryBanner": { "color": "#00195A", diff --git a/packages/ide-extension/package.nls.json b/packages/ide-extension/package.nls.json index bea59a21..0a3099ca 100644 --- a/packages/ide-extension/package.nls.json +++ b/packages/ide-extension/package.nls.json @@ -1,6 +1,7 @@ { "knowledgeHub.displayName": "Knowledge Hub extension by SAP", "knowledgeHub.description": "Knowledge Hub extension by SAP", + "configuration.telemetryEnabled": "If enabled, telemetry events to improve extension quality will be recorded", "commands.knowledgeHub.category": "SAP", "commands.knowledgeHub.openKnowledgeHub.title": "Open Knowledge Hub" } diff --git a/packages/ide-extension/src/extension.ts b/packages/ide-extension/src/extension.ts index b40f93d7..105c46b6 100644 --- a/packages/ide-extension/src/extension.ts +++ b/packages/ide-extension/src/extension.ts @@ -4,13 +4,18 @@ import type { ExtensionContext } from 'vscode'; import { KnowledgeHubPanel } from './panels/knowledgeHubPanel'; import { logString } from './logger/logger'; import { initI18n } from './i18n'; - +import { initTelemetry } from './telemetry'; /** * Activate function is called by VSCode when the extension gets active. * * @param {ExtensionContext} context - context from VSCode */ export async function activate(context: ExtensionContext): Promise { + try { + context.subscriptions.push(initTelemetry()); + } catch (error) { + logString(`Error during initialization of telemetry: ${(error as Error)?.message}`); + } // Initialize i18next await initI18n(); diff --git a/packages/ide-extension/src/panels/knowledgeHubPanel.ts b/packages/ide-extension/src/panels/knowledgeHubPanel.ts index 655fce67..22ef004d 100644 --- a/packages/ide-extension/src/panels/knowledgeHubPanel.ts +++ b/packages/ide-extension/src/panels/knowledgeHubPanel.ts @@ -2,13 +2,16 @@ import type { WebviewPanel, ExtensionContext } from 'vscode'; import { Uri, ViewColumn, window } from 'vscode'; import i18next from 'i18next'; -import { RESTART_WEBVIEW } from '@sap/knowledge-hub-extension-types'; +import { RESTART_WEBVIEW, LOG_TELEMETRY_EVENT } from '@sap/knowledge-hub-extension-types'; +import type { KnowledgeHubActions } from '@sap/knowledge-hub-extension-types'; import { MessageHandler } from '../knowledge-hub/messageHandler'; import { AppSession } from '../knowledge-hub/appSession'; import { getHtml, getWebviewUri } from '../utils/web'; import { Storage } from '../utils/storage'; import { errorInstance } from '../utils/error'; +import { trackAction } from '../telemetry'; +import { logString } from '../logger/logger'; /** * A class to handle the knowledge hub extension panel. @@ -79,7 +82,7 @@ export class KnowledgeHubPanel { ); const uri: string = getWebviewUri(knowledgeHubWebView.webview, viewRootUri).toString(); - + knowledgeHubWebView.webview.onDidReceiveMessage(this.onWebviewMessage.bind(this)); knowledgeHubWebView.webview.html = getHtml( uri.toString(), i18next.t('KNOWLEDGE_HUB_VIEW_TITLE'), @@ -87,21 +90,38 @@ export class KnowledgeHubPanel { undefined, '/knowledgeHub.css' ); - - // Register for incoming messages from web view - knowledgeHubWebView.webview.onDidReceiveMessage(async (action) => { - await this.messageHandler.processRequestAction(action); - // Special case when restart is requested - if (action.type === RESTART_WEBVIEW) { - this.panel.dispose(); - this.panel = this.createKnowledgeHubWebview(); - } - }); - return knowledgeHubWebView; } public show(): void { this.panel.reveal(); } + + /** + * + * @param action + */ + private async onWebviewMessage(action: KnowledgeHubActions): Promise { + try { + await this.messageHandler.processRequestAction(action); + switch (action.type) { + case RESTART_WEBVIEW: { + this.panel.dispose(); + this.panel = this.createKnowledgeHubWebview(); + break; + } + case LOG_TELEMETRY_EVENT: { + await trackAction(action); + break; + } + default: { + // Nothing to do if the action is not handled + } + } + } catch (error: any) { + logString( + `Error while processing action.\n Action: ${JSON.stringify(action)}\n Message: ${error?.message}` + ); + } + } } diff --git a/packages/ide-extension/src/telemetry/action-map.ts b/packages/ide-extension/src/telemetry/action-map.ts new file mode 100644 index 00000000..b7e6271c --- /dev/null +++ b/packages/ide-extension/src/telemetry/action-map.ts @@ -0,0 +1,23 @@ +import type { LogTelemetryEvent } from '@sap/knowledge-hub-extension-types'; + +import { KHUB_OPEN_BLOGS, KHUB_OPEN_TUTORIAL } from '@sap/knowledge-hub-extension-types'; +import type { TelemetryUIOpenBlogProps, TelemetryUIOpenTutorialProps } from './types'; + +/** + * Map redux action -> telemetry event properties + * Requires respective redux action to be in allowedTelemetryActions in packages/webapp/src/webview/state/middleware.ts + */ +export const actionMap: { + [action: string]: (action: LogTelemetryEvent) => any; +} = { + [KHUB_OPEN_TUTORIAL]: (action: LogTelemetryEvent): TelemetryUIOpenTutorialProps => ({ + action: 'OPEN_TUTORIAL', + title: action.payload?.title as string, + primaryTag: action.payload?.primaryTag as string + }), + [KHUB_OPEN_BLOGS]: (action: LogTelemetryEvent): TelemetryUIOpenBlogProps => ({ + action: 'OPEN_BLOG', + title: action.payload?.title as string, + primaryTag: action.payload?.primaryTag as string + }) +}; diff --git a/packages/ide-extension/src/telemetry/index.ts b/packages/ide-extension/src/telemetry/index.ts new file mode 100644 index 00000000..58bcf51f --- /dev/null +++ b/packages/ide-extension/src/telemetry/index.ts @@ -0,0 +1 @@ +export { initTelemetry, setCommonProperties, trackAction, trackEvent } from './telemetry'; diff --git a/packages/ide-extension/src/telemetry/telemetry.ts b/packages/ide-extension/src/telemetry/telemetry.ts new file mode 100644 index 00000000..5c0014e1 --- /dev/null +++ b/packages/ide-extension/src/telemetry/telemetry.ts @@ -0,0 +1,138 @@ +import { platform, arch, release } from 'os'; +import { env, workspace } from 'vscode'; +import type { Disposable } from 'vscode'; +import { TelemetryClient } from 'applicationinsights'; +import { logString } from '../logger/logger'; +import packageJson from '../../package.json'; +import type { TelemetryEvent, TelemetryReporter } from './types'; +import { config as dotenvConfig } from 'dotenv'; +import { join } from 'path'; +import { v4 as uuidv4 } from 'uuid'; +import { actionMap } from './action-map'; + +// Telemetry reporter client +let reporter: TelemetryReporter | undefined; + +/** + * Initialize telemetry. + * + * @returns - telemetry reporter + */ +export function initTelemetry(): TelemetryReporter { + if (!reporter) { + dotenvConfig({ path: join(__dirname, '../', `.env`) }); + const instrumentationKey = process.env.KHE_TELEMETRY_INSTRUMENTATION_KEY; + if (!instrumentationKey) { + logString('Instrumentation key missing in .env file'); + } + const enabled = updateTelemetryStatus(); + const client = new TelemetryClient(instrumentationKey); + client.channel.setUseDiskRetryCaching(true); + client.context.tags[client.context.keys.userId] = env.machineId; + client.context.tags[client.context.keys.sessionId] = uuidv4(); + client.context.tags[client.context.keys.cloudRole] = env.appName; + + const disposables: Disposable[] = []; + disposables.push(workspace.onDidChangeConfiguration(() => updateTelemetryStatus())); + reporter = { + client, + enabled, + dispose: () => { + disposables.forEach((d) => d.dispose()); + reporter = undefined; + } + }; + } + return reporter; +} + +/** + * Update the telemetry setting by reading configuration. + * + * @returns - status of telemetry setting, true: enabled; false: disabled + */ +function updateTelemetryStatus(): boolean { + const enabled = !!workspace.getConfiguration('sap.ux.knowledgeHub').get('telemetryEnabled'); + if (reporter) { + reporter.enabled = enabled; + } + return enabled; +} + +/** + * Set common properties which will be added to every telemetry event. + * If called without properties, all common properties are removed. + * + * @param properties - name/value pair of properties (optional) + * @param properties.ide - development environment VSCODE or SBAS + * @param properties.sbasdevSpace - SBAS devspace + */ +export function setCommonProperties(properties?: { ide: 'VSCODE' | 'SBAS'; sbasdevSpace: string }) { + if (reporter) { + reporter.commonProperties = properties + ? { + 'cmn.appstudio': properties.ide === 'SBAS' ? 'true' : 'false', + 'cmn.devspace': properties.sbasdevSpace, + 'common.os': platform(), + 'common.nodeArch': arch(), + 'common.platformversion': (release() || '').replace(/^(\d+)(\.\d+)?(\.\d+)?(.*)/, '$1$2$3'), + 'common.extname': packageJson.name, + 'common.extversion': packageJson.version + } + : undefined; + } +} + +/** + * Track an even using telemetry. + * + * @param event - telemetry event + */ +export async function trackEvent(event: TelemetryEvent): Promise { + if (!reporter?.enabled) { + return; + } + try { + const name = `${packageJson.name}/${event.name}`; + const properties = propertyValuesToString({ ...event.properties, ...(reporter.commonProperties ?? {}) }); + reporter.client.trackEvent({ name, properties }); + } catch (error) { + logString(`Error sending telemetry event '${event.name}': ${(error as Error).message}`); + } +} + +/** + * Map specified redux actions to to telemetry events and track them. + * + * @param action - action that occurred + */ +export async function trackAction(action: any): Promise { + if (!reporter?.enabled) { + return; + } + try { + if (actionMap[action.payload.action.type]) { + const properties = actionMap[action.payload.action.type](action.payload.action); + await trackEvent({ name: 'USER_INTERACTION', properties }); + } + } catch (error) { + logString(`Error sending telemetry action '${action?.payload?.action?.type}': ${(error as Error).message}`); + } +} + +/** + * Ensure all property values are strings. While type TelemetryEventProperties defines the values + * as string | number | any, the call LogTelemetryEvent() throws an exception if a non-string + * property value is passed. + * + * @param properties - key/value map of properties + * @returns - key/value map where all values are strings + */ +function propertyValuesToString(properties: { [key: string]: any }): { [key: string]: string } { + for (const property in properties) { + if (typeof properties[property] !== 'string') { + properties[property] = properties[property].toString(); + } + } + return properties; +} diff --git a/packages/ide-extension/src/telemetry/types.ts b/packages/ide-extension/src/telemetry/types.ts new file mode 100644 index 00000000..c066827e --- /dev/null +++ b/packages/ide-extension/src/telemetry/types.ts @@ -0,0 +1,51 @@ +import type { Disposable } from 'vscode'; +import type { TelemetryClient } from 'applicationinsights'; + +export interface TelemetryReporter extends Disposable { + client: TelemetryClient; + commonProperties?: TelemetryCommonProperties; + enabled: boolean; +} + +export interface TelemetryEventProperties { + readonly [key: string]: string; +} + +export type TelemetryEvent = TelemetryUIEvent; + +interface TelemetryBaseEvent { + name: string; + properties?: TelemetryEventProperties; +} + +export interface TelemetryCommonProperties extends TelemetryEventProperties { + 'cmn.appstudio': 'true' | 'false'; + 'cmn.devspace': string; + 'common.os': string; + 'common.nodeArch': string; + 'common.platformversion': string; + 'common.extname': string; + 'common.extversion': string; +} + +export interface TelemetryUIEvent extends TelemetryBaseEvent { + name: 'USER_INTERACTION'; + properties: TelemetryUIEventProps; +} + +export interface TelemetryUIEventProps extends TelemetryEventProperties { + action: string; + [prop: string]: string; +} + +export interface TelemetryUIOpenTutorialProps extends TelemetryUIEventProps { + action: 'OPEN_TUTORIAL'; + title: string; + primaryTag: string; +} + +export interface TelemetryUIOpenBlogProps extends TelemetryUIEventProps { + action: 'OPEN_BLOG'; + title: string; + primaryTag: string; +} diff --git a/packages/ide-extension/test/unit/panels/knowledgeHubPanel.test.ts b/packages/ide-extension/test/unit/panels/knowledgeHubPanel.test.ts index 4c5e6bd1..268a60a8 100644 --- a/packages/ide-extension/test/unit/panels/knowledgeHubPanel.test.ts +++ b/packages/ide-extension/test/unit/panels/knowledgeHubPanel.test.ts @@ -1,7 +1,24 @@ +import { KnowledgeHubActions, RESTART_WEBVIEW } from '@sap/knowledge-hub-extension-types'; import vscode from 'vscode'; +import { window } from 'vscode'; +import type { WebviewPanel } from 'vscode'; import { initI18n } from '../../../src/i18n'; - import { KnowledgeHubPanel } from '../../../src/panels/knowledgeHubPanel'; +type WebviewMessageCallback = (action: KnowledgeHubActions) => void; +const getWebViewPanelMock = (onDidReceiveMessage: (callback: WebviewMessageCallback) => void) => +({ + webview: { + message: '', + html: '', + onDidReceiveMessage, + asWebviewUri: jest.fn().mockReturnValue(''), + cspSource: '', + postMessage: jest.fn() + }, + onDidChangeViewState: jest.fn(), + onDidDispose: jest.fn(), + reveal: jest.fn() +} as unknown as WebviewPanel); describe('knowledgeHubPanel', () => { const values: { [key: string]: unknown } = {}; @@ -31,4 +48,23 @@ describe('knowledgeHubPanel', () => { const webviewPanel = knowledgeHubPanel.createKnowledgeHubWebview(); expect(webviewPanel).toBeDefined(); }); + + test('KnowledgeHub communication RESTART_WEBVIEW', async () => { + // Mock setup + let onDidReceiveMessageMock: WebviewMessageCallback = () => { }; + const webViewPanelMock = getWebViewPanelMock((callback: WebviewMessageCallback) => { + onDidReceiveMessageMock = callback; + }); + jest.spyOn(window, 'createWebviewPanel').mockImplementation(() => webViewPanelMock); + + // Test execution + const panel = new KnowledgeHubPanel(extensionContext); + panel.show(); + await onDidReceiveMessageMock({ + type: RESTART_WEBVIEW + }); + + // Result check + expect(panel.createKnowledgeHubWebview()).toBeDefined(); + }); }); diff --git a/packages/ide-extension/test/unit/telemetry/telemetry.test.ts b/packages/ide-extension/test/unit/telemetry/telemetry.test.ts new file mode 100644 index 00000000..1243fcb0 --- /dev/null +++ b/packages/ide-extension/test/unit/telemetry/telemetry.test.ts @@ -0,0 +1,195 @@ +import * as os from 'os'; +import { workspace, ConfigurationChangeEvent, Disposable, ExtensionContext } from 'vscode'; +import { initTelemetry, setCommonProperties, trackAction, trackEvent } from '../../../src/telemetry/telemetry'; +import { LOG_TELEMETRY_EVENT } from '@sap/knowledge-hub-extension-types'; +import type { TelemetryEvent, TelemetryReporter } from '../../../src/telemetry/types'; +import packageJson from '../../../package.json'; +import { KHUB_OPEN_BLOGS, KHUB_OPEN_TUTORIAL } from '../../../../types/dist/types'; + +jest.mock('applicationinsights', () => ({ + TelemetryClient: jest.fn().mockImplementation((key) => ({ + addTelemetryProcessor: jest.fn(), + channel: { setUseDiskRetryCaching: jest.fn() }, + context: { tags: {}, keys: {} }, + key, + trackEvent: jest.fn() + })) +})); +jest.mock('os'); +jest.spyOn(os, 'arch').mockImplementation(() => 'arch' as any); +jest.spyOn(os, 'platform').mockImplementation(() => 'platform' as any); +jest.spyOn(os, 'release').mockImplementation(() => '1.2.3release' as any); + +describe('Test for initTelemetry()', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('Init telemetry, enabled in config', () => { + // Mock setup + jest.spyOn(workspace, 'getConfiguration').mockReturnValue({ get: () => true } as any); + + // Test execution + const reporter = initTelemetry(); + + // Result check + expect(reporter.enabled).toBe(true); + expect(typeof reporter.dispose).toBe('function'); + }); +}); + +describe('Telemetry disabled', () => { + let reporter: TelemetryReporter; + + beforeEach(() => { + jest.clearAllMocks(); + reporter = initTelemetry(); + reporter.enabled = false; + }); + + test('Track event when telemetry is disabled, should not send anything', () => { + // Mock setup + jest.spyOn(workspace, 'getConfiguration').mockReturnValue({ get: () => false } as any); + + // Test execution + reporter = initTelemetry(); + trackEvent({ name: '', properties: {} } as unknown as TelemetryEvent); + + // Result check + expect(reporter.client.trackEvent).not.toBeCalled(); + }); + + test('Track action when telemetry is disabled, should not send anything', () => { + // Mock setup + jest.spyOn(workspace, 'getConfiguration').mockReturnValue({ get: () => false } as any); + + // Test execution + reporter = initTelemetry(); + trackAction(getDummyAction('')); + + // Result check + expect(reporter.client.trackEvent).not.toBeCalled(); + }); + + test('Toggle telemetry setting', () => { + // Mock setup + let enabled = true; + jest.spyOn(workspace, 'getConfiguration').mockReturnValue({ get: () => enabled } as any); + + // Test execution + reporter = initTelemetry(); + trackEvent({ name: '', properties: {} } as unknown as TelemetryEvent); + + // Result check + expect(reporter.enabled).toBe(false); + expect(reporter.client.trackEvent).not.toBeCalled(); + + // Enable telemetry + reporter.enabled = true; + let changeHandler: (e: ConfigurationChangeEvent) => any = () => { }; + jest.spyOn(workspace, 'onDidChangeConfiguration').mockImplementation( + (listener: (e: ConfigurationChangeEvent) => any) => { + changeHandler = listener; + return {} as Disposable; + } + ); + reporter = initTelemetry(); + enabled = true; + changeHandler({} as ConfigurationChangeEvent); + + // Test again + trackEvent({ name: '', properties: {} } as unknown as TelemetryEvent); + + // Result check + expect(reporter.enabled).toBe(true); + expect(reporter.client.trackEvent).toBeCalled(); + }); + test('Track action when telemetry is enabled for logging tutorials', () => { + // Test execution + reporter = initTelemetry(); + reporter.enabled = true; + trackAction(getDummyAction('')); + + // Result check + expect(reporter.client.trackEvent).toBeCalled(); + }); + + test('Track action when telemetry is enabled for logging blogs', () => { + // Test execution + reporter = initTelemetry(); + reporter.enabled = true; + trackAction(getDummyAction1('')); + + // Result check + expect(reporter.client.trackEvent).toBeCalledWith({ + name: 'sap-knowledge-hub-extension/USER_INTERACTION', + properties: { + action: 'OPEN_BLOG', + primaryTag: 'abc-def-fgh', + title: 'hello sap' + } + }); + }); +}); + +describe('Test for setCommonProperties()', () => { + let reporter: TelemetryReporter; + + beforeEach(() => { + jest.clearAllMocks(); + if (reporter) { + reporter.dispose(); + } + reporter = initTelemetry(); + }); + + test('Set common properties for VSCode, no release', () => { + jest.spyOn(os, 'release').mockImplementation(() => undefined as any); + setCommonProperties({ ide: 'VSCODE', sbasdevSpace: '' }); + expect(reporter.commonProperties).toEqual({ + 'cmn.appstudio': 'false', + 'cmn.devspace': '', + 'common.os': 'platform', + 'common.nodeArch': 'arch', + 'common.platformversion': '', + 'common.extname': packageJson.name, + 'common.extversion': packageJson.version + }); + }); +}); + +/** + * + * @param _actionName + */ +function getDummyAction(_actionName: string): any { + return { + type: LOG_TELEMETRY_EVENT, + payload: { + action: { + type: KHUB_OPEN_TUTORIAL, + payload: { + action: 'OPEN_TUTORIAL', + title: 'hello sap', + primaryTag: 'abc-def-fgh' + } + } + } + }; +} + +function getDummyAction1(_actionName: string): any { + return { + type: LOG_TELEMETRY_EVENT, + payload: { + action: { + type: KHUB_OPEN_BLOGS, + payload: { + action: 'OPEN_BLOG', + title: 'hello sap', + primaryTag: 'abc-def-fgh' + } + } + } + }; +} diff --git a/packages/types/src/types/actions.ts b/packages/types/src/types/actions.ts index 668c431c..b44ada65 100644 --- a/packages/types/src/types/actions.ts +++ b/packages/types/src/types/actions.ts @@ -1,5 +1,7 @@ import type { TutorialsSearchQuery, TutorialsTagWithTitle } from './tutorials.types'; import type { BlogsSearchQuery, BlogFiltersEntry } from './blogs.types'; +import type { KnowledgeHubOpenBlogPayload, KnowledgeHubOpenTutorialPayload } from './app.types'; + export interface PendingActions { [key: string]: boolean; } @@ -25,6 +27,22 @@ export const BLOGS_FETCH_BLOGS = 'BLOGS_FETCH_BLOGS'; export const TAGS_FETCH_TAGS = 'TAGS_FETCH_TAGS'; export const HOME_FETCH_TUTORIALS = 'HOME_FETCH_TUTORIALS'; export const HOME_FETCH_BLOGS = 'HOME_FETCH_BLOGS'; +export const LOG_TELEMETRY_EVENT = 'LOG_TELEMETRY_EVENT'; +export const KHUB_OPEN_TUTORIAL = 'KHUB_OPEN_TUTORIAL'; +export const KHUB_OPEN_BLOGS = 'KHUB_OPEN_BLOGS'; + +export type KnowledgeHubActions = RestartWebviewAction | KnowledgeHubTelemetryAction; + +export interface RestartWebviewAction { + type: typeof RESTART_WEBVIEW; +} + +export interface KnowledgeHubTelemetryAction { + type: typeof LOG_TELEMETRY_EVENT; + payload: KnowledgeHubTelemetryAllowedActions; +} + +export type KnowledgeHubTelemetryAllowedActions = KnowledgeHubOpenBlogPayload | KnowledgeHubOpenTutorialPayload; // Actions export interface KnowledgeHubWebViewReady { diff --git a/packages/types/src/types/app.types.ts b/packages/types/src/types/app.types.ts index b7e095c6..e8619d49 100644 --- a/packages/types/src/types/app.types.ts +++ b/packages/types/src/types/app.types.ts @@ -1,5 +1,6 @@ import type { BlogFiltersEntry } from './blogs.types'; import type { TutorialsTagWithTitle } from './tutorials.types'; +import type { KHUB_OPEN_BLOGS, KHUB_OPEN_TUTORIAL, KnowledgeHubTelemetryAllowedActions } from './actions'; export const SET_GLOBAL_SETTINGS = 'SET_GLOBAL_SETTINGS'; export const UPDATE_GLOBAL_SETTING = 'UPDATE_GLOBAL_SETTING'; @@ -38,3 +39,17 @@ export type TabsConfigEntry = { path: string; text: string; }; + +export interface LogTelemetryEvent { + type: typeof KHUB_OPEN_TUTORIAL | typeof KHUB_OPEN_BLOGS; + payload?: KnowledgeHubTelemetryAllowedActions; +} + +export interface KnowledgeHubOpenBlogPayload { + title: string; + primaryTag: string; +} +export interface KnowledgeHubOpenTutorialPayload { + title: string; + primaryTag: string; +} diff --git a/packages/webapp/src/webview/components/BlogCard/BlogCard.tsx b/packages/webapp/src/webview/components/BlogCard/BlogCard.tsx index 507fe047..088e2c2e 100644 --- a/packages/webapp/src/webview/components/BlogCard/BlogCard.tsx +++ b/packages/webapp/src/webview/components/BlogCard/BlogCard.tsx @@ -3,12 +3,17 @@ import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { UILoader, UIPersona, UIPersonaSize } from '@sap-ux/ui-components'; -import type { BlogsSearchResultContentItem, Tag } from '@sap/knowledge-hub-extension-types'; +import type { + BlogsSearchResultContentItem, + Tag, + KnowledgeHubOpenBlogPayload +} from '@sap/knowledge-hub-extension-types'; import { DateTime } from './DateTime'; import { TagsBlog } from './TagsBlog'; import './BlogCard.scss'; +import { actions } from '../../store'; type BlogCardProps = { blog?: BlogsSearchResultContentItem; @@ -23,6 +28,18 @@ export const BlogCard: FC = ({ blog, loading, onSelectedTag }: Bl onSelectedTag(tag, true); }, []); + const onClickBlogTitle = useCallback( + (title: string, primaryTag: string) => + (_event: React.MouseEvent) => { + const blogsTelemetryPayload: KnowledgeHubOpenBlogPayload = { + title, + primaryTag + }; + actions.logOpenBlogTelemetryEvent('KHUB_OPEN_BLOGS', blogsTelemetryPayload); + }, + [] + ); + return (
{!loading && blog && ( @@ -48,7 +65,16 @@ export const BlogCard: FC = ({ blog, loading, onSelectedTag }: Bl
- + u.displayName).join(', ') + )}> {blog.title} diff --git a/packages/webapp/src/webview/components/TutorialCard/TutorialCard.tsx b/packages/webapp/src/webview/components/TutorialCard/TutorialCard.tsx index 6ccd6d84..93ea20d2 100644 --- a/packages/webapp/src/webview/components/TutorialCard/TutorialCard.tsx +++ b/packages/webapp/src/webview/components/TutorialCard/TutorialCard.tsx @@ -3,7 +3,11 @@ import type { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { UILoader, UIPersona, UIPersonaSize } from '@sap-ux/ui-components'; -import type { TutorialsEntry, TutorialsTags } from '@sap/knowledge-hub-extension-types'; +import type { + TutorialsEntry, + TutorialsTags, + KnowledgeHubOpenTutorialPayload +} from '@sap/knowledge-hub-extension-types'; import { TaskType } from './TaskType'; import { Experience } from './Experience'; @@ -12,6 +16,7 @@ import { Featured } from './Featured'; import { TagsTutorial } from './TagsTutorial'; import './TutorialCard.scss'; +import { actions } from '../../store'; type TutorialCardProps = { tutorial?: TutorialsEntry; @@ -33,7 +38,17 @@ export const TutorialCard: FC = ({ const onClickedTag = useCallback((tag: string) => { onSelectedTag(tag, true); }, []); - + const onClickTutorialCard = useCallback( + (title: string, primaryTag: string) => + (_event: React.MouseEvent) => { + const tutorialsTelemetryPayload: KnowledgeHubOpenTutorialPayload = { + title, + primaryTag + }; + actions.logOpenTutorialTelemetryEvent('KHUB_OPEN_TUTORIALS', tutorialsTelemetryPayload); + }, + [] + ); const getFullNameForTag = (tag: string): string => { if (tags?.[tag]) { return tags[tag].title; @@ -41,12 +56,13 @@ export const TutorialCard: FC = ({ return ''; } }; - return (
{!loading && tutorial && tag && ( diff --git a/packages/webapp/src/webview/store/actions.ts b/packages/webapp/src/webview/store/actions.ts index 266e79a4..b434f8db 100644 --- a/packages/webapp/src/webview/store/actions.ts +++ b/packages/webapp/src/webview/store/actions.ts @@ -7,10 +7,14 @@ import type { BlogFiltersEntry, Tag, TagsFetchTags, - TutorialsTagWithTitle + TutorialsTagWithTitle, + LogTelemetryEvent, + KnowledgeHubOpenBlogPayload, + KnowledgeHubOpenTutorialPayload } from '@sap/knowledge-hub-extension-types'; - import { + KHUB_OPEN_TUTORIAL, + KHUB_OPEN_BLOGS, KNOWLEDGE_HUB_WEB_VIEW_READY, TUTORIALS_FETCH_TUTORIALS, TAGS_FETCH_TAGS, @@ -43,6 +47,17 @@ export const blogsFetchBlogs = ( filters, home }); +export const logOpenTutorialTelemetryEvent = ( + type: string, + payload: KnowledgeHubOpenTutorialPayload +): LogTelemetryEvent => ({ + type: KHUB_OPEN_TUTORIAL, + payload: payload +}); +export const logOpenBlogTelemetryEvent = (type: string, payload: KnowledgeHubOpenBlogPayload): LogTelemetryEvent => ({ + type: KHUB_OPEN_BLOGS, + payload: payload +}); export const tagsFetchTags = (): TagsFetchTags => ({ type: TAGS_FETCH_TAGS diff --git a/packages/webapp/src/webview/store/middleware.ts b/packages/webapp/src/webview/store/middleware.ts index 43a45be8..345d3465 100644 --- a/packages/webapp/src/webview/store/middleware.ts +++ b/packages/webapp/src/webview/store/middleware.ts @@ -1,3 +1,4 @@ +import { KHUB_OPEN_BLOGS, KHUB_OPEN_TUTORIAL, LOG_TELEMETRY_EVENT } from '@sap/knowledge-hub-extension-types'; import type { Middleware, MiddlewareAPI, Dispatch, Action } from 'redux'; import { createLogger } from 'redux-logger'; @@ -35,3 +36,19 @@ export const postMessageMiddleware: Middleware = (store: MiddlewareAPI) => { export const loggerMiddleware = createLogger({ duration: true }); + +const allowedTelemetryActions = new Set([KHUB_OPEN_BLOGS, KHUB_OPEN_TUTORIAL]); + +export const telemetryMiddleware: Middleware = (): ((next: Dispatch) => (action: Action) => Action) => { + return (next: Dispatch) => + (action): Action => { + action = next(action); + if (action && typeof action.type === 'string' && allowedTelemetryActions.has(action.type)) { + window.vscode.postMessage({ + type: LOG_TELEMETRY_EVENT, + payload: { action } + } as unknown as string); + } + return action; + }; +}; diff --git a/packages/webapp/src/webview/store/store.ts b/packages/webapp/src/webview/store/store.ts index 12997fd5..4d6d33ac 100644 --- a/packages/webapp/src/webview/store/store.ts +++ b/packages/webapp/src/webview/store/store.ts @@ -2,14 +2,14 @@ import { configureStore } from '@reduxjs/toolkit'; import { bindActionCreators } from 'redux'; import { reducer, getInitialState } from './reducer'; -import { postMessageMiddleware, loggerMiddleware } from './middleware'; +import { postMessageMiddleware, loggerMiddleware, telemetryMiddleware } from './middleware'; import * as AllActions from './actions'; export const store = configureStore({ reducer, preloadedState: getInitialState(), devTools: false, - middleware: [postMessageMiddleware, loggerMiddleware] + middleware: [postMessageMiddleware, loggerMiddleware, telemetryMiddleware] }); // bind actions to store diff --git a/packages/webapp/test/components/BlogCard/BlogCard.test.tsx b/packages/webapp/test/components/BlogCard/BlogCard.test.tsx index 692ef894..24e683ae 100644 --- a/packages/webapp/test/components/BlogCard/BlogCard.test.tsx +++ b/packages/webapp/test/components/BlogCard/BlogCard.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import '@testing-library/jest-dom'; -import { screen } from '@testing-library/react'; +import { fireEvent, screen } from '@testing-library/react'; import type { RenderResult } from '@testing-library/react'; import type { BlogsSearchResultContentItem, Tag } from '@sap/knowledge-hub-extension-types'; @@ -11,6 +11,7 @@ import { blogEntry } from '../../../test/__mocks__/blogs'; import { render } from '../../../test/__mocks__/store.mock'; import { BlogCard } from '../../../src/webview/components/BlogCard'; +import { actions } from '../../../src/webview/store'; describe('BlogCard', () => { // Initialize and register ui-components icons and specific icon to LC @@ -32,5 +33,17 @@ describe('BlogCard', () => { const authorNameDOM = screen.getByText(/John Doe/i); expect(authorNameDOM.className).toEqual('blog-card-data-header-author'); + const logTelemetryEventSpy = jest.spyOn(actions, 'logOpenBlogTelemetryEvent'); + if (screen) { + // Simulate click + fireEvent( + screen.getByTestId('blog-card-link'), + new MouseEvent('click', { + bubbles: true, + cancelable: true + }) + ); + expect(logTelemetryEventSpy).toBeCalledTimes(1); + } }); }); diff --git a/packages/webapp/test/components/TutorialCard/TutorialCard.test.tsx b/packages/webapp/test/components/TutorialCard/TutorialCard.test.tsx index cca870b9..2982fbaa 100644 --- a/packages/webapp/test/components/TutorialCard/TutorialCard.test.tsx +++ b/packages/webapp/test/components/TutorialCard/TutorialCard.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import '@testing-library/jest-dom'; -import { screen } from '@testing-library/react'; +import { fireEvent, screen } from '@testing-library/react'; import type { RenderResult } from '@testing-library/react'; import type { TutorialsEntry } from '@sap/knowledge-hub-extension-types'; @@ -11,8 +11,12 @@ import { withDataNoError } from '../../__mocks__/tutorials'; import { render } from '../../__mocks__/store.mock'; import { TutorialCard } from '../../../src/webview/components/TutorialCard'; +import { actions } from '../../../src/webview/store'; describe('TutorialCard', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); // Initialize and register ui-components icons and specific icon to LC initIcons(); @@ -49,5 +53,18 @@ describe('TutorialCard', () => { const featuredDOM = screen.getByText(/Featured/i); expect(featuredDOM.className).toEqual('featured-text'); + const logTelemetryEventSpy = jest.spyOn(actions, 'logOpenTutorialTelemetryEvent'); + if (featuredDOM) { + // Simulate click + + fireEvent( + screen.getByTestId('tutorial-card-link'), + new MouseEvent('click', { + bubbles: true, + cancelable: true + }) + ); + expect(logTelemetryEventSpy).toBeCalledTimes(1); + } }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec7d609d..1a54dc36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,8 +1,4 @@ -lockfileVersion: '6.1' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false +lockfileVersion: '6.0' overrides: strip-ansi@3.0.1>ansi-regex: ^5.0.1 @@ -116,12 +112,24 @@ importers: '@sap/knowledge-hub-extension-types': specifier: workspace:* version: link:../types + '@types/uuid': + specifier: 9.0.1 + version: 9.0.1 '@types/vscode': specifier: 1.39.0 version: 1.39.0 + '@vscode/vsce': + specifier: 2.19.0 + version: 2.19.0 + applicationinsights: + specifier: 2.5.1 + version: 2.5.1 axios: specifier: 1.3.4 version: 1.3.4 + dotenv: + specifier: 16.0.3 + version: 16.0.3 esbuild: specifier: 0.15.12 version: 0.15.12 @@ -137,9 +145,9 @@ importers: qs: specifier: 6.11.1 version: 6.11.1 - vsce: - specifier: 2.15.0 - version: 2.15.0 + uuid: + specifier: 9.0.0 + version: 9.0.0 vscode-uri: specifier: 3.0.7 version: 3.0.7 @@ -296,6 +304,74 @@ packages: '@jridgewell/trace-mapping': 0.3.17 dev: true + /@azure/abort-controller@1.1.0: + resolution: {integrity: sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==} + engines: {node: '>=12.0.0'} + dependencies: + tslib: 2.5.0 + dev: true + + /@azure/core-auth@1.4.0: + resolution: {integrity: sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==} + engines: {node: '>=12.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + tslib: 2.5.0 + dev: true + + /@azure/core-rest-pipeline@1.10.3: + resolution: {integrity: sha512-AMQb0ttiGJ0MIV/r+4TVra6U4+90mPeOveehFnrqKlo7dknPJYdJ61wOzYJXJjDxF8LcCtSogfRelkq+fCGFTw==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + '@azure/core-auth': 1.4.0 + '@azure/core-tracing': 1.0.1 + '@azure/core-util': 1.3.1 + '@azure/logger': 1.0.4 + form-data: 4.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + tslib: 2.5.0 + transitivePeerDependencies: + - supports-color + dev: true + + /@azure/core-tracing@1.0.1: + resolution: {integrity: sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==} + engines: {node: '>=12.0.0'} + dependencies: + tslib: 2.5.0 + dev: true + + /@azure/core-util@1.3.1: + resolution: {integrity: sha512-pjfOUAb+MPLODhGuXot/Hy8wUgPD0UTqYkY3BiYcwEETrLcUCVM1t0roIvlQMgvn1lc48TGy5bsonsFpF862Jw==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/abort-controller': 1.1.0 + tslib: 2.5.0 + dev: true + + /@azure/logger@1.0.4: + resolution: {integrity: sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.5.0 + dev: true + + /@azure/opentelemetry-instrumentation-azure-sdk@1.0.0-beta.2: + resolution: {integrity: sha512-WZ2u3J7LmwwVbyXGguiEGNYHyDoUjNb+VZ9S76xecsYOkoKSzFdWJtv/vYBknW9fLuoWCoyVVg8+lU2ouaZbJQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@azure/core-tracing': 1.0.1 + '@azure/logger': 1.0.4 + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/instrumentation': 0.33.0(@opentelemetry/api@1.4.1) + tslib: 2.5.0 + transitivePeerDependencies: + - supports-color + dev: true + /@babel/code-frame@7.18.6: resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} engines: {node: '>=6.9.0'} @@ -2704,6 +2780,10 @@ packages: read-yaml-file: 1.1.0 dev: true + /@microsoft/applicationinsights-web-snippet@1.0.1: + resolution: {integrity: sha512-2IHAOaLauc8qaAitvWS+U931T+ze+7MNWrDHY47IENP5y2UA0vqJDu67kWZDdpCN1fFC77sfgfB+HV7SrKshnQ==} + dev: true + /@microsoft/load-themed-styles@1.10.295: resolution: {integrity: sha512-W+IzEBw8a6LOOfRJM02dTT7BDZijxm+Z7lhtOAz1+y9vQm1Kdz9jlAO+qCEKsfxtUOmKilW8DIRqFw2aUgKeGg==} dev: false @@ -2729,6 +2809,72 @@ packages: fastq: 1.14.0 dev: true + /@opentelemetry/api-metrics@0.33.0: + resolution: {integrity: sha512-78evfPRRRnJA6uZ3xuBuS3VZlXTO/LRs+Ff1iv3O/7DgibCtq9k27T6Zlj8yRdJDFmcjcbQrvC0/CpDpWHaZYA==} + engines: {node: '>=14'} + deprecated: Please use @opentelemetry/api >= 1.3.0 + dependencies: + '@opentelemetry/api': 1.4.1 + dev: true + + /@opentelemetry/api@1.4.1: + resolution: {integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==} + engines: {node: '>=8.0.0'} + dev: true + + /@opentelemetry/core@1.12.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-4DWYNb3dLs2mSCGl65jY3aEgbvPWSHVQV/dmDWiYeWUrMakZQFcymqZOSUNZO0uDrEJoxMu8O5tZktX6UKFwag==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.5.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/semantic-conventions': 1.12.0 + dev: true + + /@opentelemetry/instrumentation@0.33.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-8joPjKJ6TznNt04JbnzZG+m1j/4wm1OIrX7DEw/V5lyZ9/2fahIqG72jeZ26VKOZnLOpVzUUnU/dweURqBzT3Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.0.0 + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/api-metrics': 0.33.0 + require-in-the-middle: 5.2.0 + semver: 7.3.8 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@opentelemetry/resources@1.12.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-gunMKXG0hJrR0LXrqh7BVbziA/+iJBL3ZbXCXO64uY+SrExkwoyJkpiq9l5ismkGF/A20mDEV7tGwh+KyPw00Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.5.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.12.0 + dev: true + + /@opentelemetry/sdk-trace-base@1.12.0(@opentelemetry/api@1.4.1): + resolution: {integrity: sha512-pfCOB3tNDlYVoWuz4D7Ji+Jmy9MHnATWHVpkERdCEiwUGEZ+4IvNPXUcPc37wJVmMpjGLeaWgPPrie0KIpWf1A==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.5.0' + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.12.0 + dev: true + + /@opentelemetry/semantic-conventions@1.12.0: + resolution: {integrity: sha512-hO+bdeGOlJwqowUBoZF5LyP3ORUFOP1G0GRv8N45W/cztXbT2ZEXaAzfokRS9Xc9FWmYrDj32mF6SzH6wuoIyA==} + engines: {node: '>=14'} + dev: true + /@pkgr/utils@2.3.1: resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -3218,6 +3364,10 @@ packages: resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} dev: true + /@types/uuid@9.0.1: + resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==} + dev: true + /@types/vscode@1.39.0: resolution: {integrity: sha512-rlg0okXDt7NjAyHXbZ2nO1I/VY/8y9w67ltLRrOxXQ46ayvrYZavD4A6zpYrGbs2+ZOEQzcUs+QZOqcVGQIxXQ==} dev: true @@ -3362,6 +3512,35 @@ packages: eslint-visitor-keys: 3.4.0 dev: true + /@vscode/vsce@2.19.0: + resolution: {integrity: sha512-dAlILxC5ggOutcvJY24jxz913wimGiUrHaPkk16Gm9/PGFbz1YezWtrXsTKUtJws4fIlpX2UIlVlVESWq8lkfQ==} + engines: {node: '>= 14'} + hasBin: true + dependencies: + azure-devops-node-api: 11.2.0 + chalk: 2.4.2 + cheerio: 1.0.0-rc.12 + commander: 6.2.1 + glob: 7.2.3 + hosted-git-info: 4.1.0 + jsonc-parser: 3.2.0 + leven: 3.1.0 + markdown-it: 12.3.2 + mime: 1.6.0 + minimatch: 3.1.2 + parse-semver: 1.1.1 + read: 1.0.7 + semver: 5.7.1 + tmp: 0.2.1 + typed-rest-client: 1.8.9 + url-join: 4.0.1 + xml2js: 0.5.0 + yauzl: 2.10.0 + yazl: 2.5.1 + optionalDependencies: + keytar: 7.9.0 + dev: true + /JSONStream@1.3.5: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true @@ -3489,6 +3668,31 @@ packages: picomatch: 2.3.1 dev: true + /applicationinsights@2.5.1: + resolution: {integrity: sha512-FkkvevcsaPnfifZvVTSxZy6njnNKpTaOFZQ55cGlbvVX9Y15vuRFpZUdLTLLyS0vqOeNUa+xk30jzwLjJBTXbQ==} + engines: {node: '>=8.0.0'} + peerDependencies: + applicationinsights-native-metrics: '*' + peerDependenciesMeta: + applicationinsights-native-metrics: + optional: true + dependencies: + '@azure/core-auth': 1.4.0 + '@azure/core-rest-pipeline': 1.10.3 + '@azure/opentelemetry-instrumentation-azure-sdk': 1.0.0-beta.2 + '@microsoft/applicationinsights-web-snippet': 1.0.1 + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-trace-base': 1.12.0(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.12.0 + cls-hooked: 4.2.2 + continuation-local-storage: 3.2.1 + diagnostic-channel: 1.1.0 + diagnostic-channel-publishers: 1.0.5(diagnostic-channel@1.1.0) + transitivePeerDependencies: + - supports-color + dev: true + /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true @@ -3583,6 +3787,21 @@ packages: engines: {node: '>=8'} dev: true + /async-hook-jl@1.7.6: + resolution: {integrity: sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==} + engines: {node: ^4.7 || >=6.9 || >=7.3} + dependencies: + stack-chain: 1.3.7 + dev: true + + /async-listener@0.6.10: + resolution: {integrity: sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw==} + engines: {node: <=0.11.8 || >0.11.10} + dependencies: + semver: 5.7.1 + shimmer: 1.2.1 + dev: true + /asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -3713,6 +3932,7 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: true + optional: true /better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} @@ -3733,6 +3953,7 @@ packages: inherits: 2.0.4 readable-stream: 3.6.0 dev: true + optional: true /boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -3802,6 +4023,7 @@ packages: base64-js: 1.5.1 ieee754: 1.2.1 dev: true + optional: true /call-bind@1.0.2: resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} @@ -3918,6 +4140,7 @@ packages: /chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} dev: true + optional: true /ci-info@3.7.0: resolution: {integrity: sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==} @@ -3978,6 +4201,15 @@ packages: engines: {node: '>=0.8'} dev: true + /cls-hooked@4.2.2: + resolution: {integrity: sha512-J4Xj5f5wq/4jAvcdgoGsL3G103BtWpZrMo8NEinRltN+xpTZdI+M38pyQqhuFU/P792xkMFvnKSf+Lm81U1bxw==} + engines: {node: ^4.7 || >=6.9 || >=7.3 || >=8.2.1} + dependencies: + async-hook-jl: 1.7.6 + emitter-listener: 1.1.2 + semver: 5.7.1 + dev: true + /clsx@1.2.1: resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} engines: {node: '>=6'} @@ -4049,6 +4281,13 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true + /continuation-local-storage@3.2.1: + resolution: {integrity: sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA==} + dependencies: + async-listener: 0.6.10 + emitter-listener: 1.1.2 + dev: true + /conventional-changelog-angular@5.0.13: resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} engines: {node: '>=10'} @@ -4273,6 +4512,7 @@ packages: dependencies: mimic-response: 3.1.0 dev: true + optional: true /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} @@ -4313,6 +4553,7 @@ packages: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} dev: true + optional: true /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -4360,12 +4601,27 @@ packages: resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} engines: {node: '>=8'} dev: true + optional: true /detect-newline@3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} dev: true + /diagnostic-channel-publishers@1.0.5(diagnostic-channel@1.1.0): + resolution: {integrity: sha512-dJwUS0915pkjjimPJVDnS/QQHsH0aOYhnZsLJdnZIMOrB+csj8RnZhWTuwnm8R5v3Z7OZs+ksv5luC14DGB7eg==} + peerDependencies: + diagnostic-channel: '*' + dependencies: + diagnostic-channel: 1.1.0 + dev: true + + /diagnostic-channel@1.1.0: + resolution: {integrity: sha512-fwujyMe1gj6rk6dYi9hMZm0c8Mz8NDMVl2LB4iaYh3+LIAThZC8RKFGXWG0IML2OxAit/ZFRgZhMkhQ3d/bobQ==} + dependencies: + semver: 5.7.1 + dev: true + /diff-sequences@29.4.3: resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4471,6 +4727,11 @@ packages: is-obj: 2.0.0 dev: true + /dotenv@16.0.3: + resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} + engines: {node: '>=12'} + dev: true + /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} dev: true @@ -4479,6 +4740,12 @@ packages: resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} dev: true + /emitter-listener@1.1.2: + resolution: {integrity: sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ==} + dependencies: + shimmer: 1.2.1 + dev: true + /emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} @@ -5482,6 +5749,7 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} dev: true + optional: true /expect@29.5.0: resolution: {integrity: sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==} @@ -5649,6 +5917,7 @@ packages: /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} dev: true + optional: true /fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} @@ -5782,6 +6051,7 @@ packages: /github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} dev: true + optional: true /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -6058,6 +6328,7 @@ packages: /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true + optional: true /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} @@ -7011,6 +7282,10 @@ packages: hasBin: true dev: true + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: @@ -7049,6 +7324,7 @@ packages: node-addon-api: 4.3.0 prebuild-install: 7.1.1 dev: true + optional: true /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} @@ -7487,6 +7763,7 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} dev: true + optional: true /min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} @@ -7532,6 +7809,11 @@ packages: /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} dev: true + optional: true + + /module-details-from-path@1.0.3: + resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + dev: true /moment@2.29.4: resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} @@ -7573,6 +7855,7 @@ packages: /napi-build-utils@1.0.2: resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} dev: true + optional: true /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} @@ -7588,10 +7871,12 @@ packages: dependencies: semver: 7.3.8 dev: true + optional: true /node-addon-api@4.3.0: resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} dev: true + optional: true /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -8060,6 +8345,7 @@ packages: tar-fs: 2.1.1 tunnel-agent: 0.6.0 dev: true + optional: true /preferred-pm@3.0.3: resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} @@ -8208,6 +8494,7 @@ packages: minimist: 1.2.7 strip-json-comments: 2.0.1 dev: true + optional: true /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} @@ -8481,6 +8768,17 @@ packages: engines: {node: '>=0.10.0'} dev: true + /require-in-the-middle@5.2.0: + resolution: {integrity: sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg==} + engines: {node: '>=6'} + dependencies: + debug: 4.3.4 + module-details-from-path: 1.0.3 + resolve: 1.22.1 + transitivePeerDependencies: + - supports-color + dev: true + /require-main-filename@2.0.0: resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true @@ -8696,6 +8994,10 @@ packages: engines: {node: '>=8'} dev: true + /shimmer@1.2.1: + resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} + dev: true + /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -8710,6 +9012,7 @@ packages: /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} dev: true + optional: true /simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} @@ -8718,6 +9021,7 @@ packages: once: 1.4.0 simple-concat: 1.0.1 dev: true + optional: true /sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -8832,6 +9136,10 @@ packages: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true + /stack-chain@1.3.7: + resolution: {integrity: sha512-D8cWtWVdIe/jBA7v5p5Hwl5yOSOrmZPWDPe2KxQ5UAGD+nxbxU0lKXA4h85Ta6+qgdKVL3vUxsbIZjc1kBG7ug==} + dev: true + /stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -8960,6 +9268,7 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} dev: true + optional: true /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} @@ -9032,6 +9341,7 @@ packages: pump: 3.0.0 tar-stream: 2.2.0 dev: true + optional: true /tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -9043,6 +9353,7 @@ packages: inherits: 2.0.4 readable-stream: 3.6.0 dev: true + optional: true /term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} @@ -9250,6 +9561,7 @@ packages: dependencies: safe-buffer: 5.2.1 dev: true + optional: true /tunnel@0.0.6: resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} @@ -9447,34 +9759,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /vsce@2.15.0: - resolution: {integrity: sha512-P8E9LAZvBCQnoGoizw65JfGvyMqNGlHdlUXD1VAuxtvYAaHBKLBdKPnpy60XKVDAkQCfmMu53g+gq9FM+ydepw==} - engines: {node: '>= 14'} - deprecated: vsce has been renamed to @vscode/vsce. Install using @vscode/vsce instead. - hasBin: true - dependencies: - azure-devops-node-api: 11.2.0 - chalk: 2.4.2 - cheerio: 1.0.0-rc.12 - commander: 6.2.1 - glob: 7.2.3 - hosted-git-info: 4.1.0 - keytar: 7.9.0 - leven: 3.1.0 - markdown-it: 12.3.2 - mime: 1.6.0 - minimatch: 3.1.2 - parse-semver: 1.1.1 - read: 1.0.7 - semver: 5.7.1 - tmp: 0.2.1 - typed-rest-client: 1.8.9 - url-join: 4.0.1 - xml2js: 0.4.23 - yauzl: 2.10.0 - yazl: 2.5.1 - dev: true - /vscode-uri@3.0.7: resolution: {integrity: sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==} dev: true @@ -9641,8 +9925,8 @@ packages: engines: {node: '>=12'} dev: true - /xml2js@0.4.23: - resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==} + /xml2js@0.5.0: + resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} engines: {node: '>=4.0.0'} dependencies: sax: 1.2.4