From 56607ecddb2423b0864e9fd7b3c77559a05cde5f Mon Sep 17 00:00:00 2001 From: amitjoshi438 <54068463+amitjoshi438@users.noreply.github.com> Date: Wed, 20 Sep 2023 22:27:10 +0530 Subject: [PATCH 01/22] [Copilot] Added orgId to login event log for copilot (#718) * Added orgId to login event * Added log for copilot not available --------- Co-authored-by: amitjoshi --- src/common/copilot/PowerPagesCopilot.ts | 9 +++++---- src/common/copilot/telemetry/telemetryConstants.ts | 5 +++-- src/web/client/common/authenticationProvider.ts | 6 +++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 4b4a6c5a..0a66a666 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -16,7 +16,7 @@ import { escapeDollarSign, getLastThreePartsOfFileName, getNonce, getUserName, o import { CESUserFeedback } from "./user-feedback/CESSurvey"; import { GetAuthProfileWatchPattern } from "../../client/lib/AuthPanelView"; import { ActiveOrgOutput } from "../../client/pac/PacTypes"; -import { CopilotWalkthroughEvent, CopilotCopyCodeToClipboardEvent, CopilotInsertCodeToEditorEvent, CopilotLoadedEvent, CopilotOrgChangedEvent, CopilotUserFeedbackThumbsDownEvent, CopilotUserFeedbackThumbsUpEvent, CopilotUserPromptedEvent, CopilotCodeLineCountEvent, CopilotClearChatEvent } from "./telemetry/telemetryConstants"; +import { CopilotWalkthroughEvent, CopilotCopyCodeToClipboardEvent, CopilotInsertCodeToEditorEvent, CopilotLoadedEvent, CopilotOrgChangedEvent, CopilotUserFeedbackThumbsDownEvent, CopilotUserFeedbackThumbsUpEvent, CopilotUserPromptedEvent, CopilotCodeLineCountEvent, CopilotClearChatEvent, CopilotNotAvailable } from "./telemetry/telemetryConstants"; import { sendTelemetryEvent } from "./telemetry/copilotTelemetry"; import { INTELLIGENCE_SCOPE_DEFAULT, PROVIDER_ID } from "../../web/client/common/constants"; import { getIntelligenceEndpoint } from "../ArtemisService"; @@ -243,7 +243,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { if (pacOutput && pacOutput.Status === PAC_SUCCESS) { this.handleOrgChangeSuccess.call(this, pacOutput.Results); - intelligenceAPIAuthentication(this.telemetry, sessionID).then(({ accessToken, user, userId }) => { + intelligenceAPIAuthentication(this.telemetry, sessionID, orgID).then(({ accessToken, user, userId }) => { this.intelligenceAPIAuthenticationHandler.call(this, accessToken, user, userId); }); @@ -262,7 +262,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { } const pacAuthCreateOutput = await showProgressWithNotification(AUTH_CREATE_MESSAGE, async () => { return await this._pacWrapper?.authCreateNewAuthProfileForOrg(userOrgUrl) }); pacAuthCreateOutput && pacAuthCreateOutput.Status === PAC_SUCCESS - ? intelligenceAPIAuthentication(this.telemetry, sessionID).then(({ accessToken, user, userId }) => + ? intelligenceAPIAuthentication(this.telemetry, sessionID, orgID).then(({ accessToken, user, userId }) => this.intelligenceAPIAuthenticationHandler.call(this, accessToken, user, userId) ) : vscode.window.showErrorMessage(AUTH_CREATE_FAILED); @@ -285,7 +285,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { } private async authenticateAndSendAPIRequest(data: string, activeFileParams: IActiveFileParams, orgID: string, telemetry: ITelemetry) { - return intelligenceAPIAuthentication(telemetry, sessionID) + return intelligenceAPIAuthentication(telemetry, sessionID, orgID) .then(async ({ accessToken, user, userId }) => { intelligenceApiToken = accessToken; userName = getUserName(user); @@ -327,6 +327,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { this.aibEndpoint = await getIntelligenceEndpoint(orgID, this.telemetry, sessionID); if (this.aibEndpoint === COPILOT_UNAVAILABLE) { + sendTelemetryEvent(this.telemetry, {eventName: CopilotNotAvailable, copilotSessionId: sessionID, orgId: orgID}); this.sendMessageToWebview({ type: 'Unavailable' }); } else { this.sendMessageToWebview({ type: 'Available' }); diff --git a/src/common/copilot/telemetry/telemetryConstants.ts b/src/common/copilot/telemetry/telemetryConstants.ts index 9ea77794..676646a0 100644 --- a/src/common/copilot/telemetry/telemetryConstants.ts +++ b/src/common/copilot/telemetry/telemetryConstants.ts @@ -30,5 +30,6 @@ export const CopilotResponseOkFailureEvent = 'CopilotResponseOkFailureEvent'; export const CopilotResponseFailureEventWithMessage = 'CopilotResponseFailureEventWithMessage'; export const CopilotCodeLineCountEvent = 'CopilotCodeLineCountEvent'; export const CopilotNotificationShown = 'CopilotNotificationShown'; -export const CopilotNotificationDoNotShowChecked = 'CopilotNotificationDoNotShowChecked' -export const CopilotNotificationDoNotShowUnchecked = 'CopilotNotificationDoNotShowUnchecked' +export const CopilotNotificationDoNotShowChecked = 'CopilotNotificationDoNotShowChecked'; +export const CopilotNotificationDoNotShowUnchecked = 'CopilotNotificationDoNotShowUnchecked'; +export const CopilotNotAvailable = 'CopilotNotAvailable'; diff --git a/src/web/client/common/authenticationProvider.ts b/src/web/client/common/authenticationProvider.ts index 0bfbc43a..495e253f 100644 --- a/src/web/client/common/authenticationProvider.ts +++ b/src/web/client/common/authenticationProvider.ts @@ -34,7 +34,7 @@ export function getCommonHeaders( } //Get access token for Intelligence API service -export async function intelligenceAPIAuthentication(telemetry: ITelemetry, sessionID: string, firstTimeAuth = false): Promise<{ accessToken: string, user: string, userId: string }> { +export async function intelligenceAPIAuthentication(telemetry: ITelemetry, sessionID: string, orgId: string, firstTimeAuth = false): Promise<{ accessToken: string, user: string, userId: string }> { let accessToken = ''; let user = ''; let userId = ''; @@ -54,13 +54,13 @@ export async function intelligenceAPIAuthentication(telemetry: ITelemetry, sessi } if (firstTimeAuth) { - sendTelemetryEvent(telemetry, { eventName: CopilotLoginSuccessEvent, copilotSessionId: sessionID }); + sendTelemetryEvent(telemetry, { eventName: CopilotLoginSuccessEvent, copilotSessionId: sessionID, orgId: orgId }); } } catch (error) { const authError = (error as 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, error: authError }); + sendTelemetryEvent(telemetry, { eventName: CopilotLoginFailureEvent, copilotSessionId: sessionID, orgId: orgId, error: authError }); } return { accessToken, user, userId }; } From 26364d9701b5de48969b333b89474200293d1280 Mon Sep 17 00:00:00 2001 From: amitjoshi438 <54068463+amitjoshi438@users.noreply.github.com> Date: Wed, 20 Sep 2023 22:32:27 +0530 Subject: [PATCH 02/22] [copilot] Updated responsive behavior of notification panel (#719) * star icon size fix * enhancing responsiveness of webview --------- Co-authored-by: amitjoshi --- src/common/copilot/assets/scripts/copilot.js | 6 ++++++ src/common/copilot/assets/styles/copilot.css | 17 +++++++++++------ .../CopilotNotificationPanel.ts | 2 ++ .../copilotNotification.css | 15 ++++++++------- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/common/copilot/assets/scripts/copilot.js b/src/common/copilot/assets/scripts/copilot.js index 4b88864d..92f0524d 100644 --- a/src/common/copilot/assets/scripts/copilot.js +++ b/src/common/copilot/assets/scripts/copilot.js @@ -266,17 +266,23 @@ suggestedPrompt.innerHTML = `

Here are a few suggestions to get you started

+ ${starIconSvg} + ${formPrompt}
+ ${starIconSvg} + ${webApiPrompt}
+ ${starIconSvg} + ${listPrompt} `; diff --git a/src/common/copilot/assets/styles/copilot.css b/src/common/copilot/assets/styles/copilot.css index a1488e26..5380351c 100644 --- a/src/common/copilot/assets/styles/copilot.css +++ b/src/common/copilot/assets/styles/copilot.css @@ -76,6 +76,8 @@ body { .disclaimer { margin: 0px; margin-top: 12px; + color: var(--vscode-titleBar-inactiveForeground); + font-size: 12px; } .send-icon, @@ -96,12 +98,12 @@ body { .user-message { padding: 12px; - background-color: var(--vscode-editor-background); + background-color: var(--vscode-sideBar-background); } .api-response { padding: 12px; - background-color: var(--vscode-sideBar-background); + background-color: var(--vscode-editor-background); } .message { @@ -153,8 +155,8 @@ body { padding: 20px 5px 1px 10px; margin: 10px 0px; border-radius: 4px; - background-color: var(--vscode-editor-background); - + background-color: var(--vscode-sideBar-background) + } .code-pre { @@ -239,6 +241,7 @@ li:before { .feedback-statement { margin: 0 10px 0 0; + font-size: 11px; } .feedback-icons { @@ -305,6 +308,8 @@ hr { margin: 16px 0px; } -.star-icon { - margin-right: 4px; + +.icon-container { + width: 14px; /* Set a fixed width for the icon */ + margin-right: 4px; /* Add some spacing between icon and text */ } \ No newline at end of file diff --git a/src/common/copilot/welcome-notification/CopilotNotificationPanel.ts b/src/common/copilot/welcome-notification/CopilotNotificationPanel.ts index ff065c6e..ddbbac1e 100644 --- a/src/common/copilot/welcome-notification/CopilotNotificationPanel.ts +++ b/src/common/copilot/welcome-notification/CopilotNotificationPanel.ts @@ -109,7 +109,9 @@ function getWebviewContent(notificationCssUri: vscode.Uri, notificationJsUri: vs Learn more about Copilot +
Image +
diff --git a/src/common/copilot/welcome-notification/copilotNotification.css b/src/common/copilot/welcome-notification/copilotNotification.css index 87ca13f4..bf1f441f 100644 --- a/src/common/copilot/welcome-notification/copilotNotification.css +++ b/src/common/copilot/welcome-notification/copilotNotification.css @@ -15,9 +15,9 @@ body { /* flex-direction: column; */ align-items: center; justify-content: space-between; - margin-left: 32px; - margin-right: 32px; - margin-top: 159px; + margin-left: 2rem; + margin-right: 2rem; + margin-top: 2rem; } .container-text { @@ -26,14 +26,15 @@ body { } /* Media query for smaller screens */ -@media (max-width: 650px) { +@media (max-width: 450px) { .container { flex-direction: column; /* Stack elements on top of each other */ align-items: center; + margin-top: 3rem; } .container-text { - margin-right: 0; /* Remove margin for smaller screens */ + margin: 0rem 3rem; } } @@ -65,7 +66,7 @@ button { .walkthrough-content { padding: 6px 0px 6px 0px; - margin-bottom: 68px; + margin-bottom: 0px; text-decoration: none; display: block; align-items: center; @@ -77,7 +78,7 @@ button { align-items: center; justify-content: center; margin-bottom: 30px; - margin-top: 180px; + margin-top: 3rem; } #arrow-icon { From 91ef19f99ba0a79460c15f8b0e0cefd7ff4647a7 Mon Sep 17 00:00:00 2001 From: Crash Collison <3751389+tehcrashxor@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:15:33 -0700 Subject: [PATCH 03/22] August Refresh (#704) * Update PAC version and start release notes * localization files not yet extracted by ci build * Bump PAC and Extension versions * Updated release notes --------- Co-authored-by: tyaginidhi --- README.md | 5 +++++ gulpfile.mjs | 2 +- l10n/bundle.l10n.de.json | 1 + l10n/bundle.l10n.fr.json | 1 + l10n/bundle.l10n.it.json | 1 + l10n/bundle.l10n.json | 10 +++++----- l10n/bundle.l10n.tr.json | 1 + 7 files changed, 15 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 093c56d2..102bdfc3 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ Installing this extension will also make the latest Power Platform CLI (aka pac) [Power Platform CLI Exposed](https://www.youtube.com/playlist?list=PLlrxD0HtieHhEdLHxQOU96ySSZpMCyAxf) ## Release Notes + +2.0.10: + - pac CLI 1.27.5 (Auguest refresh, see release notes on [nuget.org](https://www.nuget.org/packages/Microsoft.PowerApps.CLI/)) + - (Preview) Added 'Copilot in Power Pages' to Web Extension + 2.0.7: - QFE for styling files load for new data model schema diff --git a/gulpfile.mjs b/gulpfile.mjs index 04422778..50399608 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -368,7 +368,7 @@ async function snapshot() { } const feedName = 'CAP_ISVExp_Tools_Stable'; -const cliVersion = '1.26.6'; +const cliVersion = '1.27.5'; const recompile = gulp.series( clean, diff --git a/l10n/bundle.l10n.de.json b/l10n/bundle.l10n.de.json index 87c34e59..81956104 100644 --- a/l10n/bundle.l10n.de.json +++ b/l10n/bundle.l10n.de.json @@ -63,6 +63,7 @@ "Profile Kind: {0}/The {0} represents the profile type (Admin vs Dataverse)": "Profil-Variante: {0}", "Resource: {0}/The {0} represents profile's resource/environment URL": "Ressource: {0}", "Saving your file ...": "Datei wird gespeichert ...", + "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Ordner für neues PCF-Steuerelement auswählen", "Select Type": "Typ auswählen", "The Power Pages generator is ready for use in your VS Code extension!": "Der Power Pages-Generator ist in Ihrer VS Code-Erweiterung zur Verwendung bereit!", "The name you want to give to this authentication profile": "Der Name, den Sie diesem Authentifizierungsprofil geben möchten", diff --git a/l10n/bundle.l10n.fr.json b/l10n/bundle.l10n.fr.json index 4125094f..cbece89d 100644 --- a/l10n/bundle.l10n.fr.json +++ b/l10n/bundle.l10n.fr.json @@ -63,6 +63,7 @@ "Profile Kind: {0}/The {0} represents the profile type (Admin vs Dataverse)": "Type de profil : {0}", "Resource: {0}/The {0} represents profile's resource/environment URL": "Ressource : {0}", "Saving your file ...": "Enregistrement de votre fichier en cours...", + "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Sélectionner un dossier pour le nouveau contrôle PCF", "Select Type": "Sélectionner le type", "The Power Pages generator is ready for use in your VS Code extension!": "Le générateur Power Pages est prêt à être utilisé dans votre extension VS Code !", "The name you want to give to this authentication profile": "Nom que vous souhaitez attribuer à ce profil d’authentification", diff --git a/l10n/bundle.l10n.it.json b/l10n/bundle.l10n.it.json index 5cd12d3f..9d5293b2 100644 --- a/l10n/bundle.l10n.it.json +++ b/l10n/bundle.l10n.it.json @@ -63,6 +63,7 @@ "Profile Kind: {0}/The {0} represents the profile type (Admin vs Dataverse)": "Tipo di profilo: {0}", "Resource: {0}/The {0} represents profile's resource/environment URL": "Risorsa: {0}", "Saving your file ...": "Salvataggio del file in corso...", + "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Seleziona cartella per nuovo controllo PCF", "Select Type": "Seleziona tipo", "The Power Pages generator is ready for use in your VS Code extension!": "Il generatore Power Pages è pronto per l'utilizzo nell'estensione VS Code.", "The name you want to give to this authentication profile": "Il nome che vuoi assegnare a questo profilo di autenticazione", diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index f1384b34..5e4beea7 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -5,6 +5,8 @@ "Edit the site": "Edit the site", "Be careful making changes. Anyone can see the changes you make immediately. Choose Edit the site to make edits, or close the editor tab to cancel without editing.": "Be careful making changes. Anyone can see the changes you make immediately. Choose Edit the site to make edits, or close the editor tab to cancel without editing.", "You are editing a live, public site ": "You are editing a live, public site ", + "Get help writing code in HTML, CSS, and JS languages for Power Pages sites with Copilot.": "Get help writing code in HTML, CSS, and JS languages for Power Pages sites with Copilot.", + "Try Copilot for Power Pages": "Try Copilot for Power Pages", "Microsoft wants your feeback": "Microsoft wants your feeback", "Check the URL and verify the parameters are correct": "Check the URL and verify the parameters are correct", "Unable to complete the request": "Unable to complete the request", @@ -33,8 +35,6 @@ "Do not translate 'PCF' as it is a product name." ] }, - "Get help writing code in HTML, CSS, and JS languages for Power Pages sites with Copilot.": "Get help writing code in HTML, CSS, and JS languages for Power Pages sites with Copilot.", - "Try Copilot for Power Pages": "Try Copilot for Power Pages", "Preparing pac CLI (v{0}).../{0} represents the version number": { "message": "Preparing pac CLI (v{0})...", "comment": [ @@ -66,6 +66,9 @@ "{0} represents the name of the file" ] }, + "Enter the name of the web template": "Enter the name of the web template", + "Please enter a name for the web template.": "Please enter a name for the web template.", + "A webtemplate with the same name already exists. Please enter a different name.": "A webtemplate with the same name already exists. Please enter a different name.", "No page templates found": "No page templates found", "No webpages found": "No webpages found", "New Webpage": "New Webpage", @@ -78,9 +81,6 @@ "File(s) already exist. No new files to add": "File(s) already exist. No new files to add", "Web files": "Web files", "Webfile(s) added successfully": "Webfile(s) added successfully", - "Enter the name of the web template": "Enter the name of the web template", - "Please enter a name for the web template.": "Please enter a name for the web template.", - "A webtemplate with the same name already exists. Please enter a different name.": "A webtemplate with the same name already exists. Please enter a different name.", "Page Template name cannot be empty.": "Page Template name cannot be empty.", "New Page Template": "New Page Template", "Choose web template": "Choose web template", diff --git a/l10n/bundle.l10n.tr.json b/l10n/bundle.l10n.tr.json index 0b5ca2de..5048bb52 100644 --- a/l10n/bundle.l10n.tr.json +++ b/l10n/bundle.l10n.tr.json @@ -63,6 +63,7 @@ "Profile Kind: {0}/The {0} represents the profile type (Admin vs Dataverse)": "Profil Türü: {0}", "Resource: {0}/The {0} represents profile's resource/environment URL": "Kaynak: {0}", "Saving your file ...": "Dosyanız kaydediliyor...", + "Select Folder for new PCF Control/Do not translate 'PCF' as it is a product name.": "Yeni PCF Denetimi için Klasör Seçin", "Select Type": "Tür Seç", "The Power Pages generator is ready for use in your VS Code extension!": "Power Pages oluşturucusu, VS Code uzantınızda kullanıma hazır!", "The name you want to give to this authentication profile": "Bu kimlik doğrulama profiline vermek istediğiniz ad", From 349a78d49953d1e814297a4b89a801bb03b86024 Mon Sep 17 00:00:00 2001 From: amitjoshi438 <54068463+amitjoshi438@users.noreply.github.com> Date: Tue, 26 Sep 2023 10:47:40 +0530 Subject: [PATCH 04/22] fixed feedback div color (#723) Co-authored-by: amitjoshi --- src/common/copilot/assets/styles/copilot.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/copilot/assets/styles/copilot.css b/src/common/copilot/assets/styles/copilot.css index 5380351c..953a125d 100644 --- a/src/common/copilot/assets/styles/copilot.css +++ b/src/common/copilot/assets/styles/copilot.css @@ -156,7 +156,7 @@ body { margin: 10px 0px; border-radius: 4px; background-color: var(--vscode-sideBar-background) - + } .code-pre { @@ -236,7 +236,7 @@ li:before { align-items: center; justify-content: space-between; padding: 6px 20px 12px 20px; - background-color: var(--vscode-sideBar-background); + background-color: var(--vscode-editor-background); } .feedback-statement { @@ -312,4 +312,4 @@ hr { .icon-container { width: 14px; /* Set a fixed width for the icon */ margin-right: 4px; /* Add some spacing between icon and text */ -} \ No newline at end of file +} From 8bb5a03ebc734ba21019d648b02d591fddeec12e Mon Sep 17 00:00:00 2001 From: amitjoshi438 <54068463+amitjoshi438@users.noreply.github.com> Date: Wed, 27 Sep 2023 10:16:02 +0530 Subject: [PATCH 05/22] Added copilot to editor context menu (#724) Co-authored-by: amitjoshi --- package.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/package.json b/package.json index 760266fd..24cc5260 100644 --- a/package.json +++ b/package.json @@ -326,6 +326,11 @@ "category": "Copilot In Power Pages", "title": "Clear Conversation", "icon": "$(clear-all)" + }, + { + "command": "powerpages.copilot.explain", + "category": "Copilot In Power Pages", + "title": "Explain" } ], "configuration": { @@ -559,6 +564,19 @@ } ], "menus": { + "editor/context": [ + { + "submenu": "microsoft-powerapps-portals.powerpages-copilot", + "group": "0_powerpages-copilot", + "when": "never" + } + ], + "microsoft-powerapps-portals.powerpages-copilot": [ + { + "command": "powerpages.copilot.explain", + "group": "0_powerpages-copilot@1" + } + ], "explorer/context": [ { "submenu": "microsoft-powerapps-portals.powerpages", @@ -713,6 +731,10 @@ "command": "powerpages.copilot.clearConversation", "when": "never" }, + { + "command": "powerpages.copilot.explain", + "when": "never" + }, { "command": "microsoft-powerapps-portals.webpage", "when": "config.powerPlatform.generatorInstalled" @@ -826,6 +848,10 @@ { "id": "microsoft-powerapps-portals.powerpages", "label": "Power Pages" + }, + { + "id": "microsoft-powerapps-portals.powerpages-copilot", + "label": "Copilot In Power Pages" } ], "viewsContainers": { From 91823a7ddb1d486014d498f4a10e91341cc89851 Mon Sep 17 00:00:00 2001 From: tyaginidhi Date: Thu, 28 Sep 2023 10:04:59 +0530 Subject: [PATCH 06/22] Do not show welcome banner in non-us orgs (#725) --- src/common/copilot/PowerPagesCopilot.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 0a66a666..8e5c1147 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -140,9 +140,9 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { const pacOutput = await this._pacWrapper?.activeOrg(); if (pacOutput && pacOutput.Status === PAC_SUCCESS) { - this.handleOrgChangeSuccess(pacOutput.Results); + await this.handleOrgChangeSuccess(pacOutput.Results); } else if (!IS_DESKTOP && orgID && activeOrgUrl) { - this.handleOrgChangeSuccess({ OrgId: orgID, UserId: userID, OrgUrl: activeOrgUrl } as ActiveOrgOutput); + await this.handleOrgChangeSuccess({ OrgId: orgID, UserId: userID, OrgUrl: activeOrgUrl } as ActiveOrgOutput); } webviewView.webview.html = this._getHtmlForWebview(webviewView.webview); @@ -150,6 +150,11 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { webviewView.webview.onDidReceiveMessage(async (data) => { switch (data.type) { case "webViewLoaded": { + if (this.aibEndpoint === COPILOT_UNAVAILABLE) { + this.sendMessageToWebview({ type: 'Unavailable' }); + return; + } + sendTelemetryEvent(this.telemetry, { eventName: CopilotLoadedEvent, copilotSessionId: sessionID, orgId: orgID }); this.sendMessageToWebview({ type: 'env' }); //TODO Use IS_DESKTOP await this.checkAuthentication(); @@ -327,7 +332,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { this.aibEndpoint = await getIntelligenceEndpoint(orgID, this.telemetry, sessionID); if (this.aibEndpoint === COPILOT_UNAVAILABLE) { - sendTelemetryEvent(this.telemetry, {eventName: CopilotNotAvailable, copilotSessionId: sessionID, orgId: orgID}); + sendTelemetryEvent(this.telemetry, { eventName: CopilotNotAvailable, copilotSessionId: sessionID, orgId: orgID }); this.sendMessageToWebview({ type: 'Unavailable' }); } else { this.sendMessageToWebview({ type: 'Available' }); From af604dc1805dc1f83c635be460ac1960916168c8 Mon Sep 17 00:00:00 2001 From: Andrew Petrochuk <30735471+petrochuk@users.noreply.github.com> Date: Mon, 2 Oct 2023 11:12:38 -0700 Subject: [PATCH 07/22] CVE-2023-43646 in get-func-name 2.0.0 (#728) Co-authored-by: Andrew Petrochuk --- package-lock.json | 7 ++++--- package.json | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index e2515662..b815d770 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,7 @@ "eslint-plugin-header": "^3.1.1", "fancy-log": "^1.3.3", "fs-extra": "^9.0.1", + "get-func-name": "^2.0.2", "gulp": "^4.0.2", "gulp-eslint": "^6.0.0", "gulp-filter": "^7.0.0", @@ -6276,9 +6277,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, "engines": { "node": "*" diff --git a/package.json b/package.json index 24cc5260..640f61c6 100644 --- a/package.json +++ b/package.json @@ -982,6 +982,7 @@ "eslint-plugin-header": "^3.1.1", "fancy-log": "^1.3.3", "fs-extra": "^9.0.1", + "get-func-name": "^2.0.2", "gulp": "^4.0.2", "gulp-eslint": "^6.0.0", "gulp-filter": "^7.0.0", From d1b9a3bbc9f8816a013d0fe61ba4ae1f016e55f4 Mon Sep 17 00:00:00 2001 From: tyaginidhi Date: Tue, 3 Oct 2023 23:53:01 +0530 Subject: [PATCH 08/22] Complete Navigation loop - add go to runtime and back to studio (#697) * Runtime preview and back to studio integration * update for new org and test calls to runtime * headers change * Update to config API * Add svg and make entire loop work * Add telemetry events * Make commands unavailable in command palette * Add localized strings for the explorer visible text * Localize provider command text --- l10n/bundle.l10n.json | 5 +- .../vscode-powerplatform.xlf | 18 ++- package.json | 21 ++- package.nls.json | 22 +-- src/web/client/WebExtensionContext.ts | 10 +- src/web/client/assets/backToStudio.svg | 3 + src/web/client/assets/powerPages.svg | 10 ++ src/web/client/common/constants.ts | 4 + src/web/client/extension.ts | 14 +- src/web/client/telemetry/constants.ts | 4 + src/web/client/utilities/commonUtil.ts | 21 ++- src/web/client/webViews/NPSWebView.ts | 5 +- .../webViews/powerPagesNavigationProvider.ts | 152 ++++++++++++++++++ 13 files changed, 259 insertions(+), 30 deletions(-) create mode 100644 src/web/client/assets/backToStudio.svg create mode 100644 src/web/client/assets/powerPages.svg create mode 100644 src/web/client/webViews/powerPagesNavigationProvider.ts diff --git a/l10n/bundle.l10n.json b/l10n/bundle.l10n.json index 5e4beea7..543eb689 100644 --- a/l10n/bundle.l10n.json +++ b/l10n/bundle.l10n.json @@ -5,8 +5,9 @@ "Edit the site": "Edit the site", "Be careful making changes. Anyone can see the changes you make immediately. Choose Edit the site to make edits, or close the editor tab to cancel without editing.": "Be careful making changes. Anyone can see the changes you make immediately. Choose Edit the site to make edits, or close the editor tab to cancel without editing.", "You are editing a live, public site ": "You are editing a live, public site ", - "Get help writing code in HTML, CSS, and JS languages for Power Pages sites with Copilot.": "Get help writing code in HTML, CSS, and JS languages for Power Pages sites with Copilot.", - "Try Copilot for Power Pages": "Try Copilot for Power Pages", + "Preview site": "Preview site", + "Open in Power Pages": "Open in Power Pages", + "Opening preview site...": "Opening preview site...", "Microsoft wants your feeback": "Microsoft wants your feeback", "Check the URL and verify the parameters are correct": "Check the URL and verify the parameters are correct", "Unable to complete the request": "Unable to complete the request", diff --git a/loc/translations-export/vscode-powerplatform.xlf b/loc/translations-export/vscode-powerplatform.xlf index 358a0994..a74eea10 100644 --- a/loc/translations-export/vscode-powerplatform.xlf +++ b/loc/translations-export/vscode-powerplatform.xlf @@ -121,9 +121,6 @@ The {3} represents Solution's Type (Managed or Unmanaged), but that test is loca File(s) already exist. No new files to add - - Get help writing code in HTML, CSS, and JS languages for Power Pages sites with Copilot. - Installing Power Pages generator(v{0})... {0} represents the version number @@ -182,6 +179,12 @@ The {3} represents Dataverse Environment's Organization ID (GUID) One or more attribute names have been changed or removed. Contact your admin. + + Open in Power Pages + + + Opening preview site... + PAC Telemetry disabled @@ -211,6 +214,9 @@ The {3} represents Dataverse Environment's Organization ID (GUID) Preparing pac CLI (v{0})... {0} represents the version number + + Preview site + Profile Kind: {0} The {0} represents the profile type (Admin vs Dataverse) @@ -247,9 +253,6 @@ The {3} represents Dataverse Environment's Organization ID (GUID) There’s a problem on the back end - - Try Copilot for Power Pages - Try again @@ -407,6 +410,9 @@ The second line should be '[TRANSLATION HERE](command:powerplatform-walkthrough. Overview + + POWER PAGES ACTIONS + Power Platform diff --git a/package.json b/package.json index 640f61c6..d7004a17 100644 --- a/package.json +++ b/package.json @@ -153,6 +153,16 @@ } ], "commands": [ + { + "command": "powerpages.powerPagesFileExplorer.powerPagesRuntimePreview", + "title": "Preview site", + "when": "never" + }, + { + "command": "powerpages.powerPagesFileExplorer.backToStudio", + "title": "Open in Power Pages", + "when": "never" + }, { "command": "extension.createChatView", "title": "Create Chat View" @@ -886,9 +896,12 @@ ], "explorer": [ { - "id": "powerpages.treeWebView", - "name": "Power Pages Actions", - "when": "isWeb && config.powerPlatform.experimental.enableCoPresenceFeature" + "id": "powerpages.powerPagesFileExplorer", + "name": "%microsoft-powerplatform-portals.navigation-loop.powerPagesFileExplorer.title%", + "when": "isWeb && virtualWorkspace", + "icon": "./src/web/client/assets/powerPages.svg", + "contextualTitle": "%microsoft-powerplatform-portals.navigation-loop.powerPagesFileExplorer.title%", + "visibility": "visible" } ] }, @@ -994,9 +1007,9 @@ "jwt-decode": "2.2.0", "mocha": "^9.2.2", "moment": "^2.29.4", + "nanoid": "^3.1.31", "node-fetch": "^2.6.7", "nyc": "^15.1.0", - "nanoid": "^3.1.31", "os-browserify": "^0.3.0", "path-browserify": "^1.0.1", "process": "^0.11.10", diff --git a/package.nls.json b/package.nls.json index 0ade7e5e..99f9a882 100644 --- a/package.nls.json +++ b/package.nls.json @@ -13,10 +13,10 @@ "pacCLI.authPanel.title": "Auth Profiles", "pacCLI.authPanel.welcome.whenInteractiveSupported": { - "message": "No auth profiles found on this computer.\n[Add Auth Profile](command:pacCLI.authPanel.newAuthProfile)", - "comment": [ - "This is a Markdown formatted string, and the formatting must persist across translations.", - "The second line should be '[TRANSLATION HERE](command:pacCLI.authPanel.newAuthProfile)', keeping brackets and the text in the parentheses unmodified" + "message": "No auth profiles found on this computer.\n[Add Auth Profile](command:pacCLI.authPanel.newAuthProfile)", + "comment": [ + "This is a Markdown formatted string, and the formatting must persist across translations.", + "The second line should be '[TRANSLATION HERE](command:pacCLI.authPanel.newAuthProfile)', keeping brackets and the text in the parentheses unmodified" ] }, "pacCLI.authPanel.welcome.whenInteractiveNotSupported": { @@ -25,8 +25,7 @@ "This is a Markdown formatted string, and the formatting must persist across translations.", "The second line should not translate the argument `--deviceCode`", "The third line should be '[TRANSLATION HERE](command:pacCLI.pacAuthHelp)', keeping brackets and the text in the parentheses unmodified" - - ] + ] }, "pacCLI.authPanel.clearAuthProfile.title": "Clear Auth Profiles", "pacCLI.authPanel.refresh.title": "Refresh", @@ -67,7 +66,7 @@ "comment": [ "This is a Markdown formatted string, and the formatting must persist across translations.", "The second line should be '[TRANSLATION HERE](command:powerplatform-walkthrough.overview-learn-more)', keeping brackets and the text in the parentheses unmodified" - ] + ] }, "microsoft-powerapps-portals.walkthrough.fileSystem.title": "File explorer", "microsoft-powerapps-portals.walkthrough.fileSystem.description": { @@ -76,7 +75,7 @@ "This is a Markdown formatted string, and the formatting must persist across translations.", "The seventh line should be '[TRANSLATION HERE](command:powerplatform-walkthrough.fileSystem-documentation).', keeping brackets and the text in the parentheses unmodified", "The eighth line should be '[TRANSLATION HERE](command:powerplatform-walkthrough.fileSystem-open-folder)', keeping brackets and the text in the parentheses unmodified" - ] + ] }, "microsoft-powerapps-portals.walkthrough.advancedCapabilities.title": "Advanced capabilities", "microsoft-powerapps-portals.walkthrough.advancedCapabilities.description": { @@ -85,7 +84,7 @@ "This is a Markdown formatted string, and the formatting must persist across translations.", "The fifth line should be '[TRANSLATION HERE](command:powerplatform-walkthrough.advancedCapabilities-learn-more) TRANSLATION', keeping brackets and the text in the parentheses unmodified", "The seventh line should be '[TRANSLATION HERE](command:powerplatform-walkthrough.advancedCapabilities-start-coding)', keeping brackets and the text in the parentheses unmodified" - ] + ] }, "microsoft-powerapps-portals.walkthrough.saveConflict.title": "Save conflict", "microsoft-powerapps-portals.walkthrough.saveConflict.description": { @@ -93,6 +92,7 @@ "comment": [ "This is a Markdown formatted string, and the formatting must persist across translations.", "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" } diff --git a/src/web/client/WebExtensionContext.ts b/src/web/client/WebExtensionContext.ts index 6d975b01..dccfa282 100644 --- a/src/web/client/WebExtensionContext.ts +++ b/src/web/client/WebExtensionContext.ts @@ -56,6 +56,7 @@ export interface IWebExtensionContext { defaultFileUri: vscode.Uri; // This will default to home page or current page in multifile scenario showMultifileInVSCode: boolean; extensionActivationTime: number; + extensionUri: vscode.Uri // Org specific details dataverseAccessToken: string; @@ -90,6 +91,7 @@ class WebExtensionContext implements IWebExtensionContext { private _defaultFileUri: vscode.Uri; private _showMultifileInVSCode: boolean; private _extensionActivationTime: number; + private _extensionUri: vscode.Uri; private _dataverseAccessToken: string; private _entityDataMap: EntityDataMap; private _isContextSet: boolean; @@ -149,6 +151,9 @@ class WebExtensionContext implements IWebExtensionContext { public get extensionActivationTime() { return this._extensionActivationTime } + public get extensionUri() { + return this._extensionUri + } public get dataverseAccessToken() { return this._dataverseAccessToken; } @@ -199,6 +204,7 @@ class WebExtensionContext implements IWebExtensionContext { this._defaultFileUri = vscode.Uri.parse(``); this._showMultifileInVSCode = false; this._extensionActivationTime = new Date().getTime(); + this._extensionUri = vscode.Uri.parse(""); this._isContextSet = false; this._currentSchemaVersion = ""; this._websiteLanguageCode = ""; @@ -212,7 +218,8 @@ class WebExtensionContext implements IWebExtensionContext { public setWebExtensionContext( entityName: string, entityId: string, - queryParamsMap: Map + queryParamsMap: Map, + extensionUri?: vscode.Uri ) { const schema = queryParamsMap.get(schemaKey.SCHEMA_VERSION) as string; // Initialize context from URL params @@ -227,6 +234,7 @@ class WebExtensionContext implements IWebExtensionContext { }/`, true ); + this._extensionUri = extensionUri as vscode.Uri; // Initialize multifile FF here const enableMultifile = queryParamsMap?.get(Constants.queryParameters.ENABLE_MULTIFILE); diff --git a/src/web/client/assets/backToStudio.svg b/src/web/client/assets/backToStudio.svg new file mode 100644 index 00000000..a7103f1b --- /dev/null +++ b/src/web/client/assets/backToStudio.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/web/client/assets/powerPages.svg b/src/web/client/assets/powerPages.svg new file mode 100644 index 00000000..a249689f --- /dev/null +++ b/src/web/client/assets/powerPages.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/web/client/common/constants.ts b/src/web/client/common/constants.ts index 960960ce..f11a2358 100644 --- a/src/web/client/common/constants.ts +++ b/src/web/client/common/constants.ts @@ -26,6 +26,8 @@ export const MAX_ENTITY_FETCH_COUNT = 100; export const MAX_CONCURRENT_REQUEST_COUNT = 50; export const MAX_CONCURRENT_REQUEST_QUEUE_COUNT = 1000; export const INTELLIGENCE_SCOPE_DEFAULT = "https://text.pai.dynamics.com/.default"; +export const BACK_TO_STUDIO_URL_TEMPLATE = "https://make{.region}.powerpages.microsoft.com/e/{environmentId}/sites/{webSiteId}/pages"; +export const STUDIO_PROD_REGION = "prod"; // Web extension constants export const BASE_64 = 'base64'; @@ -74,12 +76,14 @@ export enum queryParameters { ENV_ID = "envid", GEO = "geo", ENABLE_MULTIFILE = "enablemultifile", + WEBSITE_PREVIEW_URL = "websitepreviewurl" } export enum httpMethod { PATCH = "PATCH", GET = "GET", POST = "POST", + DELETE = "DELETE", } export enum SurveyConstants { diff --git a/src/web/client/extension.ts b/src/web/client/extension.ts index e419c466..42142cc1 100644 --- a/src/web/client/extension.ts +++ b/src/web/client/extension.ts @@ -30,6 +30,7 @@ import { } from "./utilities/fileAndEntityUtil"; import { IEntityInfo } from "./common/interfaces"; import { telemetryEventNames } from "./telemetry/constants"; +import { PowerPagesNavigationProvider } from "./webViews/powerPagesNavigationProvider"; import * as copilot from "../../common/copilot/PowerPagesCopilot"; import { IOrgInfo } from "../../common/copilot/model"; import { copilotNotificationPanel, disposeNotificationPanel } from "../../common/copilot/welcome-notification/CopilotNotificationPanel"; @@ -102,7 +103,8 @@ export function activate(context: vscode.ExtensionContext): void { WebExtensionContext.setWebExtensionContext( entity, entityId, - queryParamsMap + queryParamsMap, + context.extensionUri ); WebExtensionContext.setVscodeWorkspaceState(context.workspaceState); WebExtensionContext.telemetry.sendExtensionInitPathParametersTelemetry( @@ -123,6 +125,8 @@ export function activate(context: vscode.ExtensionContext): void { processWalkthroughFirstRunExperience(context); + powerPagesNavigation(); + await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, @@ -187,6 +191,14 @@ export function activate(context: vscode.ExtensionContext): void { showWalkthrough(context, WebExtensionContext.telemetry); } +export function powerPagesNavigation() { + const powerPagesNavigationProvider = new PowerPagesNavigationProvider(); + vscode.window.registerTreeDataProvider('powerpages.powerPagesFileExplorer', powerPagesNavigationProvider); + vscode.commands.registerCommand('powerpages.powerPagesFileExplorer.powerPagesRuntimePreview', () => powerPagesNavigationProvider.previewPowerPageSite()); + vscode.commands.registerCommand('powerpages.powerPagesFileExplorer.backToStudio', () => powerPagesNavigationProvider.backToStudio()); + WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.WEB_EXTENSION_POWER_PAGES_WEB_VIEW_REGISTERED); +} + export function processWalkthroughFirstRunExperience(context: vscode.ExtensionContext) { const isMultifileFirstRun = context.globalState.get( IS_MULTIFILE_FIRST_RUN_EXPERIENCE, diff --git a/src/web/client/telemetry/constants.ts b/src/web/client/telemetry/constants.ts index e7ca18d3..b2a2ade4 100644 --- a/src/web/client/telemetry/constants.ts +++ b/src/web/client/telemetry/constants.ts @@ -93,4 +93,8 @@ export enum telemetryEventNames { WEB_EXTENSION_WEB_COPILOT_REGISTRATION_FAILED = 'webExtensionCopilotRegisterFailed', WEB_EXTENSION_WEB_COPILOT_NOTIFICATION_SHOWN = 'webExtensionCopilotNotificationShown', WEB_EXTENSION_WEB_COPILOT_NOTIFICATION_EVENT_CLICKED = 'webExtensionCopilotNotificationEventClicked', + WEB_EXTENSION_POWER_PAGES_WEB_VIEW_REGISTERED = 'webExtensionPowerPagesWebViewRegistered', + WEB_EXTENSION_POWER_PAGES_WEB_VIEW_REGISTER_FAILED = 'webExtensionPowerPagesWebViewRegisterFailed', + WEB_EXTENSION_BACK_TO_STUDIO_TRIGGERED = 'webExtensionBackToStudioTriggered', + WEB_EXTENSION_PREVIEW_SITE_TRIGGERED = 'webExtensionPreviewSiteTriggered', } diff --git a/src/web/client/utilities/commonUtil.ts b/src/web/client/utilities/commonUtil.ts index eeb235be..aac3829c 100644 --- a/src/web/client/utilities/commonUtil.ts +++ b/src/web/client/utilities/commonUtil.ts @@ -5,13 +5,16 @@ import * as vscode from "vscode"; import { + BACK_TO_STUDIO_URL_TEMPLATE, BASE_64, CO_PRESENCE_FEATURE_SETTING_NAME, DATA, MULTI_FILE_FEATURE_SETTING_NAME, NO_CONTENT, + STUDIO_PROD_REGION, VERSION_CONTROL_FOR_WEB_EXTENSION_SETTING_NAME, - portalSchemaVersion + portalSchemaVersion, + queryParameters } from "../common/constants"; import { IAttributePath } from "../common/interfaces"; import { schemaEntityName } from "../schema/constants"; @@ -199,10 +202,24 @@ export function isPortalVersionV2(): boolean { return WebExtensionContext.currentSchemaVersion.toLowerCase() === portalSchemaVersion.V2; } -export function getWorkSpaceName(websiteId : string) : string { +export function getWorkSpaceName(websiteId: string): string { if (isPortalVersionV1()) { return `Site-v1-${websiteId}`; } else { return `Site-v2-${websiteId}`; } } + +// ENV_ID is the last part of the parameter value sent in the vscode URL from studio +export function getEnvironmentIdFromUrl() { + return (WebExtensionContext.urlParametersMap.get(queryParameters.ENV_ID) as string).split("/")?.pop() as string; +} + +export function getBackToStudioURL() { + const region = WebExtensionContext.urlParametersMap.get(queryParameters.REGION) as string; + + return BACK_TO_STUDIO_URL_TEMPLATE + .replace("{environmentId}", getEnvironmentIdFromUrl()) + .replace("{.region}", region.toLowerCase() === STUDIO_PROD_REGION ? "" : `.${WebExtensionContext.urlParametersMap.get(queryParameters.REGION) as string}`) + .replace("{webSiteId}", WebExtensionContext.urlParametersMap.get(queryParameters.WEBSITE_ID) as string); +} diff --git a/src/web/client/webViews/NPSWebView.ts b/src/web/client/webViews/NPSWebView.ts index 1cf3930c..35fcdb40 100644 --- a/src/web/client/webViews/NPSWebView.ts +++ b/src/web/client/webViews/NPSWebView.ts @@ -8,6 +8,7 @@ import WebExtensionContext from "../WebExtensionContext"; import { queryParameters } from "../common/constants"; import { getDeviceType } from "../utilities/deviceType"; import { telemetryEventNames } from "../telemetry/constants"; +import { getEnvironmentIdFromUrl } from "../utilities/commonUtil"; export class NPSWebView { private readonly _webviewPanel: vscode.WebviewPanel; @@ -27,9 +28,7 @@ export class NPSWebView { const tid = WebExtensionContext.urlParametersMap?.get( queryParameters.TENANT_ID ); - const envId = WebExtensionContext.urlParametersMap - ?.get(queryParameters.ENV_ID) - ?.split("/")[4]; + const envId = getEnvironmentIdFromUrl(); const geo = WebExtensionContext.urlParametersMap?.get( queryParameters.GEO ); diff --git a/src/web/client/webViews/powerPagesNavigationProvider.ts b/src/web/client/webViews/powerPagesNavigationProvider.ts new file mode 100644 index 00000000..34972a1b --- /dev/null +++ b/src/web/client/webViews/powerPagesNavigationProvider.ts @@ -0,0 +1,152 @@ +/* + * 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 WebExtensionContext from "../WebExtensionContext"; +import { httpMethod, queryParameters } from '../common/constants'; +import { getBackToStudioURL } from '../utilities/commonUtil'; +import { telemetryEventNames } from '../telemetry/constants'; + +export class PowerPagesNavigationProvider implements vscode.TreeDataProvider { + + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + + refresh(): void { + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: PowerPagesNode): vscode.TreeItem { + return element; + } + + getChildren(element?: PowerPagesNode): Thenable { + if (element) { + return Promise.resolve(this.getNodes(path.join(element.label))); + } else { + return Promise.resolve(this.getNodes()); + } + } + + getNodes(label?: string): PowerPagesNode[] { + const nodes: PowerPagesNode[] = []; + const previewPowerPage = new PowerPagesNode(vscode.l10n.t("Preview site"), + { + command: 'powerpages.powerPagesFileExplorer.powerPagesRuntimePreview', + title: vscode.l10n.t("Preview site"), + arguments: [] + }, + 'powerPages.svg'); + const backToStudio = new PowerPagesNode(vscode.l10n.t("Open in Power Pages"), + { + command: 'powerpages.powerPagesFileExplorer.backToStudio', + title: vscode.l10n.t("Open in Power Pages"), + arguments: [] + }, + 'backToStudio.svg'); + + if (label && label === previewPowerPage.label) { + nodes.push(previewPowerPage); + } else if (label && label === backToStudio.label) { + nodes.push(backToStudio); + } else { + nodes.push(previewPowerPage); + nodes.push(backToStudio); + } + + return nodes; + } + + async previewPowerPageSite(): Promise { + let requestSentAtTime = new Date().getTime(); + const websitePreviewUrl = WebExtensionContext.urlParametersMap.get(queryParameters.WEBSITE_PREVIEW_URL) as string; + // Runtime clear cache call + const requestUrl = `${websitePreviewUrl.endsWith('/') ? websitePreviewUrl : websitePreviewUrl.concat('/')}_services/cache/config`; + + WebExtensionContext.telemetry.sendAPITelemetry( + requestUrl, + "Preview power pages site", + httpMethod.DELETE, + this.previewPowerPageSite.name + ); + requestSentAtTime = new Date().getTime(); + WebExtensionContext.dataverseAuthentication(); + + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + cancellable: true, + title: vscode.l10n.t("Opening preview site..."), + }, + async () => { + const response = await WebExtensionContext.concurrencyHandler.handleRequest(requestUrl, { + headers: { + authorization: "Bearer " + WebExtensionContext.dataverseAccessToken, + 'Accept': '*/*', + 'Content-Type': 'text/plain', + }, + method: 'DELETE', + }); + + if (response.ok) { + WebExtensionContext.telemetry.sendAPISuccessTelemetry( + requestUrl, + "Preview power pages site", + httpMethod.DELETE, + new Date().getTime() - requestSentAtTime, + this.previewPowerPageSite.name + ); + } else { + WebExtensionContext.telemetry.sendAPIFailureTelemetry( + requestUrl, + "Preview power pages site", + httpMethod.DELETE, + new Date().getTime() - requestSentAtTime, + this.previewPowerPageSite.name, + JSON.stringify(response), + '', + response?.status.toString() + ); + } + + + } + ); + + vscode.env.openExternal(vscode.Uri.parse(websitePreviewUrl)); + WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.WEB_EXTENSION_PREVIEW_SITE_TRIGGERED); + } + + backToStudio(): void { + const backToStudioUrl = getBackToStudioURL(); + vscode.env.openExternal(vscode.Uri.parse(backToStudioUrl)); + + WebExtensionContext.telemetry.sendInfoTelemetry(telemetryEventNames.WEB_EXTENSION_BACK_TO_STUDIO_TRIGGERED, { + backToStudioUrl: backToStudioUrl + }); + } +} + +export class PowerPagesNode extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly command: vscode.Command, + public readonly svgFileName: string + ) { + super(label, vscode.TreeItemCollapsibleState.None); + + this.tooltip = this.label; + this.command = command; + this.iconPath = this.getIconPath(svgFileName); + } + + getIconPath(svgFileName: string) { + return { + light: vscode.Uri.joinPath(WebExtensionContext.extensionUri, '..', '..', 'src', 'web', 'client', 'assets', svgFileName), + dark: vscode.Uri.joinPath(WebExtensionContext.extensionUri, '..', '..', 'src', 'web', 'client', 'assets', svgFileName) + }; + } +} \ No newline at end of file From 2e047ffdfe17d48585d3e5e092c0f2e6b23c1388 Mon Sep 17 00:00:00 2001 From: amitjoshi438 <54068463+amitjoshi438@users.noreply.github.com> Date: Tue, 3 Oct 2023 23:59:17 +0530 Subject: [PATCH 09/22] [Copilot] Label for selected code (#727) * getting info about selected code * showing label for selected lines of code * add flag to disable feature * updated responsiveness * code formatting fix * removed log statement * handles empty selections --------- Co-authored-by: amitjoshi Co-authored-by: tyaginidhi --- src/common/Utils.ts | 124 +++++++++++-------- src/common/copilot/PowerPagesCopilot.ts | 20 ++- src/common/copilot/assets/scripts/copilot.js | 13 +- src/common/copilot/assets/styles/copilot.css | 20 ++- src/common/copilot/constants.ts | 1 + 5 files changed, 117 insertions(+), 61 deletions(-) diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 7ce9c19f..e2319713 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -7,94 +7,112 @@ import * as vscode from "vscode"; import { EXTENSION_ID } from "../client/constants"; -export function getSelectedSnippet(): string { - const editor = vscode.window.activeTextEditor; - if (!editor) { - return ""; - } - const selection = editor.selection; - const text = editor.document.getText(selection); - return text; +export function getSelectedCode(editor: vscode.TextEditor): string { + if (!editor) { + return ""; + } + const selection = editor.selection; + const text = editor.document.getText(selection); + return text.trim(); //handles empty selection +} + +/** + * Returns the start and end line numbers of the selected code in the editor. + * @param editor The vscode TextEditor object. + * @returns An object with start and end line numbers. + */ +export function getSelectedCodeLineRange(editor: vscode.TextEditor): { start: number, end: number } { + if (!editor) { + return { start: 0, end: 0 }; + } + // Get the selection(s) in the editor + const selection = editor.selection; + + const startLine = selection.start.line; + const endLine = selection.end.line; + + return { start: startLine, end: endLine }; } // Get the organization ID from the user during login export async function getOrgID(): Promise { - const orgID = await vscode.window.showInputBox({ - placeHolder: vscode.l10n.t("Enter Organization ID") - }); - if (!orgID) { - throw new Error("Organization ID is required"); - } - return Promise.resolve(orgID); + const orgID = await vscode.window.showInputBox({ + placeHolder: vscode.l10n.t("Enter Organization ID") + }); + if (!orgID) { + throw new Error("Organization ID is required"); + } + return Promise.resolve(orgID); } export function getNonce() { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; } export function getUserName(user: string) { - const parts = user.split(" - "); - return parts[0]; + const parts = user.split(" - "); + return parts[0]; } export function getLastThreePartsOfFileName(string: string): string[] { - const parts: string[] = string.split('.'); - if (parts.length >= 3) { - return parts.slice(-3); - } else { - return parts; - } + const parts: string[] = string.split('.'); + if (parts.length >= 3) { + return parts.slice(-3); + } else { + return parts; + } } export function escapeDollarSign(paragraph: string): string { - return paragraph.replace(/\$/g, "\\$"); + return paragraph.replace(/\$/g, "\\$"); } //TODO: Take message as a parameter export function showConnectedOrgMessage(environmentName: string, orgUrl: string) { - vscode.window.showInformationMessage( - vscode.l10n.t({ - message: "Power Pages Copilot is now connected to the environment: {0} : {1}", - args: [environmentName, orgUrl], - comment: ["{0} represents the environment name"] - }) - ); + vscode.window.showInformationMessage( + vscode.l10n.t({ + message: "Power Pages Copilot is now connected to the environment: {0} : {1}", + args: [environmentName, orgUrl], + comment: ["{0} represents the environment name"] + }) + ); } export async function showInputBoxAndGetOrgUrl() { - return vscode.window.showInputBox({ - placeHolder: vscode.l10n.t("Enter the environment URL"), - prompt: vscode.l10n.t("Active auth profile is not found or has expired. To create a new auth profile, enter the environment URL.") - }); + return vscode.window.showInputBox({ + placeHolder: vscode.l10n.t("Enter the environment URL"), + prompt: vscode.l10n.t("Active auth profile is not found or has expired. To create a new auth profile, enter the environment URL.") + }); } export async function showProgressWithNotification(title: string, task: () => Promise): Promise { - return await vscode.window.withProgress({ - location: vscode.ProgressLocation.Notification, - title: title, - cancellable: false - }, async () => { - return await task(); - }); + return await vscode.window.withProgress({ + location: vscode.ProgressLocation.Notification, + title: title, + cancellable: false + }, async () => { + return await task(); + }); } export function getExtensionVersion(): string { - const extension = vscode.extensions.getExtension(EXTENSION_ID); - return extension ? extension.packageJSON.version : ""; + const extension = vscode.extensions.getExtension(EXTENSION_ID); + return extension ? extension.packageJSON.version : ""; } export function getExtensionType(): string { - return vscode.env.uiKind === vscode.UIKind.Desktop ? "Desktop" : "Web"; + return vscode.env.uiKind === vscode.UIKind.Desktop ? "Desktop" : "Web"; } export function openWalkthrough(extensionUri: vscode.Uri) { - const walkthroughUri = vscode.Uri.joinPath(extensionUri, 'src', 'common', 'copilot', 'assets', 'walkthrough', 'Copilot-In-PowerPages.md'); - vscode.commands.executeCommand("markdown.showPreview", walkthroughUri); + const walkthroughUri = vscode.Uri.joinPath(extensionUri, 'src', 'common', 'copilot', 'assets', 'walkthrough', 'Copilot-In-PowerPages.md'); + vscode.commands.executeCommand("markdown.showPreview", walkthroughUri); } + diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 8e5c1147..1a3a8c6a 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -10,9 +10,9 @@ import { dataverseAuthentication, intelligenceAPIAuthentication } from "../../we import { v4 as uuidv4 } from 'uuid' import { PacWrapper } from "../../client/pac/PacWrapper"; import { ITelemetry } from "../../client/telemetry/ITelemetry"; -import { AUTH_CREATE_FAILED, AUTH_CREATE_MESSAGE, AuthProfileNotFound, COPILOT_UNAVAILABLE, CopilotDisclaimer, CopilotStylePathSegments, DataverseEntityNameMap, EntityFieldMap, FieldTypeMap, PAC_SUCCESS, WebViewMessage, sendIconSvg } from "./constants"; +import { AUTH_CREATE_FAILED, AUTH_CREATE_MESSAGE, AuthProfileNotFound, COPILOT_UNAVAILABLE, CopilotDisclaimer, CopilotStylePathSegments, DataverseEntityNameMap, EntityFieldMap, FieldTypeMap, PAC_SUCCESS, SELECTED_CODE_INFO_ENABLED, WebViewMessage, sendIconSvg } from "./constants"; import { IActiveFileParams, IActiveFileData, IOrgInfo } from './model'; -import { escapeDollarSign, getLastThreePartsOfFileName, getNonce, getUserName, openWalkthrough, showConnectedOrgMessage, showInputBoxAndGetOrgUrl, showProgressWithNotification } from "../Utils"; +import { escapeDollarSign, getLastThreePartsOfFileName, getNonce, getSelectedCode, getSelectedCodeLineRange, getUserName, openWalkthrough, showConnectedOrgMessage, showInputBoxAndGetOrgUrl, showProgressWithNotification } from "../Utils"; import { CESUserFeedback } from "./user-feedback/CESSurvey"; import { GetAuthProfileWatchPattern } from "../../client/lib/AuthPanelView"; import { ActiveOrgOutput } from "../../client/pac/PacTypes"; @@ -66,6 +66,21 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { ) ); + + if (SELECTED_CODE_INFO_ENABLED) { //TODO: Remove this check once the feature is ready + this._disposables.push( + vscode.window.onDidChangeTextEditorSelection(async () => { + const editor = vscode.window.activeTextEditor; + if (!editor) { + return; + } + const selectedCode = getSelectedCode(editor); + const selectedCodeLineRange = getSelectedCodeLineRange(editor); + this.sendMessageToWebview({ type: "selectedCodeInfo", value: {start: selectedCodeLineRange.start, end: selectedCodeLineRange.end, selectedCode: selectedCode} }); + }) + ); + } + if (this._pacWrapper) { this.setupFileWatcher(); } @@ -422,6 +437,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider {
+