From a8e909acc80ae10663285433b96ebad6e739ffac Mon Sep 17 00:00:00 2001 From: Priyanshu Agrawal Date: Thu, 12 Dec 2024 10:55:04 +0530 Subject: [PATCH 1/7] Remove unused command from package.json (#1070) Removing the command `extension.createChatView` from package.json as it is registered but there is no handler associated with it. --- package.json | 4 ---- 1 file changed, 4 deletions(-) diff --git a/package.json b/package.json index 67982961..ecadf0b2 100644 --- a/package.json +++ b/package.json @@ -191,10 +191,6 @@ "title": "Open in Power Pages", "when": "never" }, - { - "command": "extension.createChatView", - "title": "Create Chat View" - }, { "command": "pacCLI.openDocumentation", "category": "Power Platform CLI", From ea60e774c4f9eb2de43354e7bcbc5bcc6b480015 Mon Sep 17 00:00:00 2001 From: Priyanshu Agrawal Date: Thu, 12 Dec 2024 11:05:58 +0530 Subject: [PATCH 2/7] Fix error type for concurrency handler (#1071) Fixes the error throws in case of bulk header rejection --- src/web/client/dal/concurrencyHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/web/client/dal/concurrencyHandler.ts b/src/web/client/dal/concurrencyHandler.ts index 41ca6f28..0bca09d3 100644 --- a/src/web/client/dal/concurrencyHandler.ts +++ b/src/web/client/dal/concurrencyHandler.ts @@ -26,7 +26,7 @@ export class ConcurrencyHandler { this.handleRequest.name, this._bulkhead.executionSlots.toString(), ); - throw new Error(ERROR_CONSTANTS.SUBURI_EMPTY); + throw new Error(ERROR_CONSTANTS.BULKHEAD_LIMITS_EXCEEDED); } else { throw e; } From 134343df9618a4e58241f1e443e11bb3f7c3e1ff Mon Sep 17 00:00:00 2001 From: Priyanshu Agrawal Date: Thu, 12 Dec 2024 13:58:01 +0530 Subject: [PATCH 3/7] Add code coverage output folder in gitignore (#1072) Adding the code coverage output folder `.nyc_ouput` folder in `.gitignore` so that it doesn't show up in `git diff` after running code coverage command. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e6fd29f2..cd87b705 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ telemetry-generated # tasks copy them to the correct location, but leave the originals where # we do not want them checked in l10n/package.nls.*.json + +# Code Coverage Output +.nyc_output/ From 46d8cc03fc9adcd0b9fa04ddf6ba69fc9309ac67 Mon Sep 17 00:00:00 2001 From: Priyanshu Agrawal Date: Thu, 12 Dec 2024 19:06:35 +0530 Subject: [PATCH 4/7] Refactor CLI Acquisition Context (#1074) * Refactor CLI Acquisition Context * Fix import * Add tests --- src/client/extension.ts | 71 +--------- src/client/lib/CliAcquisition.ts | 17 +-- src/client/lib/CliAcquisitionContext.ts | 86 ++++++++++++ src/client/lib/GeneratorAcquisition.ts | 2 +- .../test/Integration/CliAcquisition.test.ts | 3 +- .../Integration/CliAcquisitionContext.test.ts | 125 ++++++++++++++++++ 6 files changed, 217 insertions(+), 87 deletions(-) create mode 100644 src/client/lib/CliAcquisitionContext.ts create mode 100644 src/client/test/Integration/CliAcquisitionContext.test.ts diff --git a/src/client/extension.ts b/src/client/extension.ts index 2f023c29..20e04ccf 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -9,10 +9,9 @@ import * as vscode from "vscode"; import { AppTelemetryConfigUtility } from "../common/pp-tooling-telemetry-node"; import { vscodeExtAppInsightsResourceProvider } from "../common/telemetry-generated/telemetryConfiguration"; import { ITelemetryData } from "../common/TelemetryData"; -import { CliAcquisition, ICliAcquisitionContext } from "./lib/CliAcquisition"; +import { CliAcquisition } from "./lib/CliAcquisition"; import { PacTerminal } from "./lib/PacTerminal"; import { PortalWebView } from "./PortalWebView"; -import { ITelemetry } from "../common/OneDSLoggerTelemetry/telemetry/ITelemetry"; import { LanguageClient, @@ -46,6 +45,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 { CliAcquisitionContext } from "./lib/CliAcquisitionContext"; let client: LanguageClient; let _context: vscode.ExtensionContext; @@ -473,70 +473,3 @@ function showNotificationForCopilot(telemetry: TelemetryReporter, telemetryData: } } - -// allow for DI without direct reference to vscode's d.ts file: that definintions file is being generated at VS Code runtime -class CliAcquisitionContext implements ICliAcquisitionContext { - public constructor( - private readonly _context: vscode.ExtensionContext, - private readonly _telemetry: ITelemetry - ) { } - - public get extensionPath(): string { - return this._context.extensionPath; - } - public get globalStorageLocalPath(): string { - return this._context.globalStorageUri.fsPath; - } - public get telemetry(): ITelemetry { - return this._telemetry; - } - - showInformationMessage(message: string, ...items: string[]): void { - vscode.window.showInformationMessage(message, ...items); - } - - showErrorMessage(message: string, ...items: string[]): void { - vscode.window.showErrorMessage(message, ...items); - } - - showCliPreparingMessage(version: string): void { - vscode.window.showInformationMessage( - vscode.l10n.t({ - message: "Preparing pac CLI (v{0})...", - args: [version], - comment: ["{0} represents the version number"] - }) - ); - } - - showCliReadyMessage(): void { - vscode.window.showInformationMessage( - vscode.l10n.t('The pac CLI is ready for use in your VS Code terminal!')); - } - - showCliInstallFailedError(err: string): void { - vscode.window.showErrorMessage( - vscode.l10n.t({ - message: "Cannot install pac CLI: {0}", - args: [err], - comment: ["{0} represents the error message returned from the exception"] - }) - ); - } - - showGeneratorInstallingMessage(version: string): void { - vscode.window.showInformationMessage( - vscode.l10n.t({ - message: "Installing Power Pages generator(v{0})...", - args: [version], - comment: ["{0} represents the version number"] - })) - } - - locDotnetNotInstalledOrInsufficient(): string { - return vscode.l10n.t({ - message: "dotnet sdk 6.0 or greater must be installed", - comment: ["Do not translate 'dotnet' or 'sdk'"] - }); - } -} diff --git a/src/client/lib/CliAcquisition.ts b/src/client/lib/CliAcquisition.ts index 531e7ef2..2642f012 100644 --- a/src/client/lib/CliAcquisition.ts +++ b/src/client/lib/CliAcquisition.ts @@ -10,31 +10,17 @@ import * as fs from 'fs-extra'; import * as glob from 'glob'; import * as os from 'os'; import { Extract } from 'unzip-stream' -import { ITelemetry } from '../../common/OneDSLoggerTelemetry/telemetry/ITelemetry'; import find from 'find-process'; import { spawnSync } from 'child_process'; import commandExists from 'command-exists'; import { oneDSLoggerWrapper } from '../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper'; - -// allow for DI without direct reference to vscode's d.ts file: that definintions file is being generated at VS Code runtime -export interface ICliAcquisitionContext { - readonly extensionPath: string; - readonly globalStorageLocalPath: string; - readonly telemetry: ITelemetry; - showInformationMessage(message: string, ...items: string[]): void; - showErrorMessage(message: string, ...items: string[]): void; - showCliPreparingMessage(version: string): void; - showCliReadyMessage(): void; - showCliInstallFailedError(err: string): void; - locDotnetNotInstalledOrInsufficient(): string; -} +import { ICliAcquisitionContext } from './CliAcquisitionContext'; export interface IDisposable { dispose(): void; } export class CliAcquisition implements IDisposable { - private readonly _context: ICliAcquisitionContext; private readonly _cliPath: string; private readonly _cliVersion: string; @@ -241,4 +227,3 @@ export class CliAcquisition implements IDisposable { } } } - diff --git a/src/client/lib/CliAcquisitionContext.ts b/src/client/lib/CliAcquisitionContext.ts new file mode 100644 index 00000000..77a165d2 --- /dev/null +++ b/src/client/lib/CliAcquisitionContext.ts @@ -0,0 +1,86 @@ +/* + * 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 { ITelemetry } from '../../common/OneDSLoggerTelemetry/telemetry/ITelemetry'; + +export interface ICliAcquisitionContext { + readonly extensionPath: string; + readonly globalStorageLocalPath: string; + readonly telemetry: ITelemetry; + showInformationMessage(message: string, ...items: string[]): void; + showErrorMessage(message: string, ...items: string[]): void; + showCliPreparingMessage(version: string): void; + showCliReadyMessage(): void; + showCliInstallFailedError(err: string): void; + locDotnetNotInstalledOrInsufficient(): string; +} + +// allow for DI without direct reference to vscode's d.ts file: that definintions file is being generated at VS Code runtime +export class CliAcquisitionContext implements ICliAcquisitionContext { + public constructor( + private readonly _context: vscode.ExtensionContext, + private readonly _telemetry: ITelemetry + ) { } + + public get extensionPath(): string { + return this._context.extensionPath; + } + public get globalStorageLocalPath(): string { + return this._context.globalStorageUri.fsPath; + } + public get telemetry(): ITelemetry { + return this._telemetry; + } + + showInformationMessage(message: string, ...items: string[]): void { + vscode.window.showInformationMessage(message, ...items); + } + + showErrorMessage(message: string, ...items: string[]): void { + vscode.window.showErrorMessage(message, ...items); + } + + showCliPreparingMessage(version: string): void { + vscode.window.showInformationMessage( + vscode.l10n.t({ + message: "Preparing pac CLI (v{0})...", + args: [version], + comment: ["{0} represents the version number"] + }) + ); + } + + showCliReadyMessage(): void { + vscode.window.showInformationMessage( + vscode.l10n.t('The pac CLI is ready for use in your VS Code terminal!')); + } + + showCliInstallFailedError(err: string): void { + vscode.window.showErrorMessage( + vscode.l10n.t({ + message: "Cannot install pac CLI: {0}", + args: [err], + comment: ["{0} represents the error message returned from the exception"] + }) + ); + } + + showGeneratorInstallingMessage(version: string): void { + vscode.window.showInformationMessage( + vscode.l10n.t({ + message: "Installing Power Pages generator(v{0})...", + args: [version], + comment: ["{0} represents the version number"] + })) + } + + locDotnetNotInstalledOrInsufficient(): string { + return vscode.l10n.t({ + message: "dotnet sdk 6.0 or greater must be installed", + comment: ["Do not translate 'dotnet' or 'sdk'"] + }); + } +} diff --git a/src/client/lib/GeneratorAcquisition.ts b/src/client/lib/GeneratorAcquisition.ts index 9321d673..e07c9aeb 100644 --- a/src/client/lib/GeneratorAcquisition.ts +++ b/src/client/lib/GeneratorAcquisition.ts @@ -11,7 +11,7 @@ import * as fs from 'fs-extra'; import * as os from 'os'; import * as path from 'path'; import { PORTAL_YEOMAN_GENERATOR_PACKAGE_NAME, PORTAL_YEOMAN_GENERATOR_PACKAGE_TARBALL_NAME } from '../../common/constants'; -import { ICliAcquisitionContext } from './CliAcquisition'; +import { ICliAcquisitionContext } from './CliAcquisitionContext'; import { glob } from 'glob'; import commandExists from 'command-exists'; import { oneDSLoggerWrapper } from '../../common/OneDSLoggerTelemetry/oneDSLoggerWrapper'; diff --git a/src/client/test/Integration/CliAcquisition.test.ts b/src/client/test/Integration/CliAcquisition.test.ts index 0280144f..7b236871 100644 --- a/src/client/test/Integration/CliAcquisition.test.ts +++ b/src/client/test/Integration/CliAcquisition.test.ts @@ -5,7 +5,8 @@ import * as fs from 'fs-extra'; import * as path from 'path'; -import { CliAcquisition, ICliAcquisitionContext } from '../../lib/CliAcquisition'; +import { CliAcquisition } from '../../lib/CliAcquisition'; +import { ICliAcquisitionContext } from '../../lib/CliAcquisitionContext'; import { expect } from 'chai'; import { ITelemetry } from '../../../common/OneDSLoggerTelemetry/telemetry/ITelemetry'; import { NoopTelemetryInstance } from '../../telemetry/NoopTelemetry'; diff --git a/src/client/test/Integration/CliAcquisitionContext.test.ts b/src/client/test/Integration/CliAcquisitionContext.test.ts new file mode 100644 index 00000000..b6f7b808 --- /dev/null +++ b/src/client/test/Integration/CliAcquisitionContext.test.ts @@ -0,0 +1,125 @@ +/* + * 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 { ITelemetry } from '../../../common/OneDSLoggerTelemetry/telemetry/ITelemetry'; +import { CliAcquisitionContext } from '../../lib/CliAcquisitionContext'; +import { expect } from 'chai'; +import sinon from 'sinon'; + +describe('CliAcquisitionContext', () => { + let context: vscode.ExtensionContext; + let telemetry: ITelemetry; + let showInformationMessageSpy: sinon.SinonSpy; + let showErrorMessageSpy: sinon.SinonSpy; + + beforeEach(() => { + context = { + extensionPath: 'testExtensionPath', + globalStorageUri: { + fsPath: 'testGlobalStorageUri' + } + } as vscode.ExtensionContext; + + telemetry = {} as ITelemetry; + + showInformationMessageSpy = sinon.spy(vscode.window, "showInformationMessage"); + showErrorMessageSpy = sinon.spy(vscode.window, "showErrorMessage"); + }); + + afterEach(() => { + showInformationMessageSpy.restore(); + showErrorMessageSpy.restore(); + }); + + it('should return the extension path', () => { + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + expect(cliAcquisitionContext.extensionPath).to.equal('testExtensionPath'); + }); + + it('should return the global storage local path', () => { + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + expect(cliAcquisitionContext.globalStorageLocalPath).to.equal('testGlobalStorageUri'); + }); + + it('should return the telemetry', () => { + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + expect(cliAcquisitionContext.telemetry).to.equal(telemetry); + }); + + it('should show information message', () => { + const message = 'testMessage'; + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + cliAcquisitionContext.showInformationMessage(message); + + const showInformationMessageArgs = showInformationMessageSpy.getCalls()[0].args; + + expect(showInformationMessageArgs[0]).eq("testMessage"); + }); + + it('should show error message', () => { + const message = 'testMessage'; + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + cliAcquisitionContext.showErrorMessage(message); + + const showErrorMessageArgs = showErrorMessageSpy.getCalls()[0].args; + + expect(showErrorMessageArgs[0]).eq("testMessage"); + }); + + it('should show cli preparing message', () => { + const version = 'testVersion'; + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + cliAcquisitionContext.showCliPreparingMessage(version); + + const showInformationMessageArgs = showInformationMessageSpy.getCalls()[0].args; + + expect(showInformationMessageArgs[0]).eq("Preparing pac CLI (vtestVersion)..."); + }); + + it('should show cli ready message', () => { + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + cliAcquisitionContext.showCliReadyMessage(); + + const showInformationMessageArgs = showInformationMessageSpy.getCalls()[0].args; + + expect(showInformationMessageArgs[0]).eq("The pac CLI is ready for use in your VS Code terminal!"); + }); + + it('should show cli install failed error', () => { + const err = 'testError'; + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + cliAcquisitionContext.showCliInstallFailedError(err); + + const showErrorMessageArgs = showErrorMessageSpy.getCalls()[0].args; + + expect(showErrorMessageArgs[0]).eq("Cannot install pac CLI: testError"); + }); + + it('should show generator installing message', () => { + const version = 'testVersion'; + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + cliAcquisitionContext.showGeneratorInstallingMessage(version); + + const showInformationMessageArgs = showInformationMessageSpy.getCalls()[0].args; + + expect(showInformationMessageArgs[0]).eq("Installing Power Pages generator(vtestVersion)..."); + }); + + it('should return loc dotnet not installed or insufficient', () => { + const cliAcquisitionContext = new CliAcquisitionContext(context, telemetry); + + expect(cliAcquisitionContext.locDotnetNotInstalledOrInsufficient()).eq("dotnet sdk 6.0 or greater must be installed"); + }); +}); From c115f296ee710298c4a1a72f97e56efe7fc9bae6 Mon Sep 17 00:00:00 2001 From: amitjoshi438 <54068463+amitjoshi438@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:42:07 +0530 Subject: [PATCH 5/7] [PowerPages] [Create-Site] Implement multi-step site creation with enhanced input handling (#1063) * Enhance CreateSiteCommand to include extension context and add ReadonlyFileSystemProvider for site page previews * Implement EditableFileSystemProvider for site page editing and update CreateSiteHelper to utilize it * Integrate CreateSiteCommand into CommandRegistry and update related components for site creation functionality * Disable copy functionality in EditableFileSystemProvider implementation * Remove ReadonlyFileSystemProvider implementation * Add telemetry constant for previewing site pages and refactor related components * Refactor CommandRegistry and add command registration utility for chat participants * Add constants for site creation parameters and refactor NL2SiteService to use them * Refactor CreateSiteCommand and CreateSiteHelper to use structured options and improve readability; add CreateSiteTypes for better type management * Add error telemetry constant for previewing site pages and handle errors in previewSitePagesContent function * Rename fileContentMap to _fileContentMap for consistency and clarity in EditableFileSystemProvider * Implement multi-step input for site creation and register command for user inputs in CreateSiteHelper * Enhance copilot availability checks and update response structure in ArtemisService * Add site creation inputs and environment info interfaces; refactor CreateSiteCommand * Update src/common/chat-participants/powerpages/commands/create-site/CreateSiteTypes.ts Co-authored-by: Priyanshu Agrawal * Update src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts Co-authored-by: Priyanshu Agrawal * Update src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts Co-authored-by: Priyanshu Agrawal * Add eslint directives to suppress no-explicit-any warnings in site page handling --------- Co-authored-by: amitjoshi Co-authored-by: Priyanshu Agrawal --- .../powerpages/PowerPagesChatParticipant.ts | 10 ++- .../PowerPagesChatParticipantUtils.ts | 22 +++++ .../commands/create-site/CreateSiteCommand.ts | 2 +- .../create-site/CreateSiteConstants.ts | 7 ++ .../commands/create-site/CreateSiteHelper.ts | 85 +++++++++++++++++-- .../commands/create-site/CreateSiteTypes.ts | 13 ++- src/common/constants.ts | 5 ++ src/common/copilot/PowerPagesCopilot.ts | 4 + src/common/services/ArtemisService.ts | 6 +- src/common/services/Interfaces.ts | 8 +- src/common/utilities/Utils.ts | 11 ++- 11 files changed, 150 insertions(+), 23 deletions(-) diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts index f7a15077..eb0a9882 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipant.ts @@ -14,7 +14,7 @@ import { intelligenceAPIAuthentication } from '../../services/AuthenticationProv import { ActiveOrgOutput } from '../../../client/pac/PacTypes'; import { AUTHENTICATION_FAILED_MSG, COPILOT_NOT_AVAILABLE_MSG, COPILOT_NOT_RELEASED_MSG, DISCLAIMER_MESSAGE, INVALID_RESPONSE, NO_PROMPT_MESSAGE, PAC_AUTH_INPUT, PAC_AUTH_NOT_FOUND, POWERPAGES_CHAT_PARTICIPANT_ID, POWERPAGES_COMMANDS, RESPONSE_AWAITED_MSG, RESPONSE_SCENARIOS, SKIP_CODES, STATER_PROMPTS, WELCOME_MESSAGE, WELCOME_PROMPT } from './PowerPagesChatParticipantConstants'; import { ORG_DETAILS_KEY, handleOrgChangeSuccess, initializeOrgDetails } from '../../utilities/OrgHandlerUtils'; -import { createAndReferenceLocation, getComponentInfo, getEndpoint, provideChatParticipantFollowups, handleChatParticipantFeedback, createErrorResult, createSuccessResult, removeChatVariables } from './PowerPagesChatParticipantUtils'; +import { createAndReferenceLocation, getComponentInfo, getEndpoint, provideChatParticipantFollowups, handleChatParticipantFeedback, createErrorResult, createSuccessResult, removeChatVariables, registerButtonCommands } from './PowerPagesChatParticipantUtils'; import { checkCopilotAvailability, fetchRelatedFiles, getActiveEditorContent } from '../../utilities/Utils'; import { IIntelligenceAPIEndpointInformation } from '../../services/Interfaces'; import { v4 as uuidv4 } from 'uuid'; @@ -63,6 +63,8 @@ export class PowerPagesChatParticipant { this._pacWrapper = pacWrapper; + registerButtonCommands(); + this._disposables.push(orgChangeEvent(async (orgDetails: ActiveOrgOutput) => { await this.handleOrgChangeSuccess(orgDetails); })); @@ -131,9 +133,13 @@ export class PowerPagesChatParticipant { const userId = intelligenceApiAuthResponse.userId; const intelligenceAPIEndpointInfo = await getEndpoint(this.orgID, this.environmentID, this.telemetry, this.cachedEndpoint, this.powerPagesAgentSessionId); + if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { + return createErrorResult(COPILOT_NOT_AVAILABLE_MSG, RESPONSE_SCENARIOS.COPILOT_NOT_AVAILABLE, this.orgID); + } + const copilotAvailabilityStatus = checkCopilotAvailability(intelligenceAPIEndpointInfo.intelligenceEndpoint, this.orgID, this.telemetry, this.powerPagesAgentSessionId); - if (!copilotAvailabilityStatus || !intelligenceAPIEndpointInfo.intelligenceEndpoint) { + if (!copilotAvailabilityStatus) { return createErrorResult(COPILOT_NOT_AVAILABLE_MSG, RESPONSE_SCENARIOS.COPILOT_NOT_AVAILABLE, this.orgID); } diff --git a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts index 94749968..8fd130f7 100644 --- a/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts +++ b/src/common/chat-participants/powerpages/PowerPagesChatParticipantUtils.ts @@ -11,6 +11,9 @@ import { ITelemetry } from "../../OneDSLoggerTelemetry/telemetry/ITelemetry"; import { ArtemisService } from "../../services/ArtemisService"; import { dataverseAuthentication } from "../../services/AuthenticationProvider"; import { IIntelligenceAPIEndpointInformation } from "../../services/Interfaces"; +import { EditableFileSystemProvider } from "../../utilities/EditableFileSystemProvider"; +import { CREATE_SITE_BTN_CMD } from "./commands/create-site/CreateSiteConstants"; +import { collectSiteCreationInputs, getUpdatedPageContent } from "./commands/create-site/CreateSiteHelper"; import { SUPPORTED_ENTITIES, EXPLAIN_CODE_PROMPT, FORM_PROMPT, LIST_PROMPT, STATER_PROMPTS, WEB_API_PROMPT } from "./PowerPagesChatParticipantConstants"; import { VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_SCENARIO_FEEDBACK_THUMBSUP, VSCODE_EXTENSION_GITHUB_POWER_PAGES_AGENT_SCENARIO_FEEDBACK_THUMBSDOWN } from "./PowerPagesChatParticipantTelemetryConstants"; import { IComponentInfo, IPowerPagesChatResult } from "./PowerPagesChatParticipantTypes"; @@ -127,3 +130,22 @@ export function removeChatVariables(userPrompt: string): string { return userPrompt.replace(regex, '').trim(); } + +export function registerButtonCommands() { + vscode.commands.registerCommand(CREATE_SITE_BTN_CMD, async (siteName: string, sitePages, envList, contentProvider: EditableFileSystemProvider, isCreateSiteInputsReceived) => { + if (!isCreateSiteInputsReceived) { + //Update Page Content will be used for the site creation + // eslint-disable-next-line @typescript-eslint/no-explicit-any + sitePages.map((page: any) => { + return { + ...page, + code: getUpdatedPageContent(contentProvider, page.metadata.pageTitle) + } + }); + const siteCreateInputs = await collectSiteCreationInputs(siteName, envList); + if (siteCreateInputs) { + isCreateSiteInputsReceived = true; + } + } + }); +} diff --git a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteCommand.ts b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteCommand.ts index f6212e79..61bd117b 100644 --- a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteCommand.ts +++ b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteCommand.ts @@ -19,7 +19,7 @@ export class CreateSiteCommand implements Command { try { // eslint-disable-next-line @typescript-eslint/no-unused-vars const result = await createSite({ - intelligenceEndpoint: intelligenceAPIEndpointInfo.intelligenceEndpoint, + intelligenceAPIEndpointInfo, intelligenceApiToken, userPrompt: request.prompt, sessionId: powerPagesAgentSessionId, diff --git a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteConstants.ts b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteConstants.ts index eac8c582..3ebbfd0a 100644 --- a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteConstants.ts +++ b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteConstants.ts @@ -7,3 +7,10 @@ export const EDITABLE_SCHEME = 'editable'; export const ENGLISH = "English"; export const MIN_PAGES = 7; export const MAX_PAGES = 7; +export const SITE_CREATE_INPUTS = 'New Power Pages Site'; +export const ENVIRONMENT_FOR_SITE_CREATION = 'Select Environment for Site Creation'; +export const SITE_NAME = 'Enter Site Name'; +export const SITE_NAME_REQUIRED = 'Site Name is required'; +export const CREATE_SITE_BTN_CMD = 'create-site-inputs'; +export const CREATE_SITE_BTN_TITLE = 'Create Site'; +export const CREATE_SITE_BTN_TOOLTIP = 'Create a new Power Pages site'; diff --git a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts index 77d04bc6..b465174b 100644 --- a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts +++ b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteHelper.ts @@ -11,13 +11,15 @@ import { NL2SITE_REQUEST_FAILED, NL2PAGE_GENERATING_WEBPAGES, NL2PAGE_RESPONSE_F import { oneDSLoggerWrapper } from '../../../../OneDSLoggerTelemetry/oneDSLoggerWrapper'; import { VSCODE_EXTENSION_NL2PAGE_REQUEST, VSCODE_EXTENSION_NL2SITE_REQUEST, VSCODE_EXTENSION_PREVIEW_SITE_PAGES, VSCODE_EXTENSION_PREVIEW_SITE_PAGES_ERROR } from '../../PowerPagesChatParticipantTelemetryConstants'; import { EditableFileSystemProvider } from '../../../../utilities/EditableFileSystemProvider'; -import { HTML_FILE_EXTENSION, UTF8_ENCODING } from '../../../../constants'; -import { EDITABLE_SCHEME } from './CreateSiteConstants'; -import { ICreateSiteOptions, IPreviewSitePagesContentOptions } from './CreateSiteTypes'; +import { HTML_FILE_EXTENSION, IEnvInfo, UTF8_ENCODING } from '../../../../constants'; +import { CREATE_SITE_BTN_CMD, CREATE_SITE_BTN_TITLE, CREATE_SITE_BTN_TOOLTIP, EDITABLE_SCHEME, ENVIRONMENT_FOR_SITE_CREATION, SITE_CREATE_INPUTS, SITE_NAME, SITE_NAME_REQUIRED } from './CreateSiteConstants'; +import { ICreateSiteOptions, IPreviewSitePagesContentOptions, ISiteInputState } from './CreateSiteTypes'; +import { MultiStepInput } from '../../../../utilities/MultiStepInput'; +import { getEnvList } from '../../../../utilities/Utils'; export const createSite = async (createSiteOptions: ICreateSiteOptions) => { const { - intelligenceEndpoint, + intelligenceAPIEndpointInfo, intelligenceApiToken, userPrompt, sessionId, @@ -29,12 +31,22 @@ export const createSite = async (createSiteOptions: ICreateSiteOptions) => { extensionContext } = createSiteOptions; - const { siteName, siteDescription, sitePages } = await fetchSiteAndPageData(intelligenceEndpoint, intelligenceApiToken, userPrompt, sessionId, telemetry, stream, orgId, envId, userId); + if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { + return; + } + const { siteName, siteDescription, sitePages } = await fetchSiteAndPageData(intelligenceAPIEndpointInfo.intelligenceEndpoint, intelligenceApiToken, userPrompt, sessionId, telemetry, stream, orgId, envId, userId); // eslint-disable-next-line @typescript-eslint/no-unused-vars - const contentProvider = previewSitePagesContent({sitePages, stream, extensionContext, telemetry, sessionId, orgId, envId, userId}); + const contentProvider = previewSitePagesContent({ sitePages, stream, extensionContext, telemetry, sessionId, orgId, envId, userId }); + + const envList = await getEnvList(telemetry, intelligenceAPIEndpointInfo.endpointStamp); - // TODO: Implement the create site button click handler + stream.button({ + command: CREATE_SITE_BTN_CMD, + title: CREATE_SITE_BTN_TITLE, + tooltip: CREATE_SITE_BTN_TOOLTIP, + arguments: [siteName, envList, contentProvider, false], + }); return { siteName, @@ -117,4 +129,63 @@ function previewSitePagesContent( throw error; } } +// Function to get updated content +export function getUpdatedPageContent(contentProvider: EditableFileSystemProvider, pageName: string): string { + const pageUri = vscode.Uri.parse(`${EDITABLE_SCHEME}:/${pageName}${HTML_FILE_EXTENSION}`); + return contentProvider.getFileContent(pageUri); +} + +export async function collectSiteCreationInputs(siteName: string, envList: IEnvInfo[]) { + const envNames: vscode.QuickPickItem[] = envList.map((env: IEnvInfo) => { + return { + label: env.envDisplayName, + description: env.orgUrl, + }; + }); + + const title = vscode.l10n.t(SITE_CREATE_INPUTS); + + async function collectInputs() { + const state = {} as Partial; + await MultiStepInput.run((input) => selectEnvName(input, state)); + return state as ISiteInputState; + } + async function selectEnvName( + input: MultiStepInput, + state: Partial + ) { + const pick = await input.showQuickPick({ + title, + step: 1, + totalSteps: 2, + placeholder: vscode.l10n.t(ENVIRONMENT_FOR_SITE_CREATION), + items: envNames, + activeItem: + typeof state.envName !== "string" + ? state.envName + : undefined, + }); + state.envName = pick.label; + state.OrgUrl = pick.description; + return (input: MultiStepInput) => inputSiteName(input, state); + } + + async function inputSiteName( + input: MultiStepInput, + state: Partial + ) { + state.siteName = await input.showInputBox({ + title, + step: 2, + totalSteps: 2, + value: state.siteName || siteName, + placeholder: vscode.l10n.t(SITE_NAME), + validate: async (value) => (value ? undefined : vscode.l10n.t(SITE_NAME_REQUIRED)), + }); + } + + const siteInputState = await collectInputs(); + // Return the collected site creation inputs including site name, environment name, and domain name + return siteInputState; +} diff --git a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteTypes.ts b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteTypes.ts index 30d8f358..6d43ea2e 100644 --- a/src/common/chat-participants/powerpages/commands/create-site/CreateSiteTypes.ts +++ b/src/common/chat-participants/powerpages/commands/create-site/CreateSiteTypes.ts @@ -5,9 +5,10 @@ import { ITelemetry } from "../../../../OneDSLoggerTelemetry/telemetry/ITelemetry"; import * as vscode from 'vscode'; +import { IIntelligenceAPIEndpointInformation } from "../../../../services/Interfaces"; export interface ICreateSiteOptions { - intelligenceEndpoint: string; + intelligenceAPIEndpointInfo: IIntelligenceAPIEndpointInformation; intelligenceApiToken: string; userPrompt: string; sessionId: string; @@ -31,3 +32,13 @@ export interface IPreviewSitePagesContentOptions { envId: string; userId: string; } + +export interface ISiteInputState { + siteName: string; + envName: string; + orgUrl: string; + domainName: string; + title: string; + step: number; + totalSteps: number; +} diff --git a/src/common/constants.ts b/src/common/constants.ts index dfb5f8fb..b7c36538 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -85,6 +85,11 @@ export interface IApiRequestParams { relatedFiles?: IRelatedFiles[]; } +export interface IEnvInfo { + orgUrl: string; + envDisplayName: string; +} + export const VSCODE_EXTENSION_COPILOT_CONTEXT_RELATED_FILES_FETCH_FAILED = "VSCodeExtensionCopilotContextRelatedFilesFetchFailed"; export const ADX_WEBPAGE = 'adx_webpage' export const HTML_FILE_EXTENSION = '.html'; diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 4adfdeb3..9fd14b33 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -426,6 +426,10 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider { sendTelemetryEvent(this.telemetry, { eventName: CopilotOrgChangedEvent, copilotSessionId: sessionID, orgId: orgID }); const intelligenceAPIEndpointInfo = await ArtemisService.getIntelligenceEndpoint(orgID, this.telemetry, sessionID, environmentId); + if (!intelligenceAPIEndpointInfo.intelligenceEndpoint) { + this.sendMessageToWebview({ type: 'Unavailable' }); + return; + } this.aibEndpoint = intelligenceAPIEndpointInfo.intelligenceEndpoint; this.geoName = intelligenceAPIEndpointInfo.geoName; this.crossGeoDataMovementEnabledPPACFlag = intelligenceAPIEndpointInfo.crossGeoDataMovementEnabledPPACFlag; diff --git a/src/common/services/ArtemisService.ts b/src/common/services/ArtemisService.ts index 9410e853..10c623a1 100644 --- a/src/common/services/ArtemisService.ts +++ b/src/common/services/ArtemisService.ts @@ -21,7 +21,9 @@ export class ArtemisService { if (artemisResponse === null) { return { intelligenceEndpoint: null, geoName: null, crossGeoDataMovementEnabledPPACFlag: false }; } - const { geoName, environment, clusterNumber } = artemisResponse.response as unknown as IArtemisAPIOrgResponse; + + const endpointStamp = artemisResponse.stamp; + const { geoName, environment, clusterNumber } = artemisResponse.response as IArtemisAPIOrgResponse; sendTelemetryEvent(telemetry, { eventName: CopilotArtemisSuccessEvent, copilotSessionId: sessionID, geoName: String(geoName), orgId: orgId }); const crossGeoDataMovementEnabledPPACFlag = await BAPService.getCrossGeoCopilotDataMovementEnabledFlag(artemisResponse.stamp, telemetry, environmentId); @@ -38,7 +40,7 @@ export class ArtemisService { const intelligenceEndpoint = `https://aibuildertextapiservice.${geoName}-${'il' + clusterNumber}.gateway.${environment}.island.powerapps.com/v1.0/${orgId}/appintelligence/chat` - return { intelligenceEndpoint: intelligenceEndpoint, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag }; + return { intelligenceEndpoint: intelligenceEndpoint, geoName: geoName, crossGeoDataMovementEnabledPPACFlag: crossGeoDataMovementEnabledPPACFlag, endpointStamp: endpointStamp }; } // Function to fetch Artemis response diff --git a/src/common/services/Interfaces.ts b/src/common/services/Interfaces.ts index e32457a3..49a7030a 100644 --- a/src/common/services/Interfaces.ts +++ b/src/common/services/Interfaces.ts @@ -25,15 +25,11 @@ export interface IArtemisAPIOrgResponse { clusterType: string, } -export interface IArtemisServiceResponse { - stamp: ServiceEndpointCategory; - response: IArtemisAPIOrgResponse; -} - export interface IIntelligenceAPIEndpointInformation { intelligenceEndpoint: string | null, geoName: string | null, - crossGeoDataMovementEnabledPPACFlag: boolean + crossGeoDataMovementEnabledPPACFlag: boolean, + endpointStamp?: ServiceEndpointCategory, } export interface IWebsiteDetails { diff --git a/src/common/utilities/Utils.ts b/src/common/utilities/Utils.ts index 0bbe584c..c53080eb 100644 --- a/src/common/utilities/Utils.ts +++ b/src/common/utilities/Utils.ts @@ -4,7 +4,7 @@ */ import * as vscode from "vscode"; -import { componentTypeSchema, EXTENSION_ID, EXTENSION_NAME, IRelatedFiles, relatedFilesSchema, SETTINGS_EXPERIMENTAL_STORE_NAME, VSCODE_EXTENSION_COPILOT_CONTEXT_RELATED_FILES_FETCH_FAILED } from "../constants"; +import { componentTypeSchema, EXTENSION_ID, EXTENSION_NAME, IEnvInfo, IRelatedFiles, relatedFilesSchema, SETTINGS_EXPERIMENTAL_STORE_NAME, VSCODE_EXTENSION_COPILOT_CONTEXT_RELATED_FILES_FETCH_FAILED } from "../constants"; import { CUSTOM_TELEMETRY_FOR_POWER_PAGES_SETTING_NAME } from "../OneDSLoggerTelemetry/telemetryConstants"; import { COPILOT_UNAVAILABLE, DataverseEntityNameMap, EntityFieldMap, FieldTypeMap } from "../copilot/constants"; import { IActiveFileData } from "../copilot/model"; @@ -325,8 +325,11 @@ export function getECSOrgLocationValue(clusterName: string, clusterNumber: strin } //API call to get env list for an org -export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEndpointCategory): Promise<{ envId: string, envDisplayName: string }[]> { - const envInfo: { envId: string, envDisplayName: string }[] = []; +export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEndpointCategory | undefined): Promise { + if(!endpointStamp) { + return []; + } + const envInfo: IEnvInfo[] = []; try { const bapAuthToken = await bapServiceAuthentication(telemetry, true); const bapEndpoint = getBAPEndpoint(endpointStamp, telemetry); @@ -344,7 +347,7 @@ export async function getEnvList(telemetry: ITelemetry, endpointStamp: ServiceEn // eslint-disable-next-line @typescript-eslint/no-explicit-any envListJson.value.forEach((env: any) => { envInfo.push({ - envId: env.properties.linkedEnvironmentMetadata.instanceUrl, + orgUrl: env.properties.linkedEnvironmentMetadata.instanceUrl, envDisplayName: env.properties.displayName }); }); From bb7e46980bcc88da9eac54d6b41b7323cce40e95 Mon Sep 17 00:00:00 2001 From: Priyanshu Agrawal Date: Mon, 16 Dec 2024 14:06:43 +0530 Subject: [PATCH 6/7] Fix accessibility issue in Pages Copilot pane (#1076) Fix accessibility issue in `Send` button in Power Pages Copilot pane. Updated the `aria-label` to `Send` instead of `Match Case` --- src/common/copilot/PowerPagesCopilot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/copilot/PowerPagesCopilot.ts b/src/common/copilot/PowerPagesCopilot.ts index 9fd14b33..229a8735 100644 --- a/src/common/copilot/PowerPagesCopilot.ts +++ b/src/common/copilot/PowerPagesCopilot.ts @@ -505,7 +505,7 @@ export class PowerPagesCopilot implements vscode.WebviewViewProvider {
-