diff --git a/package.json b/package.json index 3de01409..fb28b543 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,11 @@ "title": "%containerApps.deployContainerApp%", "category": "Azure Container Apps" }, + { + "command": "containerApps.deployContainerAppWithCopilot", + "title": "%containerApps.deployContainerAppWithCopilot%", + "category": "Azure Container Apps" + }, { "command": "containerApps.deleteContainerApp", "title": "%containerApps.deleteContainerApp%", @@ -383,6 +388,11 @@ "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single/i", "group": "2@1" }, + { + "command": "containerApps.deployContainerAppWithCopilot", + "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single/i", + "group": "2@2" + }, { "command": "containerApps.deployRevisionDraft", "when": "view =~ /(azureResourceGroups|azureFocusView)/ && viewItem =~ /containerAppItem(.*)revisionMode:single(.*)unsavedChanges:true/i", diff --git a/package.nls.json b/package.nls.json index 518ffadd..78c2c122 100644 --- a/package.nls.json +++ b/package.nls.json @@ -25,6 +25,7 @@ "containerApps.deployWorkspaceProject": "Deploy Project from Workspace...", "containerApps.deployWorkspaceProjectApi": "Deploy Project from Workspace (API)...", "containerApps.deployContainerApp": "Deploy to Container App...", + "containerApps.deployContainerAppWithCopilot": "Deploy to Container App with Copilot...", "containerApps.deleteContainerApp": "Delete Container App...", "containerApps.disableIngress": "Disable Ingress for Container App", "containerApps.enableIngress": "Enable Ingress for Container App...", diff --git a/src/commands/copilot/deployWithCopilot.ts b/src/commands/copilot/deployWithCopilot.ts new file mode 100644 index 00000000..22bdf511 --- /dev/null +++ b/src/commands/copilot/deployWithCopilot.ts @@ -0,0 +1,15 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.md in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import { CopilotUserInput, type IActionContext } from "@microsoft/vscode-azext-utils"; +import * as vscode from 'vscode'; +import { type ContainerAppItem } from "../../tree/ContainerAppItem"; +import { SharedState } from "../../webviews/OpenConfirmationViewStep"; +import { deployContainerApp } from "../deployContainerApp/deployContainerApp"; + +export async function deployWithCopilot(context: IActionContext, node: ContainerAppItem): Promise { + context.ui = new CopilotUserInput(vscode, JSON.stringify(node.viewProperties), () => SharedState.currentPanel); + await deployContainerApp(context, node); +} diff --git a/src/commands/deployContainerApp/deployContainerApp.ts b/src/commands/deployContainerApp/deployContainerApp.ts index e7e2e911..7e0b6568 100644 --- a/src/commands/deployContainerApp/deployContainerApp.ts +++ b/src/commands/deployContainerApp/deployContainerApp.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { KnownActiveRevisionsMode } from "@azure/arm-appcontainers"; -import { AzureWizard, createSubscriptionContext, nonNullProp, type IActionContext, type ISubscriptionActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; +import { AzureWizard, CopilotUserInput, createSubscriptionContext, nonNullProp, type AzureWizardPromptStep, type IActionContext, type ISubscriptionActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils"; import { ImageSource } from "../../constants"; import { type ContainerAppItem } from "../../tree/ContainerAppItem"; import { createActivityContext } from "../../utils/activityUtils"; @@ -14,6 +14,7 @@ import { getVerifyProvidersStep } from "../../utils/getVerifyProvidersStep"; import { localize } from "../../utils/localize"; import { pickContainerApp } from "../../utils/pickItem/pickContainerApp"; import { OpenConfirmationViewStep } from "../../webviews/OpenConfirmationViewStep"; +import { OpenLoadingViewStep } from "../../webviews/OpenLoadingViewStep"; import { CommandAttributes } from "../CommandAttributes"; import { ContainerAppOverwriteConfirmStep } from "../ContainerAppOverwriteConfirmStep"; import { deployWorkspaceProject } from "../deployWorkspaceProject/deployWorkspaceProject"; @@ -60,18 +61,28 @@ export async function deployContainerApp(context: IActionContext, node?: Contain wizardContext.telemetry.properties.revisionMode = item.containerApp.revisionsMode; const confirmationViewTitle: string = localize('summary', 'Summary'); - const confirmationViewDescription: string = localize('viewDescription', 'Please select an input you would like to change. Note: Any input proceeding the changed input may need to change as well'); - const confirmationViewTabTitle: string = localize('deployContainerAppTabTitle', 'Summary - Deploy Image to Container App'); - const title: string = localize('deployContainerAppTitle', 'Deploy image to container app'); + let confirmationViewDescription: string = localize('viewDescription', 'Please select an input you would like to change. Note: Any input proceeding the changed input will need to change as well'); + let confirmationViewTabTitle: string = localize('deployContainerAppTabTitle', 'Summary - Deploy Image to Container App'); + let title: string = localize('deployContainerAppTitle', 'Deploy image to container app'); + + const promptSteps: AzureWizardPromptStep[] = [] + if (wizardContext.ui instanceof CopilotUserInput) { + promptSteps.push(new OpenLoadingViewStep()); + confirmationViewDescription = localize('viewDescription', 'Please review AI generated inputs and select any you would like to modify. Note: Any input proceeding the modified input will need to change as well'); + confirmationViewTabTitle = localize('deployContainerAppTabTitle', 'Summary - Deploy Image to Container App using Copilot'); + title = localize('deployContainerAppWithCopilotTitle', 'Deploy image to container app using copilot'); + } + + promptSteps.push( + new ContainerAppDeployStartingResourcesLogStep(), + new ImageSourceListStep(), + new ContainerAppOverwriteConfirmStep(), + new OpenConfirmationViewStep(confirmationViewTitle, confirmationViewTabTitle, confirmationViewDescription, title, () => wizard.confirmationViewProperties) + ); const wizard: AzureWizard = new AzureWizard(wizardContext, { title: title, - promptSteps: [ - new ContainerAppDeployStartingResourcesLogStep(), - new ImageSourceListStep(), - new ContainerAppOverwriteConfirmStep(), - new OpenConfirmationViewStep(confirmationViewTitle, confirmationViewTabTitle, confirmationViewDescription, title, () => wizard.confirmationViewProperties) - ], + promptSteps: promptSteps, executeSteps: [ getVerifyProvidersStep(), new ContainerAppUpdateStep(), diff --git a/src/commands/registerCommands.ts b/src/commands/registerCommands.ts index 85253235..6db1abaa 100644 --- a/src/commands/registerCommands.ts +++ b/src/commands/registerCommands.ts @@ -7,6 +7,7 @@ import { registerCommand, registerCommandWithTreeNodeUnwrapping, registerErrorHa import { type EnvironmentVariableItem } from '../tree/containers/EnvironmentVariableItem'; import { deployImageApiCompat } from './api/deployImageApi'; import { browseContainerAppNode } from './browseContainerApp'; +import { deployWithCopilot } from './copilot/deployWithCopilot'; import { createContainerApp } from './createContainerApp/createContainerApp'; import { createManagedEnvironment } from './createManagedEnvironment/createManagedEnvironment'; import { deleteContainerApp } from './deleteContainerApp/deleteContainerApp'; @@ -87,6 +88,7 @@ export function registerCommands(): void { registerCommandWithTreeNodeUnwrapping('containerApps.deployRevisionDraft', deployRevisionDraft); registerCommandWithTreeNodeUnwrapping('containerApps.deployWorkspaceProject', deployWorkspaceProject); registerCommandWithTreeNodeUnwrapping('containerApps.deployContainerApp', deployContainerApp); + registerCommandWithTreeNodeUnwrapping('containerApps.deployContainerAppWithCopilot', deployWithCopilot); // github registerCommandWithTreeNodeUnwrapping('containerApps.connectToGitHub', connectToGitHub); diff --git a/src/webviews/LoadingView.tsx b/src/webviews/LoadingView.tsx new file mode 100644 index 00000000..8cf8505a --- /dev/null +++ b/src/webviews/LoadingView.tsx @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Spinner } from "@fluentui/react-components"; +import './loadingView.scss'; + +export const LoadingView = () => +
+ +
+ diff --git a/src/webviews/LoadingViewController.ts b/src/webviews/LoadingViewController.ts new file mode 100644 index 00000000..84180fb7 --- /dev/null +++ b/src/webviews/LoadingViewController.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.md in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import { ViewColumn } from "vscode"; +import { ext } from "../extensionVariables"; +import { WebviewController } from "./extension-server/WebviewController"; + +export type LoadingViewControllerType = { + title: string; +} + +export class LoadingViewController extends WebviewController { + constructor(viewConfiguration: LoadingViewControllerType) { + super(ext.context, viewConfiguration.title, 'loadingView', viewConfiguration, ViewColumn.Active); + } +} diff --git a/src/webviews/OpenConfirmationViewStep.ts b/src/webviews/OpenConfirmationViewStep.ts index 0b401047..077634fc 100644 --- a/src/webviews/OpenConfirmationViewStep.ts +++ b/src/webviews/OpenConfirmationViewStep.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AzureWizardPromptStep, GoBackError, openUrl, UserCancelledError, type ConfirmationViewProperty, type IActionContext } from "@microsoft/vscode-azext-utils"; +import { AzExtUserInput, AzureWizardPromptStep, CopilotUserInput, GoBackError, openUrl, UserCancelledError, type ConfirmationViewProperty, type IActionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from 'vscode'; +import { type WebviewPanel } from 'vscode'; import { localize } from "../utils/localize"; import { ConfirmationViewController } from "./ConfirmationViewController"; @@ -13,6 +14,7 @@ export const SharedState = { cancelled: true, copilotClicked: false, editingPicks: false, + currentPanel: undefined as WebviewPanel | undefined, }; export class OpenConfirmationViewStep extends AzureWizardPromptStep { @@ -32,6 +34,11 @@ export class OpenConfirmationViewStep extends AzureWiz } public async prompt(context: T): Promise { + if (SharedState.currentPanel) { + SharedState.currentPanel.dispose(); + SharedState.currentPanel = undefined; + } + const confirmationView = new ConfirmationViewController({ title: this.title, tabTitle: this.tabTitle, @@ -48,6 +55,9 @@ export class OpenConfirmationViewStep extends AzureWiz if (SharedState.itemsToClear > 0) { context.telemetry.properties.editingPicks = 'true'; SharedState.editingPicks = true; + if (context.ui instanceof CopilotUserInput) { + context.ui = new AzExtUserInput(context); + } throw new GoBackError(SharedState.itemsToClear); } diff --git a/src/webviews/OpenLoadingViewStep.ts b/src/webviews/OpenLoadingViewStep.ts new file mode 100644 index 00000000..c706e596 --- /dev/null +++ b/src/webviews/OpenLoadingViewStep.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- +* Copyright (c) Microsoft Corporation. All rights reserved. +* Licensed under the MIT License. See License.md in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import { AzureWizardPromptStep, type IActionContext } from "@microsoft/vscode-azext-utils"; +import * as vscode from 'vscode'; +import { localize } from "../utils/localize"; +import { LoadingViewController } from "./LoadingViewController"; +import { SharedState } from "./OpenConfirmationViewStep"; + +export class OpenLoadingViewStep extends AzureWizardPromptStep { + public async prompt(): Promise { + const loadingView = new LoadingViewController({ title: localize('loadingViewTitle', 'Loading...') }); + loadingView.revealToForeground(vscode.ViewColumn.Active); + SharedState.currentPanel = loadingView.panel; + } + + public shouldPrompt(): boolean { + return true; + } +} diff --git a/src/webviews/WebviewRegistry.ts b/src/webviews/WebviewRegistry.ts index 80a6b315..c61fc46a 100644 --- a/src/webviews/WebviewRegistry.ts +++ b/src/webviews/WebviewRegistry.ts @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { ConfirmationView } from "./ConfirmationView"; +import { LoadingView } from "./LoadingView"; export const WebviewRegistry = { - confirmationView: ConfirmationView + confirmationView: ConfirmationView, + loadingView: LoadingView } as const; diff --git a/src/webviews/loadingView.scss b/src/webviews/loadingView.scss new file mode 100644 index 00000000..778c3b6c --- /dev/null +++ b/src/webviews/loadingView.scss @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.loadingView { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +}