diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 4215ccd5..02f57d61 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -93,36 +93,13 @@ "Do not translate 'PCF' as it is a product name." ] }, - "Preparing pac CLI (v{0}).../{0} represents the version number": { - "message": "Preparing pac CLI (v{0})...", - "comment": [ - "{0} represents the version number" - ] - }, - "The pac CLI is ready for use in your VS Code terminal!": "The pac CLI is ready for use in your VS Code terminal!", - "Cannot install pac CLI: {0}/{0} represents the error message returned from the exception": { - "message": "Cannot install pac CLI: {0}", - "comment": [ - "{0} represents the error message returned from the exception" - ] - }, - "Installing Power Pages generator(v{0}).../{0} represents the version number": { - "message": "Installing Power Pages generator(v{0})...", - "comment": [ - "{0} represents the version number" - ] - }, - "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": { - "message": "dotnet sdk 6.0 or greater must be installed", - "comment": [ - "Do not translate 'dotnet' or 'sdk'" - ] - }, "The extension Microsoft Edge Tools is required to run this command. Do you want to install it now?": "The extension Microsoft Edge Tools is required to run this command. Do you want to install it now?", "Install": "Install", "Cancel": "Cancel", - "No workspace folder is open.": "No workspace folder is open.", - "Failed to update launch.json: ${0}": "Failed to update launch.json: ${0}", + "Site runtime preview feature is not enabled.": "Site runtime preview feature is not enabled.", + "No workspace folder opened. Please open a site folder to preview.": "No workspace folder opened. Please open a site folder to preview.", + "Website URL not found.": "Website URL not found.", + "Opening site preview...": "Opening site preview...", "File might be referenced by name {0} here./{0} represents the name of the file": { "message": "File might be referenced by name {0} here.", "comment": [ @@ -194,6 +171,12 @@ "Do not translate 'npm'" ] }, + "Installing Power Pages generator(v{0}).../{0} represents the version number": { + "message": "Installing Power Pages generator(v{0})...", + "comment": [ + "{0} represents the version number" + ] + }, "Cannot install Power Pages generator: {0}/{0} represents the error message returned from the exception": { "message": "Cannot install Power Pages generator: {0}", "comment": [ @@ -223,6 +206,25 @@ "The {3} represents Dataverse Environment's Organization ID (GUID)" ] }, + "Preparing pac CLI (v{0}).../{0} represents the version number": { + "message": "Preparing pac CLI (v{0})...", + "comment": [ + "{0} represents the version number" + ] + }, + "The pac CLI is ready for use in your VS Code terminal!": "The pac CLI is ready for use in your VS Code terminal!", + "Cannot install pac CLI: {0}/{0} represents the error message returned from the exception": { + "message": "Cannot install pac CLI: {0}", + "comment": [ + "{0} represents the error message returned from the exception" + ] + }, + "dotnet sdk 6.0 or greater must be installed/Do not translate 'dotnet' or 'sdk'": { + "message": "dotnet sdk 6.0 or greater must be installed", + "comment": [ + "Do not translate 'dotnet' or 'sdk'" + ] + }, "Confirm": "Confirm", "Are you sure you want to clear all the Auth Profiles?": "Are you sure you want to clear all the Auth Profiles?", "Are you sure you want to delete the Auth Profile {0}-{1}?/{0} is the user name, {1} is the URL of environment of the auth profile": { diff --git a/loc/translations-export/vscode-powerplatform.xlf b/loc/translations-export/vscode-powerplatform.xlf index 9f9c6f0f..ca709b39 100644 --- a/loc/translations-export/vscode-powerplatform.xlf +++ b/loc/translations-export/vscode-powerplatform.xlf @@ -162,9 +162,6 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca Failed to get file ready for edit: {0} - - Failed to update launch.json: ${0} - Feature is not enabled for this geo. @@ -300,8 +297,8 @@ The {3} represents Dataverse Environment's Organization ID (GUID) No workspace folder found - - No workspace folder is open. + + No workspace folder opened. Please open a site folder to preview. One or more attribute names have been changed or removed. Contact your admin. @@ -312,6 +309,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID) Opening preview site... + + Opening site preview... + Operation failed. See output panel for details. @@ -385,6 +385,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID) Show Output Panel + + Site runtime preview feature is not enabled. + Some references might be broken. Please check diagnostics for details. @@ -461,6 +464,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID) Webpage names should contain only letters, numbers, hyphens, or underscores. + + Website URL not found. + Website not found in the environment. Please check the credentials and root folder path. @@ -632,6 +638,9 @@ The second line should be '[TRANSLATION HERE](command:powerplatform-walkthrough. PowerApps Portal -> Show preview + + Preview Site + Refresh diff --git a/package.json b/package.json index ecadf0b2..40939fad 100644 --- a/package.json +++ b/package.json @@ -370,6 +370,10 @@ "command": "powerPlatform.previewCurrentActiveUsers", "title": "Current Active Users", "icon": "$(person)" + }, + { + "command": "microsoft.powerplatform.pages.preview-site", + "title": "%powerplatform.pages.previewSite.title%" } ], "configuration": { diff --git a/package.nls.json b/package.nls.json index 70304aba..e4affd92 100644 --- a/package.nls.json +++ b/package.nls.json @@ -97,5 +97,6 @@ "The fifth line should be '[TRANSLATION HERE](command:powerplatform-walkthrough.saveConflict-learn-more).', keeping brackets and the text in the parentheses unmodified" ] }, - "microsoft-powerplatform-portals.navigation-loop.powerPagesFileExplorer.title": "POWER PAGES ACTIONS" + "microsoft-powerplatform-portals.navigation-loop.powerPagesFileExplorer.title": "POWER PAGES ACTIONS", + "powerplatform.pages.previewSite.title": "Preview Site" } diff --git a/src/client/extension.ts b/src/client/extension.ts index 0a49712a..1feac8c7 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -46,7 +46,7 @@ import { PowerPagesAppName, PowerPagesClientName } from "../common/ecs-features/ import { ECSFeaturesClient } from "../common/ecs-features/ecsFeatureClient"; import { getECSOrgLocationValue } from "../common/utilities/Utils"; import { CliAcquisitionContext } from "./lib/CliAcquisitionContext"; -import { PreviewSite } from "./runtime-site-preview/PreviewSite"; +import { PreviewSite, SITE_PREVIEW_COMMAND_ID } from "./runtime-site-preview/PreviewSite"; let client: LanguageClient; let _context: vscode.ExtensionContext; @@ -102,6 +102,22 @@ export async function activate( ); } + // portal web view panel + _context.subscriptions.push( + vscode.commands.registerCommand( + "microsoft-powerapps-portals.preview-show", + () => { + _telemetry.sendTelemetryEvent("StartCommand", { + commandId: "microsoft-powerapps-portals.preview-show", + }); + oneDSLoggerWrapper.getLogger().traceInfo("StartCommand", { + commandId: "microsoft-powerapps-portals.preview-show" + }); + PortalWebView.createOrShow(); + } + ) + ); + // registering bootstrapdiff command _context.subscriptions.push( vscode.commands.registerCommand('microsoft-powerapps-portals.bootstrap-diff', async () => { @@ -180,7 +196,8 @@ export async function activate( ) || []; - let websiteURL = ""; + let websiteURL: string | undefined = ""; + const isSiteRuntimePreviewEnabled = PreviewSite.isSiteRuntimePreviewEnabled(); _context.subscriptions.push( orgChangeEvent(async (orgDetails: ActiveOrgOutput) => { @@ -236,7 +253,8 @@ export async function activate( copilotNotificationShown = true; } - if(artemisResponse !== null && PreviewSite.isSiteRuntimePreviewEnabled()) { + + if (artemisResponse !== null && isSiteRuntimePreviewEnabled) { websiteURL = await PreviewSite.getWebSiteURL(workspaceFolders, artemisResponse?.stamp, orgDetails.EnvironmentId, _telemetry); } @@ -260,33 +278,19 @@ export async function activate( vscode.commands.executeCommand('setContext', 'powerpages.websiteYmlExists', false); } - const isEnabled = PreviewSite.isSiteRuntimePreviewEnabled(); - _telemetry.sendTelemetryEvent("EnableSiteRuntimePreview", { - isEnabled: isEnabled.toString(), + isEnabled: isSiteRuntimePreviewEnabled.toString(), websiteURL: websiteURL }); oneDSLoggerWrapper.getLogger().traceInfo("EnableSiteRuntimePreview", { - isEnabled: isEnabled.toString(), + isEnabled: isSiteRuntimePreviewEnabled.toString(), websiteURL: websiteURL }); _context.subscriptions.push( vscode.commands.registerCommand( - "microsoft-powerapps-portals.preview-show", - () => { - if (!isEnabled) { - _telemetry.sendTelemetryEvent("StartCommand", { - commandId: "microsoft-powerapps-portals.preview-show", - }); - oneDSLoggerWrapper.getLogger().traceInfo("StartCommand", { - commandId: "microsoft-powerapps-portals.preview-show" - }); - PortalWebView.createOrShow(); - } else { - PreviewSite.launchBrowserAndDevToolsWithinVsCode(websiteURL); - } - } + SITE_PREVIEW_COMMAND_ID, + async () => await PreviewSite.handlePreviewRequest(isSiteRuntimePreviewEnabled, websiteURL, _telemetry) ) ); diff --git a/src/client/runtime-site-preview/LaunchJsonHelper.ts b/src/client/runtime-site-preview/LaunchJsonHelper.ts deleted file mode 100644 index 9fc29b20..00000000 --- a/src/client/runtime-site-preview/LaunchJsonHelper.ts +++ /dev/null @@ -1,90 +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 * as vscode from 'vscode'; - -export async function updateLaunchJsonConfig(url: string): Promise { - - const workspaceFolders = vscode.workspace.workspaceFolders; - if (!workspaceFolders) { - vscode.window.showErrorMessage( - vscode.l10n.t('No workspace folder is open.')); - return; - } - - const launchJsonPath = vscode.Uri.joinPath(workspaceFolders[0].uri, '.vscode', 'launch.json'); - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let launchJson: any; - let launchJsonDoc: vscode.TextDocument | undefined; - - try { - launchJsonDoc = await vscode.workspace.openTextDocument(launchJsonPath); - const launchJsonText = launchJsonDoc.getText(); - launchJson = launchJsonText ? JSON.parse(launchJsonText) : { configurations: [], compounds: [] }; - } catch (error) { - // If the file does not exist or is empty, initialize it - launchJson = { configurations: [], compounds: [] }; - } - - // Update or add the configurations for Microsoft Edge - const edgeConfigurations = [ - { - type: 'pwa-msedge', - name: 'Launch Microsoft Edge', - request: 'launch', - runtimeArgs: ['--remote-debugging-port=9222'], - url: url, - presentation: { - hidden: true - } - }, - { - type: 'pwa-msedge', - name: 'Launch Microsoft Edge in headless mode', - request: 'launch', - runtimeArgs: ['--headless', '--remote-debugging-port=9222'], - url: url, - presentation: { - hidden: true - } - }, - { - type: 'vscode-edge-devtools.debug', - name: 'Open Edge DevTools', - request: 'attach', - url: url, - presentation: { - hidden: true - } - } - ]; - - // Update or add the compounds for Microsoft Edge - const edgeCompounds = [ - { - name: 'Launch Edge Headless and attach DevTools', - configurations: ['Launch Microsoft Edge in headless mode', 'Open Edge DevTools'] - }, - { - name: 'Launch Edge and attach DevTools', - configurations: ['Launch Microsoft Edge', 'Open Edge DevTools'] - } - ]; - - // Merge the new configurations and compounds with the existing ones - launchJson.configurations = [...launchJson.configurations, ...edgeConfigurations]; - launchJson.compounds = [...launchJson.compounds, ...edgeCompounds]; - - // Write the updated launch.json file - const launchJsonContent = JSON.stringify(launchJson, null, 4); - await vscode.workspace.fs.writeFile(launchJsonPath, Buffer.from(launchJsonContent, 'utf8')); - } catch (e) { - if(e instanceof Error) { - vscode.window.showErrorMessage( - vscode.l10n.t("Failed to update launch.json: ${0}", e.message)); - } - } -} diff --git a/src/client/runtime-site-preview/PreviewSite.ts b/src/client/runtime-site-preview/PreviewSite.ts index 2fd34e54..dc5cbe13 100644 --- a/src/client/runtime-site-preview/PreviewSite.ts +++ b/src/client/runtime-site-preview/PreviewSite.ts @@ -4,33 +4,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 { getWebsiteRecordId } from '../../common/utilities/WorkspaceInfoFinderUtil'; import { ServiceEndpointCategory } from '../../common/services/Constants'; import { PPAPIService } from '../../common/services/PPAPIService'; import { VSCODE_EXTENSION_GET_WEBSITE_RECORD_ID_EMPTY } from '../../common/services/TelemetryConstants'; +import { EDGE_TOOLS_EXTENSION_ID } from '../../common/constants'; +import { oneDSLoggerWrapper } from "../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper"; +import { showProgressNotification } from '../../common/controls/ShowProgressNotification'; -export class PreviewSite { +export const SITE_PREVIEW_COMMAND_ID = "microsoft.powerplatform.pages.preview-site"; +export class PreviewSite { static isSiteRuntimePreviewEnabled(): boolean { const enableSiteRuntimePreview = ECSFeaturesClient.getConfig(EnableSiteRuntimePreview).enableSiteRuntimePreview - if(enableSiteRuntimePreview === undefined) { + if (enableSiteRuntimePreview === undefined) { return false; } - return enableSiteRuntimePreview; + return true; } static async getWebSiteURL(workspaceFolders: WorkspaceFolder[], stamp: ServiceEndpointCategory, envId: string, telemetry: ITelemetry): Promise { - - const websiteRecordId = getWebsiteRecordID(workspaceFolders, telemetry); + const websiteRecordId = getWebsiteRecordId(workspaceFolders, telemetry); if (!websiteRecordId) { telemetry.sendTelemetryEvent(VSCODE_EXTENSION_GET_WEBSITE_RECORD_ID_EMPTY, { websiteRecordId: websiteRecordId @@ -41,54 +41,14 @@ export class PreviewSite { return websiteDetails?.websiteUrl || ""; } - static async launchBrowserAndDevToolsWithinVsCode(webSitePreviewURL: string): Promise { - - const edgeToolsExtensionId = 'ms-edgedevtools.vscode-edge-devtools'; - const edgeToolsExtension = vscode.extensions.getExtension(edgeToolsExtensionId); - - if (edgeToolsExtension) { - // Preserve the original state of the launch.json file and .vscode folder - const workspaceFolder = vscode.workspace.workspaceFolders?.[0]; - const vscodeFolderPath = workspaceFolder ? path.join(workspaceFolder.uri.fsPath, '.vscode') : null; - const launchJsonPath = vscodeFolderPath ? path.join(vscodeFolderPath, 'launch.json') : null; - let originalLaunchJsonContent: string | null = null; - let vscodeFolderExisted = false; + static async launchBrowserAndDevToolsWithinVsCode(webSitePreviewURL: string | undefined): Promise { + if (!webSitePreviewURL || webSitePreviewURL === "") { + return; + } - if (vscodeFolderPath && fs.existsSync(vscodeFolderPath)) { - vscodeFolderExisted = true; - if (launchJsonPath && fs.existsSync(launchJsonPath)) { - originalLaunchJsonContent = fs.readFileSync(launchJsonPath, 'utf8'); - } - } + const edgeToolsExtension = vscode.extensions.getExtension(EDGE_TOOLS_EXTENSION_ID); - await updateLaunchJsonConfig(webSitePreviewURL); - - try { - // Added a 2-second delay before executing the launchProject command to handle the case where the launch.json file is not saved yet - await new Promise(resolve => setTimeout(resolve, 2000)); - 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 - - // Added a 2-second delay to ensure that debug session is closed and then launch.json file is removed - await new Promise(resolve => setTimeout(resolve, 2000)); - if (launchJsonPath) { - if (originalLaunchJsonContent !== null) { - fs.writeFileSync(launchJsonPath, originalLaunchJsonContent, 'utf8'); - } else if (fs.existsSync(launchJsonPath)) { - fs.unlinkSync(launchJsonPath); - } - } - - if (vscodeFolderPath && !vscodeFolderExisted && fs.existsSync(vscodeFolderPath)) { - const files = fs.readdirSync(vscodeFolderPath); - if (files.length === 0) { - fs.rmdirSync(vscodeFolderPath); - } - } - } - } else { + if (!edgeToolsExtension) { const install = await vscode.window.showWarningMessage( vscode.l10n.t( `The extension Microsoft Edge Tools is required to run this command. Do you want to install it now?` @@ -98,11 +58,45 @@ export class PreviewSite { ); if (install === vscode.l10n.t('Install')) { - // Open the Extensions view with the specific extension - vscode.commands.executeCommand('workbench.extensions.search', edgeToolsExtensionId); + await vscode.commands.executeCommand('workbench.extensions.search', EDGE_TOOLS_EXTENSION_ID); } return; } + + const settings = vscode.workspace.getConfiguration('vscode-edge-devtools'); + const currentDefaultUrl = await settings.get('defaultUrl'); + await settings.update('defaultUrl', webSitePreviewURL); + await vscode.commands.executeCommand('vscode-edge-devtools-view.launch'); + await settings.update('defaultUrl', currentDefaultUrl); + } + + static async handlePreviewRequest(isSiteRuntimePreviewEnabled: boolean, websiteURL: string | undefined, telemetry: ITelemetry) { + telemetry.sendTelemetryEvent("StartCommand", { + commandId: SITE_PREVIEW_COMMAND_ID, + }); + oneDSLoggerWrapper.getLogger().traceInfo("StartCommand", { + commandId: SITE_PREVIEW_COMMAND_ID + }); + + if (!isSiteRuntimePreviewEnabled) { + await vscode.window.showInformationMessage(vscode.l10n.t("Site runtime preview feature is not enabled.")); + return; + } + + if (!vscode.workspace.workspaceFolders) { + await vscode.window.showErrorMessage(vscode.l10n.t("No workspace folder opened. Please open a site folder to preview.")); + return; + } + + if (!websiteURL || websiteURL === "") { + await vscode.window.showErrorMessage(vscode.l10n.t("Website URL not found.")); + return; + } + + await showProgressNotification( + vscode.l10n.t('Opening site preview...'), + async () => await PreviewSite.launchBrowserAndDevToolsWithinVsCode(websiteURL) + ); } } diff --git a/src/common/OneDSLoggerTelemetry/telemetry/telemetry.ts b/src/common/OneDSLoggerTelemetry/telemetry/telemetry.ts index 1975fb82..0eeaaa85 100644 --- a/src/common/OneDSLoggerTelemetry/telemetry/telemetry.ts +++ b/src/common/OneDSLoggerTelemetry/telemetry/telemetry.ts @@ -18,7 +18,7 @@ export const CleanupRelatedFilesEvent = 'CleanupRelatedFilesEvent'; export const UpdateEntityNameInYmlEvent = 'UpdateEntityNameInYmlEvent'; export const UserFileCreateEvent = 'UserFileCreateEvent'; export const FileCreateEvent = 'FileCreateEvent'; -export const GetWebsiteRecordID = 'getWebsiteRecordID'; +export const GetWebsiteRecordID = 'getWebsiteRecordId'; interface ITelemetryData { eventName: string, @@ -69,4 +69,3 @@ export function sendTelemetryEvent(telemetry: ITelemetry, telemetryData: ITeleme oneDSLoggerWrapper.getLogger().traceInfo(telemetryData.eventName, telemetryDataProperties, telemetryDataMeasurements); } } - diff --git a/src/common/constants.ts b/src/common/constants.ts index b7c36538..410a0aac 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -94,3 +94,4 @@ export const VSCODE_EXTENSION_COPILOT_CONTEXT_RELATED_FILES_FETCH_FAILED = "VSCo export const ADX_WEBPAGE = 'adx_webpage' export const HTML_FILE_EXTENSION = '.html'; export const UTF8_ENCODING = 'utf8'; +export const EDGE_TOOLS_EXTENSION_ID = 'ms-edgedevtools.vscode-edge-devtools'; diff --git a/src/common/controls/ShowProgressNotification.ts b/src/common/controls/ShowProgressNotification.ts new file mode 100644 index 00000000..5a7c606f --- /dev/null +++ b/src/common/controls/ShowProgressNotification.ts @@ -0,0 +1,19 @@ +/* + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + */ + +import * as vscode from 'vscode'; +import { Progress } from 'vscode'; +import { CancellationToken } from 'vscode-languageserver'; + +export async function showProgressNotification(title: string, task: (progress: Progress<{ + message: string; + increment?: number; +}>, token: CancellationToken) => Thenable) { + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title, + cancellable: false + }, async (progress, token) => await task(progress, token)); +} diff --git a/src/common/utilities/WorkspaceInfoFinderUtil.ts b/src/common/utilities/WorkspaceInfoFinderUtil.ts index 4d60e91a..e0ed9944 100644 --- a/src/common/utilities/WorkspaceInfoFinderUtil.ts +++ b/src/common/utilities/WorkspaceInfoFinderUtil.ts @@ -40,7 +40,7 @@ export function getPortalsOrgURLs(workspaceRootFolders: WorkspaceFolder[] | null return output; } -export function getWebsiteRecordID(workspaceFolders: { uri: string }[], telemetry: ITelemetry): string { +export function getWebsiteRecordId(workspaceFolders: { uri: string }[], telemetry: ITelemetry): string { try { if (!workspaceFolders || workspaceFolders.length === 0) { return ""; @@ -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 ""; }