From 5c0fe3d7a1fb86e5ae3b2a182bfb1aeb42395cba Mon Sep 17 00:00:00 2001 From: alexweininger Date: Thu, 19 Dec 2024 17:26:32 -0500 Subject: [PATCH] Prefix all ids with account and tenant ids --- src/tree/BranchDataItemCache.ts | 16 ++++------------ src/tree/BranchDataItemWrapper.ts | 9 +++++++++ src/tree/ResourceTreeDataProviderBase.ts | 12 +++++++++--- src/tree/azure/AzureResourceItem.ts | 6 ++++-- src/tree/azure/SubscriptionItem.ts | 5 +++-- src/tree/azure/grouping/GroupingItem.ts | 7 +++++-- src/tree/azure/idPrefix.ts | 5 +++++ test/api/viewProperties.test.ts | 2 +- 8 files changed, 40 insertions(+), 22 deletions(-) create mode 100644 src/tree/azure/idPrefix.ts diff --git a/src/tree/BranchDataItemCache.ts b/src/tree/BranchDataItemCache.ts index 4a90de60..a13c95ac 100644 --- a/src/tree/BranchDataItemCache.ts +++ b/src/tree/BranchDataItemCache.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { isAzExtTreeItem } from '@microsoft/vscode-azext-utils'; import { ResourceModelBase } from 'api/src/resources/base'; import { BranchDataItemWrapper } from './BranchDataItemWrapper'; import { ResourceGroupsItem } from './ResourceGroupsItem'; @@ -29,17 +28,14 @@ export class BranchDataItemCache { return this.branchItemToResourceGroupsItemCache.get(branchItem); } - getItemForBranchItemById(branchItem: ResourceModelBase): ResourceGroupsItem | undefined { - const id = this.getIdForBranchItem(branchItem); - if (!id) { - return undefined; - } + getItemForBranchItemById(id: string): ResourceGroupsItem | undefined { const cachedBranchItem = this.idToBranchItemCache.get(id); return cachedBranchItem ? this.branchItemToResourceGroupsItemCache.get(cachedBranchItem) : undefined; } - createOrGetItem(branchItem: ResourceModelBase, createItem: () => T): T { - const cachedItem = this.getItemForBranchItemById(branchItem) as T | undefined; + createOrGetItem(branchItem: ResourceModelBase, createItem: () => T, id: string): T { + const itemId = id ?? this.getIdForBranchItem(branchItem); + const cachedItem = this.getItemForBranchItemById(itemId) as T | undefined; if (cachedItem) { cachedItem.branchItem = branchItem; this.addBranchItem(branchItem, cachedItem); @@ -49,10 +45,6 @@ export class BranchDataItemCache { } private getIdForBranchItem(branchItem: ResourceModelBase): string | undefined { - if (isAzExtTreeItem(branchItem)) { - return branchItem.fullId; - } - return branchItem.id; } } diff --git a/src/tree/BranchDataItemWrapper.ts b/src/tree/BranchDataItemWrapper.ts index 095aa26d..6b9bae97 100644 --- a/src/tree/BranchDataItemWrapper.ts +++ b/src/tree/BranchDataItemWrapper.ts @@ -17,6 +17,7 @@ export type BranchDataItemOptions = { defaults?: vscode.TreeItem; portalUrl?: vscode.Uri; viewProperties?: ViewPropertiesModel; + idPrefix?: string; }; function appendContextValues(originalValues: string | undefined, optionsValues: string[] | undefined, extraValues: string[] | undefined): string { @@ -49,6 +50,7 @@ export class BranchDataItemWrapper implements ResourceGroupsItem, Wrapper { } else { this.id = this.branchItem.id ?? this?.options?.defaultId ?? uuidv4(); } + this.id = this.options?.idPrefix ? `${this.options.idPrefix}/${this.id}` : this.id; } public readonly id: string; @@ -69,12 +71,18 @@ export class BranchDataItemWrapper implements ResourceGroupsItem, Wrapper { factory(child, this.branchDataProvider, { portalUrl: (child as AzureResourceModel).portalUrl, viewProperties: (child as AzureResourceModel).viewProperties, + // recursively prefix child items with the account and tenant id + // this ensures that items provided by branch data providers are prefixed + idPrefix: this.options?.idPrefix, }) ); } async getTreeItem(): Promise { const treeItem = await this.branchDataProvider.getTreeItem(this.branchItem); + // set the id of the tree item to the id of the branch item + // we do this because the branch item has already modified the item's id (see constructor) + treeItem.id = this.id; const contextValue = appendContextValues(treeItem.contextValue, this.options?.contextValues, this.getExtraContextValues()); @@ -119,5 +127,6 @@ export function createBranchDataItemFactory(itemCache: BranchDataItemCache): Bra itemCache.createOrGetItem( branchItem, () => new BranchDataItemWrapper(branchItem, branchDataProvider, itemCache, options), + `${options?.idPrefix ?? ''}${branchItem.id}`, ) } diff --git a/src/tree/ResourceTreeDataProviderBase.ts b/src/tree/ResourceTreeDataProviderBase.ts index 3b1bc7a1..17077111 100644 --- a/src/tree/ResourceTreeDataProviderBase.ts +++ b/src/tree/ResourceTreeDataProviderBase.ts @@ -112,6 +112,8 @@ export abstract class ResourceTreeDataProviderBase extends vscode.Disposable imp for (const child of children) { if (child.id.toLowerCase() === id.toLowerCase()) { return child; + } else if (removePrefix(child.id.toLowerCase()) === id.toLowerCase()) { + return child; } else if (this.isAncestorOf(child, id)) { element = child; continue outerLoop; @@ -123,10 +125,14 @@ export abstract class ResourceTreeDataProviderBase extends vscode.Disposable imp } protected isAncestorOf(element: ResourceGroupsItem, id: string): boolean { - // remove accounts//tenant/ from the beginning of the id - const elementId = element.id.replace(/\/accounts\/[^/]+\/tenants\/[^/]+\//i, '/').toLowerCase() + '/'; - return id.toLowerCase().startsWith(elementId); + // remove accounts / /tenant/ from the beginning of the id + const elementId = removePrefix(element.id) + '/'; + return id.toLowerCase().startsWith(elementId.toLowerCase()); } protected abstract onGetChildren(element?: ResourceGroupsItem | undefined): Promise; } + +function removePrefix(id: string): string { + return id.replace(/\/accounts\/[^/]+\/tenants\/[^/]+\//i, '/') +} diff --git a/src/tree/azure/AzureResourceItem.ts b/src/tree/azure/AzureResourceItem.ts index ed582647..c84e5285 100644 --- a/src/tree/azure/AzureResourceItem.ts +++ b/src/tree/azure/AzureResourceItem.ts @@ -11,6 +11,7 @@ import { createPortalUrl } from '../../utils/v2/createPortalUrl'; import { BranchDataItemCache } from '../BranchDataItemCache'; import { BranchDataItemOptions, BranchDataItemWrapper } from '../BranchDataItemWrapper'; import { ResourceGroupsItem } from '../ResourceGroupsItem'; +import { createAzureIdPrefix } from './idPrefix'; export class AzureResourceItem extends BranchDataItemWrapper { constructor( @@ -27,7 +28,7 @@ export class AzureResourceItem extends BranchDataItemWr } override readonly portalUrl: Uri; - readonly id = this.resource.id; + readonly id = `${createAzureIdPrefix(this.resource.subscription)}${this.resource.id}`; readonly tagsModel = new ResourceTags(this.resource); override async getParent(): Promise { @@ -57,6 +58,7 @@ export function createResourceItemFactory(itemCache: Br return (resource, branchItem, branchDataProvider, parent, options) => itemCache.createOrGetItem( branchItem, - () => new AzureResourceItem(resource, branchItem, branchDataProvider, itemCache, parent, options) + () => new AzureResourceItem(resource, branchItem, branchDataProvider, itemCache, parent, options), + `${createAzureIdPrefix(resource.subscription)}${resource.id}`, ); } diff --git a/src/tree/azure/SubscriptionItem.ts b/src/tree/azure/SubscriptionItem.ts index 57f8f0c4..da8599ab 100644 --- a/src/tree/azure/SubscriptionItem.ts +++ b/src/tree/azure/SubscriptionItem.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { callWithTelemetryAndErrorHandling, createSubscriptionContext, IActionContext, ISubscriptionContext, nonNullValueAndProp } from "@microsoft/vscode-azext-utils"; +import { callWithTelemetryAndErrorHandling, createSubscriptionContext, IActionContext, ISubscriptionContext } from "@microsoft/vscode-azext-utils"; import * as vscode from "vscode"; import { AzureSubscription } from "../../../api/src/index"; import { AzureResourceProviderManager } from "../../api/ResourceProviderManagers"; @@ -13,6 +13,7 @@ import { createPortalUrl } from "../../utils/v2/createPortalUrl"; import { ResourceGroupsItem } from "../ResourceGroupsItem"; import { ResourceGroupsTreeContext } from "../ResourceGroupsTreeContext"; import { AzureResourceGroupingManager } from "./grouping/AzureResourceGroupingManager"; +import { createAzureIdPrefix } from "./idPrefix"; export class SubscriptionItem implements ResourceGroupsItem { constructor( @@ -28,7 +29,7 @@ export class SubscriptionItem implements ResourceGroupsItem { ...subscription }; - this.id = `/accounts/${nonNullValueAndProp(subscription.account, 'id')}/tenants/${subscription.tenantId}/subscriptions/${subscription.subscriptionId}`; + this.id = `${createAzureIdPrefix(this.subscription)}/subscriptions/${subscription.subscriptionId}`; this.description = description ? description : ''; this.portalUrl = createPortalUrl(this.subscription, `/subscriptions/${this.subscription.subscriptionId}`); diff --git a/src/tree/azure/grouping/GroupingItem.ts b/src/tree/azure/grouping/GroupingItem.ts index 69854732..f498a78b 100644 --- a/src/tree/azure/grouping/GroupingItem.ts +++ b/src/tree/azure/grouping/GroupingItem.ts @@ -17,6 +17,7 @@ import { ResourceGroupsItem } from '../../ResourceGroupsItem'; import { ResourceGroupsTreeContext } from '../../ResourceGroupsTreeContext'; import { BranchDataProviderFactory } from '../AzureResourceBranchDataProviderManager'; import { ResourceItemFactory } from '../AzureResourceItem'; +import { createAzureIdPrefix } from '../idPrefix'; import { GroupingItemFactoryOptions } from './GroupingItemFactory'; export class GroupingItem implements ResourceGroupsItem { @@ -56,7 +57,7 @@ export class GroupingItem implements ResourceGroupsItem { } : undefined; if (this.context?.subscription) { - this.id = `/subscriptions/${this.context?.subscriptionContext.subscriptionId}/account/${this.context?.subscription.account?.id}/groupings/${this.label}`; + this.id = `${createAzureIdPrefix(this.context?.subscription)}/subscriptions/${this.context?.subscriptionContext.subscriptionId}/groupings/${this.label}`; } else { // favorites groups don't always have a subscription this.id = `/groupings/${this.label}`; @@ -119,7 +120,9 @@ export class GroupingItem implements ResourceGroupsItem { viewProperties: resourceItem.viewProperties ?? { label: resource.name, data: resource.raw - } + }, + // prefix child items with the account and tenant id + idPrefix: createAzureIdPrefix(resource.subscription), }; items.push(this.resourceItemFactory(resource, resourceItem, branchDataProvider, this, options)); diff --git a/src/tree/azure/idPrefix.ts b/src/tree/azure/idPrefix.ts new file mode 100644 index 00000000..538c5582 --- /dev/null +++ b/src/tree/azure/idPrefix.ts @@ -0,0 +1,5 @@ +import { AzureSubscription } from "api/src/resources/azure"; + +export function createAzureIdPrefix(subscription: AzureSubscription): string { + return `/accounts/${subscription.account?.id}/tenants/${subscription.tenantId}`; +} diff --git a/test/api/viewProperties.test.ts b/test/api/viewProperties.test.ts index a0c7d791..3e4a2562 100644 --- a/test/api/viewProperties.test.ts +++ b/test/api/viewProperties.test.ts @@ -90,7 +90,7 @@ suite('AzureResourceModel.viewProperties tests', async () => { const functionGroup = groups!.find(g => g.label?.toString().includes('Func')); const children = await tdp.getChildren(functionGroup) as BranchDataItemWrapper[]; - const functionApp1Item = children.find(child => child.id === mockResources.functionApp1.id); + const functionApp1Item = children.find(child => child.id.endsWith(mockResources.functionApp1.id)); assert.ok(functionApp1Item); assert.ok(hasViewProperties(functionApp1Item));