Port the Azure Resource Explorer extension to core. (#2701)

* Port the Azure Resource Explorer extension to core.

This will enable Azure viewlet by default in the next release.

- Moving this code from the SQL Server 2019 extension to Azure Data Studio core
- Ported tests and verified they work in the integration tests.sh file
- Fixed an issue that caused integration tests to fail if you have a SQL Server 2019 big data cluster endpoint listed, but the extension isn't installed.
This commit is contained in:
Kevin Cunnane
2018-10-03 10:41:07 -07:00
committed by GitHub
parent 3f84e8e652
commit a77bb50b9e
70 changed files with 3955 additions and 5 deletions

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceItemType } from '../constants';
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
public getChildren(): TreeNode[] | Promise<TreeNode[]> {
return [];
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(AzureResourceAccountNotSignedInTreeNode.SignInLabel, TreeItemCollapsibleState.None);
item.contextValue = AzureResourceItemType.message;
item.command = {
title: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
command: 'azureresource.signin',
arguments: [this]
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.message,
nodeSubType: undefined,
iconType: AzureResourceItemType.message
};
}
public get nodePathValue(): string {
return 'message_accountNotSignedIn';
}
private static readonly SignInLabel = localize('azureResource.tree.accountNotSignedInTreeNode.signIn', 'Sign in to Azure ...');
}

View File

@@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Account, NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceErrorMessageUtil } from '../utils';
import { AzureResourceSubscription } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
public constructor(
account: Account,
treeChangeHandler: IAzureResourceTreeChangeHandler
) {
super(account, treeChangeHandler, undefined);
this._id = `account_${this.account.key.accountId}`;
this._label = this.generateLabel();
}
public async getChildren(): Promise<TreeNode[]> {
try {
let subscriptions: AzureResourceSubscription[] = [];
if (this._isClearingCache) {
const credentials = await this.getCredentials();
subscriptions = (await this.servicePool.subscriptionService.getSubscriptions(this.account, credentials)) || <AzureResourceSubscription[]>[];
let cache = this.getCache<AzureResourceSubscriptionsCache>();
if (!cache) {
cache = { subscriptions: { } };
}
cache.subscriptions[this.account.key.accountId] = subscriptions;
this.updateCache<AzureResourceSubscriptionsCache>(cache);
this._isClearingCache = false;
} else {
subscriptions = await this.getCachedSubscriptions();
}
this._totalSubscriptionCount = subscriptions.length;
let selectedSubscriptions = await this.servicePool.subscriptionFilterService.getSelectedSubscriptions(this.account);
let selectedSubscriptionIds = (selectedSubscriptions || <AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
if (selectedSubscriptionIds.length > 0) {
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
} else {
// ALL subscriptions are listed by default
this._selectedSubscriptionCount = this._totalSubscriptionCount;
}
this.refreshLabel();
if (subscriptions.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.NoSubscriptions, this)];
} else {
return subscriptions.map((subscription) => new AzureResourceSubscriptionTreeNode(subscription, this.account, this.treeChangeHandler, this));
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
}
}
public async getCachedSubscriptions(): Promise<AzureResourceSubscription[]> {
const subscriptions: AzureResourceSubscription[] = [];
const cache = this.getCache<AzureResourceSubscriptionsCache>();
if (cache) {
subscriptions.push(...cache.subscriptions[this.account.key.accountId]);
}
return subscriptions;
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this._label, TreeItemCollapsibleState.Collapsed);
item.id = this._id;
item.contextValue = AzureResourceItemType.account;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/account_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/account.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: this._label,
isLeaf: false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.account,
nodeSubType: undefined,
iconType: AzureResourceItemType.account
};
}
public get nodePathValue(): string {
return this._id;
}
public get totalSubscriptionCount(): number {
return this._totalSubscriptionCount;
}
public get selectedSubscriptionCount(): number {
return this._selectedSubscriptionCount;
}
protected refreshLabel(): void {
const newLabel = this.generateLabel();
if (this._label !== newLabel) {
this._label = newLabel;
this.treeChangeHandler.notifyNodeChanged(this);
}
}
protected get cacheKey(): string {
return 'azureResource.cache.subscriptions';
}
private generateLabel(): string {
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
if (this._totalSubscriptionCount !== 0) {
label += ` (${this._selectedSubscriptionCount} / ${this._totalSubscriptionCount} subscriptions)`;
}
return label;
}
private _id: string = undefined;
private _label: string = undefined;
private _totalSubscriptionCount = 0;
private _selectedSubscriptionCount = 0;
private static readonly NoSubscriptions = localize('azureResource.tree.accountTreeNode.noSubscriptions', 'No Subscriptions found.');
}
interface AzureResourceSubscriptionsCache {
subscriptions: { [accountId: string]: AzureResourceSubscription[] };
}

