diff --git a/azure-pipelines-windows.yml b/azure-pipelines-windows.yml index e5a444e7aa..74bed5ab3a 100644 --- a/azure-pipelines-windows.yml +++ b/azure-pipelines-windows.yml @@ -32,10 +32,6 @@ steps: .\scripts\test.bat --reporter mocha-junit-reporter --coverage displayName: 'Test' -- script: | - cat .\.build\coverage\lcov.info | node .\node_modules\coveralls\bin\coveralls.js - displayName: 'Upload coverage to Coveralls' - - task: PublishTestResults@2 inputs: testResultsFiles: 'test-results.xml' diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json index e631820981..946005aebc 100644 --- a/extensions/azurecore/package.json +++ b/extensions/azurecore/package.json @@ -118,6 +118,33 @@ ] }, "menus": { + "commandPalette": [ + { + "command": "azure.resource.signin", + "when": "true" + }, + { + "command": "azure.resource.refreshall", + "when": "true" + }, + { + "command": "azure.resource.selectsubscriptions", + "when": "false" + }, + { + "command": "azure.resource.refresh", + "when": "false" + }, + { + "command": "azure.resource.connectsqlserver", + "when": "false" + }, + { + "command": "azure.resource.connectsqldb", + "when": "false" + } + + ], "view/title": [ { "command": "azure.resource.signin", diff --git a/extensions/azurecore/package.nls.json b/extensions/azurecore/package.nls.json index 21f0e1c6f6..418d2cfc86 100644 --- a/extensions/azurecore/package.nls.json +++ b/extensions/azurecore/package.nls.json @@ -6,9 +6,9 @@ "azure.resource.config.title": "Azure Resource Configuration", "azure.resource.config.filter.description": "The resource filter, each element is an account id, a subscription id and name separated by a slash", "azure.resource.explorer.title": "Azure", - "azure.resource.refreshall.title": "Refresh All", + "azure.resource.refreshall.title": "Azure: Refresh All Accounts", "azure.resource.refresh.title": "Refresh", - "azure.resource.signin.title": "Sign In", + "azure.resource.signin.title": "Azure: Sign In", "azure.resource.selectsubscriptions.title": "Select Subscriptions", "azure.resource.connectsqlserver.title": "Connect", "azure.resource.connectsqldb.title": "Add to Servers", diff --git a/extensions/azurecore/src/azureResource/tree/treeProvider.ts b/extensions/azurecore/src/azureResource/tree/treeProvider.ts index 17de2cf079..db40281163 100644 --- a/extensions/azurecore/src/azureResource/tree/treeProvider.ts +++ b/extensions/azurecore/src/azureResource/tree/treeProvider.ts @@ -6,7 +6,7 @@ 'use strict'; import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode'; -import { setInterval, clearInterval } from 'timers'; +import * as azdata from 'azdata'; import { AppContext } from '../../appContext'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); @@ -16,13 +16,40 @@ import { AzureResourceAccountTreeNode } from './accountTreeNode'; import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode'; import { AzureResourceMessageTreeNode } from '../messageTreeNode'; import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes'; -import { AzureResourceErrorMessageUtil } from '../utils'; +import { AzureResourceErrorMessageUtil, equals } from '../utils'; import { IAzureResourceTreeChangeHandler } from './treeChangeHandler'; import { IAzureResourceAccountService } from '../../azureResource/interfaces'; import { AzureResourceServiceNames } from '../constants'; + export class AzureResourceTreeProvider implements TreeDataProvider, IAzureResourceTreeChangeHandler { + public isSystemInitialized: boolean = false; + + private accountService: IAzureResourceAccountService; + private accounts: azdata.Account[]; + private _onDidChangeTreeData = new EventEmitter(); + private loadingAccountsPromise: Promise; + public constructor(public readonly appContext: AppContext) { + if (appContext) { + this.hookAccountService(appContext); + } + } + + private hookAccountService(appContext: AppContext): void { + this.accountService = appContext.getService(AzureResourceServiceNames.accountService); + if (this.accountService) { + this.accountService.onDidChangeAccounts((e: azdata.DidChangeAccountsParams) => { + // the onDidChangeAccounts event will trigger in many cases where the accounts didn't actually change + // the notifyNodeChanged event triggers a refresh which triggers a getChildren which can trigger this callback + // this below check short-circuits the infinite callback loop + this.setSystemInitialized(); + if (!equals(e.accounts, this.accounts)) { + this.accounts = e.accounts; + this.notifyNodeChanged(undefined); + } + }); + } } public async getChildren(element?: TreeNode): Promise { @@ -30,34 +57,16 @@ export class AzureResourceTreeProvider implements TreeDataProvider, IA return element.getChildren(true); } - if (!this.isSystemInitialized && !this._loadingTimer) { - this._loadingTimer = setInterval(async () => { - try { - // Call azdata.accounts.getAllAccounts() to determine whether the system has been initialized. - await this.appContext.getService(AzureResourceServiceNames.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.loadingLabel, undefined)]; + if (!this.isSystemInitialized) { + if (!this.loadingAccountsPromise) { + this.loadingAccountsPromise = this.loadAccounts(); + } + return [AzureResourceMessageTreeNode.create(localize('azure.resource.tree.treeProvider.loadingLabel', 'Loading ...'), undefined)]; } try { - const accounts = await this.appContext.getService(AzureResourceServiceNames.accountService).getAccounts(); - - if (accounts && accounts.length > 0) { - return accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this)); + if (this.accounts && this.accounts.length > 0) { + return this.accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this)); } else { return [new AzureResourceAccountNotSignedInTreeNode()]; } @@ -66,6 +75,23 @@ export class AzureResourceTreeProvider implements TreeDataProvider, IA } } + private async loadAccounts(): Promise { + try { + this.accounts = await this.appContext.getService(AzureResourceServiceNames.accountService).getAccounts(); + // System has been initialized + this.setSystemInitialized(); + this._onDidChangeTreeData.fire(undefined); + } catch (err) { + // Skip for now, we can assume that the accounts changed event will eventually notify instead + this.isSystemInitialized = false; + } + } + + private setSystemInitialized(): void { + this.isSystemInitialized = true; + this.loadingAccountsPromise = undefined; + } + public get onDidChangeTreeData(): Event { return this._onDidChangeTreeData.event; } @@ -87,12 +113,4 @@ export class AzureResourceTreeProvider implements TreeDataProvider, IA public getTreeItem(element: TreeNode): TreeItem | Thenable { return element.getTreeItem(); } - - public isSystemInitialized: boolean = false; - - private _loadingTimer: NodeJS.Timer = undefined; - private _onDidChangeTreeData = new EventEmitter(); - - private static readonly loadingLabel = localize('azure.resource.tree.treeProvider.loadingLabel', 'Loading ...'); - private static readonly loadingTimerInterval = 5000; } diff --git a/extensions/azurecore/src/extension.ts b/extensions/azurecore/src/extension.ts index aa232b20f6..75cd797c8b 100644 --- a/extensions/azurecore/src/extension.ts +++ b/extensions/azurecore/src/extension.ts @@ -8,7 +8,6 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import * as constants from './constants'; -import * as azdata from 'azdata'; import { AppContext } from './appContext'; import { ApiWrapper } from './apiWrapper'; @@ -30,7 +29,6 @@ import { registerAzureResourceCommands } from './azureResource/commands'; import { registerAzureResourceDatabaseServerCommands } from './azureResource/providers/databaseServer/commands'; import { registerAzureResourceDatabaseCommands } from './azureResource/providers/database/commands'; import { AzureResourceTreeProvider } from './azureResource/tree/treeProvider'; -import { equals } from './azureResource/utils'; let extensionContext: vscode.ExtensionContext; @@ -72,7 +70,6 @@ export function activate(context: vscode.ExtensionContext) { registerAzureServices(appContext); const azureResourceTree = new AzureResourceTreeProvider(appContext); pushDisposable(apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree)); - registerAccountService(appContext, azureResourceTree); registerCommands(appContext, azureResourceTree); return { @@ -113,26 +110,12 @@ async function initAzureAccountProvider(extensionContext: vscode.ExtensionContex function registerAzureServices(appContext: AppContext): void { appContext.registerService(AzureResourceServiceNames.resourceService, new AzureResourceService()); + appContext.registerService(AzureResourceServiceNames.accountService, new AzureResourceAccountService(appContext.apiWrapper)); appContext.registerService(AzureResourceServiceNames.cacheService, new AzureResourceCacheService(extensionContext)); appContext.registerService(AzureResourceServiceNames.subscriptionService, new AzureResourceSubscriptionService()); appContext.registerService(AzureResourceServiceNames.subscriptionFilterService, new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(extensionContext))); appContext.registerService(AzureResourceServiceNames.tenantService, new AzureResourceTenantService()); } -function registerAccountService(appContext: AppContext, azureResourceTree: AzureResourceTreeProvider): void { - let accountService = new AzureResourceAccountService(appContext.apiWrapper); - appContext.registerService(AzureResourceServiceNames.accountService, accountService); - let previousAccounts: Array = undefined; - accountService.onDidChangeAccounts((e: azdata.DidChangeAccountsParams) => { - // the onDidChangeAccounts event will trigger in many cases where the accounts didn't actually change - // the notifyNodeChanged event triggers a refresh which triggers a getChildren which can trigger this callback - // this below check short-circuits the infinite callback loop - if (!equals(e.accounts, previousAccounts)) { - azureResourceTree.notifyNodeChanged(undefined); - } - previousAccounts = e.accounts; - }); - -} function registerCommands(appContext: AppContext, azureResourceTree: AzureResourceTreeProvider): void { registerAzureResourceCommands(appContext, azureResourceTree); diff --git a/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts b/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts index e84bdceda6..81b7bf2a0a 100644 --- a/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts +++ b/src/sql/workbench/services/accountManagement/browser/accountManagementService.ts @@ -364,6 +364,8 @@ export class AccountManagementService implements IAccountManagementService { addedProvider: provider.metadata, initialAccounts: provider.accounts.slice(0) // Slice here to make sure no one can modify our cache }); + // Notify listeners that the account has been updated + self.fireAccountListUpdate(provider, false); }); // TODO: Add stale event handling to the providers