Skip to content

Commit

Permalink
Changes to tree item picker (#1232)
Browse files Browse the repository at this point in the history
Co-authored-by: Brandon Waterloo [MSFT] <36966225+bwateratmsft@users.noreply.github.com>
  • Loading branch information
alexweininger and bwateratmsft authored Sep 7, 2022
1 parent bb38f55 commit 28c5e44
Show file tree
Hide file tree
Showing 17 changed files with 149 additions and 102 deletions.
96 changes: 79 additions & 17 deletions utils/hostapi.v2.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,54 @@
import { IActionContext } from "./index";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzExtTreeItem, IActionContext } from "./index";
import * as vscode from 'vscode';
import type { Environment } from '@azure/ms-rest-azure-env';
import { AzExtResourceType } from "./src/AzExtResourceType";

export interface ApplicationAuthentication {
getSession(scopes?: string[]): vscode.ProviderResult<vscode.AuthenticationSession>;
}

/**
* Information specific to the Subscription
*/
export interface ApplicationSubscription {
readonly authentication: ApplicationAuthentication;
readonly displayName: string;
readonly subscriptionId: string;
readonly environment: Environment;
readonly isCustomCloud: boolean;
}

export interface ResourceBase {
readonly id: string;
readonly name: string;
}

export interface ApplicationResourceType {
readonly type: string;
readonly kinds?: string[];
}

/**
* Represents an individual resource in Azure.
* @remarks The `id` property is expected to be the Azure resource ID.
*/
export interface ApplicationResource extends ResourceBase {
readonly subscription: ApplicationSubscription;
readonly type: ApplicationResourceType;
readonly azExtResourceType?: AzExtResourceType;
readonly location?: string;
readonly resourceGroup?: string;
/** Resource tags */
readonly tags?: {
[propertyName: string]: string;
};
/* add more properties from GenericResource if needed */
}

/**
* Interface describing an object that wraps another object.
Expand All @@ -12,28 +62,40 @@ import { IActionContext } from "./index";
* aren't boxes)
*/
export interface Box {
unwrap<T>(): Promise<T>;
unwrap<T>(): T;
}

/**
* Tests to see if something is a box, by ensuring it is an object
* and has an "unwrap" function
* @param maybeBox An object to test if it is a box
* @returns True if a box, false otherwise
*/
export declare function isBox(maybeBox: unknown): maybeBox is Box;

/**
* Describes command callbacks for tree node context menu commands
*/
export type TreeNodeCommandCallback<T> = (context: IActionContext, node?: T, nodes?: T[], ...args: any[]) => any;

/**
* Used to register VSCode tree node context menu commands that are in the host extension's tree. It wraps your callback with consistent error and telemetry handling
* Use debounce property if you need a delay between clicks for this particular command
* A telemetry event is automatically sent whenever a command is executed. The telemetry event ID will default to the same as the
* commandId passed in, but can be overridden per command with telemetryId
* The telemetry event for this command will be named telemetryId if specified, otherwise it defaults to the commandId
* NOTE: If the environment variable `DEBUGTELEMETRY` is set to a non-empty, non-zero value, then telemetry will not be sent. If the value is 'verbose' or 'v', telemetry will be displayed in the console window.
* Describes filtering based on context value. Items that pass the filter will
* match at least one of the `include` filters, but none of the `exclude` filters.
*/
export declare function registerCommandWithTreeNodeUnboxing<T>(commandId: string, callback: TreeNodeCommandCallback<T>, debounce?: number, telemetryId?: string): void;
export interface ContextValueFilter {
/**
* This filter will include items that match *any* of the values in the array.
* When a string is used, exact value comparison is done.
*/
include: string | RegExp | (string | RegExp)[];

/**
* This filter will exclude items that match *any* of the values in the array.
* When a string is used, exact value comparison is done.
*/
exclude?: string | RegExp | (string | RegExp)[];
}

export interface ContextValueFilterableTreeNodeV2 {
readonly quickPickOptions: {
readonly contextValues: Array<string>;
readonly isLeaf: boolean;
}
}

export type ContextValueFilterableTreeNode = ContextValueFilterableTreeNodeV2 | AzExtTreeItem;