View File

@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Account } from 'sqlops';
import { ServiceClientCredentials } from 'ms-rest';
import { TreeNode } from '../../treeNodes';
import { AzureResourceServicePool } from '../servicePool';
import { AzureResourceCredentialError } from '../errors';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
export abstract class AzureResourceTreeNodeBase extends TreeNode {
public constructor(
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super();
this.parent = parent;
}
public readonly servicePool = AzureResourceServicePool.getInstance();
}
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
public constructor(
public readonly account: Account,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
}
public clearCache(): void {
this._isClearingCache = true;
}
public get isClearingCache(): boolean {
return this._isClearingCache;
}
protected async getCredentials(): Promise<ServiceClientCredentials[]> {
try {
return await this.servicePool.credentialService.getCredentials(this.account);
} catch (error) {
if (error instanceof AzureResourceCredentialError) {
this.servicePool.contextService.showErrorMessage(error.message);
this.servicePool.contextService.executeCommand('azureresource.signin');
} else {
throw error;
}
}
}
protected updateCache<T>(cache: T): void {
this.servicePool.cacheService.update<T>(this.cacheKey, cache);
}
protected getCache<T>(): T {
return this.servicePool.cacheService.get<T>(this.cacheKey);
}
protected abstract get cacheKey(): string;
protected _isClearingCache = true;
}

View File

@@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Account, NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceErrorMessageUtil } from '../utils';
import { AzureResourceDatabaseTreeNode } from './databaseTreeNode';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceSubscription, AzureResourceDatabase } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
export class AzureResourceDatabaseContainerTreeNode extends AzureResourceContainerTreeNodeBase {
public constructor(
public readonly subscription: AzureResourceSubscription,
account: Account,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(account, treeChangeHandler, parent);
}
public async getChildren(): Promise<TreeNode[]> {
try {
let databases: AzureResourceDatabase[] = [];
if (this._isClearingCache) {
let credentials = await this.getCredentials();
databases = (await this.servicePool.databaseService.getDatabases(this.subscription, credentials)) || <AzureResourceDatabase[]>[];
let cache = this.getCache<AzureResourceDatabasesCache>();
if (!cache) {
cache = { databases: { } };
}
cache.databases[this.subscription.id] = databases;
this.updateCache(cache);
this._isClearingCache = false;
} else {
const cache = this.getCache<AzureResourceDatabasesCache>();
if (cache) {
databases = cache.databases[this.subscription.id] || <AzureResourceDatabase[]>[];
}
}
if (databases.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceDatabaseContainerTreeNode.NoDatabases, this)];
} else {
return databases.map((database) => new AzureResourceDatabaseTreeNode(database, this.treeChangeHandler, this));
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
}
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(AzureResourceDatabaseContainerTreeNode.Label, TreeItemCollapsibleState.Collapsed);
item.contextValue = AzureResourceItemType.databaseContainer;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/folder_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/folder.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: AzureResourceDatabaseContainerTreeNode.Label,
isLeaf: false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.databaseContainer,
nodeSubType: undefined,
iconType: AzureResourceItemType.databaseContainer
};
}
public get nodePathValue(): string {
return 'databaseContainer';
}
protected get cacheKey(): string {
return 'azureResource.cache.databases';
}
private static readonly Label = localize('azureResource.tree.databaseContainerTreeNode.label', 'SQL Databases');
private static readonly NoDatabases = localize('azureResource.tree.databaseContainerTreeNode.noDatabases', 'No SQL Databases found.');
}
interface AzureResourceDatabasesCache {
databases: { [subscriptionId: string]: AzureResourceDatabase[] };
}

View File

