From 1f0d7f572af6a0916aae43f1762cb58e9052cf63 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 24 Jan 2025 14:36:54 +0530 Subject: [PATCH 01/38] Enhance Actions Hub: Add support for environment retrieval and localization updates --- l10n/bundle.l10n.json | 1 + .../vscode-powerplatform.xlf | 3 ++ src/client/extension.ts | 6 +-- .../actions-hub/ActionsHubTreeDataProvider.ts | 48 ++++++++++++++----- .../power-pages/actions-hub/Constants.ts | 6 ++- .../telemetryConstants.ts | 1 + 6 files changed, 48 insertions(+), 17 deletions(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index d5fe4b7b..8ca1cb0a 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -181,6 +181,7 @@ "Active Sites": "Active Sites", "Inactive Sites": "Inactive Sites", "No sites found": "No sites found", + "No environments found": "No environments found", "PAC Telemetry enabled": "PAC Telemetry enabled", "Failed to enable PAC telemetry.": "Failed to enable PAC telemetry.", "PAC Telemetry disabled": "PAC Telemetry disabled", diff --git a/loc/translations-export/vscode-powerplatform.xlf b/loc/translations-export/vscode-powerplatform.xlf index 347bcc10..62c771cb 100644 --- a/loc/translations-export/vscode-powerplatform.xlf +++ b/loc/translations-export/vscode-powerplatform.xlf @@ -324,6 +324,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID) No Website Data Found in Current Directory. Please switch to a directory that contains the PAC downloaded website data to continue. + + No environments found + No page templates found diff --git a/src/client/extension.ts b/src/client/extension.ts index 030ce728..24ab5c8a 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -295,7 +295,7 @@ export async function activate( const workspaceFolderWatcher = vscode.workspace.onDidChangeWorkspaceFolders(handleWorkspaceFolderChange); _context.subscriptions.push(workspaceFolderWatcher); - initializeActionsHub(context); + initializeActionsHub(context, pacTerminal); if (shouldEnableDebugger()) { activateDebugger(context, _telemetry); @@ -305,14 +305,14 @@ export async function activate( oneDSLoggerWrapper.getLogger().traceInfo("activated"); } -function initializeActionsHub(context: vscode.ExtensionContext) { +function initializeActionsHub(context: vscode.ExtensionContext, pacTerminal: PacTerminal) { //TODO: Initialize this based on ECS feature flag const actionsHubEnabled = false; vscode.commands.executeCommand("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", actionsHubEnabled); if (actionsHubEnabled) { - ActionsHubTreeDataProvider.initialize(context, _telemetry); + ActionsHubTreeDataProvider.initialize(context, pacTerminal); } } diff --git a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts index 42eb5d88..75b0d63c 100644 --- a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts +++ b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts @@ -6,40 +6,64 @@ import * as vscode from "vscode"; import { ActionsHubTreeItem } from "./tree-items/ActionsHubTreeItem"; import { OtherSitesGroupTreeItem } from "./tree-items/OtherSitesGroupTreeItem"; -import { ITelemetry } from "../../../common/OneDSLoggerTelemetry/telemetry/ITelemetry"; import { Constants } from "./Constants"; import { oneDSLoggerWrapper } from "../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; +import { PacTerminal } from "../../lib/PacTerminal"; +import { SUCCESS } from "../../../common/constants"; +import { EnvironmentGroupTreeItem } from "./tree-items/EnvironmentGroupTreeItem"; +import { IEnvironmentInfo } from "./models/IEnvironmentInfo"; +import { OrganizationFriendlyNameKey } from "../../../common/OneDSLoggerTelemetry/telemetryConstants"; export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider { private readonly _disposables: vscode.Disposable[] = []; private readonly _context: vscode.ExtensionContext; - private readonly _telemetry: ITelemetry; + private readonly _pacTerminal: PacTerminal; - private constructor(context: vscode.ExtensionContext, telemetry: ITelemetry) { + private constructor(context: vscode.ExtensionContext, pacTerminal: PacTerminal) { this._disposables.push( vscode.window.registerTreeDataProvider("powerpages.actionsHub", this) ); this._context = context; - this._telemetry = telemetry; + this._pacTerminal = pacTerminal; } - public static initialize(context: vscode.ExtensionContext, telemetry: ITelemetry): void { - new ActionsHubTreeDataProvider(context, telemetry); - - telemetry.sendTelemetryEvent(Constants.EventNames.ACTIONS_HUB_INITIALIZED); + public static initialize(context: vscode.ExtensionContext, pacTerminal: PacTerminal): ActionsHubTreeDataProvider { + const actionsHubTreeDataProvider = new ActionsHubTreeDataProvider(context, pacTerminal); oneDSLoggerWrapper.getLogger().traceInfo(Constants.EventNames.ACTIONS_HUB_INITIALIZED); + + return actionsHubTreeDataProvider; } getTreeItem(element: ActionsHubTreeItem): vscode.TreeItem | Thenable { return element; } - getChildren(element?: ActionsHubTreeItem | undefined): vscode.ProviderResult { + async getChildren(element?: ActionsHubTreeItem): Promise { if (!element) { - return [ - new OtherSitesGroupTreeItem() - ]; + try { + const pacWrapper = this._pacTerminal.getWrapper(); + const pacActiveAuth = await pacWrapper?.activeAuth(); + let orgFriendlyName = Constants.Strings.NO_ENVIRONMENTS_FOUND; // Login experience scenario + let currentEnvInfo: IEnvironmentInfo = { currentEnvironmentName: orgFriendlyName }; + + if (pacActiveAuth && pacActiveAuth.Status === SUCCESS) { + // Currently only filtering out the OrganizationFriendlyNameKey, but we can all the keys for showing session info + const result = pacActiveAuth.Results?.find(obj => obj.Key === OrganizationFriendlyNameKey); + if (result) { + orgFriendlyName = result.Value; + currentEnvInfo = { currentEnvironmentName: orgFriendlyName }; + } + } + + return [ + new EnvironmentGroupTreeItem(currentEnvInfo, this._context), + new OtherSitesGroupTreeItem() + ]; + } catch (error) { + oneDSLoggerWrapper.getLogger().traceError(Constants.EventNames.ACTIONS_HUB_CURRENT_ENV_FETCH_FAILED, error as string, error as Error, { methodName: this.getChildren }, {}); + return null; + } } else { return []; } diff --git a/src/client/power-pages/actions-hub/Constants.ts b/src/client/power-pages/actions-hub/Constants.ts index 5819c92e..9f1e5a0c 100644 --- a/src/client/power-pages/actions-hub/Constants.ts +++ b/src/client/power-pages/actions-hub/Constants.ts @@ -25,9 +25,11 @@ export const Constants = { OTHER_SITES: vscode.l10n.t("Other Sites"), ACTIVE_SITES: vscode.l10n.t("Active Sites"), INACTIVE_SITES: vscode.l10n.t("Inactive Sites"), - NO_SITES_FOUND: vscode.l10n.t("No sites found") + NO_SITES_FOUND: vscode.l10n.t("No sites found"), + NO_ENVIRONMENTS_FOUND: vscode.l10n.t("No environments found") }, EventNames: { - ACTIONS_HUB_INITIALIZED: "actionsHubInitialized" + ACTIONS_HUB_INITIALIZED: "actionsHubInitialized", + ACTIONS_HUB_CURRENT_ENV_FETCH_FAILED: "Actions Hub Current Environment Fetch Failed", } }; diff --git a/src/common/OneDSLoggerTelemetry/telemetryConstants.ts b/src/common/OneDSLoggerTelemetry/telemetryConstants.ts index fc98d128..63b1f5f9 100644 --- a/src/common/OneDSLoggerTelemetry/telemetryConstants.ts +++ b/src/common/OneDSLoggerTelemetry/telemetryConstants.ts @@ -42,3 +42,4 @@ export const CUSTOM_TELEMETRY_FOR_POWER_PAGES_SETTING_NAME = 'enableTelemetry'; export const AadIdKey= 'Entra ID Object Id:'; export const EnvIdKey = "Environment Id:"; export const TenantIdKey = "Tenant Id:"; +export const OrganizationFriendlyNameKey = "Organization Friendly Name:"; From 7a2f296b56e9fa30fa9f4e917b5cf95243ed9b09 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 24 Jan 2025 14:37:56 +0530 Subject: [PATCH 02/38] Add unit tests for ActionsHubTreeDataProvider functionality --- .../ActionsHubTreeDataProvider.test.ts | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts new file mode 100644 index 00000000..afb1448c --- /dev/null +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -0,0 +1,85 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { expect } from "chai"; + import * as sinon from "sinon"; + import * as vscode from "vscode"; + import { ActionsHubTreeDataProvider } from "../../../../power-pages/actions-hub/ActionsHubTreeDataProvider"; + import { oneDSLoggerWrapper } from "../../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; + import { Constants } from "../../../../power-pages/actions-hub/Constants"; +// import { EnvironmentGroupTreeItem } from "../../../../power-pages/actions-hub/tree-items/EnvironmentGroupTreeItem"; +// import { OtherSitesGroupTreeItem } from "../../../../power-pages/actions-hub/tree-items/OtherSitesGroupTreeItem"; +import { ActionsHubTreeItem } from "../../../../power-pages/actions-hub/tree-items/ActionsHubTreeItem"; +import { PacTerminal } from "../../../../lib/PacTerminal"; + + describe("ActionsHubTreeDataProvider", () => { + let context: vscode.ExtensionContext; + let pacTerminal: PacTerminal; + let actionsHubTreeDataProvider: ActionsHubTreeDataProvider; + + beforeEach(() => { + context = {} as vscode.ExtensionContext; + pacTerminal = {} as PacTerminal; + actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should initialize and log initialization event", () => { + const traceInfoStub = sinon.stub(oneDSLoggerWrapper.getLogger(), "traceInfo"); + ActionsHubTreeDataProvider.initialize(context, pacTerminal); + expect(traceInfoStub.calledWith(Constants.EventNames.ACTIONS_HUB_INITIALIZED)).to.be.true; + }); + + it("should return the element in getTreeItem", () => { + const element = {} as ActionsHubTreeItem; + const result = actionsHubTreeDataProvider.getTreeItem(element); + expect(result).to.equal(element); + }); + + //TODO: Fix this test for pacWrapper + // it("should return environment and other sites group tree items in getChildren when no element is passed", async () => { + // const pacWrapper = { + // activeAuth: sinon.stub().resolves({ Status: "Success", Results: [{ Key: "OrganizationFriendlyNameKey", Value: "TestOrg" }] }) + // }; + // sinon.stub(pacTerminal, "getWrapper").returns(pacWrapper); + + // const result = await actionsHubTreeDataProvider.getChildren(); + // expect(result).to.not.be.null; + // expect(result).to.not.be.undefined; + // expect(result).to.have.lengthOf(2); + // expect(result![0]).to.be.instanceOf(EnvironmentGroupTreeItem); + // expect(result![1]).to.be.instanceOf(OtherSitesGroupTreeItem); + // }); + + it("should return null in getChildren when an error occurs", async () => { + sinon.stub(pacTerminal, "getWrapper").throws(new Error("Test Error")); + const traceErrorStub = sinon.stub(oneDSLoggerWrapper.getLogger(), "traceError"); + + const result = await actionsHubTreeDataProvider.getChildren(); + expect(result).to.be.null; + expect(traceErrorStub.calledWith(Constants.EventNames.ACTIONS_HUB_CURRENT_ENV_FETCH_FAILED)).to.be.true; + }); + + it("should return an empty array in getChildren when an element is passed", async () => { + const element = {} as ActionsHubTreeItem; + const result = await actionsHubTreeDataProvider.getChildren(element); + expect(result).to.be.an("array").that.is.empty; + }); + + it("should dispose all disposables", () => { + const disposable1 = { dispose: sinon.spy() }; + const disposable2 = { dispose: sinon.spy() }; + actionsHubTreeDataProvider["_disposables"].push(disposable1 as vscode.Disposable, disposable2 as vscode.Disposable); + + actionsHubTreeDataProvider.dispose(); + expect(disposable1.dispose.calledOnce).to.be.true; + expect(disposable2.dispose.calledOnce).to.be.true; + }); + + + }); From fa5a7d18d5732d15cf76b146c97f4a4755277585 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Mon, 27 Jan 2025 20:59:34 +0530 Subject: [PATCH 03/38] Add AuthManager class and AuthInfo interface for authentication handling --- src/client/AuthManager.ts | 28 +++++++++++++++++++ .../power-pages/actions-hub/Constants.ts | 19 +++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 src/client/AuthManager.ts diff --git a/src/client/AuthManager.ts b/src/client/AuthManager.ts new file mode 100644 index 00000000..dbea5e32 --- /dev/null +++ b/src/client/AuthManager.ts @@ -0,0 +1,28 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import { AuthInfo } from "./power-pages/actions-hub/Constants"; + +class AuthManager { + private static instance: AuthManager; + private authInfo: AuthInfo | null = null; + + public static getInstance(): AuthManager { + if (!AuthManager.instance) { + AuthManager.instance = new AuthManager(); + } + return AuthManager.instance; + } + + public setAuthInfo(authInfo: AuthInfo): void { + this.authInfo = authInfo; + } + + public getAuthInfo(): AuthInfo | null { + return this.authInfo; + } +} + +export const authManager = AuthManager.getInstance(); diff --git a/src/client/power-pages/actions-hub/Constants.ts b/src/client/power-pages/actions-hub/Constants.ts index 9f1e5a0c..703e7b46 100644 --- a/src/client/power-pages/actions-hub/Constants.ts +++ b/src/client/power-pages/actions-hub/Constants.ts @@ -33,3 +33,22 @@ export const Constants = { ACTIONS_HUB_CURRENT_ENV_FETCH_FAILED: "Actions Hub Current Environment Fetch Failed", } }; + +export interface AuthInfo { + userType: string; + cloud: string; + tenantId: string; + tenantCountry: string; + user: string; + entraIdObjectId: string; + puid: string; + userCountryRegion: string; + tokenExpires: string; + authority: string; + environmentGeo: string; + environmentId: string; + environmentType: string; + organizationId: string; + organizationUniqueName: string; + organizationFriendlyName: string; +} From a94ac275dd8c8cdaa462793fb6c0e3298090c516 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Mon, 27 Jan 2025 21:00:11 +0530 Subject: [PATCH 04/38] Enhance authentication handling: Add auth info extraction and update environment info retrieval in Actions Hub --- src/client/extension.ts | 10 +- .../actions-hub/ActionsHubTreeDataProvider.ts | 21 +-- .../tree-items/EnvironmentGroupTreeItem.ts | 1 + src/client/power-pages/commonUtility.ts | 28 +++ .../ActionsHubTreeDataProvider.test.ts | 173 +++++++++++------- .../telemetryConstants.ts | 19 +- 6 files changed, 166 insertions(+), 86 deletions(-) diff --git a/src/client/extension.ts b/src/client/extension.ts index 24ab5c8a..67a603b9 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -48,6 +48,8 @@ import { getECSOrgLocationValue, getWorkspaceFolders } from "../common/utilities import { CliAcquisitionContext } from "./lib/CliAcquisitionContext"; import { PreviewSite, SITE_PREVIEW_COMMAND_ID } from "./power-pages/preview-site/PreviewSite"; import { ActionsHubTreeDataProvider } from "./power-pages/actions-hub/ActionsHubTreeDataProvider"; +import { authManager } from "./AuthManager"; +import { extractAuthInfo } from "./power-pages/commonUtility"; let client: LanguageClient; let _context: vscode.ExtensionContext; @@ -305,10 +307,16 @@ export async function activate( oneDSLoggerWrapper.getLogger().traceInfo("activated"); } -function initializeActionsHub(context: vscode.ExtensionContext, pacTerminal: PacTerminal) { +async function initializeActionsHub(context: vscode.ExtensionContext, pacTerminal: PacTerminal) { //TODO: Initialize this based on ECS feature flag const actionsHubEnabled = false; + const pacActiveAuth = await pacTerminal.getWrapper()?.activeAuth(); + if (pacActiveAuth && pacActiveAuth.Status === SUCCESS) { + const authInfo = extractAuthInfo(pacActiveAuth.Results); + authManager.setAuthInfo(authInfo); + } + vscode.commands.executeCommand("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", actionsHubEnabled); if (actionsHubEnabled) { diff --git a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts index 75b0d63c..4193fba0 100644 --- a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts +++ b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts @@ -9,10 +9,9 @@ import { OtherSitesGroupTreeItem } from "./tree-items/OtherSitesGroupTreeItem"; import { Constants } from "./Constants"; import { oneDSLoggerWrapper } from "../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; import { PacTerminal } from "../../lib/PacTerminal"; -import { SUCCESS } from "../../../common/constants"; import { EnvironmentGroupTreeItem } from "./tree-items/EnvironmentGroupTreeItem"; import { IEnvironmentInfo } from "./models/IEnvironmentInfo"; -import { OrganizationFriendlyNameKey } from "../../../common/OneDSLoggerTelemetry/telemetryConstants"; +import { authManager } from "../../AuthManager"; export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider { private readonly _disposables: vscode.Disposable[] = []; @@ -42,20 +41,16 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider { if (!element) { try { - const pacWrapper = this._pacTerminal.getWrapper(); - const pacActiveAuth = await pacWrapper?.activeAuth(); - let orgFriendlyName = Constants.Strings.NO_ENVIRONMENTS_FOUND; // Login experience scenario - let currentEnvInfo: IEnvironmentInfo = { currentEnvironmentName: orgFriendlyName }; - if (pacActiveAuth && pacActiveAuth.Status === SUCCESS) { - // Currently only filtering out the OrganizationFriendlyNameKey, but we can all the keys for showing session info - const result = pacActiveAuth.Results?.find(obj => obj.Key === OrganizationFriendlyNameKey); - if (result) { - orgFriendlyName = result.Value; - currentEnvInfo = { currentEnvironmentName: orgFriendlyName }; - } + const orgFriendlyName = Constants.Strings.NO_ENVIRONMENTS_FOUND; // Login experience scenario + let currentEnvInfo: IEnvironmentInfo = { currentEnvironmentName: orgFriendlyName }; + const authInfo = authManager.getAuthInfo(); + if (authInfo) { + currentEnvInfo = { currentEnvironmentName: authInfo.organizationFriendlyName }; } + //TODO: Handle the case when the user is not logged in + return [ new EnvironmentGroupTreeItem(currentEnvInfo, this._context), new OtherSitesGroupTreeItem() diff --git a/src/client/power-pages/actions-hub/tree-items/EnvironmentGroupTreeItem.ts b/src/client/power-pages/actions-hub/tree-items/EnvironmentGroupTreeItem.ts index 90bba386..ac0e8741 100644 --- a/src/client/power-pages/actions-hub/tree-items/EnvironmentGroupTreeItem.ts +++ b/src/client/power-pages/actions-hub/tree-items/EnvironmentGroupTreeItem.ts @@ -9,6 +9,7 @@ import { IEnvironmentInfo } from "../models/IEnvironmentInfo"; import { Constants } from "../Constants"; export class EnvironmentGroupTreeItem extends ActionsHubTreeItem { + environmentInfo: any; constructor(environmentInfo: IEnvironmentInfo, context: vscode.ExtensionContext) { super( environmentInfo.currentEnvironmentName, diff --git a/src/client/power-pages/commonUtility.ts b/src/client/power-pages/commonUtility.ts index cdb8a8fd..8a8ef67c 100644 --- a/src/client/power-pages/commonUtility.ts +++ b/src/client/power-pages/commonUtility.ts @@ -7,6 +7,8 @@ import path from "path"; import * as vscode from "vscode"; import { removeTrailingSlash } from "../../debugger/utils"; import * as Constants from "./constants"; +import { AUTH_KEYS } from "../../common/OneDSLoggerTelemetry/telemetryConstants"; +import { AuthInfo } from "./actions-hub/Constants"; export interface IFileProperties { fileCompleteName?: string, @@ -197,3 +199,29 @@ export function getRegExPattern(fileNameArray: string[]): RegExp[] { return patterns; } + +export function extractAuthInfo(results: any[]): AuthInfo { + return { + userType: findAuthValue(results, AUTH_KEYS.USER_TYPE), + cloud: findAuthValue(results, AUTH_KEYS.CLOUD), + tenantId: findAuthValue(results, AUTH_KEYS.TENANT_ID), + tenantCountry: findAuthValue(results, AUTH_KEYS.TENANT_COUNTRY), + user: findAuthValue(results, AUTH_KEYS.USER), + entraIdObjectId: findAuthValue(results, AUTH_KEYS.ENTRA_ID_OBJECT_ID), + puid: findAuthValue(results, AUTH_KEYS.PUID), + userCountryRegion: findAuthValue(results, AUTH_KEYS.USER_COUNTRY_REGION), + tokenExpires: findAuthValue(results, AUTH_KEYS.TOKEN_EXPIRES), + authority: findAuthValue(results, AUTH_KEYS.AUTHORITY), + environmentGeo: findAuthValue(results, AUTH_KEYS.ENVIRONMENT_GEO), + environmentId: findAuthValue(results, AUTH_KEYS.ENVIRONMENT_ID), + environmentType: findAuthValue(results, AUTH_KEYS.ENVIRONMENT_TYPE), + organizationId: findAuthValue(results, AUTH_KEYS.ORGANIZATION_ID), + organizationUniqueName: findAuthValue(results, AUTH_KEYS.ORGANIZATION_UNIQUE_NAME), + organizationFriendlyName: findAuthValue(results, AUTH_KEYS.ORGANIZATION_FRIENDLY_NAME) + }; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function findAuthValue(results: any[], key: string): string { + return results?.find(obj => obj.Key === key)?.Value ?? ''; +} diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index afb1448c..fe372c77 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -9,77 +9,108 @@ import { expect } from "chai"; import { ActionsHubTreeDataProvider } from "../../../../power-pages/actions-hub/ActionsHubTreeDataProvider"; import { oneDSLoggerWrapper } from "../../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; import { Constants } from "../../../../power-pages/actions-hub/Constants"; -// import { EnvironmentGroupTreeItem } from "../../../../power-pages/actions-hub/tree-items/EnvironmentGroupTreeItem"; -// import { OtherSitesGroupTreeItem } from "../../../../power-pages/actions-hub/tree-items/OtherSitesGroupTreeItem"; +import { EnvironmentGroupTreeItem } from "../../../../power-pages/actions-hub/tree-items/EnvironmentGroupTreeItem"; +import { OtherSitesGroupTreeItem } from "../../../../power-pages/actions-hub/tree-items/OtherSitesGroupTreeItem"; import { ActionsHubTreeItem } from "../../../../power-pages/actions-hub/tree-items/ActionsHubTreeItem"; import { PacTerminal } from "../../../../lib/PacTerminal"; +import { authManager } from "../../../../AuthManager"; - describe("ActionsHubTreeDataProvider", () => { - let context: vscode.ExtensionContext; - let pacTerminal: PacTerminal; - let actionsHubTreeDataProvider: ActionsHubTreeDataProvider; - - beforeEach(() => { - context = {} as vscode.ExtensionContext; - pacTerminal = {} as PacTerminal; - actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); - }); - - afterEach(() => { - sinon.restore(); - }); - - it("should initialize and log initialization event", () => { - const traceInfoStub = sinon.stub(oneDSLoggerWrapper.getLogger(), "traceInfo"); - ActionsHubTreeDataProvider.initialize(context, pacTerminal); - expect(traceInfoStub.calledWith(Constants.EventNames.ACTIONS_HUB_INITIALIZED)).to.be.true; - }); - - it("should return the element in getTreeItem", () => { - const element = {} as ActionsHubTreeItem; - const result = actionsHubTreeDataProvider.getTreeItem(element); - expect(result).to.equal(element); - }); - - //TODO: Fix this test for pacWrapper - // it("should return environment and other sites group tree items in getChildren when no element is passed", async () => { - // const pacWrapper = { - // activeAuth: sinon.stub().resolves({ Status: "Success", Results: [{ Key: "OrganizationFriendlyNameKey", Value: "TestOrg" }] }) - // }; - // sinon.stub(pacTerminal, "getWrapper").returns(pacWrapper); - - // const result = await actionsHubTreeDataProvider.getChildren(); - // expect(result).to.not.be.null; - // expect(result).to.not.be.undefined; - // expect(result).to.have.lengthOf(2); - // expect(result![0]).to.be.instanceOf(EnvironmentGroupTreeItem); - // expect(result![1]).to.be.instanceOf(OtherSitesGroupTreeItem); - // }); - - it("should return null in getChildren when an error occurs", async () => { - sinon.stub(pacTerminal, "getWrapper").throws(new Error("Test Error")); - const traceErrorStub = sinon.stub(oneDSLoggerWrapper.getLogger(), "traceError"); - - const result = await actionsHubTreeDataProvider.getChildren(); - expect(result).to.be.null; - expect(traceErrorStub.calledWith(Constants.EventNames.ACTIONS_HUB_CURRENT_ENV_FETCH_FAILED)).to.be.true; - }); - - it("should return an empty array in getChildren when an element is passed", async () => { - const element = {} as ActionsHubTreeItem; - const result = await actionsHubTreeDataProvider.getChildren(element); - expect(result).to.be.an("array").that.is.empty; - }); - - it("should dispose all disposables", () => { - const disposable1 = { dispose: sinon.spy() }; - const disposable2 = { dispose: sinon.spy() }; - actionsHubTreeDataProvider["_disposables"].push(disposable1 as vscode.Disposable, disposable2 as vscode.Disposable); - - actionsHubTreeDataProvider.dispose(); - expect(disposable1.dispose.calledOnce).to.be.true; - expect(disposable2.dispose.calledOnce).to.be.true; - }); - - - }); +describe("ActionsHubTreeDataProvider", () => { + let context: vscode.ExtensionContext; + let pacTerminal: PacTerminal; + let actionsHubTreeDataProvider: ActionsHubTreeDataProvider; + + beforeEach(() => { + context = {} as vscode.ExtensionContext; + pacTerminal = {} as PacTerminal; + actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + }); + + afterEach(() => { + sinon.restore(); + }); + + it("should initialize and log initialization event", () => { + const traceInfoStub = sinon.stub(oneDSLoggerWrapper.getLogger(), "traceInfo"); + ActionsHubTreeDataProvider.initialize(context, pacTerminal); + expect(traceInfoStub.calledWith(Constants.EventNames.ACTIONS_HUB_INITIALIZED)).to.be.true; + }); + + it("should return the element in getTreeItem", () => { + const element = {} as ActionsHubTreeItem; + const result = actionsHubTreeDataProvider.getTreeItem(element); + expect(result).to.equal(element); + }); + + it("should return environment and other sites group tree items in getChildren when no element is passed", async () => { + const authInfoStub = sinon.stub(authManager, "getAuthInfo").returns({ + organizationFriendlyName: "TestOrg", + userType: "", + cloud: "", + tenantId: "", + tenantCountry: "", + user: "", + entraIdObjectId: "", + puid: "", + userCountryRegion: "", + tokenExpires: "", + authority: "", + environmentGeo: "", + environmentId: "", + environmentType: "", + organizationId: "", + organizationUniqueName: "" + }); + const result = await actionsHubTreeDataProvider.getChildren(); + + expect(result).to.not.be.null; + expect(result).to.not.be.undefined; + expect(result).to.have.lengthOf(2); + expect(result![0]).to.be.instanceOf(EnvironmentGroupTreeItem); + expect(result![1]).to.be.instanceOf(OtherSitesGroupTreeItem); + + authInfoStub.restore(); + }); + + it("should return environment group tree item with default name when no auth info is available", async () => { + const authInfoStub = sinon.stub(authManager, "getAuthInfo").returns(null); + const result = await actionsHubTreeDataProvider.getChildren(); + + expect(result).to.not.be.null; + expect(result).to.not.be.undefined; + expect(result).to.have.lengthOf(2); + expect(result![0]).to.be.instanceOf(EnvironmentGroupTreeItem); + expect((result![0] as EnvironmentGroupTreeItem).environmentInfo.currentEnvironmentName).to.equal(Constants.Strings.NO_ENVIRONMENTS_FOUND); + expect(result![1]).to.be.instanceOf(OtherSitesGroupTreeItem); + + authInfoStub.restore(); + }); + + it("should return null in getChildren when an error occurs", async () => { + const authInfoStub = sinon.stub(authManager, "getAuthInfo").throws(new Error("Test Error")); + const traceErrorStub = sinon.stub(oneDSLoggerWrapper.getLogger(), "traceError"); + + const result = await actionsHubTreeDataProvider.getChildren(); + expect(result).to.be.null; + expect(traceErrorStub.calledWith(Constants.EventNames.ACTIONS_HUB_CURRENT_ENV_FETCH_FAILED)).to.be.true; + + authInfoStub.restore(); + traceErrorStub.restore(); + }); + + it("should return an empty array in getChildren when an element is passed", async () => { + const element = {} as ActionsHubTreeItem; + const result = await actionsHubTreeDataProvider.getChildren(element); + expect(result).to.be.an("array").that.is.empty; + }); + + it("should dispose all disposables", () => { + const disposable1 = { dispose: sinon.spy() }; + const disposable2 = { dispose: sinon.spy() }; + actionsHubTreeDataProvider["_disposables"].push(disposable1 as vscode.Disposable, disposable2 as vscode.Disposable); + + actionsHubTreeDataProvider.dispose(); + expect(disposable1.dispose.calledOnce).to.be.true; + expect(disposable2.dispose.calledOnce).to.be.true; + }); +}); diff --git a/src/common/OneDSLoggerTelemetry/telemetryConstants.ts b/src/common/OneDSLoggerTelemetry/telemetryConstants.ts index 63b1f5f9..8be3bd4f 100644 --- a/src/common/OneDSLoggerTelemetry/telemetryConstants.ts +++ b/src/common/OneDSLoggerTelemetry/telemetryConstants.ts @@ -42,4 +42,21 @@ export const CUSTOM_TELEMETRY_FOR_POWER_PAGES_SETTING_NAME = 'enableTelemetry'; export const AadIdKey= 'Entra ID Object Id:'; export const EnvIdKey = "Environment Id:"; export const TenantIdKey = "Tenant Id:"; -export const OrganizationFriendlyNameKey = "Organization Friendly Name:"; +export const AUTH_KEYS = { + USER_TYPE: 'Type:', + CLOUD: 'Cloud:', + TENANT_ID: 'Tenant Id:', + TENANT_COUNTRY: 'Tenant Country:', + USER: 'User:', + ENTRA_ID_OBJECT_ID: 'Entra ID Object Id:', + PUID: 'PUID:', + USER_COUNTRY_REGION: 'User Country/Region:', + TOKEN_EXPIRES: 'Token Expires:', + AUTHORITY: 'Authority:', + ENVIRONMENT_GEO: 'Environment Geo:', + ENVIRONMENT_ID: 'Environment Id:', + ENVIRONMENT_TYPE: 'Environment Type:', + ORGANIZATION_ID: 'Organization Id:', + ORGANIZATION_UNIQUE_NAME: 'Organization Unique Name:', + ORGANIZATION_FRIENDLY_NAME: 'Organization Friendly Name:' +}; From 47acab7780d7f924f7849b8ff7ab0a874d4310cb Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Wed, 29 Jan 2025 15:49:44 +0530 Subject: [PATCH 05/38] Add refresh command and event handling for Actions Hub environment changes --- package.json | 10 ++++++++++ src/client/AuthManager.ts | 4 ++++ src/client/OrgChangeNotifier.ts | 1 + src/client/extension.ts | 10 +++++++++- .../actions-hub/ActionsHubTreeDataProvider.ts | 17 +++++++++++++++++ src/client/power-pages/actions-hub/Constants.ts | 2 ++ 6 files changed, 43 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8058db7b..123a702c 100644 --- a/package.json +++ b/package.json @@ -375,6 +375,11 @@ "command": "microsoft.powerplatform.pages.preview-site", "category": "Powerpages", "title": "%powerplatform.pages.previewSite.title%" + }, + { + "command": "powerpages.actionsHub.refresh", + "title": "Refresh", + "icon": "$(refresh)" } ], "configuration": { @@ -850,6 +855,11 @@ "command": "powerpages.copilot.clearConversation", "when": "view == powerpages.copilot", "group": "navigation" + }, + { + "command": "powerpages.actionsHub.refresh", + "when": "view == powerpages.actionsHub", + "group": "navigation" } ], "view/item/context": [ diff --git a/src/client/AuthManager.ts b/src/client/AuthManager.ts index dbea5e32..e90a06e6 100644 --- a/src/client/AuthManager.ts +++ b/src/client/AuthManager.ts @@ -3,11 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ +import { EventEmitter, Event } from "vscode"; import { AuthInfo } from "./power-pages/actions-hub/Constants"; class AuthManager { private static instance: AuthManager; private authInfo: AuthInfo | null = null; + private _onDidChangeEnvironment: EventEmitter = new EventEmitter(); + public readonly onDidChangeEnvironment: Event = this._onDidChangeEnvironment.event; public static getInstance(): AuthManager { if (!AuthManager.instance) { @@ -18,6 +21,7 @@ class AuthManager { public setAuthInfo(authInfo: AuthInfo): void { this.authInfo = authInfo; + this._onDidChangeEnvironment.fire(); } public getAuthInfo(): AuthInfo | null { diff --git a/src/client/OrgChangeNotifier.ts b/src/client/OrgChangeNotifier.ts index ef6dc9eb..39f217b1 100644 --- a/src/client/OrgChangeNotifier.ts +++ b/src/client/OrgChangeNotifier.ts @@ -50,6 +50,7 @@ export class OrgChangeNotifier { this._orgDetails = pacActiveOrg.Results; orgChangeEventEmitter.fire(this._orgDetails); } else { + //If org/env is expired or deleted, this event will be fired orgChangeErrorEventEmitter.fire(); } } diff --git a/src/client/extension.ts b/src/client/extension.ts index 67a603b9..645fb3ea 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -34,7 +34,7 @@ import { CopilotNotificationShown } from "../common/copilot/telemetry/telemetryC import { copilotNotificationPanel, disposeNotificationPanel } from "../common/copilot/welcome-notification/CopilotNotificationPanel"; import { COPILOT_NOTIFICATION_DISABLED, EXTENSION_VERSION_KEY } from "../common/copilot/constants"; import { oneDSLoggerWrapper } from "../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; -import { OrgChangeNotifier, orgChangeEvent } from "./OrgChangeNotifier"; +import { OrgChangeNotifier, orgChangeErrorEvent, orgChangeEvent } from "./OrgChangeNotifier"; import { ActiveOrgOutput } from "./pac/PacTypes"; import { desktopTelemetryEventNames } from "../common/OneDSLoggerTelemetry/client/desktopExtensionTelemetryEventNames"; import { ArtemisService } from "../common/services/ArtemisService"; @@ -50,6 +50,8 @@ import { PreviewSite, SITE_PREVIEW_COMMAND_ID } from "./power-pages/preview-site import { ActionsHubTreeDataProvider } from "./power-pages/actions-hub/ActionsHubTreeDataProvider"; import { authManager } from "./AuthManager"; import { extractAuthInfo } from "./power-pages/commonUtility"; +import { showErrorDialog } from "../common/utilities/errorHandlerUtil"; +import { ENVIRONMENT_EXPIRED } from "./power-pages/actions-hub/Constants"; let client: LanguageClient; let _context: vscode.ExtensionContext; @@ -211,6 +213,8 @@ export async function activate( AadIdObject = pacActiveAuth.Results?.filter(obj => obj.Key === AadIdKey); EnvID = pacActiveAuth.Results?.filter(obj => obj.Key === EnvIdKey); TenantID = pacActiveAuth.Results?.filter(obj => obj.Key === TenantIdKey); + const authInfo = extractAuthInfo(pacActiveAuth.Results); + authManager.setAuthInfo(authInfo); } if (EnvID?.[0]?.Value && TenantID?.[0]?.Value && AadIdObject?.[0]?.Value) { @@ -258,6 +262,10 @@ export async function activate( await PreviewSite.loadSiteUrl(workspaceFolders, artemisResponse?.stamp, orgDetails.EnvironmentId, _telemetry); } + }), + + orgChangeErrorEvent(() => { + showErrorDialog(ENVIRONMENT_EXPIRED); }) ); diff --git a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts index 4193fba0..068ea234 100644 --- a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts +++ b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts @@ -17,6 +17,9 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + private constructor(context: vscode.ExtensionContext, pacTerminal: PacTerminal) { this._disposables.push( @@ -25,6 +28,10 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider this.refresh()); + this._disposables.push(...this.registerPanel()); } public static initialize(context: vscode.ExtensionContext, pacTerminal: PacTerminal): ActionsHubTreeDataProvider { @@ -64,7 +71,17 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider d.dispose()); } + + private registerPanel(): vscode.Disposable[] { + return [ + vscode.commands.registerCommand("powerpages.actionsHub.refresh", () => this.refresh()) + ]; + } } diff --git a/src/client/power-pages/actions-hub/Constants.ts b/src/client/power-pages/actions-hub/Constants.ts index 703e7b46..791f1c99 100644 --- a/src/client/power-pages/actions-hub/Constants.ts +++ b/src/client/power-pages/actions-hub/Constants.ts @@ -52,3 +52,5 @@ export interface AuthInfo { organizationUniqueName: string; organizationFriendlyName: string; } + +export const ENVIRONMENT_EXPIRED = vscode.l10n.t("Active Environment is expired or deleted. Please select a new environment.") From eea2bb82febf7fe9a8c606adb71f49df862a84b9 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Mon, 3 Feb 2025 17:46:11 +0530 Subject: [PATCH 06/38] Refactor ActionsHubTreeDataProvider: Manual refresh should trigger Pac --- .../actions-hub/ActionsHubTreeDataProvider.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts index 068ea234..103a24a2 100644 --- a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts +++ b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts @@ -12,6 +12,8 @@ import { PacTerminal } from "../../lib/PacTerminal"; import { EnvironmentGroupTreeItem } from "./tree-items/EnvironmentGroupTreeItem"; import { IEnvironmentInfo } from "./models/IEnvironmentInfo"; import { authManager } from "../../AuthManager"; +import { SUCCESS } from "../../../common/constants"; +import { extractAuthInfo } from "../commonUtility"; export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider { private readonly _disposables: vscode.Disposable[] = []; @@ -31,7 +33,7 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider this.refresh()); - this._disposables.push(...this.registerPanel()); + this._disposables.push(...this.registerPanel(this._pacTerminal)); } public static initialize(context: vscode.ExtensionContext, pacTerminal: PacTerminal): ActionsHubTreeDataProvider { @@ -79,9 +81,16 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider d.dispose()); } - private registerPanel(): vscode.Disposable[] { - return [ - vscode.commands.registerCommand("powerpages.actionsHub.refresh", () => this.refresh()) - ]; + private registerPanel(pacTerminal: PacTerminal): vscode.Disposable[] { + return [ + vscode.commands.registerCommand("powerpages.actionsHub.refresh", async () => { + const pacActiveAuth = await pacTerminal.getWrapper()?.activeAuth(); + if (pacActiveAuth && pacActiveAuth.Status === SUCCESS) { + const authInfo = extractAuthInfo(pacActiveAuth.Results); + authManager.setAuthInfo(authInfo); + } + this.refresh(); + }), + ]; } } From 638652e4dbefb710aa32cd974490799965c1d8c8 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Mon, 3 Feb 2025 18:02:54 +0530 Subject: [PATCH 07/38] Remove AuthManager class and related authentication logic --- src/client/AuthManager.ts | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 src/client/AuthManager.ts diff --git a/src/client/AuthManager.ts b/src/client/AuthManager.ts deleted file mode 100644 index e90a06e6..00000000 --- a/src/client/AuthManager.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - */ - -import { EventEmitter, Event } from "vscode"; -import { AuthInfo } from "./power-pages/actions-hub/Constants"; - -class AuthManager { - private static instance: AuthManager; - private authInfo: AuthInfo | null = null; - private _onDidChangeEnvironment: EventEmitter = new EventEmitter(); - public readonly onDidChangeEnvironment: Event = this._onDidChangeEnvironment.event; - - public static getInstance(): AuthManager { - if (!AuthManager.instance) { - AuthManager.instance = new AuthManager(); - } - return AuthManager.instance; - } - - public setAuthInfo(authInfo: AuthInfo): void { - this.authInfo = authInfo; - this._onDidChangeEnvironment.fire(); - } - - public getAuthInfo(): AuthInfo | null { - return this.authInfo; - } -} - -export const authManager = AuthManager.getInstance(); From 7423e2954b4b03701e743f354e9f9739e28de0ef Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 11:45:00 +0530 Subject: [PATCH 08/38] Add error handling to refresh command in ActionsHubTreeDataProvider --- .../actions-hub/ActionsHubTreeDataProvider.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts index 085af04a..fd497cab 100644 --- a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts +++ b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts @@ -84,13 +84,17 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider { - const pacActiveAuth = await pacTerminal.getWrapper()?.activeAuth(); - if (pacActiveAuth && pacActiveAuth.Status === SUCCESS) { - const authInfo = extractAuthInfo(pacActiveAuth.Results); - authManager.setAuthInfo(authInfo); + try { + const pacActiveAuth = await pacTerminal.getWrapper()?.activeAuth(); + if (pacActiveAuth && pacActiveAuth.Status === SUCCESS) { + const authInfo = extractAuthInfo(pacActiveAuth.Results); + authManager.setAuthInfo(authInfo); + } + this.refresh(); + } catch (error) { + oneDSLoggerWrapper.getLogger().traceError(Constants.EventNames.ACTIONS_HUB_INITIALIZATION_FAILED, error as string, error as Error, { methodName: this.refresh.name }, {}); } - this.refresh(); - }), + }) ]; } } From 84178b72d166f44e4d41c698b372ec50796cbb52 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 12:40:13 +0530 Subject: [PATCH 09/38] RemoveDependencyOnOldTelemetryCluster --- .../powerpages/PowerPagesChatParticipant.ts | 1 - src/common/constants.ts | 2 - src/common/copilot/IntelligenceApiService.ts | 11 ++-- src/common/copilot/PowerPagesCopilot.ts | 49 +++++++++--------- .../copilot/telemetry/copilotTelemetry.ts | 5 +- src/common/services/ArtemisService.ts | 4 +- src/common/services/AuthenticationProvider.ts | 51 +++++++++---------- src/common/services/BAPService.ts | 6 +-- src/common/utilities/Utils.ts | 16 +++--- 9 files changed, 67 insertions(+), 78 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index eb0a9882..ffce51f1 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -218,7 +218,6 @@ export class PowerPagesChatParticipant { sessionID: this.powerPagesAgentSessionId, entityName: entityName, entityColumns: componentInfo, - telemetry: this.telemetry, aibEndpoint: intelligenceAPIEndpointInfo.intelligenceEndpoint, geoName: intelligenceAPIEndpointInfo.geoName, crossGeoDataMovementEnabledPPACFlag: intelligenceAPIEndpointInfo.crossGeoDataMovementEnabledPPACFlag, diff --git a/src/common/constants.ts b/src/common/constants.ts index 410a0aac..9d3223d8 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -4,7 +4,6 @@ */ import { IActiveFileParams } from "./copilot/model"; -import { ITelemetry } from "./OneDSLoggerTelemetry/telemetry/ITelemetry"; export const EXTENSION_ID = "microsoft-IsvExpTools.powerplatform-vscode"; @@ -78,7 +77,6 @@ export interface IApiRequestParams { sessionID: string; entityName: string; entityColumns: string[]; - telemetry: ITelemetry; aibEndpoint: string | null; geoName: string | null; crossGeoDataMovementEnabledPPACFlag?: boolean; diff --git a/src/common/copilot/IntelligenceApiService.ts b/src/common/copilot/IntelligenceApiService.ts index fa34b841..ce740f3c 100644 --- a/src/common/copilot/IntelligenceApiService.ts +++ b/src/common/copilot/IntelligenceApiService.ts @@ -23,7 +23,6 @@ export async function sendApiRequest(params: IApiRequestParams) { sessionID, entityName, entityColumns, - telemetry, aibEndpoint, geoName, crossGeoDataMovementEnabledPPACFlag = false, @@ -94,7 +93,7 @@ export async function sendApiRequest(params: IApiRequestParams) { const jsonResponse = await response.json(); if (jsonResponse.operationStatus === 'Success') { - sendTelemetryEvent(telemetry, { eventName: CopilotResponseSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgId: orgID }); if (jsonResponse.additionalData && Array.isArray(jsonResponse.additionalData) && jsonResponse.additionalData.length > 0) { const additionalData = jsonResponse.additionalData[0]; if (additionalData.properties && additionalData.properties.response) { @@ -110,7 +109,7 @@ export async function sendApiRequest(params: IApiRequestParams) { } throw new Error("Invalid response format"); } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotResponseOkFailureEvent, copilotSessionId: sessionID, error: error as Error, durationInMills: responseTime, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseOkFailureEvent, copilotSessionId: sessionID, error: error as Error, durationInMills: responseTime, orgId: orgID }); return InvalidResponse; } } else { @@ -120,7 +119,7 @@ export async function sendApiRequest(params: IApiRequestParams) { const errorMessage = errorResponse.error && errorResponse.error.messages[0]; const responseError = new Error(errorMessage); - sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEventWithMessage, copilotSessionId: sessionID, responseStatus: String(response.status), error: responseError, durationInMills: responseTime, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseFailureEventWithMessage, copilotSessionId: sessionID, responseStatus: String(response.status), error: responseError, durationInMills: responseTime, orgId: orgID }); if (response.status === 429) { return RateLimitingResponse @@ -137,12 +136,12 @@ export async function sendApiRequest(params: IApiRequestParams) { return InvalidResponse; } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, responseStatus: String(response.status), error: error as Error, durationInMills: responseTime, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, responseStatus: String(response.status), error: error as Error, durationInMills: responseTime, orgId: orgID }); return InvalidResponse; } } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, error: error as Error, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, error: error as Error, orgId: orgID }); return NetworkError; } diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 229a8735..cb3cc558 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -65,7 +65,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { this._disposables.push( vscode.commands.registerCommand("powerpages.copilot.clearConversation", () => { if (userName && orgID) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotClearChatEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotClearChatEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.sendMessageToWebview({ type: "clearConversation" }); sessionID = uuidv4(); } @@ -91,7 +91,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { const withinTokenLimit = isWithinTokenLimit(selectedCode, 1000); if (commandType === EXPLAIN_CODE) { const tokenSize = encode(selectedCode).length; - sendTelemetryEvent(this.telemetry, { eventName: CopilotExplainCodeSize, copilotSessionId: sessionID, orgId: orgID, userId: userID, codeLineCount: String(selectedCodeLineRange.end - selectedCodeLineRange.start), tokenSize: String(tokenSize) }); + sendTelemetryEvent({ eventName: CopilotExplainCodeSize, copilotSessionId: sessionID, orgId: orgID, userId: userID, codeLineCount: String(selectedCodeLineRange.end - selectedCodeLineRange.start), tokenSize: String(tokenSize) }); if (withinTokenLimit === false) { return; } @@ -104,7 +104,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { ); this._disposables.push( - vscode.commands.registerCommand("powerpages.copilot.explain", () => { sendTelemetryEvent(this.telemetry, { eventName: CopilotExplainCode, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.show(); handleSelectionChange(EXPLAIN_CODE) }) + vscode.commands.registerCommand("powerpages.copilot.explain", () => { sendTelemetryEvent({ eventName: CopilotExplainCode, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.show(); handleSelectionChange(EXPLAIN_CODE) }) ); } this._disposables.push( @@ -210,12 +210,12 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { this.sendMessageToWebview({ type: 'Unavailable' }); return; } else if (getDisabledOrgList()?.includes(orgID) || getDisabledTenantList()?.includes(tenantId ?? "")) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotNotAvailableECSConfig, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotNotAvailableECSConfig, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.sendMessageToWebview({ type: 'Unavailable' }); return; } - sendTelemetryEvent(this.telemetry, { eventName: CopilotLoadedEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotLoadedEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.sendMessageToWebview({ type: 'env' }); await this.checkAuthentication(); if (orgID && userName) { @@ -233,10 +233,10 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { break; } case "newUserPrompt": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserPromptedEvent, copilotSessionId: sessionID, aibEndpoint: this.aibEndpoint ?? '', orgId: orgID, userId: userID, isSuggestedPrompt: String(data.value.isSuggestedPrompt), crossGeoDataMovementEnabledPPACFlag: this.crossGeoDataMovementEnabledPPACFlag }); //TODO: Add active Editor info + sendTelemetryEvent({ eventName: CopilotUserPromptedEvent, copilotSessionId: sessionID, aibEndpoint: this.aibEndpoint ?? '', orgId: orgID, userId: userID, isSuggestedPrompt: String(data.value.isSuggestedPrompt), crossGeoDataMovementEnabledPPACFlag: this.crossGeoDataMovementEnabledPPACFlag }); //TODO: Add active Editor info orgID ? (async () => { - await this.authenticateAndSendAPIRequest(data.value.userPrompt, orgID, this.telemetry); + await this.authenticateAndSendAPIRequest(data.value.userPrompt, orgID); })() : (() => { this.sendMessageToWebview({ type: 'apiResponse', value: AuthProfileNotFound }); @@ -253,14 +253,14 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { vscode.window.activeTextEditor?.insertSnippet( new vscode.SnippetString(`${escapedSnippet}`) ); - sendTelemetryEvent(this.telemetry, { eventName: CopilotInsertCodeToEditorEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotInsertCodeToEditorEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); break; } case "copyCodeToClipboard": { vscode.env.clipboard.writeText(data.value); vscode.window.showInformationMessage(vscode.l10n.t('Copied to clipboard!')) - sendTelemetryEvent(this.telemetry, { eventName: CopilotCopyCodeToClipboardEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotCopyCodeToClipboardEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); break; } case "clearChat": { @@ -275,32 +275,32 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { if (feedbackValue === THUMBS_UP) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsUpEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID, subScenario: String(messageScenario) }); + sendTelemetryEvent({ eventName: CopilotUserFeedbackThumbsUpEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID, subScenario: String(messageScenario) }); CESUserFeedback(this._extensionContext, sessionID, userID, THUMBS_UP, this.telemetry, this.geoName as string, messageScenario, tenantId) } else if (feedbackValue === THUMBS_DOWN) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsDownEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID, subScenario: String(messageScenario) }); + sendTelemetryEvent({ eventName: CopilotUserFeedbackThumbsDownEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID, subScenario: String(messageScenario) }); CESUserFeedback(this._extensionContext, sessionID, userID, THUMBS_DOWN, this.telemetry, this.geoName as string, messageScenario, tenantId) } break; } case "walkthrough": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotWalkthroughEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotWalkthroughEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); openWalkthrough(this._extensionUri); break; } case "codeLineCount": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotCodeLineCountEvent, copilotSessionId: sessionID, codeLineCount: String(data.value), orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotCodeLineCountEvent, copilotSessionId: sessionID, codeLineCount: String(data.value), orgId: orgID, userId: userID }); break; } case "openGitHubCopilotLink": { //Open the GitHub Copilot Chat with @powerpages if GitHub Copilot Chat is installed - sendTelemetryEvent(this.telemetry, { eventName: CopilotPanelTryGitHubCopilotClicked, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotPanelTryGitHubCopilotClicked, copilotSessionId: sessionID, orgId: orgID, userId: userID }); if (vscode.extensions.getExtension(GITHUB_COPILOT_CHAT_EXT)) { - sendTelemetryEvent(this.telemetry, { eventName: VSCodeExtensionGitHubChatPanelOpened, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: VSCodeExtensionGitHubChatPanelOpened, copilotSessionId: sessionID, orgId: orgID, userId: userID }); vscode.commands.executeCommand('workbench.action.chat.open', PowerPagesParticipantPrompt); } else { - sendTelemetryEvent(this.telemetry, { eventName: VSCodeExtensionGitHubChatNotFound, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: VSCodeExtensionGitHubChatNotFound, copilotSessionId: sessionID, orgId: orgID, userId: userID }); vscode.env.openExternal(vscode.Uri.parse(PowerPagesParticipantDocLink)); } } @@ -354,15 +354,15 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { if (session) { intelligenceApiToken = session.accessToken; userName = getUserName(session.account.label); - userID = getOIDFromToken(session.accessToken, this.telemetry); + userID = getOIDFromToken(session.accessToken); } else { intelligenceApiToken = ""; userName = ""; } } - private async authenticateAndSendAPIRequest(data: UserPrompt[], orgID: string, telemetry: ITelemetry) { - return intelligenceAPIAuthentication(telemetry, sessionID, orgID) + private async authenticateAndSendAPIRequest(data: UserPrompt[], orgID: string) { + return intelligenceAPIAuthentication(this.telemetry, sessionID, orgID) .then(async ({ accessToken, user, userId }) => { intelligenceApiToken = accessToken; userName = getUserName(user); @@ -376,15 +376,15 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { let componentInfo: string[] = []; if (activeFileParams.dataverseEntity == ADX_ENTITYFORM || activeFileParams.dataverseEntity == ADX_ENTITYLIST) { - metadataInfo = await getEntityName(telemetry, sessionID, activeFileParams.dataverseEntity); + metadataInfo = await getEntityName(this.telemetry, sessionID, activeFileParams.dataverseEntity); - const dataverseToken = (await dataverseAuthentication(telemetry, activeOrgUrl, true)).accessToken; + const dataverseToken = (await dataverseAuthentication(this.telemetry, activeOrgUrl, true)).accessToken; if (activeFileParams.dataverseEntity == ADX_ENTITYFORM) { - const formColumns = await getFormXml(metadataInfo.entityName, metadataInfo.formName, activeOrgUrl, dataverseToken, telemetry, sessionID); + const formColumns = await getFormXml(metadataInfo.entityName, metadataInfo.formName, activeOrgUrl, dataverseToken, this.telemetry, sessionID); componentInfo = formColumns; } else { - const entityColumns = await getEntityColumns(metadataInfo.entityName, activeOrgUrl, dataverseToken, telemetry, sessionID); + const entityColumns = await getEntityColumns(metadataInfo.entityName, activeOrgUrl, dataverseToken, this.telemetry, sessionID); componentInfo = entityColumns; } @@ -397,7 +397,6 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { sessionID: sessionID, entityName: metadataInfo.entityName, entityColumns: componentInfo, - telemetry: telemetry, aibEndpoint: this.aibEndpoint, geoName: this.geoName, crossGeoDataMovementEnabledPPACFlag: this.crossGeoDataMovementEnabledPPACFlag @@ -423,7 +422,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { environmentId = activeOrg.EnvironmentId sessionID = uuidv4(); // Generate a new session ID on org change - sendTelemetryEvent(this.telemetry, { eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); const intelligenceAPIEndpointInfo = await ArtemisService.getIntelligenceEndpoint(orgID, this.telemetry, sessionID, environmentId); if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { diff --git a/src/common/copilot/telemetry/copilotTelemetry.ts b/src/common/copilot/telemetry/copilotTelemetry.ts index 838b2cc2..26b72aa1 100644 --- a/src/common/copilot/telemetry/copilotTelemetry.ts +++ b/src/common/copilot/telemetry/copilotTelemetry.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ -import { ITelemetry } from "../../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { oneDSLoggerWrapper } from "../../OneDSLoggerTelemetry/oneDSLoggerWrapper"; import { IProDevCopilotTelemetryData } from "./ITelemetry"; -export function sendTelemetryEvent(telemetry: ITelemetry, telemetryData: IProDevCopilotTelemetryData): void { +export function sendTelemetryEvent(telemetryData: IProDevCopilotTelemetryData): void { const telemetryDataProperties: Record = {} const telemetryDataMeasurements: Record = {} @@ -33,11 +32,9 @@ export function sendTelemetryEvent(telemetry: ITelemetry, telemetryData: IProDev if (telemetryData.error) { telemetryDataProperties.eventName = telemetryData.eventName; - telemetry.sendTelemetryException(telemetryData.error, telemetryDataProperties, telemetryDataMeasurements); oneDSLoggerWrapper.getLogger().traceError(telemetryData.error.name, telemetryData.error.message, telemetryData.error, telemetryDataProperties, telemetryDataMeasurements); } else { - telemetry.sendTelemetryEvent(telemetryData.eventName, telemetryDataProperties, telemetryDataMeasurements); oneDSLoggerWrapper.getLogger().traceInfo(telemetryData.eventName, telemetryDataProperties, telemetryDataMeasurements); } } diff --git a/src/common/services/ArtemisService.ts b/src/common/services/ArtemisService.ts index 10c623a1..6789b93b 100644 --- a/src/common/services/ArtemisService.ts +++ b/src/common/services/ArtemisService.ts @@ -24,7 +24,7 @@ export class ArtemisService { const endpointStamp = artemisResponse.stamp; const { geoName, environment, clusterNumber } = artemisResponse.response as IArtemisAPIOrgResponse; - sendTelemetryEvent(telemetry, { eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); + sendTelemetryEvent({ eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp, telemetry, environmentId); @@ -79,7 +79,7 @@ export class ArtemisService { const successfulResponses = results.filter(result => result !== null && result.response !== null); return successfulResponses as IArtemisServiceResponse[]; } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotArtemisFailureEvent, copilotSessionId: sessionID, error: error as Error }) + sendTelemetryEvent({ eventName: CopilotArtemisFailureEvent, copilotSessionId: sessionID, error: error as Error }) return null; } } diff --git a/src/common/services/AuthenticationProvider.ts b/src/common/services/AuthenticationProvider.ts index 4d2f284d..4766ca0f 100644 --- a/src/common/services/AuthenticationProvider.ts +++ b/src/common/services/AuthenticationProvider.ts @@ -94,18 +94,18 @@ export async function intelligenceAPIAuthentication(telemetry: ITelemetry, sessi } accessToken = session?.accessToken ?? ''; user = session.account.label; - userId = getOIDFromToken(accessToken, telemetry); + userId = getOIDFromToken(accessToken); if (!accessToken) { throw new Error(ERROR_CONSTANTS.NO_ACCESS_TOKEN); } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, { eventName: CopilotLoginSuccessEvent, copilotSessionId: sessionID, orgId: orgId }); + sendTelemetryEvent({ eventName: CopilotLoginSuccessEvent, copilotSessionId: sessionID, orgId: orgId }); } } catch (error) { showErrorDialog(vscode.l10n.t("Authorization Failed. Please run again to authorize it"), vscode.l10n.t("There was a permissions problem with the server")); - sendTelemetryEvent(telemetry, { eventName: CopilotLoginFailureEvent, copilotSessionId: sessionID, orgId: orgId, errorMsg: (error as Error).message }); + sendTelemetryEvent({ eventName: CopilotLoginFailureEvent, copilotSessionId: sessionID, orgId: orgId, errorMsg: (error as Error).message }); } return { accessToken, user, userId }; } @@ -138,18 +138,16 @@ export async function dataverseAuthentication( } accessToken = session?.accessToken ?? ""; - userId = getOIDFromToken(accessToken, telemetry); + userId = getOIDFromToken(accessToken); if (!accessToken) { throw new Error(ERROR_CONSTANTS.NO_ACCESS_TOKEN); } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, - { - eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED, - userId: userId - } - ); + sendTelemetryEvent({ + eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED, + userId: userId + }); } } catch (error) { showErrorDialog( @@ -159,10 +157,10 @@ export async function dataverseAuthentication( vscode.l10n.t("There was a permissions problem with the server") ); sendTelemetryEvent( - telemetry, { - eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED, - errorMsg: (error as Error).message - } + { + eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED, + errorMsg: (error as Error).message + } ); } @@ -174,7 +172,7 @@ export async function npsAuthentication( cesSurveyAuthorizationEndpoint: string ): Promise { let accessToken = ""; - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_NPS_AUTHENTICATION_STARTED } ); try { @@ -187,7 +185,7 @@ export async function npsAuthentication( if (!accessToken) { throw new Error(ERROR_CONSTANTS.NO_ACCESS_TOKEN); } - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_NPS_AUTHENTICATION_COMPLETED } ); } catch (error) { @@ -198,7 +196,6 @@ export async function npsAuthentication( vscode.l10n.t("There was a permissions problem with the server") ); sendTelemetryEvent( - telemetry, { eventName: VSCODE_EXTENSION_NPS_AUTHENTICATION_FAILED, errorMsg: (error as Error).message @@ -241,9 +238,9 @@ export async function graphClientAuthentication( } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED, - userId: getOIDFromToken(accessToken, telemetry), + userId: getOIDFromToken(accessToken), }); } } catch (error) { @@ -253,7 +250,7 @@ export async function graphClientAuthentication( ), vscode.l10n.t("There was a permissions problem with the server") ); - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED, errorMsg: (error as Error).message } ) } @@ -287,9 +284,9 @@ export async function bapServiceAuthentication( } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_BAP_SERVICE_AUTHENTICATION_COMPLETED, - userId: getOIDFromToken(accessToken, telemetry), + userId: getOIDFromToken(accessToken), }); } } catch (error) { @@ -299,7 +296,7 @@ export async function bapServiceAuthentication( ), vscode.l10n.t("There was a permissions problem with the server") ); - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_BAP_SERVICE_AUTHENTICATION_FAILED, errorMsg: (error as Error).message } ) } @@ -307,12 +304,12 @@ export async function bapServiceAuthentication( return accessToken; } -export function getOIDFromToken(token: string, telemetry: ITelemetry) { +export function getOIDFromToken(token: string) { try { const decoded = jwt_decode(token); return decoded?.oid ?? ""; } catch (error) { - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_DECODE_JWT_TOKEN_FAILED, errorMsg: (error as Error).message } ) } @@ -347,7 +344,7 @@ export async function powerPlatformAPIAuthentication( } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_COMPLETED, userId: session?.account.id.split("/").pop() ?? @@ -362,7 +359,7 @@ export async function powerPlatformAPIAuthentication( ), vscode.l10n.t("There was a permissions problem with the server") ); - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_FAILED, errorMsg: (error as Error).message } ) } diff --git a/src/common/services/BAPService.ts b/src/common/services/BAPService.ts index bcfb5131..f9dd5c1b 100644 --- a/src/common/services/BAPService.ts +++ b/src/common/services/BAPService.ts @@ -23,12 +23,12 @@ export class BAPService { if (response.ok) { const data = await response.json(); - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_COMPLETED, data: data.properties.copilotPolicies?.crossGeoCopilotDataMovementEnabled }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_COMPLETED, data: data.properties.copilotPolicies?.crossGeoCopilotDataMovementEnabled }); return data.properties.copilotPolicies?.crossGeoCopilotDataMovementEnabled; } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, errorMsg: (error as Error).message }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, errorMsg: (error as Error).message }); } return false; @@ -36,7 +36,7 @@ export class BAPService { static async getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry, environmentId: string): Promise { - const bapEndpoint = await getBAPEndpoint(serviceEndpointStamp, telemetry); + const bapEndpoint = await getBAPEndpoint(serviceEndpointStamp); return BAP_SERVICE_ENDPOINT.replace('{rootURL}', bapEndpoint) + BAP_SERVICE_COPILOT_CROSS_GEO_FLAG_RELATIVE_URL.replace('{environmentID}', environmentId).replace('{apiVersion}', BAP_API_VERSION); diff --git a/src/common/utilities/Utils.ts b/src/common/utilities/Utils.ts index 810d8a15..a803df46 100644 --- a/src/common/utilities/Utils.ts +++ b/src/common/utilities/Utils.ts @@ -208,10 +208,10 @@ export function checkCopilotAvailability( return false; } else if (aibEndpoint === COPILOT_UNAVAILABLE) { - sendTelemetryEvent(telemetry, { eventName: CopilotNotAvailable, copilotSessionId: sessionID, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotNotAvailable, copilotSessionId: sessionID, orgId: orgID }); return false; } else if (getDisabledOrgList()?.includes(orgID) || getDisabledTenantList()?.includes(tenantId ?? "")) { // Tenant ID not available in desktop - sendTelemetryEvent(telemetry, { eventName: CopilotNotAvailableECSConfig, copilotSessionId: sessionID, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotNotAvailableECSConfig, copilotSessionId: sessionID, orgId: orgID }); return false; } else { return true; @@ -337,7 +337,7 @@ export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEn const envInfo: IEnvInfo[] = []; try { const bapAuthToken = await bapServiceAuthentication(telemetry, true); - const bapEndpoint = getBAPEndpoint(endpointStamp, telemetry); + const bapEndpoint = getBAPEndpoint(endpointStamp); const envListEndpoint = `${bapEndpoint}${BAP_ENVIRONMENT_LIST_URL.replace('{apiVersion}', BAP_API_VERSION)}`; const envListResponse = await fetch(envListEndpoint, { @@ -356,17 +356,17 @@ export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEn envDisplayName: env.properties.displayName }); }); - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_ENV_LIST_SUCCESS }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_ENV_LIST_SUCCESS }); oneDSLoggerWrapper.getLogger().traceInfo(VSCODE_EXTENSION_GET_ENV_LIST_SUCCESS); } else { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_ENV_LIST_FAILED, errorMsg: envListResponse.statusText }); oneDSLoggerWrapper.getLogger().traceError(VSCODE_EXTENSION_GET_ENV_LIST_FAILED, VSCODE_EXTENSION_GET_ENV_LIST_FAILED, new Error(envListResponse.statusText)); } } catch (error) { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_ENV_LIST_FAILED, errorMsg: (error as Error).message }); @@ -376,7 +376,7 @@ export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEn } -export function getBAPEndpoint(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry): string { +export function getBAPEndpoint(serviceEndpointStamp: ServiceEndpointCategory): string { let bapEndpoint = ""; switch (serviceEndpointStamp) { @@ -395,7 +395,7 @@ export function getBAPEndpoint(serviceEndpointStamp: ServiceEndpointCategory, te case ServiceEndpointCategory.HIGH: case ServiceEndpointCategory.MOONCAKE: default: - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_BAP_ENDPOINT_UNSUPPORTED_REGION, data: serviceEndpointStamp }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_BAP_ENDPOINT_UNSUPPORTED_REGION, data: serviceEndpointStamp }); oneDSLoggerWrapper.getLogger().traceInfo(VSCODE_EXTENSION_GET_BAP_ENDPOINT_UNSUPPORTED_REGION, { data: serviceEndpointStamp }); break; } From 854157e4b9e7447b392e8c38874dbacbc9173b52 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 12:47:19 +0530 Subject: [PATCH 10/38] Refactor ActionsHubTreeDataProvider tests to include PacTerminal in initialization --- .../ActionsHubTreeDataProvider.test.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index 51109ec5..510d1bb0 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -14,12 +14,14 @@ import { EnvironmentGroupTreeItem } from "../../../../power-pages/actions-hub/tr import { OtherSitesGroupTreeItem } from "../../../../power-pages/actions-hub/tree-items/OtherSitesGroupTreeItem"; import { ActionsHubTreeItem } from "../../../../power-pages/actions-hub/tree-items/ActionsHubTreeItem"; import { pacAuthManager } from "../../../../pac/PacAuthManager"; +import { PacTerminal } from "../../../../lib/PacTerminal"; describe("ActionsHubTreeDataProvider", () => { let context: vscode.ExtensionContext; let traceInfoStub: sinon.SinonStub; let traceErrorStub: sinon.SinonStub; let authInfoStub: sinon.SinonStub; + let pacTerminal: PacTerminal; beforeEach(() => { context = { @@ -34,6 +36,7 @@ describe("ActionsHubTreeDataProvider", () => { featureUsage: sinon.stub() }); authInfoStub = sinon.stub(pacAuthManager, "getAuthInfo"); + pacTerminal = {} as PacTerminal; }); afterEach(() => { @@ -42,7 +45,7 @@ describe("ActionsHubTreeDataProvider", () => { describe('initialize', () => { it("should initialize and log initialization event", () => { - ActionsHubTreeDataProvider.initialize(context); + ActionsHubTreeDataProvider.initialize(context, pacTerminal); expect(traceInfoStub.calledWith(Constants.EventNames.ACTIONS_HUB_INITIALIZED)).to.be.true; }); }); @@ -50,7 +53,7 @@ describe("ActionsHubTreeDataProvider", () => { describe('getTreeItem', () => { it("should return the element in getTreeItem", () => { const element = {} as ActionsHubTreeItem; - const result = ActionsHubTreeDataProvider.initialize(context).getTreeItem(element); + const result = ActionsHubTreeDataProvider.initialize(context, pacTerminal).getTreeItem(element); expect(result).to.equal(element); }); }); @@ -75,7 +78,7 @@ describe("ActionsHubTreeDataProvider", () => { organizationId: "", organizationUniqueName: "" }); - const provider = ActionsHubTreeDataProvider.initialize(context); + const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); const result = await provider.getChildren(); expect(result).to.not.be.null; @@ -87,7 +90,7 @@ describe("ActionsHubTreeDataProvider", () => { it("should return environment group tree item with default name when no auth info is available", async () => { authInfoStub.returns(null); - const provider = ActionsHubTreeDataProvider.initialize(context); + const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); const result = await provider.getChildren(); expect(result).to.not.be.null; @@ -101,7 +104,7 @@ describe("ActionsHubTreeDataProvider", () => { it("should return null in getChildren when an error occurs", async () => { authInfoStub.throws(new Error("Test Error")); - const provider = ActionsHubTreeDataProvider.initialize(context); + const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); const result = await provider.getChildren(); expect(result).to.be.null; @@ -112,7 +115,7 @@ describe("ActionsHubTreeDataProvider", () => { it("should return an empty array in getChildren when an element is passed", async () => { const element = {} as ActionsHubTreeItem; - const provider = ActionsHubTreeDataProvider.initialize(context); + const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); const result = await provider.getChildren(element); expect(result).to.be.an("array").that.is.empty; @@ -123,7 +126,7 @@ describe("ActionsHubTreeDataProvider", () => { it("should dispose all disposables", () => { const disposable1 = { dispose: sinon.spy() }; const disposable2 = { dispose: sinon.spy() }; - const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context); + const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); actionsHubTreeDataProvider["_disposables"].push(disposable1 as vscode.Disposable, disposable2 as vscode.Disposable); actionsHubTreeDataProvider.dispose(); From 8d06ba2dcb9451c6cccf4c7ef904c1aaf20cdfbb Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 12:50:46 +0530 Subject: [PATCH 11/38] Remove telemetry dependency from intelligence API endpoint retrieval --- .../powerpages/PowerPagesChatParticipant.ts | 2 +- .../powerpages/PowerPagesChatParticipantUtils.ts | 3 +-- src/common/copilot/PowerPagesCopilot.ts | 2 +- src/common/services/ArtemisService.ts | 13 ++++++------- src/common/services/AuthenticationProvider.ts | 1 - src/common/services/BAPService.ts | 9 ++++----- src/common/utilities/Utils.ts | 4 ++-- 7 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index ffce51f1..e23acbfe 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -131,7 +131,7 @@ export class PowerPagesChatParticipant { const intelligenceApiToken = intelligenceApiAuthResponse.accessToken; const userId = intelligenceApiAuthResponse.userId; - const intelligenceAPIEndpointInfo = await getEndpoint(this.orgID, this.environmentID, this.telemetry, this.cachedEndpoint, this.powerPagesAgentSessionId); + const intelligenceAPIEndpointInfo = await getEndpoint(this.orgID, this.environmentID, this.cachedEndpoint, this.powerPagesAgentSessionId); if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { return createErrorResult(COPILOT_NOT_AVAILABLE_MSG, RESPONSE_SCENARIOS.COPILOT_NOT_AVAILABLE, this.orgID); diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts index 8fd130f7..0a66e2c3 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts @@ -22,12 +22,11 @@ import * as vscode from 'vscode'; export async function getEndpoint( orgID: string, environmentID: string, - telemetry: ITelemetry, cachedEndpoint: IIntelligenceAPIEndpointInformation | null, sessionID: string ): Promise { if (!cachedEndpoint) { - cachedEndpoint = await ArtemisService.getIntelligenceEndpoint(orgID, telemetry, sessionID, environmentID) as IIntelligenceAPIEndpointInformation; // TODO - add session ID + cachedEndpoint = await ArtemisService.getIntelligenceEndpoint(orgID, sessionID, environmentID) as IIntelligenceAPIEndpointInformation; // TODO - add session ID } return cachedEndpoint; } diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index cb3cc558..52f38666 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -424,7 +424,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { sessionID = uuidv4(); // Generate a new session ID on org change sendTelemetryEvent({ eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); - const intelligenceAPIEndpointInfo = await ArtemisService.getIntelligenceEndpoint(orgID, this.telemetry, sessionID, environmentId); + const intelligenceAPIEndpointInfo = await ArtemisService.getIntelligenceEndpoint(orgID, sessionID, environmentId); if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { this.sendMessageToWebview({ type: 'Unavailable' }); return; diff --git a/src/common/services/ArtemisService.ts b/src/common/services/ArtemisService.ts index 6789b93b..0978c0c5 100644 --- a/src/common/services/ArtemisService.ts +++ b/src/common/services/ArtemisService.ts @@ -5,7 +5,6 @@ import fetch, { RequestInit } from "node-fetch"; import { COPILOT_UNAVAILABLE } from "../copilot/constants"; -import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry"; import { CopilotArtemisFailureEvent, CopilotArtemisSuccessEvent } from "../copilot/telemetry/telemetryConstants"; import { ServiceEndpointCategory } from "./Constants"; @@ -14,9 +13,9 @@ import { isCopilotDisabledInGeo, isCopilotSupportedInGeo } from "../copilot/util import { BAPService } from "./BAPService"; export class ArtemisService { - public static async getIntelligenceEndpoint(orgId: string, telemetry: ITelemetry, sessionID: string, environmentId: string): Promise { + public static async getIntelligenceEndpoint(orgId: string, sessionID: string, environmentId: string): Promise { - const artemisResponse = await ArtemisService.getArtemisResponse(orgId, telemetry, sessionID); + const artemisResponse = await ArtemisService.getArtemisResponse(orgId, sessionID); if (artemisResponse === null) { return { intelligenceEndpoint: null, geoName: null, crossGeoDataMovementEnabledPPACFlag: false }; @@ -26,7 +25,7 @@ export class ArtemisService { const { geoName, environment, clusterNumber } = artemisResponse.response as IArtemisAPIOrgResponse; sendTelemetryEvent({ eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); - const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp, telemetry, environmentId); + const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp,environmentId); if (isCopilotDisabledInGeo().includes(geoName)) { return { intelligenceEndpoint: COPILOT_UNAVAILABLE, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag }; @@ -44,9 +43,9 @@ export class ArtemisService { } // Function to fetch Artemis response - public static async getArtemisResponse(orgId: string, telemetry: ITelemetry, sessionID: string): Promise { + public static async getArtemisResponse(orgId: string, sessionID: string): Promise { const endpointDetails = ArtemisService.convertGuidToUrls(orgId); - const artemisResponses = await ArtemisService.fetchIslandInfo(endpointDetails, telemetry, sessionID); + const artemisResponses = await ArtemisService.fetchIslandInfo(endpointDetails, sessionID); if (artemisResponses === null || artemisResponses.length === 0) { return null; @@ -55,7 +54,7 @@ export class ArtemisService { return artemisResponses[0]; } - static async fetchIslandInfo(endpointDetails: IArtemisServiceEndpointInformation[], telemetry: ITelemetry, sessionID: string): Promise { + static async fetchIslandInfo(endpointDetails: IArtemisServiceEndpointInformation[], sessionID: string): Promise { const requestInit: RequestInit = { method: 'GET', diff --git a/src/common/services/AuthenticationProvider.ts b/src/common/services/AuthenticationProvider.ts index 4766ca0f..a96ce5e0 100644 --- a/src/common/services/AuthenticationProvider.ts +++ b/src/common/services/AuthenticationProvider.ts @@ -259,7 +259,6 @@ export async function graphClientAuthentication( } export async function bapServiceAuthentication( - telemetry: ITelemetry, firstTimeAuth = false ): Promise { let accessToken = ""; diff --git a/src/common/services/BAPService.ts b/src/common/services/BAPService.ts index f9dd5c1b..e16fa87e 100644 --- a/src/common/services/BAPService.ts +++ b/src/common/services/BAPService.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ -import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { bapServiceAuthentication, getCommonHeaders } from "./AuthenticationProvider"; import { VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_COMPLETED, VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED } from "./TelemetryConstants"; import { ServiceEndpointCategory, BAP_API_VERSION, BAP_SERVICE_COPILOT_CROSS_GEO_FLAG_RELATIVE_URL, BAP_SERVICE_ENDPOINT } from "./Constants"; @@ -11,12 +10,12 @@ import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry"; import { getBAPEndpoint } from "../utilities/Utils"; export class BAPService { - public static async getCrossGeoCopilotDataMovementEnabledFlag(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry, environmentId: string): Promise { + public static async getCrossGeoCopilotDataMovementEnabledFlag(serviceEndpointStamp: ServiceEndpointCategory, environmentId: string): Promise { try { - const accessToken = await bapServiceAuthentication(telemetry, true); + const accessToken = await bapServiceAuthentication(true); - const response = await fetch(await BAPService.getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp, telemetry, environmentId), { + const response = await fetch(await BAPService.getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp, environmentId), { method: 'GET', headers: getCommonHeaders(accessToken) }); @@ -34,7 +33,7 @@ export class BAPService { return false; } - static async getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry, environmentId: string): Promise { + static async getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp: ServiceEndpointCategory, environmentId: string): Promise { const bapEndpoint = await getBAPEndpoint(serviceEndpointStamp); diff --git a/src/common/utilities/Utils.ts b/src/common/utilities/Utils.ts index a803df46..b92127e9 100644 --- a/src/common/utilities/Utils.ts +++ b/src/common/utilities/Utils.ts @@ -330,13 +330,13 @@ export function getECSOrgLocationValue(clusterName: string, clusterNumber: strin } //API call to get env list for an org -export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEndpointCategory | undefined): Promise { +export async function getEnvList(endpointStamp: ServiceEndpointCategory | undefined): Promise { if(!endpointStamp) { return []; } const envInfo: IEnvInfo[] = []; try { - const bapAuthToken = await bapServiceAuthentication(telemetry, true); + const bapAuthToken = await bapServiceAuthentication(true); const bapEndpoint = getBAPEndpoint(endpointStamp); const envListEndpoint = `${bapEndpoint}${BAP_ENVIRONMENT_LIST_URL.replace('{apiVersion}', BAP_API_VERSION)}`; From dcc317d8bed6dc5218558224e4fa216918cffb40 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 12:53:30 +0530 Subject: [PATCH 12/38] Refactor telemetry handling by removing ITelemetry parameter from sendTelemetryEvent and related functions --- .../powerpages/PowerPagesChatParticipant.ts | 1 - src/common/constants.ts | 2 - src/common/copilot/IntelligenceApiService.ts | 11 ++-- src/common/copilot/PowerPagesCopilot.ts | 49 +++++++++--------- src/common/copilot/dataverseMetadata.ts | 12 ++--- .../copilot/telemetry/copilotTelemetry.ts | 5 +- src/common/copilot/user-feedback/CESSurvey.ts | 10 ++-- src/common/services/ArtemisService.ts | 4 +- src/common/services/AuthenticationProvider.ts | 51 +++++++++---------- src/common/services/BAPService.ts | 6 +-- src/common/services/PPAPIService.ts | 12 ++--- src/common/utilities/Utils.ts | 16 +++--- 12 files changed, 84 insertions(+), 95 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index eb0a9882..ffce51f1 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -218,7 +218,6 @@ export class PowerPagesChatParticipant { sessionID: this.powerPagesAgentSessionId, entityName: entityName, entityColumns: componentInfo, - telemetry: this.telemetry, aibEndpoint: intelligenceAPIEndpointInfo.intelligenceEndpoint, geoName: intelligenceAPIEndpointInfo.geoName, crossGeoDataMovementEnabledPPACFlag: intelligenceAPIEndpointInfo.crossGeoDataMovementEnabledPPACFlag, diff --git a/src/common/constants.ts b/src/common/constants.ts index 410a0aac..9d3223d8 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -4,7 +4,6 @@ */ import { IActiveFileParams } from "./copilot/model"; -import { ITelemetry } from "./OneDSLoggerTelemetry/telemetry/ITelemetry"; export const EXTENSION_ID = "microsoft-IsvExpTools.powerplatform-vscode"; @@ -78,7 +77,6 @@ export interface IApiRequestParams { sessionID: string; entityName: string; entityColumns: string[]; - telemetry: ITelemetry; aibEndpoint: string | null; geoName: string | null; crossGeoDataMovementEnabledPPACFlag?: boolean; diff --git a/src/common/copilot/IntelligenceApiService.ts b/src/common/copilot/IntelligenceApiService.ts index fa34b841..ce740f3c 100644 --- a/src/common/copilot/IntelligenceApiService.ts +++ b/src/common/copilot/IntelligenceApiService.ts @@ -23,7 +23,6 @@ export async function sendApiRequest(params: IApiRequestParams) { sessionID, entityName, entityColumns, - telemetry, aibEndpoint, geoName, crossGeoDataMovementEnabledPPACFlag = false, @@ -94,7 +93,7 @@ export async function sendApiRequest(params: IApiRequestParams) { const jsonResponse = await response.json(); if (jsonResponse.operationStatus === 'Success') { - sendTelemetryEvent(telemetry, { eventName: CopilotResponseSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgId: orgID }); if (jsonResponse.additionalData && Array.isArray(jsonResponse.additionalData) && jsonResponse.additionalData.length > 0) { const additionalData = jsonResponse.additionalData[0]; if (additionalData.properties && additionalData.properties.response) { @@ -110,7 +109,7 @@ export async function sendApiRequest(params: IApiRequestParams) { } throw new Error("Invalid response format"); } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotResponseOkFailureEvent, copilotSessionId: sessionID, error: error as Error, durationInMills: responseTime, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseOkFailureEvent, copilotSessionId: sessionID, error: error as Error, durationInMills: responseTime, orgId: orgID }); return InvalidResponse; } } else { @@ -120,7 +119,7 @@ export async function sendApiRequest(params: IApiRequestParams) { const errorMessage = errorResponse.error && errorResponse.error.messages[0]; const responseError = new Error(errorMessage); - sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEventWithMessage, copilotSessionId: sessionID, responseStatus: String(response.status), error: responseError, durationInMills: responseTime, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseFailureEventWithMessage, copilotSessionId: sessionID, responseStatus: String(response.status), error: responseError, durationInMills: responseTime, orgId: orgID }); if (response.status === 429) { return RateLimitingResponse @@ -137,12 +136,12 @@ export async function sendApiRequest(params: IApiRequestParams) { return InvalidResponse; } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, responseStatus: String(response.status), error: error as Error, durationInMills: responseTime, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, responseStatus: String(response.status), error: error as Error, durationInMills: responseTime, orgId: orgID }); return InvalidResponse; } } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, error: error as Error, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotResponseFailureEvent, copilotSessionId: sessionID, error: error as Error, orgId: orgID }); return NetworkError; } diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 229a8735..cb3cc558 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -65,7 +65,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { this._disposables.push( vscode.commands.registerCommand("powerpages.copilot.clearConversation", () => { if (userName && orgID) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotClearChatEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotClearChatEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.sendMessageToWebview({ type: "clearConversation" }); sessionID = uuidv4(); } @@ -91,7 +91,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { const withinTokenLimit = isWithinTokenLimit(selectedCode, 1000); if (commandType === EXPLAIN_CODE) { const tokenSize = encode(selectedCode).length; - sendTelemetryEvent(this.telemetry, { eventName: CopilotExplainCodeSize, copilotSessionId: sessionID, orgId: orgID, userId: userID, codeLineCount: String(selectedCodeLineRange.end - selectedCodeLineRange.start), tokenSize: String(tokenSize) }); + sendTelemetryEvent({ eventName: CopilotExplainCodeSize, copilotSessionId: sessionID, orgId: orgID, userId: userID, codeLineCount: String(selectedCodeLineRange.end - selectedCodeLineRange.start), tokenSize: String(tokenSize) }); if (withinTokenLimit === false) { return; } @@ -104,7 +104,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { ); this._disposables.push( - vscode.commands.registerCommand("powerpages.copilot.explain", () => { sendTelemetryEvent(this.telemetry, { eventName: CopilotExplainCode, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.show(); handleSelectionChange(EXPLAIN_CODE) }) + vscode.commands.registerCommand("powerpages.copilot.explain", () => { sendTelemetryEvent({ eventName: CopilotExplainCode, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.show(); handleSelectionChange(EXPLAIN_CODE) }) ); } this._disposables.push( @@ -210,12 +210,12 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { this.sendMessageToWebview({ type: 'Unavailable' }); return; } else if (getDisabledOrgList()?.includes(orgID) || getDisabledTenantList()?.includes(tenantId ?? "")) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotNotAvailableECSConfig, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotNotAvailableECSConfig, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.sendMessageToWebview({ type: 'Unavailable' }); return; } - sendTelemetryEvent(this.telemetry, { eventName: CopilotLoadedEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotLoadedEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); this.sendMessageToWebview({ type: 'env' }); await this.checkAuthentication(); if (orgID && userName) { @@ -233,10 +233,10 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { break; } case "newUserPrompt": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserPromptedEvent, copilotSessionId: sessionID, aibEndpoint: this.aibEndpoint ?? '', orgId: orgID, userId: userID, isSuggestedPrompt: String(data.value.isSuggestedPrompt), crossGeoDataMovementEnabledPPACFlag: this.crossGeoDataMovementEnabledPPACFlag }); //TODO: Add active Editor info + sendTelemetryEvent({ eventName: CopilotUserPromptedEvent, copilotSessionId: sessionID, aibEndpoint: this.aibEndpoint ?? '', orgId: orgID, userId: userID, isSuggestedPrompt: String(data.value.isSuggestedPrompt), crossGeoDataMovementEnabledPPACFlag: this.crossGeoDataMovementEnabledPPACFlag }); //TODO: Add active Editor info orgID ? (async () => { - await this.authenticateAndSendAPIRequest(data.value.userPrompt, orgID, this.telemetry); + await this.authenticateAndSendAPIRequest(data.value.userPrompt, orgID); })() : (() => { this.sendMessageToWebview({ type: 'apiResponse', value: AuthProfileNotFound }); @@ -253,14 +253,14 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { vscode.window.activeTextEditor?.insertSnippet( new vscode.SnippetString(`${escapedSnippet}`) ); - sendTelemetryEvent(this.telemetry, { eventName: CopilotInsertCodeToEditorEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotInsertCodeToEditorEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); break; } case "copyCodeToClipboard": { vscode.env.clipboard.writeText(data.value); vscode.window.showInformationMessage(vscode.l10n.t('Copied to clipboard!')) - sendTelemetryEvent(this.telemetry, { eventName: CopilotCopyCodeToClipboardEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotCopyCodeToClipboardEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); break; } case "clearChat": { @@ -275,32 +275,32 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { if (feedbackValue === THUMBS_UP) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsUpEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID, subScenario: String(messageScenario) }); + sendTelemetryEvent({ eventName: CopilotUserFeedbackThumbsUpEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID, subScenario: String(messageScenario) }); CESUserFeedback(this._extensionContext, sessionID, userID, THUMBS_UP, this.telemetry, this.geoName as string, messageScenario, tenantId) } else if (feedbackValue === THUMBS_DOWN) { - sendTelemetryEvent(this.telemetry, { eventName: CopilotUserFeedbackThumbsDownEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID, subScenario: String(messageScenario) }); + sendTelemetryEvent({ eventName: CopilotUserFeedbackThumbsDownEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID, subScenario: String(messageScenario) }); CESUserFeedback(this._extensionContext, sessionID, userID, THUMBS_DOWN, this.telemetry, this.geoName as string, messageScenario, tenantId) } break; } case "walkthrough": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotWalkthroughEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotWalkthroughEvent, copilotSessionId: sessionID, orgId: orgID, userId: userID }); openWalkthrough(this._extensionUri); break; } case "codeLineCount": { - sendTelemetryEvent(this.telemetry, { eventName: CopilotCodeLineCountEvent, copilotSessionId: sessionID, codeLineCount: String(data.value), orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotCodeLineCountEvent, copilotSessionId: sessionID, codeLineCount: String(data.value), orgId: orgID, userId: userID }); break; } case "openGitHubCopilotLink": { //Open the GitHub Copilot Chat with @powerpages if GitHub Copilot Chat is installed - sendTelemetryEvent(this.telemetry, { eventName: CopilotPanelTryGitHubCopilotClicked, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: CopilotPanelTryGitHubCopilotClicked, copilotSessionId: sessionID, orgId: orgID, userId: userID }); if (vscode.extensions.getExtension(GITHUB_COPILOT_CHAT_EXT)) { - sendTelemetryEvent(this.telemetry, { eventName: VSCodeExtensionGitHubChatPanelOpened, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: VSCodeExtensionGitHubChatPanelOpened, copilotSessionId: sessionID, orgId: orgID, userId: userID }); vscode.commands.executeCommand('workbench.action.chat.open', PowerPagesParticipantPrompt); } else { - sendTelemetryEvent(this.telemetry, { eventName: VSCodeExtensionGitHubChatNotFound, copilotSessionId: sessionID, orgId: orgID, userId: userID }); + sendTelemetryEvent({ eventName: VSCodeExtensionGitHubChatNotFound, copilotSessionId: sessionID, orgId: orgID, userId: userID }); vscode.env.openExternal(vscode.Uri.parse(PowerPagesParticipantDocLink)); } } @@ -354,15 +354,15 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { if (session) { intelligenceApiToken = session.accessToken; userName = getUserName(session.account.label); - userID = getOIDFromToken(session.accessToken, this.telemetry); + userID = getOIDFromToken(session.accessToken); } else { intelligenceApiToken = ""; userName = ""; } } - private async authenticateAndSendAPIRequest(data: UserPrompt[], orgID: string, telemetry: ITelemetry) { - return intelligenceAPIAuthentication(telemetry, sessionID, orgID) + private async authenticateAndSendAPIRequest(data: UserPrompt[], orgID: string) { + return intelligenceAPIAuthentication(this.telemetry, sessionID, orgID) .then(async ({ accessToken, user, userId }) => { intelligenceApiToken = accessToken; userName = getUserName(user); @@ -376,15 +376,15 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { let componentInfo: string[] = []; if (activeFileParams.dataverseEntity == ADX_ENTITYFORM || activeFileParams.dataverseEntity == ADX_ENTITYLIST) { - metadataInfo = await getEntityName(telemetry, sessionID, activeFileParams.dataverseEntity); + metadataInfo = await getEntityName(this.telemetry, sessionID, activeFileParams.dataverseEntity); - const dataverseToken = (await dataverseAuthentication(telemetry, activeOrgUrl, true)).accessToken; + const dataverseToken = (await dataverseAuthentication(this.telemetry, activeOrgUrl, true)).accessToken; if (activeFileParams.dataverseEntity == ADX_ENTITYFORM) { - const formColumns = await getFormXml(metadataInfo.entityName, metadataInfo.formName, activeOrgUrl, dataverseToken, telemetry, sessionID); + const formColumns = await getFormXml(metadataInfo.entityName, metadataInfo.formName, activeOrgUrl, dataverseToken, this.telemetry, sessionID); componentInfo = formColumns; } else { - const entityColumns = await getEntityColumns(metadataInfo.entityName, activeOrgUrl, dataverseToken, telemetry, sessionID); + const entityColumns = await getEntityColumns(metadataInfo.entityName, activeOrgUrl, dataverseToken, this.telemetry, sessionID); componentInfo = entityColumns; } @@ -397,7 +397,6 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { sessionID: sessionID, entityName: metadataInfo.entityName, entityColumns: componentInfo, - telemetry: telemetry, aibEndpoint: this.aibEndpoint, geoName: this.geoName, crossGeoDataMovementEnabledPPACFlag: this.crossGeoDataMovementEnabledPPACFlag @@ -423,7 +422,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { environmentId = activeOrg.EnvironmentId sessionID = uuidv4(); // Generate a new session ID on org change - sendTelemetryEvent(this.telemetry, { eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); const intelligenceAPIEndpointInfo = await ArtemisService.getIntelligenceEndpoint(orgID, this.telemetry, sessionID, environmentId); if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { diff --git a/src/common/copilot/dataverseMetadata.ts b/src/common/copilot/dataverseMetadata.ts index 55b7ad21..a63b683b 100644 --- a/src/common/copilot/dataverseMetadata.ts +++ b/src/common/copilot/dataverseMetadata.ts @@ -36,11 +36,11 @@ export async function getEntityColumns(entityName: string, orgUrl: string, apiTo const responseTime = endTime - startTime || 0; const attributes = getAttributesFromResponse(jsonResponse); //Display name and logical name fetching from response - sendTelemetryEvent(telemetry, { eventName: CopilotDataverseMetadataSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgUrl: orgUrl }) + sendTelemetryEvent({ eventName: CopilotDataverseMetadataSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgUrl: orgUrl }) return attributes } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotDataverseMetadataFailureEvent, copilotSessionId: sessionID, error: error as Error, orgUrl: orgUrl }) + sendTelemetryEvent({ eventName: CopilotDataverseMetadataFailureEvent, copilotSessionId: sessionID, error: error as Error, orgUrl: orgUrl }) return []; } } @@ -70,11 +70,11 @@ export async function getFormXml(entityName: string, formName: string, orgUrl: s const formxml = getFormXMLFromResponse(jsonResponse); - sendTelemetryEvent(telemetry, { eventName: CopilotDataverseMetadataSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgUrl: orgUrl }) + sendTelemetryEvent({ eventName: CopilotDataverseMetadataSuccessEvent, copilotSessionId: sessionID, durationInMills: responseTime, orgUrl: orgUrl }) return parseXML(formxml); } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotDataverseMetadataFailureEvent, copilotSessionId: sessionID, error: error as Error, orgUrl: orgUrl }) + sendTelemetryEvent({ eventName: CopilotDataverseMetadataFailureEvent, copilotSessionId: sessionID, error: error as Error, orgUrl: orgUrl }) return []; } } @@ -193,7 +193,7 @@ export async function getEntityName(telemetry: ITelemetry, sessionID: string, da } } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotGetEntityFailureEvent, copilotSessionId: sessionID, dataverseEntity: dataverseEntity, error: error as Error }); + sendTelemetryEvent({ eventName: CopilotGetEntityFailureEvent, copilotSessionId: sessionID, dataverseEntity: dataverseEntity, error: error as Error }); entityName = ''; } return { entityName, formName }; @@ -215,7 +215,7 @@ function parseYamlContent(content: string, telemetry: ITelemetry, sessionID: str try { return yaml.parse(content); } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotYamlParsingFailureEvent, copilotSessionId: sessionID, dataverseEntity: dataverseEntity, error: error as Error }); + sendTelemetryEvent({ eventName: CopilotYamlParsingFailureEvent, copilotSessionId: sessionID, dataverseEntity: dataverseEntity, error: error as Error }); return {}; } } diff --git a/src/common/copilot/telemetry/copilotTelemetry.ts b/src/common/copilot/telemetry/copilotTelemetry.ts index 838b2cc2..26b72aa1 100644 --- a/src/common/copilot/telemetry/copilotTelemetry.ts +++ b/src/common/copilot/telemetry/copilotTelemetry.ts @@ -3,12 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ -import { ITelemetry } from "../../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { oneDSLoggerWrapper } from "../../OneDSLoggerTelemetry/oneDSLoggerWrapper"; import { IProDevCopilotTelemetryData } from "./ITelemetry"; -export function sendTelemetryEvent(telemetry: ITelemetry, telemetryData: IProDevCopilotTelemetryData): void { +export function sendTelemetryEvent(telemetryData: IProDevCopilotTelemetryData): void { const telemetryDataProperties: Record = {} const telemetryDataMeasurements: Record = {} @@ -33,11 +32,9 @@ export function sendTelemetryEvent(telemetry: ITelemetry, telemetryData: IProDev if (telemetryData.error) { telemetryDataProperties.eventName = telemetryData.eventName; - telemetry.sendTelemetryException(telemetryData.error, telemetryDataProperties, telemetryDataMeasurements); oneDSLoggerWrapper.getLogger().traceError(telemetryData.error.name, telemetryData.error.message, telemetryData.error, telemetryDataProperties, telemetryDataMeasurements); } else { - telemetry.sendTelemetryEvent(telemetryData.eventName, telemetryDataProperties, telemetryDataMeasurements); oneDSLoggerWrapper.getLogger().traceInfo(telemetryData.eventName, telemetryDataProperties, telemetryDataMeasurements); } } diff --git a/src/common/copilot/user-feedback/CESSurvey.ts b/src/common/copilot/user-feedback/CESSurvey.ts index ba659ab6..83e8d5fb 100644 --- a/src/common/copilot/user-feedback/CESSurvey.ts +++ b/src/common/copilot/user-feedback/CESSurvey.ts @@ -39,9 +39,9 @@ export async function CESUserFeedback(context: vscode.ExtensionContext, sessionI const apiToken: string = await npsAuthentication(telemetry, SurveyConstants.AUTHORIZATION_ENDPOINT); if (apiToken) { - sendTelemetryEvent(telemetry, { eventName: CopilotNpsAuthenticationCompleted, feedbackType: thumbType, copilotSessionId: sessionId }); + sendTelemetryEvent({ eventName: CopilotNpsAuthenticationCompleted, feedbackType: thumbType, copilotSessionId: sessionId }); } else { - sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackFailureEvent, feedbackType: thumbType, copilotSessionId: sessionId, error: new Error(ERROR_CONSTANTS.NPS_FAILED_AUTH) }); + sendTelemetryEvent({ eventName: CopilotUserFeedbackFailureEvent, feedbackType: thumbType, copilotSessionId: sessionId, error: new Error(ERROR_CONSTANTS.NPS_FAILED_AUTH) }); } const endpointUrl = useEUEndpoint(geoName) ? `https://europe.ces.microsoftcloud.com/api/v1/portalsdesigner/Surveys/powerpageschatgpt/Feedbacks?userId=${userID}` : @@ -141,15 +141,15 @@ async function handleFeedbackSubmission(text: string, endpointUrl: string, apiTo // Feedback sent successfully const responseJson = await response.json(); const feedbackId = responseJson.FeedbackId; - sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackSuccessEvent, feedbackType: thumbType, FeedbackId: feedbackId, copilotSessionId: sessionID }); + sendTelemetryEvent({ eventName: CopilotUserFeedbackSuccessEvent, feedbackType: thumbType, FeedbackId: feedbackId, copilotSessionId: sessionID }); } else { // Error sending feedback const feedBackError = new Error(response.statusText); - sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackFailureEvent, feedbackType: thumbType, copilotSessionId: sessionID, error: feedBackError }); + sendTelemetryEvent({ eventName: CopilotUserFeedbackFailureEvent, feedbackType: thumbType, copilotSessionId: sessionID, error: feedBackError }); } } catch (error) { // Network error or other exception - sendTelemetryEvent(telemetry, { eventName: CopilotUserFeedbackFailureEvent, feedbackType: thumbType, copilotSessionId: sessionID, error: error as Error }); + sendTelemetryEvent({ eventName: CopilotUserFeedbackFailureEvent, feedbackType: thumbType, copilotSessionId: sessionID, error: error as Error }); } } diff --git a/src/common/services/ArtemisService.ts b/src/common/services/ArtemisService.ts index 10c623a1..6789b93b 100644 --- a/src/common/services/ArtemisService.ts +++ b/src/common/services/ArtemisService.ts @@ -24,7 +24,7 @@ export class ArtemisService { const endpointStamp = artemisResponse.stamp; const { geoName, environment, clusterNumber } = artemisResponse.response as IArtemisAPIOrgResponse; - sendTelemetryEvent(telemetry, { eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); + sendTelemetryEvent({ eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp, telemetry, environmentId); @@ -79,7 +79,7 @@ export class ArtemisService { const successfulResponses = results.filter(result => result !== null && result.response !== null); return successfulResponses as IArtemisServiceResponse[]; } catch (error) { - sendTelemetryEvent(telemetry, { eventName: CopilotArtemisFailureEvent, copilotSessionId: sessionID, error: error as Error }) + sendTelemetryEvent({ eventName: CopilotArtemisFailureEvent, copilotSessionId: sessionID, error: error as Error }) return null; } } diff --git a/src/common/services/AuthenticationProvider.ts b/src/common/services/AuthenticationProvider.ts index 4d2f284d..4766ca0f 100644 --- a/src/common/services/AuthenticationProvider.ts +++ b/src/common/services/AuthenticationProvider.ts @@ -94,18 +94,18 @@ export async function intelligenceAPIAuthentication(telemetry: ITelemetry, sessi } accessToken = session?.accessToken ?? ''; user = session.account.label; - userId = getOIDFromToken(accessToken, telemetry); + userId = getOIDFromToken(accessToken); if (!accessToken) { throw new Error(ERROR_CONSTANTS.NO_ACCESS_TOKEN); } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, { eventName: CopilotLoginSuccessEvent, copilotSessionId: sessionID, orgId: orgId }); + sendTelemetryEvent({ eventName: CopilotLoginSuccessEvent, copilotSessionId: sessionID, orgId: orgId }); } } catch (error) { showErrorDialog(vscode.l10n.t("Authorization Failed. Please run again to authorize it"), vscode.l10n.t("There was a permissions problem with the server")); - sendTelemetryEvent(telemetry, { eventName: CopilotLoginFailureEvent, copilotSessionId: sessionID, orgId: orgId, errorMsg: (error as Error).message }); + sendTelemetryEvent({ eventName: CopilotLoginFailureEvent, copilotSessionId: sessionID, orgId: orgId, errorMsg: (error as Error).message }); } return { accessToken, user, userId }; } @@ -138,18 +138,16 @@ export async function dataverseAuthentication( } accessToken = session?.accessToken ?? ""; - userId = getOIDFromToken(accessToken, telemetry); + userId = getOIDFromToken(accessToken); if (!accessToken) { throw new Error(ERROR_CONSTANTS.NO_ACCESS_TOKEN); } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, - { - eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED, - userId: userId - } - ); + sendTelemetryEvent({ + eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_COMPLETED, + userId: userId + }); } } catch (error) { showErrorDialog( @@ -159,10 +157,10 @@ export async function dataverseAuthentication( vscode.l10n.t("There was a permissions problem with the server") ); sendTelemetryEvent( - telemetry, { - eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED, - errorMsg: (error as Error).message - } + { + eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED, + errorMsg: (error as Error).message + } ); } @@ -174,7 +172,7 @@ export async function npsAuthentication( cesSurveyAuthorizationEndpoint: string ): Promise { let accessToken = ""; - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_NPS_AUTHENTICATION_STARTED } ); try { @@ -187,7 +185,7 @@ export async function npsAuthentication( if (!accessToken) { throw new Error(ERROR_CONSTANTS.NO_ACCESS_TOKEN); } - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_NPS_AUTHENTICATION_COMPLETED } ); } catch (error) { @@ -198,7 +196,6 @@ export async function npsAuthentication( vscode.l10n.t("There was a permissions problem with the server") ); sendTelemetryEvent( - telemetry, { eventName: VSCODE_EXTENSION_NPS_AUTHENTICATION_FAILED, errorMsg: (error as Error).message @@ -241,9 +238,9 @@ export async function graphClientAuthentication( } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_COMPLETED, - userId: getOIDFromToken(accessToken, telemetry), + userId: getOIDFromToken(accessToken), }); } } catch (error) { @@ -253,7 +250,7 @@ export async function graphClientAuthentication( ), vscode.l10n.t("There was a permissions problem with the server") ); - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED, errorMsg: (error as Error).message } ) } @@ -287,9 +284,9 @@ export async function bapServiceAuthentication( } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_BAP_SERVICE_AUTHENTICATION_COMPLETED, - userId: getOIDFromToken(accessToken, telemetry), + userId: getOIDFromToken(accessToken), }); } } catch (error) { @@ -299,7 +296,7 @@ export async function bapServiceAuthentication( ), vscode.l10n.t("There was a permissions problem with the server") ); - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_BAP_SERVICE_AUTHENTICATION_FAILED, errorMsg: (error as Error).message } ) } @@ -307,12 +304,12 @@ export async function bapServiceAuthentication( return accessToken; } -export function getOIDFromToken(token: string, telemetry: ITelemetry) { +export function getOIDFromToken(token: string) { try { const decoded = jwt_decode(token); return decoded?.oid ?? ""; } catch (error) { - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_DECODE_JWT_TOKEN_FAILED, errorMsg: (error as Error).message } ) } @@ -347,7 +344,7 @@ export async function powerPlatformAPIAuthentication( } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_COMPLETED, userId: session?.account.id.split("/").pop() ?? @@ -362,7 +359,7 @@ export async function powerPlatformAPIAuthentication( ), vscode.l10n.t("There was a permissions problem with the server") ); - sendTelemetryEvent(telemetry, + sendTelemetryEvent( { eventName: VSCODE_EXTENSION_PPAPI_WEBSITES_AUTHENTICATION_FAILED, errorMsg: (error as Error).message } ) } diff --git a/src/common/services/BAPService.ts b/src/common/services/BAPService.ts index bcfb5131..f9dd5c1b 100644 --- a/src/common/services/BAPService.ts +++ b/src/common/services/BAPService.ts @@ -23,12 +23,12 @@ export class BAPService { if (response.ok) { const data = await response.json(); - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_COMPLETED, data: data.properties.copilotPolicies?.crossGeoCopilotDataMovementEnabled }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_COMPLETED, data: data.properties.copilotPolicies?.crossGeoCopilotDataMovementEnabled }); return data.properties.copilotPolicies?.crossGeoCopilotDataMovementEnabled; } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, errorMsg: (error as Error).message }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, errorMsg: (error as Error).message }); } return false; @@ -36,7 +36,7 @@ export class BAPService { static async getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry, environmentId: string): Promise { - const bapEndpoint = await getBAPEndpoint(serviceEndpointStamp, telemetry); + const bapEndpoint = await getBAPEndpoint(serviceEndpointStamp); return BAP_SERVICE_ENDPOINT.replace('{rootURL}', bapEndpoint) + BAP_SERVICE_COPILOT_CROSS_GEO_FLAG_RELATIVE_URL.replace('{environmentID}', environmentId).replace('{apiVersion}', BAP_API_VERSION); diff --git a/src/common/services/PPAPIService.ts b/src/common/services/PPAPIService.ts index 7cc7ae13..8d13a764 100644 --- a/src/common/services/PPAPIService.ts +++ b/src/common/services/PPAPIService.ts @@ -22,12 +22,12 @@ export class PPAPIService { if (response.ok) { const websiteDetails = await response.json() as unknown as IWebsiteDetails; - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_COMPLETED, orgUrl: websiteDetails.dataverseInstanceUrl }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_COMPLETED, orgUrl: websiteDetails.dataverseInstanceUrl }); return websiteDetails; } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, errorMsg: (error as Error).message }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, errorMsg: (error as Error).message }); } return null; @@ -36,7 +36,7 @@ export class PPAPIService { public static async getWebsiteDetailsByWebsiteRecordId(serviceEndpointStamp: ServiceEndpointCategory | undefined, environmentId: string, websiteRecordId: string, telemetry: ITelemetry): Promise { if (!serviceEndpointStamp) { - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_SERVICE_STAMP_NOT_FOUND, data: serviceEndpointStamp }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_SERVICE_STAMP_NOT_FOUND, data: serviceEndpointStamp }); return null; } @@ -46,7 +46,7 @@ export class PPAPIService { if (websiteDetails) { - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_RECORD_ID_COMPLETED, orgUrl: websiteDetails.dataverseInstanceUrl }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_RECORD_ID_COMPLETED, orgUrl: websiteDetails.dataverseInstanceUrl }); return websiteDetails; } @@ -67,7 +67,7 @@ export class PPAPIService { } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_DETAILS_FAILED, errorMsg: (error as Error).message }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_DETAILS_FAILED, errorMsg: (error as Error).message }); } return null; } @@ -95,7 +95,7 @@ export class PPAPIService { ppApiEndpoint = "https://api.powerplatform.cn"; break; default: - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_PPAPI_WEBSITES_ENDPOINT_UNSUPPORTED_REGION, data: serviceEndpointStamp }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_PPAPI_WEBSITES_ENDPOINT_UNSUPPORTED_REGION, data: serviceEndpointStamp }); break; } diff --git a/src/common/utilities/Utils.ts b/src/common/utilities/Utils.ts index 810d8a15..a803df46 100644 --- a/src/common/utilities/Utils.ts +++ b/src/common/utilities/Utils.ts @@ -208,10 +208,10 @@ export function checkCopilotAvailability( return false; } else if (aibEndpoint === COPILOT_UNAVAILABLE) { - sendTelemetryEvent(telemetry, { eventName: CopilotNotAvailable, copilotSessionId: sessionID, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotNotAvailable, copilotSessionId: sessionID, orgId: orgID }); return false; } else if (getDisabledOrgList()?.includes(orgID) || getDisabledTenantList()?.includes(tenantId ?? "")) { // Tenant ID not available in desktop - sendTelemetryEvent(telemetry, { eventName: CopilotNotAvailableECSConfig, copilotSessionId: sessionID, orgId: orgID }); + sendTelemetryEvent({ eventName: CopilotNotAvailableECSConfig, copilotSessionId: sessionID, orgId: orgID }); return false; } else { return true; @@ -337,7 +337,7 @@ export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEn const envInfo: IEnvInfo[] = []; try { const bapAuthToken = await bapServiceAuthentication(telemetry, true); - const bapEndpoint = getBAPEndpoint(endpointStamp, telemetry); + const bapEndpoint = getBAPEndpoint(endpointStamp); const envListEndpoint = `${bapEndpoint}${BAP_ENVIRONMENT_LIST_URL.replace('{apiVersion}', BAP_API_VERSION)}`; const envListResponse = await fetch(envListEndpoint, { @@ -356,17 +356,17 @@ export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEn envDisplayName: env.properties.displayName }); }); - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_ENV_LIST_SUCCESS }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_ENV_LIST_SUCCESS }); oneDSLoggerWrapper.getLogger().traceInfo(VSCODE_EXTENSION_GET_ENV_LIST_SUCCESS); } else { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_ENV_LIST_FAILED, errorMsg: envListResponse.statusText }); oneDSLoggerWrapper.getLogger().traceError(VSCODE_EXTENSION_GET_ENV_LIST_FAILED, VSCODE_EXTENSION_GET_ENV_LIST_FAILED, new Error(envListResponse.statusText)); } } catch (error) { - sendTelemetryEvent(telemetry, { + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_ENV_LIST_FAILED, errorMsg: (error as Error).message }); @@ -376,7 +376,7 @@ export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEn } -export function getBAPEndpoint(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry): string { +export function getBAPEndpoint(serviceEndpointStamp: ServiceEndpointCategory): string { let bapEndpoint = ""; switch (serviceEndpointStamp) { @@ -395,7 +395,7 @@ export function getBAPEndpoint(serviceEndpointStamp: ServiceEndpointCategory, te case ServiceEndpointCategory.HIGH: case ServiceEndpointCategory.MOONCAKE: default: - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_BAP_ENDPOINT_UNSUPPORTED_REGION, data: serviceEndpointStamp }); + sendTelemetryEvent({ eventName: VSCODE_EXTENSION_GET_BAP_ENDPOINT_UNSUPPORTED_REGION, data: serviceEndpointStamp }); oneDSLoggerWrapper.getLogger().traceInfo(VSCODE_EXTENSION_GET_BAP_ENDPOINT_UNSUPPORTED_REGION, { data: serviceEndpointStamp }); break; } From 91b17ae70c59fb1794725c02f81880695a74b0c5 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 12:53:53 +0530 Subject: [PATCH 13/38] Refactor: Remove telemetry parameter from getEndpoint and related functions --- .../powerpages/PowerPagesChatParticipant.ts | 2 +- .../powerpages/PowerPagesChatParticipantUtils.ts | 3 +-- src/common/copilot/PowerPagesCopilot.ts | 2 +- src/common/services/ArtemisService.ts | 13 ++++++------- src/common/services/AuthenticationProvider.ts | 1 - src/common/services/BAPService.ts | 9 ++++----- src/common/utilities/Utils.ts | 4 ++-- 7 files changed, 15 insertions(+), 19 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index ffce51f1..e23acbfe 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -131,7 +131,7 @@ export class PowerPagesChatParticipant { const intelligenceApiToken = intelligenceApiAuthResponse.accessToken; const userId = intelligenceApiAuthResponse.userId; - const intelligenceAPIEndpointInfo = await getEndpoint(this.orgID, this.environmentID, this.telemetry, this.cachedEndpoint, this.powerPagesAgentSessionId); + const intelligenceAPIEndpointInfo = await getEndpoint(this.orgID, this.environmentID, this.cachedEndpoint, this.powerPagesAgentSessionId); if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { return createErrorResult(COPILOT_NOT_AVAILABLE_MSG, RESPONSE_SCENARIOS.COPILOT_NOT_AVAILABLE, this.orgID); diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts index 8fd130f7..0a66e2c3 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts @@ -22,12 +22,11 @@ import * as vscode from 'vscode'; export async function getEndpoint( orgID: string, environmentID: string, - telemetry: ITelemetry, cachedEndpoint: IIntelligenceAPIEndpointInformation | null, sessionID: string ): Promise { if (!cachedEndpoint) { - cachedEndpoint = await ArtemisService.getIntelligenceEndpoint(orgID, telemetry, sessionID, environmentID) as IIntelligenceAPIEndpointInformation; // TODO - add session ID + cachedEndpoint = await ArtemisService.getIntelligenceEndpoint(orgID, sessionID, environmentID) as IIntelligenceAPIEndpointInformation; // TODO - add session ID } return cachedEndpoint; } diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index cb3cc558..52f38666 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -424,7 +424,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { sessionID = uuidv4(); // Generate a new session ID on org change sendTelemetryEvent({ eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); - const intelligenceAPIEndpointInfo = await ArtemisService.getIntelligenceEndpoint(orgID, this.telemetry, sessionID, environmentId); + const intelligenceAPIEndpointInfo = await ArtemisService.getIntelligenceEndpoint(orgID, sessionID, environmentId); if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { this.sendMessageToWebview({ type: 'Unavailable' }); return; diff --git a/src/common/services/ArtemisService.ts b/src/common/services/ArtemisService.ts index 6789b93b..0978c0c5 100644 --- a/src/common/services/ArtemisService.ts +++ b/src/common/services/ArtemisService.ts @@ -5,7 +5,6 @@ import fetch, { RequestInit } from "node-fetch"; import { COPILOT_UNAVAILABLE } from "../copilot/constants"; -import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry"; import { CopilotArtemisFailureEvent, CopilotArtemisSuccessEvent } from "../copilot/telemetry/telemetryConstants"; import { ServiceEndpointCategory } from "./Constants"; @@ -14,9 +13,9 @@ import { isCopilotDisabledInGeo, isCopilotSupportedInGeo } from "../copilot/util import { BAPService } from "./BAPService"; export class ArtemisService { - public static async getIntelligenceEndpoint(orgId: string, telemetry: ITelemetry, sessionID: string, environmentId: string): Promise { + public static async getIntelligenceEndpoint(orgId: string, sessionID: string, environmentId: string): Promise { - const artemisResponse = await ArtemisService.getArtemisResponse(orgId, telemetry, sessionID); + const artemisResponse = await ArtemisService.getArtemisResponse(orgId, sessionID); if (artemisResponse === null) { return { intelligenceEndpoint: null, geoName: null, crossGeoDataMovementEnabledPPACFlag: false }; @@ -26,7 +25,7 @@ export class ArtemisService { const { geoName, environment, clusterNumber } = artemisResponse.response as IArtemisAPIOrgResponse; sendTelemetryEvent({ eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); - const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp, telemetry, environmentId); + const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp,environmentId); if (isCopilotDisabledInGeo().includes(geoName)) { return { intelligenceEndpoint: COPILOT_UNAVAILABLE, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag }; @@ -44,9 +43,9 @@ export class ArtemisService { } // Function to fetch Artemis response - public static async getArtemisResponse(orgId: string, telemetry: ITelemetry, sessionID: string): Promise { + public static async getArtemisResponse(orgId: string, sessionID: string): Promise { const endpointDetails = ArtemisService.convertGuidToUrls(orgId); - const artemisResponses = await ArtemisService.fetchIslandInfo(endpointDetails, telemetry, sessionID); + const artemisResponses = await ArtemisService.fetchIslandInfo(endpointDetails, sessionID); if (artemisResponses === null || artemisResponses.length === 0) { return null; @@ -55,7 +54,7 @@ export class ArtemisService { return artemisResponses[0]; } - static async fetchIslandInfo(endpointDetails: IArtemisServiceEndpointInformation[], telemetry: ITelemetry, sessionID: string): Promise { + static async fetchIslandInfo(endpointDetails: IArtemisServiceEndpointInformation[], sessionID: string): Promise { const requestInit: RequestInit = { method: 'GET', diff --git a/src/common/services/AuthenticationProvider.ts b/src/common/services/AuthenticationProvider.ts index 4766ca0f..a96ce5e0 100644 --- a/src/common/services/AuthenticationProvider.ts +++ b/src/common/services/AuthenticationProvider.ts @@ -259,7 +259,6 @@ export async function graphClientAuthentication( } export async function bapServiceAuthentication( - telemetry: ITelemetry, firstTimeAuth = false ): Promise { let accessToken = ""; diff --git a/src/common/services/BAPService.ts b/src/common/services/BAPService.ts index f9dd5c1b..e16fa87e 100644 --- a/src/common/services/BAPService.ts +++ b/src/common/services/BAPService.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ -import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { bapServiceAuthentication, getCommonHeaders } from "./AuthenticationProvider"; import { VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_COMPLETED, VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED } from "./TelemetryConstants"; import { ServiceEndpointCategory, BAP_API_VERSION, BAP_SERVICE_COPILOT_CROSS_GEO_FLAG_RELATIVE_URL, BAP_SERVICE_ENDPOINT } from "./Constants"; @@ -11,12 +10,12 @@ import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry"; import { getBAPEndpoint } from "../utilities/Utils"; export class BAPService { - public static async getCrossGeoCopilotDataMovementEnabledFlag(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry, environmentId: string): Promise { + public static async getCrossGeoCopilotDataMovementEnabledFlag(serviceEndpointStamp: ServiceEndpointCategory, environmentId: string): Promise { try { - const accessToken = await bapServiceAuthentication(telemetry, true); + const accessToken = await bapServiceAuthentication(true); - const response = await fetch(await BAPService.getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp, telemetry, environmentId), { + const response = await fetch(await BAPService.getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp, environmentId), { method: 'GET', headers: getCommonHeaders(accessToken) }); @@ -34,7 +33,7 @@ export class BAPService { return false; } - static async getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp: ServiceEndpointCategory, telemetry: ITelemetry, environmentId: string): Promise { + static async getBAPCopilotCrossGeoFlagEndpoint(serviceEndpointStamp: ServiceEndpointCategory, environmentId: string): Promise { const bapEndpoint = await getBAPEndpoint(serviceEndpointStamp); diff --git a/src/common/utilities/Utils.ts b/src/common/utilities/Utils.ts index a803df46..b92127e9 100644 --- a/src/common/utilities/Utils.ts +++ b/src/common/utilities/Utils.ts @@ -330,13 +330,13 @@ export function getECSOrgLocationValue(clusterName: string, clusterNumber: strin } //API call to get env list for an org -export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEndpointCategory | undefined): Promise { +export async function getEnvList(endpointStamp: ServiceEndpointCategory | undefined): Promise { if(!endpointStamp) { return []; } const envInfo: IEnvInfo[] = []; try { - const bapAuthToken = await bapServiceAuthentication(telemetry, true); + const bapAuthToken = await bapServiceAuthentication(true); const bapEndpoint = getBAPEndpoint(endpointStamp); const envListEndpoint = `${bapEndpoint}${BAP_ENVIRONMENT_LIST_URL.replace('{apiVersion}', BAP_API_VERSION)}`; From 22edc08c8cd7d31252929d20fa641a0cb6bc80fc Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 14:39:39 +0530 Subject: [PATCH 14/38] Refactor: Remove telemetry parameter from authentication functions and related calls --- .../PowerPagesChatParticipantUtils.ts | 2 +- src/common/copilot/PowerPagesCopilot.ts | 2 +- src/common/copilot/user-feedback/CESSurvey.ts | 2 +- src/common/services/AuthenticationProvider.ts | 4 ---- src/common/services/PPAPIService.ts | 4 ++-- .../integration/AuthenticationProvider.test.ts | 17 ++++------------- 6 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts index 0a66e2c3..52207fe9 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts @@ -43,7 +43,7 @@ export async function getComponentInfo(telemetry: ITelemetry, orgUrl: string | u if (isEntityInSupportedList(activeFileParams.dataverseEntity)) { metadataInfo = await getEntityName(telemetry, sessionID, activeFileParams.dataverseEntity); - const dataverseToken = (await dataverseAuthentication(telemetry, orgUrl ?? '', true)).accessToken; + const dataverseToken = (await dataverseAuthentication(orgUrl ?? '', true)).accessToken; if (activeFileParams.dataverseEntity == ADX_ENTITYFORM) { const formColumns = await getFormXml(metadataInfo.entityName, metadataInfo.formName, orgUrl ?? '', dataverseToken, telemetry, sessionID); diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 52f38666..30c9ab4c 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -378,7 +378,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { if (activeFileParams.dataverseEntity == ADX_ENTITYFORM || activeFileParams.dataverseEntity == ADX_ENTITYLIST) { metadataInfo = await getEntityName(this.telemetry, sessionID, activeFileParams.dataverseEntity); - const dataverseToken = (await dataverseAuthentication(this.telemetry, activeOrgUrl, true)).accessToken; + const dataverseToken = (await dataverseAuthentication(activeOrgUrl, true)).accessToken; if (activeFileParams.dataverseEntity == ADX_ENTITYFORM) { const formColumns = await getFormXml(metadataInfo.entityName, metadataInfo.formName, activeOrgUrl, dataverseToken, this.telemetry, sessionID); diff --git a/src/common/copilot/user-feedback/CESSurvey.ts b/src/common/copilot/user-feedback/CESSurvey.ts index 83e8d5fb..d42e22a0 100644 --- a/src/common/copilot/user-feedback/CESSurvey.ts +++ b/src/common/copilot/user-feedback/CESSurvey.ts @@ -36,7 +36,7 @@ export async function CESUserFeedback(context: vscode.ExtensionContext, sessionI const feedbackData = initializeFeedbackData(sessionId, vscode.env.uiKind === vscode.UIKind.Web, geoName, messageScenario, tenantId); - const apiToken: string = await npsAuthentication(telemetry, SurveyConstants.AUTHORIZATION_ENDPOINT); + const apiToken: string = await npsAuthentication(SurveyConstants.AUTHORIZATION_ENDPOINT); if (apiToken) { sendTelemetryEvent({ eventName: CopilotNpsAuthenticationCompleted, feedbackType: thumbType, copilotSessionId: sessionId }); diff --git a/src/common/services/AuthenticationProvider.ts b/src/common/services/AuthenticationProvider.ts index a96ce5e0..2c200000 100644 --- a/src/common/services/AuthenticationProvider.ts +++ b/src/common/services/AuthenticationProvider.ts @@ -111,7 +111,6 @@ export async function intelligenceAPIAuthentication(telemetry: ITelemetry, sessi } export async function dataverseAuthentication( - telemetry: ITelemetry, dataverseOrgURL: string, firstTimeAuth = false ): Promise<{ accessToken: string, userId: string }> { @@ -168,7 +167,6 @@ export async function dataverseAuthentication( } export async function npsAuthentication( - telemetry: ITelemetry, cesSurveyAuthorizationEndpoint: string ): Promise { let accessToken = ""; @@ -207,7 +205,6 @@ export async function npsAuthentication( } export async function graphClientAuthentication( - telemetry: ITelemetry, firstTimeAuth = false ): Promise { let accessToken = ""; @@ -316,7 +313,6 @@ export function getOIDFromToken(token: string) { } export async function powerPlatformAPIAuthentication( - telemetry: ITelemetry, serviceEndpointStamp: ServiceEndpointCategory, firstTimeAuth = false ): Promise { diff --git a/src/common/services/PPAPIService.ts b/src/common/services/PPAPIService.ts index 8d13a764..614193e9 100644 --- a/src/common/services/PPAPIService.ts +++ b/src/common/services/PPAPIService.ts @@ -14,7 +14,7 @@ export class PPAPIService { public static async getWebsiteDetailsById(serviceEndpointStamp: ServiceEndpointCategory, environmentId: string, websitePreviewId: string, telemetry: ITelemetry): Promise { // websitePreviewId aka portalId try { - const accessToken = await powerPlatformAPIAuthentication(telemetry, serviceEndpointStamp, true); + const accessToken = await powerPlatformAPIAuthentication(serviceEndpointStamp, true); const response = await fetch(await PPAPIService.getPPAPIServiceEndpoint(serviceEndpointStamp, telemetry, environmentId, websitePreviewId), { method: 'GET', headers: getCommonHeaders(accessToken) @@ -55,7 +55,7 @@ export class PPAPIService { static async getWebsiteDetails(serviceEndpointStamp: ServiceEndpointCategory, environmentId: string, telemetry: ITelemetry): Promise<{ value: IWebsiteDetails[] } | null> { try { - const accessToken = await powerPlatformAPIAuthentication(telemetry, serviceEndpointStamp, true); + const accessToken = await powerPlatformAPIAuthentication(serviceEndpointStamp, true); const response = await fetch(await PPAPIService.getPPAPIServiceEndpoint(serviceEndpointStamp, telemetry, environmentId), { method: 'GET', headers: getCommonHeaders(accessToken) diff --git a/src/web/client/test/integration/AuthenticationProvider.test.ts b/src/web/client/test/integration/AuthenticationProvider.test.ts index 55378eca..795408f8 100644 --- a/src/web/client/test/integration/AuthenticationProvider.test.ts +++ b/src/web/client/test/integration/AuthenticationProvider.test.ts @@ -13,9 +13,7 @@ import vscode from "vscode"; import * as errorHandler from "../../../../common/utilities/errorHandlerUtil"; import { oneDSLoggerWrapper } from "../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; import * as copilotTelemetry from "../../../../common/copilot/telemetry/copilotTelemetry"; -import { vscodeExtAppInsightsResourceProvider } from "../../../../common/telemetry-generated/telemetryConfiguration"; import { VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED } from "../../../../common/services/TelemetryConstants"; -import TelemetryReporter from "@vscode/extension-telemetry"; // eslint-disable-next-line @typescript-eslint/no-explicit-any let traceError: any @@ -35,13 +33,6 @@ describe("Authentication Provider", () => { sinon.restore(); }); - const appInsightsResource = - vscodeExtAppInsightsResourceProvider.GetAppInsightsResourceForDataBoundary( - undefined - ); - - const telemetry = new TelemetryReporter("", "", appInsightsResource.instrumentationKey); - it("getHeader", () => { const accessToken = "f068ee9f-a010-47b9-b1e1-7e6353730e7d"; const result = getCommonHeaders(accessToken); @@ -62,7 +53,7 @@ describe("Authentication Provider", () => { scopes: [], }); - const result = await dataverseAuthentication(telemetry, dataverseOrgURL); + const result = await dataverseAuthentication(dataverseOrgURL); sinon.assert.calledOnce(_mockgetSession); expect(result.accessToken).eq("f068ee9f-a010-47b9-b1e1-7e6353730e7d"); expect(result.userId).empty; @@ -90,7 +81,7 @@ describe("Authentication Provider", () => { ); //Act - await dataverseAuthentication(telemetry, dataverseOrgURL); + await dataverseAuthentication(dataverseOrgURL); sinon.assert.calledWith( showErrorDialog, @@ -116,13 +107,13 @@ describe("Authentication Provider", () => { ); // Act - const result = await dataverseAuthentication(telemetry, dataverseOrgURL); + const result = await dataverseAuthentication(dataverseOrgURL); //Assert sinon.assert.calledOnce(sendTelemetryEvent); sinon.assert.calledWith( sendTelemetryEvent, - telemetry, { + { eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED, errorMsg: errorMessage } From c6bc1708e61f85d30f89331c031a6e97fbfb6360 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 15:07:24 +0530 Subject: [PATCH 15/38] Refactor telemetry event assertion in AuthenticationProvider tests --- src/web/client/test/integration/AuthenticationProvider.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/web/client/test/integration/AuthenticationProvider.test.ts b/src/web/client/test/integration/AuthenticationProvider.test.ts index 55378eca..bd45800e 100644 --- a/src/web/client/test/integration/AuthenticationProvider.test.ts +++ b/src/web/client/test/integration/AuthenticationProvider.test.ts @@ -121,8 +121,7 @@ describe("Authentication Provider", () => { //Assert sinon.assert.calledOnce(sendTelemetryEvent); sinon.assert.calledWith( - sendTelemetryEvent, - telemetry, { + sendTelemetryEvent, { eventName: VSCODE_EXTENSION_DATAVERSE_AUTHENTICATION_FAILED, errorMsg: errorMessage } From f49c9c369b3023f7a574a110d6a7b4a5153cb305 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 15:53:37 +0530 Subject: [PATCH 16/38] Refactor: Remove telemetry parameter from getArtemisResponse call in activate function --- src/client/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/extension.ts b/src/client/extension.ts index 5a23aca2..744ab694 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -196,7 +196,7 @@ export async function activate( _context.subscriptions.push( orgChangeEvent(async (orgDetails: ActiveOrgOutput) => { const orgID = orgDetails.OrgId; - const artemisResponse = await ArtemisService.getArtemisResponse(orgID, _telemetry, ""); + const artemisResponse = await ArtemisService.getArtemisResponse(orgID, ""); if (artemisResponse !== null && artemisResponse.response !== null) { const { geoName, geoLongName, clusterName, clusterNumber } = artemisResponse.response; const pacActiveAuth = await pacTerminal.getWrapper()?.activeAuth(); From adcbed0e84335bdc6f78ed7e2b058a75030dc492 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 16:44:48 +0530 Subject: [PATCH 17/38] Refactor: Remove telemetry parameter from various authentication and service calls --- src/client/power-pages/preview-site/PreviewSite.ts | 4 ++-- .../powerpages/commands/create-site/CreateSiteHelper.ts | 2 +- src/web/client/WebExtensionContext.ts | 1 - src/web/client/extension.ts | 2 +- src/web/client/services/NPSService.ts | 2 +- src/web/client/services/graphClientService.ts | 2 +- src/web/client/test/integration/WebExtensionContext.test.ts | 6 +++--- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/client/power-pages/preview-site/PreviewSite.ts b/src/client/power-pages/preview-site/PreviewSite.ts index abba3e51..a47a1f49 100644 --- a/src/client/power-pages/preview-site/PreviewSite.ts +++ b/src/client/power-pages/preview-site/PreviewSite.ts @@ -204,7 +204,7 @@ export class PreviewSite { async (progress) => { progress.report({ message: Messages.CLEARING_CACHE }); - const authResponse = await dataverseAuthentication(telemetry, orgDetails.orgUrl); + const authResponse = await dataverseAuthentication(orgDetails.orgUrl); const clearCacheResponse = await fetch(requestUrl, { headers: { @@ -241,7 +241,7 @@ export class PreviewSite { async (progress) => { progress.report({ message: Messages.GETTING_REGION_INFORMATION }); - const artemisResponse = await ArtemisService.getArtemisResponse(orgDetails.orgID, telemetry, ""); + const artemisResponse = await ArtemisService.getArtemisResponse(orgDetails.orgID, ""); if (artemisResponse === null || artemisResponse.response === null) { vscode.window.showErrorMessage(Messages.FAILED_TO_GET_ENDPOINT); diff --git a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts index 488d486f..287353ec 100644 --- a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts +++ b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts @@ -39,7 +39,7 @@ export const createSite = async (createSiteOptions: ICreateSiteOptions) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const contentProvider = previewSitePagesContent({ sitePages, stream, extensionContext, telemetry, sessionId, orgId, envId, userId }); - const envList = await getEnvList(telemetry, intelligenceAPIEndpointInfo.endpointStamp); + const envList = await getEnvList(intelligenceAPIEndpointInfo.endpointStamp); stream.button({ command: CREATE_SITE_BTN_CMD, diff --git a/src/web/client/WebExtensionContext.ts b/src/web/client/WebExtensionContext.ts index 7377ccc8..211b376b 100644 --- a/src/web/client/WebExtensionContext.ts +++ b/src/web/client/WebExtensionContext.ts @@ -429,7 +429,6 @@ class WebExtensionContext implements IWebExtensionContext { Constants.queryParameters.ORG_URL ) as string; const { accessToken, userId } = await dataverseAuthentication( - this._telemetry.getTelemetryReporter(), dataverseOrgUrl, firstTimeAuth ); diff --git a/src/web/client/extension.ts b/src/web/client/extension.ts index 7557b8cd..f4a234da 100644 --- a/src/web/client/extension.ts +++ b/src/web/client/extension.ts @@ -665,7 +665,7 @@ function isActiveDocument(fileFsPath: string): boolean { } async function fetchArtemisData(orgId: string) { - const artemisResponse = await ArtemisService.getArtemisResponse(orgId, WebExtensionContext.telemetry.getTelemetryReporter(), ""); + const artemisResponse = await ArtemisService.getArtemisResponse(orgId, ""); if (artemisResponse === null || artemisResponse.response === null) { WebExtensionContext.telemetry.sendErrorTelemetry( webExtensionTelemetryEventNames.WEB_EXTENSION_ARTEMIS_RESPONSE_FAILED, diff --git a/src/web/client/services/NPSService.ts b/src/web/client/services/NPSService.ts index 9b0ddd45..77a2f3eb 100644 --- a/src/web/client/services/NPSService.ts +++ b/src/web/client/services/NPSService.ts @@ -66,7 +66,7 @@ export class NPSService { try { const baseApiUrl = this.getNpsSurveyEndpoint(); - const accessToken: string = await npsAuthentication(WebExtensionContext.telemetry.getTelemetryReporter(), SurveyConstants.AUTHORIZATION_ENDPOINT); + const accessToken: string = await npsAuthentication(SurveyConstants.AUTHORIZATION_ENDPOINT); if (accessToken) { WebExtensionContext.telemetry.sendInfoTelemetry(webExtensionTelemetryEventNames.WEB_EXTENSION_NPS_AUTHENTICATION_COMPLETED); diff --git a/src/web/client/services/graphClientService.ts b/src/web/client/services/graphClientService.ts index 50edba52..02b5e278 100644 --- a/src/web/client/services/graphClientService.ts +++ b/src/web/client/services/graphClientService.ts @@ -21,7 +21,7 @@ export class GraphClientService { } public async graphClientAuthentication(firstTimeAuth = false) { - const accessToken = await graphClientAuthentication(WebExtensionContext.telemetry.getTelemetryReporter(), firstTimeAuth); + const accessToken = await graphClientAuthentication(firstTimeAuth); if (!accessToken) { WebExtensionContext.telemetry.sendErrorTelemetry( webExtensionTelemetryEventNames.WEB_EXTENSION_GRAPH_CLIENT_AUTHENTICATION_FAILED, diff --git a/src/web/client/test/integration/WebExtensionContext.test.ts b/src/web/client/test/integration/WebExtensionContext.test.ts index 38270f9d..5e94ef23 100644 --- a/src/web/client/test/integration/WebExtensionContext.test.ts +++ b/src/web/client/test/integration/WebExtensionContext.test.ts @@ -463,7 +463,7 @@ describe("WebExtensionContext", () => { ); expect(WebExtensionContext.dataverseAccessToken).eq(accessToken); - assert.calledOnceWithExactly(dataverseAuthentication, WebExtensionContext.telemetry.getTelemetryReporter(), ORG_URL, true); + assert.calledOnceWithExactly(dataverseAuthentication, ORG_URL, true); assert.callCount(sendAPISuccessTelemetry, 3); assert.calledOnceWithExactly( getLcidCodeMap, @@ -641,7 +641,7 @@ describe("WebExtensionContext", () => { ); expect(WebExtensionContext.dataverseAccessToken).eq(accessToken); - assert.calledOnceWithExactly(dataverseAuthentication, WebExtensionContext.telemetry.getTelemetryReporter(), ORG_URL, true); + assert.calledOnceWithExactly(dataverseAuthentication, ORG_URL, true); assert.notCalled(getLcidCodeMap); assert.notCalled(getWebsiteIdToLcidMap); assert.notCalled(getPortalLanguageIdToLcidMap); @@ -757,7 +757,7 @@ describe("WebExtensionContext", () => { ); expect(WebExtensionContext.dataverseAccessToken).eq(accessToken); - assert.calledOnceWithExactly(dataverseAuthentication, WebExtensionContext.telemetry.getTelemetryReporter(), ORG_URL, true); + assert.calledOnceWithExactly(dataverseAuthentication, ORG_URL, true); //#region Fetch const header = getCommonHeadersForDataverse(accessToken); assert.calledThrice(_mockFetch); From d1c515984e2f29f02f908dc3b92392201869ade8 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 17:27:15 +0530 Subject: [PATCH 18/38] Refactor: Rename AuthManager to PacAuthManager for consistency --- src/client/pac/PacAuthManager.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/pac/PacAuthManager.ts b/src/client/pac/PacAuthManager.ts index 87ba232a..f9931338 100644 --- a/src/client/pac/PacAuthManager.ts +++ b/src/client/pac/PacAuthManager.ts @@ -6,17 +6,17 @@ import { EventEmitter, Event } from "vscode"; import { AuthInfo } from "./PacTypes"; -class AuthManager { - private static instance: AuthManager; +class PacAuthManager { + private static instance: PacAuthManager; private authInfo: AuthInfo | null = null; private _onDidChangeEnvironment: EventEmitter = new EventEmitter(); public readonly onDidChangeEnvironment: Event = this._onDidChangeEnvironment.event; - public static getInstance(): AuthManager { - if (!AuthManager.instance) { - AuthManager.instance = new AuthManager(); + public static getInstance(): PacAuthManager { + if (!PacAuthManager.instance) { + PacAuthManager.instance = new PacAuthManager(); } - return AuthManager.instance; + return PacAuthManager.instance; } public setAuthInfo(authInfo: AuthInfo): void { From 5a2424da68eccc273aefe2901e4d52798e00d320 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Tue, 4 Feb 2025 17:32:03 +0530 Subject: [PATCH 19/38] Refactor: Use pacWrapper for activeAuth retrieval in ActionsHubTreeDataProvider --- .../power-pages/actions-hub/ActionsHubTreeDataProvider.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts index 29e38c42..f2ff005f 100644 --- a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts +++ b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts @@ -80,10 +80,11 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider { try { - const pacActiveAuth = await pacTerminal.getWrapper()?.activeAuth(); + const pacActiveAuth = await pacWrapper.activeAuth(); if (pacActiveAuth && pacActiveAuth.Status === SUCCESS) { const authInfo = extractAuthInfo(pacActiveAuth.Results); pacAuthManager.setAuthInfo(authInfo); From 1726fbc572ac5f10c6515d9c335fa1680dd708fe Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Wed, 5 Feb 2025 14:44:35 +0530 Subject: [PATCH 20/38] Add ACTIONS_HUB_REFRESH_FAILED constant and update error logging in ActionsHubTreeDataProvider --- .../power-pages/actions-hub/ActionsHubTreeDataProvider.ts | 2 +- src/client/power-pages/actions-hub/Constants.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts index f2ff005f..55925bac 100644 --- a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts +++ b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts @@ -91,7 +91,7 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider Date: Wed, 5 Feb 2025 15:05:02 +0530 Subject: [PATCH 21/38] Remove unused AuthInfo interface from Constants.ts --- .../power-pages/actions-hub/Constants.ts | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/src/client/power-pages/actions-hub/Constants.ts b/src/client/power-pages/actions-hub/Constants.ts index 1f580d53..dedd9910 100644 --- a/src/client/power-pages/actions-hub/Constants.ts +++ b/src/client/power-pages/actions-hub/Constants.ts @@ -36,23 +36,4 @@ export const Constants = { } }; -export interface AuthInfo { - userType: string; - cloud: string; - tenantId: string; - tenantCountry: string; - user: string; - entraIdObjectId: string; - puid: string; - userCountryRegion: string; - tokenExpires: string; - authority: string; - environmentGeo: string; - environmentId: string; - environmentType: string; - organizationId: string; - organizationUniqueName: string; - organizationFriendlyName: string; -} - export const ENVIRONMENT_EXPIRED = vscode.l10n.t("Active Environment is expired or deleted. Please select a new environment.") From a14ff626984c99cd4e37dbabd149c15ae081aef7 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Wed, 5 Feb 2025 16:52:04 +0530 Subject: [PATCH 22/38] Refactor ActionsHub initialization by removing authentication handling and simplifying the process --- .../power-pages/actions-hub/ActionsHub.ts | 17 +---------------- .../actions-hub/ActionsHubTreeDataProvider.ts | 1 - 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/src/client/power-pages/actions-hub/ActionsHub.ts b/src/client/power-pages/actions-hub/ActionsHub.ts index 2ef19e3d..21643c8e 100644 --- a/src/client/power-pages/actions-hub/ActionsHub.ts +++ b/src/client/power-pages/actions-hub/ActionsHub.ts @@ -9,11 +9,6 @@ import { EnableActionsHub } from "../../../common/ecs-features/ecsFeatureGates"; import { ActionsHubTreeDataProvider } from "./ActionsHubTreeDataProvider"; import { oneDSLoggerWrapper } from "../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; import { PacTerminal } from "../../lib/PacTerminal"; -import { SUCCESS } from "../../../common/constants"; -import { extractAuthInfo } from "../commonUtility"; -import { pacAuthManager } from "../../pac/PacAuthManager"; -import { Constants } from "./Constants"; - export class ActionsHub { static isEnabled(): boolean { const enableActionsHub = ECSFeaturesClient.getConfig(EnableActionsHub).enableActionsHub @@ -38,16 +33,6 @@ export class ActionsHub { return; } - try { - const pacActiveAuth = await pacTerminal.getWrapper()?.activeAuth(); - if (pacActiveAuth && pacActiveAuth.Status === SUCCESS) { - const authInfo = extractAuthInfo(pacActiveAuth.Results); - pacAuthManager.setAuthInfo(authInfo); - } - - ActionsHubTreeDataProvider.initialize(context, pacTerminal); - } catch (error) { - oneDSLoggerWrapper.getLogger().traceError(Constants.EventNames.ACTIONS_HUB_INITIALIZATION_FAILED, error as string, error as Error, { methodName: ActionsHub.initialize.name }, {}); - } + ActionsHubTreeDataProvider.initialize(context, pacTerminal); } } diff --git a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts index 55925bac..40d72fa6 100644 --- a/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts +++ b/src/client/power-pages/actions-hub/ActionsHubTreeDataProvider.ts @@ -89,7 +89,6 @@ export class ActionsHubTreeDataProvider implements vscode.TreeDataProvider Date: Wed, 5 Feb 2025 16:52:18 +0530 Subject: [PATCH 23/38] Add localization for expired environment message and update refresh title --- l10n/bundle.l10n.json | 1 + loc/translations-export/vscode-powerplatform.xlf | 6 ++++++ package.json | 2 +- package.nls.json | 3 ++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 8ca1cb0a..64b615f9 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -182,6 +182,7 @@ "Inactive Sites": "Inactive Sites", "No sites found": "No sites found", "No environments found": "No environments found", + "Active Environment is expired or deleted. Please select a new environment.": "Active Environment is expired or deleted. Please select a new environment.", "PAC Telemetry enabled": "PAC Telemetry enabled", "Failed to enable PAC telemetry.": "Failed to enable PAC telemetry.", "PAC Telemetry disabled": "PAC Telemetry disabled", diff --git a/loc/translations-export/vscode-powerplatform.xlf b/loc/translations-export/vscode-powerplatform.xlf index 62c771cb..9c6d83c9 100644 --- a/loc/translations-export/vscode-powerplatform.xlf +++ b/loc/translations-export/vscode-powerplatform.xlf @@ -22,6 +22,9 @@ AI-generated content can contain mistakes + + Active Environment is expired or deleted. Please select a new environment. + Active Sites @@ -693,6 +696,9 @@ The second line should be '[TRANSLATION HERE](command:powerplatform-walkthrough. Preview Site + + Refresh + Refresh diff --git a/package.json b/package.json index 123a702c..29532a4d 100644 --- a/package.json +++ b/package.json @@ -378,7 +378,7 @@ }, { "command": "powerpages.actionsHub.refresh", - "title": "Refresh", + "title": "%powerpages.actionsHub.refresh.title%", "icon": "$(refresh)" } ], diff --git a/package.nls.json b/package.nls.json index cba41a2f..7b76055f 100644 --- a/package.nls.json +++ b/package.nls.json @@ -99,5 +99,6 @@ }, "microsoft-powerplatform-portals.navigation-loop.powerPagesFileExplorer.title": "POWER PAGES ACTIONS", "powerplatform.pages.previewSite.title": "Preview Site", - "microsoft.powerplatform.pages.actionsHub.title": "POWER PAGES ACTIONS" + "microsoft.powerplatform.pages.actionsHub.title": "POWER PAGES ACTIONS", + "powerpages.actionsHub.refresh.title": "Refresh" } From 5057d2df90bcbf3d34895a8290b54d558f8947a4 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Thu, 6 Feb 2025 16:11:02 +0530 Subject: [PATCH 24/38] Refactor ActionsHub tests to use ECS feature flags and improve error handling in ActionsHubTreeDataProvider --- .../actions-hub/ActionsHub.test.ts | 120 +++++++----------- .../ActionsHubTreeDataProvider.test.ts | 29 +++++ 2 files changed, 74 insertions(+), 75 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts index 95072ac5..f27e20fa 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts @@ -6,107 +6,77 @@ import { expect } from "chai"; import * as sinon from "sinon"; import * as vscode from "vscode"; -import * as CommonUtils from "../../../../power-pages/commonUtility"; import { ActionsHub } from "../../../../power-pages/actions-hub/ActionsHub"; import { PacTerminal } from "../../../../lib/PacTerminal"; import { ECSFeaturesClient } from "../../../../../common/ecs-features/ecsFeatureClient"; import { oneDSLoggerWrapper } from "../../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; import { ActionsHubTreeDataProvider } from "../../../../power-pages/actions-hub/ActionsHubTreeDataProvider"; -import { SUCCESS } from "../../../../../common/constants"; -import { AUTH_KEYS } from "../../../../../common/OneDSLoggerTelemetry/telemetryConstants"; -import { PacWrapper } from "../../../../pac/PacWrapper"; -import { AuthInfo, PacAuthWhoOutput } from "../../../../pac/PacTypes"; -import { pacAuthManager } from "../../../../pac/PacAuthManager"; - -describe("ActionsHub", () => { - let getConfigStub: sinon.SinonStub; +import { EnableActionsHub } from "../../../../../common/ecs-features/ecsFeatureGates"; + +describe('ActionsHub', () => { + let context: vscode.ExtensionContext; + let pacTerminal: PacTerminal; + let loggerStub: sinon.SinonStub; + let ecsFeaturesClientStub: sinon.SinonStub; let executeCommandStub: sinon.SinonStub; - let traceInfoStub: sinon.SinonStub; - let fakeContext: vscode.ExtensionContext; - let fakePacWrapper: sinon.SinonStubbedInstance; - let fakePacTerminal: sinon.SinonStubbedInstance; beforeEach(() => { - getConfigStub = sinon.stub(ECSFeaturesClient, "getConfig"); - fakeContext = {} as vscode.ExtensionContext; - fakePacWrapper = sinon.createStubInstance(PacWrapper, { activeAuth: sinon.stub() }); - fakePacTerminal = sinon.createStubInstance(PacTerminal, { getWrapper: fakePacWrapper }); - executeCommandStub = sinon.stub(vscode.commands, "executeCommand"); - traceInfoStub = sinon.stub(); - sinon.stub(oneDSLoggerWrapper, "getLogger").returns({ - traceInfo: traceInfoStub, - traceWarning: sinon.stub(), - traceError: sinon.stub(), - featureUsage: sinon.stub() - }); + context = {} as vscode.ExtensionContext; + pacTerminal = {} as PacTerminal; + loggerStub = sinon.stub(oneDSLoggerWrapper.getLogger(), 'traceInfo'); + ecsFeaturesClientStub = sinon.stub(ECSFeaturesClient, 'getConfig'); + executeCommandStub = sinon.stub(vscode.commands, 'executeCommand'); }); afterEach(() => { sinon.restore(); }); - describe("isEnabled", () => { - it("should return false if enableActionsHub is undefined", () => { - getConfigStub.returns({ enableActionsHub: undefined }); + describe('isEnabled', () => { + it('should return false if enableActionsHub is undefined', () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: undefined }); + const result = ActionsHub.isEnabled(); + expect(result).to.be.false; }); - it("should return the actual boolean if enableActionsHub has a value", () => { - getConfigStub.returns({ enableActionsHub: true }); - let result = ActionsHub.isEnabled(); + it('should return true if enableActionsHub is true', () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: true }); + + const result = ActionsHub.isEnabled(); + expect(result).to.be.true; + }); + + it('should return false if enableActionsHub is false', () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: false }); + + const result = ActionsHub.isEnabled(); - getConfigStub.returns({ enableActionsHub: false }); - result = ActionsHub.isEnabled(); expect(result).to.be.false; }); }); - describe("initialize", () => { - describe("when ActionsHub is enabled", () => { - it("should set context to true and return early if isEnabled returns true", async () => { - getConfigStub.returns({ enableActionsHub: true }); - await ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(traceInfoStub.calledWith("EnableActionsHub", { isEnabled: "true" })).to.be.true; - expect(executeCommandStub.calledWith("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", true)).to.be.true; - }); - - it("should set Auth Info", async () => { - getConfigStub.returns({ enableActionsHub: true }); - const data = { organizationFriendlyName: 'Foo' } as AuthInfo; - sinon.stub(CommonUtils, 'extractAuthInfo').returns(data); - const pacAuthSetInfoStub = sinon.stub(pacAuthManager, 'setAuthInfo'); - fakePacWrapper.activeAuth.returns(Promise.resolve({ Status: SUCCESS, Results: [{ Key: AUTH_KEYS.ORGANIZATION_FRIENDLY_NAME, Value: 'Foo' }] }) as Promise); - - await ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(pacAuthSetInfoStub.calledWith(data)).to.be.true; - }); - - it("should initialize ActionsHubTreeDataProvider", async () => { - getConfigStub.returns({ enableActionsHub: true }); - const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); - fakePacWrapper.activeAuth.returns(Promise.resolve({ Status: SUCCESS, Results: [{ Key: AUTH_KEYS.ORGANIZATION_FRIENDLY_NAME, Value: 'Foo' }] }) as Promise); - - await ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(actionsHubTreeDataProviderStub.calledWith(fakeContext)).to.be.true; - }); + describe('initialize', () => { + it('should log telemetry and set context when ActionsHub is enabled', async () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: true }); + const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); + + await ActionsHub.initialize(context, pacTerminal); + + expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'true' })).to.be.true; + expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", true)).to.be.true; + expect(actionsHubTreeDataProviderStub.calledOnceWithExactly(context, pacTerminal)).to.be.true; }); - describe("when ActionsHub is disabled", () => { - it("should set context to false and return early if isEnabled returns false", () => { - getConfigStub.returns({ enableActionsHub: false }); - ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(traceInfoStub.calledWith("EnableActionsHub", { isEnabled: "false" })).to.be.true; - expect(executeCommandStub.calledWith("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", false)).to.be.true; - }); - - it("should not initialize ActionsHubTreeDataProvider", () => { - getConfigStub.returns({ enableActionsHub: false }); - const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); - ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(actionsHubTreeDataProviderStub.called).to.be.false; - }); + it('should log telemetry and set context when ActionsHub is disabled', async () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: false }); + + await ActionsHub.initialize(context, pacTerminal); + + expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'false' })).to.be.true; + expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", false)).to.be.true; }); }); }); diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index 510d1bb0..2a161077 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -15,6 +15,8 @@ import { OtherSitesGroupTreeItem } from "../../../../power-pages/actions-hub/tre import { ActionsHubTreeItem } from "../../../../power-pages/actions-hub/tree-items/ActionsHubTreeItem"; import { pacAuthManager } from "../../../../pac/PacAuthManager"; import { PacTerminal } from "../../../../lib/PacTerminal"; +import { PacWrapper } from "../../../../pac/PacWrapper"; +import { SUCCESS } from "../../../../../common/constants"; describe("ActionsHubTreeDataProvider", () => { let context: vscode.ExtensionContext; @@ -22,6 +24,7 @@ describe("ActionsHubTreeDataProvider", () => { let traceErrorStub: sinon.SinonStub; let authInfoStub: sinon.SinonStub; let pacTerminal: PacTerminal; + let pacWrapperStub: sinon.SinonStubbedInstance; beforeEach(() => { context = { @@ -37,6 +40,9 @@ describe("ActionsHubTreeDataProvider", () => { }); authInfoStub = sinon.stub(pacAuthManager, "getAuthInfo"); pacTerminal = {} as PacTerminal; + pacWrapperStub = sinon.createStubInstance(PacWrapper); + pacWrapperStub.activeAuth.resolves({ Status: SUCCESS, Results: [], Errors: [], Information: [] }); + sinon.stub(pacTerminal, "getWrapper").returns(pacWrapperStub); }); afterEach(() => { @@ -134,4 +140,27 @@ describe("ActionsHubTreeDataProvider", () => { expect(disposable2.dispose.calledOnce).to.be.true; }); }); + + describe('registerPanel', () => { + it("should register refresh command and handle errors", async () => { + const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + const refreshCommandStub = sinon.stub(vscode.commands, "registerCommand"); + const refreshStub = sinon.stub(actionsHubTreeDataProvider, "refresh"); + + actionsHubTreeDataProvider["registerPanel"](pacTerminal); + + const refreshCommandCallback = refreshCommandStub.args[0][1]; + await refreshCommandCallback(); + + expect(refreshCommandStub.calledOnceWith("powerpages.actionsHub.refresh", sinon.match.func)).to.be.true; + expect(refreshStub.calledOnce).to.be.true; + expect(traceErrorStub.called).to.be.false; + + // Simulate error + pacWrapperStub.activeAuth.returns(Promise.reject(new Error("Test Error"))); + await refreshCommandCallback(); + + expect(traceErrorStub.calledOnceWith(Constants.EventNames.ACTIONS_HUB_REFRESH_FAILED)).to.be.true; + }); + }); }); From 0bacb1a5120b3b6196d5ec0a8ab0dfcb86ed2f51 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Thu, 6 Feb 2025 16:49:18 +0530 Subject: [PATCH 25/38] Refactor ActionsHub tests to use ECS feature flags and improve error handling in ActionsHubTreeDataProvider --- .../actions-hub/ActionsHub.test.ts | 117 +++++++----------- .../ActionsHubTreeDataProvider.test.ts | 31 ++++- webpack.config.js | 15 +++ 3 files changed, 91 insertions(+), 72 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts index 95072ac5..d4a7469e 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts @@ -6,107 +6,82 @@ import { expect } from "chai"; import * as sinon from "sinon"; import * as vscode from "vscode"; -import * as CommonUtils from "../../../../power-pages/commonUtility"; import { ActionsHub } from "../../../../power-pages/actions-hub/ActionsHub"; import { PacTerminal } from "../../../../lib/PacTerminal"; import { ECSFeaturesClient } from "../../../../../common/ecs-features/ecsFeatureClient"; import { oneDSLoggerWrapper } from "../../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; import { ActionsHubTreeDataProvider } from "../../../../power-pages/actions-hub/ActionsHubTreeDataProvider"; -import { SUCCESS } from "../../../../../common/constants"; -import { AUTH_KEYS } from "../../../../../common/OneDSLoggerTelemetry/telemetryConstants"; -import { PacWrapper } from "../../../../pac/PacWrapper"; -import { AuthInfo, PacAuthWhoOutput } from "../../../../pac/PacTypes"; -import { pacAuthManager } from "../../../../pac/PacAuthManager"; - -describe("ActionsHub", () => { - let getConfigStub: sinon.SinonStub; +import { EnableActionsHub } from "../../../../../common/ecs-features/ecsFeatureGates"; + +describe('ActionsHub', () => { + let context: vscode.ExtensionContext; + let pacTerminal: PacTerminal; + let loggerStub: sinon.SinonStub; + let ecsFeaturesClientStub: sinon.SinonStub; let executeCommandStub: sinon.SinonStub; - let traceInfoStub: sinon.SinonStub; - let fakeContext: vscode.ExtensionContext; - let fakePacWrapper: sinon.SinonStubbedInstance; - let fakePacTerminal: sinon.SinonStubbedInstance; beforeEach(() => { - getConfigStub = sinon.stub(ECSFeaturesClient, "getConfig"); - fakeContext = {} as vscode.ExtensionContext; - fakePacWrapper = sinon.createStubInstance(PacWrapper, { activeAuth: sinon.stub() }); - fakePacTerminal = sinon.createStubInstance(PacTerminal, { getWrapper: fakePacWrapper }); - executeCommandStub = sinon.stub(vscode.commands, "executeCommand"); - traceInfoStub = sinon.stub(); - sinon.stub(oneDSLoggerWrapper, "getLogger").returns({ - traceInfo: traceInfoStub, + context = {} as vscode.ExtensionContext; + pacTerminal = {} as PacTerminal; + loggerStub = sinon.stub(oneDSLoggerWrapper, 'getLogger').returns({ + traceInfo: sinon.stub(), traceWarning: sinon.stub(), traceError: sinon.stub(), featureUsage: sinon.stub() }); + ecsFeaturesClientStub = sinon.stub(ECSFeaturesClient, 'getConfig'); + executeCommandStub = sinon.stub(vscode.commands, 'executeCommand'); }); afterEach(() => { sinon.restore(); }); - describe("isEnabled", () => { - it("should return false if enableActionsHub is undefined", () => { - getConfigStub.returns({ enableActionsHub: undefined }); + describe('isEnabled', () => { + it('should return false if enableActionsHub is undefined', () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: undefined }); + const result = ActionsHub.isEnabled(); + expect(result).to.be.false; }); - it("should return the actual boolean if enableActionsHub has a value", () => { - getConfigStub.returns({ enableActionsHub: true }); - let result = ActionsHub.isEnabled(); + it('should return true if enableActionsHub is true', () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: true }); + + const result = ActionsHub.isEnabled(); + expect(result).to.be.true; + }); + + it('should return false if enableActionsHub is false', () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: false }); + + const result = ActionsHub.isEnabled(); - getConfigStub.returns({ enableActionsHub: false }); - result = ActionsHub.isEnabled(); expect(result).to.be.false; }); }); - describe("initialize", () => { - describe("when ActionsHub is enabled", () => { - it("should set context to true and return early if isEnabled returns true", async () => { - getConfigStub.returns({ enableActionsHub: true }); - await ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(traceInfoStub.calledWith("EnableActionsHub", { isEnabled: "true" })).to.be.true; - expect(executeCommandStub.calledWith("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", true)).to.be.true; - }); - - it("should set Auth Info", async () => { - getConfigStub.returns({ enableActionsHub: true }); - const data = { organizationFriendlyName: 'Foo' } as AuthInfo; - sinon.stub(CommonUtils, 'extractAuthInfo').returns(data); - const pacAuthSetInfoStub = sinon.stub(pacAuthManager, 'setAuthInfo'); - fakePacWrapper.activeAuth.returns(Promise.resolve({ Status: SUCCESS, Results: [{ Key: AUTH_KEYS.ORGANIZATION_FRIENDLY_NAME, Value: 'Foo' }] }) as Promise); - - await ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(pacAuthSetInfoStub.calledWith(data)).to.be.true; - }); - - it("should initialize ActionsHubTreeDataProvider", async () => { - getConfigStub.returns({ enableActionsHub: true }); - const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); - fakePacWrapper.activeAuth.returns(Promise.resolve({ Status: SUCCESS, Results: [{ Key: AUTH_KEYS.ORGANIZATION_FRIENDLY_NAME, Value: 'Foo' }] }) as Promise); - - await ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(actionsHubTreeDataProviderStub.calledWith(fakeContext)).to.be.true; - }); + describe('initialize', () => { + it('should log telemetry and set context when ActionsHub is enabled', async () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: true }); + const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); + + await ActionsHub.initialize(context, pacTerminal); + + expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'true' })).to.be.true; + expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", true)).to.be.true; + expect(actionsHubTreeDataProviderStub.calledOnceWithExactly(context, pacTerminal)).to.be.true; }); - describe("when ActionsHub is disabled", () => { - it("should set context to false and return early if isEnabled returns false", () => { - getConfigStub.returns({ enableActionsHub: false }); - ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(traceInfoStub.calledWith("EnableActionsHub", { isEnabled: "false" })).to.be.true; - expect(executeCommandStub.calledWith("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", false)).to.be.true; - }); - - it("should not initialize ActionsHubTreeDataProvider", () => { - getConfigStub.returns({ enableActionsHub: false }); - const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); - ActionsHub.initialize(fakeContext, fakePacTerminal); - expect(actionsHubTreeDataProviderStub.called).to.be.false; - }); + it('should log telemetry and set context when ActionsHub is disabled', async () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: false }); + + await ActionsHub.initialize(context, pacTerminal); + + expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'false' })).to.be.true; + expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", false)).to.be.true; }); }); }); diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index 510d1bb0..e96dd6c5 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -15,6 +15,8 @@ import { OtherSitesGroupTreeItem } from "../../../../power-pages/actions-hub/tre import { ActionsHubTreeItem } from "../../../../power-pages/actions-hub/tree-items/ActionsHubTreeItem"; import { pacAuthManager } from "../../../../pac/PacAuthManager"; import { PacTerminal } from "../../../../lib/PacTerminal"; +import { PacWrapper } from "../../../../pac/PacWrapper"; +import { SUCCESS } from "../../../../../common/constants"; describe("ActionsHubTreeDataProvider", () => { let context: vscode.ExtensionContext; @@ -22,6 +24,7 @@ describe("ActionsHubTreeDataProvider", () => { let traceErrorStub: sinon.SinonStub; let authInfoStub: sinon.SinonStub; let pacTerminal: PacTerminal; + let pacWrapperStub: sinon.SinonStubbedInstance; beforeEach(() => { context = { @@ -36,7 +39,10 @@ describe("ActionsHubTreeDataProvider", () => { featureUsage: sinon.stub() }); authInfoStub = sinon.stub(pacAuthManager, "getAuthInfo"); - pacTerminal = {} as PacTerminal; + pacTerminal = sinon.createStubInstance(PacTerminal); + pacWrapperStub = sinon.createStubInstance(PacWrapper); + pacWrapperStub.activeAuth.resolves({ Status: SUCCESS, Results: [], Errors: [], Information: [] }); + (pacTerminal.getWrapper as sinon.SinonStub).returns(pacWrapperStub); }); afterEach(() => { @@ -134,4 +140,27 @@ describe("ActionsHubTreeDataProvider", () => { expect(disposable2.dispose.calledOnce).to.be.true; }); }); + + describe('registerPanel', () => { + it("should register refresh command and handle errors", async () => { + const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + const refreshCommandStub = sinon.stub(vscode.commands, "registerCommand"); + const refreshStub = sinon.stub(actionsHubTreeDataProvider, "refresh"); + + actionsHubTreeDataProvider["registerPanel"](pacTerminal); + + const refreshCommandCallback = refreshCommandStub.args[0][1]; + await refreshCommandCallback(); + + expect(refreshCommandStub.calledOnceWith("powerpages.actionsHub.refresh", sinon.match.func)).to.be.true; + expect(refreshStub.calledOnce).to.be.true; + expect(traceErrorStub.called).to.be.false; + + // Simulate error + pacWrapperStub.activeAuth.returns(Promise.reject(new Error("Test Error"))); + await refreshCommandCallback(); + + expect(traceErrorStub.calledOnceWith(Constants.EventNames.ACTIONS_HUB_REFRESH_FAILED)).to.be.true; + }); + }); }); diff --git a/webpack.config.js b/webpack.config.js index c27c00a8..a2acfff5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -56,6 +56,11 @@ const nodeConfig = { new webpack.DefinePlugin({ IS_DESKTOP: true, }), + ], + ignoreWarnings: [ + { + message: /dependency is an expression|require function is used in a way in which dependencies cannot be statically extracted/ + } ] }; const webConfig = { @@ -120,6 +125,11 @@ const webConfig = { infrastructureLogging: { level: "log", // enables logging required for problem matchers }, + ignoreWarnings: [ + { + message: /dependency is an expression|require function is used in a way in which dependencies cannot be statically extracted/ + } + ] }; /** @type fluent container scripts web worker config */ @@ -174,6 +184,11 @@ const webWorkerConfig = { hints: false, }, devtool: "source-map", + ignoreWarnings: [ + { + message: /dependency is an expression|require function is used in a way in which dependencies cannot be statically extracted/ + } + ] }; module.exports = [nodeConfig, webConfig, webWorkerConfig]; From 31757645b241149e19527e1d82cfb90255accdaa Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Thu, 6 Feb 2025 16:55:31 +0530 Subject: [PATCH 26/38] Fix logger and terminal stubs in ActionsHub tests to resolve merge conflicts --- .../Integration/power-pages/actions-hub/ActionsHub.test.ts | 4 ---- .../actions-hub/ActionsHubTreeDataProvider.test.ts | 7 ------- 2 files changed, 11 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts index cc65e39a..d4a7469e 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts @@ -23,16 +23,12 @@ describe('ActionsHub', () => { beforeEach(() => { context = {} as vscode.ExtensionContext; pacTerminal = {} as PacTerminal; -<<<<<<< HEAD loggerStub = sinon.stub(oneDSLoggerWrapper, 'getLogger').returns({ traceInfo: sinon.stub(), traceWarning: sinon.stub(), traceError: sinon.stub(), featureUsage: sinon.stub() }); -======= - loggerStub = sinon.stub(oneDSLoggerWrapper.getLogger(), 'traceInfo'); ->>>>>>> fe78e41e3d27cfd7c71d7206ed90380e9d62c40d ecsFeaturesClientStub = sinon.stub(ECSFeaturesClient, 'getConfig'); executeCommandStub = sinon.stub(vscode.commands, 'executeCommand'); }); diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index e00344e4..e96dd6c5 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -39,17 +39,10 @@ describe("ActionsHubTreeDataProvider", () => { featureUsage: sinon.stub() }); authInfoStub = sinon.stub(pacAuthManager, "getAuthInfo"); -<<<<<<< HEAD pacTerminal = sinon.createStubInstance(PacTerminal); pacWrapperStub = sinon.createStubInstance(PacWrapper); pacWrapperStub.activeAuth.resolves({ Status: SUCCESS, Results: [], Errors: [], Information: [] }); (pacTerminal.getWrapper as sinon.SinonStub).returns(pacWrapperStub); -======= - pacTerminal = {} as PacTerminal; - pacWrapperStub = sinon.createStubInstance(PacWrapper); - pacWrapperStub.activeAuth.resolves({ Status: SUCCESS, Results: [], Errors: [], Information: [] }); - sinon.stub(pacTerminal, "getWrapper").returns(pacWrapperStub); ->>>>>>> fe78e41e3d27cfd7c71d7206ed90380e9d62c40d }); afterEach(() => { From 3635f2790ae3bd4fa5309fe3df7152ca0f94083d Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Thu, 6 Feb 2025 17:46:36 +0530 Subject: [PATCH 27/38] Add command registration stub to ActionsHubTreeDataProvider tests --- .../actions-hub/ActionsHubTreeDataProvider.test.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index e96dd6c5..2cc21f4a 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -25,6 +25,7 @@ describe("ActionsHubTreeDataProvider", () => { let authInfoStub: sinon.SinonStub; let pacTerminal: PacTerminal; let pacWrapperStub: sinon.SinonStubbedInstance; + let registerCommandStub: sinon.SinonStub; beforeEach(() => { context = { @@ -43,6 +44,7 @@ describe("ActionsHubTreeDataProvider", () => { pacWrapperStub = sinon.createStubInstance(PacWrapper); pacWrapperStub.activeAuth.resolves({ Status: SUCCESS, Results: [], Errors: [], Information: [] }); (pacTerminal.getWrapper as sinon.SinonStub).returns(pacWrapperStub); + registerCommandStub = sinon.stub(vscode.commands, "registerCommand"); }); afterEach(() => { @@ -144,15 +146,14 @@ describe("ActionsHubTreeDataProvider", () => { describe('registerPanel', () => { it("should register refresh command and handle errors", async () => { const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); - const refreshCommandStub = sinon.stub(vscode.commands, "registerCommand"); const refreshStub = sinon.stub(actionsHubTreeDataProvider, "refresh"); actionsHubTreeDataProvider["registerPanel"](pacTerminal); - const refreshCommandCallback = refreshCommandStub.args[0][1]; + const refreshCommandCallback = registerCommandStub.args[0][1]; await refreshCommandCallback(); - expect(refreshCommandStub.calledOnceWith("powerpages.actionsHub.refresh", sinon.match.func)).to.be.true; + expect(registerCommandStub.calledOnceWith("powerpages.actionsHub.refresh", sinon.match.func)).to.be.true; expect(refreshStub.calledOnce).to.be.true; expect(traceErrorStub.called).to.be.false; From ae6cfff65fff63214ae8f45d70aa6c75cd48a0a1 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Thu, 6 Feb 2025 18:09:35 +0530 Subject: [PATCH 28/38] Fix logger stub assertions in ActionsHub tests to correctly check traceInfo calls --- .../Integration/power-pages/actions-hub/ActionsHub.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts index d4a7469e..1bd75e29 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts @@ -70,7 +70,7 @@ describe('ActionsHub', () => { await ActionsHub.initialize(context, pacTerminal); - expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'true' })).to.be.true; + expect(loggerStub.getCall(0).args[0].traceInfo.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'true' })).to.be.true; expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", true)).to.be.true; expect(actionsHubTreeDataProviderStub.calledOnceWithExactly(context, pacTerminal)).to.be.true; }); @@ -80,7 +80,7 @@ describe('ActionsHub', () => { await ActionsHub.initialize(context, pacTerminal); - expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'false' })).to.be.true; + expect(loggerStub.getCall(0).args[0].traceInfo.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'false' })).to.be.true; expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", false)).to.be.true; }); }); From 068ac9c221caf5df7c1e1ae3fe2814d4bd0a75d3 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Thu, 6 Feb 2025 18:26:57 +0530 Subject: [PATCH 29/38] Refactor logger assertions in ActionsHub tests to simplify traceInfo checks --- .../Integration/power-pages/actions-hub/ActionsHub.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts index 1bd75e29..d4a7469e 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts @@ -70,7 +70,7 @@ describe('ActionsHub', () => { await ActionsHub.initialize(context, pacTerminal); - expect(loggerStub.getCall(0).args[0].traceInfo.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'true' })).to.be.true; + expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'true' })).to.be.true; expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", true)).to.be.true; expect(actionsHubTreeDataProviderStub.calledOnceWithExactly(context, pacTerminal)).to.be.true; }); @@ -80,7 +80,7 @@ describe('ActionsHub', () => { await ActionsHub.initialize(context, pacTerminal); - expect(loggerStub.getCall(0).args[0].traceInfo.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'false' })).to.be.true; + expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'false' })).to.be.true; expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", false)).to.be.true; }); }); From 32c13bdea786c5bb8389662bb414c332dd29c569 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 7 Feb 2025 12:01:20 +0530 Subject: [PATCH 30/38] Refactor ActionsHub tests to improve logger assertions and add exception handling cases --- .../actions-hub/ActionsHub.test.ts | 66 +++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts index d4a7469e..43f0539b 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts @@ -3,27 +3,27 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ -import { expect } from "chai"; -import * as sinon from "sinon"; -import * as vscode from "vscode"; -import { ActionsHub } from "../../../../power-pages/actions-hub/ActionsHub"; -import { PacTerminal } from "../../../../lib/PacTerminal"; -import { ECSFeaturesClient } from "../../../../../common/ecs-features/ecsFeatureClient"; -import { oneDSLoggerWrapper } from "../../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; -import { ActionsHubTreeDataProvider } from "../../../../power-pages/actions-hub/ActionsHubTreeDataProvider"; -import { EnableActionsHub } from "../../../../../common/ecs-features/ecsFeatureGates"; +import * as vscode from 'vscode'; +import { expect } from 'chai'; +import * as sinon from 'sinon'; +import { ActionsHub } from '../../../../power-pages/actions-hub/ActionsHub'; +import { ECSFeaturesClient } from '../../../../../common/ecs-features/ecsFeatureClient'; +import { EnableActionsHub } from '../../../../../common/ecs-features/ecsFeatureGates'; +import { ActionsHubTreeDataProvider } from '../../../../power-pages/actions-hub/ActionsHubTreeDataProvider'; +import { oneDSLoggerWrapper } from '../../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper'; +import { PacTerminal } from '../../../../lib/PacTerminal'; describe('ActionsHub', () => { let context: vscode.ExtensionContext; let pacTerminal: PacTerminal; - let loggerStub: sinon.SinonStub; + let loggerStub: sinon.SinonStubbedInstance; let ecsFeaturesClientStub: sinon.SinonStub; let executeCommandStub: sinon.SinonStub; beforeEach(() => { context = {} as vscode.ExtensionContext; pacTerminal = {} as PacTerminal; - loggerStub = sinon.stub(oneDSLoggerWrapper, 'getLogger').returns({ + loggerStub = sinon.createStubInstance(oneDSLoggerWrapper, { traceInfo: sinon.stub(), traceWarning: sinon.stub(), traceError: sinon.stub(), @@ -61,6 +61,14 @@ describe('ActionsHub', () => { expect(result).to.be.false; }); + + it('should handle exceptions and return false', () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).throws(new Error('Test Error')); + + const result = ActionsHub.isEnabled(); + + expect(result).to.be.false; + }); }); describe('initialize', () => { @@ -70,9 +78,11 @@ describe('ActionsHub', () => { await ActionsHub.initialize(context, pacTerminal); - expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'true' })).to.be.true; + expect(loggerStub.traceInfo.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'true' })).to.be.true; expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", true)).to.be.true; expect(actionsHubTreeDataProviderStub.calledOnceWithExactly(context, pacTerminal)).to.be.true; + + actionsHubTreeDataProviderStub.restore(); }); it('should log telemetry and set context when ActionsHub is disabled', async () => { @@ -80,8 +90,38 @@ describe('ActionsHub', () => { await ActionsHub.initialize(context, pacTerminal); - expect(loggerStub.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'false' })).to.be.true; + expect(loggerStub.traceInfo.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'false' })).to.be.true; expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", false)).to.be.true; }); + + it('should handle exceptions and log error telemetry', async () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).throws(new Error('Test Error')); + + await ActionsHub.initialize(context, pacTerminal); + + expect(loggerStub.traceError.calledOnce).to.be.true; + }); + + it('should not initialize ActionsHubTreeDataProvider if ActionsHub is disabled', async () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: false }); + const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); + + await ActionsHub.initialize(context, pacTerminal); + + expect(actionsHubTreeDataProviderStub.notCalled).to.be.true; + + actionsHubTreeDataProviderStub.restore(); + }); + + it('should initialize ActionsHubTreeDataProvider if ActionsHub is enabled', async () => { + ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: true }); + const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); + + await ActionsHub.initialize(context, pacTerminal); + + expect(actionsHubTreeDataProviderStub.calledOnceWithExactly(context, pacTerminal)).to.be.true; + + actionsHubTreeDataProviderStub.restore(); + }); }); }); From e23fd3f2f08b2e501ba5b2cde6fd1f7b55a8b119 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 7 Feb 2025 12:19:39 +0530 Subject: [PATCH 31/38] Add logger stub declaration in ActionsHub tests for improved type safety --- .../test/Integration/power-pages/actions-hub/ActionsHub.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts index 43f0539b..c11f8781 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts @@ -16,6 +16,7 @@ import { PacTerminal } from '../../../../lib/PacTerminal'; describe('ActionsHub', () => { let context: vscode.ExtensionContext; let pacTerminal: PacTerminal; + // eslint-disable-next-line @typescript-eslint/no-explicit-any let loggerStub: sinon.SinonStubbedInstance; let ecsFeaturesClientStub: sinon.SinonStub; let executeCommandStub: sinon.SinonStub; From 7be87cf1e49c2e692043f22d27380fdfd9325495 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 7 Feb 2025 13:42:25 +0530 Subject: [PATCH 32/38] Refactor ActionsHub tests to use stubs for configuration and context, improving test isolation and clarity --- .../actions-hub/ActionsHub.test.ts | 154 +++++++----------- 1 file changed, 62 insertions(+), 92 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts index c11f8781..02459fa1 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHub.test.ts @@ -3,126 +3,96 @@ * Licensed under the MIT License. See License.txt in the project root for license information. */ -import * as vscode from 'vscode'; -import { expect } from 'chai'; -import * as sinon from 'sinon'; -import { ActionsHub } from '../../../../power-pages/actions-hub/ActionsHub'; -import { ECSFeaturesClient } from '../../../../../common/ecs-features/ecsFeatureClient'; -import { EnableActionsHub } from '../../../../../common/ecs-features/ecsFeatureGates'; -import { ActionsHubTreeDataProvider } from '../../../../power-pages/actions-hub/ActionsHubTreeDataProvider'; -import { oneDSLoggerWrapper } from '../../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper'; -import { PacTerminal } from '../../../../lib/PacTerminal'; - -describe('ActionsHub', () => { - let context: vscode.ExtensionContext; - let pacTerminal: PacTerminal; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let loggerStub: sinon.SinonStubbedInstance; - let ecsFeaturesClientStub: sinon.SinonStub; +import { expect } from "chai"; +import * as sinon from "sinon"; +import * as vscode from "vscode"; +import { ActionsHub } from "../../../../power-pages/actions-hub/ActionsHub"; +import { PacTerminal } from "../../../../lib/PacTerminal"; +import { ECSFeaturesClient } from "../../../../../common/ecs-features/ecsFeatureClient"; +import { oneDSLoggerWrapper } from "../../../../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; +import { ActionsHubTreeDataProvider } from "../../../../power-pages/actions-hub/ActionsHubTreeDataProvider"; +import { PacWrapper } from "../../../../pac/PacWrapper"; + +describe("ActionsHub", () => { + let getConfigStub: sinon.SinonStub; let executeCommandStub: sinon.SinonStub; + let traceInfoStub: sinon.SinonStub; + let fakeContext: vscode.ExtensionContext; + let fakePacWrapper: sinon.SinonStubbedInstance; + let fakePacTerminal: sinon.SinonStubbedInstance; beforeEach(() => { - context = {} as vscode.ExtensionContext; - pacTerminal = {} as PacTerminal; - loggerStub = sinon.createStubInstance(oneDSLoggerWrapper, { - traceInfo: sinon.stub(), + getConfigStub = sinon.stub(ECSFeaturesClient, "getConfig"); + fakeContext = {} as vscode.ExtensionContext; + fakePacWrapper = sinon.createStubInstance(PacWrapper, { activeAuth: sinon.stub() }); + fakePacTerminal = sinon.createStubInstance(PacTerminal, { getWrapper: fakePacWrapper }); + executeCommandStub = sinon.stub(vscode.commands, "executeCommand"); + traceInfoStub = sinon.stub(); + sinon.stub(oneDSLoggerWrapper, "getLogger").returns({ + traceInfo: traceInfoStub, traceWarning: sinon.stub(), traceError: sinon.stub(), featureUsage: sinon.stub() }); - ecsFeaturesClientStub = sinon.stub(ECSFeaturesClient, 'getConfig'); - executeCommandStub = sinon.stub(vscode.commands, 'executeCommand'); }); afterEach(() => { sinon.restore(); }); - describe('isEnabled', () => { - it('should return false if enableActionsHub is undefined', () => { - ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: undefined }); - + describe("isEnabled", () => { + it("should return false if enableActionsHub is undefined", () => { + getConfigStub.returns({ enableActionsHub: undefined }); const result = ActionsHub.isEnabled(); - expect(result).to.be.false; }); - it('should return true if enableActionsHub is true', () => { - ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: true }); - - const result = ActionsHub.isEnabled(); - + it("should return the actual boolean if enableActionsHub has a value", () => { + getConfigStub.returns({ enableActionsHub: true }); + let result = ActionsHub.isEnabled(); expect(result).to.be.true; - }); - - it('should return false if enableActionsHub is false', () => { - ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: false }); - - const result = ActionsHub.isEnabled(); - - expect(result).to.be.false; - }); - - it('should handle exceptions and return false', () => { - ecsFeaturesClientStub.withArgs(EnableActionsHub).throws(new Error('Test Error')); - - const result = ActionsHub.isEnabled(); + getConfigStub.returns({ enableActionsHub: false }); + result = ActionsHub.isEnabled(); expect(result).to.be.false; }); }); - describe('initialize', () => { - it('should log telemetry and set context when ActionsHub is enabled', async () => { - ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: true }); - const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); + describe("initialize", () => { + describe("when ActionsHub is enabled", () => { + it("should set context to true and return early if isEnabled returns true", async () => { + getConfigStub.returns({ enableActionsHub: true }); + await ActionsHub.initialize(fakeContext, fakePacTerminal); + expect(traceInfoStub.calledWith("EnableActionsHub", { isEnabled: "true" })).to.be.true; + expect(executeCommandStub.calledWith("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", true)).to.be.true; + }); - await ActionsHub.initialize(context, pacTerminal); + it("should initialize ActionsHubTreeDataProvider", async () => { + getConfigStub.returns({ enableActionsHub: true }); + const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); - expect(loggerStub.traceInfo.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'true' })).to.be.true; - expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", true)).to.be.true; - expect(actionsHubTreeDataProviderStub.calledOnceWithExactly(context, pacTerminal)).to.be.true; + await ActionsHub.initialize(fakeContext, fakePacTerminal); + expect(actionsHubTreeDataProviderStub.calledWith(fakeContext)).to.be.true; - actionsHubTreeDataProviderStub.restore(); + actionsHubTreeDataProviderStub.restore(); + }); }); - it('should log telemetry and set context when ActionsHub is disabled', async () => { - ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: false }); - - await ActionsHub.initialize(context, pacTerminal); - - expect(loggerStub.traceInfo.calledOnceWithExactly("EnableActionsHub", { isEnabled: 'false' })).to.be.true; - expect(executeCommandStub.calledOnceWithExactly("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", false)).to.be.true; - }); - - it('should handle exceptions and log error telemetry', async () => { - ecsFeaturesClientStub.withArgs(EnableActionsHub).throws(new Error('Test Error')); - - await ActionsHub.initialize(context, pacTerminal); - - expect(loggerStub.traceError.calledOnce).to.be.true; - }); - - it('should not initialize ActionsHubTreeDataProvider if ActionsHub is disabled', async () => { - ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: false }); - const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); - - await ActionsHub.initialize(context, pacTerminal); - - expect(actionsHubTreeDataProviderStub.notCalled).to.be.true; - - actionsHubTreeDataProviderStub.restore(); - }); - - it('should initialize ActionsHubTreeDataProvider if ActionsHub is enabled', async () => { - ecsFeaturesClientStub.withArgs(EnableActionsHub).returns({ enableActionsHub: true }); - const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); - - await ActionsHub.initialize(context, pacTerminal); - - expect(actionsHubTreeDataProviderStub.calledOnceWithExactly(context, pacTerminal)).to.be.true; - - actionsHubTreeDataProviderStub.restore(); + describe("when ActionsHub is disabled", () => { + it("should set context to false and return early if isEnabled returns false", async () => { + getConfigStub.returns({ enableActionsHub: false }); + await ActionsHub.initialize(fakeContext, fakePacTerminal); + expect(traceInfoStub.calledWith("EnableActionsHub", { isEnabled: "false" })).to.be.true; + expect(executeCommandStub.calledWith("setContext", "microsoft.powerplatform.pages.actionsHubEnabled", false)).to.be.true; + }); + + it("should not initialize ActionsHubTreeDataProvider", () => { + getConfigStub.returns({ enableActionsHub: false }); + const actionsHubTreeDataProviderStub = sinon.stub(ActionsHubTreeDataProvider, 'initialize'); + + ActionsHub.initialize(fakeContext, fakePacTerminal); + expect(actionsHubTreeDataProviderStub.called).to.be.false; + }); }); }); }); From 61bb1a33e6e5a143739984024f44bef3fcff05c2 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 7 Feb 2025 14:35:21 +0530 Subject: [PATCH 33/38] Refactor ActionsHubTreeDataProvider tests to use a fakePacTerminal, enhancing test isolation and removing unnecessary dependencies --- .../ActionsHubTreeDataProvider.test.ts | 54 +++++-------------- 1 file changed, 14 insertions(+), 40 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index 2cc21f4a..9e3f7495 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -15,17 +15,12 @@ import { OtherSitesGroupTreeItem } from "../../../../power-pages/actions-hub/tre import { ActionsHubTreeItem } from "../../../../power-pages/actions-hub/tree-items/ActionsHubTreeItem"; import { pacAuthManager } from "../../../../pac/PacAuthManager"; import { PacTerminal } from "../../../../lib/PacTerminal"; -import { PacWrapper } from "../../../../pac/PacWrapper"; -import { SUCCESS } from "../../../../../common/constants"; describe("ActionsHubTreeDataProvider", () => { let context: vscode.ExtensionContext; let traceInfoStub: sinon.SinonStub; let traceErrorStub: sinon.SinonStub; let authInfoStub: sinon.SinonStub; - let pacTerminal: PacTerminal; - let pacWrapperStub: sinon.SinonStubbedInstance; - let registerCommandStub: sinon.SinonStub; beforeEach(() => { context = { @@ -40,11 +35,6 @@ describe("ActionsHubTreeDataProvider", () => { featureUsage: sinon.stub() }); authInfoStub = sinon.stub(pacAuthManager, "getAuthInfo"); - pacTerminal = sinon.createStubInstance(PacTerminal); - pacWrapperStub = sinon.createStubInstance(PacWrapper); - pacWrapperStub.activeAuth.resolves({ Status: SUCCESS, Results: [], Errors: [], Information: [] }); - (pacTerminal.getWrapper as sinon.SinonStub).returns(pacWrapperStub); - registerCommandStub = sinon.stub(vscode.commands, "registerCommand"); }); afterEach(() => { @@ -53,21 +43,24 @@ describe("ActionsHubTreeDataProvider", () => { describe('initialize', () => { it("should initialize and log initialization event", () => { - ActionsHubTreeDataProvider.initialize(context, pacTerminal); + const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; + ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); expect(traceInfoStub.calledWith(Constants.EventNames.ACTIONS_HUB_INITIALIZED)).to.be.true; }); }); describe('getTreeItem', () => { it("should return the element in getTreeItem", () => { + const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; const element = {} as ActionsHubTreeItem; - const result = ActionsHubTreeDataProvider.initialize(context, pacTerminal).getTreeItem(element); + const result = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal).getTreeItem(element); expect(result).to.equal(element); }); }); describe('getChildren', () => { it("should return environment and other sites group tree items in getChildren when no element is passed", async () => { + const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; authInfoStub.returns({ organizationFriendlyName: "TestOrg", userType: "", @@ -86,7 +79,7 @@ describe("ActionsHubTreeDataProvider", () => { organizationId: "", organizationUniqueName: "" }); - const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + const provider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); const result = await provider.getChildren(); expect(result).to.not.be.null; @@ -97,8 +90,9 @@ describe("ActionsHubTreeDataProvider", () => { }); it("should return environment group tree item with default name when no auth info is available", async () => { + const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; authInfoStub.returns(null); - const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + const provider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); const result = await provider.getChildren(); expect(result).to.not.be.null; @@ -110,9 +104,9 @@ describe("ActionsHubTreeDataProvider", () => { }); it("should return null in getChildren when an error occurs", async () => { + const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; authInfoStub.throws(new Error("Test Error")); - - const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + const provider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); const result = await provider.getChildren(); expect(result).to.be.null; @@ -122,8 +116,9 @@ describe("ActionsHubTreeDataProvider", () => { }); it("should return an empty array in getChildren when an element is passed", async () => { + const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; const element = {} as ActionsHubTreeItem; - const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + const provider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); const result = await provider.getChildren(element); expect(result).to.be.an("array").that.is.empty; @@ -132,9 +127,10 @@ describe("ActionsHubTreeDataProvider", () => { describe('dispose', () => { it("should dispose all disposables", () => { + const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; const disposable1 = { dispose: sinon.spy() }; const disposable2 = { dispose: sinon.spy() }; - const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); actionsHubTreeDataProvider["_disposables"].push(disposable1 as vscode.Disposable, disposable2 as vscode.Disposable); actionsHubTreeDataProvider.dispose(); @@ -142,26 +138,4 @@ describe("ActionsHubTreeDataProvider", () => { expect(disposable2.dispose.calledOnce).to.be.true; }); }); - - describe('registerPanel', () => { - it("should register refresh command and handle errors", async () => { - const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); - const refreshStub = sinon.stub(actionsHubTreeDataProvider, "refresh"); - - actionsHubTreeDataProvider["registerPanel"](pacTerminal); - - const refreshCommandCallback = registerCommandStub.args[0][1]; - await refreshCommandCallback(); - - expect(registerCommandStub.calledOnceWith("powerpages.actionsHub.refresh", sinon.match.func)).to.be.true; - expect(refreshStub.calledOnce).to.be.true; - expect(traceErrorStub.called).to.be.false; - - // Simulate error - pacWrapperStub.activeAuth.returns(Promise.reject(new Error("Test Error"))); - await refreshCommandCallback(); - - expect(traceErrorStub.calledOnceWith(Constants.EventNames.ACTIONS_HUB_REFRESH_FAILED)).to.be.true; - }); - }); }); From c51f37835fb7a3865a0bd18d3021c7f59d6614ee Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 7 Feb 2025 14:55:21 +0530 Subject: [PATCH 34/38] Refactor ActionsHubTreeDataProvider tests to use a stubbed PacTerminal, enhancing test reliability and reducing reliance on real implementations --- .../ActionsHubTreeDataProvider.test.ts | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index 9e3f7495..0a563af0 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -15,12 +15,16 @@ import { OtherSitesGroupTreeItem } from "../../../../power-pages/actions-hub/tre import { ActionsHubTreeItem } from "../../../../power-pages/actions-hub/tree-items/ActionsHubTreeItem"; import { pacAuthManager } from "../../../../pac/PacAuthManager"; import { PacTerminal } from "../../../../lib/PacTerminal"; +import { PacWrapper } from "../../../../pac/PacWrapper"; +import { SUCCESS } from "../../../../../common/constants"; describe("ActionsHubTreeDataProvider", () => { let context: vscode.ExtensionContext; let traceInfoStub: sinon.SinonStub; let traceErrorStub: sinon.SinonStub; let authInfoStub: sinon.SinonStub; + let pacTerminal: PacTerminal; + let pacWrapperStub: sinon.SinonStubbedInstance; beforeEach(() => { context = { @@ -35,6 +39,10 @@ describe("ActionsHubTreeDataProvider", () => { featureUsage: sinon.stub() }); authInfoStub = sinon.stub(pacAuthManager, "getAuthInfo"); + pacTerminal = sinon.createStubInstance(PacTerminal); + pacWrapperStub = sinon.createStubInstance(PacWrapper); + pacWrapperStub.activeAuth.resolves({ Status: SUCCESS, Results: [], Errors: [], Information: [] }); + (pacTerminal.getWrapper as sinon.SinonStub).returns(pacWrapperStub); }); afterEach(() => { @@ -43,24 +51,21 @@ describe("ActionsHubTreeDataProvider", () => { describe('initialize', () => { it("should initialize and log initialization event", () => { - const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; - ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); + ActionsHubTreeDataProvider.initialize(context, pacTerminal); expect(traceInfoStub.calledWith(Constants.EventNames.ACTIONS_HUB_INITIALIZED)).to.be.true; }); }); describe('getTreeItem', () => { it("should return the element in getTreeItem", () => { - const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; const element = {} as ActionsHubTreeItem; - const result = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal).getTreeItem(element); + const result = ActionsHubTreeDataProvider.initialize(context, pacTerminal).getTreeItem(element); expect(result).to.equal(element); }); }); describe('getChildren', () => { it("should return environment and other sites group tree items in getChildren when no element is passed", async () => { - const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; authInfoStub.returns({ organizationFriendlyName: "TestOrg", userType: "", @@ -79,7 +84,7 @@ describe("ActionsHubTreeDataProvider", () => { organizationId: "", organizationUniqueName: "" }); - const provider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); + const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); const result = await provider.getChildren(); expect(result).to.not.be.null; @@ -90,9 +95,8 @@ describe("ActionsHubTreeDataProvider", () => { }); it("should return environment group tree item with default name when no auth info is available", async () => { - const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; authInfoStub.returns(null); - const provider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); + const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); const result = await provider.getChildren(); expect(result).to.not.be.null; @@ -104,9 +108,9 @@ describe("ActionsHubTreeDataProvider", () => { }); it("should return null in getChildren when an error occurs", async () => { - const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; authInfoStub.throws(new Error("Test Error")); - const provider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); + + const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); const result = await provider.getChildren(); expect(result).to.be.null; @@ -116,26 +120,11 @@ describe("ActionsHubTreeDataProvider", () => { }); it("should return an empty array in getChildren when an element is passed", async () => { - const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; const element = {} as ActionsHubTreeItem; - const provider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); + const provider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); const result = await provider.getChildren(element); expect(result).to.be.an("array").that.is.empty; }); }); - - describe('dispose', () => { - it("should dispose all disposables", () => { - const fakePacTerminal = { getWrapper: sinon.stub() } as unknown as PacTerminal; - const disposable1 = { dispose: sinon.spy() }; - const disposable2 = { dispose: sinon.spy() }; - const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, fakePacTerminal); - actionsHubTreeDataProvider["_disposables"].push(disposable1 as vscode.Disposable, disposable2 as vscode.Disposable); - - actionsHubTreeDataProvider.dispose(); - expect(disposable1.dispose.calledOnce).to.be.true; - expect(disposable2.dispose.calledOnce).to.be.true; - }); - }); }); From 0549017fd24a3a60c504bdb3c8ae580c920200d0 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 7 Feb 2025 15:05:11 +0530 Subject: [PATCH 35/38] Add tests for dispose and registerPanel methods in ActionsHubTreeDataProvider, ensuring proper command registration and error handling --- .../ActionsHubTreeDataProvider.test.ts | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index 0a563af0..2cc21f4a 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -25,6 +25,7 @@ describe("ActionsHubTreeDataProvider", () => { let authInfoStub: sinon.SinonStub; let pacTerminal: PacTerminal; let pacWrapperStub: sinon.SinonStubbedInstance; + let registerCommandStub: sinon.SinonStub; beforeEach(() => { context = { @@ -43,6 +44,7 @@ describe("ActionsHubTreeDataProvider", () => { pacWrapperStub = sinon.createStubInstance(PacWrapper); pacWrapperStub.activeAuth.resolves({ Status: SUCCESS, Results: [], Errors: [], Information: [] }); (pacTerminal.getWrapper as sinon.SinonStub).returns(pacWrapperStub); + registerCommandStub = sinon.stub(vscode.commands, "registerCommand"); }); afterEach(() => { @@ -127,4 +129,39 @@ describe("ActionsHubTreeDataProvider", () => { expect(result).to.be.an("array").that.is.empty; }); }); + + describe('dispose', () => { + it("should dispose all disposables", () => { + const disposable1 = { dispose: sinon.spy() }; + const disposable2 = { dispose: sinon.spy() }; + const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + actionsHubTreeDataProvider["_disposables"].push(disposable1 as vscode.Disposable, disposable2 as vscode.Disposable); + + actionsHubTreeDataProvider.dispose(); + expect(disposable1.dispose.calledOnce).to.be.true; + expect(disposable2.dispose.calledOnce).to.be.true; + }); + }); + + describe('registerPanel', () => { + it("should register refresh command and handle errors", async () => { + const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + const refreshStub = sinon.stub(actionsHubTreeDataProvider, "refresh"); + + actionsHubTreeDataProvider["registerPanel"](pacTerminal); + + const refreshCommandCallback = registerCommandStub.args[0][1]; + await refreshCommandCallback(); + + expect(registerCommandStub.calledOnceWith("powerpages.actionsHub.refresh", sinon.match.func)).to.be.true; + expect(refreshStub.calledOnce).to.be.true; + expect(traceErrorStub.called).to.be.false; + + // Simulate error + pacWrapperStub.activeAuth.returns(Promise.reject(new Error("Test Error"))); + await refreshCommandCallback(); + + expect(traceErrorStub.calledOnceWith(Constants.EventNames.ACTIONS_HUB_REFRESH_FAILED)).to.be.true; + }); + }); }); From fa27d7ecfe06bce7765b09168671a238cf3bad55 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 7 Feb 2025 15:14:39 +0530 Subject: [PATCH 36/38] Remove unused stubs and tests for dispose and registerPanel methods in ActionsHubTreeDataProvider, streamlining test code --- .../ActionsHubTreeDataProvider.test.ts | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index 2cc21f4a..db14aa63 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -25,7 +25,6 @@ describe("ActionsHubTreeDataProvider", () => { let authInfoStub: sinon.SinonStub; let pacTerminal: PacTerminal; let pacWrapperStub: sinon.SinonStubbedInstance; - let registerCommandStub: sinon.SinonStub; beforeEach(() => { context = { @@ -44,7 +43,6 @@ describe("ActionsHubTreeDataProvider", () => { pacWrapperStub = sinon.createStubInstance(PacWrapper); pacWrapperStub.activeAuth.resolves({ Status: SUCCESS, Results: [], Errors: [], Information: [] }); (pacTerminal.getWrapper as sinon.SinonStub).returns(pacWrapperStub); - registerCommandStub = sinon.stub(vscode.commands, "registerCommand"); }); afterEach(() => { @@ -130,38 +128,4 @@ describe("ActionsHubTreeDataProvider", () => { }); }); - describe('dispose', () => { - it("should dispose all disposables", () => { - const disposable1 = { dispose: sinon.spy() }; - const disposable2 = { dispose: sinon.spy() }; - const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); - actionsHubTreeDataProvider["_disposables"].push(disposable1 as vscode.Disposable, disposable2 as vscode.Disposable); - - actionsHubTreeDataProvider.dispose(); - expect(disposable1.dispose.calledOnce).to.be.true; - expect(disposable2.dispose.calledOnce).to.be.true; - }); - }); - - describe('registerPanel', () => { - it("should register refresh command and handle errors", async () => { - const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); - const refreshStub = sinon.stub(actionsHubTreeDataProvider, "refresh"); - - actionsHubTreeDataProvider["registerPanel"](pacTerminal); - - const refreshCommandCallback = registerCommandStub.args[0][1]; - await refreshCommandCallback(); - - expect(registerCommandStub.calledOnceWith("powerpages.actionsHub.refresh", sinon.match.func)).to.be.true; - expect(refreshStub.calledOnce).to.be.true; - expect(traceErrorStub.called).to.be.false; - - // Simulate error - pacWrapperStub.activeAuth.returns(Promise.reject(new Error("Test Error"))); - await refreshCommandCallback(); - - expect(traceErrorStub.calledOnceWith(Constants.EventNames.ACTIONS_HUB_REFRESH_FAILED)).to.be.true; - }); - }); }); From 8bec8333c8ed25d116a6e6d7a42edd32a7c8e1e3 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 7 Feb 2025 15:29:19 +0530 Subject: [PATCH 37/38] Add test for command registration in ActionsHubTreeDataProvider, verifying refresh command registration --- .../actions-hub/ActionsHubTreeDataProvider.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index db14aa63..c2f28e5c 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -25,8 +25,10 @@ describe("ActionsHubTreeDataProvider", () => { let authInfoStub: sinon.SinonStub; let pacTerminal: PacTerminal; let pacWrapperStub: sinon.SinonStubbedInstance; + let registerCommandStub: sinon.SinonStub; beforeEach(() => { + registerCommandStub = sinon.stub(vscode.commands, "registerCommand"); context = { extensionUri: vscode.Uri.parse("https://localhost:3000") } as vscode.ExtensionContext; @@ -126,6 +128,15 @@ describe("ActionsHubTreeDataProvider", () => { expect(result).to.be.an("array").that.is.empty; }); + + it("should register refresh command", () => { + // Initialize + const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + actionsHubTreeDataProvider["registerPanel"](pacTerminal); + + // Assert that the command was registered + expect(registerCommandStub.calledWith("powerpages.actionsHub.refresh")).to.be.true; + }); }); }); From 592095f8c09fb638bf58ae8ee19120059e812fe6 Mon Sep 17 00:00:00 2001 From: amitjoshi Date: Fri, 7 Feb 2025 15:57:00 +0530 Subject: [PATCH 38/38] Add test for refresh command registration in ActionsHubTreeDataProvider --- .../ActionsHubTreeDataProvider.test.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts index c2f28e5c..a164fdeb 100644 --- a/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts +++ b/src/client/test/Integration/power-pages/actions-hub/ActionsHubTreeDataProvider.test.ts @@ -56,6 +56,15 @@ describe("ActionsHubTreeDataProvider", () => { ActionsHubTreeDataProvider.initialize(context, pacTerminal); expect(traceInfoStub.calledWith(Constants.EventNames.ACTIONS_HUB_INITIALIZED)).to.be.true; }); + + it("should register refresh command", () => { + // Initialize + const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); + actionsHubTreeDataProvider["registerPanel"](pacTerminal); + + // Assert that the command was registered + expect(registerCommandStub.calledWith("powerpages.actionsHub.refresh")).to.be.true; + }); }); describe('getTreeItem', () => { @@ -128,15 +137,6 @@ describe("ActionsHubTreeDataProvider", () => { expect(result).to.be.an("array").that.is.empty; }); - - it("should register refresh command", () => { - // Initialize - const actionsHubTreeDataProvider = ActionsHubTreeDataProvider.initialize(context, pacTerminal); - actionsHubTreeDataProvider["registerPanel"](pacTerminal); - - // Assert that the command was registered - expect(registerCommandStub.calledWith("powerpages.actionsHub.refresh")).to.be.true; - }); }); });