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

Merged
merged 13 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
9 changes: 7 additions & 2 deletions l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
},
"Enter the environment URL": "Enter the environment URL",
"Active auth profile is not found or has expired. To create a new auth profile, enter the environment URL.": "Active auth profile is not found or has expired. To create a new auth profile, enter the environment URL.",
"Website not found in the environment. Please check the credentials and root folder path.": "Website not found in the environment. Please check the credentials and root folder path.",
"Selection is empty.": "Selection is empty.",
"PREVIEW": "PREVIEW",
"Explain the following code snippet:": "Explain the following code snippet:",
Expand Down Expand Up @@ -83,9 +84,9 @@
"Hi! Power Pages lets you build secure, professional websites that you can quickly configure and publish across web browsers and devices.\n\nTo create your website, visit the [Power Pages](https://powerpages.microsoft.com/).\nReturn to this chat and @powerpages can help you write and edit your website code.": "Hi! Power Pages lets you build secure, professional websites that you can quickly configure and publish across web browsers and devices.\n\nTo create your website, visit the [Power Pages](https://powerpages.microsoft.com/).\nReturn to this chat and @powerpages can help you write and edit your website code.",
"Checking for active auth profile...": "Checking for active auth profile...",
"@PowerPages is not yet available in your region.": "@PowerPages is not yet available in your region.",
"Failed to get site content from NL2Site service": "Failed to get site content from NL2Site service",
"Generating webpages...": "Generating webpages...",
"Generating a new Power Pages site...": "Generating a new Power Pages site...",
"Failed to create a new Power Pages site. Please try again.": "Failed to create a new Power Pages site. Please try again.",
"Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": {
"message": "Select Folder for new PCF Control",
"comment": [
Expand Down Expand Up @@ -117,6 +118,11 @@
"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}",
"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 Expand Up @@ -219,7 +225,6 @@
},
"Confirm": "Confirm",
"Are you sure you want to clear all the Auth Profiles?": "Are you sure you want to clear all the Auth Profiles?",
"Cancel": "Cancel",
"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": {
"message": "Are you sure you want to delete the Auth Profile {0}-{1}?",
"comment": [
Expand Down
19 changes: 17 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 Expand Up @@ -449,6 +461,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID)</note>
<trans-unit id="++CODE++00dc171124ab430bbbaae51ec39dda1c5e7d045f382f56b1767d9e733292731c">
<source xml:lang="en">Webpage names should contain only letters, numbers, hyphens, or underscores.</source>
</trans-unit>
<trans-unit id="++CODE++4ed689987736ce09e17c67eb32f441ca7d0f7ed013a282aafd1ebcf302e6386b">
<source xml:lang="en">Website not found in the environment. Please check the credentials and root folder path.</source>
</trans-unit>
<trans-unit id="++CODE++e4bb03b399c07eeda658d87305435a58818cb5e0c3b76ae054d99564cf14ef6b">
<source xml:lang="en">What do you need help with?</source>
</trans-unit>
Expand Down
52 changes: 36 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 "./runtime-site-preview/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,9 @@ export async function activate(
copilotNotificationShown = true;

}
if(artemisResponse !== null && PreviewSite.isSiteRuntimePreviewEnabled()) {
websiteURL = await PreviewSite.getWebSiteURL(workspaceFolders, artemisResponse?.stamp, orgDetails.EnvironmentId, _telemetry);
}

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

const isEnabled = PreviewSite.isSiteRuntimePreviewEnabled();

_telemetry.sendTelemetryEvent("EnableSiteRuntimePreview", {
isEnabled: isEnabled.toString(),
websiteURL: websiteURL
});
oneDSLoggerWrapper.getLogger().traceInfo("EnableSiteRuntimePreview", {
isEnabled: isEnabled.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);
}
}
)
);

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

Expand Down
90 changes: 90 additions & 0 deletions src/client/runtime-site-preview/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'],
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));
}
}
}
108 changes: 108 additions & 0 deletions src/client/runtime-site-preview/PreviewSite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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) {
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));
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?`
),
vscode.l10n.t('Install'),
vscode.l10n.t('Cancel')
);

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
Loading
Loading