-
Notifications
You must be signed in to change notification settings - Fork 66
/
AzureAccountTreeItemBase.ts
213 lines (186 loc) · 11.5 KB
/
AzureAccountTreeItemBase.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commands, Disposable, Extension, extensions, MessageItem, ProgressLocation, window } from 'vscode';
import * as types from '../../index';
import { AzureAccount, AzureLoginStatus, AzureResourceFilter } from '../azure-account.api';
import { UserCancelledError } from '../errors';
import { ext } from '../extensionVariables';
import { localize } from '../localize';
import { nonNullProp, nonNullValue } from '../utils/nonNull';
import { AzureWizardPromptStep } from '../wizard/AzureWizardPromptStep';
import { AzExtParentTreeItem } from './AzExtParentTreeItem';
import { AzExtTreeItem } from './AzExtTreeItem';
import { GenericTreeItem } from './GenericTreeItem';
import { getIconPath, getThemedIconPath } from './IconPath';
import { SubscriptionTreeItemBase } from './SubscriptionTreeItemBase';
const signInLabel: string = localize('signInLabel', 'Sign in to Azure...');
const createAccountLabel: string = localize('createAccountLabel', 'Create a Free Azure Account...');
const selectSubscriptionsLabel: string = localize('noSubscriptions', 'Select Subscriptions...');
const signInCommandId: string = 'azure-account.login';
const createAccountCommandId: string = 'azure-account.createAccount';
const selectSubscriptionsCommandId: string = 'azure-account.selectSubscriptions';
const azureAccountExtensionId: string = 'ms-vscode.azure-account';
const extensionOpenCommand: string = 'extension.open';
export abstract class AzureAccountTreeItemBase extends AzExtParentTreeItem implements types.AzureAccountTreeItemBase {
public static contextValue: string = 'azureextensionui.azureAccount';
public readonly contextValue: string = AzureAccountTreeItemBase.contextValue;
public readonly label: string = 'Azure';
public readonly childTypeLabel: string = localize('subscription', 'subscription');
public autoSelectInTreeItemPicker: boolean = true;
public disposables: Disposable[] = [];
private _azureAccountTask: Promise<AzureAccount | undefined>;
private _subscriptionTreeItems: SubscriptionTreeItemBase[] | undefined;
private _testAccount: AzureAccount | undefined;
constructor(parent?: AzExtParentTreeItem, testAccount?: AzureAccount) {
super(parent);
this._testAccount = testAccount;
this._azureAccountTask = this.loadAzureAccount(testAccount);
}
//#region Methods implemented by base class
public abstract createSubscriptionTreeItem(root: types.ISubscriptionContext): SubscriptionTreeItemBase | Promise<SubscriptionTreeItemBase>;
//#endregion
public get iconPath(): types.TreeItemIconPath {
return getIconPath('azure');
}
public dispose(): void {
Disposable.from(...this.disposables).dispose();
}
public hasMoreChildrenImpl(): boolean {
return false;
}
public async loadMoreChildrenImpl(_clearCache: boolean, context: types.IActionContext): Promise<AzExtTreeItem[]> {
let azureAccount: AzureAccount | undefined = await this._azureAccountTask;
if (!azureAccount) {
// Refresh the AzureAccount, to handle Azure account extension installation after the previous refresh
this._azureAccountTask = this.loadAzureAccount(this._testAccount);
azureAccount = await this._azureAccountTask;
}
if (!azureAccount) {
context.telemetry.properties.accountStatus = 'notInstalled';
const label: string = localize('installAzureAccount', 'Install Azure Account Extension...');
const result: AzExtTreeItem = new GenericTreeItem(this, { label, commandId: extensionOpenCommand, contextValue: 'installAzureAccount', includeInTreeItemPicker: true });
result.commandArgs = [azureAccountExtensionId];
return [result];
}
context.telemetry.properties.accountStatus = azureAccount.status;
const existingSubscriptions: SubscriptionTreeItemBase[] = this._subscriptionTreeItems ? this._subscriptionTreeItems : [];
this._subscriptionTreeItems = [];
const contextValue: string = 'azureCommand';
if (azureAccount.status === 'Initializing' || azureAccount.status === 'LoggingIn') {
return [new GenericTreeItem(this, {
label: azureAccount.status === 'Initializing' ? localize('loadingTreeItem', 'Loading...') : localize('signingIn', 'Waiting for Azure sign-in...'),
commandId: signInCommandId,
contextValue,
id: signInCommandId,
iconPath: getThemedIconPath('Loading')
})];
} else if (azureAccount.status === 'LoggedOut') {
return [
new GenericTreeItem(this, { label: signInLabel, commandId: signInCommandId, contextValue, id: signInCommandId, iconPath: getThemedIconPath('signIn'), includeInTreeItemPicker: true }),
new GenericTreeItem(this, { label: createAccountLabel, commandId: createAccountCommandId, contextValue, id: createAccountCommandId, iconPath: getThemedIconPath('add'), includeInTreeItemPicker: true })
];
}
await azureAccount.waitForFilters();
if (azureAccount.filters.length === 0) {
return [
new GenericTreeItem(this, { label: selectSubscriptionsLabel, commandId: selectSubscriptionsCommandId, contextValue, id: selectSubscriptionsCommandId, includeInTreeItemPicker: true })
];
} else {
this._subscriptionTreeItems = await Promise.all(azureAccount.filters.map(async (filter: AzureResourceFilter) => {
const existingTreeItem: SubscriptionTreeItemBase | undefined = existingSubscriptions.find(ti => ti.id === filter.subscription.id);
if (existingTreeItem) {
// Return existing treeItem (which might have many 'cached' tree items underneath it) rather than creating a brand new tree item every time
return existingTreeItem;
} else {
// filter.subscription.id is the The fully qualified ID of the subscription (For example, /subscriptions/00000000-0000-0000-0000-000000000000) and should be used as the tree item's id for the purposes of OpenInPortal
// filter.subscription.subscriptionId is just the guid and is used in all other cases when creating clients for managing Azure resources
return await this.createSubscriptionTreeItem({
credentials: filter.session.credentials,
subscriptionDisplayName: nonNullProp(filter.subscription, 'displayName'),
subscriptionId: nonNullProp(filter.subscription, 'subscriptionId'),
subscriptionPath: nonNullProp(filter.subscription, 'id'),
tenantId: filter.session.tenantId,
userId: filter.session.userId,
environment: filter.session.environment
});
}
}));
return this._subscriptionTreeItems;
}
}
public async getSubscriptionPromptStep(context: Partial<types.ISubscriptionWizardContext> & types.IActionContext): Promise<types.AzureWizardPromptStep<types.ISubscriptionWizardContext> | undefined> {
const subscriptions: SubscriptionTreeItemBase[] = await this.ensureSubscriptionTreeItems(context);
if (subscriptions.length === 1) {
Object.assign(context, subscriptions[0].root);
return undefined;
} else {
// tslint:disable-next-line: no-var-self
const me: AzureAccountTreeItemBase = this;
class SubscriptionPromptStep extends AzureWizardPromptStep<types.ISubscriptionWizardContext> {
public async prompt(): Promise<void> {
const ti: SubscriptionTreeItemBase = <SubscriptionTreeItemBase>await me.treeDataProvider.showTreeItemPicker(SubscriptionTreeItemBase.contextValue, context);
Object.assign(context, ti.root);
}
public shouldPrompt(): boolean { return !(<types.ISubscriptionWizardContext>context).subscriptionId; }
}
return new SubscriptionPromptStep();
}
}
public async pickTreeItemImpl(_expectedContextValues: (string | RegExp)[]): Promise<AzExtTreeItem | undefined> {
const azureAccount: AzureAccount | undefined = await this._azureAccountTask;
if (azureAccount && (azureAccount.status === 'LoggingIn' || azureAccount.status === 'Initializing')) {
const title: string = localize('waitingForAzureSignin', 'Waiting for Azure sign-in...');
// tslint:disable-next-line: no-non-null-assertion
await window.withProgress({ location: ProgressLocation.Notification, title }, async (): Promise<boolean> => await azureAccount!.waitForSubscriptions());
}
return undefined;
}
public compareChildrenImpl(item1: AzExtTreeItem, item2: AzExtTreeItem): number {
if (item1 instanceof GenericTreeItem && item2 instanceof GenericTreeItem) {
return 0; // already sorted
} else {
return super.compareChildrenImpl(item1, item2);
}
}
private async loadAzureAccount(azureAccount: AzureAccount | undefined): Promise<AzureAccount | undefined> {
if (!azureAccount) {
const extension: Extension<AzureAccount> | undefined = extensions.getExtension<AzureAccount>(azureAccountExtensionId);
if (extension) {
if (!extension.isActive) {
await extension.activate();
}
azureAccount = extension.exports;
}
}
if (azureAccount) {
this.disposables.push(azureAccount.onFiltersChanged(async () => await this.refresh()));
this.disposables.push(azureAccount.onStatusChanged(async (status: AzureLoginStatus) => {
// Ignore status change to 'LoggedIn' and wait for the 'onFiltersChanged' event to fire instead
// (so that the tree stays in 'Loading...' state until the filters are actually ready)
if (status !== 'LoggedIn') {
await this.refresh();
}
}));
await commands.executeCommand('setContext', 'isAzureAccountInstalled', true);
}
return azureAccount;
}
private async ensureSubscriptionTreeItems(context: types.IActionContext): Promise<SubscriptionTreeItemBase[]> {
const azureAccount: AzureAccount | undefined = await this._azureAccountTask;
if (!azureAccount) {
context.telemetry.properties.cancelStep = 'requiresAzureAccount';
const message: string = localize('requiresAzureAccount', "This functionality requires installing the Azure Account extension.");
const viewInMarketplace: MessageItem = { title: localize('viewInMarketplace', "View in Marketplace") };
if (await ext.ui.showWarningMessage(message, viewInMarketplace) === viewInMarketplace) {
await commands.executeCommand(extensionOpenCommand, azureAccountExtensionId);
}
throw new UserCancelledError();
}
if (!this._subscriptionTreeItems) {
await this.getCachedChildren(context);
}
return nonNullValue(this._subscriptionTreeItems, 'subscriptionTreeItems');
}
}