Skip to content

Commit

Permalink
feat(vscode): Move extension tab to resources tab (#2107)
Browse files Browse the repository at this point in the history
* Add report issue command

* Initial commit changing commands to look for rg tree and initialize rg tree

* Add resolver for logic apps resources and update commands view

* Remove ProductionSlotTreeItem

* Update slot tree item

* Remove slot tree item base

* Remove refresh command

* Add unwrapping for registered commands and remove open in portal and view properties declarations

* Fix notify complete

* Remove azure icon and update when in commands

* Fix Open Overview command

* Add icon to overview panel and update azuretreeview

* Fix commands

* Fix rest of commands

* Fix refresh for folders

* Move get credentials to a separate file

* Add suppressCreatePick: false
  • Loading branch information
ccastrotrejo committed Apr 28, 2023
1 parent b201440 commit 2a92423
Show file tree
Hide file tree
Showing 59 changed files with 1,091 additions and 777 deletions.
24 changes: 24 additions & 0 deletions apps/vs-code-designer/src/LogicAppResolver.ts
Original file line number Diff line number Diff line change
@@ -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<LogicAppResourceTree | undefined> {
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -20,7 +20,10 @@ import * as vscode from 'vscode';

export async function downloadAppSettings(context: IActionContext, node?: AppSettingsTreeItem): Promise<void> {
if (!node) {
node = await ext.tree.showTreeItemPicker<AppSettingsTreeItem>(AppSettingsTreeItem.contextValue, context);
node = await ext.rgApi.pickAppResource<AppSettingsTreeItem>(context, {
filter: logicAppFilter,
expectedChildContextValue: new RegExp(AppSettingsTreeItem.contextValue),
});
}

const client: IAppSettingsClient = await node.clientProvider.createClient(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
if (!node) {
node = await ext.tree.showTreeItemPicker<AppSettingTreeItem>(AppSettingTreeItem.contextValue, context);
node = await ext.rgApi.pickAppResource<AppSettingTreeItem>(context, {
filter: logicAppFilter,
expectedChildContextValue: new RegExp(AppSettingTreeItem.contextValue),
});
}

await node.edit(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
if (!node) {
node = await ext.tree.showTreeItemPicker<AppSettingTreeItem>(AppSettingTreeItem.contextValue, context);
node = await ext.rgApi.pickAppResource<AppSettingTreeItem>(context, {
filter: logicAppFilter,
expectedChildContextValue: new RegExp(AppSettingTreeItem.contextValue),
});
}

await node.rename(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
if (!node) {
node = await ext.tree.showTreeItemPicker<AppSettingTreeItem>(AppSettingTreeItem.contextValue, context);
node = await ext.rgApi.pickAppResource<AppSettingTreeItem>(context, {
filter: logicAppFilter,
expectedChildContextValue: new RegExp(AppSettingTreeItem.contextValue),
});
}

await node.toggleSlotSetting(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>} Workspace file path.
*/
export async function uploadAppSettings(
context: IActionContext,
node?: AppSettingsTreeItem,
workspacePath?: WorkspaceFolder,
settingsToExclude: string[] = []
_nodes?: [],
workspacePath?: vscode.WorkspaceFolder,
exclude?: (RegExp | string)[]
): Promise<void> {
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>(AppSettingsTreeItem.contextValue, context);
node = await ext.rgApi.pickAppResource<AppSettingsTreeItem>(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));
}
Expand Down
10 changes: 6 additions & 4 deletions apps/vs-code-designer/src/app/commands/browseWebsite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
export async function browseWebsite(context: IActionContext, node?: SlotTreeItem): Promise<void> {
if (!node) {
node = await ext.tree.showTreeItemPicker<SlotTreeItemBase>(ProductionSlotTreeItem.contextValue, context);
node = await ext.rgApi.pickAppResource<SlotTreeItem>(context, {
filter: logicAppFilter,
});
}

await openUrl(node.site.defaultHostUrl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
export async function configureDeploymentSource(context: IActionContext, node?: SlotTreeItem): Promise<void> {
if (!node) {
node = await ext.tree.showTreeItemPicker<SlotTreeItemBase>(ProductionSlotTreeItem.contextValue, context);
node = await ext.rgApi.pickAppResource<SlotTreeItem>(context, {
filter: logicAppFilter,
});
}

const updatedScmType: string | undefined = await editScmType(context, node.site, node.subscription);
Expand Down
9 changes: 8 additions & 1 deletion apps/vs-code-designer/src/app/commands/createChildNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -11,7 +12,13 @@ export async function createChildNode(
node?: AzExtParentTreeItem
): Promise<void> {
if (!node) {
node = await ext.tree.showTreeItemPicker<AzExtParentTreeItem>(expectedContextValue, context);
node = await ext.rgApi.pickAppResource<AzExtParentTreeItem>(
{ ...context, suppressCreatePick: true },
{
filter: logicAppFilter,
expectedChildContextValue: expectedContextValue,
}
);
}

await node.createChild(context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -14,26 +14,31 @@ import type { ICreateLogicAppContext } from '@microsoft/vscode-extension';
export async function createLogicApp(
context: IActionContext & Partial<ICreateLogicAppContext>,
subscription?: AzExtParentTreeItem | string,
newResourceGroupName?: string
nodesOrNewResourceGroupName?: string | (string | AzExtParentTreeItem)[]
): Promise<string> {
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<AzExtParentTreeItem>(SubscriptionTreeItem.contextValue, context);
node = await ext.rgApi.appResourceTree.showTreeItemPicker<AzExtParentTreeItem>(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}`);
}
Expand All @@ -42,7 +47,7 @@ export async function createLogicApp(
export async function createLogicAppAdvanced(
context: IActionContext,
subscription?: AzExtParentTreeItem | string,
newResourceGroupName?: string
nodesOrNewResourceGroupName?: string | (string | AzExtParentTreeItem)[]
): Promise<string> {
return await createLogicApp({ ...context, advancedCreation: true }, subscription, newResourceGroupName);
return await createLogicApp({ ...context, advancedCreation: true }, subscription, nodesOrNewResourceGroupName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@
*--------------------------------------------------------------------------------------------*/
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';
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<void> {
export async function notifyCreateLogicAppComplete(node: SlotTreeItem): Promise<void> {
const deployComplete: string = localize('creationComplete', 'Creation of "{0}" completed.', node.site.fullName);
const viewOutput: MessageItem = { title: localize('viewOutput', 'View output') };

Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}
});
}
}
Loading

0 comments on commit 2a92423

Please sign in to comment.