@@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Account, NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceErrorMessageUtil } from '../utils';
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../models';
import { AzureResourceDatabaseServerTreeNode } from './databaseServerTreeNode';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
export class AzureResourceDatabaseServerContainerTreeNode extends AzureResourceContainerTreeNodeBase {
public constructor(
public readonly subscription: AzureResourceSubscription,
account: Account,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(account, treeChangeHandler, parent);
}
public async getChildren(): Promise<TreeNode[]> {
try {
let databaseServers: AzureResourceDatabaseServer[] = [];
if (this._isClearingCache) {
let credentials = await this.getCredentials();
databaseServers = (await this.servicePool.databaseServerService.getDatabaseServers(this.subscription, credentials)) || <AzureResourceDatabaseServer[]>[];
let cache = this.getCache<AzureResourceDatabaseServersCache>();
if (!cache) {
cache = { databaseServers: { } };
}
cache.databaseServers[this.subscription.id] = databaseServers;
this.updateCache<AzureResourceDatabaseServersCache>(cache);
this._isClearingCache = false;
} else {
const cache = this.getCache<AzureResourceDatabaseServersCache>();
if (cache) {
databaseServers = cache.databaseServers[this.subscription.id] || <AzureResourceDatabaseServer[]>[];
}
}
if (databaseServers.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceDatabaseServerContainerTreeNode.NoDatabaseServers, this)];
} else {
return databaseServers.map((server) => new AzureResourceDatabaseServerTreeNode(server, this.treeChangeHandler, this));
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
}
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(AzureResourceDatabaseServerContainerTreeNode.Label, TreeItemCollapsibleState.Collapsed);
item.contextValue = AzureResourceItemType.databaseServerContainer;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/folder_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/folder.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: AzureResourceDatabaseServerContainerTreeNode.Label,
isLeaf: false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.databaseServerContainer,
nodeSubType: undefined,
iconType: AzureResourceItemType.databaseServerContainer
};
}
public get nodePathValue(): string {
return 'databaseServerContainer';
}
protected get cacheKey(): string {
return 'azureResource.cache.databaseServers';
}
private static readonly Label = localize('azureResource.tree.databaseServerContainerTreeNode.label', 'SQL Servers');
private static readonly NoDatabaseServers = localize('azureResource.tree.databaseContainerTreeNode.noDatabaseServers', 'No SQL Servers found.');
}
interface AzureResourceDatabaseServersCache {
databaseServers: { [subscriptionId: string]: AzureResourceDatabaseServer[] };
}

View File

@@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { AzureResourceTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceDatabaseServer } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
export class AzureResourceDatabaseServerTreeNode extends AzureResourceTreeNodeBase {
public constructor(
public readonly databaseServer: AzureResourceDatabaseServer,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
}
public async getChildren(): Promise<TreeNode[]> {
return [];
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this.databaseServer.name, TreeItemCollapsibleState.None);
item.contextValue = AzureResourceItemType.databaseServer;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/sql_server_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/sql_server.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: this.databaseServer.name,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.databaseServer,
nodeSubType: undefined,
iconType: AzureResourceItemType.databaseServer
};
}
public get nodePathValue(): string {
return `databaseServer_${this.databaseServer.name}`;
}
}

View File

@@ -0,0 +1,61 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { AzureResourceTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceDatabase } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeProvider';
export class AzureResourceDatabaseTreeNode extends AzureResourceTreeNodeBase {
public constructor(
public readonly database: AzureResourceDatabase,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
this._label = `${this.database.name} (${this.database.serverName})`;
}
public async getChildren(): Promise<TreeNode[]> {
return [];
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this._label, TreeItemCollapsibleState.None);
item.contextValue = AzureResourceItemType.database;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/sql_database_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/sql_database.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: this._label,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.database,
nodeSubType: undefined,
iconType: AzureResourceItemType.database
};
}
public get nodePathValue(): string {
return `database_${this.database.name}`;
}
private _label: string = undefined;
}

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { AzureResourceItemType } from '../constants';
export class AzureResourceMessageTreeNode extends TreeNode {
public constructor(
public readonly message: string,
parent: TreeNode
) {
super();
this.parent = parent;
this._id = `message_${AzureResourceMessageTreeNode._messageNum++}`;
}
public static create(message: string, parent: TreeNode): AzureResourceMessageTreeNode {
return new AzureResourceMessageTreeNode(message, parent);
}
public getChildren(): TreeNode[] | Promise<TreeNode[]> {
return [];
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this.message, TreeItemCollapsibleState.None);
item.contextValue = AzureResourceItemType.message;
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: this.message,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.message,
nodeSubType: undefined,
iconType: AzureResourceItemType.message
};
}
public get nodePathValue(): string {
return this._id;
}
private _id: string;
private static _messageNum: number = 0;
}

