Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions src/commands/createContainerApp/ContainerAppListStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { type ContainerApp, type ContainerAppsAPIClient } from "@azure/arm-appcontainers";
import { LocationListStep, parseAzureResourceId, ResourceGroupListStep, uiUtils } from "@microsoft/vscode-azext-azureutils";
import { AzureWizardPromptStep, nonNullProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type IWizardOptions } from "@microsoft/vscode-azext-utils";
import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, type AzureWizardExecuteStep, type ConfirmationViewProperty, type IAzureQuickPickItem, type IWizardOptions } from "@microsoft/vscode-azext-utils";
import { containerAppProvider, containerAppResourceType } from "../../constants";
import { ContainerAppItem } from "../../tree/ContainerAppItem";
import { createContainerAppsAPIClient } from "../../utils/azureClients";
Expand Down Expand Up @@ -59,6 +59,14 @@
return !context.containerApp && !context.newContainerAppName;
}

public confirmationViewProperty(context: T): ConfirmationViewProperty {
return {
name: localize('containerApp', 'Container App'),
value: nonNullValueAndProp(context.containerApp, 'name'),
contextPropertyName: 'containerApp',
}

Check warning on line 67 in src/commands/createContainerApp/ContainerAppListStep.ts

View workflow job for this annotation

GitHub Actions / Build / Build

Missing semicolon
}

private async getPicks(context: T): Promise<IAzureQuickPickItem<ContainerApp>[]> {
const client: ContainerAppsAPIClient = await createContainerAppsAPIClient(context);

Expand All @@ -72,7 +80,7 @@
}

if (context.managedEnvironment) {
containerApps = containerApps.filter(ca => ca.managedEnvironmentId === context.managedEnvironment.id);
containerApps = containerApps.filter(ca => ca.managedEnvironmentId === context.managedEnvironment?.id);
}

return containerApps.map(ca => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { type ContainerAppsAPIClient, type ManagedEnvironment } from "@azure/arm
import { type Workspace } from "@azure/arm-operationalinsights";
import { type ResourceGroup } from "@azure/arm-resources";
import { getResourceGroupFromId, LocationListStep, ResourceGroupListStep, uiUtils } from "@microsoft/vscode-azext-azureutils";
import { AzureWizardPromptStep, nonNullProp, type AzureWizardExecuteStep, type IAzureQuickPickItem, type ISubscriptionActionContext, type IWizardOptions } from "@microsoft/vscode-azext-utils";
import { AzureWizardPromptStep, nonNullProp, nonNullValueAndProp, type AzureWizardExecuteStep, type ConfirmationViewProperty, type IAzureQuickPickItem, type ISubscriptionActionContext, type IWizardOptions } from "@microsoft/vscode-azext-utils";
import { logAnalyticsProvider, logAnalyticsResourceType, managedEnvironmentProvider, managedEnvironmentResourceType } from "../../constants";
import { createContainerAppsAPIClient } from "../../utils/azureClients";
import { localize } from "../../utils/localize";
Expand Down Expand Up @@ -57,6 +57,14 @@ export class ManagedEnvironmentListStep<T extends ManagedEnvironmentCreateContex
return !context.managedEnvironment && !context.newManagedEnvironmentName;
}

public confirmationViewProperty(context: T): ConfirmationViewProperty {
return {
name: localize('containerAppEnvironment', 'Container Apps Environment'),
value: nonNullValueAndProp(context.managedEnvironment, 'name'),
contextPropertyName: 'managedEnvironment',
};
}

