Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove reliance on quickPickOptions, use TreeItems directly #1239

Merged
merged 17 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions utils/hostapi.v2.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

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

Expand Down Expand Up @@ -54,10 +54,7 @@ export declare interface ApplicationResource extends ResourceBase {
*/
export declare type TreeNodeCommandCallback<T> = (context: IActionContext, node?: T, nodes?: T[], ...args: any[]) => any;

// temporary type until we have the real type from RGs
export declare type ResourceGroupsItem = ContextValueFilterableTreeNode;

export declare interface AzureResourceQuickPickWizardContext extends QuickPickWizardContext<ResourceGroupsItem> {
export declare interface AzureResourceQuickPickWizardContext extends QuickPickWizardContext {
subscription?: ApplicationSubscription;
resource?: ApplicationResource;
resourceGroup?: string;
Expand Down
33 changes: 8 additions & 25 deletions utils/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +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 { ResourceGroupsItem, TreeNodeCommandCallback } from './hostapi.v2';
import type { TreeNodeCommandCallback } from './hostapi.v2';

export declare interface RunWithTemporaryDescriptionOptions {
description: string;
Expand Down Expand Up @@ -1706,19 +1706,19 @@ export declare interface Wrapper {
*/
export declare function isWrapper(maybeWrapper: unknown): maybeWrapper is Wrapper;

export declare function appResourceExperience<TPick extends ContextValueFilterableTreeNode>(context: IActionContext, tdp: TreeDataProvider<ResourceGroupsItem>, resourceTypes?: AzExtResourceType | AzExtResourceType[], childItemFilter?: ContextValueFilter): Promise<TPick>;
export declare function contextValueExperience<TPick extends ContextValueFilterableTreeNode>(context: IActionContext, tdp: TreeDataProvider<TPick>, contextValueFilter: ContextValueFilter): Promise<TPick>;
export declare function findByIdExperience<TPick extends FindableByIdTreeNode>(context: IActionContext, tdp: TreeDataProvider<TPick>, id: string | Uri): Promise<TPick>;
export declare function appResourceExperience<TPick extends unknown>(context: IActionContext, tdp: TreeDataProvider<TPick>, resourceTypes?: AzExtResourceType | AzExtResourceType[], childItemFilter?: ContextValueFilter): Promise<TPick>;
export declare function contextValueExperience<TPick extends unknown>(context: IActionContext, tdp: TreeDataProvider<TPick>, contextValueFilter: ContextValueFilter): Promise<TPick>;
export declare function findByIdExperience<TPick extends unknown>(context: IActionContext, tdp: TreeDataProvider<TPick>, id: string | Uri): Promise<TPick>;

interface CompatibilityPickResourceExperienceOptions {
resourceTypes?: AzExtResourceType | AzExtResourceType[];
childItemFilter?: ContextValueFilter
}

export declare function compatibilityPickAppResourceExperience<TPick extends AzExtTreeItem>(context: IActionContext, tdp: TreeDataProvider<ResourceGroupsItem>, options: CompatibilityPickResourceExperienceOptions): Promise<TPick>;
export declare function compatibilityPickAppResourceExperience<TPick extends AzExtTreeItem>(context: IActionContext, tdp: TreeDataProvider<unknown>, options: CompatibilityPickResourceExperienceOptions): Promise<TPick>;
bwateratmsft marked this conversation as resolved.
Show resolved Hide resolved

export declare interface QuickPickWizardContext<TNode extends unknown> extends IActionContext {
pickedNodes: TNode[];
export declare interface QuickPickWizardContext extends IActionContext {
pickedNodes: unknown[];
}

/**
Expand All @@ -1739,31 +1739,14 @@ export declare interface ContextValueFilter {
exclude?: string | RegExp | (string | RegExp)[];
}

interface QuickPickOptions {
readonly contextValues: Array<string>;
readonly isLeaf: boolean;
}

type CreateCallback<TNode = unknown> = (context: IActionContext) => TNode | Promise<TNode>;

type CreateOptions<TNode = unknown> = {
label?: string;
callback: CreateCallback<TNode>;
}

interface CompatibleQuickPickOptions extends QuickPickOptions {
readonly createChild?: CreateOptions;
}

export declare interface ContextValueFilterableTreeNode {
readonly quickPickOptions: QuickPickOptions;
}

export declare interface CompatibleContextValueFilterableTreeNode {
readonly quickPickOptions: CompatibleQuickPickOptions;
}

export declare interface FindableByIdTreeNodeV2 extends ContextValueFilterableTreeNode {
export declare interface FindableByIdTreeNodeV2 {
id: string;
}

Expand Down
13 changes: 8 additions & 5 deletions utils/src/treev2/quickPickWizard/ContextValueQuickPickStep.ts
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 { TreeItem } from 'vscode';
import * as types from '../../../index';
import { parseContextValue } from '../../utils/contextUtils';
import { GenericQuickPickOptions, GenericQuickPickStep } from './GenericQuickPickStep';

export interface ContextValueFilterQuickPickOptions extends GenericQuickPickOptions {
contextValueFilter: types.ContextValueFilter;
}

export class ContextValueQuickPickStep<TNode extends types.ContextValueFilterableTreeNode, TContext extends types.QuickPickWizardContext<TNode>, TOptions extends ContextValueFilterQuickPickOptions> extends GenericQuickPickStep<TNode, TContext, TOptions> {
protected override isDirectPick(node: TNode): boolean {
export class ContextValueQuickPickStep<TContext extends types.QuickPickWizardContext, TOptions extends ContextValueFilterQuickPickOptions> extends GenericQuickPickStep<TContext, TOptions> {
protected override isDirectPick(node: TreeItem): boolean {
const includeOption = this.pickOptions.contextValueFilter.include;
const excludeOption = this.pickOptions.contextValueFilter.exclude;

Expand All @@ -20,14 +22,15 @@ export class ContextValueQuickPickStep<TNode extends types.ContextValueFilterabl
(Array.isArray(excludeOption) ? excludeOption : [excludeOption]) :
[];

const nodeContextValues: string[] = node.quickPickOptions.contextValues;
const nodeContextValues: string[] = parseContextValue(node.contextValue);

return includeArray.some(i => this.matchesSingleFilter(i, nodeContextValues)) &&
!excludeArray.some(e => this.matchesSingleFilter(e, nodeContextValues));
}

protected override isIndirectPick(node: TNode): boolean {
return node.quickPickOptions.isLeaf === false;
protected override isIndirectPick(node: TreeItem): boolean {
// TreeItemCollapsibleState.None is falsy
alexweininger marked this conversation as resolved.
Show resolved Hide resolved
return !node.collapsibleState;
}

private matchesSingleFilter(matcher: string | RegExp, nodeContextValues: string[]): boolean {
Expand Down
50 changes: 10 additions & 40 deletions utils/src/treev2/quickPickWizard/FindByIdQuickPickStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import * as types from '../../../index';
import * as vscode from 'vscode';
import { getLastNode } from './QuickPickWizardContext';
import { GenericQuickPickStep, SkipIfOneQuickPickOptions } from './GenericQuickPickStep';
import { isAzExtParentTreeItem } from '../../tree/isAzExtParentTreeItem';

interface FindByIdQuickPickOptions extends SkipIfOneQuickPickOptions {
id: string;
skipIfOne?: true;
}

export class FindByIdQuickPickStep<TNode extends types.FindableByIdTreeNode, TContext extends types.QuickPickWizardContext<TNode>> extends GenericQuickPickStep<TNode, TContext, FindByIdQuickPickOptions> {
public constructor(tdp: vscode.TreeDataProvider<TNode>, options: FindByIdQuickPickOptions) {
export class FindByIdQuickPickStep<TContext extends types.QuickPickWizardContext> extends GenericQuickPickStep<TContext, FindByIdQuickPickOptions> {
public constructor(tdp: vscode.TreeDataProvider<unknown>, options: FindByIdQuickPickOptions) {
super(
tdp,
{
Expand Down Expand Up @@ -50,49 +49,20 @@ export class FindByIdQuickPickStep<TNode extends types.FindableByIdTreeNode, TCo
}
}

protected override isIndirectPick(node: TNode): boolean {
if (isFindableByIdTreeNodeV2(node)) {
if (node.quickPickOptions.isLeaf) {
return false;
}

return this.pickOptions.id.startsWith(node.id);
} else {
if (!isAzExtParentTreeItem(node)) {
return false;
}

return this.pickOptions.id.startsWith(node.fullId);
protected override isIndirectPick(node: vscode.TreeItem): boolean {
if (!node.collapsibleState) {
bwateratmsft marked this conversation as resolved.
Show resolved Hide resolved
// can't be an indirect pick if it doesn't have children
return false;
}
}

protected override isDirectPick(node: TNode): boolean {
if (isFindableByIdTreeNodeV2(node)) {
return this.pickOptions.id === node.id;
} else {
return this.pickOptions.id === node.fullId;
if (node.id) {
return node.id.startsWith(node.id);
}
}
}

function isContextValueFilterableTreeNodeV2(maybeNode: unknown): maybeNode is types.ContextValueFilterableTreeNode {
if (typeof maybeNode === 'object') {
return Array.isArray((maybeNode as types.ContextValueFilterableTreeNode).quickPickOptions.contextValues) &&
(maybeNode as types.ContextValueFilterableTreeNode).quickPickOptions?.isLeaf !== undefined &&
(maybeNode as types.ContextValueFilterableTreeNode).quickPickOptions?.isLeaf !== null;
}

return false;
}

function isFindableByIdTreeNodeV2(maybeNode: unknown): maybeNode is types.FindableByIdTreeNodeV2 {
if (!isContextValueFilterableTreeNodeV2(maybeNode)) {
return false;
}

if (typeof maybeNode === 'object') {
return typeof (maybeNode as types.FindableByIdTreeNodeV2).id === 'string' && !!(maybeNode as types.FindableByIdTreeNodeV2).id;
protected override isDirectPick(node: vscode.TreeItem): boolean {
return this.pickOptions.id === node.id;
}

return false;
}
38 changes: 19 additions & 19 deletions utils/src/treev2/quickPickWizard/GenericQuickPickStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ export interface SkipIfOneQuickPickOptions extends GenericQuickPickOptions {
skipIfOne?: true;
}

export abstract class GenericQuickPickStep<TNode extends unknown, TContext extends types.QuickPickWizardContext<TNode>, TOptions extends GenericQuickPickOptions> extends AzureWizardPromptStep<TContext> {
export abstract class GenericQuickPickStep<TContext extends types.QuickPickWizardContext, TOptions extends GenericQuickPickOptions> extends AzureWizardPromptStep<TContext> {
public readonly supportsDuplicateSteps = true;

public constructor(
protected readonly treeDataProvider: vscode.TreeDataProvider<TNode>,
protected readonly treeDataProvider: vscode.TreeDataProvider<unknown>,
protected readonly pickOptions: TOptions
) {
super();
Expand All @@ -49,7 +49,7 @@ export abstract class GenericQuickPickStep<TNode extends unknown, TContext exten
return true;
}

protected async promptInternal(wizardContext: TContext): Promise<TNode> {
protected async promptInternal(wizardContext: TContext): Promise<unknown> {
const picks = await this.getPicks(wizardContext);

if (picks.length === 1 && this.pickOptions.skipIfOne) {
Expand All @@ -64,16 +64,18 @@ export abstract class GenericQuickPickStep<TNode extends unknown, TContext exten
}
}

protected async getPicks(wizardContext: TContext): Promise<types.IAzureQuickPickItem<TNode>[]> {
const lastPickedItem: TNode | undefined = getLastNode(wizardContext);
protected async getPicks(wizardContext: TContext): Promise<types.IAzureQuickPickItem<unknown>[]> {
const lastPickedItem: unknown | undefined = getLastNode(wizardContext);

// TODO: if `lastPickedItem` is an `AzExtParentTreeItem`, should we clear its cache?
const children = (await this.treeDataProvider.getChildren(lastPickedItem)) || [];
const childElements = (await this.treeDataProvider.getChildren(lastPickedItem)) || [];
alexweininger marked this conversation as resolved.
Show resolved Hide resolved
const childItems = await Promise.all(childElements.map(async (childElement: unknown) => await this.treeDataProvider.getTreeItem(childElement)));
const childs: [unknown, vscode.TreeItem][] = childElements.map((childElement: unknown, i: number) => [childElement, childItems[i]]);
alexweininger marked this conversation as resolved.
Show resolved Hide resolved

const directChoices = children.filter(c => this.isDirectPick(c));
const indirectChoices = children.filter(c => this.isIndirectPick(c));
const directChoices = childs.filter(([, ti]) => this.isDirectPick(ti));
const indirectChoices = childs.filter(([, ti]) => this.isIndirectPick(ti));

let promptChoices: TNode[];
let promptChoices: [unknown, vscode.TreeItem][];
if (directChoices.length === 0) {
if (indirectChoices.length === 0) {
throw new NoResourceFoundError();
Expand All @@ -84,9 +86,9 @@ export abstract class GenericQuickPickStep<TNode extends unknown, TContext exten
promptChoices = directChoices;
}

const picks: types.IAzureQuickPickItem<TNode>[] = [];
const picks: types.IAzureQuickPickItem<unknown>[] = [];
for (const choice of promptChoices) {
picks.push(await this.getQuickPickItem(choice));
picks.push(await this.getQuickPickItem(...choice));
}

return picks;
Expand All @@ -96,21 +98,19 @@ export abstract class GenericQuickPickStep<TNode extends unknown, TContext exten
* Filters for nodes that match the final target.
* @param node The node to apply the filter to
*/
protected abstract isDirectPick(node: TNode): boolean;
protected abstract isDirectPick(node: vscode.TreeItem): boolean;

/**
* Filters for nodes that could have a descendant matching the final target.
* @param node The node to apply the filter to
*/
protected abstract isIndirectPick(node: TNode): boolean;

private async getQuickPickItem(resource: TNode): Promise<types.IAzureQuickPickItem<TNode>> {
const treeItem = await Promise.resolve(this.treeDataProvider.getTreeItem(resource));
protected abstract isIndirectPick(node: vscode.TreeItem): boolean;

private async getQuickPickItem(node: unknown, item: vscode.TreeItem,): Promise<types.IAzureQuickPickItem<unknown>> {
alexweininger marked this conversation as resolved.
Show resolved Hide resolved
return {
label: ((treeItem.label as vscode.TreeItemLabel)?.label || treeItem.label) as string,
description: treeItem.description as string,
data: resource,
label: ((item.label as vscode.TreeItemLabel)?.label || item.label) as string,
description: item.description as string,
data: node,
};
}
}
12 changes: 6 additions & 6 deletions utils/src/treev2/quickPickWizard/QuickPickWithCreateStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@ import { ContextValueFilterQuickPickOptions, ContextValueQuickPickStep } from '.
import { localize } from '../../localize';
import { NoResourceFoundError } from '../../errors';

type CreateCallback = <TNode extends types.ContextValueFilterableTreeNode>() => TNode | Promise<TNode>;
type CreateCallback = <TNode extends unknown>() => TNode | Promise<TNode>;
interface CreateQuickPickOptions extends ContextValueFilterQuickPickOptions {
skipIfOne?: never; // Not allowed in CreateQuickPickStep
createLabel?: string;
createCallback: CreateCallback;
}

export class CreateQuickPickStep<TNode extends types.ContextValueFilterableTreeNode, TContext extends types.QuickPickWizardContext<TNode>> extends ContextValueQuickPickStep<TNode, TContext, CreateQuickPickOptions> {
export class CreateQuickPickStep<TContext extends types.QuickPickWizardContext> extends ContextValueQuickPickStep<TContext, CreateQuickPickOptions> {
public override async prompt(wizardContext: TContext): Promise<void> {
await super.prompt(wizardContext);

const lastNode = getLastNode(wizardContext) as (TNode | CreateCallback);
const lastNode = getLastNode(wizardContext) as (unknown | CreateCallback);
bwateratmsft marked this conversation as resolved.
Show resolved Hide resolved
if (typeof lastNode === 'function') {
// If the last node is a function, pop it off the list and execute it
const callback = wizardContext.pickedNodes.pop() as unknown as CreateCallback;
wizardContext.pickedNodes.push(await callback());
}
}

protected override async getPicks(wizardContext: TContext): Promise<types.IAzureQuickPickItem<TNode>[]> {
const picks: types.IAzureQuickPickItem<TNode | types.CreateCallback>[] = [];
protected override async getPicks(wizardContext: TContext): Promise<types.IAzureQuickPickItem<unknown>[]> {
const picks: types.IAzureQuickPickItem<unknown | types.CreateCallback>[] = [];
try {
picks.push(...await super.getPicks(wizardContext));
} catch (error) {
Expand All @@ -41,7 +41,7 @@ export class CreateQuickPickStep<TNode extends types.ContextValueFilterableTreeN
}

picks.push(this.getCreatePick());
return picks as types.IAzureQuickPickItem<TNode>[];
return picks as types.IAzureQuickPickItem<unknown>[];
}

private getCreatePick(): types.IAzureQuickPickItem<CreateCallback> {
Expand Down
2 changes: 1 addition & 1 deletion utils/src/treev2/quickPickWizard/QuickPickWizardContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import * as types from '../../../index';

export function getLastNode<TNode extends unknown>(context: types.QuickPickWizardContext<TNode>): TNode | undefined {
export function getLastNode(context: types.QuickPickWizardContext): unknown | undefined {
alexweininger marked this conversation as resolved.
Show resolved Hide resolved
if (context.pickedNodes.length) {
return context.pickedNodes[context.pickedNodes.length - 1];
}
Expand Down
4 changes: 2 additions & 2 deletions utils/src/treev2/quickPickWizard/RecursiveQuickPickStep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as types from '../../../index';
import { ContextValueFilterQuickPickOptions, ContextValueQuickPickStep } from './ContextValueQuickPickStep';
import { getLastNode } from './QuickPickWizardContext';

export class RecursiveQuickPickStep<TNode extends types.ContextValueFilterableTreeNode, TContext extends types.QuickPickWizardContext<TNode>> extends ContextValueQuickPickStep<TNode, TContext, ContextValueFilterQuickPickOptions> {
export class RecursiveQuickPickStep<TContext extends types.QuickPickWizardContext> extends ContextValueQuickPickStep<TContext, ContextValueFilterQuickPickOptions> {
public async getSubWizard(wizardContext: TContext): Promise<types.IWizardOptions<TContext> | undefined> {
const lastPickedItem = getLastNode(wizardContext);

Expand All @@ -16,7 +16,7 @@ export class RecursiveQuickPickStep<TNode extends types.ContextValueFilterableTr
throw new Error('No node was set after prompt step.');
}

if (super.isDirectPick(lastPickedItem)) {
if (super.isDirectPick(await this.treeDataProvider.getTreeItem((lastPickedItem)))) {
// The last picked node matches the expected filter
// No need to continue prompting
return undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { isAzExtParentTreeItem } from "../../../tree/isAzExtParentTreeItem";
/**
* Provides compatability with {@link AzExtParentTreeItem.pickTreeItemImpl}
*/
export class CompatibilityContextValueQuickPickStep<TNode extends types.CompatibleContextValueFilterableTreeNode, TContext extends types.QuickPickWizardContext<TNode>, TOptions extends ContextValueFilterQuickPickOptions> extends ContextValueQuickPickStep<TNode, TContext, TOptions> {
export class CompatibilityContextValueQuickPickStep<TContext extends types.QuickPickWizardContext, TOptions extends ContextValueFilterQuickPickOptions> extends ContextValueQuickPickStep<TContext, TOptions> {

public override async prompt(wizardContext: TContext): Promise<void> {
await this.provideCompatabilityWithPickTreeItemImpl(wizardContext) || await super.prompt(wizardContext);
Expand Down
Loading