View File

@@ -0,0 +1,65 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Account, NodeInfo } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { AzureResourceTreeNodeBase, AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
import { AzureResourceItemType } from '../constants';
import { AzureResourceDatabaseContainerTreeNode } from './databaseContainerTreeNode';
import { AzureResourceDatabaseServerContainerTreeNode } from './databaseServerContainerTreeNode';
import { AzureResourceSubscription } from '../models';
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase {
public constructor(
public readonly subscription: AzureResourceSubscription,
account: Account,
treeChangeHandler: IAzureResourceTreeChangeHandler,
parent: TreeNode
) {
super(treeChangeHandler, parent);
this._children.push(new AzureResourceDatabaseContainerTreeNode(subscription, account, treeChangeHandler, this));
this._children.push(new AzureResourceDatabaseServerContainerTreeNode(subscription, account, treeChangeHandler, this));
}
public async getChildren(): Promise<TreeNode[]> {
return this._children;
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
item.contextValue = AzureResourceItemType.subscription;
item.iconPath = {
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/subscription_inverse.svg'),
light: this.servicePool.contextService.getAbsolutePath('resources/light/subscription.svg')
};
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: this.subscription.name,
isLeaf: false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: AzureResourceItemType.subscription,
nodeSubType: undefined,
iconType: AzureResourceItemType.subscription
};
}
public get nodePathValue(): string {
return `subscription_${this.subscription.id}`;
}
private _children: AzureResourceContainerTreeNodeBase[] = [];
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeNode } from '../../treeNodes';
export interface IAzureResourceTreeChangeHandler {
notifyNodeChanged(node: TreeNode): void;
}

View File

@@ -0,0 +1,101 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
import { DidChangeAccountsParams } from 'sqlops';
import { TreeNode } from '../../treeNodes';
import { setInterval, clearInterval } from 'timers';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceServicePool } from '../servicePool';
import { AzureResourceAccountTreeNode } from './accountTreeNode';
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
import { AzureResourceMessageTreeNode } from './messageTreeNode';
import { AzureResourceContainerTreeNodeBase, AzureResourceTreeNodeBase } from './baseTreeNodes';
import { AzureResourceErrorMessageUtil } from '../utils';
export interface IAzureResourceTreeChangeHandler {
notifyNodeChanged(node: TreeNode): void;
}
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
public constructor() {
AzureResourceServicePool.getInstance().accountService.onDidChangeAccounts((e: DidChangeAccountsParams) => { this._onDidChangeTreeData.fire(undefined); });
}
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
if (element) {
return element.getChildren(true);
}
if (!this.isSystemInitialized) {
this._loadingTimer = setInterval(async () => {
try {
// Call sqlops.accounts.getAllAccounts() to determine whether the system has been initialized.
await AzureResourceServicePool.getInstance().accountService.getAccounts();
// System has been initialized
this.isSystemInitialized = true;
if (this._loadingTimer) {
clearInterval(this._loadingTimer);
}
this._onDidChangeTreeData.fire(undefined);
} catch (error) {
// System not initialized yet
this.isSystemInitialized = false;
}
}, AzureResourceTreeProvider.LoadingTimerInterval);
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.Loading, undefined)];
}
try {
const accounts = await AzureResourceServicePool.getInstance().accountService.getAccounts();
if (accounts && accounts.length > 0) {
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this));
} else {
return [new AzureResourceAccountNotSignedInTreeNode()];
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), undefined)];
}
}
public get onDidChangeTreeData(): Event<TreeNode> {
return this._onDidChangeTreeData.event;
}
public notifyNodeChanged(node: TreeNode): void {
this._onDidChangeTreeData.fire(node);
}
public async refresh(node: TreeNode, isClearingCache: boolean): Promise<void> {
if (isClearingCache) {
if ((node instanceof AzureResourceContainerTreeNodeBase)) {
node.clearCache();
}
}
this._onDidChangeTreeData.fire(node);
}
public getTreeItem(element: TreeNode): TreeItem | Thenable<TreeItem> {
return element.getTreeItem();
}
public isSystemInitialized: boolean = false;
private _loadingTimer: NodeJS.Timer = undefined;
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
private static readonly Loading = localize('azureResource.tree.treeProvider.loading', 'Loading ...');
private static readonly LoadingTimerInterval = 5000;
}