private async getPicks(context: T): Promise<ManagedEnvironmentPick[]> {
const client: ContainerAppsAPIClient = await createContainerAppsAPIClient(context);

Expand Down
74 changes: 55 additions & 19 deletions src/commands/deployContainerApp/deployContainerApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { KnownActiveRevisionsMode } from "@azure/arm-appcontainers";
import { AzureWizard, CopilotUserInput, createSubscriptionContext, nonNullProp, type AzureWizardPromptStep, type IActionContext, type ISubscriptionActionContext, type ISubscriptionContext } from "@microsoft/vscode-azext-utils";
import { type AzureSubscription } from "@microsoft/vscode-azureresources-api";
import { ImageSource } from "../../constants";
import { type ContainerAppItem } from "../../tree/ContainerAppItem";
import { createActivityContext } from "../../utils/activityUtils";
Expand All @@ -14,9 +15,11 @@ 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 { openLoadingViewPanel } from "../../webviews/OpenLoadingViewStep";
import { CommandAttributes } from "../CommandAttributes";
import { ContainerAppOverwriteConfirmStep } from "../ContainerAppOverwriteConfirmStep";
import { ContainerAppListStep } from "../createContainerApp/ContainerAppListStep";
import { ManagedEnvironmentListStep } from "../createManagedEnvironment/ManagedEnvironmentListStep";
import { deployWorkspaceProject } from "../deployWorkspaceProject/deployWorkspaceProject";
import { type DeployWorkspaceProjectResults } from "../deployWorkspaceProject/getDeployWorkspaceProjectResults";
import { editContainerCommandName } from "../editContainer/editContainer";
Expand Down Expand Up @@ -45,30 +48,59 @@ export async function deployContainerApp(context: IActionContext, node?: Contain
return await deployWorkspaceProject(context, item);
}

const wizardContext: ContainerAppDeployContext = {
...subscriptionActionContext,
...await createActivityContext({ withChildren: true }),
subscription: item.subscription,
containerApp: item.containerApp,
managedEnvironment: await getManagedEnvironmentFromContainerApp(subscriptionActionContext, item.containerApp),
imageSource,
activityAttributes: CommandAttributes.DeployContainerAppContainerRegistry,
};

if (isAzdExtensionInstalled()) {
wizardContext.telemetry.properties.isAzdExtensionInstalled = 'true';
return await deployContainerAppInternal(subscriptionActionContext, item, imageSource);
}

export async function deployContainerAppInternal(context: ISubscriptionActionContext, node?: ContainerAppItem, imageSource?: ImageSource, subscription?: AzureSubscription): Promise<void> {
if (isCopilotUserInput(context)) {
await openLoadingViewPanel(context);
}

const promptSteps: AzureWizardPromptStep<ContainerAppDeployContext>[] = [];

if (!node) {
promptSteps.push(new ManagedEnvironmentListStep(), new ContainerAppListStep());
}

let wizardContext: ContainerAppDeployContext = {} as ContainerAppDeployContext;
Copy link
Contributor

Choose a reason for hiding this comment

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

This might remove the need for the as cast:

Suggested change
let wizardContext: ContainerAppDeployContext = {} as ContainerAppDeployContext;
let wizardContext: Partial<ContainerAppDeployContext> = {};


if (node && imageSource) {
// If this command gets re run we only want the internal portion of the command to run, so we set the callbackid
context.callbackId = 'containerApps.deployContainerAppInternal';
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain what this means?

Copy link
Contributor Author

@motm32 motm32 Feb 10, 2026

Choose a reason for hiding this comment

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

If you look at the deployContainerApp it is now split into a internal and non internal portion. The internal portion does not include the pickContainerApp and subscriptionContext creation as I am doing that seperately in the copilot re run command. Thus we want to change the callbackId to go straight to the internal portion instead of the regular deployContainerApp route.

wizardContext = {
...context,
...await createActivityContext({ withChildren: true }),
subscription: node.subscription,
containerApp: node.containerApp,
managedEnvironment: await getManagedEnvironmentFromContainerApp(context, node.containerApp),
imageSource,
activityAttributes: {
...CommandAttributes.DeployContainerAppContainerRegistry,
subscription: node.subscription,
},
};

if (isAzdExtensionInstalled()) {
wizardContext.telemetry.properties.isAzdExtensionInstalled = 'true';
}
wizardContext.telemetry.properties.revisionMode = node.containerApp.revisionsMode;
} else if (subscription) {
wizardContext = {
...context,
...await createActivityContext({ withChildren: true }),
subscription: subscription,
//at the moment we are only supporting re run with container registry image source
imageSource: ImageSource.ContainerRegistry
};
}
wizardContext.telemetry.properties.revisionMode = item.containerApp.revisionsMode;

const confirmationViewTitle: string = localize('summary', 'Summary');
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 confirmationViewDescription: string = localize('viewDescription', 'Please select an input you would like to change. Note: Any input preceding 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<ContainerAppDeployContext>[] = [];
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');
if (isCopilotUserInput(wizardContext)) {
confirmationViewDescription = localize('viewDescription', 'Please review AI generated inputs and select any you would like to modify. Note: Any input preceding 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');
}
Expand Down Expand Up @@ -105,3 +137,7 @@ async function promptImageSource(context: ISubscriptionActionContext): Promise<I

return nonNullProp(promptContext, 'imageSource');
}

function isCopilotUserInput(context: IActionContext): boolean {
return context.ui instanceof CopilotUserInput;
Comment on lines +141 to +142
Copy link
Contributor

Choose a reason for hiding this comment

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

instanceof can break down if there are multiple versions of the utils package at play, which we aren't always careful about. We've had problems before with UserCancelledError. I'd recommend a canary property on CopilotUserInput, adding it if there isn't one already.

Additionally, this helps TypeScript narrow the type better:

Suggested change
function isCopilotUserInput(context: IActionContext): boolean {
return context.ui instanceof CopilotUserInput;
function isCopilotUserInput(context: IActionContext): context.ui is CopilotUserInput {
return context.ui instanceof CopilotUserInput;

}
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export class ContainerAppStartVerificationStep<T extends ContainerAppStartVerifi
throw new Error(invalidName);
}

const workspaceId = context.managedEnvironment.appLogsConfiguration?.logAnalyticsConfiguration?.customerId;
const workspaceId = context.managedEnvironment?.appLogsConfiguration?.logAnalyticsConfiguration?.customerId;
if (!workspaceId) {
return;
}
Expand Down
3 changes: 2 additions & 1 deletion src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { createContainerApp } from './createContainerApp/createContainerApp';
import { createManagedEnvironment } from './createManagedEnvironment/createManagedEnvironment';
import { deleteContainerApp } from './deleteContainerApp/deleteContainerApp';
import { deleteManagedEnvironment } from './deleteManagedEnvironment/deleteManagedEnvironment';
import { deployContainerApp } from './deployContainerApp/deployContainerApp';
import { deployContainerApp, deployContainerAppInternal } from './deployContainerApp/deployContainerApp';
import { deployWorkspaceProject } from './deployWorkspaceProject/deployWorkspaceProject';
import { editContainer } from './editContainer/editContainer';
import { editContainerImage } from './editContainer/editContainerImage/editContainerImage';
Expand Down Expand Up @@ -89,6 +89,7 @@ export function registerCommands(): void {
registerCommandWithTreeNodeUnwrapping('containerApps.deployWorkspaceProject', deployWorkspaceProject);
registerCommandWithTreeNodeUnwrapping('containerApps.deployContainerApp', deployContainerApp);
registerCommandWithTreeNodeUnwrapping('containerApps.deployContainerAppWithCopilot', deployWithCopilot);
registerCommand('containerApps.deployContainerAppInternal', deployContainerAppInternal);

// github
registerCommandWithTreeNodeUnwrapping('containerApps.connectToGitHub', connectToGitHub);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { type RegistryCredentials } from "@azure/arm-appcontainers";
import { type SupportedRegistries } from "../../../constants";
import { type IContainerAppContext } from "../../IContainerAppContext";
import { type CreateAcrContext } from "../../image/imageSource/containerRegistry/acr/createAcr/CreateAcrContext";
import { type ManagedEnvironmentRequiredContext } from "../../ManagedEnvironmentContext";
import { type ManagedEnvironmentContext } from "../../ManagedEnvironmentContext";

export interface ManagedIdentityRegistryCredentialsContext extends CreateAcrContext, ManagedEnvironmentRequiredContext, IContainerAppContext {
export interface ManagedIdentityRegistryCredentialsContext extends CreateAcrContext, ManagedEnvironmentContext, IContainerAppContext {
Copy link
Member

Choose a reason for hiding this comment

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

Context loosened from ManagedEnvironmentRequiredContext → ManagedEnvironmentContext.
This is fine only if all consumers tolerate managedEnvironment being undefined. Worth a quick scan of downstream usage.

registryDomain?: SupportedRegistries;
newRegistryCredential?: RegistryCredentials;
}
3 changes: 1 addition & 2 deletions src/webviews/OpenConfirmationViewStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

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";

Expand All @@ -14,7 +13,7 @@ export const SharedState = {
cancelled: true,
copilotClicked: false,
editingPicks: false,
currentPanel: undefined as WebviewPanel | undefined,
currentPanel: undefined as vscode.WebviewPanel | undefined,
};

export class OpenConfirmationViewStep<T extends IActionContext> extends AzureWizardPromptStep<T> {
Expand Down
10 changes: 9 additions & 1 deletion src/webviews/OpenLoadingViewStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* 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 { AzureWizardPromptStep, runGenericPromptStep, type IActionContext } from "@microsoft/vscode-azext-utils";
import * as vscode from 'vscode';
import { localize } from "../utils/localize";
import { LoadingViewController } from "./LoadingViewController";
Expand All @@ -20,3 +20,11 @@ export class OpenLoadingViewStep<T extends IActionContext> extends AzureWizardPr
return true;
}
}

export async function openLoadingViewPanel(context: IActionContext): Promise<void> {
const promptSteps: AzureWizardPromptStep<IActionContext>[] = [new OpenLoadingViewStep<IActionContext>()];

return await runGenericPromptStep(context, {
promptSteps
});
}
Loading