Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added site runtime preview code behind ECS Config #1052

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@
"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",
"No workspace folder is open.": "No workspace folder is open.",
"Failed to update launch.json: ${0}": "Failed to update launch.json: ${0}",
"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": [
Expand Down
16 changes: 14 additions & 2 deletions loc/translations-export/vscode-powerplatform.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca
<trans-unit id="++CODE++f9e17ed11037dab93f8820c30db63b2ff2a045b5761f71818b7291afae60f199">
<source xml:lang="en">Explain the following code {% include 'Page Copy'%}</source>
</trans-unit>
<trans-unit id="++CODE++b985f1515c42b4b5b0c11a3d7b3286fc9d66997d476668ab1f93a4a11499fef5">
<source xml:lang="en">Failed to create a new Power Pages site. Please try again.</source>
</trans-unit>
<trans-unit id="++CODE++2310c6b7e5953cab877859ba1fcfa98e58e1508677df9412010e9b578ea237f4">
<source xml:lang="en">Failed to create: {0}.</source>
<note>{0} will be replaced by the error message.</note>
Expand All @@ -159,8 +162,8 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca
<trans-unit id="++CODE++862d6197d64601aa13ce30db5ec5b8f819ad00fe21e3b031a3e47fe22ef68fb3">
<source xml:lang="en">Failed to get file ready for edit: {0}</source>
</trans-unit>
<trans-unit id="++CODE++41405814c44fb391a3f8e31d1a3bc20299cd2e87979ebfbd1eb9488be12c617a">
<source xml:lang="en">Failed to get site content from NL2Site service</source>
<trans-unit id="++CODE++bc7c38bba120feb9d6acc70f0a26050f3ce2a70dd87afa046c3f962be7d015e3">
<source xml:lang="en">Failed to update launch.json: ${0}</source>
</trans-unit>
<trans-unit id="++CODE++a9e36b880dd45b64ae5601865540605296febf9bd855fc46d9c35c2c2ed9a7f2">
<source xml:lang="en">Feature is not enabled for this geo.</source>
Expand Down Expand Up @@ -217,6 +220,9 @@ Return to this chat and @powerpages can help you write and edit your website cod
<trans-unit id="++CODE++e992151b1efc99f93484c7d7f3076b66ab072a8af2383e96104cc597c971339c">
<source xml:lang="en">Insert code into editor</source>
</trans-unit>
<trans-unit id="++CODE++569ca49f4aaf7846e952c1d4aeca72febd0b79fa1c4f9db08fd3127551218572">
<source xml:lang="en">Install</source>
</trans-unit>
<trans-unit id="++CODE++25109e9c19daeeed3977b84ace83722ac8a4daafcfe4e3709082fcc5b228e7a8">
<source xml:lang="en">Installing Power Pages generator(v{0})...</source>
<note>{0} represents the version number</note>
Expand Down Expand Up @@ -294,6 +300,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID)</note>
<trans-unit id="++CODE++6da29e062697a9f26659ef14ebda075afe939756af5d8e1d3451eb7d6d6e1a8a">
<source xml:lang="en">No workspace folder found</source>
</trans-unit>
<trans-unit id="++CODE++3c8a93afe6d6e99f8ad4b7fd72ffe91e62b1cbc29a8887d4922f80e92f4b78b2">
<source xml:lang="en">No workspace folder is open.</source>
</trans-unit>
<trans-unit id="++CODE++bda6bda1e902d120a7f4515ceac8546c3112e3cb9351df1d8b9713b8f86e0370">
<source xml:lang="en">One or more attribute names have been changed or removed. Contact your admin.</source>
</trans-unit>
Expand Down Expand Up @@ -391,6 +400,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID)</note>
<trans-unit id="++CODE++618f7afd7be2f12bf2ebbaba5bc7e951d2ebfb11d4480647f991fbb664caa26e">
<source xml:lang="en">The Power Pages generator is ready for use in your VS Code extension!</source>
</trans-unit>
<trans-unit id="++CODE++538ecf1398703f8a2048b99a2b2a533012b06ee88d67a0fd6fbbcd716cfbd663">
<source xml:lang="en">The extension Microsoft Edge Tools is required to run this command. Do you want to install it now?</source>
</trans-unit>
<trans-unit id="++CODE++e4a2396fd7a366292a40abc87b18a2329458c258f4d0e0e593e6189dff19a117">
<source xml:lang="en">The name you want to give to this authentication profile</source>
</trans-unit>
Expand Down
61 changes: 45 additions & 16 deletions src/client/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import { AadIdKey, EnvIdKey, TenantIdKey } from "../common/OneDSLoggerTelemetry/
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";

