diff --git a/apps/vs-code-designer/src/LogicAppResolver.ts b/apps/vs-code-designer/src/LogicAppResolver.ts new file mode 100644 index 00000000000..fc25b5038a9 --- /dev/null +++ b/apps/vs-code-designer/src/LogicAppResolver.ts @@ -0,0 +1,24 @@ +import { LogicAppResourceTree } from './app/tree/LogicAppResourceTree'; +import { logicAppFilter } from './constants'; +import { createWebSiteClient } from '@microsoft/vscode-azext-azureappservice'; +import { getResourceGroupFromId } from '@microsoft/vscode-azext-azureutils'; +import type { IActionContext, ISubscriptionContext } from '@microsoft/vscode-azext-utils'; +import { callWithTelemetryAndErrorHandling, nonNullProp } from '@microsoft/vscode-azext-utils'; +import type { AppResource, AppResourceResolver } from '@microsoft/vscode-azext-utils/hostapi'; + +export class LogicAppResolver implements AppResourceResolver { + public async resolveResource(subContext: ISubscriptionContext, resource: AppResource): Promise { + return await callWithTelemetryAndErrorHandling('resolveResource', async (context: IActionContext) => { + const client = await createWebSiteClient({ ...context, ...subContext }); + const rg = getResourceGroupFromId(nonNullProp(resource, 'id')); + const name = nonNullProp(resource, 'name'); + + const site = await client.webApps.get(rg, name); + return LogicAppResourceTree.createLogicAppResourceTree(context, subContext, site); + }); + } + + public matchesResource(resource: AppResource): boolean { + return resource.type.toLowerCase() === logicAppFilter.type && resource.kind?.toLowerCase() === logicAppFilter.kind; + } +} diff --git a/apps/vs-code-designer/src/app/commands/appSettings/downloadAppSettings.ts b/apps/vs-code-designer/src/app/commands/appSettings/downloadAppSettings.ts index 400d28664ca..5c9f95d58b4 100644 --- a/apps/vs-code-designer/src/app/commands/appSettings/downloadAppSettings.ts +++ b/apps/vs-code-designer/src/app/commands/appSettings/downloadAppSettings.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localSettingsFileName, viewOutput } from '../../../constants'; +import { localSettingsFileName, logicAppFilter, viewOutput } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; import { executeOnFunctions } from '../../functionsExtension/executeOnFunctionsExt'; @@ -20,7 +20,10 @@ import * as vscode from 'vscode'; export async function downloadAppSettings(context: IActionContext, node?: AppSettingsTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(AppSettingsTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: new RegExp(AppSettingsTreeItem.contextValue), + }); } const client: IAppSettingsClient = await node.clientProvider.createClient(context); diff --git a/apps/vs-code-designer/src/app/commands/appSettings/editAppSetting.ts b/apps/vs-code-designer/src/app/commands/appSettings/editAppSetting.ts index d8620dbf1c8..680797edaf6 100644 --- a/apps/vs-code-designer/src/app/commands/appSettings/editAppSetting.ts +++ b/apps/vs-code-designer/src/app/commands/appSettings/editAppSetting.ts @@ -2,13 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { AppSettingTreeItem } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; export async function editAppSetting(context: IActionContext, node?: AppSettingTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(AppSettingTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: new RegExp(AppSettingTreeItem.contextValue), + }); } await node.edit(context); diff --git a/apps/vs-code-designer/src/app/commands/appSettings/renameAppSetting.ts b/apps/vs-code-designer/src/app/commands/appSettings/renameAppSetting.ts index 3d1b0b3f838..d8de78a84d4 100644 --- a/apps/vs-code-designer/src/app/commands/appSettings/renameAppSetting.ts +++ b/apps/vs-code-designer/src/app/commands/appSettings/renameAppSetting.ts @@ -2,13 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { AppSettingTreeItem } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; export async function renameAppSetting(context: IActionContext, node?: AppSettingTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(AppSettingTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: new RegExp(AppSettingTreeItem.contextValue), + }); } await node.rename(context); diff --git a/apps/vs-code-designer/src/app/commands/appSettings/toggleSlotSetting.ts b/apps/vs-code-designer/src/app/commands/appSettings/toggleSlotSetting.ts index 4efe23fdc73..2c89f39b414 100644 --- a/apps/vs-code-designer/src/app/commands/appSettings/toggleSlotSetting.ts +++ b/apps/vs-code-designer/src/app/commands/appSettings/toggleSlotSetting.ts @@ -2,13 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { AppSettingTreeItem } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; export async function toggleSlotSetting(context: IActionContext, node?: AppSettingTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(AppSettingTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: new RegExp(AppSettingTreeItem.contextValue), + }); } await node.toggleSlotSetting(context); diff --git a/apps/vs-code-designer/src/app/commands/appSettings/uploadAppSettings.ts b/apps/vs-code-designer/src/app/commands/appSettings/uploadAppSettings.ts index 51b20694aaa..f0251cbf910 100644 --- a/apps/vs-code-designer/src/app/commands/appSettings/uploadAppSettings.ts +++ b/apps/vs-code-designer/src/app/commands/appSettings/uploadAppSettings.ts @@ -2,59 +2,91 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localSettingsFileName } from '../../../constants'; +import { localSettingsFileName, logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; import { getLocalSettingsJson } from '../../utils/appSettings/localSettings'; import { getLocalSettingsFile } from './getLocalSettingsFile'; import type { StringDictionary } from '@azure/arm-appservice'; -import type { IAppSettingsClient } from '@microsoft/vscode-azext-azureappservice'; +import { isString } from '@microsoft/utils-logic-apps'; import { AppSettingsTreeItem, confirmOverwriteSettings } from '@microsoft/vscode-azext-azureappservice'; +import type { IAppSettingsClient } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import type { ILocalSettingsJson } from '@microsoft/vscode-extension'; -import type { WorkspaceFolder } from 'vscode'; +import * as vscode from 'vscode'; /** * Uploads local settings file to the portal. * @param {IActionContext} context - Command context. * @param {AppSettingsTreeItem} node - App settings node structure. - * @param {WorkspaceFolder} workspacePath - Workspace folder path. - * @param {string[]} settingsToExclude - Array of settings to exclude from uploading. + * @param {[]} _nodes - App settings node structure. + * @param {vscode.WorkspaceFolder} workspacePath - Workspace folder path. + * @param {(RegExp | string)[]} exclude - Array of settings to exclude from uploading. * @returns {Promise} Workspace file path. */ export async function uploadAppSettings( context: IActionContext, node?: AppSettingsTreeItem, - workspacePath?: WorkspaceFolder, - settingsToExclude: string[] = [] + _nodes?: [], + workspacePath?: vscode.WorkspaceFolder, + exclude?: (RegExp | string)[] ): Promise { const message: string = localize('selectLocalSettings', 'Select the local settings file to upload.'); const localSettingsPath: string = await getLocalSettingsFile(context, message, workspacePath); if (!node) { - node = await ext.tree.showTreeItemPicker(AppSettingsTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: new RegExp(AppSettingsTreeItem.contextValue), + }); } const client: IAppSettingsClient = await node.clientProvider.createClient(context); await node.runWithTemporaryDescription(context, localize('uploading', 'Uploading...'), async () => { ext.outputChannel.show(true); - ext.outputChannel.appendLog(localize('uploadStart', 'Uploading settings to "{0}"...', client.fullName)); const localSettings: ILocalSettingsJson = await getLocalSettingsJson(context, localSettingsPath); if (localSettings.Values) { const remoteSettings: StringDictionary = await client.listApplicationSettings(); + const excludedAppSettings: string[] = []; + if (!remoteSettings.properties) { remoteSettings.properties = {}; } - for (const settingToDelete of settingsToExclude) { - delete localSettings.Values[settingToDelete]; + if (exclude) { + Object.keys(localSettings.Values).forEach((settingName) => { + if ( + exclude.some((exclusion) => + isString(exclusion) ? settingName.toLowerCase() === exclusion.toLowerCase() : settingName.match(new RegExp(exclusion, 'i')) + ) + ) { + delete localSettings.Values?.[settingName]; + excludedAppSettings.push(settingName); + } + }); } + ext.outputChannel.appendLog(localize('uploadingSettings', 'Uploading settings...'), { resourceName: client.fullName }); await confirmOverwriteSettings(context, localSettings.Values, remoteSettings.properties, client.fullName); - await client.updateApplicationSettings(remoteSettings); + if (excludedAppSettings.length) { + ext.outputChannel.appendLog(localize('excludedSettings', 'Excluded the following settings:')); + excludedAppSettings.forEach((key) => ext.outputChannel.appendLine(`- ${key}`)); + } + + await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: localize('uploadingSettingsTo', 'Uploading settings to "{0}"...', client.fullName), + }, + async () => { + await client.updateApplicationSettings(remoteSettings); + } + ); + + ext.outputChannel.appendLog(localize('uploadedSettings', 'Successfully uploaded settings.'), { resourceName: client.fullName }); } else { throw new Error(localize('noSettings', 'No settings found in "{0}".', localSettingsFileName)); } diff --git a/apps/vs-code-designer/src/app/commands/browseWebsite.ts b/apps/vs-code-designer/src/app/commands/browseWebsite.ts index 107efff75ab..f2536672e4f 100644 --- a/apps/vs-code-designer/src/app/commands/browseWebsite.ts +++ b/apps/vs-code-designer/src/app/commands/browseWebsite.ts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../constants'; import { ext } from '../../extensionVariables'; -import { ProductionSlotTreeItem } from '../tree/slotsTree/ProductionSlotTreeItem'; -import type { SlotTreeItemBase } from '../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import { openUrl } from '@microsoft/vscode-azext-utils'; -export async function browseWebsite(context: IActionContext, node?: SlotTreeItemBase): Promise { +export async function browseWebsite(context: IActionContext, node?: SlotTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + }); } await openUrl(node.site.defaultHostUrl); diff --git a/apps/vs-code-designer/src/app/commands/configureDeploymentSource.ts b/apps/vs-code-designer/src/app/commands/configureDeploymentSource.ts index 3f3880c92e4..40dd415f9a4 100644 --- a/apps/vs-code-designer/src/app/commands/configureDeploymentSource.ts +++ b/apps/vs-code-designer/src/app/commands/configureDeploymentSource.ts @@ -2,15 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../constants'; import { ext } from '../../extensionVariables'; -import { ProductionSlotTreeItem } from '../tree/slotsTree/ProductionSlotTreeItem'; -import type { SlotTreeItemBase } from '../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; import { editScmType } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; -export async function configureDeploymentSource(context: IActionContext, node?: SlotTreeItemBase): Promise { +export async function configureDeploymentSource(context: IActionContext, node?: SlotTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + }); } const updatedScmType: string | undefined = await editScmType(context, node.site, node.subscription); diff --git a/apps/vs-code-designer/src/app/commands/createChildNode.ts b/apps/vs-code-designer/src/app/commands/createChildNode.ts index 9733e034a9e..3cb2f79a668 100644 --- a/apps/vs-code-designer/src/app/commands/createChildNode.ts +++ b/apps/vs-code-designer/src/app/commands/createChildNode.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../constants'; import { ext } from '../../extensionVariables'; import type { AzExtParentTreeItem, IActionContext } from '@microsoft/vscode-azext-utils'; @@ -11,7 +12,13 @@ export async function createChildNode( node?: AzExtParentTreeItem ): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(expectedContextValue, context); + node = await ext.rgApi.pickAppResource( + { ...context, suppressCreatePick: true }, + { + filter: logicAppFilter, + expectedChildContextValue: expectedContextValue, + } + ); } await node.createChild(context); diff --git a/apps/vs-code-designer/src/app/commands/createLogicApp/createLogicApp.ts b/apps/vs-code-designer/src/app/commands/createLogicApp/createLogicApp.ts index 266fb5009f1..5f99d996a68 100644 --- a/apps/vs-code-designer/src/app/commands/createLogicApp/createLogicApp.ts +++ b/apps/vs-code-designer/src/app/commands/createLogicApp/createLogicApp.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; -import type { ProductionSlotTreeItem } from '../../tree/slotsTree/ProductionSlotTreeItem'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import { SubscriptionTreeItem } from '../../tree/subscriptionTree/SubscriptionTreeItem'; import { notifyCreateLogicAppComplete } from './notifyCreateLogicAppComplete'; import { isString } from '@microsoft/utils-logic-apps'; @@ -14,26 +14,31 @@ import type { ICreateLogicAppContext } from '@microsoft/vscode-extension'; export async function createLogicApp( context: IActionContext & Partial, subscription?: AzExtParentTreeItem | string, - newResourceGroupName?: string + nodesOrNewResourceGroupName?: string | (string | AzExtParentTreeItem)[] ): Promise { + const newResourceGroupName = Array.isArray(nodesOrNewResourceGroupName) ? undefined : nodesOrNewResourceGroupName; let node: AzExtParentTreeItem | undefined; if (isString(subscription)) { - node = await ext.tree.findTreeItem(`/subscriptions/${subscription}`, context); + node = await ext.rgApi.appResourceTree.findTreeItem(`/subscriptions/${subscription}`, context); if (!node) { throw new Error(localize('noMatchingSubscription', 'Failed to find a subscription matching id "{0}".', subscription)); } } else if (!subscription) { - node = await ext.tree.showTreeItemPicker(SubscriptionTreeItem.contextValue, context); + node = await ext.rgApi.appResourceTree.showTreeItemPicker(SubscriptionTreeItem.contextValue, context); } else { node = subscription; } context.newResourceGroupName = newResourceGroupName; + try { - const funcAppNode: ProductionSlotTreeItem = await node.createChild(context); - await notifyCreateLogicAppComplete(funcAppNode); - return funcAppNode.fullId; + const logicAppNode: SlotTreeItem = await SubscriptionTreeItem.createChild( + context as ICreateLogicAppContext, + node as SubscriptionTreeItem + ); + await notifyCreateLogicAppComplete(logicAppNode); + return logicAppNode.fullId; } catch (error) { throw new Error(`Error in creating logic app. ${error}`); } @@ -42,7 +47,7 @@ export async function createLogicApp( export async function createLogicAppAdvanced( context: IActionContext, subscription?: AzExtParentTreeItem | string, - newResourceGroupName?: string + nodesOrNewResourceGroupName?: string | (string | AzExtParentTreeItem)[] ): Promise { - return await createLogicApp({ ...context, advancedCreation: true }, subscription, newResourceGroupName); + return await createLogicApp({ ...context, advancedCreation: true }, subscription, nodesOrNewResourceGroupName); } diff --git a/apps/vs-code-designer/src/app/commands/createLogicApp/notifyCreateLogicAppComplete.ts b/apps/vs-code-designer/src/app/commands/createLogicApp/notifyCreateLogicAppComplete.ts index da5b58ce78c..063c95a238c 100644 --- a/apps/vs-code-designer/src/app/commands/createLogicApp/notifyCreateLogicAppComplete.ts +++ b/apps/vs-code-designer/src/app/commands/createLogicApp/notifyCreateLogicAppComplete.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import { callWithTelemetryAndErrorHandling } from '@microsoft/vscode-azext-utils'; import type { MessageItem } from 'vscode'; @@ -12,9 +12,9 @@ import { window } from 'vscode'; /** * Shows information message after the creation of Logic app has been completed and let user select post actions. - * @param {SlotTreeItemBase} node - Logic app node structure. + * @param {SlotTreeItem} node - Logic app node structure. */ -export async function notifyCreateLogicAppComplete(node: SlotTreeItemBase): Promise { +export async function notifyCreateLogicAppComplete(node: SlotTreeItem): Promise { const deployComplete: string = localize('creationComplete', 'Creation of "{0}" completed.', node.site.fullName); const viewOutput: MessageItem = { title: localize('viewOutput', 'View output') }; diff --git a/apps/vs-code-designer/src/app/commands/createLogicApp/showSiteCreated.ts b/apps/vs-code-designer/src/app/commands/createLogicApp/showSiteCreated.ts new file mode 100644 index 00000000000..9f5ce4adbad --- /dev/null +++ b/apps/vs-code-designer/src/app/commands/createLogicApp/showSiteCreated.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { viewOutput } from '../../../constants'; +import { ext } from '../../../extensionVariables'; +import { localize } from '../../../localize'; +import type { ParsedSite } from '@microsoft/vscode-azext-azureappservice'; +import type { IActionContext } from '@microsoft/vscode-azext-utils'; +import { window } from 'vscode'; + +export interface ISiteCreatedOptions extends IActionContext { + showCreatedNotification?: boolean; +} + +export function showSiteCreated(site: ParsedSite, context: ISiteCreatedOptions): void { + const message: string = site.isSlot + ? localize('createdNewSlot', 'Successfully created slot "{0}": {1}', site.slotName, site.defaultHostUrl) + : localize('createdNewApp', 'Successfully created logic app "{0}": {1}', site.fullName, site.defaultHostUrl); + + ext.outputChannel.appendLog(message); + + if (context.showCreatedNotification) { + // don't wait + void window.showInformationMessage(message, viewOutput).then((result) => { + if (result === viewOutput) { + ext.outputChannel.show(); + } + }); + } +} diff --git a/apps/vs-code-designer/src/app/commands/createSlot.ts b/apps/vs-code-designer/src/app/commands/createSlot.ts index 9dd0f79b860..3f5930be026 100644 --- a/apps/vs-code-designer/src/app/commands/createSlot.ts +++ b/apps/vs-code-designer/src/app/commands/createSlot.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../constants'; import { ext } from '../../extensionVariables'; import type { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; import { SlotsTreeItem } from '../tree/slotsTree/SlotsTreeItem'; @@ -9,7 +10,10 @@ import type { IActionContext } from '@microsoft/vscode-azext-utils'; export async function createSlot(context: IActionContext, node?: SlotsTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(SlotsTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: SlotsTreeItem.contextValue, + }); } const slotNode: SlotTreeItem = await node.createChild(context); diff --git a/apps/vs-code-designer/src/app/commands/deleteLogicApp/deleteLogicApp.ts b/apps/vs-code-designer/src/app/commands/deleteLogicApp/deleteLogicApp.ts new file mode 100644 index 00000000000..80e3f500794 --- /dev/null +++ b/apps/vs-code-designer/src/app/commands/deleteLogicApp/deleteLogicApp.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; +import { ext } from '../../../extensionVariables'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; +import type { IActionContext } from '@microsoft/vscode-azext-utils'; + +export async function deleteLogicApp(context: IActionContext, node?: SlotTreeItem): Promise { + if (!node) { + node = await ext.rgApi.pickAppResource( + { ...context, suppressCreatePick: true }, + { + filter: logicAppFilter, + } + ); + } + + await node.deleteTreeItem(context); +} diff --git a/apps/vs-code-designer/src/app/commands/deleteNode.ts b/apps/vs-code-designer/src/app/commands/deleteNode.ts index 5f68b535bac..0461f81693f 100644 --- a/apps/vs-code-designer/src/app/commands/deleteNode.ts +++ b/apps/vs-code-designer/src/app/commands/deleteNode.ts @@ -2,12 +2,19 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../constants'; import { ext } from '../../extensionVariables'; import type { AzExtTreeItem, IActionContext } from '@microsoft/vscode-azext-utils'; export async function deleteNode(context: IActionContext, expectedContextValue: string | RegExp, node?: AzExtTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(expectedContextValue, { ...context, suppressCreatePick: true }); + node = await ext.rgApi.pickAppResource( + { ...context, suppressCreatePick: true }, + { + filter: logicAppFilter, + expectedChildContextValue: expectedContextValue, + } + ); } await node.deleteTreeItem(context); diff --git a/apps/vs-code-designer/src/app/commands/deploy/deploy.ts b/apps/vs-code-designer/src/app/commands/deploy/deploy.ts index 26e29668282..c9ed049dc18 100644 --- a/apps/vs-code-designer/src/app/commands/deploy/deploy.ts +++ b/apps/vs-code-designer/src/app/commands/deploy/deploy.ts @@ -13,12 +13,12 @@ import { workflowAppAADTenantId, kubernetesKind, showDeployConfirmationSetting, + logicAppFilter, } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; -import { ProductionSlotTreeItem } from '../../tree/slotsTree/ProductionSlotTreeItem'; -import { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import { LogicAppResourceTree } from '../../tree/LogicAppResourceTree'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import { createAclInConnectionIfNeeded, getConnectionsJson } from '../../utils/codeless/connection'; import { getParametersJson } from '../../utils/codeless/parameter'; import { isPathEqual, writeFormattedJson } from '../../utils/fs'; @@ -31,14 +31,12 @@ import { AdvancedIdentityTenantIdStep, AdvancedIdentityClientSecretStep, } from '../createLogicApp/createLogicAppSteps/AdvancedIdentityPromptSteps'; -import { getDeployNode as getInnerDeployNode } from './getDeployNode'; -import type { IDeployNode } from './getDeployNode'; import { notifyDeployComplete } from './notifyDeployComplete'; import { updateAppSettingsWithIdentityDetails } from './updateAppSettings'; import { verifyAppSettings } from './verifyAppSettings'; import type { SiteConfigResource, StringDictionary } from '@azure/arm-appservice'; import { ResolutionService } from '@microsoft/parsers-logic-apps'; -import { deploy as innerDeploy, getDeployFsPath, runPreDeployTask } from '@microsoft/vscode-azext-azureappservice'; +import { deploy as innerDeploy, getDeployFsPath, runPreDeployTask, getDeployNode } from '@microsoft/vscode-azext-azureappservice'; import type { IDeployContext } from '@microsoft/vscode-azext-azureappservice'; import { ScmType } from '@microsoft/vscode-azext-azureappservice/out/src/ScmType'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; @@ -50,25 +48,25 @@ import type { Uri, MessageItem, WorkspaceFolder } from 'vscode'; export async function deployProductionSlot( context: IActionContext, - target?: Uri | string | SlotTreeItemBase, + target?: Uri | string | SlotTreeItem, functionAppId?: string | Record ): Promise { - await deploy(context, target, functionAppId, ProductionSlotTreeItem.contextValue); + await deploy(context, target, functionAppId); } export async function deploySlot( context: IActionContext, - target?: Uri | string | SlotTreeItemBase, + target?: Uri | string | SlotTreeItem, functionAppId?: string | Record ): Promise { - await deploy(context, target, functionAppId, SlotTreeItem.contextValue); + await deploy(context, target, functionAppId, new RegExp(LogicAppResourceTree.pickSlotContextValue)); } async function deploy( actionContext: IActionContext, - target: Uri | string | SlotTreeItemBase | undefined, + target: Uri | string | SlotTreeItem | undefined, functionAppId: string | Record | undefined, - expectedContextValue: string + expectedContextValue?: string | RegExp ): Promise { addLocalFuncTelemetry(actionContext); @@ -80,7 +78,15 @@ async function deploy( ext.deploymentFolderPath = originalDeployFsPath; - const { node, isNewFunctionApp }: IDeployNode = await getInnerDeployNode(context, target, functionAppId, expectedContextValue); + const node: SlotTreeItem = await getDeployNode(context, ext.rgApi.appResourceTree, target, functionAppId, async () => + ext.rgApi.pickAppResource( + { ...context, suppressCreatePick: false }, + { + filter: logicAppFilter, + expectedChildContextValue: expectedContextValue, + } + ) + ); const nodeKind = node.site.kind && node.site.kind.toLowerCase(); const isWorkflowApp = nodeKind?.includes(logicAppKind); @@ -120,13 +126,13 @@ async function deploy( identityWizardContext?.useAdvancedIdentity ? await updateAppSettingsWithIdentityDetails(context, node, identityWizardContext) : undefined; - await verifyAppSettings(context, node, version, language, originalDeployFsPath, isNewFunctionApp); + await verifyAppSettings(context, node, version, language, originalDeployFsPath, !context.isNewApp); const client = await node.site.createClient(actionContext); const siteConfig: SiteConfigResource = await client.getSiteConfig(); const isZipDeploy: boolean = siteConfig.scmType !== ScmType.LocalGit && siteConfig.scmType !== ScmType.GitHub; - if (getWorkspaceSetting(showDeployConfirmationSetting, workspaceFolder.uri.fsPath) && !isNewFunctionApp && isZipDeploy) { + if (getWorkspaceSetting(showDeployConfirmationSetting, workspaceFolder.uri.fsPath) && !context.isNewApp && isZipDeploy) { const warning: string = localize( 'confirmDeploy', 'Are you sure you want to deploy to "{0}"? This will overwrite any previous deployment and cannot be undone.', @@ -207,7 +213,7 @@ async function managedApiConnectionsExists(workspaceFolder: WorkspaceFolder): Pr } async function getProjectPathToDeploy( - node: SlotTreeItemBase, + node: SlotTreeItem, workspaceFolder: WorkspaceFolder, settingsToExclude: string[], originalDeployFsPath: string, @@ -277,7 +283,7 @@ async function cleanAndRemoveDeployFolder(deployProjectPath: string): Promise { +async function checkAADDetailsExistsInAppSettings(node: SlotTreeItem, identityWizardContext: IIdentityWizardContext): Promise { const client = await node.site.createClient(identityWizardContext); const appSettings: StringDictionary | undefined = (await client.listApplicationSettings())?.properties; if (appSettings) { diff --git a/apps/vs-code-designer/src/app/commands/deploy/getDeployNode.ts b/apps/vs-code-designer/src/app/commands/deploy/getDeployNode.ts deleted file mode 100644 index f69400f1116..00000000000 --- a/apps/vs-code-designer/src/app/commands/deploy/getDeployNode.ts +++ /dev/null @@ -1,49 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { ext } from '../../../extensionVariables'; -import { localize } from '../../../localize'; -import { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; -import { isString } from '@microsoft/utils-logic-apps'; -import type { AzExtTreeItem, IActionContext } from '@microsoft/vscode-azext-utils'; -import type { Disposable, Uri } from 'vscode'; - -export interface IDeployNode { - node: SlotTreeItemBase; - isNewFunctionApp: boolean; -} - -export async function getDeployNode( - context: IActionContext, - target: Uri | string | SlotTreeItemBase | undefined, - functionAppId: string | Record | undefined, - expectedContextValue: string -): Promise { - let node: SlotTreeItemBase | undefined; - let isNewFunctionApp = false; - - if (target instanceof SlotTreeItemBase) { - node = target; - } else if (functionAppId && isString(functionAppId)) { - node = await ext.tree.findTreeItem(functionAppId, context); - if (!node) { - throw new Error(localize('noMatchingFunctionApp', 'Failed to find a Logic App (Standard) matching id "{0}".', functionAppId)); - } - } else { - const newNodes: SlotTreeItemBase[] = []; - const disposable: Disposable = ext.tree.onTreeItemCreate((newNode: SlotTreeItemBase) => { - newNodes.push(newNode); - }); - try { - node = await ext.tree.showTreeItemPicker(expectedContextValue, context); - } finally { - disposable.dispose(); - } - - isNewFunctionApp = newNodes.some((newNode: AzExtTreeItem) => node && newNode.fullId === node.fullId); - } - - context.telemetry.properties.isNewFunctionApp = String(isNewFunctionApp); - return { node, isNewFunctionApp }; -} diff --git a/apps/vs-code-designer/src/app/commands/deploy/notifyDeployComplete.ts b/apps/vs-code-designer/src/app/commands/deploy/notifyDeployComplete.ts index a6129f9e3d4..f4497be6daa 100644 --- a/apps/vs-code-designer/src/app/commands/deploy/notifyDeployComplete.ts +++ b/apps/vs-code-designer/src/app/commands/deploy/notifyDeployComplete.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import { uploadAppSettings } from '../appSettings/uploadAppSettings'; import { startStreamingLogs } from '../logstream/startStreamingLogs'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; @@ -14,12 +14,12 @@ import { window } from 'vscode'; /** * Shows information message after deployment has been completed and let user select post actions. - * @param {SlotTreeItemBase} node - Logic app node structure. + * @param {SlotTreeItem} node - Logic app node structure. * @param {WorkspaceFolder} workspaceFolder - Workspace folder path. * @param {string[]} settingsToExclude - Array of settings to exclude from uploading. */ export async function notifyDeployComplete( - node: SlotTreeItemBase, + node: SlotTreeItem, workspaceFolder: WorkspaceFolder, settingsToExclude?: string[] ): Promise { @@ -36,7 +36,7 @@ export async function notifyDeployComplete( } else if (result === streamLogs) { await startStreamingLogs(postDeployContext, node); } else if (result === uploadSettings) { - await uploadAppSettings(postDeployContext, node.appSettingsTreeItem, workspaceFolder, settingsToExclude); + await uploadAppSettings(postDeployContext, node.appSettingsTreeItem, undefined, workspaceFolder, settingsToExclude); } }); }); diff --git a/apps/vs-code-designer/src/app/commands/deploy/updateAppSettings.ts b/apps/vs-code-designer/src/app/commands/deploy/updateAppSettings.ts index ee5d637011a..3bf9634d5f8 100644 --- a/apps/vs-code-designer/src/app/commands/deploy/updateAppSettings.ts +++ b/apps/vs-code-designer/src/app/commands/deploy/updateAppSettings.ts @@ -1,5 +1,5 @@ import { workflowAppAADClientId, workflowAppAADClientSecret, workflowAppAADObjectId, workflowAppAADTenantId } from '../../../constants'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import type { StringDictionary } from '@azure/arm-appservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import type { IIdentityWizardContext } from '@microsoft/vscode-extension'; @@ -7,12 +7,12 @@ import type { IIdentityWizardContext } from '@microsoft/vscode-extension'; /** * Updates remote logic app settings with identity details. * @param {IActionContext} context - Command context. - * @param {SlotTreeItemBase} node - Logic app node structure. + * @param {SlotTreeItem} node - Logic app node structure. * @param {IIdentityWizardContext} identityWizardContext - Identity context. */ export async function updateAppSettingsWithIdentityDetails( context: IActionContext, - node: SlotTreeItemBase, + node: SlotTreeItem, identityWizardContext: IIdentityWizardContext ): Promise { const client = await node.site.createClient(context); diff --git a/apps/vs-code-designer/src/app/commands/deploy/verifyAppSettings.ts b/apps/vs-code-designer/src/app/commands/deploy/verifyAppSettings.ts index e059b91ea14..e28d10d0fdc 100644 --- a/apps/vs-code-designer/src/app/commands/deploy/verifyAppSettings.ts +++ b/apps/vs-code-designer/src/app/commands/deploy/verifyAppSettings.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { extensionVersionKey, workerRuntimeKey } from '../../../constants'; import { localize } from '../../../localize'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import { verifyDeploymentResourceGroup } from '../../utils/codeless/common'; import { tryParseFuncVersion } from '../../utils/funcCoreTools/funcVersion'; import { getFunctionsWorkerRuntime } from '../../utils/vsCodeConfig/settings'; @@ -17,7 +17,7 @@ import type { MessageItem } from 'vscode'; /** * Verifies remote app settings. * @param {IActionContext} context - Command context. - * @param {SlotTreeItemBase} node - Logic app node structure. + * @param {SlotTreeItem} node - Logic app node structure. * @param {FuncVersion} version - Function core tools local version. * @param {ProjectLanguage} language - Project local language. * @param {string} originalDeployFsPath - Workflow path to deploy. @@ -25,7 +25,7 @@ import type { MessageItem } from 'vscode'; */ export async function verifyAppSettings( context: IActionContext, - node: SlotTreeItemBase, + node: SlotTreeItem, version: FuncVersion, language: ProjectLanguage, originalDeployFsPath: string, @@ -102,12 +102,12 @@ export async function verifyVersionAndLanguage( /** * Gets remote resource group and verifies deployment of it. * @param {IActionContext} context - Command context. - * @param {SlotTreeItemBase} node - Logic app node structure. + * @param {SlotTreeItem} node - Logic app node structure. * @param {string} originalDeployFsPath - Workflow path to deploy. */ export async function verifyConnectionResourceGroup( context: IActionContext, - node: SlotTreeItemBase, + node: SlotTreeItem, originalDeployFsPath: string ): Promise { const workflowResourceGroupRemote = node.site.resourceGroup; diff --git a/apps/vs-code-designer/src/app/commands/deployments/connectToGitHub.ts b/apps/vs-code-designer/src/app/commands/deployments/connectToGitHub.ts index 5e92397e887..fe9ccfd18bc 100644 --- a/apps/vs-code-designer/src/app/commands/deployments/connectToGitHub.ts +++ b/apps/vs-code-designer/src/app/commands/deployments/connectToGitHub.ts @@ -2,34 +2,29 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; -import { ProductionSlotTreeItem } from '../../tree/slotsTree/ProductionSlotTreeItem'; import { isSlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; -import type { DeploymentsTreeItem } from '@microsoft/vscode-azext-azureappservice'; -import { editScmType } from '@microsoft/vscode-azext-azureappservice'; +import { editScmType, DeploymentsTreeItem } from '@microsoft/vscode-azext-azureappservice'; import { ScmType } from '@microsoft/vscode-azext-azureappservice/out/src/ScmType'; import type { GenericTreeItem, IActionContext } from '@microsoft/vscode-azext-utils'; export async function connectToGitHub(context: IActionContext, target?: GenericTreeItem): Promise { - let node: ProductionSlotTreeItem | DeploymentsTreeItem; + let deployments: DeploymentsTreeItem; if (!target) { - node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + deployments = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: new RegExp(DeploymentsTreeItem.contextValueUnconnected), + }); } else { - node = target.parent as DeploymentsTreeItem; + deployments = target.parent as DeploymentsTreeItem; } - if (node && isSlotTreeItem(node)) { - await editScmType(context, node.site, node.subscription, ScmType.GitHub); + if (deployments.parent && isSlotTreeItem(deployments.parent)) { + await editScmType(context, deployments.site, deployments.subscription, ScmType.GitHub); + await deployments.refresh(context); } else { throw Error('Internal error: Action not supported.'); } - - if (node instanceof ProductionSlotTreeItem) { - if (node.deploymentsNode) { - await node.deploymentsNode.refresh(context); - } - } else { - await node.parent?.refresh(context); - } } diff --git a/apps/vs-code-designer/src/app/commands/deployments/disconnectRepo.ts b/apps/vs-code-designer/src/app/commands/deployments/disconnectRepo.ts index ea183615986..3b639c32a86 100644 --- a/apps/vs-code-designer/src/app/commands/deployments/disconnectRepo.ts +++ b/apps/vs-code-designer/src/app/commands/deployments/disconnectRepo.ts @@ -2,17 +2,21 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; -import { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import { isLogicAppResourceTree } from '../../tree/LogicAppResourceTree'; import { DeploymentsTreeItem, disconnectRepo as disconnectRepository } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; export async function disconnectRepo(context: IActionContext, node?: DeploymentsTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(DeploymentsTreeItem.contextValueConnected, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: new RegExp(DeploymentsTreeItem.contextValueConnected), + }); } - if (node.parent instanceof SlotTreeItemBase) { + if (isLogicAppResourceTree(node.parent)) { await disconnectRepository(context, node.site, node.subscription); await node.refresh(context); } else { diff --git a/apps/vs-code-designer/src/app/commands/deployments/redeployDeployment.ts b/apps/vs-code-designer/src/app/commands/deployments/redeployDeployment.ts index 97d7f438cc5..f2fe5171664 100644 --- a/apps/vs-code-designer/src/app/commands/deployments/redeployDeployment.ts +++ b/apps/vs-code-designer/src/app/commands/deployments/redeployDeployment.ts @@ -2,13 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { DeploymentTreeItem } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; export async function redeployDeployment(context: IActionContext, node?: DeploymentTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(DeploymentTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: DeploymentTreeItem.contextValue, + }); } await node.redeployDeployment(context); } diff --git a/apps/vs-code-designer/src/app/commands/deployments/viewCommitInGitHub.ts b/apps/vs-code-designer/src/app/commands/deployments/viewCommitInGitHub.ts index 17fb569cef2..14088bd2c68 100644 --- a/apps/vs-code-designer/src/app/commands/deployments/viewCommitInGitHub.ts +++ b/apps/vs-code-designer/src/app/commands/deployments/viewCommitInGitHub.ts @@ -2,13 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; import type { DeploymentTreeItem } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; export async function viewCommitInGitHub(context: IActionContext, node?: DeploymentTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker('deployment/github', context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: 'deployment/github', + }); } await node.viewCommitInGitHub(context); } diff --git a/apps/vs-code-designer/src/app/commands/deployments/viewDeploymentLogs.ts b/apps/vs-code-designer/src/app/commands/deployments/viewDeploymentLogs.ts index 9fc469e964b..a1f364a3675 100644 --- a/apps/vs-code-designer/src/app/commands/deployments/viewDeploymentLogs.ts +++ b/apps/vs-code-designer/src/app/commands/deployments/viewDeploymentLogs.ts @@ -2,13 +2,17 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { DeploymentTreeItem } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; export async function viewDeploymentLogs(context: IActionContext, node?: DeploymentTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(DeploymentTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + expectedChildContextValue: DeploymentTreeItem.contextValue, + }); } await node.viewDeploymentLogs(context); } diff --git a/apps/vs-code-designer/src/app/commands/logstream/startStreamingLogs.ts b/apps/vs-code-designer/src/app/commands/logstream/startStreamingLogs.ts index 2b81925e119..ae626838556 100644 --- a/apps/vs-code-designer/src/app/commands/logstream/startStreamingLogs.ts +++ b/apps/vs-code-designer/src/app/commands/logstream/startStreamingLogs.ts @@ -2,10 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; -import { ProductionSlotTreeItem } from '../../tree/slotsTree/ProductionSlotTreeItem'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import { enableFileLogging } from './enableFileLogging'; import type { ApplicationInsightsManagementClient, ApplicationInsightsComponent } from '@azure/arm-appinsights'; import type { SiteLogsConfig, StringDictionary } from '@azure/arm-appservice'; @@ -18,11 +18,13 @@ import type { AzExtTreeItem, IActionContext } from '@microsoft/vscode-azext-util /** * Start streaming logs to remote app. * @param {IActionContext} context - Workflow file path. - * @param {SlotTreeItemBase} treeItem - Logic app node structure. + * @param {SlotTreeItem} treeItem - Logic app node structure. */ -export async function startStreamingLogs(context: IActionContext, treeItem?: SlotTreeItemBase): Promise { +export async function startStreamingLogs(context: IActionContext, treeItem?: SlotTreeItem): Promise { if (!treeItem) { - treeItem = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + treeItem = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + }); } const site: ParsedSite = treeItem.site; diff --git a/apps/vs-code-designer/src/app/commands/logstream/stopStreamingLogs.ts b/apps/vs-code-designer/src/app/commands/logstream/stopStreamingLogs.ts index 861b7a97c8b..bf64cc0cf55 100644 --- a/apps/vs-code-designer/src/app/commands/logstream/stopStreamingLogs.ts +++ b/apps/vs-code-designer/src/app/commands/logstream/stopStreamingLogs.ts @@ -2,16 +2,21 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; -import { ProductionSlotTreeItem } from '../../tree/slotsTree/ProductionSlotTreeItem'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import * as appservice from '@microsoft/vscode-azext-azureappservice'; import type { ParsedSite } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; -export async function stopStreamingLogs(context: IActionContext, node?: SlotTreeItemBase): Promise { +export async function stopStreamingLogs(context: IActionContext, node?: SlotTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource( + { ...context, suppressCreatePick: true }, + { + filter: logicAppFilter, + } + ); } const site: ParsedSite = node.site; diff --git a/apps/vs-code-designer/src/app/commands/openInPortal.ts b/apps/vs-code-designer/src/app/commands/openInPortal.ts index addb52b7ce4..83536d20d25 100644 --- a/apps/vs-code-designer/src/app/commands/openInPortal.ts +++ b/apps/vs-code-designer/src/app/commands/openInPortal.ts @@ -2,14 +2,16 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../constants'; import { ext } from '../../extensionVariables'; -import { ProductionSlotTreeItem } from '../tree/slotsTree/ProductionSlotTreeItem'; import { openInPortal as uiOpenInPortal } from '@microsoft/vscode-azext-azureutils'; import type { AzExtTreeItem, IActionContext } from '@microsoft/vscode-azext-utils'; export async function openInPortal(context: IActionContext, node?: AzExtTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + }); } await uiOpenInPortal(node, node.fullId); diff --git a/apps/vs-code-designer/src/app/commands/registerCommands.ts b/apps/vs-code-designer/src/app/commands/registerCommands.ts index 54bae918071..ebb46760cce 100644 --- a/apps/vs-code-designer/src/app/commands/registerCommands.ts +++ b/apps/vs-code-designer/src/app/commands/registerCommands.ts @@ -5,8 +5,7 @@ import { extensionCommand } from '../../constants'; import { ext } from '../../extensionVariables'; import { executeOnFunctions } from '../functionsExtension/executeOnFunctionsExt'; -import { ProductionSlotTreeItem } from '../tree/slotsTree/ProductionSlotTreeItem'; -import { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; +import { LogicAppResourceTree } from '../tree/LogicAppResourceTree'; import { downloadAppSettings } from './appSettings/downloadAppSettings'; import { editAppSetting } from './appSettings/editAppSetting'; import { renameAppSetting } from './appSettings/renameAppSetting'; @@ -19,6 +18,7 @@ import { createCodeless } from './createCodeless/createCodeless'; import { createLogicApp, createLogicAppAdvanced } from './createLogicApp/createLogicApp'; import { createNewProjectFromCommand } from './createNewProject/createNewProject'; import { createSlot } from './createSlot'; +import { deleteLogicApp } from './deleteLogicApp/deleteLogicApp'; import { deleteNode } from './deleteNode'; import { deployProductionSlot, deploySlot } from './deploy/deploy'; import { connectToGitHub } from './deployments/connectToGitHub'; @@ -51,85 +51,75 @@ import { useSQLStorage } from './workflows/useSQLStorage'; import { viewContent } from './workflows/viewContent'; import { AppSettingsTreeItem, AppSettingTreeItem, registerSiteCommand } from '@microsoft/vscode-azext-azureappservice'; import type { FileTreeItem } from '@microsoft/vscode-azext-azureappservice'; -import { registerCommand } from '@microsoft/vscode-azext-utils'; +import { registerCommand, registerCommandWithTreeNodeUnwrapping, unwrapTreeNodeCommandCallback } from '@microsoft/vscode-azext-utils'; import type { AzExtTreeItem, IActionContext, AzExtParentTreeItem } from '@microsoft/vscode-azext-utils'; -import { commands } from 'vscode'; export function registerCommands(): void { - registerCommand(extensionCommand.openDesigner, openDesigner); - registerCommand( - extensionCommand.loadMore, - async (context: IActionContext, node: AzExtTreeItem) => await ext.tree.loadMore(node, context) - ); - registerCommand(extensionCommand.selectSubscriptions, () => commands.executeCommand(extensionCommand.azureSelectSubscriptions)); - registerCommand(extensionCommand.openFile, (context: IActionContext, node: FileTreeItem) => + registerCommandWithTreeNodeUnwrapping(extensionCommand.openDesigner, openDesigner); + registerCommandWithTreeNodeUnwrapping(extensionCommand.openFile, (context: IActionContext, node: FileTreeItem) => executeOnFunctions(openFile, context, context, node) ); - registerCommand(extensionCommand.viewContent, viewContent); + registerCommandWithTreeNodeUnwrapping(extensionCommand.viewContent, viewContent); registerCommand(extensionCommand.createNewProject, createNewProjectFromCommand); registerCommand(extensionCommand.createCodeless, createCodeless); - registerCommand(extensionCommand.createLogicApp, createLogicApp); - registerCommand(extensionCommand.createLogicAppAdvanced, createLogicAppAdvanced); - registerSiteCommand(extensionCommand.deploy, deployProductionSlot); - registerSiteCommand(extensionCommand.deploySlot, deploySlot); - registerSiteCommand(extensionCommand.redeploy, redeployDeployment); - registerCommand(extensionCommand.showOutputChannel, () => { + registerCommandWithTreeNodeUnwrapping(extensionCommand.createLogicApp, createLogicApp); + registerCommandWithTreeNodeUnwrapping(extensionCommand.createLogicAppAdvanced, createLogicAppAdvanced); + registerSiteCommand(extensionCommand.deploy, unwrapTreeNodeCommandCallback(deployProductionSlot)); + registerSiteCommand(extensionCommand.deploySlot, unwrapTreeNodeCommandCallback(deploySlot)); + registerSiteCommand(extensionCommand.redeploy, unwrapTreeNodeCommandCallback(redeployDeployment)); + registerCommandWithTreeNodeUnwrapping(extensionCommand.showOutputChannel, () => { ext.outputChannel.show(); }); - registerCommand(extensionCommand.startLogicApp, startLogicApp); - registerCommand(extensionCommand.stopLogicApp, stopLogicApp); - registerCommand(extensionCommand.restartLogicApp, restartLogicApp); - registerCommand(extensionCommand.pickProcess, pickFuncProcess); - registerCommand(extensionCommand.getDebugSymbolDll, getDebugSymbolDll); - registerCommand( - extensionCommand.deleteLogicApp, - async (context: IActionContext, node?: AzExtTreeItem) => await deleteNode(context, ProductionSlotTreeItem.contextValue, node) - ); - registerCommand(extensionCommand.openOverview, openOverview); - registerCommand(extensionCommand.refresh, async (context: IActionContext, node?: AzExtTreeItem) => await ext.tree.refresh(context, node)); - registerCommand(extensionCommand.exportLogicApp, exportLogicApp); - registerCommand(extensionCommand.reviewValidation, reviewValidation); - registerCommand(extensionCommand.switchToDotnetProject, switchToDotnetProject); - registerCommand(extensionCommand.openInPortal, openInPortal); - registerCommand(extensionCommand.browseWebsite, browseWebsite); - registerCommand(extensionCommand.viewProperties, viewProperties); - registerCommand(extensionCommand.createSlot, createSlot); - registerCommand( + registerCommandWithTreeNodeUnwrapping(extensionCommand.startLogicApp, startLogicApp); + registerCommandWithTreeNodeUnwrapping(extensionCommand.stopLogicApp, stopLogicApp); + registerCommandWithTreeNodeUnwrapping(extensionCommand.restartLogicApp, restartLogicApp); + registerCommandWithTreeNodeUnwrapping(extensionCommand.pickProcess, pickFuncProcess); + registerCommandWithTreeNodeUnwrapping(extensionCommand.getDebugSymbolDll, getDebugSymbolDll); + registerCommandWithTreeNodeUnwrapping(extensionCommand.deleteLogicApp, deleteLogicApp); + registerCommandWithTreeNodeUnwrapping(extensionCommand.openOverview, openOverview); + registerCommandWithTreeNodeUnwrapping(extensionCommand.exportLogicApp, exportLogicApp); + registerCommandWithTreeNodeUnwrapping(extensionCommand.reviewValidation, reviewValidation); + registerCommandWithTreeNodeUnwrapping(extensionCommand.switchToDotnetProject, switchToDotnetProject); + registerCommandWithTreeNodeUnwrapping(extensionCommand.openInPortal, openInPortal); + registerCommandWithTreeNodeUnwrapping(extensionCommand.browseWebsite, browseWebsite); + registerCommandWithTreeNodeUnwrapping(extensionCommand.viewProperties, viewProperties); + registerCommandWithTreeNodeUnwrapping(extensionCommand.createSlot, createSlot); + registerCommandWithTreeNodeUnwrapping( extensionCommand.deleteSlot, - async (context: IActionContext, node?: AzExtTreeItem) => await deleteNode(context, SlotTreeItem.contextValue, node) + async (context: IActionContext, node?: AzExtTreeItem) => await deleteNode(context, LogicAppResourceTree.pickSlotContextValue, node) ); - registerCommand(extensionCommand.swapSlot, swapSlot); - registerCommand(extensionCommand.startStreamingLogs, startStreamingLogs); - registerCommand(extensionCommand.stopStreamingLogs, stopStreamingLogs); - registerSiteCommand(extensionCommand.viewDeploymentLogs, viewDeploymentLogs); - registerCommand(extensionCommand.switchDebugMode, switchDebugMode); - registerCommand( + registerCommandWithTreeNodeUnwrapping(extensionCommand.swapSlot, swapSlot); + registerCommandWithTreeNodeUnwrapping(extensionCommand.startStreamingLogs, startStreamingLogs); + registerCommandWithTreeNodeUnwrapping(extensionCommand.stopStreamingLogs, stopStreamingLogs); + registerSiteCommand(extensionCommand.viewDeploymentLogs, unwrapTreeNodeCommandCallback(viewDeploymentLogs)); + registerCommandWithTreeNodeUnwrapping(extensionCommand.switchDebugMode, switchDebugMode); + registerCommandWithTreeNodeUnwrapping( extensionCommand.toggleAppSettingVisibility, async (context: IActionContext, node: AppSettingTreeItem) => { await node.toggleValueVisibility(context); }, 250 ); - registerCommand( + registerCommandWithTreeNodeUnwrapping( extensionCommand.appSettingsAdd, async (context: IActionContext, node?: AzExtParentTreeItem) => await createChildNode(context, AppSettingsTreeItem.contextValue, node) ); - registerCommand( + registerCommandWithTreeNodeUnwrapping( extensionCommand.appSettingsDelete, async (context: IActionContext, node?: AzExtTreeItem) => await deleteNode(context, AppSettingTreeItem.contextValue, node) ); - registerCommand(extensionCommand.appSettingsDownload, downloadAppSettings); - registerCommand(extensionCommand.appSettingsEdit, editAppSetting); - registerCommand(extensionCommand.appSettingsRename, renameAppSetting); - registerCommand(extensionCommand.appSettingsToggleSlotSetting, toggleSlotSetting); - registerCommand(extensionCommand.appSettingsUpload, uploadAppSettings); - registerCommand(extensionCommand.configureWebhookRedirectEndpoint, configureWebhookRedirectEndpoint); - registerCommand(extensionCommand.useSQLStorage, useSQLStorage); - registerCommand(extensionCommand.connectToGitHub, connectToGitHub); - registerCommand(extensionCommand.disconnectRepo, disconnectRepo); - registerCommand(extensionCommand.viewCommitInGitHub, viewCommitInGitHub); - registerCommand(extensionCommand.enableAzureConnectors, enableAzureConnectors); + registerCommandWithTreeNodeUnwrapping(extensionCommand.appSettingsDownload, downloadAppSettings); + registerCommandWithTreeNodeUnwrapping(extensionCommand.appSettingsEdit, editAppSetting); + registerCommandWithTreeNodeUnwrapping(extensionCommand.appSettingsRename, renameAppSetting); + registerCommandWithTreeNodeUnwrapping(extensionCommand.appSettingsToggleSlotSetting, toggleSlotSetting); + registerCommandWithTreeNodeUnwrapping(extensionCommand.appSettingsUpload, uploadAppSettings); + registerCommandWithTreeNodeUnwrapping(extensionCommand.configureWebhookRedirectEndpoint, configureWebhookRedirectEndpoint); + registerCommandWithTreeNodeUnwrapping(extensionCommand.useSQLStorage, useSQLStorage); + registerCommandWithTreeNodeUnwrapping(extensionCommand.connectToGitHub, connectToGitHub); + registerCommandWithTreeNodeUnwrapping(extensionCommand.disconnectRepo, disconnectRepo); + registerCommandWithTreeNodeUnwrapping(extensionCommand.viewCommitInGitHub, viewCommitInGitHub); + registerCommandWithTreeNodeUnwrapping(extensionCommand.enableAzureConnectors, enableAzureConnectors); registerCommand(extensionCommand.initProjectForVSCode, initProjectForVSCode); - registerCommand(extensionCommand.configureDeploymentSource, configureDeploymentSource); - registerCommand(extensionCommand.startRemoteDebug, startRemoteDebug); + registerCommandWithTreeNodeUnwrapping(extensionCommand.configureDeploymentSource, configureDeploymentSource); + registerCommandWithTreeNodeUnwrapping(extensionCommand.startRemoteDebug, startRemoteDebug); } diff --git a/apps/vs-code-designer/src/app/commands/remoteDebug/startRemoteDebug.ts b/apps/vs-code-designer/src/app/commands/remoteDebug/startRemoteDebug.ts index 1b20de3d0d5..daf0bc82499 100644 --- a/apps/vs-code-designer/src/app/commands/remoteDebug/startRemoteDebug.ts +++ b/apps/vs-code-designer/src/app/commands/remoteDebug/startRemoteDebug.ts @@ -2,18 +2,20 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../../constants'; import { ext } from '../../../extensionVariables'; -import { ProductionSlotTreeItem } from '../../tree/slotsTree/ProductionSlotTreeItem'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import { getRemoteDebugLanguage } from './getRemoteDebugLanguage'; import type { SiteConfig } from '@azure/arm-appservice'; import * as appservice from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import * as vscode from 'vscode'; -export async function startRemoteDebug(context: IActionContext, node?: SlotTreeItemBase): Promise { +export async function startRemoteDebug(context: IActionContext, node?: SlotTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + }); } const siteClient = await node.site.createClient(context); diff --git a/apps/vs-code-designer/src/app/commands/restartLogicApp.ts b/apps/vs-code-designer/src/app/commands/restartLogicApp.ts index 5092b81a711..341a251bbc2 100644 --- a/apps/vs-code-designer/src/app/commands/restartLogicApp.ts +++ b/apps/vs-code-designer/src/app/commands/restartLogicApp.ts @@ -2,12 +2,12 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { SlotTreeItemBase } from '../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; import { startLogicApp } from './startLogicApp'; import { stopLogicApp } from './stopLogicApp'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; -export async function restartLogicApp(context: IActionContext, node?: SlotTreeItemBase): Promise { +export async function restartLogicApp(context: IActionContext, node?: SlotTreeItem): Promise { node = await stopLogicApp(context, node); await startLogicApp(context, node); } diff --git a/apps/vs-code-designer/src/app/commands/startLogicApp.ts b/apps/vs-code-designer/src/app/commands/startLogicApp.ts index 48bf04991de..1bab8c5e619 100644 --- a/apps/vs-code-designer/src/app/commands/startLogicApp.ts +++ b/apps/vs-code-designer/src/app/commands/startLogicApp.ts @@ -2,16 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../constants'; import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; -import { ProductionSlotTreeItem } from '../tree/slotsTree/ProductionSlotTreeItem'; -import type { SlotTreeItemBase } from '../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; import type { SiteClient } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; -export async function startLogicApp(context: IActionContext, node?: SlotTreeItemBase): Promise { +export async function startLogicApp(context: IActionContext, node?: SlotTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + }); } const client: SiteClient = await node.site.createClient(context); diff --git a/apps/vs-code-designer/src/app/commands/stopLogicApp.ts b/apps/vs-code-designer/src/app/commands/stopLogicApp.ts index a612e8deb53..9e7d630ef2f 100644 --- a/apps/vs-code-designer/src/app/commands/stopLogicApp.ts +++ b/apps/vs-code-designer/src/app/commands/stopLogicApp.ts @@ -2,16 +2,18 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../constants'; import { ext } from '../../extensionVariables'; import { localize } from '../../localize'; -import { ProductionSlotTreeItem } from '../tree/slotsTree/ProductionSlotTreeItem'; -import type { SlotTreeItemBase } from '../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; import type { SiteClient } from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; -export async function stopLogicApp(context: IActionContext, node?: SlotTreeItemBase): Promise { +export async function stopLogicApp(context: IActionContext, node?: SlotTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + node = await ext.rgApi.pickAppResource(context, { + filter: logicAppFilter, + }); } const client: SiteClient = await node.site.createClient(context); diff --git a/apps/vs-code-designer/src/app/commands/swapSlot.ts b/apps/vs-code-designer/src/app/commands/swapSlot.ts index 98f98376e7e..81fee71020a 100644 --- a/apps/vs-code-designer/src/app/commands/swapSlot.ts +++ b/apps/vs-code-designer/src/app/commands/swapSlot.ts @@ -2,14 +2,22 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { logicAppFilter } from '../../constants'; import { ext } from '../../extensionVariables'; -import { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; +import { LogicAppResourceTree } from '../tree/LogicAppResourceTree'; +import type { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; import * as appservice from '@microsoft/vscode-azext-azureappservice'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; export async function swapSlot(context: IActionContext, sourceSlotNode?: SlotTreeItem): Promise { if (!sourceSlotNode) { - sourceSlotNode = await ext.tree.showTreeItemPicker(SlotTreeItem.contextValue, context); + sourceSlotNode = await ext.rgApi.pickAppResource( + { ...context, suppressCreatePick: true }, + { + filter: logicAppFilter, + expectedChildContextValue: new RegExp(LogicAppResourceTree.pickSlotContextValue), + } + ); } const deploymentSlots: SlotTreeItem[] = (await sourceSlotNode.parent.getCachedChildren(context)) as SlotTreeItem[]; diff --git a/apps/vs-code-designer/src/app/commands/viewProperties.ts b/apps/vs-code-designer/src/app/commands/viewProperties.ts index 725d9a7af19..095f4f8cabc 100644 --- a/apps/vs-code-designer/src/app/commands/viewProperties.ts +++ b/apps/vs-code-designer/src/app/commands/viewProperties.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { ext } from '../../extensionVariables'; -import { ProductionSlotTreeItem } from '../tree/slotsTree/ProductionSlotTreeItem'; -import type { SlotTreeItemBase } from '../tree/slotsTree/SlotTreeItemBase'; +import { LogicAppResourceTree } from '../tree/LogicAppResourceTree'; +import type { SlotTreeItem } from '../tree/slotsTree/SlotTreeItem'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import { openReadOnlyJson } from '@microsoft/vscode-azext-utils'; -export async function viewProperties(context: IActionContext, node?: SlotTreeItemBase | ProductionSlotTreeItem): Promise { +export async function viewProperties(context: IActionContext, node?: SlotTreeItem): Promise { if (!node) { - node = await ext.tree.showTreeItemPicker(ProductionSlotTreeItem.contextValue, context); + node = await ext.rgApi.appResourceTree.showTreeItemPicker(LogicAppResourceTree.productionContextValue, context); } const data = node.site; diff --git a/apps/vs-code-designer/src/app/commands/workflows/exportLogicApp.ts b/apps/vs-code-designer/src/app/commands/workflows/exportLogicApp.ts index 1da1367e593..6d994058158 100644 --- a/apps/vs-code-designer/src/app/commands/workflows/exportLogicApp.ts +++ b/apps/vs-code-designer/src/app/commands/workflows/exportLogicApp.ts @@ -14,6 +14,7 @@ import { localize } from '../../../localize'; import { cacheWebviewPanel, removeWebviewPanelFromCache, tryGetWebviewPanel } from '../../utils/codeless/common'; import { getAuthorizationToken, getCloudHost } from '../../utils/codeless/getAuthorizationToken'; import { getWebViewHTML } from '../../utils/codeless/getWebViewHTML'; +import { getAccountCredentials } from '../../utils/credentials'; import { getRandomHexString } from '../../utils/fs'; import { delay } from '@azure/ms-rest-js'; import type { ServiceClientCredentials } from '@azure/ms-rest-js'; @@ -299,7 +300,7 @@ export async function exportLogicApp(): Promise { const panelName: string = localize('export', 'Export'); const panelGroupKey = ext.webViewKey.export; let accessToken: string; - const credentials: ServiceClientCredentials | undefined = await ext.azureAccountTreeItem.getAccountCredentials(); + const credentials: ServiceClientCredentials | undefined = await getAccountCredentials(); const apiVersion = '2021-03-01'; const dialogOptions: vscode.OpenDialogOptions = { diff --git a/apps/vs-code-designer/src/app/commands/workflows/openOverview.ts b/apps/vs-code-designer/src/app/commands/workflows/openOverview.ts index c9f086ac63b..4e65c22dcaf 100644 --- a/apps/vs-code-designer/src/app/commands/workflows/openOverview.ts +++ b/apps/vs-code-designer/src/app/commands/workflows/openOverview.ts @@ -27,6 +27,7 @@ import type { ICallbackUrlResponse } from '@microsoft/vscode-extension'; import { ExtensionCommand } from '@microsoft/vscode-extension'; import { readFileSync } from 'fs'; import { basename, dirname, join } from 'path'; +import * as path from 'path'; import * as vscode from 'vscode'; export async function openOverview(context: IAzureConnectorsContext, node: vscode.Uri | RemoteWorkflowTreeItem | undefined): Promise { @@ -102,6 +103,11 @@ export async function openOverview(context: IAzureConnectorsContext, node: vscod options ); + panel.iconPath = { + light: vscode.Uri.file(path.join(ext.context.extensionPath, 'assets', 'light', 'Codeless.svg')), + dark: vscode.Uri.file(path.join(ext.context.extensionPath, 'assets', 'dark', 'Codeless.svg')), + }; + panel.webview.html = await getWebViewHTML('vs-code-react', panel); let interval; diff --git a/apps/vs-code-designer/src/app/resourcesExtension/getExtensionApi.ts b/apps/vs-code-designer/src/app/resourcesExtension/getExtensionApi.ts new file mode 100644 index 00000000000..077ac4efcf8 --- /dev/null +++ b/apps/vs-code-designer/src/app/resourcesExtension/getExtensionApi.ts @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { localize } from '../../localize'; +import { apiUtils } from '@microsoft/vscode-azext-utils'; +import type { AzureHostExtensionApi } from '@microsoft/vscode-azext-utils/hostapi'; + +export async function getResourceGroupsApi(): Promise { + const rgApiProvider = await apiUtils.getExtensionExports('ms-azuretools.vscode-azureresourcegroups'); + if (rgApiProvider) { + return rgApiProvider.getApi('0.0.1'); + } else { + throw new Error(localize('noResourceGroupExt', 'Could not find the Azure Resource Groups extension')); + } +} diff --git a/apps/vs-code-designer/src/app/tree/AzureAccountTreeItemWithProjects.ts b/apps/vs-code-designer/src/app/tree/AzureAccountTreeItemWithProjects.ts index 37bb42bb094..a436c07901f 100644 --- a/apps/vs-code-designer/src/app/tree/AzureAccountTreeItemWithProjects.ts +++ b/apps/vs-code-designer/src/app/tree/AzureAccountTreeItemWithProjects.ts @@ -75,7 +75,7 @@ export class AzureAccountTreeItemWithProjects extends AzureAccountTreeItemBase { } if (this.currentLoggedInSessions) { - return this._getCredentialsForSessions(this.currentLoggedInSessions, tenantId); + return this.getCredentialsForSessions(this.currentLoggedInSessions, tenantId); } return undefined; @@ -108,7 +108,7 @@ export class AzureAccountTreeItemWithProjects extends AzureAccountTreeItemBase { return super.pickTreeItemImpl(expectedContextValues); } - private _getCredentialsForSessions(sessions: any, tenantId?: string): ServiceClientCredentials { + private getCredentialsForSessions(sessions: any, tenantId?: string): ServiceClientCredentials { if (tenantId) { const tenantDetails = sessions.filter((session) => session.tenantId.toLowerCase() == tenantId); return tenantDetails.length ? tenantDetails[0].credentials2 : sessions[0].credentials2; diff --git a/apps/vs-code-designer/src/app/tree/LogicAppResourceTree.ts b/apps/vs-code-designer/src/app/tree/LogicAppResourceTree.ts new file mode 100644 index 00000000000..74e1b3aaba8 --- /dev/null +++ b/apps/vs-code-designer/src/app/tree/LogicAppResourceTree.ts @@ -0,0 +1,385 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { localize } from '../../localize'; +import { parseHostJson } from '../funcConfig/host'; +import { getFileOrFolderContent } from '../utils/codeless/apiUtils'; +import { tryParseFuncVersion } from '../utils/funcCoreTools/funcVersion'; +import { getIconPath } from '../utils/tree/assets'; +import { matchesAnyPart } from '../utils/tree/projectContextValues'; +import { ConfigurationsTreeItem } from './configurationsTree/ConfigurationsTreeItem'; +import { RemoteWorkflowsTreeItem } from './remoteWorkflowsTree/RemoteWorkflowsTreeItem'; +import type { SlotTreeItem } from './slotsTree/SlotTreeItem'; +import { SlotsTreeItem } from './slotsTree/SlotsTreeItem'; +import { ArtifactsTreeItem } from './slotsTree/artifactsTree/ArtifactsTreeItem'; +import type { Site, SiteConfig, SiteSourceControl, StringDictionary } from '@azure/arm-appservice'; +import { isString } from '@microsoft/utils-logic-apps'; +import { + DeleteLastServicePlanStep, + DeleteSiteStep, + DeploymentsTreeItem, + DeploymentTreeItem, + getFile, + ParsedSite, + AppSettingsTreeItem, + LogFilesTreeItem, + SiteFilesTreeItem, +} from '@microsoft/vscode-azext-azureappservice'; +import { AzureWizard, DeleteConfirmationStep, nonNullValue } from '@microsoft/vscode-azext-utils'; +import type { AzExtTreeItem, IActionContext, ISubscriptionContext, TreeItemIconPath } from '@microsoft/vscode-azext-utils'; +import type { ResolvedAppResourceBase } from '@microsoft/vscode-azext-utils/hostapi'; +import { ProjectResource, ProjectSource, latestGAVersion } from '@microsoft/vscode-extension'; +import type { ApplicationSettings, FuncHostRequest, FuncVersion, IParsedHostJson } from '@microsoft/vscode-extension'; + +export function isLogicAppResourceTree(ti: unknown): ti is ResolvedAppResourceBase { + return (ti as unknown as LogicAppResourceTree).instance === LogicAppResourceTree.instance; +} + +export class LogicAppResourceTree implements ResolvedAppResourceBase { + public static instance = 'logicAppResourceTree'; + public readonly instance = LogicAppResourceTree.instance; + + public site: ParsedSite; + public data: Site; + + private _subscription: ISubscriptionContext; + public logStreamPath = ''; + public appSettingsTreeItem: AppSettingsTreeItem; + public deploymentsNode: DeploymentsTreeItem | undefined; + public readonly source: ProjectSource = ProjectSource.Remote; + + public contextValuesToAdd?: string[] | undefined; + public maskedValuesToAdd: string[] = []; + + public configurationsTreeItem: ConfigurationsTreeItem; + private _cachedVersion: FuncVersion | undefined; + private _cachedHostJson: IParsedHostJson | undefined; + private _workflowsTreeItem: RemoteWorkflowsTreeItem | undefined; + private _artifactsTreeItem: ArtifactsTreeItem; + private _logFilesTreeItem: LogFilesTreeItem; + private _siteFilesTreeItem: SiteFilesTreeItem; + private _slotsTreeItem: SlotsTreeItem; + + private _cachedIsConsumption: boolean | undefined; + + public static pickSlotContextValue = new RegExp(/azLogicAppsSlot(?!s)/); + public static productionContextValue = 'azLogicAppsProductionSlot'; + public static slotContextValue = 'azLogicAppsSlot'; + + commandId?: string | undefined; + tooltip?: string | undefined; + commandArgs?: unknown[] | undefined; + + public constructor(subscription: ISubscriptionContext, site: Site) { + this.site = new ParsedSite(site, subscription); + this.data = this.site.rawSite; + this._subscription = subscription; + this.contextValuesToAdd = [this.site.isSlot ? LogicAppResourceTree.slotContextValue : LogicAppResourceTree.productionContextValue]; + + const valuesToMask = [ + this.site.siteName, + this.site.slotName, + this.site.defaultHostName, + this.site.resourceGroup, + this.site.planName, + this.site.planResourceGroup, + this.site.kuduHostName, + this.site.gitUrl, + this.site.rawSite.repositorySiteName, + ...(this.site.rawSite.hostNames || []), + ...(this.site.rawSite.enabledHostNames || []), + ]; + + for (const v of valuesToMask) { + if (v) { + this.maskedValuesToAdd.push(v); + } + } + } + + public static createLogicAppResourceTree(context: IActionContext, subscription: ISubscriptionContext, site: Site): LogicAppResourceTree { + const resource = new LogicAppResourceTree(subscription, site); + void resource.site.createClient(context).then(async (client) => (resource.data.siteConfig = await client.getSiteConfig())); + return resource; + } + + public get name(): string { + return this.label; + } + + public get label(): string { + return this.site.slotName ?? this.site.fullName; + } + + public get id(): string { + return this.site.id; + } + + public get logStreamLabel(): string { + return this.site.fullName; + } + + public async getHostRequest(): Promise { + return { url: this.site.defaultHostUrl }; + } + + public get description(): string | undefined { + return this._state?.toLowerCase() !== 'running' ? this._state : undefined; + } + + public get iconPath(): TreeItemIconPath { + const proxyTree: SlotTreeItem = this as unknown as SlotTreeItem; + return getIconPath(proxyTree.contextValue); + } + + private get _state(): string | undefined { + return this.site.rawSite.state; + } + + public hasMoreChildrenImpl(): boolean { + return false; + } + + /** + * NOTE: We need to be extra careful in this method because it blocks many core scenarios (e.g. deploy) if the tree item is listed as invalid + */ + public async refreshImpl(context: IActionContext): Promise { + this._cachedVersion = undefined; + this._cachedHostJson = undefined; + this._cachedIsConsumption = undefined; + + const client = await this.site.createClient(context); + this.site = new ParsedSite(nonNullValue(await client.getSite(), 'site'), this._subscription); + } + + public async getVersion(context: IActionContext): Promise { + let result: FuncVersion | undefined = this._cachedVersion; + if (result === undefined) { + let version: FuncVersion | undefined; + try { + const client = await this.site.createClient(context); + const appSettings: StringDictionary = await client.listApplicationSettings(); + version = tryParseFuncVersion(appSettings.properties && appSettings.properties.FUNCTIONS_EXTENSION_VERSION); + } catch { + // ignore and use default + } + // tslint:disable-next-line: strict-boolean-expressions + result = version || latestGAVersion; + this._cachedVersion = result; + } + + return result; + } + + public async getHostJson(context: IActionContext): Promise { + let result: IParsedHostJson | undefined = this._cachedHostJson; + if (!result) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let data: any; + try { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + data = JSON.parse((await getFile(context, this.site, 'site/wwwroot/host.json')).data); + } catch { + // ignore and use default + } + const version: FuncVersion = await this.getVersion(context); + result = parseHostJson(data, version); + this._cachedHostJson = result; + } + + return result; + } + + public async getApplicationSettings(context: IActionContext): Promise { + const client = await this.site.createClient(context); + const appSettings: StringDictionary = await client.listApplicationSettings(); + return appSettings.properties || {}; + } + + public async setApplicationSetting(context: IActionContext, key: string, value: string): Promise { + const client = await this.site.createClient(context); + const settings: StringDictionary = await client.listApplicationSettings(); + if (!settings.properties) { + settings.properties = {}; + } + settings.properties[key] = value; + await client.updateApplicationSettings(settings); + } + + public async getIsConsumption(context: IActionContext): Promise { + let result: boolean | undefined = this._cachedIsConsumption; + if (result === undefined) { + try { + const client = await this.site.createClient(context); + result = await client.getIsConsumption(context); + } catch { + // ignore and use default + result = true; + } + this._cachedIsConsumption = result; + } + + return result; + } + + public async loadMoreChildrenImpl(_clearCache: boolean, context: IActionContext): Promise { + const client = await this.site.createClient(context); + const siteConfig: SiteConfig = await client.getSiteConfig(); + const sourceControl: SiteSourceControl = await client.getSourceControl(); + const proxyTree: SlotTreeItem = this as unknown as SlotTreeItem; + + this.deploymentsNode = new DeploymentsTreeItem(proxyTree, { + site: this.site, + siteConfig: siteConfig, + sourceControl: sourceControl, + }); + + this.deploymentsNode = new DeploymentsTreeItem(proxyTree, { + site: this.site, + siteConfig, + sourceControl, + contextValuesToAdd: ['azLogicApps'], + }); + this.appSettingsTreeItem = new AppSettingsTreeItem(proxyTree, this.site, { + contextValuesToAdd: ['azLogicApps'], + }); + this._siteFilesTreeItem = new SiteFilesTreeItem(proxyTree, { + site: this.site, + isReadOnly: true, + contextValuesToAdd: ['azLogicApps'], + }); + this._logFilesTreeItem = new LogFilesTreeItem(proxyTree, { + site: this.site, + contextValuesToAdd: ['azLogicApps'], + }); + + if (!this._workflowsTreeItem) { + this._workflowsTreeItem = await RemoteWorkflowsTreeItem.createWorkflowsTreeItem(context, proxyTree); + } + + if (!this.configurationsTreeItem) { + this.configurationsTreeItem = await ConfigurationsTreeItem.createConfigurationsTreeItem(proxyTree, context); + } + + const children: AzExtTreeItem[] = [ + this._workflowsTreeItem, + this.configurationsTreeItem, + this._siteFilesTreeItem, + this._logFilesTreeItem, + this.deploymentsNode, + ]; + + if (!this.site.isSlot) { + this._slotsTreeItem = new SlotsTreeItem(proxyTree); + children.push(this._slotsTreeItem); + } + + if (!this._artifactsTreeItem) { + try { + await getFileOrFolderContent(context, proxyTree, 'Artifacts'); + } catch (error) { + if (error.statusCode === 404) { + return children; + } + } + this._artifactsTreeItem = new ArtifactsTreeItem(proxyTree, this.site); + children.push(this._artifactsTreeItem); + } + return children; + } + + public async pickTreeItemImpl(expectedContextValues: (string | RegExp)[]): Promise { + if (!this.site.isSlot) { + for (const expectedContextValue of expectedContextValues) { + switch (expectedContextValue) { + case SlotsTreeItem.contextValue: + case LogicAppResourceTree.slotContextValue: + return this._slotsTreeItem; + default: + } + } + } + + for (const expectedContextValue of expectedContextValues) { + if (expectedContextValue instanceof RegExp) { + const appSettingsContextValues = [ConfigurationsTreeItem.contextValue]; + if (matchContextValue(expectedContextValue, appSettingsContextValues)) { + return this.configurationsTreeItem; + } + const deploymentsContextValues = [ + DeploymentsTreeItem.contextValueConnected, + DeploymentsTreeItem.contextValueUnconnected, + DeploymentTreeItem.contextValue, + ]; + if (matchContextValue(expectedContextValue, deploymentsContextValues)) { + return this.deploymentsNode; + } + + if (matchContextValue(expectedContextValue, [LogicAppResourceTree.slotContextValue])) { + return this._slotsTreeItem; + } + } + + if (isString(expectedContextValue)) { + // DeploymentTreeItem.contextValue is a RegExp, but the passed in contextValue can be a string so check for a match + if (DeploymentTreeItem.contextValue.test(expectedContextValue)) { + return this.deploymentsNode; + } + } else if (matchesAnyPart(expectedContextValue, ProjectResource.Workflows, ProjectResource.Workflow)) { + return this._workflowsTreeItem; + } + } + return undefined; + } + + public compareChildrenImpl(): number { + return 0; // already sorted + } + + public async isReadOnly(context: IActionContext): Promise { + const client = await this.site.createClient(context); + const appSettings: StringDictionary = await client.listApplicationSettings(); + return !!appSettings.properties && !!(appSettings.properties.WEBSITE_RUN_FROM_PACKAGE || appSettings.properties.WEBSITE_RUN_FROM_ZIP); + } + + public async deleteTreeItemImpl(context: IActionContext): Promise { + const { isSlot, fullName, isFunctionApp } = this.site; + const confirmationMessage: string = isSlot + ? localize('confirmDeleteSlot', 'Are you sure you want to delete slot "{0}"?', fullName) + : isFunctionApp + ? localize('confirmDeleteFunctionApp', 'Are you sure you want to delete function app "{0}"?', fullName) + : localize('confirmDeleteWebApp', 'Are you sure you want to delete web app "{0}"?', fullName); + + const wizardContext = Object.assign(context, { + site: this.site, + }); + + const wizard = new AzureWizard(wizardContext, { + title: localize('deleteSwa', 'Delete Function App "{0}"', this.label), + promptSteps: [new DeleteConfirmationStep(confirmationMessage), new DeleteLastServicePlanStep()], + executeSteps: [new DeleteSiteStep()], + }); + + await wizard.prompt(); + await wizard.execute(); + } +} + +function matchContextValue(expectedContextValue: RegExp | string, matches: (string | RegExp)[]): boolean { + if (expectedContextValue instanceof RegExp) { + return matches.some((match) => { + if (match instanceof RegExp) { + return expectedContextValue.toString() === match.toString(); + } + return expectedContextValue.test(match); + }); + } else { + return matches.some((match) => { + if (match instanceof RegExp) { + return match.test(expectedContextValue); + } + return expectedContextValue === match; + }); + } +} diff --git a/apps/vs-code-designer/src/app/tree/configurationsTree/ConfigurationsTreeItem.ts b/apps/vs-code-designer/src/app/tree/configurationsTree/ConfigurationsTreeItem.ts index e4e48a5594f..b24b63dd7e8 100644 --- a/apps/vs-code-designer/src/app/tree/configurationsTree/ConfigurationsTreeItem.ts +++ b/apps/vs-code-designer/src/app/tree/configurationsTree/ConfigurationsTreeItem.ts @@ -5,7 +5,7 @@ import { localize } from '../../../localize'; import { getThemedIconPath } from '../../utils/tree/assets'; import { getProjectContextValue } from '../../utils/tree/projectContextValues'; -import type { SlotTreeItemBase } from '../slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../slotsTree/SlotTreeItem'; import { ConnectionsTreeItem } from './connectionsTree/ConnectionsTreeItem'; import { ParametersTreeItem } from './parametersTree/ParametersTreeItem'; import { AppSettingsTreeItem } from '@microsoft/vscode-azext-azureappservice'; @@ -16,19 +16,19 @@ import { ProjectAccess, ProjectResource } from '@microsoft/vscode-extension'; export class ConfigurationsTreeItem extends AzExtParentTreeItem { public static contextValue = 'azLogicAppsConfigurations'; public readonly label: string = localize('Configurations', 'Configurations'); - public readonly parent: SlotTreeItemBase; + public readonly parent: SlotTreeItem; public readonly appSettingsTreeItem: AppSettingsTreeItem; public isReadOnly: boolean; private _connectionsTreeItem: ConnectionsTreeItem; private _parametersTreeItem: ParametersTreeItem; - private constructor(parent: SlotTreeItemBase) { + private constructor(parent: SlotTreeItem) { super(parent); this.appSettingsTreeItem = new AppSettingsTreeItem(this, this.parent.site); } - public static async createConfigurationsTreeItem(parent: SlotTreeItemBase, context: IActionContext): Promise { + public static async createConfigurationsTreeItem(parent: SlotTreeItem, context: IActionContext): Promise { const treeItem: ConfigurationsTreeItem = new ConfigurationsTreeItem(parent); await treeItem.refreshImpl(context); return treeItem; diff --git a/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowTreeItem.ts b/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowTreeItem.ts index 0d2f9e8f4dd..d8841d146ed 100644 --- a/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowTreeItem.ts +++ b/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowTreeItem.ts @@ -15,7 +15,7 @@ import type { StringDictionary } from '@azure/arm-appservice'; import type { ServiceClientCredentials } from '@azure/ms-rest-js'; import { isEmptyString, HTTP_METHODS } from '@microsoft/utils-logic-apps'; import { AzExtTreeItem, DialogResponses } from '@microsoft/vscode-azext-utils'; -import type { AzExtParentTreeItem, IActionContext, TreeItemIconPath } from '@microsoft/vscode-azext-utils'; +import type { IActionContext, TreeItemIconPath } from '@microsoft/vscode-azext-utils'; import { ProjectResource } from '@microsoft/vscode-extension'; import type { ServiceProviderConnectionModel, @@ -32,7 +32,7 @@ export class RemoteWorkflowTreeItem extends AzExtTreeItem { public readonly workflowFileContent: IWorkflowFileContent; public credentials: ServiceClientCredentials; - private constructor(parent: AzExtParentTreeItem, name: string, workflowFileContent: IWorkflowFileContent) { + private constructor(parent: RemoteWorkflowsTreeItem, name: string, workflowFileContent: IWorkflowFileContent) { super(parent); this.name = name; this.workflowFileContent = workflowFileContent; diff --git a/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowsTreeItem.ts b/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowsTreeItem.ts index a59618ae88e..6200f62edd0 100644 --- a/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowsTreeItem.ts +++ b/apps/vs-code-designer/src/app/tree/remoteWorkflowsTree/RemoteWorkflowsTreeItem.ts @@ -8,7 +8,7 @@ import { getAllArtifacts, getOptionalFileContent, listWorkflows } from '../../ut import { getRequestTriggerSchema } from '../../utils/codeless/common'; import { getThemedIconPath } from '../../utils/tree/assets'; import { getProjectContextValue } from '../../utils/tree/projectContextValues'; -import type { SlotTreeItemBase } from '../slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../slotsTree/SlotTreeItem'; import { RemoteWorkflowTreeItem } from './RemoteWorkflowTreeItem'; import { isNullOrEmpty } from '@microsoft/utils-logic-apps'; import { AzExtParentTreeItem } from '@microsoft/vscode-azext-utils'; @@ -19,7 +19,7 @@ import type { Artifacts, Parameter } from '@microsoft/vscode-extension'; export class RemoteWorkflowsTreeItem extends AzExtParentTreeItem { public readonly label: string = localize('Workflows', 'Workflows'); public readonly childTypeLabel: string = localize('Workflow', 'Workflow'); - public readonly parent: SlotTreeItemBase; + public readonly parent: SlotTreeItem; public isReadOnly: boolean; private _artifacts: Artifacts; @@ -28,12 +28,12 @@ export class RemoteWorkflowsTreeItem extends AzExtParentTreeItem { private _nextLink: string | undefined; public readonly _context: IActionContext; - private constructor(context: IActionContext, parent: SlotTreeItemBase) { + private constructor(context: IActionContext, parent: SlotTreeItem) { super(parent); this._context = context; } - public static async createWorkflowsTreeItem(context: IActionContext, parent: SlotTreeItemBase): Promise { + public static async createWorkflowsTreeItem(context: IActionContext, parent: SlotTreeItem): Promise { const ti: RemoteWorkflowsTreeItem = new RemoteWorkflowsTreeItem(context, parent); // initialize await ti.refreshImpl(); diff --git a/apps/vs-code-designer/src/app/tree/slotsTree/ProductionSlotTreeItem.ts b/apps/vs-code-designer/src/app/tree/slotsTree/ProductionSlotTreeItem.ts deleted file mode 100644 index d31afa8dd20..00000000000 --- a/apps/vs-code-designer/src/app/tree/slotsTree/ProductionSlotTreeItem.ts +++ /dev/null @@ -1,57 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { localize } from '../../../localize'; -import type { SubscriptionTreeItem } from '../subscriptionTree/SubscriptionTreeItem'; -import { SlotTreeItem } from './SlotTreeItem'; -import { SlotTreeItemBase } from './SlotTreeItemBase'; -import { SlotsTreeItem } from './SlotsTreeItem'; -import type { ParsedSite } from '@microsoft/vscode-azext-azureappservice'; -import type { AzExtTreeItem, IActionContext } from '@microsoft/vscode-azext-utils'; - -export class ProductionSlotTreeItem extends SlotTreeItemBase { - public static contextValue = 'azLogicAppsProductionSlot'; - public readonly contextValue: string = ProductionSlotTreeItem.contextValue; - - private readonly _slotsTreeItem: SlotsTreeItem; - - public constructor(parent: SubscriptionTreeItem, site: ParsedSite) { - super(parent, site); - this._slotsTreeItem = new SlotsTreeItem(this); - } - - public get label(): string { - return this.site.fullName; - } - - public async loadMoreChildrenImpl(clearCache: boolean, context: IActionContext): Promise { - const children: AzExtTreeItem[] = await super.loadMoreChildrenImpl(clearCache, context); - if (await this.supportsSlots()) { - children.push(this._slotsTreeItem); - } - return children; - } - - public async pickTreeItemImpl(expectedContextValues: (string | RegExp)[], context: IActionContext): Promise { - for (const expectedContextValue of expectedContextValues) { - switch (expectedContextValue) { - case SlotsTreeItem.contextValue: - case SlotTreeItem.contextValue: - if (await this.supportsSlots()) { - return this._slotsTreeItem; - } else { - throw new Error(localize('slotNotSupported', 'Linux apps do not support slots.')); - } - default: - } - } - - return super.pickTreeItemImpl(expectedContextValues, context); - } - - private async supportsSlots(): Promise { - // Slots are not yet supported for Linux Consumption - return !this.site.isLinux; - } -} diff --git a/apps/vs-code-designer/src/app/tree/slotsTree/SlotTreeItem.ts b/apps/vs-code-designer/src/app/tree/slotsTree/SlotTreeItem.ts index 132fd280d0b..2d8522fea25 100644 --- a/apps/vs-code-designer/src/app/tree/slotsTree/SlotTreeItem.ts +++ b/apps/vs-code-designer/src/app/tree/slotsTree/SlotTreeItem.ts @@ -2,26 +2,109 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { ProductionSlotTreeItem } from './ProductionSlotTreeItem'; -import { SlotTreeItemBase } from './SlotTreeItemBase'; -import type { SlotsTreeItem } from './SlotsTreeItem'; -import type { ParsedSite } from '@microsoft/vscode-azext-azureappservice'; -import type { AzExtParentTreeItem } from '@microsoft/vscode-azext-utils'; +import { getIconPath } from '../../utils/tree/assets'; +import { LogicAppResourceTree } from '../LogicAppResourceTree'; +import type { ConfigurationsTreeItem } from '../configurationsTree/ConfigurationsTreeItem'; +import type { RemoteWorkflowTreeItem } from '../remoteWorkflowsTree/RemoteWorkflowTreeItem'; +import type { AppSettingsTreeItem, DeploymentsTreeItem, IDeployContext, ParsedSite } from '@microsoft/vscode-azext-azureappservice'; +import { AzExtParentTreeItem } from '@microsoft/vscode-azext-utils'; +import type { AzExtTreeItem, IActionContext } from '@microsoft/vscode-azext-utils'; +import type { ApplicationSettings, FuncHostRequest, FuncVersion, IParsedHostJson, IProjectTreeItem } from '@microsoft/vscode-extension'; +import { ProjectSource } from '@microsoft/vscode-extension'; -export class SlotTreeItem extends SlotTreeItemBase { - public static contextValue = 'azLogicAppsSlot'; - public readonly contextValue: string = SlotTreeItem.contextValue; - public readonly parent: SlotsTreeItem; +export class SlotTreeItem extends AzExtParentTreeItem implements IProjectTreeItem { + public logStreamPath = ''; + public configurationsTreeItem: ConfigurationsTreeItem; + public deploymentsNode: DeploymentsTreeItem | undefined; + public readonly source: ProjectSource = ProjectSource.Remote; + public site: ParsedSite; + public readonly appSettingsTreeItem: AppSettingsTreeItem; - public constructor(parent: SlotsTreeItem, site: ParsedSite) { - super(parent, site); + public readonly contextValue: string; + + public resourceTree: LogicAppResourceTree; + + public constructor(parent: AzExtParentTreeItem, resourceTree: LogicAppResourceTree) { + super(parent); + this.resourceTree = resourceTree; + // this is for the slotContextValue because it never gets resolved by the Resources extension + const slotContextValue = this.resourceTree.site.isSlot + ? LogicAppResourceTree.slotContextValue + : LogicAppResourceTree.productionContextValue; + const contextValues = [slotContextValue, 'slot']; + this.contextValue = Array.from(new Set(contextValues)).sort().join(';'); + this.site = this.resourceTree.site; + this.iconPath = getIconPath(slotContextValue); + } + + public get id(): string { + return this.resourceTree.label; + } + + public get description(): string | undefined { + return this.resourceTree.description; } public get label(): string { - return this.site.slotName; + return this.resourceTree.label; + } + + public hasMoreChildrenImpl(): boolean { + return this.resourceTree.hasMoreChildrenImpl(); + } + + public get logStreamLabel(): string { + return this.resourceTree.logStreamLabel; + } + + public async getHostRequest(): Promise { + return await this.resourceTree.getHostRequest(); + } + + /** + * NOTE: We need to be extra careful in this method because it blocks many core scenarios (e.g. deploy) if the tree item is listed as invalid + */ + public async refreshImpl(context: IActionContext): Promise { + return await this.resourceTree.refreshImpl(context); + } + + public async getVersion(context: IActionContext): Promise { + return await this.resourceTree.getVersion(context); + } + + public async getHostJson(context: IActionContext): Promise { + return await this.resourceTree.getHostJson(context); + } + + public async getApplicationSettings(context: IDeployContext): Promise { + return await this.resourceTree.getApplicationSettings(context); + } + + public async setApplicationSetting(context: IActionContext, key: string, value: string): Promise { + return await this.resourceTree.setApplicationSetting(context, key, value); + } + + public async loadMoreChildrenImpl(_clearCache: boolean, context: IActionContext): Promise { + return (await this.resourceTree.loadMoreChildrenImpl.call(this, _clearCache, context)) as AzExtTreeItem[]; + } + + public async pickTreeItemImpl(expectedContextValues: (string | RegExp)[], _context: IActionContext): Promise { + return await this.resourceTree.pickTreeItemImpl(expectedContextValues); + } + + public compareChildrenImpl(): number { + return this.resourceTree.compareChildrenImpl(); + } + + public async isReadOnly(context: IActionContext): Promise { + return await this.resourceTree.isReadOnly(context); + } + + public async deleteTreeItemImpl(context: IActionContext): Promise { + await this.resourceTree.deleteTreeItemImpl(context); } } -export function isSlotTreeItem(treeItem: SlotTreeItem | ProductionSlotTreeItem | AzExtParentTreeItem): treeItem is SlotTreeItem { +export function isSlotTreeItem(treeItem: SlotTreeItem | RemoteWorkflowTreeItem | AzExtParentTreeItem): treeItem is SlotTreeItem { return !!(treeItem as SlotTreeItem).site; } diff --git a/apps/vs-code-designer/src/app/tree/slotsTree/SlotTreeItemBase.ts b/apps/vs-code-designer/src/app/tree/slotsTree/SlotTreeItemBase.ts deleted file mode 100644 index dddae5f19bd..00000000000 --- a/apps/vs-code-designer/src/app/tree/slotsTree/SlotTreeItemBase.ts +++ /dev/null @@ -1,269 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { localSettingsFileName } from '../../../constants'; -import { localize } from '../../../localize'; -import { parseHostJson } from '../../funcConfig/host'; -import { getLocalSettingsJson } from '../../utils/appSettings/localSettings'; -import { getFileOrFolderContent } from '../../utils/codeless/apiUtils'; -import { tryParseFuncVersion } from '../../utils/funcCoreTools/funcVersion'; -import { getIconPath } from '../../utils/tree/assets'; -import { ConfigurationsTreeItem } from '../configurationsTree/ConfigurationsTreeItem'; -import { RemoteWorkflowsTreeItem } from '../remoteWorkflowsTree/RemoteWorkflowsTreeItem'; -import { ArtifactsTreeItem } from './artifactsTree/ArtifactsTreeItem'; -import type { SiteConfig, SiteSourceControl, StringDictionary } from '@azure/arm-appservice'; -import { isString } from '@microsoft/utils-logic-apps'; -import { - AppSettingsTreeItem, - DeleteLastServicePlanStep, - DeleteSiteStep, - DeploymentsTreeItem, - DeploymentTreeItem, - getFile, - LogFilesTreeItem, - SiteFilesTreeItem, -} from '@microsoft/vscode-azext-azureappservice'; -import type { IDeployContext, ParsedSite } from '@microsoft/vscode-azext-azureappservice'; -import { AzExtParentTreeItem, AzureWizard, DeleteConfirmationStep } from '@microsoft/vscode-azext-utils'; -import type { AzExtTreeItem, IActionContext, TreeItemIconPath } from '@microsoft/vscode-azext-utils'; -import type { - ApplicationSettings, - ILocalSettingsJson, - FuncHostRequest, - FuncVersion, - IParsedHostJson, - IProjectTreeItem, -} from '@microsoft/vscode-extension'; -import { latestGAVersion, ProjectSource } from '@microsoft/vscode-extension'; -import * as path from 'path'; - -export abstract class SlotTreeItemBase extends AzExtParentTreeItem implements IProjectTreeItem { - public configurationsTreeItem: ConfigurationsTreeItem; - public deploymentsNode: DeploymentsTreeItem | undefined; - public readonly source: ProjectSource = ProjectSource.Remote; - public site: ParsedSite; - public readonly appSettingsTreeItem: AppSettingsTreeItem; - - public abstract readonly contextValue: string; - public abstract readonly label: string; - - private readonly _logFilesTreeItem: LogFilesTreeItem; - private readonly _siteFilesTreeItem: SiteFilesTreeItem; - private _state?: string; - private _cachedVersion: FuncVersion | undefined; - private _cachedHostJson: IParsedHostJson | undefined; - private _workflowsTreeItem: RemoteWorkflowsTreeItem | undefined; - private _artifactsTreeItem: ArtifactsTreeItem; - - public constructor(parent: AzExtParentTreeItem, site: ParsedSite) { - super(parent); - this.site = site; - this._state = this.site.rawSite.state; - this._siteFilesTreeItem = new SiteFilesTreeItem(this, { - site: site, - isReadOnly: true, - }); - this._logFilesTreeItem = new LogFilesTreeItem(this, { - site: site, - }); - this.appSettingsTreeItem = new AppSettingsTreeItem(this, site); - } - - public get id(): string { - return this.site.id; - } - - public get description(): string | undefined { - return this._state && this._state.toLowerCase() !== 'running' ? this._state : undefined; - } - - public get iconPath(): TreeItemIconPath { - return getIconPath(this.contextValue); - } - - public hasMoreChildrenImpl(): boolean { - return false; - } - - public get logStreamLabel(): string { - return this.site.fullName; - } - - public get logStreamPath(): string { - return `application/functions/function/${encodeURIComponent(this.site.fullName)}`; - } - - public async getHostRequest(): Promise { - return { url: this.site.defaultHostUrl }; - } - - /** - * NOTE: We need to be extra careful in this method because it blocks many core scenarios (e.g. deploy) if the tree item is listed as invalid - */ - public async refreshImpl(context: IActionContext): Promise { - this._cachedVersion = undefined; - this._cachedHostJson = undefined; - const client = await this.site.createClient(context); - - try { - this._state = await client.getState(); - } catch { - this._state = 'Unknown'; - } - } - - public async getVersion(context: IActionContext): Promise { - let result: FuncVersion | undefined = this._cachedVersion; - if (result === undefined) { - let version: FuncVersion | undefined; - try { - const client = await this.site.createClient(context); - const appSettings: StringDictionary = await client.listApplicationSettings(); - version = tryParseFuncVersion(appSettings.properties && appSettings.properties.FUNCTIONS_EXTENSION_VERSION); - } catch { - // ignore and use default - } - // tslint:disable-next-line: strict-boolean-expressions - result = version || latestGAVersion; - this._cachedVersion = result; - } - - return result; - } - - public async getHostJson(context: IActionContext): Promise { - let result: IParsedHostJson | undefined = this._cachedHostJson; - if (!result) { - let data: any; - try { - data = JSON.parse((await getFile(context, this.site, 'site/wwwroot/host.json')).data); - } catch { - // ignore and use default - } - const version: FuncVersion = await this.getVersion(context); - result = parseHostJson(data, version); - this._cachedHostJson = result; - } - - return result; - } - - public async getApplicationSettings(context: IDeployContext): Promise { - const localSettings: ILocalSettingsJson = await getLocalSettingsJson( - context, - path.join(context.effectiveDeployFsPath, localSettingsFileName) - ); - return localSettings.Values || {}; - } - - public async setApplicationSetting(context: IActionContext, key: string, value: string): Promise { - const client = await this.site.createClient(context); - const settings: StringDictionary = await client.listApplicationSettings(); - if (!settings.properties) { - settings.properties = {}; - } - settings.properties[key] = value; - await client.updateApplicationSettings(settings); - } - - public async loadMoreChildrenImpl(_clearCache: boolean, context: IActionContext): Promise { - const client = await this.site.createClient(context); - const siteConfig: SiteConfig = await client.getSiteConfig(); - const sourceControl: SiteSourceControl = await client.getSourceControl(); - this.deploymentsNode = new DeploymentsTreeItem(this, { - site: this.site, - siteConfig: siteConfig, - sourceControl: sourceControl, - }); - - if (!this._workflowsTreeItem) { - this._workflowsTreeItem = await RemoteWorkflowsTreeItem.createWorkflowsTreeItem(context, this); - } - - if (!this.configurationsTreeItem) { - this.configurationsTreeItem = await ConfigurationsTreeItem.createConfigurationsTreeItem(this, context); - } - - if (!this._artifactsTreeItem) { - try { - await getFileOrFolderContent(context, this, 'Artifacts'); - } catch (error) { - if (error.statusCode === 404) { - return [ - this._workflowsTreeItem, - this.configurationsTreeItem, - this._siteFilesTreeItem, - this._logFilesTreeItem, - this.deploymentsNode, - ]; - } - } - - this._artifactsTreeItem = new ArtifactsTreeItem(this, this.site); - } - - return [ - this._workflowsTreeItem, - this.configurationsTreeItem, - this._artifactsTreeItem, - this._siteFilesTreeItem, - this._logFilesTreeItem, - this.deploymentsNode, - ]; - } - - public async pickTreeItemImpl(expectedContextValues: (string | RegExp)[], _context: IActionContext): Promise { - for (const expectedContextValue of expectedContextValues) { - switch (expectedContextValue) { - case ConfigurationsTreeItem.contextValue: - return this.configurationsTreeItem; - case DeploymentsTreeItem.contextValueConnected: - case DeploymentsTreeItem.contextValueUnconnected: - case DeploymentTreeItem.contextValue: - return this.deploymentsNode; - default: - if (isString(expectedContextValue)) { - // DeploymentTreeItem.contextValue is a RegExp, but the passed in contextValue can be a string so check for a match - if (DeploymentTreeItem.contextValue.test(expectedContextValue)) { - return this.deploymentsNode; - } - } - } - } - - return undefined; - } - - public compareChildrenImpl(): number { - return 0; - } - - public async isReadOnly(context: IActionContext): Promise { - const client = await this.site.createClient(context); - const appSettings: StringDictionary = await client.listApplicationSettings(); - return !!appSettings.properties && !!(appSettings.properties.WEBSITE_RUN_FROM_PACKAGE || appSettings.properties.WEBSITE_RUN_FROM_ZIP); - } - - public async deleteTreeItemImpl(context: IActionContext): Promise { - const { isSlot, fullName, isFunctionApp } = this.site; - const confirmationMessage: string = isSlot - ? localize('confirmDeleteSlot', 'Are you sure you want to delete slot "{0}"?', fullName) - : isFunctionApp - ? localize('confirmDeleteFunctionApp', 'Are you sure you want to delete function app "{0}"?', fullName) - : localize('confirmDeleteWebApp', 'Are you sure you want to delete web app "{0}"?', fullName); - - const wizardContext = Object.assign(context, { - site: this.site, - }); - - const wizard = new AzureWizard(wizardContext, { - title: localize('deleteSwa', 'Delete Function App "{0}"', this.label), - promptSteps: [new DeleteConfirmationStep(confirmationMessage), new DeleteLastServicePlanStep()], - executeSteps: [new DeleteSiteStep()], - }); - - await wizard.prompt(); - await wizard.execute(); - } -} diff --git a/apps/vs-code-designer/src/app/tree/slotsTree/SlotsTreeItem.ts b/apps/vs-code-designer/src/app/tree/slotsTree/SlotsTreeItem.ts index 2c799a3c3d8..fd91cc8e33b 100644 --- a/apps/vs-code-designer/src/app/tree/slotsTree/SlotsTreeItem.ts +++ b/apps/vs-code-designer/src/app/tree/slotsTree/SlotsTreeItem.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../localize'; +import { showSiteCreated } from '../../commands/createLogicApp/showSiteCreated'; import { getIconPath } from '../../utils/tree/assets'; -import type { ProductionSlotTreeItem } from './ProductionSlotTreeItem'; +import { LogicAppResourceTree } from '../LogicAppResourceTree'; import { SlotTreeItem } from './SlotTreeItem'; import type { Site, WebSiteManagementClient } from '@azure/arm-appservice'; import { createSlot, createWebSiteClient, ParsedSite } from '@microsoft/vscode-azext-azureappservice'; @@ -17,11 +18,11 @@ export class SlotsTreeItem extends AzExtParentTreeItem { public readonly contextValue: string = SlotsTreeItem.contextValue; public readonly label: string = localize('slots', 'Slots'); public readonly childTypeLabel: string = localize('slot', 'Slot'); - public readonly parent: ProductionSlotTreeItem; + public readonly parent: SlotTreeItem; private _nextLink: string | undefined; - public constructor(parent: ProductionSlotTreeItem) { + public constructor(parent: SlotTreeItem) { super(parent); } @@ -51,7 +52,7 @@ export class SlotsTreeItem extends AzExtParentTreeItem { webAppCollection, 'azLogicAppInvalidSlot', async (site: Site) => { - return new SlotTreeItem(this, new ParsedSite(site, this.subscription)); + return new SlotTreeItem(this, new LogicAppResourceTree(this.subscription, site)); }, (site: Site) => { return site.name; @@ -67,6 +68,7 @@ export class SlotsTreeItem extends AzExtParentTreeItem { context ); const parsedSite = new ParsedSite(newSite, this.subscription); - return new SlotTreeItem(this, parsedSite); + showSiteCreated(parsedSite, context); + return new SlotTreeItem(this, new LogicAppResourceTree(this.subscription, newSite)); } } diff --git a/apps/vs-code-designer/src/app/tree/slotsTree/artifactsTree/ArtifactsTreeItem.ts b/apps/vs-code-designer/src/app/tree/slotsTree/artifactsTree/ArtifactsTreeItem.ts index 4714dae5eb0..888fa8792d1 100644 --- a/apps/vs-code-designer/src/app/tree/slotsTree/artifactsTree/ArtifactsTreeItem.ts +++ b/apps/vs-code-designer/src/app/tree/slotsTree/artifactsTree/ArtifactsTreeItem.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { localize } from '../../../../localize'; -import type { SlotTreeItemBase } from '../SlotTreeItemBase'; +import type { SlotTreeItem } from '../SlotTreeItem'; import type { ParsedSite } from '@microsoft/vscode-azext-azureappservice'; import { FolderTreeItem } from '@microsoft/vscode-azext-azureappservice'; @@ -17,7 +17,7 @@ export class ArtifactsTreeItem extends FolderTreeItem { protected readonly _isRoot: boolean = false; - constructor(parent: SlotTreeItemBase, client: ParsedSite) { + constructor(parent: SlotTreeItem, client: ParsedSite) { super(parent, { site: client, label: localize('Artifacts', 'Artifacts'), diff --git a/apps/vs-code-designer/src/app/tree/subscriptionTree/SubscriptionTreeItem.ts b/apps/vs-code-designer/src/app/tree/subscriptionTree/SubscriptionTreeItem.ts index e7be0991a31..e45127fcf1b 100644 --- a/apps/vs-code-designer/src/app/tree/subscriptionTree/SubscriptionTreeItem.ts +++ b/apps/vs-code-designer/src/app/tree/subscriptionTree/SubscriptionTreeItem.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { logicAppKind, projectLanguageSetting, workflowappRuntime } from '../../../constants'; +import { projectLanguageSetting, workflowappRuntime } from '../../../constants'; import { ext } from '../../../extensionVariables'; import { localize } from '../../../localize'; import { LogicAppCreateStep } from '../../commands/createLogicApp/createLogicAppSteps/LogicAppCreateStep'; @@ -15,7 +15,8 @@ import { getRandomHexString } from '../../utils/fs'; import { getDefaultFuncVersion } from '../../utils/funcCoreTools/funcVersion'; import { isProjectCV, isRemoteProjectCV } from '../../utils/tree/projectContextValues'; import { getFunctionsWorkerRuntime, getWorkspaceSettingFromAnyFolder } from '../../utils/vsCodeConfig/settings'; -import { ProductionSlotTreeItem } from '../slotsTree/ProductionSlotTreeItem'; +import { LogicAppResourceTree } from '../LogicAppResourceTree'; +import { SlotTreeItem } from '../slotsTree/SlotTreeItem'; import type { Site, WebSiteManagementClient } from '@azure/arm-appservice'; import { isNullOrUndefined } from '@microsoft/utils-logic-apps'; import { @@ -82,9 +83,9 @@ export class SubscriptionTreeItem extends SubscriptionTreeItemBase { webAppCollection, 'azLogicAppInvalidLogicApp', async (site: Site) => { - const parsedSite = new ParsedSite(site, this.subscription); - if (site.kind.includes(logicAppKind)) { - return new ProductionSlotTreeItem(this, parsedSite); + const resourceTree = new LogicAppResourceTree(this.subscription, site); + if (resourceTree.site.isWorkflowApp) { + return new SlotTreeItem(this, resourceTree); } return undefined; }, @@ -93,15 +94,14 @@ export class SubscriptionTreeItem extends SubscriptionTreeItemBase { } ); } - - public async createChildImpl(context: ICreateLogicAppContext): Promise { + public static async createChild(context: ICreateLogicAppContext, subscription: SubscriptionTreeItem): Promise { const version: FuncVersion = await getDefaultFuncVersion(context); const language: string | undefined = getWorkspaceSettingFromAnyFolder(projectLanguageSetting); context.telemetry.properties.projectRuntime = version; context.telemetry.properties.projectLanguage = language; - const wizardContext: IFunctionAppWizardContext = Object.assign(context, this.subscription, { + const wizardContext: IFunctionAppWizardContext = Object.assign(context, subscription.subscription, { newSiteKind: AppKind.workflowapp, resourceGroupDeferLocationStep: true, version, @@ -197,7 +197,7 @@ export class SubscriptionTreeItem extends SubscriptionTreeItemBase { await wizard.execute(); - const site = new ParsedSite(nonNullProp(wizardContext, 'site'), this.subscription); + const site = new ParsedSite(nonNullProp(wizardContext, 'site'), subscription.subscription); const client: SiteClient = await site.createClient(context); if (!client.isLinux) { @@ -208,7 +208,9 @@ export class SubscriptionTreeItem extends SubscriptionTreeItemBase { } } - return new ProductionSlotTreeItem(this, site); + const resolved = new LogicAppResourceTree(subscription.subscription, nonNullProp(wizardContext, 'site')); + await ext.rgApi.appResourceTree.refresh(context); + return new SlotTreeItem(subscription, resolved); } public isAncestorOfImpl(contextValue: string | RegExp): boolean { diff --git a/apps/vs-code-designer/src/app/utils/azureClients.ts b/apps/vs-code-designer/src/app/utils/azureClients.ts index 00ed4984518..8e0e85c91f1 100644 --- a/apps/vs-code-designer/src/app/utils/azureClients.ts +++ b/apps/vs-code-designer/src/app/utils/azureClients.ts @@ -2,6 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type { WebSiteManagementClient } from '@azure/arm-appservice'; import type { StorageManagementClient } from '@azure/arm-storage'; import { createAzureClient } from '@microsoft/vscode-azext-azureutils'; import type { AzExtClientContext } from '@microsoft/vscode-azext-azureutils'; @@ -9,3 +10,7 @@ import type { AzExtClientContext } from '@microsoft/vscode-azext-azureutils'; export async function createStorageClient(context: T): Promise { return createAzureClient(context, (await import('@azure/arm-storage')).StorageManagementClient); } + +export async function createWebSiteClient(context: AzExtClientContext): Promise { + return createAzureClient(context, (await import('@azure/arm-appservice')).WebSiteManagementClient); +} diff --git a/apps/vs-code-designer/src/app/utils/codeless/apiUtils.ts b/apps/vs-code-designer/src/app/utils/codeless/apiUtils.ts index 6e5fa63d3e5..d61fa301c82 100644 --- a/apps/vs-code-designer/src/app/utils/codeless/apiUtils.ts +++ b/apps/vs-code-designer/src/app/utils/codeless/apiUtils.ts @@ -5,7 +5,7 @@ import { connectionsFileName, managementApiPrefix, parametersFileName, workflowAppApiVersion } from '../../../constants'; import { localize } from '../../../localize'; import type { RemoteWorkflowTreeItem } from '../../tree/remoteWorkflowsTree/RemoteWorkflowTreeItem'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import { sendAzureRequest } from '../requestUtils'; import { HTTP_METHODS } from '@microsoft/utils-logic-apps'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; @@ -20,13 +20,13 @@ import type { import * as path from 'path'; import * as vscode from 'vscode'; -export async function getFileOrFolderContent(context: IActionContext, node: SlotTreeItemBase, filePath: string): Promise { +export async function getFileOrFolderContent(context: IActionContext, node: SlotTreeItem, filePath: string): Promise { const url = `${node.id}/hostruntime/admin/vfs/${filePath}?api-version=${workflowAppApiVersion}&relativepath=1`; const response = await sendAzureRequest(url, context, 'GET', node.site.subscription); return response.bodyAsText; } -export async function getParameters(context: IActionContext, node: SlotTreeItemBase): Promise { +export async function getParameters(context: IActionContext, node: SlotTreeItem): Promise { const parametersData = await getParameterFileContent(context, node); const parameters: IParametersFileContent[] = []; @@ -40,7 +40,7 @@ export async function getParameters(context: IActionContext, node: SlotTreeItemB return parameters; } -export async function getParameterFileContent(context: IActionContext, node: SlotTreeItemBase): Promise> { +export async function getParameterFileContent(context: IActionContext, node: SlotTreeItem): Promise> { try { const data = await getFileOrFolderContent(context, node, parametersFileName); return JSON.parse(data); @@ -55,7 +55,7 @@ export async function getParameterFileContent(context: IActionContext, node: Slo } export async function getWorkflow( - node: SlotTreeItemBase, + node: SlotTreeItem, workflow: RemoteWorkflowTreeItem, context: IActionContext ): Promise { @@ -64,7 +64,7 @@ export async function getWorkflow( return response.parsedBody; } -export async function listWorkflows(node: SlotTreeItemBase, context: IActionContext): Promise[]> { +export async function listWorkflows(node: SlotTreeItem, context: IActionContext): Promise[]> { const url = `${node.id}/hostruntime${managementApiPrefix}/workflows?api-version=${workflowAppApiVersion}`; try { const response = await sendAzureRequest(url, context, 'GET', node.site.subscription); @@ -78,7 +78,7 @@ export async function listWorkflows(node: SlotTreeItemBase, context: IActionCont } } -export async function getOptionalFileContent(context: IActionContext, node: SlotTreeItemBase, filePath: string): Promise { +export async function getOptionalFileContent(context: IActionContext, node: SlotTreeItem, filePath: string): Promise { try { return await getFileOrFolderContent(context, node, filePath); } catch (error) { @@ -91,7 +91,7 @@ export async function getOptionalFileContent(context: IActionContext, node: Slot } } -async function getArtifactFiles(context: IActionContext, node: SlotTreeItemBase, folderPath: string): Promise { +async function getArtifactFiles(context: IActionContext, node: SlotTreeItem, folderPath: string): Promise { try { const data = await getFileOrFolderContent(context, node, folderPath); const content = JSON.parse(data); @@ -109,7 +109,7 @@ async function getArtifactFiles(context: IActionContext, node: SlotTreeItemBase, } } -export async function getAllArtifacts(context: IActionContext, node: SlotTreeItemBase): Promise { +export async function getAllArtifacts(context: IActionContext, node: SlotTreeItem): Promise { const mapArtifacts = await getArtifactFiles(context, node, 'Artifacts/Maps'); const schemaArtifacts = await getArtifactFiles(context, node, 'Artifacts/Schemas'); const artifacts: Artifacts = { maps: {}, schemas: [] }; @@ -142,7 +142,7 @@ export async function getAllArtifacts(context: IActionContext, node: SlotTreeIte return artifacts; } -export async function getConnections(context: IActionContext, node: SlotTreeItemBase): Promise { +export async function getConnections(context: IActionContext, node: SlotTreeItem): Promise { const connectionJson = await getOptionalFileContent(context, node, connectionsFileName); const connectionsData = JSON.parse(connectionJson); const functionConnections = connectionsData.functionConnections || {}; diff --git a/apps/vs-code-designer/src/app/utils/codeless/connection.ts b/apps/vs-code-designer/src/app/utils/codeless/connection.ts index 661517c45e3..f68ff23e1b1 100644 --- a/apps/vs-code-designer/src/app/utils/codeless/connection.ts +++ b/apps/vs-code-designer/src/app/utils/codeless/connection.ts @@ -1,7 +1,7 @@ import { connectionsFileName } from '../../../constants'; import { localize } from '../../../localize'; import { isCSharpProject } from '../../commands/initProjectForVSCode/detectProjectLanguage'; -import type { SlotTreeItemBase } from '../../tree/slotsTree/SlotTreeItemBase'; +import type { SlotTreeItem } from '../../tree/slotsTree/SlotTreeItem'; import { addOrUpdateLocalAppSettings } from '../appSettings/localSettings'; import { writeFormattedJson } from '../fs'; import { sendAzureRequest } from '../requestUtils'; @@ -255,12 +255,12 @@ export function resolveSettingsInConnection( * Creates acknowledge connections to managed api connections. * @param {IIdentityWizardContext} identityWizardContext - Identity context. * @param {string} connectionId - Connection ID. - * @param {SlotTreeItemBase} node - Logic app node structure. + * @param {SlotTreeItem} node - Logic app node structure. */ export async function createAclInConnectionIfNeeded( identityWizardContext: IIdentityWizardContext, connectionId: string, - node: SlotTreeItemBase + node: SlotTreeItem ): Promise { if ( (!node.site || !node.site.rawSite.identity || node.site.rawSite.identity.type !== 'SystemAssigned') && @@ -296,7 +296,7 @@ export async function createAclInConnectionIfNeeded( async function createAccessPolicyInConnection( identityWizardContext: IIdentityWizardContext, connectionId: string, - node: SlotTreeItemBase, + node: SlotTreeItem, identity: any ): Promise { const accessToken = await getAuthorizationToken(undefined, undefined); diff --git a/apps/vs-code-designer/src/app/utils/codeless/getAuthorizationToken.ts b/apps/vs-code-designer/src/app/utils/codeless/getAuthorizationToken.ts index 13be911a482..d56030590aa 100644 --- a/apps/vs-code-designer/src/app/utils/codeless/getAuthorizationToken.ts +++ b/apps/vs-code-designer/src/app/utils/codeless/getAuthorizationToken.ts @@ -1,18 +1,18 @@ -import { ext } from '../../../extensionVariables'; +import { getAccountCredentials } from '../credentials'; import { WebResource } from '@azure/ms-rest-js'; import type { ServiceClientCredentials } from '@azure/ms-rest-js'; export async function getAuthorizationToken(credentials?: ServiceClientCredentials, tenantId?: string): Promise { const webResource: WebResource = new WebResource(); - credentials = !credentials ? await ext.azureAccountTreeItem.getAccountCredentials(tenantId) : credentials; + credentials = !credentials ? await getAccountCredentials(tenantId) : credentials; await credentials?.signRequest(webResource); return webResource.headers.get('authorization') ?? webResource.headers['authorization']; } export async function getCloudHost(credentials?: any, tenantId?: string): Promise { - credentials = !credentials ? await ext.azureAccountTreeItem.getAccountCredentials(tenantId) : credentials; + credentials = !credentials ? await getAccountCredentials(tenantId) : credentials; return credentials ? credentials?.environment?.managementEndpointUrl : ''; } diff --git a/apps/vs-code-designer/src/app/utils/credentials.ts b/apps/vs-code-designer/src/app/utils/credentials.ts new file mode 100644 index 00000000000..cd33808a935 --- /dev/null +++ b/apps/vs-code-designer/src/app/utils/credentials.ts @@ -0,0 +1,35 @@ +import type { ServiceClientCredentials } from '@azure/ms-rest-js'; +import { commands, extensions } from 'vscode'; + +export const getAccountCredentials = async (tenantId?: string): Promise => { + const extension = extensions.getExtension('ms-vscode.azure-account'); + let currentLoggedInSessions: any; + + if (extension) { + if (!extension.isActive) { + await extension.activate(); + } + const azureAccount = extension.exports; + if (!(await azureAccount.waitForLogin())) { + await commands.executeCommand('azure-account.askForLogin'); + } + + await azureAccount.waitForFilters(); + currentLoggedInSessions = azureAccount.sessions; + } + + if (currentLoggedInSessions) { + return getCredentialsForSessions(currentLoggedInSessions, tenantId); + } + + return undefined; +}; + +const getCredentialsForSessions = (sessions: any, tenantId?: string): ServiceClientCredentials => { + if (tenantId) { + const tenantDetails = sessions.filter((session) => session.tenantId.toLowerCase() == tenantId); + return tenantDetails.length ? tenantDetails[0].credentials2 : sessions[0].credentials2; + } else { + return sessions[0].credentials2; + } +}; diff --git a/apps/vs-code-designer/src/assets/azure.svg b/apps/vs-code-designer/src/assets/azure.svg deleted file mode 100755 index 9b9e5acbc18..00000000000 --- a/apps/vs-code-designer/src/assets/azure.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/vs-code-designer/src/constants.ts b/apps/vs-code-designer/src/constants.ts index 4525d38af93..344f8f9806d 100644 --- a/apps/vs-code-designer/src/constants.ts +++ b/apps/vs-code-designer/src/constants.ts @@ -59,9 +59,7 @@ export const workflowDesignerLoadTimeout = 300000; // Commands export enum extensionCommand { openDesigner = 'azureLogicAppsStandard.openDesigner', - loadMore = 'azureLogicAppsStandard.loadMore', activate = 'azureLogicAppsStandard.activate', - selectSubscriptions = 'azureLogicAppsStandard.selectSubscriptions', viewContent = 'azureLogicAppsStandard.viewContent', openFile = 'azureLogicAppsStandard.openFile', createNewProject = 'azureLogicAppsStandard.createNewProject', @@ -78,14 +76,12 @@ export enum extensionCommand { pickProcess = 'azureLogicAppsStandard.pickProcess', getDebugSymbolDll = 'azureLogicAppsStandard.getDebugSymbolDll', deleteLogicApp = 'azureLogicAppsStandard.deleteLogicApp', - refresh = 'azureLogicAppsStandard.refresh', switchToDotnetProject = 'azureLogicAppsStandard.switchToDotnetProject', openInPortal = 'azureLogicAppsStandard.openInPortal', azureFunctionsOpenFile = 'azureFunctions.openFile', azureFunctionsUninstallFuncCoreTools = 'azureFunctions.uninstallFuncCoreTools', azureFunctionsAppSettingsEncrypt = 'azureFunctions.appSettings.encrypt', azureFunctionsAppSettingsDecrypt = 'azureFunctions.appSettings.decrypt', - azureSelectSubscriptions = 'azure-account.selectSubscriptions', openOverview = 'azureLogicAppsStandard.openOverview', exportLogicApp = 'azureLogicAppsStandard.exportLogicApp', reviewValidation = 'azureLogicAppsStandard.reviewValidation', @@ -116,6 +112,7 @@ export enum extensionCommand { configureDeploymentSource = 'azureLogicAppsStandard.configureDeploymentSource', startRemoteDebug = 'azureLogicAppsStandard.startRemoteDebug', validateLogicAppProjects = 'azureLogicAppsStandard.validateFunctionProjects', + reportIssue = 'azureLogicAppsStandard.reportIssue', } // Context @@ -184,3 +181,8 @@ export const logicAppKind = 'workflowapp'; export const logicAppKindAppSetting = 'workflowApp'; export const sqlStorageConnectionStringKey = 'Workflows.Sql.ConnectionString'; + +export const logicAppFilter = { + type: 'microsoft.web/sites', + kind: 'functionapp,workflowapp', +}; diff --git a/apps/vs-code-designer/src/extensionVariables.ts b/apps/vs-code-designer/src/extensionVariables.ts index c789504df7d..a9c98b79c99 100644 --- a/apps/vs-code-designer/src/extensionVariables.ts +++ b/apps/vs-code-designer/src/extensionVariables.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import type { AzureAccountTreeItemWithProjects } from './app/tree/AzureAccountTreeItemWithProjects'; import { func } from './constants'; -import type { AzExtTreeDataProvider, AzExtTreeItem, IAzExtOutputChannel } from '@microsoft/vscode-azext-utils'; +import type { IAzExtOutputChannel } from '@microsoft/vscode-azext-utils'; +import type { AzureHostExtensionApi } from '@microsoft/vscode-azext-utils/hostapi'; import type * as cp from 'child_process'; -import type { ExtensionContext, TreeView, WebviewPanel } from 'vscode'; +import type { ExtensionContext, WebviewPanel } from 'vscode'; /** * Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts @@ -22,11 +23,12 @@ export namespace ext { // Tree item view export let azureAccountTreeItem: AzureAccountTreeItemWithProjects; - export let tree: AzExtTreeDataProvider; - export let treeView: TreeView; export const treeViewName = 'azLogicApps'; export let deploymentFolderPath: string; + // Resource group API + export let rgApi: AzureHostExtensionApi; + // Functions export const funcCliPath: string = func; diff --git a/apps/vs-code-designer/src/main.ts b/apps/vs-code-designer/src/main.ts index c6df2148789..2b74def2f23 100644 --- a/apps/vs-code-designer/src/main.ts +++ b/apps/vs-code-designer/src/main.ts @@ -1,20 +1,23 @@ +import { LogicAppResolver } from './LogicAppResolver'; import { runPostWorkflowCreateStepsFromCache } from './app/commands/createCodeless/createCodelessSteps/WorkflowCreateStepBase'; import { validateFuncCoreToolsIsLatest } from './app/commands/funcCoreTools/validateFuncCoreToolsIsLatest'; import { registerCommands } from './app/commands/registerCommands'; -import { AzureAccountTreeItemWithProjects } from './app/tree/AzureAccountTreeItemWithProjects'; +import { getResourceGroupsApi } from './app/resourcesExtension/getExtensionApi'; +import type { AzureAccountTreeItemWithProjects } from './app/tree/AzureAccountTreeItemWithProjects'; import { stopDesignTimeApi } from './app/utils/codeless/startDesignTimeApi'; import { UriHandler } from './app/utils/codeless/urihandler'; import { registerFuncHostTaskEvents } from './app/utils/funcCoreTools/funcHostTask'; import { verifyVSCodeConfigOnActivate } from './app/utils/vsCodeConfig/verifyVSCodeConfigOnActivate'; -import { extensionCommand } from './constants'; +import { extensionCommand, logicAppFilter } from './constants'; import { ext } from './extensionVariables'; import { registerAppServiceExtensionVariables } from '@microsoft/vscode-azext-azureappservice'; import { - AzExtTreeDataProvider, callWithTelemetryAndErrorHandling, createAzExtOutputChannel, registerEvent, + registerReportIssueCommand, registerUIExtensionVariables, + getAzExtResourceType, } from '@microsoft/vscode-azext-utils'; import type { IActionContext } from '@microsoft/vscode-azext-utils'; import * as vscode from 'vscode'; @@ -39,12 +42,10 @@ export async function activate(context: vscode.ExtensionContext) { runPostWorkflowCreateStepsFromCache(); validateFuncCoreToolsIsLatest(); - ext.azureAccountTreeItem = new AzureAccountTreeItemWithProjects(); - ext.tree = new AzExtTreeDataProvider(ext.azureAccountTreeItem, extensionCommand.loadMore); - ext.treeView = vscode.window.createTreeView(ext.treeViewName, { - treeDataProvider: ext.tree, - showCollapseAll: true, - }); + ext.rgApi = await getResourceGroupsApi(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ext.azureAccountTreeItem = ext.rgApi.appResourceTree._rootTreeItem as AzureAccountTreeItemWithProjects; callWithTelemetryAndErrorHandling(extensionCommand.validateLogicAppProjects, async (actionContext: IActionContext) => { await verifyVSCodeConfigOnActivate(actionContext, vscode.workspace.workspaceFolders); @@ -60,11 +61,13 @@ export async function activate(context: vscode.ExtensionContext) { context.subscriptions.push(ext.outputChannel); context.subscriptions.push(ext.azureAccountTreeItem); - context.subscriptions.push(ext.treeView); + registerReportIssueCommand(extensionCommand.reportIssue); registerCommands(); registerFuncHostTaskEvents(); + ext.rgApi.registerApplicationResourceResolver(getAzExtResourceType(logicAppFilter), new LogicAppResolver()); + vscode.window.registerUriHandler(new UriHandler()); }); } diff --git a/apps/vs-code-designer/src/package.json b/apps/vs-code-designer/src/package.json index a527dd4c83b..dfad1a4f996 100644 --- a/apps/vs-code-designer/src/package.json +++ b/apps/vs-code-designer/src/package.json @@ -14,25 +14,45 @@ "language": "json" } ], - "commands": [ - { - "command": "azureLogicAppsStandard.selectSubscriptions", - "title": "Select Subscriptions...", - "icon": { - "light": "assets/light/filter.svg", - "dark": "assets/dark/filter.svg" - } + "x-azResources": { + "azure": { + "branches": [ + { + "type": "LogicApp" + } + ] }, + "workspace": { + "branches": [ + { + "type": "func" + } + ], + "resources": true + }, + "commands": [ + { + "command": "azureLogicAppsStandard.createLogicApp", + "title": "Create Logic App in Azure...", + "type": "microsoft.web/sites", + "detail": "To create and run automated workflows with little to no code." + } + ], + "activation": { + "onFetch": [ + "microsoft.web/sites" + ], + "onResolve": [ + "microsoft.web/sites" + ] + } + }, + "commands": [ { "command": "azureLogicAppsStandard.openDesigner", "title": "Open Designer", "category": "Azure Logic Apps" }, - { - "command": "azureLogicAppsStandard.loadMore", - "title": "Load More", - "category": "Azure Logic Apps" - }, { "command": "azureLogicAppsStandard.viewContent", "title": "View Content", @@ -105,15 +125,6 @@ "title": "Pick Process", "category": "Azure Logic Apps" }, - { - "command": "azureLogicAppsStandard.refresh", - "title": "Refresh", - "category": "Azure Logic Apps", - "icon": { - "light": "assets/light/refresh.svg", - "dark": "assets/dark/refresh.svg" - } - }, { "command": "azureLogicAppsStandard.openOverview", "title": "Overview", @@ -281,283 +292,250 @@ "command": "azureLogicAppsStandard.configureDeploymentSource", "title": "Configure Deployment Source...", "category": "Azure Logic Apps" + }, + { + "command": "azureLogicAppsStandard.reportIssue", + "title": "Report Issue...", + "category": "Azure Logic Apps" } ], - "viewsContainers": { - "activitybar": [ - { - "id": "azure", - "title": "Azure", - "icon": "assets/azure.svg" - } - ] - }, - "views": { - "azure": [ - { - "id": "azLogicApps", - "name": "Logic Apps (Standard)", - "when": "config.azureLogicAppsStandard.showExplorer == true" - } - ] - }, "menus": { "view/title": [ { "command": "azureLogicAppsStandard.createNewProject", - "when": "view == azLogicApps", + "when": "view == azureWorkspace", "group": "navigation@1" }, { "command": "azureLogicAppsStandard.createCodeless", - "when": "view == azLogicApps", + "when": "view == azureWorkspace", "group": "navigation@2" }, { "command": "azureLogicAppsStandard.deploy", - "when": "view == azLogicApps", + "when": "view == azureWorkspace", "group": "navigation@3" }, { "command": "azureLogicAppsStandard.exportLogicApp", - "when": "view == azLogicApps", + "when": "view == azureWorkspace", "group": "navigation@4" - }, - { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps", - "group": "navigation@5" } ], "view/item/context": [ - { - "command": "azureLogicAppsStandard.selectSubscriptions", - "when": "view == azLogicApps && viewItem == azureextensionui.azureSubscription", - "group": "inline" - }, { "command": "azureLogicAppsStandard.createLogicApp", - "when": "view == azLogicApps && viewItem == azureextensionui.azureSubscription", + "when": "view == azureResourceGroups && viewItem =~ /logicapp/i && viewItem =~ /azureResourceTypeGroup/i", "group": "1@1" }, { "command": "azureLogicAppsStandard.createLogicAppAdvanced", - "when": "view == azLogicApps && viewItem == azureextensionui.azureSubscription", + "when": "view == azureResourceGroups && viewItem =~ /logicapp/i && viewItem =~ /azureResourceTypeGroup/i", "group": "1@2" }, { "command": "azureLogicAppsStandard.deleteLogicApp", - "when": "view == azLogicApps && viewItem == azLogicAppsProductionSlot", + "when": "view == azureResourceGroups && viewItem =~ /azLogicAppsProductionSlot/", "group": "2@5" }, { "command": "azureLogicAppsStandard.openDesigner", - "when": "view == azLogicApps && viewItem =~ /Remote;.*;Workflow;/i", + "when": "view == azureResourceGroups && viewItem =~ /Remote;.*;Workflow;/i", "group": "1@2" }, + { + "command": "azureLogicAppsStandard.openOverview", + "when": "view == azureResourceGroups && viewItem =~ /Remote;.*;Workflow;/i", + "group": "1@1" + }, { "command": "azureLogicAppsStandard.viewContent", - "when": "view == azLogicApps && viewItem =~ /Remote;.*;Workflow;/i", + "when": "view == azureResourceGroups && viewItem =~ /Remote;.*;Workflow;/i", "group": "2@1" }, { "command": "azureLogicAppsStandard.deploy", - "when": "view == azLogicApps && viewItem == azLogicAppsProductionSlot", + "when": "view == azureResourceGroups && viewItem =~ /azLogicAppsProductionSlot/", "group": "3@1" }, { "command": "azureLogicAppsStandard.deploySlot", - "when": "view == azLogicApps && viewItem == azLogicAppsSlot", + "when": "view == azureResourceGroups && viewItem =~ /azLogicAppsSlot/", "group": "3@2" }, { "command": "azureLogicAppsStandard.startLogicApp", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps(Production|)Slot(?!s)/", "group": "2@1" }, { "command": "azureLogicAppsStandard.stopLogicApp", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps(Production|)Slot(?!s)/", "group": "2@2" }, { "command": "azureLogicAppsStandard.restartLogicApp", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps(Production|)Slot(?!s)/", "group": "2@3" }, { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem == azureextensionui.azureSubscription", - "group": "3@1" - }, - { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/", - "group": "6@2" - }, - { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem == azLogicAppsSlots", + "command": "azureResourceGroups.refresh", + "when": "view == azureResourceGroups && viewItem == azLogicAppsSlots", "group": "2@1" }, { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem =~ /Workflows;/i", + "command": "azureResourceGroups.refresh", + "when": "view == azureResourceGroups && viewItem =~ /Workflows;/i", "group": "1@1" }, { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem == applicationSettings", + "command": "azureResourceGroups.refresh", + "when": "view == azureResourceGroups && viewItem == applicationSettings", "group": "2@1" }, { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem == azFuncProxies", + "command": "azureResourceGroups.refresh", + "when": "view == azureResourceGroups && viewItem == azFuncProxies", "group": "1@1" }, { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem =~ /^deployments(C|Unc)onnected$/", + "command": "azureResourceGroups.refresh", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps.*(deployments(C|Unc)onnected)/", "group": "2@1" }, { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem == siteFiles", + "command": "azureResourceGroups.refresh", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps.*siteFiles/", "group": "1@1" }, { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem == logFiles", + "command": "azureResourceGroups.refresh", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps.*logFiles/", "group": "1@1" }, { - "command": "azureLogicAppsStandard.refresh", - "when": "view == azLogicApps && viewItem == folder", - "group": "1@1" - }, - { - "command": "azureLogicAppsStandard.openOverview", - "when": "view == azLogicApps && viewItem =~ /Remote;.*;Workflow;/i", + "command": "azureResourceGroups.refresh", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps.*folder/", "group": "1@1" }, { "command": "azureLogicAppsStandard.redeploy", - "when": "view == azLogicApps && viewItem =~ /^deployment//", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps.*deployment\\//", "group": "1@2" }, { "command": "azureLogicAppsStandard.openInPortal", - "when": "view == azLogicApps && viewItem == azureextensionui.azureSubscription", - "group": "2@1" - }, - { - "command": "azureLogicAppsStandard.openInPortal", - "when": "view == azLogicApps && viewItem == azLogicAppsProductionSlot", - "group": "1@1" + "when": "view == azureResourceGroups && viewItem =~ /Remote;.*;Workflow;/i", + "group": "2@2" }, { "command": "azureLogicAppsStandard.openInPortal", - "when": "view == azLogicApps && viewItem =~ /^deployment//", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps.*deployment\\//", "group": "1@3" }, { "command": "azureLogicAppsStandard.browseWebsite", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps(Production|)Slot(?!s)/", "group": "1@2" }, { "command": "azureLogicAppsStandard.viewProperties", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps(Production|)Slot(?!s).*slot/", "group": "6@1" }, { "command": "azureLogicAppsStandard.createSlot", - "when": "view == azLogicApps && viewItem == azLogicAppsSlots", + "when": "view == azureResourceGroups && viewItem == azLogicAppsSlots", "group": "1@1" }, { "command": "azureLogicAppsStandard.deleteSlot", - "when": "view == azLogicApps && viewItem == azLogicAppsSlot", + "when": "view == azureResourceGroups && viewItem =~ /azLogicAppsSlot(?!s)/", "group": "2@5" }, { "command": "azureLogicAppsStandard.viewDeploymentLogs", - "when": "view == azLogicApps && viewItem =~ /^deployment//", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps.*deployment\\//", "group": "1@1" }, { "command": "azureLogicAppsStandard.startStreamingLogs", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps(Production|)Slot(?!s)/", "group": "4@1" }, + { + "command": "azureLogicAppsStandard.startStreamingLogs", + "when": "view == azureResourceGroups && viewItem =~ /Remote;.*;Workflow;/i", + "group": "3@1" + }, { "command": "azureLogicAppsStandard.stopStreamingLogs", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps(Production|)Slot(?!s)/", "group": "4@2" }, { "command": "azureLogicAppsStandard.swapSlot", - "when": "view == azLogicApps && viewItem == azLogicAppsSlot", + "when": "view == azureResourceGroups && viewItem =~ /azLogicAppsSlot(?!s)/", "group": "2@4" }, { "command": "azureLogicAppsStandard.toggleAppSettingVisibility", - "when": "view == azLogicApps && viewItem == applicationSettingItem", + "when": "view == azureResourceGroups && viewItem == applicationSettingItem", "group": "inline" }, { "command": "azureLogicAppsStandard.appSettings.add", - "when": "view == azLogicApps && viewItem == applicationSettings", + "when": "view == azureResourceGroups && viewItem == applicationSettings", "group": "1@1" }, { "command": "azureLogicAppsStandard.appSettings.delete", - "when": "view == azLogicApps && viewItem == applicationSettingItem", + "when": "view == azureResourceGroups && viewItem == applicationSettingItem", "group": "1@3" }, { "command": "azureLogicAppsStandard.appSettings.download", - "when": "view == azLogicApps && viewItem == applicationSettings", + "when": "view == azureResourceGroups && viewItem == applicationSettings", "group": "1@2" }, { "command": "azureLogicAppsStandard.appSettings.edit", - "when": "view == azLogicApps && viewItem == applicationSettingItem", + "when": "view == azureResourceGroups && viewItem == applicationSettingItem", "group": "1@1" }, { "command": "azureLogicAppsStandard.appSettings.rename", - "when": "view == azLogicApps && viewItem == applicationSettingItem", + "when": "view == azureResourceGroups && viewItem == applicationSettingItem", "group": "1@2" }, { "command": "azureLogicAppsStandard.appSettings.toggleSlotSetting", - "when": "view == azLogicApps && viewItem == applicationSettingItem", + "when": "view == azureResourceGroups && viewItem == applicationSettingItem", "group": "1@4" }, { "command": "azureLogicAppsStandard.appSettings.upload", - "when": "view == azLogicApps && viewItem == applicationSettings", + "when": "view == azureResourceGroups && viewItem == applicationSettings", "group": "1@3" }, { "command": "azureLogicAppsStandard.disconnectRepo", - "when": "view == azLogicApps && viewItem == deploymentsConnected", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps.*deploymentsConnected/", "group": "1@2" }, { "command": "azureLogicAppsStandard.viewCommitInGitHub", - "when": "view == azLogicApps && viewItem == deployment/github", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps.*deployment\\/github/", "group": "1@4" }, { "command": "azureLogicAppsStandard.startRemoteDebug", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/ && config.azureLogicAppsStandard.enableRemoteDebugging == true", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps(Production|)Slot(?!s)/ && config.azureLogicAppsStandard.enableRemoteDebugging == true", "group": "5@1" }, { "command": "azureLogicAppsStandard.configureDeploymentSource", - "when": "view == azLogicApps && viewItem =~ /^azLogicApps(Production|)Slot$/", + "when": "view == azureResourceGroups && viewItem =~ /azLogicApps(Production|)Slot(?!s)/", "group": "3@2" } ], @@ -609,18 +587,10 @@ } ], "commandPalette": [ - { - "command": "azureLogicAppsStandard.selectSubscriptions", - "when": "never" - }, { "command": "azureLogicAppsStandard.openDesigner", "when": "resourceFilename==workflow.json" }, - { - "command": "azureLogicAppsStandard.loadMore", - "when": "never" - }, { "command": "azureLogicAppsStandard.viewContent", "when": "never" @@ -629,10 +599,6 @@ "command": "azureLogicAppsStandard.pickProcess", "when": "never" }, - { - "command": "azureLogicAppsStandard.refresh", - "when": "never" - }, { "command": "azureLogicAppsStandard.openOverview", "when": "resourceFilename==workflow.json" @@ -644,6 +610,10 @@ { "command": "azureLogicAppsStandard.startRemoteDebug", "when": "config.azureLogicAppsStandard.enableRemoteDebugging == true" + }, + { + "command": "azureLogicAppsStandard.viewProperties", + "when": "never" } ] }, @@ -778,11 +748,11 @@ "extensionDependencies": [ "ms-vscode.azure-account", "ms-azuretools.vscode-azurefunctions", - "azurite.azurite" + "azurite.azurite", + "ms-azuretools.vscode-azureresourcegroups" ], "activationEvents": [ "onCommand:azureLogicAppsStandard.openDesigner", - "onCommand:azureLogicAppsStandard.loadMore", "onCommand:azureLogicAppsStandard.viewContent", "onCommand:azureLogicAppsStandard.createNewProject", "onCommand:azureLogicAppsStandard.createCodeless", @@ -796,7 +766,6 @@ "onCommand:azureLogicAppsStandard.pickProcess", "onCommand:azureLogicAppsStandard.deleteLogicApp", "onCommand:azureLogicAppsStandard.openOverview", - "onCommand:azureLogicAppsStandard.refresh", "onCommand:azureLogicAppsStandard.exportLogicApp", "onCommand:azureLogicAppsStandard.reviewValidation", "onCommand:azureLogicAppsStandard.switchToDotnetProject", @@ -825,7 +794,8 @@ "onCommand:azureLogicAppsStandard.disconnectRepo", "onCommand:azureLogicAppsStandard.connectToGitHub", "onCommand:azureLogicAppsStandard.startRemoteDebug", - "onView:azLogicApps", + "onView:azureWorkspace", + "onView:azureResourceGroups", "workspaceContains:host.json", "workspaceContains:*/host.json", "onDebugInitialConfigurations"