Skip to content

Commit

Permalink
Add Accounts & Tenants view (#869)
Browse files Browse the repository at this point in the history
  • Loading branch information
motm32 authored Dec 6, 2024
1 parent 4f7b1f5 commit 2ba8f3a
Show file tree
Hide file tree
Showing 27 changed files with 672 additions and 133 deletions.
5 changes: 5 additions & 0 deletions api/src/resources/azure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ export interface AzureSubscription {
* The tenant to which this subscription belongs or undefined, if not associated with a specific tenant.
*/
readonly tenantId: string;

/**
* The account associated with this subscription. This is optional as we only need the account if there are duplicate subscriptions.
*/
readonly account?: vscode.AuthenticationSessionAccountInformation;
}

/**
Expand Down
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

50 changes: 46 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"icon": "resources/resourceGroup.png",
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
"engines": {
"vscode": "^1.82.0"
"vscode": "^1.93.0"
},
"repository": {
"type": "git",
Expand Down Expand Up @@ -236,6 +236,24 @@
"command": "azureResourceGroups.showGroupOptions",
"title": "%azureResourceGroups.showGroupOptions%",
"category": "Azure"
},
{
"command": "azureTenantsView.refresh",
"title": "%azureResourceGroups.refresh%",
"category": "Azure",
"icon": "$(refresh)"
},
{
"command": "azureTenantsView.refreshTree",
"title": "%azureResourceGroups.refresh%",
"category": "Azure",
"icon": "$(refresh)"
},
{
"command": "azureTenantsView.signInToTenant",
"title": "%azureTenantsView.signInToTenant%",
"category": "Azure",
"icon": "$(sign-in)"
}
],
"viewsContainers": {
Expand Down Expand Up @@ -272,6 +290,11 @@
"name": "Workspace",
"visibility": "visible"
},
{
"id": "azureTenantsView",
"name": "Accounts & Tenants",
"visibility": "collapsed"
},
{
"id": "ms-azuretools.helpAndFeedback",
"name": "%ms-azuretools.helpAndFeedback%",
Expand Down Expand Up @@ -355,6 +378,11 @@
"when": "view == azureWorkspace",
"group": "navigation@10"
},
{
"command": "azureTenantsView.refreshTree",
"when": "view == azureTenantsView",
"group": "navigation@10"
},
{
"command": "azureResourceGroups.clearActivities",
"when": "view == azureActivityLog",
Expand Down Expand Up @@ -431,6 +459,16 @@
"command": "azureResourceGroups.viewProperties",
"when": "view =~ /azureResourceGroups|azureFocusView/ && viewItem =~ /azureResourceGroup/",
"group": "9@1"
},
{
"command": "azureTenantsView.signInToTenant",
"when": "view == azureTenantsView && viewItem =~ /tenantNameNotSignedIn/",
"group": "inline@1"
},
{
"command": "azureTenantsView.signInToTenant",
"when": "view == azureTenantsView && viewItem =~ /tenantNameNotSignedIn/",
"group": "1@1"
}
],
"commandPalette": [
Expand All @@ -454,6 +492,10 @@
"command": "azureWorkspace.refresh",
"when": "never"
},
{
"command": "azureTenantsView.refresh",
"when": "never"
},
{
"command": "azureFocusView.refreshTree",
"when": "never"
Expand Down Expand Up @@ -688,15 +730,15 @@
"@azure/arm-resources-subscriptions": "^2.1.0",
"@azure/identity": "^4.2.1",
"@microsoft/api-extractor": "^7.33.8",
"@microsoft/eslint-config-azuretools": "^0.2.1",
"@microsoft/eslint-config-azuretools": "0.2.1",
"@microsoft/vscode-azext-dev": "^2.0.5",
"@types/gulp": "^4.0.6",
"@types/mocha": "^7.0.2",
"@types/node": "18.19.x",
"@types/request-promise": "^4.1.51",
"@types/semver": "^7.3.12",
"@types/uuid": "^9.0.1",
"@types/vscode": "^1.81.0",
"@types/vscode": "1.93.0",
"@types/ws": "^8.5.10",
"@typescript-eslint/eslint-plugin": "^5.59.11",
"@vscode/test-electron": "^2.3.8",
Expand All @@ -720,7 +762,7 @@
"@azure/arm-resources": "^5.2.0",
"@azure/arm-resources-profile-2020-09-01-hybrid": "^2.1.0",
"@azure/ms-rest-js": "^2.7.0",
"@microsoft/vscode-azext-azureauth": "^2.5.0",
"@microsoft/vscode-azext-azureauth": "^3.1.0",
"@microsoft/vscode-azext-azureutils": "^3.1.1",
"@microsoft/vscode-azext-utils": "^2.5.11",
"buffer": "^6.0.3",
Expand Down
2 changes: 2 additions & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"azureResourceGroups.openInPortal": "Open in Portal",
"azureResourceGroups.refresh": "Refresh",
"azureWorkspace.refresh": "Refresh Workspace",
"azureTenantsView.refresh": "Refresh Accounts & Tenants",
"azureTenantsView.signInToTenant": "Sign in to Tenant",
"azureResourceGroups.selectedSubscriptions": "Selected Subscriptions",
"azureResourceGroups.revealResource": "Reveal Resource",
"azureResourceGroups.viewProperties": "View Properties",
Expand Down
4 changes: 4 additions & 0 deletions src/api/ResourceProviderManagers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import * as vscode from 'vscode';
import { AzureResource, AzureSubscription, ResourceBase, ResourceProvider, WorkspaceResource, WorkspaceResourceProvider } from '../../api/src/index';
import { AzureResourceProvider } from '../hostapi.v2.internal';
import { TenantResource, TenantResourceProvider } from '../tree/tenants/tenant';

export function isArray<T>(maybeArray: T[] | null | undefined): maybeArray is T[] {
return Array.isArray(maybeArray);
Expand Down Expand Up @@ -87,3 +88,6 @@ export class AzureResourceProviderManager extends ResourceProviderManager<AzureS

export class WorkspaceResourceProviderManager extends ResourceProviderManager<void, WorkspaceResource, WorkspaceResourceProvider> {
}

export class TenantResourceProviderManager extends ResourceProviderManager<void, TenantResource, TenantResourceProvider> {
}
2 changes: 2 additions & 0 deletions src/commands/accounts/logIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ export async function logIn(_context: IActionContext): Promise<void> {
const provider = await ext.subscriptionProviderFactory();
_isLoggingIn = true;
ext.actions.refreshAzureTree(); // Refresh to cause the "logging in" spinner to show
ext.actions.refreshTenantTree(); // Refresh to cause the "logging in" spinner to show
await provider.signIn();
} finally {
_isLoggingIn = false;
ext.actions.refreshAzureTree(); // Refresh now that sign in is complete
ext.actions.refreshTenantTree(); // Refresh now that sign in is complete
}
}

Expand Down
23 changes: 20 additions & 3 deletions src/commands/accounts/selectSubscriptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
* Licensed under the MIT License. See License.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureSubscription } from "@microsoft/vscode-azext-azureauth";
import { IActionContext, IAzureQuickPickItem } from "@microsoft/vscode-azext-utils";
import { AzureSubscription } from "api/src/resources/azure";
import * as vscode from "vscode";
import { ext } from "../../extensionVariables";
import { isTenantFilteredOut } from "../../tree/tenants/registerTenantTree";
import { localize } from "../../utils/localize";
import { settingUtils } from "../../utils/settingUtils";

Expand All @@ -31,14 +32,15 @@ export async function selectSubscriptions(context: IActionContext, options?: Sel
let subscriptionsShownInPicker: string[] = [];

const subscriptionQuickPickItems: () => Promise<IAzureQuickPickItem<AzureSubscription>[]> = async () => {

// If there are no tenants selected by default all subscriptions will be shown.
const allSubscriptions = await provider.getSubscriptions(false);
const subscriptionsFilteredByTenant = options?.tenantId ? allSubscriptions.filter(subscription => subscription.tenantId === options.tenantId) : allSubscriptions;
const duplicates = getDuplicateSubscriptions(allSubscriptions);

subscriptionsShownInPicker = subscriptionsFilteredByTenant.map(sub => `${sub.tenantId}/${sub.subscriptionId}`);
return subscriptionsFilteredByTenant
.map(subscription => ({
label: subscription.name,
label: duplicates.includes(subscription) ? subscription.name + ` (${subscription.account?.label})` : subscription.name,
description: subscription.subscriptionId,
data: subscription
}))
Expand Down Expand Up @@ -95,3 +97,18 @@ export async function getSelectedTenantAndSubscriptionIds(): Promise<string[]> {
async function setSelectedTenantAndSubscriptionIds(tenantAndSubscriptionIds: string[]): Promise<void> {
await settingUtils.updateGlobalSetting('selectedSubscriptions', tenantAndSubscriptionIds);
}

// This function is also used to filter subscription tree items in AzureResourceTreeDataProvider
export function getTenantFilteredSubscriptions(allSubscriptions: AzureSubscription[]): AzureSubscription[] | undefined {
const filteredSubscriptions = allSubscriptions.filter(subscription => !isTenantFilteredOut(subscription.tenantId, subscription.account.id));
return filteredSubscriptions.length > 0 ? filteredSubscriptions : allSubscriptions;
}

export function getDuplicateSubscriptions(subscriptions: AzureSubscription[]): AzureSubscription[] {
const lookup = subscriptions.reduce((accumulator, sub) => {
accumulator[sub.subscriptionId] = ++accumulator[sub.subscriptionId] || 0;
return accumulator;
}, {} as Record<string, number>);

return subscriptions.filter(sub => lookup[sub.subscriptionId]);
}
14 changes: 13 additions & 1 deletion src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@

import { signInToTenant } from '@microsoft/vscode-azext-azureauth';
import { AzExtTreeItem, IActionContext, isAzExtTreeItem, openUrl, registerCommand, registerErrorHandler, registerReportIssueCommand } from '@microsoft/vscode-azext-utils';
import { commands } from 'vscode';
import { AuthenticationSessionAccountInformation, commands } from 'vscode';
import { uploadFileToCloudShell } from '../cloudConsole/uploadFileToCloudShell';
import { ext } from '../extensionVariables';
import { BranchDataItemWrapper } from '../tree/BranchDataItemWrapper';
import { ResourceGroupsItem } from '../tree/ResourceGroupsItem';
import { GroupingItem } from '../tree/azure/grouping/GroupingItem';
import { TenantTreeItem } from '../tree/tenants/TenantTreeItem';
import { logIn } from './accounts/logIn';
import { SelectSubscriptionOptions, selectSubscriptions } from './accounts/selectSubscriptions';
import { clearActivities } from './activities/clearActivities';
Expand Down Expand Up @@ -40,6 +41,7 @@ export function registerCommands(): void {
registerCommand('azureResourceGroups.refreshTree', () => ext.actions.refreshAzureTree());
registerCommand('azureWorkspace.refreshTree', () => ext.actions.refreshWorkspaceTree());
registerCommand('azureFocusView.refreshTree', () => ext.actions.refreshFocusTree());
registerCommand('azureTenantsView.refreshTree', () => ext.actions.refreshTenantTree());

// v1.5 client extensions attach these commands to tree item context menus for refreshing their tree items
registerCommand('azureResourceGroups.refresh', async (context, node?: ResourceGroupsItem) => {
Expand All @@ -63,6 +65,16 @@ export function registerCommands(): void {
ext.actions.refreshFocusTree(node);
});

registerCommand('azureTenantsView.refresh', async (context, node?: ResourceGroupsItem) => {
await handleAzExtTreeItemRefresh(context, node); // for compatibility with v1.5 client extensions
ext.actions.refreshTenantTree(node);
});

registerCommand('azureTenantsView.signInToTenant', async (_context, node: TenantTreeItem, account?: AuthenticationSessionAccountInformation) => {
await (await ext.subscriptionProviderFactory()).signIn(node.tenantId, account);
ext.actions.refreshTenantTree(node);
});

registerCommand('azureResourceGroups.focusGroup', focusGroup);
registerCommand('azureResourceGroups.unfocusGroup', unfocusGroup);

Expand Down
Loading

0 comments on commit 2ba8f3a

Please sign in to comment.