let client: LanguageClient;
let _context: vscode.ExtensionContext;
Expand Down Expand Up @@ -101,22 +102,6 @@ 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 () => {
Expand Down Expand Up @@ -195,6 +180,8 @@ export async function activate(
) || [];


let websiteURL = "";

_context.subscriptions.push(
orgChangeEvent(async (orgDetails: ActiveOrgOutput) => {
const orgID = orgDetails.OrgId;
Expand Down Expand Up @@ -249,6 +236,10 @@ export async function activate(
copilotNotificationShown = true;

}
if(artemisResponse!==null && PreviewSite.isSiteRuntimePreviewEnabled()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: whitespace

Suggested change
if(artemisResponse!==null && PreviewSite.isSiteRuntimePreviewEnabled()) {
if(artemisResponse !== null && PreviewSite.isSiteRuntimePreviewEnabled()) {

websiteURL = await PreviewSite.getWebSiteURL(workspaceFolders, artemisResponse?.stamp, orgDetails.EnvironmentId, _telemetry);
}
await registerPreviewShowCommand();

})
);
Expand All @@ -270,6 +261,44 @@ export async function activate(
vscode.commands.executeCommand('setContext', 'powerpages.websiteYmlExists', false);
}

const registerPreviewShowCommand = async () => {
ashwani123p marked this conversation as resolved.
Show resolved Hide resolved
const isEnabled = PreviewSite.isSiteRuntimePreviewEnabled();

_telemetry.sendTelemetryEvent("EnableSiteRuntimePreview", {
isEnabled: isEnabled.toString(),
websiteURL: websiteURL
});
oneDSLoggerWrapper.getLogger().traceInfo("EnableSiteRuntimePreview", {
isEnabled: isEnabled.toString(),
websiteURL: websiteURL
});

if (!isEnabled || websiteURL === "") {
// 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();
}
)
);
} else {
_context.subscriptions.push(
vscode.commands.registerCommand(
"microsoft-powerapps-portals.preview-show",
ashwani123p marked this conversation as resolved.
Show resolved Hide resolved
() => PreviewSite.launchBrowserAndDevToolsWithinVsCode(websiteURL)
)
);
}
};

const workspaceFolderWatcher = vscode.workspace.onDidChangeWorkspaceFolders(handleWorkspaceFolderChange);
_context.subscriptions.push(workspaceFolderWatcher);

Expand Down
90 changes: 90 additions & 0 deletions src/client/runtimeSitePreview/LaunchJsonHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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<void> {

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'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we read the port number from configuration?

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));
}
}
}
106 changes: 106 additions & 0 deletions src/client/runtimeSitePreview/PreviewSite.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that other folders are following kebab case for naming. Can we rename this to runtime-site-preview to align with them?

Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*
* 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 * 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';
import { VSCODE_EXTENSION_GET_WEBSITE_RECORD_ID_EMPTY } from '../../common/services/TelemetryConstants';

export class PreviewSite {

static isSiteRuntimePreviewEnabled(): boolean {
const enableSiteRuntimePreview = ECSFeaturesClient.getConfig(EnableSiteRuntimePreview).enableSiteRuntimePreview

if(enableSiteRuntimePreview === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we just check like this? That'll cover both undefined and null.

Suggested change
if(enableSiteRuntimePreview === undefined) {
if(!enableSiteRuntimePreview)

return false;
}

return enableSiteRuntimePreview;
}

static async getWebSiteURL(workspaceFolders: WorkspaceFolder[], stamp: ServiceEndpointCategory, envId: string, telemetry: ITelemetry): Promise<string> {

const websiteRecordId = getWebsiteRecordID(workspaceFolders, telemetry);
if (!websiteRecordId) {
telemetry.sendTelemetryEvent(VSCODE_EXTENSION_GET_WEBSITE_RECORD_ID_EMPTY, {
websiteRecordId: websiteRecordId
});
return "";
}
const websiteDetails = await PPAPIService.getWebsiteDetailsByWebsiteRecordId(stamp, envId, websiteRecordId, telemetry);
return websiteDetails?.websiteUrl || "";
}

static async launchBrowserAndDevToolsWithinVsCode(webSitePreviewURL: string): Promise<void> {

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;

if (vscodeFolderPath && fs.existsSync(vscodeFolderPath)) {
vscodeFolderExisted = true;
if (launchJsonPath && fs.existsSync(launchJsonPath)) {
originalLaunchJsonContent = fs.readFileSync(launchJsonPath, 'utf8');
}
}

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Should we read the delay ms form config? Might be useful if we need to change it later.
  2. Does vscode.workspace.fs.writeFile not guarantee that the file is saved?
  3. If not, is it possible to listen for some save event from vscode instead of adding this delay? Delays like this generally lead to flaky behavior.

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 {
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?`,
'Install', 'Cancel'
));
Comment on lines +92 to +96
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this working or do we need to pass Install and Cancel as arguments to showWarningMessage?


if (install === vscode.l10n.t('Install')) {
// Open the Extensions view with the specific extension
vscode.commands.executeCommand('workbench.extensions.search', edgeToolsExtensionId);
}

return;
}
}
}
1 change: 1 addition & 0 deletions src/common/OneDSLoggerTelemetry/telemetry/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 10 additions & 0 deletions src/common/ecs-features/ecsFeatureGates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,13 @@ export const {
enablePowerpagesInGithubCopilot: false,
},
});

export const {
feature: EnableSiteRuntimePreview
} = getFeatureConfigs({
teamName: PowerPagesClientName,
description: 'Enable Site Runtime Preview in VS Code Desktop',
fallback: {
enableSiteRuntimePreview: false,
},
});
Loading