diff --git a/src/ecl/command.ts b/src/ecl/command.ts index 00a632d..fc4cbbc 100644 --- a/src/ecl/command.ts +++ b/src/ecl/command.ts @@ -13,6 +13,7 @@ import { createDirectory, exists, writeFile } from "../util/fs"; import { ECLR_EN_US, matchTopics, SLR_EN_US } from "./docs"; import { SaveData } from "./saveData"; import { credentialManager } from "../util/credentialManager"; +import { registerCommand, registerTextEditorCommand } from "../telemetry"; const IMPORT_MARKER = "//Import:"; const SKIP = localize("Skip"); @@ -26,27 +27,27 @@ export class ECLCommands { private constructor(ctx: vscode.ExtensionContext) { this._ctx = ctx; - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.checkSyntax", this.checkSyntax, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.checkSyntaxAll", this.checkSyntaxAll, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.checkSyntaxClear", this.checkSyntaxClear, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.submit", this.submit, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.submitNoArchive", this.submitNoArchive, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.compile", this.compile, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.showLanguageReference", this.showLanguageReference, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.showStandardLibraryReference", this.showStandardLibraryReference, this)); - ctx.subscriptions.push(vscode.commands.registerTextEditorCommand("ecl.searchTerm", this.searchTerm, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.showWUDetails", this.showWUDetails, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.selectCTVersion", selectCTVersion)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.openECLWatchExternal", this.openECLWatchExternal, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.saveResultAs", this.saveResultAs, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.openResult", this.openResult, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.browseResult", this.browseResult, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.insertRecordDef", this.insertRecordDef, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.sign", this.sign, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.verify", this.verify, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.importModFile", this.importModFile, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.copyAsEclID", this.copyAsEclID, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.clearStoredPasswords", this.clearStoredPasswords, this)); + registerCommand(ctx, "ecl.checkSyntax", this.checkSyntax, this); + registerCommand(ctx, "ecl.checkSyntaxAll", this.checkSyntaxAll, this); + registerCommand(ctx, "ecl.checkSyntaxClear", this.checkSyntaxClear, this); + registerCommand(ctx, "ecl.submit", this.submit, this); + registerCommand(ctx, "ecl.submitNoArchive", this.submitNoArchive, this); + registerCommand(ctx, "ecl.compile", this.compile, this); + registerCommand(ctx, "ecl.showLanguageReference", this.showLanguageReference, this); + registerCommand(ctx, "ecl.showStandardLibraryReference", this.showStandardLibraryReference, this); + registerTextEditorCommand(ctx, "ecl.searchTerm", this.searchTerm, this); + registerCommand(ctx, "ecl.showWUDetails", this.showWUDetails, this); + registerCommand(ctx, "ecl.selectCTVersion", selectCTVersion); + registerCommand(ctx, "ecl.openECLWatchExternal", this.openECLWatchExternal, this); + registerCommand(ctx, "ecl.saveResultAs", this.saveResultAs, this); + registerCommand(ctx, "ecl.openResult", this.openResult, this); + registerCommand(ctx, "ecl.browseResult", this.browseResult, this); + registerCommand(ctx, "ecl.insertRecordDef", this.insertRecordDef, this); + registerCommand(ctx, "ecl.sign", this.sign, this); + registerCommand(ctx, "ecl.verify", this.verify, this); + registerCommand(ctx, "ecl.importModFile", this.importModFile, this); + registerCommand(ctx, "ecl.copyAsEclID", this.copyAsEclID, this); + registerCommand(ctx, "ecl.clearStoredPasswords", this.clearStoredPasswords, this); } static attach(ctx: vscode.ExtensionContext): ECLCommands { diff --git a/src/ecl/eclWatchPanelView.ts b/src/ecl/eclWatchPanelView.ts index 84e3efc..4bde1ad 100644 --- a/src/ecl/eclWatchPanelView.ts +++ b/src/ecl/eclWatchPanelView.ts @@ -6,6 +6,7 @@ import { wuDetailsUrl, wuResultUrl } from "../hpccplatform/launchConfig"; import { sessionManager } from "../hpccplatform/session"; import type { Messages } from "../eclwatch/messages"; import { credentialManager } from "../util/credentialManager"; +import { registerCommand } from "../telemetry"; interface NavigateParams extends LaunchRequestArguments { wuid: string; @@ -37,7 +38,7 @@ export class ECLWatchPanelView implements vscode.WebviewViewProvider { this.navigateTo(launchRequestArgs, undefined, undefined, false); }); - vscode.commands.registerCommand("ecl.watch.lite.openECLWatchExternal", async () => { + registerCommand(ctx, "ecl.watch.lite.openECLWatchExternal", async () => { if (this._currParams) { if (this._currParams.resultName === undefined) { vscode.env.openExternal(vscode.Uri.parse(wuDetailsUrl(this._currParams, this._currParams.wuid))); diff --git a/src/ecl/eclWatchTree.ts b/src/ecl/eclWatchTree.ts index b103f65..516c959 100644 --- a/src/ecl/eclWatchTree.ts +++ b/src/ecl/eclWatchTree.ts @@ -6,6 +6,7 @@ import { Item, Tree } from "./tree"; import { eclWatchPanelView } from "./eclWatchPanelView"; import { SaveData } from "./saveData"; import { formatWorkunitURL, formatResultURL, formatResultsURL, formatMetricsURL } from "./util"; +import { registerCommand } from "../telemetry"; const PrevWeeks: string[] = [localize("Last Week"), localize("Two Weeks Ago"), localize("Three Weeks Ago"), localize("Four Weeks Ago"), localize("Five Weeks Ago"), localize("Six Weeks Ago"), localize("Seven Weeks Ago")]; @@ -48,132 +49,132 @@ export class ECLWatchTree extends Tree { } }); - vscode.commands.registerCommand("hpccPlatform.myWorkunits", async () => { + registerCommand(ctx, "hpccPlatform.myWorkunits", async () => { this._myWorkunits = false; this.refresh(); }); - vscode.commands.registerCommand("hpccPlatform.allWorkunits", async () => { + registerCommand(ctx, "hpccPlatform.allWorkunits", async () => { this._myWorkunits = true; this.refresh(); }); - vscode.commands.registerCommand("hpccPlatform.userRefresh", () => { + registerCommand(ctx, "hpccPlatform.userRefresh", () => { this.refresh(); }); - vscode.commands.registerCommand("hpccPlatform.refresh", (element?: Item) => { + registerCommand(ctx, "hpccPlatform.refresh", (element?: Item) => { this.refresh(element); }); - vscode.commands.registerCommand("hpccPlatform.openResults", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.openResults", (wuNode: ECLWUNode) => { wuNode.openResults(); }); - vscode.commands.registerCommand("hpccPlatform.browseResults", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.browseResults", (wuNode: ECLWUNode) => { wuNode.browseResults(); }); - vscode.commands.registerCommand("hpccPlatform.browseMetrics", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.browseMetrics", (wuNode: ECLWUNode) => { wuNode.browseMetrics(); }); - vscode.commands.registerCommand("hpccPlatform.browseWUDetails", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.browseWUDetails", (wuNode: ECLWUNode) => { wuNode.browseWUDetails(); }); - vscode.commands.registerCommand("hpccPlatform.openECL", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.openECL", (wuNode: ECLWUNode) => { wuNode.openECL(); }); - vscode.commands.registerCommand("hpccPlatform.copyWUID", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.copyWUID", (wuNode: ECLWUNode) => { wuNode.copyWuid(); }); - vscode.commands.registerCommand("hpccPlatform.saveWUResults", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.saveWUResults", (wuNode: ECLWUNode) => { wuNode.saveWUResults(); }); - vscode.commands.registerCommand("hpccPlatform.abortWU", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.abortWU", (wuNode: ECLWUNode) => { wuNode.abort(); }); - vscode.commands.registerCommand("hpccPlatform.resubmitWU", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.resubmitWU", (wuNode: ECLWUNode) => { wuNode.resubmit(); }); - vscode.commands.registerCommand("hpccPlatform.deleteWU", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.deleteWU", (wuNode: ECLWUNode) => { wuNode.delete(); }); - vscode.commands.registerCommand("hpccPlatform.protectWU", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.protectWU", (wuNode: ECLWUNode) => { wuNode.protect(); }); - vscode.commands.registerCommand("hpccPlatform.unprotectWU", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.unprotectWU", (wuNode: ECLWUNode) => { wuNode.unprotect(); }); - vscode.commands.registerCommand("hpccPlatform.moveJobUp", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.moveJobUp", (wuNode: ECLWUNode) => { wuNode.moveJobUp(); }); - vscode.commands.registerCommand("hpccPlatform.moveJobDown", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.moveJobDown", (wuNode: ECLWUNode) => { wuNode.moveJobDown(); }); - vscode.commands.registerCommand("hpccPlatform.moveJobBack", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.moveJobBack", (wuNode: ECLWUNode) => { wuNode.moveJobBack(); }); - vscode.commands.registerCommand("hpccPlatform.moveJobFront", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.moveJobFront", (wuNode: ECLWUNode) => { wuNode.moveJobFront(); }); - vscode.commands.registerCommand("hpccPlatform.setStateCompiled", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateCompiled", (wuNode: ECLWUNode) => { wuNode.setStateCompiled(); }); - vscode.commands.registerCommand("hpccPlatform.setStateRunning", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateRunning", (wuNode: ECLWUNode) => { wuNode.setStateRunning(); }); - vscode.commands.registerCommand("hpccPlatform.setStateCompleted", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateCompleted", (wuNode: ECLWUNode) => { wuNode.setStateCompleted(); }); - vscode.commands.registerCommand("hpccPlatform.setStateFailed", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateFailed", (wuNode: ECLWUNode) => { wuNode.setStateFailed(); }); - vscode.commands.registerCommand("hpccPlatform.setStateArchived", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateArchived", (wuNode: ECLWUNode) => { wuNode.setStateArchived(); }); - vscode.commands.registerCommand("hpccPlatform.setStateAborting", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateAborting", (wuNode: ECLWUNode) => { wuNode.setStateAborting(); }); - vscode.commands.registerCommand("hpccPlatform.setStateAborted", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateAborted", (wuNode: ECLWUNode) => { wuNode.setStateAborted(); }); - vscode.commands.registerCommand("hpccPlatform.setStateBlocked", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateBlocked", (wuNode: ECLWUNode) => { wuNode.setStateBlocked(); }); - vscode.commands.registerCommand("hpccPlatform.setStateSubmitted", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateSubmitted", (wuNode: ECLWUNode) => { wuNode.setStateSubmitted(); }); - vscode.commands.registerCommand("hpccPlatform.setStateScheduled", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateScheduled", (wuNode: ECLWUNode) => { wuNode.setStateScheduled(); }); - vscode.commands.registerCommand("hpccPlatform.setStateCompiling", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateCompiling", (wuNode: ECLWUNode) => { wuNode.setStateCompiling(); }); - vscode.commands.registerCommand("hpccPlatform.setStateWait", (wuNode: ECLWUNode) => { + registerCommand(ctx, "hpccPlatform.setStateWait", (wuNode: ECLWUNode) => { wuNode.setStateWait(); }); diff --git a/src/ecl/hpccResources.ts b/src/ecl/hpccResources.ts index ee6bb07..7497402 100644 --- a/src/ecl/hpccResources.ts +++ b/src/ecl/hpccResources.ts @@ -7,6 +7,7 @@ import { onDidClientToolsChange, switchClientTools } from "./clientTools"; import { Circle } from "./eclWatchTree"; import { eclTerminal } from "./terminal"; import { Tree, Item } from "./tree"; +import { registerCommand } from "../telemetry"; let hpccResources: HPCCResources; @@ -29,7 +30,7 @@ class Bundles extends Tree { protected constructor(ctx: vscode.ExtensionContext) { super(ctx, "hpccResources.bundles", false); - vscode.commands.registerCommand("hpccResources.bundles.homepage", (item: Item) => { + registerCommand(ctx, "hpccResources.bundles.homepage", (item: Item) => { if (item instanceof BundlesItem) { vscode.env.openExternal(vscode.Uri.parse(item.url())); } else { @@ -37,11 +38,11 @@ class Bundles extends Tree { } }); - vscode.commands.registerCommand("hpccResources.bundles.refresh", () => { + registerCommand(ctx, "hpccResources.bundles.refresh", () => { this.refresh(); }); - vscode.commands.registerCommand("hpccResources.bundles.install", (item: Item) => { + registerCommand(ctx, "hpccResources.bundles.install", (item: Item) => { if (item instanceof BundlesItem) { this._treeView.title = `${localize("Installing")}...`; item.install().then(response => { @@ -54,7 +55,7 @@ class Bundles extends Tree { } }); - vscode.commands.registerCommand("hpccResources.bundles.uninstall", (item: Item) => { + registerCommand(ctx, "hpccResources.bundles.uninstall", (item: Item) => { if (item instanceof BundlesItem) { this._treeView.title = `${localize("Uninstalling")}...`; item.uninstall().then(response => { @@ -134,28 +135,28 @@ class ClientToolsTree extends Tree { this.refresh(); }); - vscode.commands.registerCommand("hpccResources.clientTools.homepage", () => { + registerCommand(ctx, "hpccResources.clientTools.homepage", () => { vscode.env.openExternal(vscode.Uri.parse("https://hpccsystems.com/download")); }); - vscode.commands.registerCommand("hpccResources.clientTools.refresh", () => { + registerCommand(ctx, "hpccResources.clientTools.refresh", () => { clearAllClientToolsCache(); this.refresh(); }); - vscode.commands.registerCommand("hpccResources.clientTools.activate", (item: Item) => { + registerCommand(ctx, "hpccResources.clientTools.activate", (item: Item) => { if (item instanceof ClientToolsItem) { switchClientTools(item.clientTools); } }); - vscode.commands.registerCommand("hpccResources.clientTools.deactivate", (item: Item) => { + registerCommand(ctx, "hpccResources.clientTools.deactivate", (item: Item) => { if (item instanceof ClientToolsItem) { switchClientTools(); } }); - vscode.commands.registerCommand("hpccResources.clientTools.terminal", (item: Item) => { + registerCommand(ctx, "hpccResources.clientTools.terminal", (item: Item) => { if (item instanceof ClientToolsItem) { eclTerminal(item.clientTools); } diff --git a/src/ecl/main.ts b/src/ecl/main.ts index 846db0e..02a9769 100644 --- a/src/ecl/main.ts +++ b/src/ecl/main.ts @@ -13,6 +13,7 @@ import { HPCCResources } from "./hpccResources"; import { ECLChat } from "./lm/chat"; import { ECLLMTools } from "./lm/tools"; import { SessionManager } from "../hpccplatform/session"; +import { logActivation, logEvent } from "../telemetry"; const eclConfig = vscode.workspace.getConfiguration("ecl"); initLogger(eclConfig.get("debugLogging") ? Level.debug : Level.info); @@ -21,27 +22,28 @@ const logger = scopedLogger("ecl/main.ts"); export function activate(ctx: vscode.ExtensionContext): void { logger.debug("Activating SessionManager"); - SessionManager.attach(ctx); + logActivation("ecl.SessionManager", () => SessionManager.attach(ctx)); logger.debug("Activating ECLDiagnostic"); - ECLDiagnostic.attach(ctx); + logActivation("ecl.ECLDiagnostic", () => ECLDiagnostic.attach(ctx)); logger.debug("Activating ECLCommands"); - ECLCommands.attach(ctx); + logActivation("ecl.ECLCommands", () => ECLCommands.attach(ctx)); logger.debug("Activating ECLEditor"); - ECLEditor.attach(ctx); + logActivation("ecl.ECLEditor", () => ECLEditor.attach(ctx)); logger.debug("Activating ECLStatusBar"); - ECLStatusBar.attach(ctx); + logActivation("ecl.ECLStatusBar", () => ECLStatusBar.attach(ctx)); logger.debug("Activating ECLDocumentSymbolProvider"); - ECLDocumentSymbolProvider.attach(ctx); + logActivation("ecl.ECLDocumentSymbolProvider", () => ECLDocumentSymbolProvider.attach(ctx)); logger.debug("Activating ECLWatchTree"); - ECLWatchTree.attach(ctx); + logActivation("ecl.ECLWatchTree", () => ECLWatchTree.attach(ctx)); logger.debug("Activating ECLWatchPanelView"); - ECLWatchPanelView.attach(ctx); + logActivation("ecl.ECLWatchPanelView", () => ECLWatchPanelView.attach(ctx)); logger.debug("Activating ECLTerminal"); - ECLTerminal.attach(ctx); + logActivation("ecl.ECLTerminal", () => ECLTerminal.attach(ctx)); logger.debug("Activating HPCCResources"); - HPCCResources.attach(ctx); + logActivation("ecl.HPCCResources", () => HPCCResources.attach(ctx)); logger.debug("Activating Chat"); - ECLChat.attach(ctx); + logActivation("ecl.Chat", () => ECLChat.attach(ctx)); logger.debug("Activating LM Tools"); - ECLLMTools.attach(ctx); + logActivation("ecl.LMTools", () => ECLLMTools.attach(ctx)); + logEvent("ecl.activated"); } diff --git a/src/ecl/terminal.ts b/src/ecl/terminal.ts index 9d1041a..655745f 100644 --- a/src/ecl/terminal.ts +++ b/src/ecl/terminal.ts @@ -2,6 +2,7 @@ import { ClientTools } from "@hpcc-js/comms"; import * as vscode from "vscode"; import * as os from "os"; import { sessionManager } from "../hpccplatform/session"; +import { registerCommand } from "../telemetry"; const PATH_SEP = os.platform() === "win32" ? ";" : ":"; @@ -22,13 +23,13 @@ export class ECLTerminal { private constructor(ctx: vscode.ExtensionContext) { this._ctx = ctx; - ctx.subscriptions.push(vscode.commands.registerCommand("ecl.createTerminal", () => { + registerCommand(ctx, "ecl.createTerminal", () => { sessionManager.bestClientTools().then(clientTools => { return clientTools.version().then(() => clientTools); }).then((clientTools) => { eclTerminal(clientTools); }); - })); + }); } static attach(ctx: vscode.ExtensionContext): ECLTerminal { diff --git a/src/extension.ts b/src/extension.ts index 4d01067..6568323 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,6 @@ import * as vscode from "vscode"; import { initialize } from "./util/localize"; -import { activate as telemetryActivate, deactivate as telemetryDeactivate, reporter } from "./telemetry"; +import { activate as telemetryActivate, deactivate as telemetryDeactivate, logEvent, logError, time } from "./telemetry"; import { activate as notebookActivate } from "./notebook"; import { CredentialManager } from "./util/credentialManager"; import { checkForUpgrade } from "./util/versionNotification"; @@ -8,6 +8,7 @@ import { checkForUpgrade } from "./util/versionNotification"; export function activate(context: vscode.ExtensionContext): void { performance.mark("extension-start"); telemetryActivate(context); + const endActivation = time("extension.activate"); CredentialManager.attach(context); notebookActivate(context); initialize().then(() => { @@ -18,7 +19,11 @@ export function activate(context: vscode.ExtensionContext): void { // import("./dashy/main.js").then(({ activate }) => activate(context)) ]); }).then(() => { - reporter.sendTelemetryEvent("initialized"); + endActivation(); + logEvent("initialized"); + }, err => { + endActivation(); + logError("initialize.error", err); }); } diff --git a/src/hpccplatform/session.ts b/src/hpccplatform/session.ts index 5f47f04..abccd7f 100644 --- a/src/hpccplatform/session.ts +++ b/src/hpccplatform/session.ts @@ -6,6 +6,7 @@ import { LaunchConfigState, credentialManager, Credentials } from "../util/crede import { LaunchMode } from "../debugger/launchRequestArguments"; import localize from "../util/localize"; import { eclTempFile } from "../util/fs"; +import { registerCommand, logEvent, logError } from "../telemetry"; const logger = scopedLogger("hpccplatform/session.ts"); @@ -177,41 +178,45 @@ export class SessionManager { this._statusBarPin = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, Number.MIN_VALUE); this._statusBarPin.command = "hpccPlatform.pin"; - vscode.commands.registerCommand("hpccPlatform.pin", async () => { + registerCommand(this._ctx, "hpccPlatform.pin", async () => { const eclConfig = vscode.workspace.getConfiguration("ecl", null); const activeUri: string = vscode.window.activeTextEditor?.document?.uri.toString(true) || ""; if (activeUri) { const pinnedLaunchConfigurations = eclConfig.get("pinnedLaunchConfigurations"); + let pinned: boolean; if (pinnedLaunchConfigurations[activeUri]) { pinnedLaunchConfigurations[activeUri] = undefined; this._pinnedSession = undefined; + pinned = false; } else { this._pinnedSession = new Session(this.session.id, this.session.overriddenTargetCluster); pinnedLaunchConfigurations[activeUri] = { launchConfiguration: this.session.id, targetCluster: this.session.overriddenTargetCluster }; + pinned = true; } await eclConfig.update("pinnedLaunchConfigurations", pinnedLaunchConfigurations); this.updateSettings(); this.refreshStatusBar(); + logEvent("hpccPlatform.pin.toggle", { pinned: String(pinned) }); } }); - vscode.commands.registerCommand("hpccPlatform.switch", async () => { + registerCommand(this._ctx, "hpccPlatform.switch", async () => { this.switch(); }); - vscode.commands.registerCommand("hpccPlatform.switchTargetCluster", async () => { + registerCommand(this._ctx, "hpccPlatform.switchTargetCluster", async () => { this.switchTargetCluster(); }); - vscode.commands.registerCommand("hpccPlatform.eclwatch", async () => { + registerCommand(this._ctx, "hpccPlatform.eclwatch", async () => { vscode.env.openExternal(vscode.Uri.parse(`${this.session.baseUrl()}/esp/files/stub.htm`)); }); - vscode.commands.registerCommand("hpccPlatform.login", async () => { + registerCommand(this._ctx, "hpccPlatform.login", async () => { await this.login(); }); - vscode.commands.registerCommand("hpccPlatform.logout", async () => { + registerCommand(this._ctx, "hpccPlatform.logout", async () => { await this.logout(); }); @@ -240,6 +245,7 @@ export class SessionManager { const { targetCluster } = event.body; switch (event.event) { case "LaunchRequest": + logEvent("debug.launchRequest"); if (this.session.id !== id) { this.switchTo(id, targetCluster); } @@ -253,8 +259,21 @@ export class SessionManager { } }); + vscode.debug.onDidStartDebugSession(s => { + if (s.type === "ecl") { + logEvent("debug.session.start", { type: s.type }); + } + }); + + vscode.debug.onDidTerminateDebugSession(s => { + if (s.type === "ecl") { + logEvent("debug.session.terminate", { type: s.type }); + } + }); + vscode.workspace.onDidChangeConfiguration(e => { if (e.affectsConfiguration("launch")) { + logEvent("hpccPlatform.launchConfig.changed"); launchConfigurations(true); const currentConfig = launchConfiguration(this.session?.id); if (currentConfig && this.session) { @@ -361,6 +380,7 @@ export class SessionManager { nbSubmitURI(uri: vscode.Uri, mode: LaunchMode = "submit"): Promise | undefined { if (this.session) { return this.session.submit(uri, mode).then(wu => { + logEvent("workunit.created", { source: "notebook", mode }); this._onDidCreateWorkunit.fire({ source: "notebook", workunit: wu }); return wu; }); @@ -370,9 +390,11 @@ export class SessionManager { submitURI(uri: vscode.Uri, mode: LaunchMode = "submit") { if (this.session) { return this.session.submit(uri, mode).then(wu => { + logEvent("workunit.created", { source: "editor", mode }); this._onDidCreateWorkunit.fire({ source: "editor", workunit: wu }); return wu; }).catch(e => { + logError("workunit.create.error", e, { source: "editor", mode }); vscode.window.showErrorMessage(e.message); }); } @@ -400,9 +422,11 @@ export class SessionManager { await credentialManager.migrateLaunchConfigIfNeeded(rawConfig); } catch (error) { logger.error(`Failed to migrate credentials during switchTo: ${error}`); + logError("hpccPlatform.switchTo.migrateError", error); } } + const previousId = this.session?.id; if (!this.session || this.session.id !== id) { const configs = launchConfigurations().map(lc => lc.name); const launchID = configs.indexOf(id) >= 0 ? id : configs[0]; @@ -417,6 +441,10 @@ export class SessionManager { this.updateSettings(); + if (previousId !== this.session?.id) { + logEvent("hpccPlatform.switchTo", { hasTargetCluster: String(!!targetCluster) }); + } + if (this.session) { const storedCreds = await this.session.getStoredCredentials(); if (storedCreds?.password) { @@ -524,6 +552,7 @@ export class SessionManager { async login() { if (!this.session) { vscode.window.showWarningMessage(localize("No HPCC Platform connection available")); + logEvent("hpccPlatform.login.noSession"); return; } @@ -532,15 +561,18 @@ export class SessionManager { vscode.window.showInformationMessage(localize("Successfully logged in to HPCC Platform")); vscode.commands.executeCommand("hpccPlatform.userRefresh"); await this.refreshStatusBar(LaunchConfigState.Ok); + logEvent("hpccPlatform.login.success"); } catch (error) { vscode.window.showErrorMessage(localize("Login failed") + `: ${error instanceof Error ? error.message : String(error)}`); await this.refreshStatusBar(LaunchConfigState.CredentialsRequired); + logError("hpccPlatform.login.error", error); } } async logout() { if (!this.session) { vscode.window.showWarningMessage(localize("No HPCC Platform connection available")); + logEvent("hpccPlatform.logout.noSession"); return; } @@ -549,8 +581,10 @@ export class SessionManager { await this.switchTo(); vscode.window.showInformationMessage(localize("Successfully logged out from HPCC Platform")); + logEvent("hpccPlatform.logout.success"); } catch (error) { vscode.window.showErrorMessage(localize("Logout failed") + `: ${error instanceof Error ? error.message : String(error)}`); + logError("hpccPlatform.logout.error", error); } } diff --git a/src/kel/command.ts b/src/kel/command.ts index 8786d81..4e24e18 100644 --- a/src/kel/command.ts +++ b/src/kel/command.ts @@ -3,6 +3,7 @@ import * as vscode from "vscode"; import localize from "../util/localize"; import { locateClientTools, selectCTVersion } from "./clientTools"; import { Diagnostic } from "./diagnostic"; +import { registerCommand, logEvent, logError } from "../telemetry"; const logger = scopedLogger("kel/command.ts"); @@ -26,10 +27,10 @@ export class Commands { this._ctx = ctx; this._diagnostic = Diagnostic.attach(ctx); - ctx.subscriptions.push(vscode.commands.registerCommand("kel.checkSyntax", this.activeCheckSyntax, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("kel.generate", this.activeGenerate, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("kel.reveal", this.activeReveal, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("kel.selectCTVersion", selectCTVersion)); + registerCommand(ctx, "kel.checkSyntax", this.activeCheckSyntax, this); + registerCommand(ctx, "kel.generate", this.activeGenerate, this); + registerCommand(ctx, "kel.reveal", this.activeReveal, this); + registerCommand(ctx, "kel.selectCTVersion", selectCTVersion); } static attach(ctx: vscode.ExtensionContext): Commands { @@ -47,10 +48,12 @@ export class Commands { if (doc) { doc.save(); logger.debug("checkSyntax-start"); + logEvent("kel.checkSyntax.start"); this._diagnostic.set(doc.uri, [checking]); locateClientTools().then(clientTools => { if (!clientTools) { logger.debug("checkSyntax-noClientTools"); + logEvent("kel.checkSyntax.noClientTools"); this._diagnostic.set(doc.uri, [noClientTools]); } else { logger.debug("checkSyntax-check-start"); @@ -75,6 +78,9 @@ export class Commands { this._diagnostic.set(uri, mappedErrors[fp]); } logger.debug("checkSyntax-check-response-end"); + logEvent("kel.checkSyntax.success", {}, { errorCount: response.errors.all().length }); + }).catch(e => { + logError("kel.checkSyntax.error", e); }); } }); @@ -88,9 +94,16 @@ export class Commands { generate(doc?: vscode.TextDocument) { if (doc) { doc.save(); + logEvent("kel.generate.start"); locateClientTools().then(clientTools => { if (clientTools) { - clientTools.generate(doc.uri); + return clientTools.generate(doc.uri).then(() => { + logEvent("kel.generate.success"); + }, e => { + logError("kel.generate.error", e); + }); + } else { + logEvent("kel.generate.noClientTools"); } }); } diff --git a/src/kel/main.ts b/src/kel/main.ts index 51fa4e9..6bc8f95 100644 --- a/src/kel/main.ts +++ b/src/kel/main.ts @@ -5,12 +5,14 @@ import { DocumentSymbolProvider } from "./documentSymbolProvider"; import { Editor } from "./editor"; import { StatusBar } from "./status"; import { locateClientTools } from "./clientTools"; +import { logActivation, logEvent } from "../telemetry"; export function activate(ctx: vscode.ExtensionContext): void { - Diagnostic.attach(ctx); - Commands.attach(ctx); - Editor.attach(ctx); - StatusBar.attach(ctx); - DocumentSymbolProvider.attach(ctx); + logActivation("kel.Diagnostic", () => Diagnostic.attach(ctx)); + logActivation("kel.Commands", () => Commands.attach(ctx)); + logActivation("kel.Editor", () => Editor.attach(ctx)); + logActivation("kel.StatusBar", () => StatusBar.attach(ctx)); + logActivation("kel.DocumentSymbolProvider", () => DocumentSymbolProvider.attach(ctx)); locateClientTools(); + logEvent("kel.activated"); } diff --git a/src/notebook/controller/command.ts b/src/notebook/controller/command.ts index c9d263d..3e833da 100644 --- a/src/notebook/controller/command.ts +++ b/src/notebook/controller/command.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import localize from "../../util/localize"; import { serializer } from "./serializer"; +import { registerCommand } from "../../telemetry"; export let commands: Commands; export class Commands { @@ -9,10 +10,10 @@ export class Commands { private constructor(ctx: vscode.ExtensionContext) { this._ctx = ctx; - ctx.subscriptions.push(vscode.commands.registerCommand("notebook.cell.public", this.public, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("notebook.cell.private", this.private, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("notebook.cell.name", this.cellName, this)); - ctx.subscriptions.push(vscode.commands.registerCommand("notebook.cell.db", this.dbName, this)); + registerCommand(ctx, "notebook.cell.public", this.public, this); + registerCommand(ctx, "notebook.cell.private", this.private, this); + registerCommand(ctx, "notebook.cell.name", this.cellName, this); + registerCommand(ctx, "notebook.cell.db", this.dbName, this); // cell toolbar meta --- ctx.subscriptions.push(vscode.window.onDidChangeNotebookEditorSelection(e => { diff --git a/src/notebook/controller/controller.ts b/src/notebook/controller/controller.ts index 07011c2..8a50717 100644 --- a/src/notebook/controller/controller.ts +++ b/src/notebook/controller/controller.ts @@ -4,7 +4,7 @@ import * as path from "path"; import * as vscode from "vscode"; import { parseModule } from "@hpcc-js/observable-shim"; import { hashSum } from "@hpcc-js/util"; -import { reporter } from "../../telemetry/index"; +import { logEvent, logError } from "../../telemetry/index"; import { sessionManager } from "../../hpccplatform/session"; import { deleteFile, writeFile } from "../../util/fs"; import { launchConfiguration, LaunchRequestArguments } from "../../hpccplatform/launchConfig"; @@ -142,9 +142,10 @@ export class Controller { } catch (e) { } } } catch (e: any) { - if (e.message.indexOf("0003: Definition must contain EXPORT or SHARED value") >= 0) { + if (e.message?.indexOf("0003: Definition must contain EXPORT or SHARED value") >= 0) { outputItem = vscode.NotebookCellOutputItem.text("...no action..."); } else { + logError("notebook.executeECL.error", e); outputItem = vscode.NotebookCellOutputItem.error(e); } } finally { @@ -160,6 +161,7 @@ export class Controller { parseModule(serializer.ojsSource(cell)); } catch (e: any) { const msg = e?.message ?? "Unknown Error"; + logError("notebook.executeOJS.error", e, { language: cell.document.languageId }); return Promise.resolve(vscode.NotebookCellOutputItem.stderr(msg)); } const ojsOutput = serializer.ojsOutput(cell, notebook.uri, otherCells); @@ -187,16 +189,23 @@ export class Controller { private async executeCell(cell: vscode.NotebookCell, outputItem: vscode.NotebookCellOutputItem, notebook: vscode.NotebookDocument, otherCells: vscode.NotebookCell[]) { const execution = this._controller.createNotebookCellExecution(cell); execution.executionOrder = ++this._executionOrder; - execution.start(Date.now()); + const start = Date.now(); + execution.start(start); // serializer.node(cell).output = outputItem; await execution.replaceOutput([new vscode.NotebookCellOutput([outputItem])]); - execution.end([outputItem].every(op => op.mime.indexOf(".stderr") < 0), Date.now()); + const success = [outputItem].every(op => op.mime.indexOf(".stderr") < 0); + execution.end(success, Date.now()); + logEvent("notebook.cell.executed", { + language: cell.document.languageId, + success: String(success) + }, { duration: Date.now() - start }); } private async execute(cells: vscode.NotebookCell[], notebook: vscode.NotebookDocument): Promise { + logEvent("notebook.execute", {}, { cellCount: cells.length }); const outputItems = await Promise.all(cells.map(c => this.createOutputItem(c, notebook, []))); for (let i = 0; i < cells.length; ++i) { - reporter.sendTelemetryEvent("controller.execute.cell"); + logEvent("controller.execute.cell", { language: cells[i].document.languageId }); this.executeCell(cells[i], outputItems[i], notebook, cells.filter(c => c !== cells[i])); } } diff --git a/src/notebook/index.ts b/src/notebook/index.ts index 87bf490..f1f722a 100644 --- a/src/notebook/index.ts +++ b/src/notebook/index.ts @@ -2,37 +2,50 @@ import * as vscode from "vscode"; import { Controller } from "./controller/controller"; import { Serializer } from "./controller/serializer"; import { Commands } from "./controller/command"; +import { logActivation, logEvent } from "../telemetry"; export function activate(ctx: vscode.ExtensionContext) { - ctx.subscriptions.push(vscode.workspace.registerNotebookSerializer("ecl-notebook", Serializer.attach(), { - transientOutputs: false, - transientDocumentMetadata: { + logActivation("notebook.serializer", () => { + ctx.subscriptions.push(vscode.workspace.registerNotebookSerializer("ecl-notebook", Serializer.attach(), { + transientOutputs: false, + transientDocumentMetadata: { + } + })); + }); + logActivation("notebook.controller", () => { + ctx.subscriptions.push(new Controller()); + }); + logActivation("notebook.commands", () => Commands.attach(ctx)); + + vscode.workspace.onDidOpenNotebookDocument(nb => { + if (nb.notebookType === "ecl-notebook") { + logEvent("notebook.open", { type: nb.notebookType }, { cellCount: nb.cellCount }); + } + }, undefined, ctx.subscriptions); + + vscode.workspace.onDidCloseNotebookDocument(nb => { + if (nb.notebookType === "ecl-notebook") { + logEvent("notebook.close", { type: nb.notebookType }); } - })); - ctx.subscriptions.push(new Controller()); - Commands.attach(ctx); + }, undefined, ctx.subscriptions); vscode.window.onDidChangeNotebookEditorSelection(evt => { for (const cell of evt.selections) { } - // for (let i = evt.notebookEditor.selection.start; i < evt.notebookEditor.selection.end; ++i) { - // const cell = evt.notebookEditor.notebook.cellAt(i); - // console.log(JSON.stringify(cell.metadata)); - // } }); vscode.workspace.onDidChangeNotebookDocument(onDidChangeNotebookCells, undefined, ctx.subscriptions); + logEvent("notebook.activated"); } function onDidChangeNotebookCells(evt: vscode.NotebookDocumentChangeEvent) { - // e.contentChanges.forEach(change => { - // change.addedCells.forEach(cell => { - // // const cellMetadata = getCellMetadata(cell); - // // const edit = new vscode.WorkspaceEdit(); - // // // Don't edit the metadata directly, always get a clone (prevents accidental singletons and directly editing the objects). - // // const updatedMetadata: CellMetadata = { ...JSON.parse(JSON.stringify(cellMetadata || {})) }; - // // edit.set(cell.notebook.uri, [vscode.NotebookEdit.updateCellMetadata(cell.index, { ...(cell.metadata), custom: updatedMetadata })]); - // // vscode.workspace.applyEdit(edit); - // }); - // }); + let added = 0; + let removed = 0; + for (const change of evt.contentChanges) { + added += change.addedCells?.length ?? 0; + removed += change.removedCells?.length ?? 0; + } + if (added || removed) { + logEvent("notebook.cells.changed", {}, { added, removed }); + } } diff --git a/src/telemetry/index.ts b/src/telemetry/index.ts index db51d03..ca2405b 100644 --- a/src/telemetry/index.ts +++ b/src/telemetry/index.ts @@ -1,6 +1,9 @@ import * as vscode from "vscode"; import { TelemetryReporter } from "@vscode/extension-telemetry"; +type Props = Record | undefined; +type Measures = Record | undefined; + class MyTelemetryReporter extends TelemetryReporter { constructor(guid: string) { @@ -8,7 +11,9 @@ class MyTelemetryReporter extends TelemetryReporter { } dispose(): Promise { - reporter.sendTelemetryEvent("MyTelemetryReporter.dispose"); + try { + this.sendTelemetryEvent("MyTelemetryReporter.dispose"); + } catch { /* ignore */ } return super.dispose(); } } @@ -17,15 +22,145 @@ class MyTelemetryReporter extends TelemetryReporter { export let reporter: TelemetryReporter; export function activate(context: vscode.ExtensionContext) { - const extPackageJSON = context.extension.packageJSON; + const extPackageJSON = context.extension?.packageJSON; reporter = new MyTelemetryReporter("b785b2bb-e170-421b-8bd8-baaf895fe88b"); context.subscriptions.push(reporter); - reporter.sendTelemetryEvent("activate"); + logEvent("activate", { + extensionVersion: extPackageJSON?.version ?? "", + vscodeVersion: vscode.version ?? "", + platform: process?.platform ?? "", + }); } export function deactivate(): void { - reporter.sendTelemetryEvent("deactivate"); + logEvent("deactivate"); + try { + reporter?.dispose(); + } catch { /* ignore */ } +} + +// Safe helpers (never throw) ---------------------------------------------- + +export function logEvent(name: string, props?: Props, measurements?: Measures): void { + try { + reporter?.sendTelemetryEvent(name, props, measurements); + } catch { /* ignore */ } +} + +export function logError(name: string, error?: any, props?: Props, measurements?: Measures): void { + try { + const merged: Record = { ...(props ?? {}) }; + if (error) { + if (error.message !== undefined) merged.message = String(error.message); + if (error.code !== undefined) merged.code = String(error.code); + if (error.name !== undefined) merged.errorName = String(error.name); + } + reporter?.sendTelemetryErrorEvent(name, merged, measurements); + } catch { /* ignore */ } +} - reporter.dispose(); +/** + * Start a timer. Call the returned function when done to emit a `.duration` + * measurement on a telemetry event. Optionally pass extra props/measures. + */ +export function time(name: string): (extraProps?: Props, extraMeasures?: Measures) => void { + const start = Date.now(); + return (extraProps?: Props, extraMeasures?: Measures) => { + const duration = Date.now() - start; + logEvent(name, extraProps, { duration, ...(extraMeasures ?? {}) }); + }; +} + +/** + * Wraps `vscode.commands.registerCommand` so that every invocation of the + * registered command emits a telemetry event including its duration and + * automatically reports any thrown errors. + */ +export function registerCommand( + ctx: vscode.ExtensionContext, + id: string, + handler: (...args: any[]) => any, + thisArg?: any +): vscode.Disposable { + const wrapped = (...args: any[]) => { + const start = Date.now(); + logEvent("command", { id }); + try { + const result = handler.apply(thisArg, args); + if (result && typeof result.then === "function") { + return Promise.resolve(result).then( + value => { + logEvent("command.success", { id }, { duration: Date.now() - start }); + return value; + }, + err => { + logError("command.error", err, { id }, { duration: Date.now() - start }); + throw err; + } + ); + } + logEvent("command.success", { id }, { duration: Date.now() - start }); + return result; + } catch (err) { + logError("command.error", err, { id }, { duration: Date.now() - start }); + throw err; + } + }; + const disposable = vscode.commands.registerCommand(id, wrapped); + ctx.subscriptions.push(disposable); + return disposable; +} + +/** + * Same as `registerCommand` but for text editor commands. + */ +export function registerTextEditorCommand( + ctx: vscode.ExtensionContext, + id: string, + handler: (editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) => any, + thisArg?: any +): vscode.Disposable { + const wrapped = (editor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) => { + const start = Date.now(); + logEvent("command", { id, kind: "textEditor" }); + try { + const result = handler.apply(thisArg, [editor, edit, ...args]); + if (result && typeof result.then === "function") { + return Promise.resolve(result).then( + value => { + logEvent("command.success", { id, kind: "textEditor" }, { duration: Date.now() - start }); + return value; + }, + err => { + logError("command.error", err, { id, kind: "textEditor" }, { duration: Date.now() - start }); + throw err; + } + ); + } + logEvent("command.success", { id, kind: "textEditor" }, { duration: Date.now() - start }); + return result; + } catch (err) { + logError("command.error", err, { id, kind: "textEditor" }, { duration: Date.now() - start }); + throw err; + } + }; + const disposable = vscode.commands.registerTextEditorCommand(id, wrapped); + ctx.subscriptions.push(disposable); + return disposable; +} + +/** + * Records a feature/module activation including a duration measurement. + */ +export function logActivation(name: string, fn: () => void): void { + const end = time(`activation.${name}`); + try { + fn(); + end(); + } catch (err) { + end(); + logError(`activation.${name}.error`, err); + throw err; + } } diff --git a/src/util/credentialManager.ts b/src/util/credentialManager.ts index f199255..8aec2c9 100644 --- a/src/util/credentialManager.ts +++ b/src/util/credentialManager.ts @@ -1,6 +1,7 @@ import * as vscode from "vscode"; import { scopedLogger } from "@hpcc-js/util"; import { LaunchRequestArguments } from "../debugger/launchRequestArguments"; +import { logEvent, logError } from "../telemetry"; const logger = scopedLogger("ecl:credentials"); @@ -122,6 +123,7 @@ export class CredentialManager { const allKeys = await this.listStoredCredentials(); await Promise.all(allKeys.map(key => this.context.secrets.delete(key))); + logEvent("credentials.deleteAll", {}, { count: allKeys.length }); } async migrateLaunchConfigIfNeeded(config: LaunchRequestArguments): Promise { @@ -139,6 +141,7 @@ export class CredentialManager { throw new Error("Failed to verify stored password"); } logger.debug(`Migrated credentials for ${config.user}@${baseUrl} to secure storage`); + logEvent("credentials.migrated"); credentialManagerCache.delete(storageKey); const credentials = await this.getCredentials(baseUrl, config.user); @@ -146,6 +149,7 @@ export class CredentialManager { await this.removePasswordFromLaunchConfig(config); } catch (error) { logger.error(`Failed to migrate credentials for ${config.user}@${baseUrl}: ${error}`); + logError("credentials.migrate.error", error); throw error; } } diff --git a/src/util/versionNotification.ts b/src/util/versionNotification.ts index d921e47..342cfeb 100644 --- a/src/util/versionNotification.ts +++ b/src/util/versionNotification.ts @@ -1,5 +1,6 @@ import * as vscode from "vscode"; import localize from "./localize"; +import { logEvent } from "../telemetry"; const LAST_VERSION_KEY = "ecl.lastVersion"; @@ -14,6 +15,10 @@ export async function checkForUpgrade(context: vscode.ExtensionContext): Promise const lastVersion = context.globalState.get(LAST_VERSION_KEY); if (lastVersion !== currentVersion) { + logEvent("versionNotification.upgradeDetected", { + currentVersion, + previousVersion: lastVersion ?? "none" + }); await showWhatsNewNotification(context, currentVersion, lastVersion); } @@ -48,8 +53,13 @@ async function showWhatsNewNotification( ...buttons ); + logEvent("versionNotification.shown", { currentVersion }); + if (action === learnMore && note.learnMoreUrl) { + logEvent("versionNotification.learnMore", { currentVersion }); await openLearnMoreUrl(context, note.learnMoreUrl); + } else if (action === dismiss) { + logEvent("versionNotification.dismissed", { currentVersion }); } }