diff --git a/src/client/extension.ts b/src/client/extension.ts index e53fa05a..8e05f2f4 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -40,16 +40,13 @@ import { ActiveOrgOutput } from "./pac/PacTypes"; import { desktopTelemetryEventNames } from "../common/OneDSLoggerTelemetry/client/desktopExtensionTelemetryEventNames"; import { ArtemisService } from "../common/services/ArtemisService"; import { workspaceContainsPortalConfigFolder } from "../common/utilities/PathFinderUtil"; -import { getPortalsOrgURLs, getWebsiteRecordID } from "../common/utilities/WorkspaceInfoFinderUtil"; +import { getPortalsOrgURLs } from "../common/utilities/WorkspaceInfoFinderUtil"; import { EXTENSION_ID, SUCCESS } from "../common/constants"; import { AadIdKey, EnvIdKey, TenantIdKey } from "../common/OneDSLoggerTelemetry/telemetryConstants"; import { PowerPagesAppName, PowerPagesClientName } from "../common/ecs-features/constants"; import { ECSFeaturesClient } from "../common/ecs-features/ecsFeatureClient"; import { getECSOrgLocationValue } from "../common/utilities/Utils"; import { PreviewSite } from "./runtimeSitePreview/PreviewSite"; -import { PPAPIService } from "../common/services/PPAPIService"; -import { ServiceEndpointCategory } from "../common/services/Constants"; -import { EnableSiteRuntimePreview } from "../common/ecs-features/ecsFeatureGates"; let client: LanguageClient; let _context: vscode.ExtensionContext; @@ -175,6 +172,7 @@ export async function activate( _context.subscriptions.push(cli); _context.subscriptions.push(pacTerminal); + let websiteURL = ""; _context.subscriptions.push( orgChangeEvent(async (orgDetails: ActiveOrgOutput) => { const orgID = orgDetails.OrgId; @@ -209,17 +207,18 @@ export async function activate( } oneDSLoggerWrapper.getLogger().traceInfo(desktopTelemetryEventNames.DESKTOP_EXTENSION_INIT_CONTEXT, initContext); } + if(artemisResponse!==null && PreviewSite.isSiteRuntimePreviewEnabled()) { + websiteURL = await PreviewSite.getWebSiteURL(workspaceFolders, artemisResponse?.stamp, orgDetails.EnvironmentId, _telemetry); + } }) ); - let websiteURL: string | undefined; const workspaceFolders = vscode.workspace.workspaceFolders?.map( (fl) => ({ ...fl, uri: fl.uri.fsPath } as WorkspaceFolder) ) || []; // TODO: Handle for VSCode.dev also if (workspaceContainsPortalConfigFolder(workspaceFolders)) { - websiteURL = await getWebSiteURL(workspaceFolders, pacTerminal, _telemetry); let telemetryData = ''; let listOfActivePortals = []; try { @@ -249,18 +248,18 @@ export async function activate( } const registerPreviewShowCommand = async () => { - const isEnabled = isSiteRuntimePreviewEnabled(); + const isEnabled = PreviewSite.isSiteRuntimePreviewEnabled(); _telemetry.sendTelemetryEvent("EnableSiteRuntimePreview", { isEnabled: isEnabled.toString(), - websiteURL: websiteURL? websiteURL: "" + websiteURL: websiteURL }); oneDSLoggerWrapper.getLogger().traceInfo("EnableSiteRuntimePreview", { isEnabled: isEnabled.toString(), - websiteURL: websiteURL? websiteURL: "" + websiteURL: websiteURL }); - if (!isEnabled || !websiteURL) { + if (!isEnabled) { // portal web view panel _context.subscriptions.push( vscode.commands.registerCommand( @@ -317,37 +316,6 @@ export async function deactivate(): Promise { disposeNotificationPanel(); } -async function getWebSiteURL(workspaceFolders: WorkspaceFolder[], pacTerminal: PacTerminal, telemetry: ITelemetry): Promise { - - const envId = await getEnvId(pacTerminal); - const stamp = await getStamp(pacTerminal); - - const websiteRecordId = getWebsiteRecordID(workspaceFolders, telemetry); - const websiteDetails = await PPAPIService.getWebsiteDetailsByWebsiteRecordId(stamp, envId, websiteRecordId, _telemetry); - return websiteDetails?.websiteUrl; -} - -async function getStamp(pacTerminal: PacTerminal): Promise { - const orgDetails = (await pacTerminal.getWrapper().activeOrg()).Results; - const artemisResponse = await ArtemisService.getArtemisResponse(orgDetails.OrgId, _telemetry, ""); - return artemisResponse?.stamp || ServiceEndpointCategory.PROD; -} - -async function getEnvId(pacTerminal: PacTerminal): Promise { - const pacActiveAuth = await pacTerminal.getWrapper()?.activeAuth(); - return pacActiveAuth.Results?.filter(obj => obj.Key === EnvIdKey)?.[0]?.Value; -} - -function isSiteRuntimePreviewEnabled() { - const enableSiteRuntimePreview = ECSFeaturesClient.getConfig(EnableSiteRuntimePreview).enableSiteRuntimePreview - - if(enableSiteRuntimePreview === undefined) { - return false; - } - - return enableSiteRuntimePreview; -} - function didOpenTextDocument(document: vscode.TextDocument): void { // The debug options for the server // --inspect=6009: runs the server in Node's Inspector mode so VS Code can attach to the server for debugging diff --git a/src/client/runtimeSitePreview/LaunchJsonHelper.ts b/src/client/runtimeSitePreview/LaunchJsonHelper.ts index 0ee52f41..f592aef4 100644 --- a/src/client/runtimeSitePreview/LaunchJsonHelper.ts +++ b/src/client/runtimeSitePreview/LaunchJsonHelper.ts @@ -9,7 +9,8 @@ export async function updateLaunchJsonConfig(url: string): Promise { const workspaceFolders = vscode.workspace.workspaceFolders; if (!workspaceFolders) { - vscode.window.showErrorMessage('No workspace folder is open.'); + vscode.window.showErrorMessage( + vscode.l10n.t('No workspace folder is open.')); return; } @@ -82,7 +83,8 @@ export async function updateLaunchJsonConfig(url: string): Promise { await vscode.workspace.fs.writeFile(launchJsonPath, Buffer.from(launchJsonContent, 'utf8')); } catch (e) { if(e instanceof Error) { - vscode.window.showErrorMessage(`Failed to update launch.json: ${e.message}`); + vscode.window.showErrorMessage( + vscode.l10n.t(`Failed to update launch.json: ${e.message}`)); } } } diff --git a/src/client/runtimeSitePreview/PreviewSite.ts b/src/client/runtimeSitePreview/PreviewSite.ts index f1492b55..cd7f67fc 100644 --- a/src/client/runtimeSitePreview/PreviewSite.ts +++ b/src/client/runtimeSitePreview/PreviewSite.ts @@ -7,9 +7,33 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as fs from 'fs'; import { updateLaunchJsonConfig } from './LaunchJsonHelper'; +import { ECSFeaturesClient } from '../../common/ecs-features/ecsFeatureClient'; +import { EnableSiteRuntimePreview } from '../../common/ecs-features/ecsFeatureGates'; +import { ITelemetry } from '../../common/OneDSLoggerTelemetry/telemetry/ITelemetry'; +import { WorkspaceFolder } from 'vscode-languageclient/node'; +import { getWebsiteRecordID } from '../../common/utilities/WorkspaceInfoFinderUtil'; +import { ServiceEndpointCategory } from '../../common/services/Constants'; +import { PPAPIService } from '../../common/services/PPAPIService'; export class PreviewSite { + static isSiteRuntimePreviewEnabled(): boolean { + const enableSiteRuntimePreview = ECSFeaturesClient.getConfig(EnableSiteRuntimePreview).enableSiteRuntimePreview + + if(enableSiteRuntimePreview === undefined) { + return false; + } + + return enableSiteRuntimePreview; + } + + static async getWebSiteURL(workspaceFolders: WorkspaceFolder[], stamp: ServiceEndpointCategory, envId: string, telemetry: ITelemetry): Promise { + + const websiteRecordId = getWebsiteRecordID(workspaceFolders, telemetry); + const websiteDetails = await PPAPIService.getWebsiteDetailsByWebsiteRecordId(stamp, envId, websiteRecordId, telemetry); + return websiteDetails?.websiteUrl || ""; + } + static async launchBrowserAndDevToolsWithinVsCode(webSitePreviewURL: string | undefined): Promise { // The desired website preview URL @@ -37,14 +61,20 @@ export class PreviewSite { await updateLaunchJsonConfig(webSitePreviewURL); + try { // Add a 2-second delay before executing the launchProject command await new Promise(resolve => setTimeout(resolve, 2000)); + // Polling mechanism to check if the launch.json file has been created + //await this.waitForFileCreation(launchJsonPath); await vscode.commands.executeCommand('vscode-edge-devtools-view.launchProject'); + } finally { // Revert the changes made to the launch.json file and remove the .vscode folder if it was created await new Promise(resolve => setTimeout(resolve, 2000)); + // Event listener to detect when the Edge DevTools extension is closed + //await this.waitForExtensionClosure(edgeToolsExtensionId); if (launchJsonPath) { if (originalLaunchJsonContent !== null) { fs.writeFileSync(launchJsonPath, originalLaunchJsonContent, 'utf8'); @@ -62,9 +92,10 @@ export class PreviewSite { } } else { const install = await vscode.window.showWarningMessage( + vscode.l10n.t( `The extension "${edgeToolsExtensionId}" is required to run this command. Do you want to install it now?`, 'Install', 'Cancel' - ); + )); if (install === 'Install') { // Open the Extensions view with the specific extension @@ -74,4 +105,34 @@ export class PreviewSite { return; } } + + static async waitForFileCreation(filePath: string | null): Promise { + if (!filePath) return; + + const timeout = 10000; // 10 seconds + const interval = 100; // 100 milliseconds + let elapsed = 0; + + while (elapsed < timeout) { + if (fs.existsSync(filePath)) { + return; + } + await new Promise(resolve => setTimeout(resolve, interval)); + elapsed += interval; + } + + throw new Error(`File ${filePath} was not created within the timeout period.`); + } + + static async waitForExtensionClosure(extensionId: string): Promise { + return new Promise((resolve) => { + const checkInterval = setInterval(() => { + const extension = vscode.extensions.getExtension(extensionId); + if (!extension || !extension.isActive) { + clearInterval(checkInterval); + resolve(); + } + }, 100); + }); + } } diff --git a/src/common/ErrorConstants.ts b/src/common/ErrorConstants.ts index 1b7ef08f..3100b28b 100644 --- a/src/common/ErrorConstants.ts +++ b/src/common/ErrorConstants.ts @@ -53,4 +53,5 @@ export const ERROR_CONSTANTS = { BULKHEAD_LIMITS_EXCEEDED: "Bulkhead queue limits exceeded", NPS_FAILED_AUTH: "Failed to authenticate with NPS", PAC_AUTH_FAILED : "Failed to fetch org details from PAC" + }; diff --git a/src/common/OneDSLoggerTelemetry/telemetry/telemetry.ts b/src/common/OneDSLoggerTelemetry/telemetry/telemetry.ts index 8275fa0f..1975fb82 100644 --- a/src/common/OneDSLoggerTelemetry/telemetry/telemetry.ts +++ b/src/common/OneDSLoggerTelemetry/telemetry/telemetry.ts @@ -18,6 +18,7 @@ export const CleanupRelatedFilesEvent = 'CleanupRelatedFilesEvent'; export const UpdateEntityNameInYmlEvent = 'UpdateEntityNameInYmlEvent'; export const UserFileCreateEvent = 'UserFileCreateEvent'; export const FileCreateEvent = 'FileCreateEvent'; +export const GetWebsiteRecordID = 'getWebsiteRecordID'; interface ITelemetryData { eventName: string, diff --git a/src/common/services/PPAPIService.ts b/src/common/services/PPAPIService.ts index 77a01536..5a59ac31 100644 --- a/src/common/services/PPAPIService.ts +++ b/src/common/services/PPAPIService.ts @@ -5,7 +5,7 @@ import { ITelemetry } from "../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { getCommonHeaders, powerPlatformAPIAuthentication } from "./AuthenticationProvider"; -import { VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, VSCODE_EXTENSION_GET_PPAPI_WEBSITES_ENDPOINT_UNSUPPORTED_REGION, VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_COMPLETED } from "./TelemetryConstants"; +import { VSCODE_EXTENSION_SERVICE_STAMP_NOT_FOUND, VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, VSCODE_EXTENSION_GET_PPAPI_WEBSITES_ENDPOINT_UNSUPPORTED_REGION, VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_COMPLETED, VSCODE_EXTENSION_PPAPI_GET_WEBSITE_DETAILS_FAILED, VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_RECORD_ID_COMPLETED } from "./TelemetryConstants"; import { ServiceEndpointCategory, PPAPI_WEBSITES_ENDPOINT, PPAPI_WEBSITES_API_VERSION } from "./Constants"; import { sendTelemetryEvent } from "../copilot/telemetry/copilotTelemetry"; import { IWebsiteDetails } from "./Interfaces"; @@ -32,13 +32,18 @@ export class PPAPIService { return null; } - public static async getWebsiteDetailsByWebsiteRecordId(serviceEndpointStamp: ServiceEndpointCategory, environmentId: string, websiteRecordId: string, telemetry: ITelemetry): Promise { + 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 }); + return null; + } const websiteDetailsArray = await PPAPIService.getWebsiteDetails(serviceEndpointStamp, environmentId, telemetry); const websiteDetails = websiteDetailsArray?.find((website) => website.websiteRecordId === websiteRecordId); if (websiteDetails) { - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_COMPLETED, orgUrl: websiteDetails.dataverseInstanceUrl }); + sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_RECORD_ID_COMPLETED, orgUrl: websiteDetails.dataverseInstanceUrl }); return websiteDetails; } return null; @@ -58,7 +63,7 @@ export class PPAPIService { } } catch (error) { - sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_GET_CROSS_GEO_DATA_MOVEMENT_ENABLED_FLAG_FAILED, errorMsg: (error as Error).message }); + sendTelemetryEvent(telemetry, { eventName: VSCODE_EXTENSION_PPAPI_GET_WEBSITE_DETAILS_FAILED, errorMsg: (error as Error).message }); } return null; } diff --git a/src/common/services/TelemetryConstants.ts b/src/common/services/TelemetryConstants.ts index 4d4d2640..a9ba5570 100644 --- a/src/common/services/TelemetryConstants.ts +++ b/src/common/services/TelemetryConstants.ts @@ -24,3 +24,6 @@ export const VSCODE_EXTENSION_GET_PPAPI_WEBSITES_ENDPOINT_UNSUPPORTED_REGION = " export const VSCODE_EXTENSION_DECODE_JWT_TOKEN_FAILED = "VSCodeExtensionDecodeJWTTokenFailed"; export const VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_COMPLETED = "VSCodeExtensionPPAPIGetWebsiteByIdCompleted"; export const VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_ID_FAILED = "VSCodeExtensionPPAPIGetWebsiteByIdFailed"; +export const VSCODE_EXTENSION_SERVICE_STAMP_NOT_FOUND = "VSCodeExtensionServiceStampNotFound"; +export const VSCODE_EXTENSION_PPAPI_GET_WEBSITE_DETAILS_FAILED = "VSCodeExtensionPPAPIGetWebsiteDetailsFailed"; +export const VSCODE_EXTENSION_PPAPI_GET_WEBSITE_BY_RECORD_ID_COMPLETED = "VSCodeExtensionPPAPIGetWebsiteByRecordIdCompleted"; diff --git a/src/common/utilities/WorkspaceInfoFinderUtil.ts b/src/common/utilities/WorkspaceInfoFinderUtil.ts index ec825626..4d60e91a 100644 --- a/src/common/utilities/WorkspaceInfoFinderUtil.ts +++ b/src/common/utilities/WorkspaceInfoFinderUtil.ts @@ -11,7 +11,7 @@ import * as path from 'path'; import * as fs from 'fs'; import { parse } from 'yaml'; import { ITelemetry } from '../OneDSLoggerTelemetry/telemetry/ITelemetry'; -import { sendTelemetryEvent } from '../OneDSLoggerTelemetry/telemetry/telemetry'; +import { GetWebsiteRecordID, sendTelemetryEvent } from '../OneDSLoggerTelemetry/telemetry/telemetry'; export function getPortalsOrgURLs(workspaceRootFolders: WorkspaceFolder[] | null, telemetry: ITelemetry) { // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -56,7 +56,7 @@ export function getWebsiteRecordID(workspaceFolders: { uri: string }[], telemetr } } } catch (exception) { - sendTelemetryEvent(telemetry, { methodName: getWebsiteRecordID.name, eventName: 'getWebsiteRecordID', exception: exception as Error }); + sendTelemetryEvent(telemetry, { methodName: getWebsiteRecordID.name, eventName: GetWebsiteRecordID, exception: exception as Error }); } return ""; }