// temporary type until we have the real type from RGs
export type ResourceGroupsItem = ContextValueFilterableTreeNode;
21 changes: 21 additions & 0 deletions utils/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { CancellationToken, CancellationTokenSource, Disposable, Event, Extensio
import { TargetPopulation } from 'vscode-tas-client';
import { AzureExtensionApi, AzureExtensionApiProvider } from './api';
import type { Activity, ActivityTreeItemOptions, AppResource, OnErrorActivityData, OnProgressActivityData, OnStartActivityData, OnSuccessActivityData } from './hostapi'; // This must remain `import type` or else a circular reference will result
import type { Box, ContextValueFilter, ResourceGroupsItem, TreeNodeCommandCallback } from './hostapi.v2';

export declare interface RunWithTemporaryDescriptionOptions {
description: string;
Expand Down Expand Up @@ -1678,3 +1679,23 @@ export declare enum AzExtResourceType {
VirtualNetworks = 'VirtualNetworks',
WebHostingEnvironments = 'WebHostingEnvironments',
}

/**
* Tests to see if something is a box, by ensuring it is an object
* and has an "unwrap" function
* @param maybeBox An object to test if it is a box
* @returns True if a box, false otherwise
*/
export declare function isBox(maybeBox: unknown): maybeBox is Box;

export declare function appResourceExperience<TPick>(context: IActionContext, tdp: TreeDataProvider<ResourceGroupsItem>, resourceType: AzExtResourceType, childItemFilter?: ContextValueFilter): Promise<TPick>;

/**
* Used to register VSCode tree node context menu commands that are in the host extension's tree. It wraps your callback with consistent error and telemetry handling
* Use debounce property if you need a delay between clicks for this particular command
* A telemetry event is automatically sent whenever a command is executed. The telemetry event ID will default to the same as the
* commandId passed in, but can be overridden per command with telemetryId
* The telemetry event for this command will be named telemetryId if specified, otherwise it defaults to the commandId
* NOTE: If the environment variable `DEBUGTELEMETRY` is set to a non-empty, non-zero value, then telemetry will not be sent. If the value is 'verbose' or 'v', telemetry will be displayed in the console window.
*/
export declare function registerCommandWithTreeNodeUnboxing<T>(commandId: string, callback: TreeNodeCommandCallback<T>, debounce?: number, telemetryId?: string): void;
1 change: 1 addition & 0 deletions utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,5 @@ export * from './utils/contextUtils';
export * from './activityLog/activities/ExecuteActivity';
export * from './getAzExtResourceType';
export * from './AzExtResourceType';
export * from './treev2/quickPickWizard/experiences/appResourceExperience';
// NOTE: The auto-fix action "source.organizeImports" does weird things with this file, but there doesn't seem to be a way to disable it on a per-file basis so we'll just let it happen
2 changes: 1 addition & 1 deletion utils/src/registerCommandWithTreeNodeUnboxing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function registerCommandWithTreeNodeUnboxing<T>(commandId: string, treeNo
const boxedNodes = maybeNodeBoxArray as Box[];
nodes = [];
for (const n of boxedNodes) {
nodes.push(await n.unwrap<T>())
nodes.push(n.unwrap<T>())
}
} else if (maybeNodeBoxArray && Array.isArray(maybeNodeBoxArray)) {
// Otherwise, assume it is just an array of T's
Expand Down
29 changes: 1 addition & 28 deletions utils/src/treev2/quickPickWizard/ContextValueQuickPickStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as types from '../../../index';
import { GenericQuickPickOptions, GenericQuickPickStep } from './GenericQuickPickStep';
import { isAzExtParentTreeItem } from '../../tree/InternalInterfaces';
import { QuickPickWizardContext } from './QuickPickWizardContext';

/**
* Describes filtering based on context value. Items that pass the filter will
* match at least one of the `include` filters, but none of the `exclude` filters.
*/
export interface ContextValueFilter {
/**
* This filter will include items that match *any* of the values in the array.
* When a string is used, exact value comparison is done.
*/
include: string | RegExp | (string | RegExp)[];

/**
* This filter will exclude items that match *any* of the values in the array.
* When a string is used, exact value comparison is done.
*/
exclude?: string | RegExp | (string | RegExp)[];
}

export interface ContextValueFilterableTreeNodeV2 {
readonly quickPickOptions: {
readonly contextValues: Array<string>;
readonly isLeaf: boolean;
}
}

export type ContextValueFilterableTreeNode = ContextValueFilterableTreeNodeV2 | types.AzExtTreeItem;
import { ContextValueFilter, ContextValueFilterableTreeNode, ContextValueFilterableTreeNodeV2 } from '../../../hostapi.v2';

export interface ContextValueFilterQuickPickOptions extends GenericQuickPickOptions {
contextValueFilter: ContextValueFilter;
Expand Down
3 changes: 2 additions & 1 deletion utils/src/treev2/quickPickWizard/FindByIdQuickPickStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import * as types from '../../../index';
import * as vscode from 'vscode';
import { getLastNode, QuickPickWizardContext } from './QuickPickWizardContext';
import { isAzExtParentTreeItem } from '../../tree/InternalInterfaces';
import { ContextValueFilterableTreeNodeV2, isContextValueFilterableTreeNodeV2 } from './ContextValueQuickPickStep';
import { isContextValueFilterableTreeNodeV2 } from './ContextValueQuickPickStep';
import { GenericQuickPickStep, SkipIfOneQuickPickOptions } from './GenericQuickPickStep';
import { ContextValueFilterableTreeNodeV2 } from '../../../hostapi.v2';

interface FindableByIdTreeNodeV2 extends ContextValueFilterableTreeNodeV2 {
id: vscode.Uri;
Expand Down
3 changes: 2 additions & 1 deletion utils/src/treev2/quickPickWizard/QuickPickWithCreateStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@

import * as types from '../../../index';
import { getLastNode, QuickPickWizardContext } from './QuickPickWizardContext';
import { ContextValueFilterableTreeNode, ContextValueFilterQuickPickOptions, ContextValueQuickPickStep } from './ContextValueQuickPickStep';
import { ContextValueFilterQuickPickOptions, ContextValueQuickPickStep } from './ContextValueQuickPickStep';
import { localize } from '../../localize';
import { ContextValueFilterableTreeNode } from '../../../hostapi.v2';

type CreateCallback = <TNode extends ContextValueFilterableTreeNode>() => TNode | Promise<TNode>;
interface CreateQuickPickOptions extends ContextValueFilterQuickPickOptions {
Expand Down
3 changes: 2 additions & 1 deletion utils/src/treev2/quickPickWizard/RecursiveQuickPickStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ContextValueFilterableTreeNode } from '../../../hostapi.v2';
import * as types from '../../../index';
import { ContextValueFilterableTreeNode, ContextValueFilterQuickPickOptions, ContextValueQuickPickStep } from './ContextValueQuickPickStep';
import { ContextValueFilterQuickPickOptions, ContextValueQuickPickStep } from './ContextValueQuickPickStep';
import { getLastNode, QuickPickWizardContext } from './QuickPickWizardContext';

export class RecursiveQuickPickStep<TNode extends ContextValueFilterableTreeNode, TContext extends QuickPickWizardContext<TNode>> extends ContextValueQuickPickStep<TNode, TContext, ContextValueFilterQuickPickOptions> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,23 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as types from '../../../../index';
import * as vscode from 'vscode';
import { ContextValueFilter } from '../ContextValueQuickPickStep';
import { QuickPickAzureSubscriptionStep } from '../quickPickAzureResource/QuickPickAzureSubscriptionStep';
import { QuickPickGroupStep } from '../quickPickAzureResource/QuickPickGroupStep';
import { QuickPickAppResourceStep } from '../quickPickAzureResource/QuickPickAppResourceStep';
import { AzureResourceQuickPickWizardContext } from '../quickPickAzureResource/AzureResourceQuickPickWizardContext';
import { RecursiveQuickPickStep } from '../RecursiveQuickPickStep';
import { Box, ResourceGroupsItem } from '../quickPickAzureResource/tempTypes';
import { getLastNode } from '../QuickPickWizardContext';
import { NoResourceFoundError } from '../../../errors';

export async function appResourceExperience<TPick>(context: types.IActionContext, tdp: vscode.TreeDataProvider<ResourceGroupsItem>, resourceType: types.AzExtResourceType, childItemFilter?: ContextValueFilter): Promise<TPick> {
const promptSteps: types.AzureWizardPromptStep<AzureResourceQuickPickWizardContext>[] = [
import { IActionContext } from '../../../../index';
import { AzureWizardPromptStep } from '../../../wizard/AzureWizardPromptStep';
import { AzExtResourceType } from '../../../AzExtResourceType';
import { AzureWizard } from '../../../wizard/AzureWizard';
import { ContextValueFilter, ResourceGroupsItem } from '../../../../hostapi.v2';
import { isBox } from '../../../registerCommandWithTreeNodeUnboxing';

export async function appResourceExperience<TPick>(context: IActionContext, tdp: vscode.TreeDataProvider<ResourceGroupsItem>, resourceType: AzExtResourceType, childItemFilter?: ContextValueFilter): Promise<TPick> {
const promptSteps: AzureWizardPromptStep<AzureResourceQuickPickWizardContext>[] = [
new QuickPickAzureSubscriptionStep(tdp),
new QuickPickGroupStep(tdp, {
groupType: resourceType,
Expand All @@ -38,7 +41,7 @@ export async function appResourceExperience<TPick>(context: types.IActionContext
const wizardContext = context as AzureResourceQuickPickWizardContext;
wizardContext.pickedNodes = [];

const wizard = new types.AzureWizard(context, {
const wizard = new AzureWizard(context, {
hideStepCount: true,
promptSteps: promptSteps,
});
Expand All @@ -50,9 +53,6 @@ export async function appResourceExperience<TPick>(context: types.IActionContext
if (!lastPickedItem) {
throw new NoResourceFoundError(wizardContext);
} else {
// Treat lastPickedItem as a box containing the desired end object
// TODO
const pickedAsBox = lastPickedItem as unknown as Box<TPick>;
return pickedAsBox.unwrap();
return isBox(lastPickedItem) ? lastPickedItem.unwrap<TPick>() : lastPickedItem as unknown as TPick;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as types from '../../../../index';
import * as vscode from 'vscode';
import { ContextValueFilter, ContextValueFilterableTreeNode } from '../ContextValueQuickPickStep';
import { RecursiveQuickPickStep } from '../RecursiveQuickPickStep';
import { getLastNode, QuickPickWizardContext } from '../QuickPickWizardContext';
import { NoResourceFoundError } from '../../../errors';
import { AzureWizardPromptStep } from '../../../wizard/AzureWizardPromptStep';
import { IActionContext } from '../../../../index';
import { AzureWizard } from '../../../wizard/AzureWizard';
import { ContextValueFilter, ContextValueFilterableTreeNode } from '../../../../hostapi.v2';

export async function contextValueExperience<TPick extends ContextValueFilterableTreeNode>(context: types.IActionContext, tdp: vscode.TreeDataProvider<TPick>, contextValueFilter: ContextValueFilter): Promise<TPick> {
const promptSteps: types.AzureWizardPromptStep<QuickPickWizardContext<TPick>>[] = [
export async function contextValueExperience<TPick extends ContextValueFilterableTreeNode>(context: IActionContext, tdp: vscode.TreeDataProvider<TPick>, contextValueFilter: ContextValueFilter): Promise<TPick> {
const promptSteps: AzureWizardPromptStep<QuickPickWizardContext<TPick>>[] = [
new RecursiveQuickPickStep(tdp, {
contextValueFilter: contextValueFilter,
skipIfOne: false,
Expand All @@ -22,7 +24,7 @@ export async function contextValueExperience<TPick extends ContextValueFilterabl
const wizardContext = context as QuickPickWizardContext<TPick>;
wizardContext.pickedNodes = [];

const wizard = new types.AzureWizard(context, {
const wizard = new AzureWizard(context, {
hideStepCount: true,
promptSteps: promptSteps,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ApplicationResource, ApplicationSubscription, ResourceGroupsItem } from "../../../../hostapi.v2";
import { QuickPickWizardContext } from "../QuickPickWizardContext";
import { ApplicationResource, ApplicationSubscription, ResourceGroupsItem } from "./tempTypes";

export interface AzureResourceQuickPickWizardContext extends QuickPickWizardContext<ResourceGroupsItem> {
subscription: ApplicationSubscription | undefined;
resource: ApplicationResource | undefined;
resourceGroup: string | undefined;
subscription?: ApplicationSubscription;
resource?: ApplicationResource;
resourceGroup?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ContextValueFilter, ResourceGroupsItem } from '../../../../hostapi.v2';
import * as types from '../../../../index';
import { ContextValueFilter } from '../ContextValueQuickPickStep';
import { GenericQuickPickOptions, GenericQuickPickStep } from '../GenericQuickPickStep';
import { AzureResourceQuickPickWizardContext } from './AzureResourceQuickPickWizardContext';
import { AppResourceItem, ResourceGroupsItem } from './tempTypes';
import { AppResourceItem } from './tempTypes';

interface AppResourceQuickPickOptions extends GenericQuickPickOptions {
resourceType: types.AzExtResourceType;
Expand All @@ -19,8 +19,8 @@ export class QuickPickAppResourceStep extends GenericQuickPickStep<ResourceGroup
const pickedAppResource = await super.promptInternal(wizardContext) as AppResourceItem;

// TODO
wizardContext.resource = pickedAppResource;
wizardContext.resourceGroup = pickedAppResource.resourceGroup;
wizardContext.resource = pickedAppResource.resource;
wizardContext.resourceGroup = pickedAppResource.resource.resourceGroup;

return pickedAppResource;
}
Expand All @@ -31,7 +31,7 @@ export class QuickPickAppResourceStep extends GenericQuickPickStep<ResourceGroup
return false;
}

return node.azExtResourceType === this.pickOptions.resourceType;
return node.resource.azExtResourceType === this.pickOptions.resourceType;
}

protected isIndirectPick(node: AppResourceItem): boolean {
Expand All @@ -40,6 +40,6 @@ export class QuickPickAppResourceStep extends GenericQuickPickStep<ResourceGroup
return false;
}

return node.azExtResourceType === this.pickOptions.resourceType;
return node.resource.azExtResourceType === this.pickOptions.resourceType;
}
}
Loading

0 comments on commit 28c5e44

Please sign in to comment.