mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1263a27c1c | ||
|
|
e1c084d365 | ||
|
|
7465ec0bbd | ||
|
|
17ed57836f | ||
|
|
d0acb51fd7 | ||
|
|
71c1ed6c49 | ||
|
|
bfb68254a4 | ||
|
|
18f7662209 | ||
|
|
a0d84f383c | ||
|
|
1f447ae681 | ||
|
|
8bd6691331 | ||
|
|
42afcf9322 | ||
|
|
3d3694bb8d | ||
|
|
589b913960 | ||
|
|
7ba4f42494 | ||
|
|
c96118d2b5 | ||
|
|
0285d8cd38 | ||
|
|
ee87604a4d | ||
|
|
2235ebaf20 | ||
|
|
954d0d954f | ||
|
|
e31747d087 | ||
|
|
fc581253a4 | ||
|
|
47c4609f23 | ||
|
|
2d52bc2a49 | ||
|
|
5367101330 | ||
|
|
db145b4999 | ||
|
|
7f950ddb80 | ||
|
|
50e2251e74 |
24
.vscode/launch.json
vendored
24
.vscode/launch.json
vendored
@@ -92,6 +92,30 @@
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"timeout": 45000
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch azuredatastudio with new notebook command",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/sql.bat"
|
||||
},
|
||||
"osx": {
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/sql.sh"
|
||||
},
|
||||
"linux": {
|
||||
"runtimeExecutable": "${workspaceFolder}/scripts/sql.sh"
|
||||
},
|
||||
"urlFilter": "*index.html*",
|
||||
"runtimeArgs": [
|
||||
"--inspect=5875",
|
||||
"--command=notebook.command.new"
|
||||
],
|
||||
"skipFiles": [
|
||||
"**/winjs*.js"
|
||||
],
|
||||
"webRoot": "${workspaceFolder}",
|
||||
"timeout": 45000
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
|
||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,5 +1,27 @@
|
||||
# Change Log
|
||||
|
||||
## Version 1.3.8
|
||||
* Release date: January 9, 2019
|
||||
* Release status: General Availability
|
||||
|
||||
## What's new in this version
|
||||
* #13 Feature Request: Azure Active Directory Authentication
|
||||
* #1040 Stream initial query results as they become available
|
||||
* #3298 Сan't add an azure account.
|
||||
* #2387 Support Per-User Installer
|
||||
* SQL Server Import updates for DACPAC\BACPAC
|
||||
* SQL Server Profiler UI and UX improvements
|
||||
* Updates to [SQL Server 2019 extension](https://docs.microsoft.com/sql/azure-data-studio/sql-server-2019-extension?view=sql-server-ver15)
|
||||
* **sp_executesql to SQL** and **New Database** extensions
|
||||
|
||||
## Contributions and "thank you"
|
||||
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
||||
|
||||
* Tarig0 for `Add Routine_Type to CreateStoredProc fixes #3257 (#3286)`
|
||||
* oltruong for `typo fix #3025'`
|
||||
* Thomas-S-B for `Removed unnecessary IErrorDetectionStrategy #749`
|
||||
* Thomas-S-B for `Simplified code #750`
|
||||
|
||||
## Version 1.2.4
|
||||
* Release date: November 6, 2018
|
||||
* Release status: General Availability
|
||||
|
||||
17
README.md
17
README.md
@@ -9,12 +9,13 @@ Azure Data Studio is a data management tool that enables you to work with SQL Se
|
||||
|
||||
Platform | Link
|
||||
-- | --
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2038320
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2038323
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2038327
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2038332
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2038401
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2038405
|
||||
Windows User Installer | https://go.microsoft.com/fwlink/?linkid=2049972
|
||||
Windows System Installer | https://go.microsoft.com/fwlink/?linkid=2049975
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2050146
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2049981
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2049986
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2049989
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2050157
|
||||
|
||||
Go to our [download page](https://aka.ms/azuredatastudio) for more specific instructions.
|
||||
|
||||
@@ -62,6 +63,10 @@ The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.micro
|
||||
## Contributions and "Thank You"
|
||||
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
||||
|
||||
* Tarig0 for `Add Routine_Type to CreateStoredProc fixes #3257 (#3286)`
|
||||
* oltruong for `typo fix #3025'`
|
||||
* Thomas-S-B for `Removed unnecessary IErrorDetectionStrategy #749`
|
||||
* Thomas-S-B for `Simplified code #750`
|
||||
* rdaniels6813 for `Add query plan theme support #3031`
|
||||
* Ruturaj123 for `Fixed some typos and grammatical errors #3027`
|
||||
* PromoFaux for `Use emoji shortcodes in CONTRIBUTING.md instead of <20> #3009`
|
||||
|
||||
@@ -34,5 +34,9 @@ steps:
|
||||
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
testResultsFiles: '**/test-results.xml'
|
||||
condition: succeededOrFailed()
|
||||
testResultsFiles: '**/test-results.xml'
|
||||
condition: succeededOrFailed()
|
||||
|
||||
- script: |
|
||||
yarn run tslint
|
||||
displayName: 'Run TSLint'
|
||||
@@ -23,4 +23,8 @@ steps:
|
||||
- task: PublishTestResults@2
|
||||
inputs:
|
||||
testResultsFiles: 'test-results.xml'
|
||||
condition: succeededOrFailed()
|
||||
condition: succeededOrFailed()
|
||||
|
||||
- script: |
|
||||
yarn run tslint
|
||||
displayName: 'Run TSLint'
|
||||
@@ -17,12 +17,12 @@
|
||||
"configuration": [
|
||||
{
|
||||
"type": "object",
|
||||
"title": "%azure.config.title%",
|
||||
"title": "%azure.resource.config.title%",
|
||||
"properties": {
|
||||
"azureResource.resourceFilter": {
|
||||
"azure.resource.config.filter": {
|
||||
"type": "array",
|
||||
"default": null,
|
||||
"description": "%azure.resourceFilter.description%"
|
||||
"description": "%azure.resource.config.filter.description%"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -61,39 +61,47 @@
|
||||
"category": "Azure Accounts"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.refreshall",
|
||||
"title": "%azureresource.refreshall%",
|
||||
"command": "azure.resource.refreshall",
|
||||
"title": "%azure.resource.refreshall.title%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/refresh_inverse.svg",
|
||||
"light": "resources/light/refresh.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "azureresource.refresh",
|
||||
"title": "%azureresource.refresh%",
|
||||
"command": "azure.resource.refresh",
|
||||
"title": "%azure.resource.refresh.title%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/refresh_inverse.svg",
|
||||
"light": "resources/light/refresh.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "azureresource.signin",
|
||||
"title": "%azureresource.signin%"
|
||||
"command": "azure.resource.signin",
|
||||
"title": "%azure.resource.signin.title%"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.connectsqldb",
|
||||
"title": "%azureresource.connectsqldb%",
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"title": "%azure.resource.selectsubscriptions.title%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/filter_inverse.svg",
|
||||
"light": "resources/light/filter.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.connectsqlserver",
|
||||
"title": "%azure.resource.connectsqlserver.title%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/connect_to_inverse.svg",
|
||||
"light": "resources/light/connect_to.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "azureresource.selectsubscriptions",
|
||||
"title": "%azureresource.selectsubscriptions%",
|
||||
"command": "azure.resource.connectsqldb",
|
||||
"title": "%azure.resource.connectsqldb.title%",
|
||||
"icon": {
|
||||
"dark": "resources/dark/filter_inverse.svg",
|
||||
"light": "resources/light/filter.svg"
|
||||
"dark": "resources/dark/connect_to_inverse.svg",
|
||||
"light": "resources/light/connect_to.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -110,46 +118,47 @@
|
||||
"azureResource": [
|
||||
{
|
||||
"id": "azureResourceExplorer",
|
||||
"name": "%azure.resourceExplorer.title%"
|
||||
"name": "%azure.resource.explorer.title%"
|
||||
}
|
||||
]
|
||||
},
|
||||
"menus": {
|
||||
"view/title": [
|
||||
{
|
||||
"command": "azureresource.refreshall",
|
||||
"command": "azure.resource.refreshall",
|
||||
"when": "view == azureResourceExplorer",
|
||||
"group": "navigation@1"
|
||||
}
|
||||
],
|
||||
"view/item/context": [
|
||||
{
|
||||
"command": "azureresource.connectsqldb",
|
||||
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
|
||||
"group": "1azureresource@1"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.connectsqldb",
|
||||
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
|
||||
"command": "azure.resource.selectsubscriptions",
|
||||
"when": "viewItem == azure.resource.itemType.account",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.selectsubscriptions",
|
||||
"when": "viewItem == azureResource.itemType.account",
|
||||
"command": "azure.resource.refresh",
|
||||
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azureresource.refresh",
|
||||
"when": "viewItem =~ /^azureResource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
|
||||
"command": "azure.resource.connectsqlserver",
|
||||
"when": "viewItem == azure.resource.itemType.databaseServer",
|
||||
"group": "inline"
|
||||
},
|
||||
{
|
||||
"command": "azure.resource.connectsqldb",
|
||||
"when": "viewItem == azure.resource.itemType.database",
|
||||
"group": "inline"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"hasAzureResourceProviders": true
|
||||
},
|
||||
"dependencies": {
|
||||
"request": "2.88.0",
|
||||
"azure-arm-resource": "^7.0.0",
|
||||
"azure-arm-sql": "^5.0.1",
|
||||
"request": "2.88.0",
|
||||
"vscode-nls": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -157,6 +166,7 @@
|
||||
"@types/node": "^8.0.24",
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^13.2.1",
|
||||
"vscode": "^1.1.26",
|
||||
"typemoq": "^2.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
{
|
||||
"azure.displayName": "Azure (Core)",
|
||||
"azure.description": "Browse and work with Azure resources",
|
||||
"azure.config.title": "Azure Resource Configuration",
|
||||
"azure.resourceFilter.description": "The resource filter, each element is an account id, a subscription id and name separated by a slash",
|
||||
"azureresource.refreshall": "Refresh All",
|
||||
"azureresource.refresh": "Refresh",
|
||||
"azureresource.signin": "Sign In",
|
||||
"azureresource.connectsqldb": "Connect",
|
||||
"azureresource.selectsubscriptions": "Select Subscriptions",
|
||||
"azure.title": "Azure",
|
||||
"azure.resourceExplorer.title": "Resource Explorer",
|
||||
|
||||
"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": "Resource Explorer",
|
||||
"azure.resource.refreshall.title": "Refresh All",
|
||||
"azure.resource.refresh.title": "Refresh",
|
||||
"azure.resource.signin.title": "Sign In",
|
||||
"azure.resource.selectsubscriptions.title": "Select Subscriptions",
|
||||
"azure.resource.connectsqlserver.title": "Connect",
|
||||
"azure.resource.connectsqldb.title": "Connect",
|
||||
|
||||
"accounts.clearTokenCache": "Clear Azure Account Token Cache",
|
||||
|
||||
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
||||
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
||||
"config.enableChinaCloudDescription": "Should Azure China integration be enabled",
|
||||
|
||||
28
extensions/azurecore/src/azureResource/azure-resource.d.ts
vendored
Normal file
28
extensions/azurecore/src/azureResource/azure-resource.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { TreeDataProvider, TreeItem } from 'vscode';
|
||||
import { DataProvider, Account } from 'sqlops';
|
||||
|
||||
export namespace azureResource {
|
||||
export interface IAzureResourceProvider extends DataProvider {
|
||||
getTreeDataProvider(): IAzureResourceTreeDataProvider;
|
||||
}
|
||||
|
||||
export interface IAzureResourceTreeDataProvider extends TreeDataProvider<IAzureResourceNode> {
|
||||
}
|
||||
|
||||
export interface IAzureResourceNode {
|
||||
readonly account: Account;
|
||||
readonly subscription: AzureResourceSubscription;
|
||||
readonly tenantId: string;
|
||||
readonly treeItem: TreeItem;
|
||||
}
|
||||
|
||||
export interface AzureResourceSubscription {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
}
|
||||
@@ -6,35 +6,48 @@
|
||||
'use strict';
|
||||
|
||||
import { window, QuickPickItem } from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { generateGuid } from './utils';
|
||||
import { ApiWrapper } from '../apiWrapper';
|
||||
import { TreeNode } from '../treeNodes';
|
||||
import { AzureResource } from 'sqlops';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import { AppContext } from '../appContext';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { azureResource } from './azure-resource';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { AzureResourceCredentialError } from './errors';
|
||||
import { AzureResourceTreeProvider } from './tree/treeProvider';
|
||||
import { AzureResourceDatabaseServerTreeNode } from './tree/databaseServerTreeNode';
|
||||
import { AzureResourceDatabaseTreeNode } from './tree/databaseTreeNode';
|
||||
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
||||
import { AzureResourceServicePool } from './servicePool';
|
||||
import { AzureResourceSubscription } from './models';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../azureResource/interfaces';
|
||||
import { AzureResourceServiceNames } from './constants';
|
||||
|
||||
export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: AzureResourceTreeProvider): void {
|
||||
apiWrapper.registerCommand('azureresource.selectsubscriptions', async (node?: TreeNode) => {
|
||||
export function registerAzureResourceCommands(appContext: AppContext, tree: AzureResourceTreeProvider): void {
|
||||
appContext.apiWrapper.registerCommand('azure.resource.selectsubscriptions', async (node?: TreeNode) => {
|
||||
if (!(node instanceof AzureResourceAccountTreeNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const subscriptionService = appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||
const subscriptionFilterService = appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||
|
||||
const accountNode = node as AzureResourceAccountTreeNode;
|
||||
|
||||
const servicePool = AzureResourceServicePool.getInstance();
|
||||
const subscriptions = (await accountNode.getCachedSubscriptions()) || <azureResource.AzureResourceSubscription[]>[];
|
||||
if (subscriptions.length === 0) {
|
||||
try {
|
||||
const tokens = await this.servicePool.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
|
||||
|
||||
let subscriptions = await accountNode.getCachedSubscriptions();
|
||||
if (!subscriptions || subscriptions.length === 0) {
|
||||
const credentials = await servicePool.credentialService.getCredentials(accountNode.account, sqlops.AzureResource.ResourceManagement);
|
||||
subscriptions = await servicePool.subscriptionService.getSubscriptions(accountNode.account, credentials);
|
||||
for (const tenant of this.account.properties.tenants) {
|
||||
const token = tokens[tenant.id].token;
|
||||
const tokenType = tokens[tenant.id].tokenType;
|
||||
|
||||
subscriptions.push(...await subscriptionService.getSubscriptions(accountNode.account, new TokenCredentials(token, tokenType)));
|
||||
}
|
||||
} catch (error) {
|
||||
throw new AzureResourceCredentialError(localize('azure.resource.selectsubscriptions.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', this.account.key.accountId), error);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedSubscriptions = (await servicePool.subscriptionFilterService.getSelectedSubscriptions(accountNode.account)) || <AzureResourceSubscription[]>[];
|
||||
let selectedSubscriptions = (await subscriptionFilterService.getSelectedSubscriptions(accountNode.account)) || <azureResource.AzureResourceSubscription[]>[];
|
||||
const selectedSubscriptionIds: string[] = [];
|
||||
if (selectedSubscriptions.length > 0) {
|
||||
selectedSubscriptionIds.push(...selectedSubscriptions.map((subscription) => subscription.id));
|
||||
@@ -43,11 +56,11 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
|
||||
selectedSubscriptionIds.push(...subscriptions.map((subscription) => subscription.id));
|
||||
}
|
||||
|
||||
interface SubscriptionQuickPickItem extends QuickPickItem {
|
||||
subscription: AzureResourceSubscription;
|
||||
interface AzureResourceSubscriptionQuickPickItem extends QuickPickItem {
|
||||
subscription: azureResource.AzureResourceSubscription;
|
||||
}
|
||||
|
||||
const subscriptionItems: SubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
||||
const subscriptionQuickPickItems: AzureResourceSubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
||||
return {
|
||||
label: subscription.name,
|
||||
picked: selectedSubscriptionIds.indexOf(subscription.id) !== -1,
|
||||
@@ -55,66 +68,22 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
|
||||
};
|
||||
});
|
||||
|
||||
const pickedSubscriptionItems = (await window.showQuickPick(subscriptionItems, { canPickMany: true }));
|
||||
if (pickedSubscriptionItems && pickedSubscriptionItems.length > 0) {
|
||||
const selectedSubscriptionQuickPickItems = (await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true }));
|
||||
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
|
||||
tree.refresh(node, false);
|
||||
|
||||
const pickedSubscriptions = pickedSubscriptionItems.map((subscriptionItem) => subscriptionItem.subscription);
|
||||
await servicePool.subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, pickedSubscriptions);
|
||||
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
|
||||
await subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, selectedSubscriptions);
|
||||
}
|
||||
});
|
||||
|
||||
apiWrapper.registerCommand('azureresource.refreshall', () => tree.notifyNodeChanged(undefined));
|
||||
appContext.apiWrapper.registerCommand('azure.resource.refreshall', () => tree.notifyNodeChanged(undefined));
|
||||
|
||||
apiWrapper.registerCommand('azureresource.refresh', async (node?: TreeNode) => {
|
||||
appContext.apiWrapper.registerCommand('azure.resource.refresh', async (node?: TreeNode) => {
|
||||
tree.refresh(node, true);
|
||||
});
|
||||
|
||||
apiWrapper.registerCommand('azureresource.connectsqldb', async (node?: TreeNode) => {
|
||||
let connectionProfile: sqlops.IConnectionProfile = {
|
||||
id: generateGuid(),
|
||||
connectionName: undefined,
|
||||
serverName: undefined,
|
||||
databaseName: undefined,
|
||||
userName: undefined,
|
||||
password: '',
|
||||
authenticationType: undefined,
|
||||
savePassword: true,
|
||||
groupFullName: '',
|
||||
groupId: '',
|
||||
providerName: undefined,
|
||||
saveProfile: true,
|
||||
options: {
|
||||
}
|
||||
};
|
||||
|
||||
if (node instanceof AzureResourceDatabaseServerTreeNode) {
|
||||
let databaseServer = node.databaseServer;
|
||||
connectionProfile.connectionName = `connection to '${databaseServer.defaultDatabaseName}' on '${databaseServer.fullName}'`;
|
||||
connectionProfile.serverName = databaseServer.fullName;
|
||||
connectionProfile.databaseName = databaseServer.defaultDatabaseName;
|
||||
connectionProfile.userName = databaseServer.loginName;
|
||||
connectionProfile.authenticationType = 'SqlLogin';
|
||||
connectionProfile.providerName = 'MSSQL';
|
||||
}
|
||||
|
||||
if (node instanceof AzureResourceDatabaseTreeNode) {
|
||||
let database = node.database;
|
||||
connectionProfile.connectionName = `connection to '${database.name}' on '${database.serverFullName}'`;
|
||||
connectionProfile.serverName = database.serverFullName;
|
||||
connectionProfile.databaseName = database.name;
|
||||
connectionProfile.userName = database.loginName;
|
||||
connectionProfile.authenticationType = 'SqlLogin';
|
||||
connectionProfile.providerName = 'MSSQL';
|
||||
}
|
||||
|
||||
const conn = await apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
|
||||
if (conn) {
|
||||
apiWrapper.executeCommand('workbench.view.connections');
|
||||
}
|
||||
});
|
||||
|
||||
apiWrapper.registerCommand('azureresource.signin', async (node?: TreeNode) => {
|
||||
apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
|
||||
appContext.apiWrapper.registerCommand('azure.resource.signin', async (node?: TreeNode) => {
|
||||
appContext.apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,11 +6,19 @@
|
||||
'use strict';
|
||||
|
||||
export enum AzureResourceItemType {
|
||||
account = 'azureResource.itemType.account',
|
||||
subscription = 'azureResource.itemType.subscription',
|
||||
databaseContainer = 'azureResource.itemType.databaseContainer',
|
||||
database = 'azureResource.itemType.database',
|
||||
databaseServerContainer = 'azureResource.itemType.databaseServerContainer',
|
||||
databaseServer = 'azureResource.itemType.databaseServer',
|
||||
message = 'azureResource.itemType.message'
|
||||
account = 'azure.resource.itemType.account',
|
||||
subscription = 'azure.resource.itemType.subscription',
|
||||
databaseContainer = 'azure.resource.itemType.databaseContainer',
|
||||
database = 'azure.resource.itemType.database',
|
||||
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
|
||||
databaseServer = 'azure.resource.itemType.databaseServer',
|
||||
message = 'azure.resource.itemType.message'
|
||||
}
|
||||
|
||||
export enum AzureResourceServiceNames {
|
||||
cacheService = 'AzureResourceCacheService',
|
||||
accountService = 'AzureResourceAccountService',
|
||||
subscriptionService = 'AzureResourceSubscriptionService',
|
||||
subscriptionFilterService = 'AzureResourceSubscriptionFilterService',
|
||||
tenantService = 'AzureResourceTenantService'
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
export class AzureResourceCredentialError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public innerError: Error
|
||||
public readonly innerError: Error
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@@ -6,49 +6,40 @@
|
||||
'use strict';
|
||||
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { Account, DidChangeAccountsParams } from 'sqlops';
|
||||
import { Event } from 'vscode';
|
||||
|
||||
import { AzureResourceSubscription, AzureResourceDatabaseServer, AzureResourceDatabase } from './models';
|
||||
import { azureResource } from './azure-resource';
|
||||
|
||||
export interface IAzureResourceAccountService {
|
||||
getAccounts(): Promise<sqlops.Account[]>;
|
||||
getAccounts(): Promise<Account[]>;
|
||||
|
||||
readonly onDidChangeAccounts: Event<sqlops.DidChangeAccountsParams>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceCredentialService {
|
||||
getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]>;
|
||||
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceSubscriptionService {
|
||||
getSubscriptions(account: sqlops.Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]>;
|
||||
getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceSubscriptionFilterService {
|
||||
getSelectedSubscriptions(account: sqlops.Account): Promise<AzureResourceSubscription[]>;
|
||||
getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
|
||||
saveSelectedSubscriptions(account: sqlops.Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceDatabaseServerService {
|
||||
getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceDatabaseService {
|
||||
getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]>;
|
||||
saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceCacheService {
|
||||
generateKey(id: string): string;
|
||||
|
||||
get<T>(key: string): T | undefined;
|
||||
|
||||
update<T>(key: string, value: T): void;
|
||||
}
|
||||
|
||||
export interface IAzureResourceContextService {
|
||||
getAbsolutePath(relativePath: string): string;
|
||||
|
||||
executeCommand(commandId: string, ...args: any[]): void;
|
||||
|
||||
showErrorMessage(errorMessage: string): void;
|
||||
export interface IAzureResourceTenantService {
|
||||
getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceNodeWithProviderId {
|
||||
resourceProviderId: string;
|
||||
resourceNode: azureResource.IAzureResourceNode;
|
||||
}
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { NodeInfo } from 'sqlops';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
|
||||
import { AzureResourceItemType } from '../constants';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { AzureResourceItemType } from './constants';
|
||||
|
||||
export class AzureResourceMessageTreeNode extends TreeNode {
|
||||
public constructor(
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IConnectionProfile } from 'sqlops';
|
||||
import { AppContext } from '../../../appContext';
|
||||
|
||||
import { TreeNode } from '../../treeNode';
|
||||
import { generateGuid } from '../../utils';
|
||||
import { AzureResourceItemType } from '../../constants';
|
||||
import { IAzureResourceDatabaseNode } from './interfaces';
|
||||
import { AzureResourceResourceTreeNode } from '../../resourceTreeNode';
|
||||
|
||||
export function registerAzureResourceDatabaseCommands(appContext: AppContext): void {
|
||||
appContext.apiWrapper.registerCommand('azure.resource.connectsqldb', async (node?: TreeNode) => {
|
||||
if (!node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const treeItem = await node.getTreeItem();
|
||||
if (treeItem.contextValue !== AzureResourceItemType.database) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode;
|
||||
const database = (resourceNode as IAzureResourceDatabaseNode).database;
|
||||
|
||||
let connectionProfile: IConnectionProfile = {
|
||||
id: generateGuid(),
|
||||
connectionName: undefined,
|
||||
serverName: database.serverFullName,
|
||||
databaseName: database.name,
|
||||
userName: database.loginName,
|
||||
password: '',
|
||||
authenticationType: 'SqlLogin',
|
||||
savePassword: true,
|
||||
groupFullName: '',
|
||||
groupId: '',
|
||||
providerName: 'MSSQL',
|
||||
saveProfile: true,
|
||||
options: {}
|
||||
};
|
||||
|
||||
const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
|
||||
if (conn) {
|
||||
appContext.apiWrapper.executeCommand('workbench.view.connections');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ExtensionContext } from 'vscode';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceDatabaseService } from './interfaces';
|
||||
import { AzureResourceDatabaseTreeDataProvider } from './databaseTreeDataProvider';
|
||||
|
||||
export class AzureResourceDatabaseProvider implements azureResource.IAzureResourceProvider {
|
||||
public constructor(
|
||||
databaseService: IAzureResourceDatabaseService,
|
||||
apiWrapper: ApiWrapper,
|
||||
extensionContext: ExtensionContext
|
||||
) {
|
||||
this._databaseService = databaseService;
|
||||
this._apiWrapper = apiWrapper;
|
||||
this._extensionContext = extensionContext;
|
||||
}
|
||||
|
||||
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
|
||||
return new AzureResourceDatabaseTreeDataProvider(this._databaseService, this._apiWrapper, this._extensionContext);
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return 'azure.resource.providers.database';
|
||||
}
|
||||
|
||||
private _databaseService: IAzureResourceDatabaseService = undefined;
|
||||
private _apiWrapper: ApiWrapper = undefined;
|
||||
private _extensionContext: ExtensionContext = undefined;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SqlManagementClient } from 'azure-arm-sql';
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceDatabaseService } from './interfaces';
|
||||
import { AzureResourceDatabase } from './models';
|
||||
|
||||
export class AzureResourceDatabaseService implements IAzureResourceDatabaseService {
|
||||
public async getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabase[]> {
|
||||
const databases: AzureResourceDatabase[] = [];
|
||||
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
|
||||
const svrs = await sqlManagementClient.servers.list();
|
||||
for (const svr of svrs) {
|
||||
// Extract resource group name from svr.id
|
||||
const svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
|
||||
if (!svrIdRegExp.test(svr.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const founds = svrIdRegExp.exec(svr.id);
|
||||
const resouceGroup = founds[1];
|
||||
|
||||
const dbs = await sqlManagementClient.databases.listByServer(resouceGroup, svr.name);
|
||||
dbs.forEach((db) => databases.push({
|
||||
name: db.name,
|
||||
serverName: svr.name,
|
||||
serverFullName: svr.fullyQualifiedDomainName,
|
||||
loginName: svr.administratorLogin
|
||||
}));
|
||||
}
|
||||
|
||||
return databases;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { AzureResource } from 'sqlops';
|
||||
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceDatabaseService, IAzureResourceDatabaseNode } from './interfaces';
|
||||
import { AzureResourceDatabase } from './models';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
export class AzureResourceDatabaseTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider {
|
||||
public constructor(
|
||||
databaseService: IAzureResourceDatabaseService,
|
||||
apiWrapper: ApiWrapper,
|
||||
extensionContext: ExtensionContext
|
||||
) {
|
||||
this._databaseService = databaseService;
|
||||
this._apiWrapper = apiWrapper;
|
||||
this._extensionContext = extensionContext;
|
||||
}
|
||||
|
||||
public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable<TreeItem> {
|
||||
return element.treeItem;
|
||||
}
|
||||
|
||||
public async getChildren(element?: azureResource.IAzureResourceNode): Promise<azureResource.IAzureResourceNode[]> {
|
||||
if (!element) {
|
||||
return [this.createContainerNode()];
|
||||
}
|
||||
|
||||
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
|
||||
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
|
||||
|
||||
const databases: AzureResourceDatabase[] = (await this._databaseService.getDatabases(element.subscription, credential)) || <AzureResourceDatabase[]>[];
|
||||
|
||||
return databases.map((database) => <IAzureResourceDatabaseNode>{
|
||||
account: element.account,
|
||||
subscription: element.subscription,
|
||||
tenantId: element.tenantId,
|
||||
database: database,
|
||||
treeItem: {
|
||||
id: `databaseServer_${database.serverFullName}.database_${database.name}`,
|
||||
label: `${database.name} (${database.serverName})`,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg')
|
||||
},
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
contextValue: AzureResourceItemType.database
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createContainerNode(): azureResource.IAzureResourceNode {
|
||||
return {
|
||||
account: undefined,
|
||||
subscription: undefined,
|
||||
tenantId: undefined,
|
||||
treeItem: {
|
||||
id: AzureResourceDatabaseTreeDataProvider.containerId,
|
||||
label: AzureResourceDatabaseTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseContainer
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private _databaseService: IAzureResourceDatabaseService = undefined;
|
||||
private _apiWrapper: ApiWrapper = undefined;
|
||||
private _extensionContext: ExtensionContext = undefined;
|
||||
|
||||
private static readonly containerId = 'azure.resource.providers.database.treeDataProvider.databaseContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.database.treeDataProvider.databaseContainerLabel', 'SQL Databases');
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ServiceClientCredentials } from 'ms-rest';
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { AzureResourceDatabase } from './models';
|
||||
|
||||
export interface IAzureResourceDatabaseService {
|
||||
getDatabases(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabase[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceDatabaseNode extends azureResource.IAzureResourceNode {
|
||||
readonly database: AzureResourceDatabase;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export interface AzureResourceDatabase {
|
||||
name: string;
|
||||
serverName: string;
|
||||
serverFullName: string;
|
||||
loginName: string;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IConnectionProfile } from 'sqlops';
|
||||
import { AppContext } from '../../../appContext';
|
||||
|
||||
import { TreeNode } from '../../treeNode';
|
||||
import { generateGuid } from '../../utils';
|
||||
import { AzureResourceItemType } from '../../constants';
|
||||
import { IAzureResourceDatabaseServerNode } from './interfaces';
|
||||
import { AzureResourceResourceTreeNode } from '../../resourceTreeNode';
|
||||
|
||||
export function registerAzureResourceDatabaseServerCommands(appContext: AppContext): void {
|
||||
appContext.apiWrapper.registerCommand('azure.resource.connectsqlserver', async (node?: TreeNode) => {
|
||||
if (!node)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const treeItem = await node.getTreeItem();
|
||||
if (treeItem.contextValue !== AzureResourceItemType.databaseServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resourceNode = (node as AzureResourceResourceTreeNode).resourceNodeWithProviderId.resourceNode;
|
||||
const databaseServer = (resourceNode as IAzureResourceDatabaseServerNode).databaseServer;
|
||||
|
||||
let connectionProfile: IConnectionProfile = {
|
||||
id: generateGuid(),
|
||||
connectionName: undefined,
|
||||
serverName: databaseServer.fullName,
|
||||
databaseName: databaseServer.defaultDatabaseName,
|
||||
userName: databaseServer.loginName,
|
||||
password: '',
|
||||
authenticationType: 'SqlLogin',
|
||||
savePassword: true,
|
||||
groupFullName: '',
|
||||
groupId: '',
|
||||
providerName: 'MSSQL',
|
||||
saveProfile: true,
|
||||
options: {}
|
||||
};
|
||||
|
||||
const conn = await appContext.apiWrapper.openConnectionDialog(undefined, connectionProfile, { saveConnection: true, showDashboard: true });
|
||||
if (conn) {
|
||||
appContext.apiWrapper.executeCommand('workbench.view.connections');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ExtensionContext } from 'vscode';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceDatabaseServerService } from './interfaces';
|
||||
import { AzureResourceDatabaseServerTreeDataProvider } from './databaseServerTreeDataProvider';
|
||||
|
||||
export class AzureResourceDatabaseServerProvider implements azureResource.IAzureResourceProvider {
|
||||
public constructor(
|
||||
databaseServerService: IAzureResourceDatabaseServerService,
|
||||
apiWrapper: ApiWrapper,
|
||||
extensionContext: ExtensionContext
|
||||
) {
|
||||
this._databaseServerService = databaseServerService;
|
||||
this._apiWrapper = apiWrapper;
|
||||
this._extensionContext = extensionContext;
|
||||
}
|
||||
|
||||
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
|
||||
return new AzureResourceDatabaseServerTreeDataProvider(this._databaseServerService, this._apiWrapper, this._extensionContext);
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return 'azure.resource.providers.databaseServer';
|
||||
}
|
||||
|
||||
private _databaseServerService: IAzureResourceDatabaseServerService = undefined;
|
||||
private _apiWrapper: ApiWrapper = undefined;
|
||||
private _extensionContext: ExtensionContext = undefined;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SqlManagementClient } from 'azure-arm-sql';
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceDatabaseServerService } from './interfaces';
|
||||
import { AzureResourceDatabaseServer } from './models';
|
||||
|
||||
export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService {
|
||||
public async getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabaseServer[]> {
|
||||
const databaseServers: AzureResourceDatabaseServer[] = [];
|
||||
|
||||
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
|
||||
const svrs = await sqlManagementClient.servers.list();
|
||||
|
||||
svrs.forEach((svr) => databaseServers.push({
|
||||
name: svr.name,
|
||||
fullName: svr.fullyQualifiedDomainName,
|
||||
loginName: svr.administratorLogin,
|
||||
defaultDatabaseName: 'master'
|
||||
}));
|
||||
|
||||
return databaseServers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { AzureResource } from 'sqlops';
|
||||
import { TreeItem, TreeItemCollapsibleState, ExtensionContext } from 'vscode';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceDatabaseServerService, IAzureResourceDatabaseServerNode } from './interfaces';
|
||||
import { AzureResourceDatabaseServer } from './models';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
export class AzureResourceDatabaseServerTreeDataProvider implements azureResource.IAzureResourceTreeDataProvider {
|
||||
public constructor(
|
||||
databaseServerService: IAzureResourceDatabaseServerService,
|
||||
apiWrapper: ApiWrapper,
|
||||
extensionContext: ExtensionContext
|
||||
) {
|
||||
this._databaseServerService = databaseServerService;
|
||||
this._apiWrapper = apiWrapper;
|
||||
this._extensionContext = extensionContext;
|
||||
}
|
||||
|
||||
public getTreeItem(element: azureResource.IAzureResourceNode): TreeItem | Thenable<TreeItem> {
|
||||
return element.treeItem;
|
||||
}
|
||||
|
||||
public async getChildren(element?: azureResource.IAzureResourceNode): Promise<azureResource.IAzureResourceNode[]> {
|
||||
if (!element) {
|
||||
return [this.createContainerNode()];
|
||||
}
|
||||
|
||||
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
|
||||
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
|
||||
|
||||
const databaseServers: AzureResourceDatabaseServer[] = (await this._databaseServerService.getDatabaseServers(element.subscription, credential)) || <AzureResourceDatabaseServer[]>[];
|
||||
|
||||
return databaseServers.map((databaseServer) => <IAzureResourceDatabaseServerNode>{
|
||||
account: element.account,
|
||||
subscription: element.subscription,
|
||||
tenantId: element.tenantId,
|
||||
databaseServer: databaseServer,
|
||||
treeItem: {
|
||||
id: `databaseServer_${databaseServer.name}`,
|
||||
label: databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
|
||||
},
|
||||
collapsibleState: TreeItemCollapsibleState.None,
|
||||
contextValue: AzureResourceItemType.databaseServer
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private createContainerNode(): azureResource.IAzureResourceNode {
|
||||
return {
|
||||
account: undefined,
|
||||
subscription: undefined,
|
||||
tenantId: undefined,
|
||||
treeItem: {
|
||||
id: AzureResourceDatabaseServerTreeDataProvider.containerId,
|
||||
label: AzureResourceDatabaseServerTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private _databaseServerService: IAzureResourceDatabaseServerService = undefined;
|
||||
private _apiWrapper: ApiWrapper = undefined;
|
||||
private _extensionContext: ExtensionContext = undefined;
|
||||
|
||||
private static readonly containerId = 'azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainerLabel', 'SQL Servers');
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ServiceClientCredentials } from 'ms-rest';
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { AzureResourceDatabaseServer } from './models';
|
||||
|
||||
export interface IAzureResourceDatabaseServerService {
|
||||
getDatabaseServers(subscription: azureResource.AzureResourceSubscription, credentials: ServiceClientCredentials): Promise<AzureResourceDatabaseServer[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceDatabaseServerNode extends azureResource.IAzureResourceNode {
|
||||
readonly databaseServer: AzureResourceDatabaseServer;
|
||||
}
|
||||
@@ -5,21 +5,9 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
export interface AzureResourceSubscription {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface AzureResourceDatabaseServer {
|
||||
name: string;
|
||||
fullName: string;
|
||||
loginName: string;
|
||||
defaultDatabaseName: string;
|
||||
}
|
||||
|
||||
export interface AzureResourceDatabase {
|
||||
name: string;
|
||||
serverName: string;
|
||||
serverFullName: string;
|
||||
loginName: string;
|
||||
}
|
||||
}
|
||||
129
extensions/azurecore/src/azureResource/resourceService.ts
Normal file
129
extensions/azurecore/src/azureResource/resourceService.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { extensions, TreeItem } from 'vscode';
|
||||
import { Account } from 'sqlops';
|
||||
|
||||
import { azureResource } from './azure-resource';
|
||||
import { IAzureResourceNodeWithProviderId } from './interfaces';
|
||||
|
||||
export class AzureResourceService {
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public static getInstance(): AzureResourceService {
|
||||
return AzureResourceService._instance;
|
||||
}
|
||||
|
||||
public async listResourceProviderIds(): Promise<string[]> {
|
||||
await this.ensureResourceProvidersRegistered();
|
||||
|
||||
return Object.keys(this._resourceProviders);
|
||||
}
|
||||
|
||||
public registerResourceProvider(resourceProvider: azureResource.IAzureResourceProvider): void {
|
||||
this.doRegisterResourceProvider(resourceProvider);
|
||||
}
|
||||
|
||||
public clearResourceProviders(): void {
|
||||
this._resourceProviders = {};
|
||||
this._treeDataProviders = {};
|
||||
this._areResourceProvidersLoaded = false;
|
||||
}
|
||||
|
||||
public async getRootChildren(resourceProviderId: string, account: Account, subscription: azureResource.AzureResourceSubscription, tenatId: string): Promise<IAzureResourceNodeWithProviderId[]> {
|
||||
await this.ensureResourceProvidersRegistered();
|
||||
|
||||
if (!(resourceProviderId in this._resourceProviders)) {
|
||||
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
|
||||
}
|
||||
|
||||
const treeDataProvider = this._treeDataProviders[resourceProviderId];
|
||||
const children = await treeDataProvider.getChildren();
|
||||
|
||||
return children.map((child) => <IAzureResourceNodeWithProviderId>{
|
||||
resourceProviderId: resourceProviderId,
|
||||
resourceNode: <azureResource.IAzureResourceNode>{
|
||||
account: account,
|
||||
subscription: subscription,
|
||||
tenantId: tenatId,
|
||||
treeItem: child.treeItem
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getChildren(resourceProviderId: string, element: azureResource.IAzureResourceNode): Promise<IAzureResourceNodeWithProviderId[]> {
|
||||
await this.ensureResourceProvidersRegistered();
|
||||
|
||||
if (!(resourceProviderId in this._resourceProviders)) {
|
||||
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
|
||||
}
|
||||
|
||||
const treeDataProvider = this._treeDataProviders[resourceProviderId];
|
||||
const children = await treeDataProvider.getChildren(element);
|
||||
|
||||
return children.map((child) => <IAzureResourceNodeWithProviderId>{
|
||||
resourceProviderId: resourceProviderId,
|
||||
resourceNode: child
|
||||
});
|
||||
}
|
||||
|
||||
public async getTreeItem(resourceProviderId: string, element?: azureResource.IAzureResourceNode): Promise<TreeItem> {
|
||||
await this.ensureResourceProvidersRegistered();
|
||||
|
||||
if (!(resourceProviderId in this._resourceProviders)) {
|
||||
throw new Error(`Azure resource provider doesn't exist. Id: ${resourceProviderId}`);
|
||||
}
|
||||
|
||||
const treeDataProvider = this._treeDataProviders[resourceProviderId];
|
||||
return treeDataProvider.getTreeItem(element);
|
||||
}
|
||||
|
||||
public get areResourceProvidersLoaded(): boolean {
|
||||
return this._areResourceProvidersLoaded;
|
||||
}
|
||||
|
||||
public set areResourceProvidersLoaded(value: boolean) {
|
||||
this._areResourceProvidersLoaded = value;
|
||||
}
|
||||
|
||||
private async ensureResourceProvidersRegistered(): Promise<void> {
|
||||
if (this._areResourceProvidersLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const extension of extensions.all) {
|
||||
const contributes = extension.packageJSON && extension.packageJSON.contributes;
|
||||
if (!contributes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (contributes['hasAzureResourceProviders']) {
|
||||
await extension.activate();
|
||||
|
||||
if (extension.exports && extension.exports.provideResources) {
|
||||
for (const resourceProvider of <azureResource.IAzureResourceProvider[]>extension.exports.provideResources()) {
|
||||
this.doRegisterResourceProvider(resourceProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._areResourceProvidersLoaded = true;
|
||||
}
|
||||
|
||||
private doRegisterResourceProvider(resourceProvider: azureResource.IAzureResourceProvider): void {
|
||||
this._resourceProviders[resourceProvider.providerId] = resourceProvider;
|
||||
this._treeDataProviders[resourceProvider.providerId] = resourceProvider.getTreeDataProvider();
|
||||
}
|
||||
|
||||
private _areResourceProvidersLoaded: boolean = false;
|
||||
private _resourceProviders: { [resourceProviderId: string]: azureResource.IAzureResourceProvider } = {};
|
||||
private _treeDataProviders: { [resourceProviderId: string]: azureResource.IAzureResourceTreeDataProvider } = {};
|
||||
|
||||
private static readonly _instance = new AzureResourceService();
|
||||
}
|
||||
79
extensions/azurecore/src/azureResource/resourceTreeNode.ts
Normal file
79
extensions/azurecore/src/azureResource/resourceTreeNode.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { NodeInfo } from 'sqlops';
|
||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { TreeNode } from './treeNode';
|
||||
import { AzureResourceService } from './resourceService';
|
||||
import { IAzureResourceNodeWithProviderId } from './interfaces';
|
||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
||||
import { AzureResourceErrorMessageUtil } from './utils';
|
||||
|
||||
export class AzureResourceResourceTreeNode extends TreeNode {
|
||||
public constructor(
|
||||
public readonly resourceNodeWithProviderId: IAzureResourceNodeWithProviderId,
|
||||
parent: TreeNode
|
||||
) {
|
||||
super();
|
||||
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeNode[]> {
|
||||
// It is a leaf node.
|
||||
if (this.resourceNodeWithProviderId.resourceNode.treeItem.collapsibleState === TreeItemCollapsibleState.None) {
|
||||
return <TreeNode[]>[];
|
||||
}
|
||||
|
||||
try {
|
||||
const children = await this._resourceService.getChildren(this.resourceNodeWithProviderId.resourceProviderId, this.resourceNodeWithProviderId.resourceNode);
|
||||
|
||||
if (children.length === 0) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceResourceTreeNode.noResourcesLabel, this)];
|
||||
} else {
|
||||
return children.map((child) => {
|
||||
// To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered'
|
||||
child.resourceNode.treeItem.id = `${this.resourceNodeWithProviderId.resourceNode.treeItem.id}.${child.resourceNode.treeItem.id}`;
|
||||
return new AzureResourceResourceTreeNode(child, this);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||
}
|
||||
}
|
||||
|
||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||
return this._resourceService.getTreeItem(this.resourceNodeWithProviderId.resourceProviderId, this.resourceNodeWithProviderId.resourceNode);
|
||||
}
|
||||
|
||||
public getNodeInfo(): NodeInfo {
|
||||
const treeItem = this.resourceNodeWithProviderId.resourceNode.treeItem;
|
||||
|
||||
return {
|
||||
label: treeItem.label,
|
||||
isLeaf: treeItem.collapsibleState === TreeItemCollapsibleState.None ? true : false,
|
||||
errorMessage: undefined,
|
||||
metadata: undefined,
|
||||
nodePath: this.generateNodePath(),
|
||||
nodeStatus: undefined,
|
||||
nodeType: treeItem.contextValue,
|
||||
nodeSubType: undefined,
|
||||
iconType: treeItem.contextValue
|
||||
};
|
||||
}
|
||||
|
||||
public get nodePathValue(): string {
|
||||
return this.resourceNodeWithProviderId.resourceNode.treeItem.id;
|
||||
}
|
||||
|
||||
private _resourceService = AzureResourceService.getInstance();
|
||||
|
||||
private static readonly noResourcesLabel = localize('azure.resource.resourceTreeNode.noResourcesLabel', 'No Resources found.');
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 {
|
||||
IAzureResourceAccountService,
|
||||
IAzureResourceCredentialService,
|
||||
IAzureResourceSubscriptionService,
|
||||
IAzureResourceSubscriptionFilterService,
|
||||
IAzureResourceDatabaseService,
|
||||
IAzureResourceDatabaseServerService,
|
||||
IAzureResourceCacheService,
|
||||
IAzureResourceContextService } from './interfaces';
|
||||
|
||||
export class AzureResourceServicePool {
|
||||
private constructor() { }
|
||||
|
||||
public static getInstance(): AzureResourceServicePool {
|
||||
return AzureResourceServicePool._instance;
|
||||
}
|
||||
|
||||
public contextService: IAzureResourceContextService;
|
||||
public cacheService: IAzureResourceCacheService;
|
||||
public accountService: IAzureResourceAccountService;
|
||||
public credentialService: IAzureResourceCredentialService;
|
||||
public subscriptionService: IAzureResourceSubscriptionService;
|
||||
public subscriptionFilterService: IAzureResourceSubscriptionFilterService;
|
||||
public databaseService: IAzureResourceDatabaseService;
|
||||
public databaseServerService: IAzureResourceDatabaseServerService;
|
||||
|
||||
private static readonly _instance = new AzureResourceServicePool();
|
||||
}
|
||||
@@ -5,21 +5,30 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ExtensionContext } from "vscode";
|
||||
import { ExtensionContext } from 'vscode';
|
||||
|
||||
import { IAzureResourceCacheService } from "../interfaces";
|
||||
import { IAzureResourceCacheService } from '../interfaces';
|
||||
|
||||
export class AzureResourceCacheService implements IAzureResourceCacheService {
|
||||
public constructor(
|
||||
public readonly context: ExtensionContext
|
||||
context: ExtensionContext
|
||||
) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
public get<T>(key: string): T | undefined {
|
||||
return this.context.workspaceState.get(key);
|
||||
public generateKey(id: string): string {
|
||||
return `${AzureResourceCacheService.cacheKeyPrefix}.${id}`;
|
||||
}
|
||||
|
||||
public get<T>(key: string): T | undefined {
|
||||
return this._context.workspaceState.get(key);
|
||||
}
|
||||
|
||||
public update<T>(key: string, value: T): void {
|
||||
this.context.workspaceState.update(key, value);
|
||||
this._context.workspaceState.update(key, value);
|
||||
}
|
||||
|
||||
private _context: ExtensionContext = undefined;
|
||||
|
||||
private static readonly cacheKeyPrefix = 'azure.resource.cache';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ExtensionContext } from "vscode";
|
||||
import { ApiWrapper } from "../../apiWrapper";
|
||||
|
||||
import { IAzureResourceContextService } from "../interfaces";
|
||||
|
||||
export class AzureResourceContextService implements IAzureResourceContextService {
|
||||
public constructor(
|
||||
context: ExtensionContext,
|
||||
apiWrapper: ApiWrapper
|
||||
) {
|
||||
this._context = context;
|
||||
this._apiWrapper = apiWrapper;
|
||||
}
|
||||
|
||||
public getAbsolutePath(relativePath: string): string {
|
||||
return this._context.asAbsolutePath(relativePath);
|
||||
}
|
||||
|
||||
public executeCommand(commandId: string, ...args: any[]): void {
|
||||
this._apiWrapper.executeCommand(commandId, args);
|
||||
}
|
||||
|
||||
public showErrorMessage(errorMessage: string): void {
|
||||
this._apiWrapper.showErrorMessage(errorMessage);
|
||||
}
|
||||
|
||||
private _context: ExtensionContext = undefined;
|
||||
private _apiWrapper: ApiWrapper = undefined;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as sqlops from 'sqlops';
|
||||
import { TokenCredentials, ServiceClientCredentials } from 'ms-rest';
|
||||
import { ApiWrapper } from '../../apiWrapper';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { IAzureResourceCredentialService } from '../interfaces';
|
||||
import { AzureResourceCredentialError } from '../errors';
|
||||
|
||||
export class AzureResourceCredentialService implements IAzureResourceCredentialService {
|
||||
public constructor(
|
||||
apiWrapper: ApiWrapper
|
||||
) {
|
||||
this._apiWrapper = apiWrapper;
|
||||
}
|
||||
|
||||
public async getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]> {
|
||||
try {
|
||||
let credentials: TokenCredentials[] = [];
|
||||
let tokens = await this._apiWrapper.getSecurityToken(account, resource);
|
||||
|
||||
for (let tenant of account.properties.tenants) {
|
||||
let token = tokens[tenant.id].token;
|
||||
let tokenType = tokens[tenant.id].tokenType;
|
||||
|
||||
credentials.push(new TokenCredentials(token, tokenType));
|
||||
}
|
||||
|
||||
return credentials;
|
||||
} catch (error) {
|
||||
throw new AzureResourceCredentialError(localize('azureResource.services.credentialService.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', account.key.accountId), error);
|
||||
}
|
||||
}
|
||||
|
||||
private _apiWrapper: ApiWrapper = undefined;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SqlManagementClient } from 'azure-arm-sql';
|
||||
|
||||
import { IAzureResourceDatabaseServerService } from '../interfaces';
|
||||
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../models';
|
||||
|
||||
export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService {
|
||||
public async getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]> {
|
||||
let databaseServers: AzureResourceDatabaseServer[] = [];
|
||||
for (let cred of credentials) {
|
||||
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
|
||||
try {
|
||||
let svrs = await sqlManagementClient.servers.list();
|
||||
svrs.forEach((svr) => databaseServers.push({
|
||||
name: svr.name,
|
||||
fullName: svr.fullyQualifiedDomainName,
|
||||
loginName: svr.administratorLogin,
|
||||
defaultDatabaseName: 'master'
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
|
||||
/**
|
||||
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
|
||||
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return databaseServers;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SqlManagementClient } from 'azure-arm-sql';
|
||||
|
||||
import { IAzureResourceDatabaseService } from '../interfaces';
|
||||
import { AzureResourceSubscription, AzureResourceDatabase } from '../models';
|
||||
|
||||
export class AzureResourceDatabaseService implements IAzureResourceDatabaseService {
|
||||
public async getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]> {
|
||||
let databases: AzureResourceDatabase[] = [];
|
||||
for (let cred of credentials) {
|
||||
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
|
||||
try {
|
||||
let svrs = await sqlManagementClient.servers.list();
|
||||
for (let svr of svrs) {
|
||||
// Extract resource group name from svr.id
|
||||
let svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
|
||||
if (!svrIdRegExp.test(svr.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let founds = svrIdRegExp.exec(svr.id);
|
||||
let resouceGroup = founds[1];
|
||||
|
||||
let dbs = await sqlManagementClient.databases.listByServer(resouceGroup, svr.name);
|
||||
dbs.forEach((db) => databases.push({
|
||||
name: db.name,
|
||||
serverName: svr.name,
|
||||
serverFullName: svr.fullyQualifiedDomainName,
|
||||
loginName: svr.administratorLogin
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
|
||||
/**
|
||||
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
|
||||
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return databases;
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,11 @@
|
||||
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
|
||||
import { Account } from 'sqlops';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
|
||||
interface AzureResourceSelectedSubscriptionsCache {
|
||||
selectedSubscriptions: { [accountId: string]: AzureResourceSubscription[]};
|
||||
selectedSubscriptions: { [accountId: string]: azureResource.AzureResourceSubscription[]};
|
||||
}
|
||||
|
||||
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
||||
@@ -20,12 +20,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
cacheService: IAzureResourceCacheService
|
||||
) {
|
||||
this._cacheService = cacheService;
|
||||
|
||||
this._cacheKey = this._cacheService.generateKey('selectedSubscriptions');
|
||||
}
|
||||
|
||||
public async getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]> {
|
||||
let selectedSubscriptions: AzureResourceSubscription[] = [];
|
||||
public async getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
let selectedSubscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
||||
}
|
||||
@@ -33,10 +35,10 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
return selectedSubscriptions;
|
||||
}
|
||||
|
||||
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void> {
|
||||
let selectedSubscriptionsCache: { [accountId: string]: AzureResourceSubscription[]} = {};
|
||||
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
|
||||
let selectedSubscriptionsCache: { [accountId: string]: azureResource.AzureResourceSubscription[]} = {};
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
||||
}
|
||||
@@ -47,14 +49,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
|
||||
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
||||
|
||||
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||
|
||||
const filters: string[] = [];
|
||||
for (const accountId in selectedSubscriptionsCache) {
|
||||
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
|
||||
}
|
||||
|
||||
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.FilterConfigName);
|
||||
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.filterConfigName);
|
||||
let configTarget = ConfigurationTarget.Global;
|
||||
if (resourceFilterConfig) {
|
||||
if (resourceFilterConfig.workspaceFolderValue) {
|
||||
@@ -66,12 +68,12 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
}
|
||||
}
|
||||
|
||||
await this._config.update(AzureResourceSubscriptionFilterService.FilterConfigName, filters, configTarget);
|
||||
await this._config.update(AzureResourceSubscriptionFilterService.filterConfigName, filters, configTarget);
|
||||
}
|
||||
|
||||
private _config: WorkspaceConfiguration = undefined;
|
||||
private _cacheService: IAzureResourceCacheService = undefined;
|
||||
private _cacheKey: string = undefined;
|
||||
|
||||
private static readonly FilterConfigName = 'resourceFilter';
|
||||
private static readonly CacheKey = 'azureResource.cache.selectedSubscriptions';
|
||||
private static readonly filterConfigName = 'azure.resource.config.filter';
|
||||
}
|
||||
|
||||
@@ -9,24 +9,19 @@ import { Account } from 'sqlops';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SubscriptionClient } from 'azure-arm-resource';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { IAzureResourceSubscriptionService } from '../interfaces';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
|
||||
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
||||
public async getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]> {
|
||||
let subscriptions: AzureResourceSubscription[] = [];
|
||||
for (let cred of credentials) {
|
||||
let subClient = new SubscriptionClient.SubscriptionClient(cred);
|
||||
try {
|
||||
let subs = await subClient.subscriptions.list();
|
||||
subs.forEach((sub) => subscriptions.push({
|
||||
id: sub.subscriptionId,
|
||||
name: sub.displayName
|
||||
}));
|
||||
} catch (error) {
|
||||
// Swallow the exception here.
|
||||
}
|
||||
}
|
||||
public async getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
const subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
const subClient = new SubscriptionClient.SubscriptionClient(credential);
|
||||
const subs = await subClient.subscriptions.list();
|
||||
subs.forEach((sub) => subscriptions.push({
|
||||
id: sub.subscriptionId,
|
||||
name: sub.displayName
|
||||
}));
|
||||
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as request from 'request';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { IAzureResourceTenantService } from '../interfaces';
|
||||
|
||||
export class AzureResourceTenantService implements IAzureResourceTenantService {
|
||||
public async getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string> {
|
||||
const requestPromisified = new Promise<string>((resolve, reject) => {
|
||||
const url = `https://management.azure.com/subscriptions/${subscription.id}?api-version=2014-04-01`;
|
||||
request(url, function (error, response, body) {
|
||||
if (response.statusCode === 401) {
|
||||
const tenantIdRegEx = /authorization_uri="https:\/\/login\.windows\.net\/([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})"/;
|
||||
const teantIdString = response.headers['www-authenticate'];
|
||||
if (tenantIdRegEx.test(teantIdString)) {
|
||||
resolve(tenantIdRegEx.exec(teantIdString)[1]);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return await requestPromisified;
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,10 @@
|
||||
|
||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { NodeInfo } from 'sqlops';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureResourceItemType } from '../constants';
|
||||
|
||||
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||
@@ -19,11 +19,11 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||
}
|
||||
|
||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||
let item = new TreeItem(AzureResourceAccountNotSignedInTreeNode.SignInLabel, TreeItemCollapsibleState.None);
|
||||
let item = new TreeItem(AzureResourceAccountNotSignedInTreeNode.signInLabel, TreeItemCollapsibleState.None);
|
||||
item.contextValue = AzureResourceItemType.message;
|
||||
item.command = {
|
||||
title: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
||||
command: 'azureresource.signin',
|
||||
title: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||
command: 'azure.resource.signin',
|
||||
arguments: [this]
|
||||
};
|
||||
return item;
|
||||
@@ -31,7 +31,7 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||
|
||||
public getNodeInfo(): NodeInfo {
|
||||
return {
|
||||
label: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
||||
label: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||
isLeaf: true,
|
||||
errorMessage: undefined,
|
||||
metadata: undefined,
|
||||
@@ -47,5 +47,5 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||
return 'message_accountNotSignedIn';
|
||||
}
|
||||
|
||||
private static readonly SignInLabel = localize('azureResource.tree.accountNotSignedInTreeNode.signIn', 'Sign in to Azure ...');
|
||||
private static readonly signInLabel = localize('azure.resource.tree.accountNotSignedInTreeNode.signInLabel', 'Sign in to Azure ...');
|
||||
}
|
||||
|
||||
@@ -6,44 +6,59 @@
|
||||
'use strict';
|
||||
|
||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { Account, NodeInfo } from 'sqlops';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import { Account, NodeInfo, AzureResource } from 'sqlops';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import { AppContext } from '../../appContext';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureResourceCredentialError } from '../errors';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceItemType } from '../constants';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
||||
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureResourceTenantService } from '../../azureResource/interfaces';
|
||||
|
||||
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
public constructor(
|
||||
account: Account,
|
||||
public readonly account: Account,
|
||||
appContext: AppContext,
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler
|
||||
) {
|
||||
super(account, treeChangeHandler, undefined);
|
||||
super(appContext, treeChangeHandler, undefined);
|
||||
|
||||
this._subscriptionService = this.appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||
this._subscriptionFilterService = this.appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||
this._tenantService = this.appContext.getService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService);
|
||||
|
||||
this._id = `account_${this.account.key.accountId}`;
|
||||
this.setCacheKey(`${this._id}.subscriptions`);
|
||||
this._label = this.generateLabel();
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeNode[]> {
|
||||
try {
|
||||
let subscriptions: AzureResourceSubscription[] = [];
|
||||
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
if (this._isClearingCache) {
|
||||
const credentials = await this.getCredentials();
|
||||
subscriptions = (await this.servicePool.subscriptionService.getSubscriptions(this.account, credentials)) || <AzureResourceSubscription[]>[];
|
||||
try {
|
||||
const tokens = await this.appContext.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
|
||||
|
||||
let cache = this.getCache<AzureResourceSubscriptionsCache>();
|
||||
if (!cache) {
|
||||
cache = { subscriptions: { } };
|
||||
for (const tenant of this.account.properties.tenants) {
|
||||
const token = tokens[tenant.id].token;
|
||||
const tokenType = tokens[tenant.id].tokenType;
|
||||
|
||||
subscriptions.push(...(await this._subscriptionService.getSubscriptions(this.account, new TokenCredentials(token, tokenType)) || <azureResource.AzureResourceSubscription[]>[]));
|
||||
}
|
||||
} catch (error) {
|
||||
throw new AzureResourceCredentialError(localize('azure.resource.tree.accountTreeNode.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', this.account.key.accountId), error);
|
||||
}
|
||||
cache.subscriptions[this.account.key.accountId] = subscriptions;
|
||||
this.updateCache<AzureResourceSubscriptionsCache>(cache);
|
||||
|
||||
this.updateCache<azureResource.AzureResourceSubscription[]>(subscriptions);
|
||||
|
||||
this._isClearingCache = false;
|
||||
} else {
|
||||
@@ -52,8 +67,8 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
|
||||
this._totalSubscriptionCount = subscriptions.length;
|
||||
|
||||
let selectedSubscriptions = await this.servicePool.subscriptionFilterService.getSelectedSubscriptions(this.account);
|
||||
let selectedSubscriptionIds = (selectedSubscriptions || <AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||
const selectedSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account);
|
||||
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||
if (selectedSubscriptionIds.length > 0) {
|
||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
||||
@@ -65,31 +80,36 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
this.refreshLabel();
|
||||
|
||||
if (subscriptions.length === 0) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.NoSubscriptions, this)];
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
|
||||
} else {
|
||||
return subscriptions.map((subscription) => new AzureResourceSubscriptionTreeNode(subscription, this.account, this.treeChangeHandler, this));
|
||||
return await Promise.all(subscriptions.map(async (subscription) => {
|
||||
const tenantId = await this._tenantService.getTenantId(subscription);
|
||||
|
||||
return new AzureResourceSubscriptionTreeNode(this.account, subscription, tenantId, this.appContext, this.treeChangeHandler, this);
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||
if (error instanceof AzureResourceCredentialError) {
|
||||
this.appContext.apiWrapper.showErrorMessage(error.message);
|
||||
|
||||
this.appContext.apiWrapper.executeCommand('azure.resource.signin');
|
||||
} else {
|
||||
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 async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
return this.getCache<azureResource.AzureResourceSubscription[]>();
|
||||
}
|
||||
|
||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||
let item = new TreeItem(this._label, TreeItemCollapsibleState.Collapsed);
|
||||
const 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')
|
||||
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/account_inverse.svg'),
|
||||
light: this.appContext.extensionContext.asAbsolutePath('resources/light/account.svg')
|
||||
};
|
||||
return item;
|
||||
}
|
||||
@@ -128,10 +148,6 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
}
|
||||
}
|
||||
|
||||
protected get cacheKey(): string {
|
||||
return 'azureResource.cache.subscriptions';
|
||||
}
|
||||
|
||||
private generateLabel(): string {
|
||||
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
|
||||
|
||||
@@ -142,14 +158,14 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
||||
return label;
|
||||
}
|
||||
|
||||
private _subscriptionService: IAzureResourceSubscriptionService = undefined;
|
||||
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService = undefined;
|
||||
private _tenantService: IAzureResourceTenantService = undefined;
|
||||
|
||||
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[] };
|
||||
}
|
||||
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', 'No Subscriptions found.');
|
||||
}
|
||||
@@ -5,16 +5,16 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import { AppContext } from '../../appContext';
|
||||
|
||||
import { AzureResourceServicePool } from '../servicePool';
|
||||
import { AzureResourceCredentialError } from '../errors';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceCacheService } from '../../azureResource/interfaces';
|
||||
import { AzureResourceServiceNames } from '../constants';
|
||||
|
||||
export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
||||
public constructor(
|
||||
public readonly appContext: AppContext,
|
||||
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||
parent: TreeNode
|
||||
) {
|
||||
@@ -22,17 +22,17 @@ export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
||||
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public readonly servicePool = AzureResourceServicePool.getInstance();
|
||||
}
|
||||
|
||||
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
|
||||
public constructor(
|
||||
public readonly account: sqlops.Account,
|
||||
appContext: AppContext,
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||
parent: TreeNode
|
||||
) {
|
||||
super(treeChangeHandler, parent);
|
||||
super(appContext, treeChangeHandler, parent);
|
||||
|
||||
this._cacheService = this.appContext.getService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService);
|
||||
}
|
||||
|
||||
public clearCache(): void {
|
||||
@@ -43,29 +43,19 @@ export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTr
|
||||
return this._isClearingCache;
|
||||
}
|
||||
|
||||
protected async getCredentials(): Promise<ServiceClientCredentials[]> {
|
||||
try {
|
||||
return await this.servicePool.credentialService.getCredentials(this.account, sqlops.AzureResource.ResourceManagement);
|
||||
} catch (error) {
|
||||
if (error instanceof AzureResourceCredentialError) {
|
||||
this.servicePool.contextService.showErrorMessage(error.message);
|
||||
|
||||
this.servicePool.contextService.executeCommand('azureresource.signin');
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
protected setCacheKey(id: string): void {
|
||||
this._cacheKey = this._cacheService.generateKey(id);
|
||||
}
|
||||
|
||||
protected updateCache<T>(cache: T): void {
|
||||
this.servicePool.cacheService.update<T>(this.cacheKey, cache);
|
||||
this._cacheService.update<T>(this._cacheKey, cache);
|
||||
}
|
||||
|
||||
protected getCache<T>(): T {
|
||||
return this.servicePool.cacheService.get<T>(this.cacheKey);
|
||||
return this._cacheService.get<T>(this._cacheKey);
|
||||
}
|
||||
|
||||
protected abstract get cacheKey(): string;
|
||||
|
||||
protected _isClearingCache = true;
|
||||
private _cacheService: IAzureResourceCacheService = undefined;
|
||||
private _cacheKey: string = undefined;
|
||||
}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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[] };
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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[] };
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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}`;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
@@ -7,38 +7,66 @@
|
||||
|
||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { Account, NodeInfo } from 'sqlops';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import { AppContext } from '../../appContext';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AzureResourceTreeNodeBase, AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { IAzureResourceNodeWithProviderId } from '../interfaces';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceItemType } from '../constants';
|
||||
import { AzureResourceDatabaseContainerTreeNode } from './databaseContainerTreeNode';
|
||||
import { AzureResourceDatabaseServerContainerTreeNode } from './databaseServerContainerTreeNode';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { AzureResourceService } from '../resourceService';
|
||||
import { AzureResourceResourceTreeNode } from '../resourceTreeNode';
|
||||
|
||||
export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase {
|
||||
export class AzureResourceSubscriptionTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||
public constructor(
|
||||
public readonly subscription: AzureResourceSubscription,
|
||||
account: Account,
|
||||
public readonly account: Account,
|
||||
public readonly subscription: azureResource.AzureResourceSubscription,
|
||||
public readonly tenatId: string,
|
||||
appContext: AppContext,
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||
parent: TreeNode
|
||||
) {
|
||||
super(treeChangeHandler, parent);
|
||||
super(appContext, treeChangeHandler, parent);
|
||||
|
||||
this._children.push(new AzureResourceDatabaseContainerTreeNode(subscription, account, treeChangeHandler, this));
|
||||
this._children.push(new AzureResourceDatabaseServerContainerTreeNode(subscription, account, treeChangeHandler, this));
|
||||
this._id = `account_${this.account.key.accountId}.subscription_${this.subscription.id}.tenant_${this.tenatId}`;
|
||||
this.setCacheKey(`${this._id}.resources`);
|
||||
}
|
||||
|
||||
public async getChildren(): Promise<TreeNode[]> {
|
||||
return this._children;
|
||||
try {
|
||||
const resourceService = AzureResourceService.getInstance();
|
||||
|
||||
const children: IAzureResourceNodeWithProviderId[] = [];
|
||||
|
||||
for (const resourceProviderId of await resourceService.listResourceProviderIds()) {
|
||||
children.push(...await resourceService.getRootChildren(resourceProviderId, this.account, this.subscription, this.tenatId));
|
||||
}
|
||||
|
||||
if (children.length === 0) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceSubscriptionTreeNode.noResourcesLabel, this)];
|
||||
} else {
|
||||
return children.map((child) => {
|
||||
// To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered'
|
||||
child.resourceNode.treeItem.id = `${this._id}.${child.resourceNode.treeItem.id}`;
|
||||
return new AzureResourceResourceTreeNode(child, this);
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];
|
||||
}
|
||||
}
|
||||
|
||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
||||
let item = new TreeItem(this.subscription.name, TreeItemCollapsibleState.Collapsed);
|
||||
const 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')
|
||||
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/subscription_inverse.svg'),
|
||||
light: this.appContext.extensionContext.asAbsolutePath('resources/light/subscription.svg')
|
||||
};
|
||||
return item;
|
||||
}
|
||||
@@ -58,8 +86,10 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase
|
||||
}
|
||||
|
||||
public get nodePathValue(): string {
|
||||
return `subscription_${this.subscription.id}`;
|
||||
return this._id;
|
||||
}
|
||||
|
||||
private _children: AzureResourceContainerTreeNodeBase[] = [];
|
||||
private _id: string = undefined;
|
||||
|
||||
private static readonly noResourcesLabel = localize('azure.resource.tree.subscriptionTreeNode.noResourcesLabel', 'No Resources found.');
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import { TreeNode } from '../treeNode';
|
||||
|
||||
export interface IAzureResourceTreeChangeHandler {
|
||||
notifyNodeChanged(node: TreeNode): void;
|
||||
|
||||
@@ -6,26 +6,25 @@
|
||||
'use strict';
|
||||
|
||||
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
|
||||
import { DidChangeAccountsParams } from 'sqlops';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
import { setInterval, clearInterval } from 'timers';
|
||||
import { AppContext } from '../../appContext';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AzureResourceServicePool } from '../servicePool';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureResourceAccountTreeNode } from './accountTreeNode';
|
||||
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
||||
import { AzureResourceContainerTreeNodeBase, AzureResourceTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
|
||||
export interface IAzureResourceTreeChangeHandler {
|
||||
notifyNodeChanged(node: TreeNode): void;
|
||||
}
|
||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||
import { IAzureResourceAccountService } from '../../azureResource/interfaces';
|
||||
import { AzureResourceServiceNames } from '../constants';
|
||||
|
||||
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
|
||||
public constructor() {
|
||||
AzureResourceServicePool.getInstance().accountService.onDidChangeAccounts((e: DidChangeAccountsParams) => { this._onDidChangeTreeData.fire(undefined); });
|
||||
public constructor(
|
||||
public readonly appContext: AppContext
|
||||
) {
|
||||
}
|
||||
|
||||
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
||||
@@ -37,7 +36,7 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
||||
this._loadingTimer = setInterval(async () => {
|
||||
try {
|
||||
// Call sqlops.accounts.getAllAccounts() to determine whether the system has been initialized.
|
||||
await AzureResourceServicePool.getInstance().accountService.getAccounts();
|
||||
await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
|
||||
|
||||
// System has been initialized
|
||||
this.isSystemInitialized = true;
|
||||
@@ -51,16 +50,16 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
||||
// System not initialized yet
|
||||
this.isSystemInitialized = false;
|
||||
}
|
||||
}, AzureResourceTreeProvider.LoadingTimerInterval);
|
||||
}, AzureResourceTreeProvider.loadingTimerInterval);
|
||||
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.Loading, undefined)];
|
||||
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.loadingLabel, undefined)];
|
||||
}
|
||||
|
||||
try {
|
||||
const accounts = await AzureResourceServicePool.getInstance().accountService.getAccounts();
|
||||
const accounts = await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
|
||||
|
||||
if (accounts && accounts.length > 0) {
|
||||
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this));
|
||||
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
|
||||
} else {
|
||||
return [new AzureResourceAccountNotSignedInTreeNode()];
|
||||
}
|
||||
@@ -96,6 +95,6 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
||||
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;
|
||||
private static readonly loadingLabel = localize('azure.resource.tree.treeProvider.loadingLabel', 'Loading ...');
|
||||
private static readonly loadingTimerInterval = 5000;
|
||||
}
|
||||
|
||||
@@ -11,16 +11,6 @@ import * as vscode from 'vscode';
|
||||
type TreeNodePredicate = (node: TreeNode) => boolean;
|
||||
|
||||
export abstract class TreeNode {
|
||||
private _parent: TreeNode = undefined;
|
||||
|
||||
public get parent(): TreeNode {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public set parent(node: TreeNode) {
|
||||
this._parent = node;
|
||||
}
|
||||
|
||||
public generateNodePath(): string {
|
||||
let path = undefined;
|
||||
if (this.parent) {
|
||||
@@ -65,13 +55,23 @@ export abstract class TreeNode {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public get parent(): TreeNode {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public set parent(node: TreeNode) {
|
||||
this._parent = node;
|
||||
}
|
||||
|
||||
public abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
||||
public abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
||||
|
||||
public abstract getNodeInfo(): sqlops.NodeInfo;
|
||||
|
||||
/**
|
||||
* The value to use for this node in the node path
|
||||
*/
|
||||
public abstract get nodePathValue(): string;
|
||||
|
||||
abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
||||
abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
||||
|
||||
abstract getNodeInfo(): sqlops.NodeInfo;
|
||||
private _parent: TreeNode = undefined;
|
||||
}
|
||||
@@ -12,10 +12,9 @@ export function getErrorMessage(error: Error | string): string {
|
||||
return (error instanceof Error) ? error.message : error;
|
||||
}
|
||||
|
||||
|
||||
export class AzureResourceErrorMessageUtil {
|
||||
public static getErrorMessage(error: Error | string): string {
|
||||
return localize('azureResource.error', 'Error: {0}', getErrorMessage(error));
|
||||
return localize('azure.resource.error', 'Error: {0}', getErrorMessage(error));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 ControllerBase from './controllerBase';
|
||||
import { DidChangeAccountsParams } from 'sqlops';
|
||||
|
||||
import {
|
||||
IAzureResourceCacheService,
|
||||
IAzureResourceAccountService,
|
||||
IAzureResourceSubscriptionService,
|
||||
IAzureResourceSubscriptionFilterService,
|
||||
IAzureResourceTenantService } from '../azureResource/interfaces';
|
||||
import { AzureResourceServiceNames } from '../azureResource/constants';
|
||||
import { AzureResourceTreeProvider } from '../azureResource/tree/treeProvider';
|
||||
import { registerAzureResourceCommands } from '../azureResource/commands';
|
||||
import { AzureResourceAccountService } from '../azureResource/services/accountService';
|
||||
import { AzureResourceSubscriptionService } from '../azureResource/services/subscriptionService';
|
||||
import { AzureResourceSubscriptionFilterService } from '../azureResource/services/subscriptionFilterService';
|
||||
import { AzureResourceCacheService } from '../azureResource/services/cacheService';
|
||||
import { AzureResourceTenantService } from '../azureResource/services/tenantService';
|
||||
|
||||
import { registerAzureResourceDatabaseServerCommands } from '../azureResource/providers/databaseServer/commands';
|
||||
import { registerAzureResourceDatabaseCommands } from '../azureResource/providers/database/commands';
|
||||
|
||||
export default class AzureResourceController extends ControllerBase {
|
||||
public activate(): Promise<boolean> {
|
||||
this.appContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, new AzureResourceCacheService(this.extensionContext));
|
||||
this.appContext.registerService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService, new AzureResourceAccountService(this.apiWrapper));
|
||||
this.appContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, new AzureResourceSubscriptionService());
|
||||
this.appContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(this.extensionContext)));
|
||||
this.appContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, new AzureResourceTenantService());
|
||||
|
||||
const azureResourceTree = new AzureResourceTreeProvider(this.appContext);
|
||||
this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
|
||||
|
||||
this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).onDidChangeAccounts((e: DidChangeAccountsParams) => { azureResourceTree.notifyNodeChanged(undefined); });
|
||||
|
||||
registerAzureResourceCommands(this.appContext, azureResourceTree);
|
||||
|
||||
registerAzureResourceDatabaseServerCommands(this.appContext);
|
||||
|
||||
registerAzureResourceDatabaseCommands(this.appContext);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 ControllerBase from './controllerBase';
|
||||
|
||||
import { AzureResourceTreeProvider } from '../azureResource/tree/treeProvider';
|
||||
import { registerAzureResourceCommands } from '../azureResource/commands';
|
||||
import { AzureResourceServicePool } from '../azureResource/servicePool';
|
||||
import { AzureResourceCredentialService } from '../azureResource/services/credentialService';
|
||||
import { AzureResourceAccountService } from '../azureResource/services/accountService';
|
||||
import { AzureResourceSubscriptionService } from '../azureResource/services/subscriptionService';
|
||||
import { AzureResourceSubscriptionFilterService } from '../azureResource/services/subscriptionFilterService';
|
||||
import { AzureResourceDatabaseServerService } from '../azureResource/services/databaseServerService';
|
||||
import { AzureResourceDatabaseService } from '../azureResource/services/databaseService';
|
||||
import { AzureResourceCacheService } from '../azureResource/services/cacheService';
|
||||
import { AzureResourceContextService } from '../azureResource/services/contextService';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export default class MainController extends ControllerBase {
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
|
||||
public activate(): Promise<boolean> {
|
||||
this.configureAzureResource();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private configureAzureResource(): void {
|
||||
let servicePool = AzureResourceServicePool.getInstance();
|
||||
servicePool.cacheService = new AzureResourceCacheService(this.extensionContext);
|
||||
servicePool.contextService = new AzureResourceContextService(this.extensionContext, this.apiWrapper);
|
||||
servicePool.accountService = new AzureResourceAccountService(this.apiWrapper);
|
||||
servicePool.credentialService = new AzureResourceCredentialService(this.apiWrapper);
|
||||
servicePool.subscriptionService = new AzureResourceSubscriptionService();
|
||||
servicePool.subscriptionFilterService = new AzureResourceSubscriptionFilterService(new AzureResourceCacheService(this.extensionContext));
|
||||
servicePool.databaseService = new AzureResourceDatabaseService();
|
||||
servicePool.databaseServerService = new AzureResourceDatabaseServerService();
|
||||
|
||||
let azureResourceTree = new AzureResourceTreeProvider();
|
||||
this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('azureResourceExplorer', azureResourceTree));
|
||||
|
||||
registerAzureResourceCommands(this.apiWrapper, azureResourceTree);
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,17 @@ import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import * as constants from './constants';
|
||||
|
||||
import MainController from './controllers/mainController';
|
||||
import AzureResourceController from './controllers/azureResourceController';
|
||||
import { AppContext } from './appContext';
|
||||
import ControllerBase from './controllers/controllerBase';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
|
||||
|
||||
import { AzureResourceDatabaseServerProvider } from './azureResource/providers/databaseServer/databaseServerProvider';
|
||||
import { AzureResourceDatabaseServerService } from './azureResource/providers/databaseServer/databaseServerService';
|
||||
import { AzureResourceDatabaseProvider } from './azureResource/providers/database/databaseProvider';
|
||||
import { AzureResourceDatabaseService } from './azureResource/providers/database/databaseService';
|
||||
|
||||
let controllers: ControllerBase[] = [];
|
||||
|
||||
|
||||
@@ -35,7 +40,8 @@ export function getDefaultLogLocation() {
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
export function activate(extensionContext: vscode.ExtensionContext) {
|
||||
let appContext = new AppContext(extensionContext, new ApiWrapper());
|
||||
const apiWrapper = new ApiWrapper();
|
||||
let appContext = new AppContext(extensionContext, apiWrapper);
|
||||
let activations: Thenable<boolean>[] = [];
|
||||
|
||||
// Create the folder for storing the token caches
|
||||
@@ -56,21 +62,19 @@ export function activate(extensionContext: vscode.ExtensionContext) {
|
||||
extensionContext.subscriptions.push(accountProviderService);
|
||||
accountProviderService.activate();
|
||||
|
||||
// Start the main controller
|
||||
let mainController = new MainController(appContext);
|
||||
controllers.push(mainController);
|
||||
extensionContext.subscriptions.push(mainController);
|
||||
activations.push(mainController.activate());
|
||||
const azureResourceController = new AzureResourceController(appContext);
|
||||
controllers.push(azureResourceController);
|
||||
extensionContext.subscriptions.push(azureResourceController);
|
||||
activations.push(azureResourceController.activate());
|
||||
|
||||
return Promise.all(activations)
|
||||
.then((results: boolean[]) => {
|
||||
for (let result of results) {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return {
|
||||
provideResources() {
|
||||
return [
|
||||
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), apiWrapper, extensionContext),
|
||||
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext)
|
||||
];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
|
||||
@@ -9,8 +9,8 @@ import * as should from 'should';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
||||
import { AzureResourceItemType } from '../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../azureResource/messageTreeNode';
|
||||
|
||||
describe('AzureResourceMessageTreeNode.info', function(): void {
|
||||
it('Should be correct when created.', async function(): Promise<void> {
|
||||
@@ -0,0 +1,150 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { azureResource } from '../../../../azureResource/azure-resource';
|
||||
import { ApiWrapper } from '../../../../apiWrapper';
|
||||
import { IAzureResourceDatabaseService } from '../../../../azureResource/providers/database/interfaces';
|
||||
import { AzureResourceDatabaseTreeDataProvider } from '../../../../azureResource/providers/database/databaseTreeDataProvider';
|
||||
import { AzureResourceDatabase } from '../../../../azureResource/providers/database/models';
|
||||
import { AzureResourceItemType } from '../../../../azureResource/constants';
|
||||
|
||||
// Mock services
|
||||
let mockDatabaseService: TypeMoq.IMock<IAzureResourceDatabaseService>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
|
||||
// Mock test data
|
||||
const mockAccount: sqlops.Account = {
|
||||
key: {
|
||||
accountId: 'mock_account',
|
||||
providerId: 'mock_provider'
|
||||
},
|
||||
displayInfo: {
|
||||
displayName: 'mock_account@test.com',
|
||||
accountType: 'Microsoft',
|
||||
contextualDisplayName: 'test'
|
||||
},
|
||||
properties: undefined,
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||
id: 'mock_subscription',
|
||||
name: 'mock subscription'
|
||||
};
|
||||
|
||||
const mockTenantId: string = 'mock_tenant';
|
||||
|
||||
const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||
account: mockAccount,
|
||||
subscription: mockSubscription,
|
||||
tenantId: mockTenantId,
|
||||
treeItem: {
|
||||
id: 'mock_resource_root_node',
|
||||
label: 'mock resource root node',
|
||||
iconPath: undefined,
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: 'mock_resource_root_node'
|
||||
}
|
||||
};
|
||||
|
||||
const mockTokens = {};
|
||||
mockTokens[mockTenantId] = {
|
||||
token: 'mock_token',
|
||||
tokenType: 'Bearer'
|
||||
};
|
||||
|
||||
const mockDatabases: AzureResourceDatabase[] = [
|
||||
{
|
||||
name: 'mock database 1',
|
||||
serverName: 'mock database server 1',
|
||||
serverFullName: 'mock database server full name 1',
|
||||
loginName: 'mock login'
|
||||
},
|
||||
{
|
||||
name: 'mock database 2',
|
||||
serverName: 'mock database server 2',
|
||||
serverFullName: 'mock database server full name 2',
|
||||
loginName: 'mock login'
|
||||
}
|
||||
];
|
||||
|
||||
describe('AzureResourceDatabaseTreeDataProvider.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
});
|
||||
|
||||
it('Should be correct when created.', async function(): Promise<void> {
|
||||
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||
|
||||
const treeItem = await treeDataProvider.getTreeItem(mockResourceRootNode);
|
||||
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
|
||||
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
|
||||
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
|
||||
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceDatabaseTreeDataProvider.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
|
||||
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabases));
|
||||
mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString());
|
||||
});
|
||||
|
||||
it('Should return container node when element is undefined.', async function(): Promise<void> {
|
||||
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(1);
|
||||
|
||||
const child = children[0];
|
||||
should(child.account).undefined();
|
||||
should(child.subscription).undefined();
|
||||
should(child.tenantId).undefined();
|
||||
should(child.treeItem.id).equal('azure.resource.providers.database.treeDataProvider.databaseContainer');
|
||||
should(child.treeItem.label).equal('SQL Databases');
|
||||
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
should(child.treeItem.contextValue).equal('azure.resource.itemType.databaseContainer');
|
||||
});
|
||||
|
||||
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
|
||||
const treeDataProvider = new AzureResourceDatabaseTreeDataProvider(mockDatabaseService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||
|
||||
const children = await treeDataProvider.getChildren(mockResourceRootNode);
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(mockDatabases.length);
|
||||
|
||||
for (let ix = 0; ix < children.length; ix++) {
|
||||
const child = children[ix];
|
||||
const database = mockDatabases[ix];
|
||||
|
||||
should(child.account).equal(mockAccount);
|
||||
should(child.subscription).equal(mockSubscription);
|
||||
should(child.tenantId).equal(mockTenantId);
|
||||
should(child.treeItem.id).equal(`databaseServer_${database.serverFullName}.database_${database.name}`);
|
||||
should(child.treeItem.label).equal(`${database.name} (${database.serverName})`);
|
||||
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||
should(child.treeItem.contextValue).equal(AzureResourceItemType.database);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,150 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { azureResource } from '../../../../azureResource/azure-resource';
|
||||
import { ApiWrapper } from '../../../../apiWrapper';
|
||||
import { IAzureResourceDatabaseServerService } from '../../../../azureResource/providers/databaseServer/interfaces';
|
||||
import { AzureResourceDatabaseServerTreeDataProvider } from '../../../../azureResource/providers/databaseServer/databaseServerTreeDataProvider';
|
||||
import { AzureResourceDatabaseServer } from '../../../../azureResource/providers/databaseServer/models';
|
||||
import { AzureResourceItemType } from '../../../../azureResource/constants';
|
||||
|
||||
// Mock services
|
||||
let mockDatabaseServerService: TypeMoq.IMock<IAzureResourceDatabaseServerService>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
|
||||
// Mock test data
|
||||
const mockAccount: sqlops.Account = {
|
||||
key: {
|
||||
accountId: 'mock_account',
|
||||
providerId: 'mock_provider'
|
||||
},
|
||||
displayInfo: {
|
||||
displayName: 'mock_account@test.com',
|
||||
accountType: 'Microsoft',
|
||||
contextualDisplayName: 'test'
|
||||
},
|
||||
properties: undefined,
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||
id: 'mock_subscription',
|
||||
name: 'mock subscription'
|
||||
};
|
||||
|
||||
const mockTenantId: string = 'mock_tenant';
|
||||
|
||||
const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||
account: mockAccount,
|
||||
subscription: mockSubscription,
|
||||
tenantId: mockTenantId,
|
||||
treeItem: {
|
||||
id: 'mock_resource_root_node',
|
||||
label: 'mock resource root node',
|
||||
iconPath: undefined,
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: 'mock_resource_root_node'
|
||||
}
|
||||
};
|
||||
|
||||
const mockTokens = {};
|
||||
mockTokens[mockTenantId] = {
|
||||
token: 'mock_token',
|
||||
tokenType: 'Bearer'
|
||||
};
|
||||
|
||||
const mockDatabaseServers: AzureResourceDatabaseServer[] = [
|
||||
{
|
||||
name: 'mock database server 1',
|
||||
fullName: 'mock database server full name 1',
|
||||
loginName: 'mock login',
|
||||
defaultDatabaseName: 'master'
|
||||
},
|
||||
{
|
||||
name: 'mock database server 2',
|
||||
fullName: 'mock database server full name 2',
|
||||
loginName: 'mock login',
|
||||
defaultDatabaseName: 'master'
|
||||
}
|
||||
];
|
||||
|
||||
describe('AzureResourceDatabaseServerTreeDataProvider.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
});
|
||||
|
||||
it('Should be correct when created.', async function(): Promise<void> {
|
||||
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||
|
||||
const treeItem = await treeDataProvider.getTreeItem(mockResourceRootNode);
|
||||
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
|
||||
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
|
||||
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
|
||||
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceDatabaseServerTreeDataProvider.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
|
||||
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, TypeMoq.It.isAny())).returns(() => Promise.resolve(mockDatabaseServers));
|
||||
mockExtensionContext.setup((o) => o.asAbsolutePath(TypeMoq.It.isAnyString())).returns(() => TypeMoq.It.isAnyString());
|
||||
});
|
||||
|
||||
it('Should return container node when element is undefined.', async function(): Promise<void> {
|
||||
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||
|
||||
const children = await treeDataProvider.getChildren();
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(1);
|
||||
|
||||
const child = children[0];
|
||||
should(child.account).undefined();
|
||||
should(child.subscription).undefined();
|
||||
should(child.tenantId).undefined();
|
||||
should(child.treeItem.id).equal('azure.resource.providers.databaseServer.treeDataProvider.databaseServerContainer');
|
||||
should(child.treeItem.label).equal('SQL Servers');
|
||||
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
should(child.treeItem.contextValue).equal('azure.resource.itemType.databaseServerContainer');
|
||||
});
|
||||
|
||||
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
|
||||
const treeDataProvider = new AzureResourceDatabaseServerTreeDataProvider(mockDatabaseServerService.object, mockApiWrapper.object, mockExtensionContext.object);
|
||||
|
||||
const children = await treeDataProvider.getChildren(mockResourceRootNode);
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(mockDatabaseServers.length);
|
||||
|
||||
for (let ix = 0; ix < children.length; ix++) {
|
||||
const child = children[ix];
|
||||
const databaseServer = mockDatabaseServers[ix];
|
||||
|
||||
should(child.account).equal(mockAccount);
|
||||
should(child.subscription).equal(mockSubscription);
|
||||
should(child.tenantId).equal(mockTenantId);
|
||||
should(child.treeItem.id).equal(`databaseServer_${databaseServer.name}`);
|
||||
should(child.treeItem.label).equal(databaseServer.name);
|
||||
should(child.treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||
should(child.treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,180 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import 'mocha';
|
||||
import { fail } from 'assert';
|
||||
|
||||
import { azureResource } from '../../azureResource/azure-resource';
|
||||
import { AzureResourceService } from '../../azureResource/resourceService';
|
||||
|
||||
// Mock test data
|
||||
const mockAccount: sqlops.Account = {
|
||||
key: {
|
||||
accountId: 'mock_account',
|
||||
providerId: 'mock_provider'
|
||||
},
|
||||
displayInfo: {
|
||||
displayName: 'mock_account@test.com',
|
||||
accountType: 'Microsoft',
|
||||
contextualDisplayName: 'test'
|
||||
},
|
||||
properties: undefined,
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||
id: 'mock_subscription',
|
||||
name: 'mock subscription'
|
||||
};
|
||||
|
||||
const mockTenantId: string = 'mock_tenant';
|
||||
|
||||
let mockResourceTreeDataProvider1: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||
let mockResourceProvider1: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||
|
||||
let mockResourceTreeDataProvider2: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||
let mockResourceProvider2: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||
|
||||
const resourceService: AzureResourceService = AzureResourceService.getInstance();
|
||||
|
||||
describe('AzureResourceService.listResourceProviderIds', function(): void {
|
||||
beforeEach(() => {
|
||||
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||
|
||||
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
|
||||
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
|
||||
|
||||
resourceService.clearResourceProviders();
|
||||
resourceService.areResourceProvidersLoaded = true;
|
||||
});
|
||||
|
||||
it('Should be correct when registering providers.', async function(): Promise<void> {
|
||||
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||
let providerIds = await resourceService.listResourceProviderIds();
|
||||
should(providerIds).Array();
|
||||
should(providerIds.length).equal(1);
|
||||
should(providerIds[0]).equal(mockResourceProvider1.object.providerId);
|
||||
|
||||
resourceService.registerResourceProvider(mockResourceProvider2.object);
|
||||
providerIds = await resourceService.listResourceProviderIds();
|
||||
should(providerIds).Array();
|
||||
should(providerIds.length).equal(2);
|
||||
should(providerIds[0]).equal(mockResourceProvider1.object.providerId);
|
||||
should(providerIds[1]).equal(mockResourceProvider2.object.providerId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceService.getRootChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||
|
||||
resourceService.clearResourceProviders();
|
||||
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||
resourceService.areResourceProvidersLoaded = true;
|
||||
});
|
||||
|
||||
it('Should be correct when provider id is correct.', async function(): Promise<void> {
|
||||
const children = await resourceService.getRootChildren(mockResourceProvider1.object.providerId, mockAccount, mockSubscription, mockTenantId);
|
||||
|
||||
should(children).Array();
|
||||
});
|
||||
|
||||
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
|
||||
const providerId = 'non_existent_provider_id';
|
||||
try {
|
||||
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
|
||||
} catch (error) {
|
||||
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
fail();
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceService.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||
|
||||
resourceService.clearResourceProviders();
|
||||
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||
resourceService.areResourceProvidersLoaded = true;
|
||||
});
|
||||
|
||||
it('Should be correct when provider id is correct.', async function(): Promise<void> {
|
||||
const children = await resourceService.getChildren(mockResourceProvider1.object.providerId, TypeMoq.It.isAny());
|
||||
should(children).Array();
|
||||
});
|
||||
|
||||
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
|
||||
const providerId = 'non_existent_provider_id';
|
||||
try {
|
||||
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
|
||||
} catch (error) {
|
||||
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
fail();
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceService.getTreeItem', function(): void {
|
||||
beforeEach(() => {
|
||||
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||
|
||||
resourceService.clearResourceProviders();
|
||||
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||
resourceService.areResourceProvidersLoaded = true;
|
||||
});
|
||||
|
||||
it('Should be correct when provider id is correct.', async function(): Promise<void> {
|
||||
const treeItem = await resourceService.getTreeItem(mockResourceProvider1.object.providerId, TypeMoq.It.isAny());
|
||||
should(treeItem).Object();
|
||||
});
|
||||
|
||||
it('Should throw exceptions when provider id is incorrect.', async function(): Promise<void> {
|
||||
const providerId = 'non_existent_provider_id';
|
||||
try {
|
||||
await resourceService.getRootChildren(providerId, mockAccount, mockSubscription, mockTenantId);
|
||||
} catch (error) {
|
||||
should(error.message).equal(`Azure resource provider doesn't exist. Id: ${providerId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
fail();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,184 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { azureResource } from '../../azureResource/azure-resource';
|
||||
import { AzureResourceService } from '../../azureResource/resourceService';
|
||||
import { AzureResourceResourceTreeNode } from '../../azureResource/resourceTreeNode';
|
||||
|
||||
const resourceService = AzureResourceService.getInstance();
|
||||
|
||||
// Mock test data
|
||||
const mockAccount: sqlops.Account = {
|
||||
key: {
|
||||
accountId: 'mock_account',
|
||||
providerId: 'mock_provider'
|
||||
},
|
||||
displayInfo: {
|
||||
displayName: 'mock_account@test.com',
|
||||
accountType: 'Microsoft',
|
||||
contextualDisplayName: 'test'
|
||||
},
|
||||
properties: undefined,
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||
id: 'mock_subscription',
|
||||
name: 'mock subscription'
|
||||
};
|
||||
|
||||
const mockTenantId: string = 'mock_tenant';
|
||||
|
||||
const mockResourceProviderId: string = 'mock_resource_provider';
|
||||
|
||||
const mockResourceRootNode: azureResource.IAzureResourceNode = {
|
||||
account: mockAccount,
|
||||
subscription: mockSubscription,
|
||||
tenantId: mockTenantId,
|
||||
treeItem: {
|
||||
id: 'mock_resource_root_node',
|
||||
label: 'mock resource root node',
|
||||
iconPath: undefined,
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: 'mock_resource_root_node'
|
||||
}
|
||||
};
|
||||
|
||||
const mockResourceNode1: azureResource.IAzureResourceNode = {
|
||||
account: mockAccount,
|
||||
subscription: mockSubscription,
|
||||
tenantId: mockTenantId,
|
||||
treeItem: {
|
||||
id: 'mock_resource_node_1',
|
||||
label: 'mock resource node 1',
|
||||
iconPath: undefined,
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.None,
|
||||
contextValue: 'mock_resource_node'
|
||||
}
|
||||
};
|
||||
|
||||
const mockResourceNode2: azureResource.IAzureResourceNode = {
|
||||
account: mockAccount,
|
||||
subscription: mockSubscription,
|
||||
tenantId: mockTenantId,
|
||||
treeItem: {
|
||||
id: 'mock_resource_node_2',
|
||||
label: 'mock resource node 2',
|
||||
iconPath: undefined,
|
||||
collapsibleState: vscode.TreeItemCollapsibleState.None,
|
||||
contextValue: 'mock_resource_node'
|
||||
}
|
||||
};
|
||||
|
||||
const mockResourceNodes: azureResource.IAzureResourceNode[] = [mockResourceNode1, mockResourceNode2];
|
||||
|
||||
let mockResourceTreeDataProvider: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||
let mockResourceProvider: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||
|
||||
describe('AzureResourceResourceTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider.setup((o) => o.getTreeItem(mockResourceRootNode)).returns(() => mockResourceRootNode.treeItem);
|
||||
mockResourceTreeDataProvider.setup((o) => o.getChildren(mockResourceRootNode)).returns(() => Promise.resolve(mockResourceNodes));
|
||||
|
||||
mockResourceProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider.setup((o) => o.providerId).returns(() => mockResourceProviderId);
|
||||
mockResourceProvider.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider.object);
|
||||
|
||||
resourceService.clearResourceProviders();
|
||||
resourceService.registerResourceProvider(mockResourceProvider.object);
|
||||
|
||||
resourceService.areResourceProvidersLoaded = true;
|
||||
});
|
||||
|
||||
it('Should be correct when created.', async function(): Promise<void> {
|
||||
const resourceTreeNode = new AzureResourceResourceTreeNode({
|
||||
resourceProviderId: mockResourceProviderId,
|
||||
resourceNode: mockResourceRootNode
|
||||
}, undefined);
|
||||
|
||||
should(resourceTreeNode.nodePathValue).equal(mockResourceRootNode.treeItem.id);
|
||||
|
||||
const treeItem = await resourceTreeNode.getTreeItem();
|
||||
should(treeItem.id).equal(mockResourceRootNode.treeItem.id);
|
||||
should(treeItem.label).equal(mockResourceRootNode.treeItem.label);
|
||||
should(treeItem.collapsibleState).equal(mockResourceRootNode.treeItem.collapsibleState);
|
||||
should(treeItem.contextValue).equal(mockResourceRootNode.treeItem.contextValue);
|
||||
|
||||
const nodeInfo = resourceTreeNode.getNodeInfo();
|
||||
should(nodeInfo.label).equal(mockResourceRootNode.treeItem.label);
|
||||
should(nodeInfo.isLeaf).equal(mockResourceRootNode.treeItem.collapsibleState === vscode.TreeItemCollapsibleState.None);
|
||||
should(nodeInfo.nodeType).equal(mockResourceRootNode.treeItem.contextValue);
|
||||
should(nodeInfo.iconType).equal(mockResourceRootNode.treeItem.contextValue);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceResourceTreeNode.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider.setup((o) => o.getChildren(mockResourceRootNode)).returns(() => Promise.resolve(mockResourceNodes));
|
||||
|
||||
mockResourceProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider.setup((o) => o.providerId).returns(() => mockResourceProviderId);
|
||||
mockResourceProvider.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider.object);
|
||||
|
||||
resourceService.clearResourceProviders();
|
||||
resourceService.registerResourceProvider(mockResourceProvider.object);
|
||||
|
||||
resourceService.areResourceProvidersLoaded = true;
|
||||
});
|
||||
|
||||
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
|
||||
const resourceTreeNode = new AzureResourceResourceTreeNode({
|
||||
resourceProviderId: mockResourceProviderId,
|
||||
resourceNode: mockResourceRootNode
|
||||
}, undefined);
|
||||
|
||||
const children = await resourceTreeNode.getChildren();
|
||||
|
||||
mockResourceTreeDataProvider.verify((o) => o.getChildren(mockResourceRootNode), TypeMoq.Times.once());
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(mockResourceNodes.length);
|
||||
|
||||
for (let ix = 0; ix < children.length; ix++) {
|
||||
const child = children[ix];
|
||||
|
||||
should(child).instanceOf(AzureResourceResourceTreeNode);
|
||||
|
||||
const childNode = (child as AzureResourceResourceTreeNode).resourceNodeWithProviderId;
|
||||
should(childNode.resourceProviderId).equal(mockResourceProviderId);
|
||||
should(childNode.resourceNode.account).equal(mockAccount);
|
||||
should(childNode.resourceNode.subscription).equal(mockSubscription);
|
||||
should(childNode.resourceNode.tenantId).equal(mockTenantId);
|
||||
should(childNode.resourceNode.treeItem.id).equal(mockResourceNodes[ix].treeItem.id);
|
||||
should(childNode.resourceNode.treeItem.label).equal(mockResourceNodes[ix].treeItem.label);
|
||||
should(childNode.resourceNode.treeItem.collapsibleState).equal(mockResourceNodes[ix].treeItem.collapsibleState);
|
||||
should(childNode.resourceNode.treeItem.contextValue).equal(mockResourceNodes[ix].treeItem.contextValue);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should return empty when it is leaf node.', async function(): Promise<void> {
|
||||
const resourceTreeNode = new AzureResourceResourceTreeNode({
|
||||
resourceProviderId: mockResourceProviderId,
|
||||
resourceNode: mockResourceNode1
|
||||
}, undefined);
|
||||
|
||||
const children = await resourceTreeNode.getChildren();
|
||||
|
||||
mockResourceTreeDataProvider.verify((o) => o.getChildren(), TypeMoq.Times.exactly(0));
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(0);
|
||||
});
|
||||
});
|
||||
@@ -26,7 +26,7 @@ describe('AzureResourceAccountNotSignedInTreeNode.info', function(): void {
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||
should(treeItem.command).not.undefined();
|
||||
should(treeItem.command.title).equal(label);
|
||||
should(treeItem.command.command).equal('azureresource.signin');
|
||||
should(treeItem.command.command).equal('azure.resource.signin');
|
||||
|
||||
const nodeInfo = treeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).true();
|
||||
|
||||
@@ -10,35 +10,38 @@ import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import { AppContext } from '../../../appContext';
|
||||
|
||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
||||
import { azureResource } from '../../../azureResource/azure-resource';
|
||||
import {
|
||||
IAzureResourceCacheService,
|
||||
IAzureResourceContextService,
|
||||
IAzureResourceCredentialService,
|
||||
IAzureResourceSubscriptionService,
|
||||
IAzureResourceSubscriptionFilterService
|
||||
IAzureResourceSubscriptionFilterService,
|
||||
IAzureResourceTenantService
|
||||
} from '../../../azureResource/interfaces';
|
||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
import { generateGuid } from '../../../azureResource/utils';
|
||||
|
||||
// Mock services
|
||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
||||
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
||||
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
||||
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
|
||||
let mockTenantService: TypeMoq.IMock<IAzureResourceTenantService>;
|
||||
let mockAppContext: AppContext;
|
||||
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||
|
||||
// Mock test data
|
||||
const mockTenantId = 'mock_tenant_id';
|
||||
|
||||
const mockAccount: sqlops.Account = {
|
||||
key: {
|
||||
accountId: 'mock_account',
|
||||
@@ -49,51 +52,68 @@ const mockAccount: sqlops.Account = {
|
||||
accountType: 'Microsoft',
|
||||
contextualDisplayName: 'test'
|
||||
},
|
||||
properties: undefined,
|
||||
properties: {
|
||||
tenants: [
|
||||
{
|
||||
id: mockTenantId
|
||||
}
|
||||
]
|
||||
},
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
||||
const mockCredentials = [mockCredential];
|
||||
|
||||
const mockSubscription1: AzureResourceSubscription = {
|
||||
const mockSubscription1: azureResource.AzureResourceSubscription = {
|
||||
id: 'mock_subscription_1',
|
||||
name: 'mock subscription 1'
|
||||
};
|
||||
const mockSubscription2: AzureResourceSubscription = {
|
||||
|
||||
const mockSubscription2: azureResource.AzureResourceSubscription = {
|
||||
id: 'mock_subscription_2',
|
||||
name: 'mock subscription 2'
|
||||
};
|
||||
|
||||
const mockSubscriptions = [mockSubscription1, mockSubscription2];
|
||||
|
||||
const mockFilteredSubscriptions = [mockSubscription1];
|
||||
|
||||
let mockSubscriptionCache: { subscriptions: { [accountId: string]: AzureResourceSubscription[]} };
|
||||
const mockTokens = {};
|
||||
mockTokens[mockTenantId] = {
|
||||
token: 'mock_token',
|
||||
tokenType: 'Bearer'
|
||||
};
|
||||
|
||||
const mockCredential = new TokenCredentials(mockTokens[mockTenantId].token, mockTokens[mockTenantId].tokenType);
|
||||
|
||||
let mockSubscriptionCache: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
describe('AzureResourceAccountTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockSubscriptionCache = { subscriptions: {} };
|
||||
mockSubscriptionCache = [];
|
||||
|
||||
mockServicePool.contextService = mockContextService.object;
|
||||
mockServicePool.cacheService = mockCacheService.object;
|
||||
mockServicePool.credentialService = mockCredentialService.object;
|
||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
|
||||
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
||||
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache.subscriptions[mockAccount.key.accountId] = mockSubscriptions);
|
||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
|
||||
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
|
||||
});
|
||||
|
||||
it('Should be correct when created.', async function(): Promise<void> {
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const accountTreeNodeId = `account_${mockAccount.key.accountId}`;
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId})`;
|
||||
@@ -114,14 +134,17 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
||||
});
|
||||
|
||||
it('Should be correct when there are subscriptions listed.', async function(): Promise<void> {
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
||||
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
await accountTreeNode.getChildren();
|
||||
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||
|
||||
should(subscriptionNodes).Array();
|
||||
should(subscriptionNodes.length).equal(mockSubscriptions.length);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
@@ -131,14 +154,17 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
||||
});
|
||||
|
||||
it('Should be correct when there are subscriptions filtered.', async function(): Promise<void> {
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||
|
||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
await accountTreeNode.getChildren();
|
||||
const subscriptionNodes = await accountTreeNode.getChildren();
|
||||
|
||||
should(subscriptionNodes).Array();
|
||||
should(subscriptionNodes.length).equal(mockFilteredSubscriptions.length);
|
||||
|
||||
const treeItem = await accountTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||
@@ -150,36 +176,41 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
||||
|
||||
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockSubscriptionCache = { subscriptions: {} };
|
||||
mockSubscriptionCache = [];
|
||||
|
||||
mockServicePool.cacheService = mockCacheService.object;
|
||||
mockServicePool.credentialService = mockCredentialService.object;
|
||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
|
||||
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
||||
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache.subscriptions[mockAccount.key.accountId] = mockSubscriptions);
|
||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
|
||||
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
|
||||
});
|
||||
|
||||
it('Should load subscriptions from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve([]));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(0));
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||
|
||||
@@ -192,43 +223,42 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
should(children).Array();
|
||||
should(children.length).equal(mockSubscriptions.length);
|
||||
|
||||
should(Object.keys(mockSubscriptionCache.subscriptions)).deepEqual([mockAccount.key.accountId]);
|
||||
should(mockSubscriptionCache.subscriptions[mockAccount.key.accountId]).deepEqual(mockSubscriptions);
|
||||
should(mockSubscriptionCache).deepEqual(mockSubscriptions);
|
||||
|
||||
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
|
||||
const child = children[ix];
|
||||
const subscription = mockSubscriptions[ix];
|
||||
|
||||
should(child).instanceof(AzureResourceSubscriptionTreeNode);
|
||||
should(child.nodePathValue).equal(`subscription_${subscription.id}`);
|
||||
should(child.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${subscription.id}.tenant_${mockTenantId}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should load subscriptions from cache when it is not clearing cache.', async function(): Promise<void> {
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
await accountTreeNode.getChildren();
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.exactly(1));
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
||||
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
should(children.length).equal(mockSubscriptionCache.subscriptions[mockAccount.key.accountId].length);
|
||||
should(children.length).equal(mockSubscriptionCache.length);
|
||||
|
||||
for (let ix = 0; ix < mockSubscriptionCache.subscriptions[mockAccount.key.accountId].length; ix++) {
|
||||
should(children[ix].nodePathValue).equal(`subscription_${mockSubscriptionCache.subscriptions[mockAccount.key.accountId][ix].id}`);
|
||||
for (let ix = 0; ix < mockSubscriptionCache.length; ix++) {
|
||||
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscriptionCache[ix].id}.tenant_${mockTenantId}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should handle when there is no subscriptions.', async function(): Promise<void> {
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(undefined));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(undefined));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
@@ -242,10 +272,10 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
});
|
||||
|
||||
it('Should honor subscription filtering.', async function(): Promise<void> {
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
@@ -255,23 +285,25 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
should(children.length).equal(mockFilteredSubscriptions.length);
|
||||
|
||||
for (let ix = 0; ix < mockFilteredSubscriptions.length; ix++) {
|
||||
should(children[ix].nodePathValue).equal(`subscription_${mockFilteredSubscriptions[ix].id}`);
|
||||
should(children[ix].nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockFilteredSubscriptions[ix].id}.tenant_${mockTenantId}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should handle errors.', async function(): Promise<void> {
|
||||
const mockError = 'Test error';
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => { throw new Error(mockError); });
|
||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const mockError = 'Test error';
|
||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => { throw new Error(mockError); });
|
||||
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
||||
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.never());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(1);
|
||||
@@ -283,12 +315,33 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
|
||||
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockSubscriptionCache = [];
|
||||
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||
mockAppContext.registerService<IAzureResourceTenantService>(AzureResourceServiceNames.tenantService, mockTenantService.object);
|
||||
|
||||
mockApiWrapper.setup((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockTokens));
|
||||
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockSubscriptionCache);
|
||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockSubscriptionCache = mockSubscriptions);
|
||||
mockTenantService.setup((o) => o.getTenantId(TypeMoq.It.isAny())).returns(() => Promise.resolve(mockTenantId));
|
||||
});
|
||||
|
||||
it('Should clear cache.', async function(): Promise<void> {
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockTreeChangeHandler.object);
|
||||
const accountTreeNode = new AzureResourceAccountTreeNode(mockAccount, mockAppContext, mockTreeChangeHandler.object);
|
||||
accountTreeNode.clearCache();
|
||||
should(accountTreeNode.isClearingCache).true();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,219 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
|
||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
||||
import {
|
||||
IAzureResourceCacheService,
|
||||
IAzureResourceContextService,
|
||||
IAzureResourceCredentialService,
|
||||
IAzureResourceDatabaseService
|
||||
} from '../../../azureResource/interfaces';
|
||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||
import { AzureResourceSubscription, AzureResourceDatabase } from '../../../azureResource/models';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
||||
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
|
||||
import { AzureResourceDatabaseTreeNode } from '../../../azureResource/tree/databaseTreeNode';
|
||||
|
||||
// Mock services
|
||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
||||
|
||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
||||
let mockDatabaseService: TypeMoq.IMock<IAzureResourceDatabaseService>;
|
||||
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||
|
||||
// Mock test data
|
||||
const mockAccount: sqlops.Account = {
|
||||
key: {
|
||||
accountId: 'mock_account',
|
||||
providerId: 'mock_provider'
|
||||
},
|
||||
displayInfo: {
|
||||
displayName: 'mock_account@test.com',
|
||||
accountType: 'Microsoft',
|
||||
contextualDisplayName: 'test'
|
||||
},
|
||||
properties: undefined,
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
||||
const mockCredentials = [mockCredential];
|
||||
|
||||
const mockSubscription: AzureResourceSubscription = {
|
||||
id: 'mock_subscription',
|
||||
name: 'mock subscription'
|
||||
};
|
||||
|
||||
const mockDatabase1: AzureResourceDatabase = {
|
||||
name: 'mock database 1',
|
||||
serverName: 'mock server 1',
|
||||
serverFullName: 'mock server 1',
|
||||
loginName: 'mock user 1'
|
||||
};
|
||||
const mockDatabase2: AzureResourceDatabase = {
|
||||
name: 'mock database 2',
|
||||
serverName: 'mock server 2',
|
||||
serverFullName: 'mock server 2',
|
||||
loginName: 'mock user 2'
|
||||
};
|
||||
const mockDatabases = [mockDatabase1, mockDatabase2];
|
||||
|
||||
let mockDatabaseContainerCache: { databases: { [subscriptionId: string]: AzureResourceDatabase[] } };
|
||||
|
||||
describe('AzureResourceDatabaseContainerTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockServicePool.contextService = mockContextService.object;
|
||||
});
|
||||
|
||||
it('Should be correct when created.', async function(): Promise<void> {
|
||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
const databaseContainerTreeNodeLabel = 'SQL Databases';
|
||||
|
||||
should(databaseContainerTreeNode.nodePathValue).equal('databaseContainer');
|
||||
|
||||
const treeItem = await databaseContainerTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(databaseContainerTreeNodeLabel);
|
||||
should(treeItem.contextValue).equal(AzureResourceItemType.databaseContainer);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
|
||||
const nodeInfo = databaseContainerTreeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).false();
|
||||
should(nodeInfo.label).equal(databaseContainerTreeNodeLabel);
|
||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseContainer);
|
||||
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseContainer);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceDatabaseContainerTreeNode.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
||||
mockDatabaseService = TypeMoq.Mock.ofType<IAzureResourceDatabaseService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockDatabaseContainerCache = { databases: {} };
|
||||
|
||||
mockServicePool.cacheService = mockCacheService.object;
|
||||
mockServicePool.credentialService = mockCredentialService.object;
|
||||
mockServicePool.databaseService = mockDatabaseService.object;
|
||||
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockDatabaseContainerCache);
|
||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockDatabaseContainerCache.databases[mockSubscription.id] = mockDatabases);
|
||||
});
|
||||
|
||||
it('Should load databases from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabases));
|
||||
|
||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
const children = await databaseContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
should(databaseContainerTreeNode.isClearingCache).false();
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(mockDatabases.length);
|
||||
|
||||
should(Object.keys(mockDatabaseContainerCache.databases)).deepEqual([mockSubscription.id]);
|
||||
should(mockDatabaseContainerCache.databases[mockSubscription.id]).deepEqual(mockDatabases);
|
||||
|
||||
for (let ix = 0; ix < mockDatabases.length; ix++) {
|
||||
const child = children[ix];
|
||||
const database = mockDatabases[ix];
|
||||
|
||||
should(child).instanceof(AzureResourceDatabaseTreeNode);
|
||||
should(child.nodePathValue).equal(`database_${database.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should load databases from cache when it is not clearing cache.', async function(): Promise<void> {
|
||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabases));
|
||||
|
||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
await databaseContainerTreeNode.getChildren();
|
||||
const children = await databaseContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
|
||||
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.exactly(1));
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
||||
|
||||
should(children.length).equal(mockDatabaseContainerCache.databases[mockSubscription.id].length);
|
||||
|
||||
for (let ix = 0; ix < mockDatabaseContainerCache.databases[mockSubscription.id].length; ix++) {
|
||||
should(children[ix].nodePathValue).equal(`database_${mockDatabaseContainerCache.databases[mockSubscription.id][ix].name}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should handle when there is no databases.', async function(): Promise<void> {
|
||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => Promise.resolve(undefined));
|
||||
|
||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
const children = await databaseContainerTreeNode.getChildren();
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(1);
|
||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
||||
should(children[0].nodePathValue).startWith('message_');
|
||||
should(children[0].getNodeInfo().label).equal('No SQL Databases found.');
|
||||
});
|
||||
|
||||
it('Should handle errors.', async function(): Promise<void> {
|
||||
const mockError = 'Test error';
|
||||
mockDatabaseService.setup((o) => o.getDatabases(mockSubscription, mockCredentials)).returns(() => { throw new Error(mockError); });
|
||||
|
||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
const children = await databaseContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockDatabaseService.verify((o) => o.getDatabases(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(1);
|
||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
||||
should(children[0].nodePathValue).startWith('message_');
|
||||
should(children[0].getNodeInfo().label).equal(`Error: ${mockError}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceDatabaseContainerTreeNode.clearCache', function() : void {
|
||||
beforeEach(() => {
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
});
|
||||
|
||||
it('Should clear cache.', async function(): Promise<void> {
|
||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
databaseContainerTreeNode.clearCache();
|
||||
should(databaseContainerTreeNode.isClearingCache).true();
|
||||
});
|
||||
});
|
||||
@@ -1,219 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
|
||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
||||
import {
|
||||
IAzureResourceCacheService,
|
||||
IAzureResourceContextService,
|
||||
IAzureResourceCredentialService,
|
||||
IAzureResourceDatabaseServerService
|
||||
} from '../../../azureResource/interfaces';
|
||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../../../azureResource/models';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
||||
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
|
||||
import { AzureResourceDatabaseServerTreeNode } from '../../../azureResource/tree/databaseServerTreeNode';
|
||||
|
||||
// Mock services
|
||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
||||
|
||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
||||
let mockDatabaseServerService: TypeMoq.IMock<IAzureResourceDatabaseServerService>;
|
||||
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||
|
||||
// Mock test data
|
||||
const mockAccount: sqlops.Account = {
|
||||
key: {
|
||||
accountId: 'mock_account',
|
||||
providerId: 'mock_provider'
|
||||
},
|
||||
displayInfo: {
|
||||
displayName: 'mock_account@test.com',
|
||||
accountType: 'Microsoft',
|
||||
contextualDisplayName: 'test'
|
||||
},
|
||||
properties: undefined,
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
||||
const mockCredentials = [mockCredential];
|
||||
|
||||
const mockSubscription: AzureResourceSubscription = {
|
||||
id: 'mock_subscription',
|
||||
name: 'mock subscription'
|
||||
};
|
||||
|
||||
const mockDatabaseServer1: AzureResourceDatabaseServer = {
|
||||
name: 'mock server 1',
|
||||
fullName: 'mock server 1',
|
||||
loginName: 'mock user 1',
|
||||
defaultDatabaseName: 'master'
|
||||
};
|
||||
const mockDatabaseServer2: AzureResourceDatabaseServer = {
|
||||
name: 'mock server 2',
|
||||
fullName: 'mock server 2',
|
||||
loginName: 'mock user 2',
|
||||
defaultDatabaseName: 'master'
|
||||
};
|
||||
const mockDatabaseServers = [mockDatabaseServer1, mockDatabaseServer2];
|
||||
|
||||
let mockDatabaseServerContainerCache: { databaseServers: { [subscriptionId: string]: AzureResourceDatabaseServer[] } };
|
||||
|
||||
describe('AzureResourceDatabaseServerContainerTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockServicePool.contextService = mockContextService.object;
|
||||
});
|
||||
|
||||
it('Should be correct when created.', async function(): Promise<void> {
|
||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
const databaseServerContainerTreeNodeLabel = 'SQL Servers';
|
||||
|
||||
should(databaseServerContainerTreeNode.nodePathValue).equal('databaseServerContainer');
|
||||
|
||||
const treeItem = await databaseServerContainerTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(databaseServerContainerTreeNodeLabel);
|
||||
should(treeItem.contextValue).equal(AzureResourceItemType.databaseServerContainer);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
|
||||
const nodeInfo = databaseServerContainerTreeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).false();
|
||||
should(nodeInfo.label).equal(databaseServerContainerTreeNodeLabel);
|
||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseServerContainer);
|
||||
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseServerContainer);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceDatabaseServerContainerTreeNode.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
||||
mockDatabaseServerService = TypeMoq.Mock.ofType<IAzureResourceDatabaseServerService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockDatabaseServerContainerCache = { databaseServers: {} };
|
||||
|
||||
mockServicePool.cacheService = mockCacheService.object;
|
||||
mockServicePool.credentialService = mockCredentialService.object;
|
||||
mockServicePool.databaseServerService = mockDatabaseServerService.object;
|
||||
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
||||
mockCacheService.setup((o) => o.get(TypeMoq.It.isAnyString())).returns(() => mockDatabaseServerContainerCache);
|
||||
mockCacheService.setup((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny())).returns(() => mockDatabaseServerContainerCache.databaseServers[mockSubscription.id] = mockDatabaseServers);
|
||||
});
|
||||
|
||||
it('Should load database servers from scratch and update cache when it is clearing cache.', async function(): Promise<void> {
|
||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabaseServers));
|
||||
|
||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
const children = await databaseServerContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
should(databaseServerContainerTreeNode.isClearingCache).false();
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(mockDatabaseServers.length);
|
||||
|
||||
should(Object.keys(mockDatabaseServerContainerCache.databaseServers)).deepEqual([mockSubscription.id]);
|
||||
should(mockDatabaseServerContainerCache.databaseServers[mockSubscription.id]).deepEqual(mockDatabaseServers);
|
||||
|
||||
for (let ix = 0; ix < mockDatabaseServers.length; ix++) {
|
||||
const child = children[ix];
|
||||
const databaseServer = mockDatabaseServers[ix];
|
||||
|
||||
should(child).instanceof(AzureResourceDatabaseServerTreeNode);
|
||||
should(child.nodePathValue).equal(`databaseServer_${databaseServer.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should load database servers from cache when it is not clearing cache.', async function(): Promise<void> {
|
||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(mockDatabaseServers));
|
||||
|
||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
await databaseServerContainerTreeNode.getChildren();
|
||||
const children = await databaseServerContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
|
||||
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.exactly(1));
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
||||
|
||||
should(children.length).equal(mockDatabaseServerContainerCache.databaseServers[mockSubscription.id].length);
|
||||
|
||||
for (let ix = 0; ix < mockDatabaseServerContainerCache.databaseServers[mockSubscription.id].length; ix++) {
|
||||
should(children[ix].nodePathValue).equal(`databaseServer_${mockDatabaseServerContainerCache.databaseServers[mockSubscription.id][ix].name}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('Should handle when there is no database servers.', async function(): Promise<void> {
|
||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => Promise.resolve(undefined));
|
||||
|
||||
const databaseContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
const children = await databaseContainerTreeNode.getChildren();
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(1);
|
||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
||||
should(children[0].nodePathValue).startWith('message_');
|
||||
should(children[0].getNodeInfo().label).equal('No SQL Servers found.');
|
||||
});
|
||||
|
||||
it('Should handle errors.', async function(): Promise<void> {
|
||||
const mockError = 'Test error';
|
||||
mockDatabaseServerService.setup((o) => o.getDatabaseServers(mockSubscription, mockCredentials)).returns(() => { throw new Error(mockError); });
|
||||
|
||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
const children = await databaseServerContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockDatabaseServerService.verify((o) => o.getDatabaseServers(mockSubscription, mockCredentials), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(1);
|
||||
should(children[0]).instanceof(AzureResourceMessageTreeNode);
|
||||
should(children[0].nodePathValue).startWith('message_');
|
||||
should(children[0].getNodeInfo().label).equal(`Error: ${mockError}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AzureResourceDatabaseServerContainerTreeNode.clearCache', function() : void {
|
||||
beforeEach(() => {
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
});
|
||||
|
||||
it('Should clear cache.', async function(): Promise<void> {
|
||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
databaseServerContainerTreeNode.clearCache();
|
||||
should(databaseServerContainerTreeNode.isClearingCache).true();
|
||||
});
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||
import { AzureResourceDatabaseServer } from '../../../azureResource/models';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceDatabaseServerTreeNode } from '../../../azureResource/tree/databaseServerTreeNode';
|
||||
|
||||
// Mock services
|
||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
||||
|
||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
||||
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||
|
||||
// Mock test data
|
||||
const mockDatabaseServer: AzureResourceDatabaseServer = {
|
||||
name: 'mock database 1',
|
||||
fullName: 'mock server 1',
|
||||
loginName: 'mock user 1',
|
||||
defaultDatabaseName: 'master'
|
||||
};
|
||||
|
||||
describe('AzureResourceDatabaseServerTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockServicePool.contextService = mockContextService.object;
|
||||
});
|
||||
|
||||
it('Should be correct when created.', async function(): Promise<void> {
|
||||
const databaseServerTreeNode = new AzureResourceDatabaseServerTreeNode(mockDatabaseServer, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
const databaseServerTreeNodeLabel = mockDatabaseServer.name;
|
||||
|
||||
should(databaseServerTreeNode.nodePathValue).equal(`databaseServer_${mockDatabaseServer.name}`);
|
||||
|
||||
const treeItem = await databaseServerTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(databaseServerTreeNodeLabel);
|
||||
should(treeItem.contextValue).equal(AzureResourceItemType.databaseServer);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||
|
||||
const nodeInfo = databaseServerTreeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).true();
|
||||
should(nodeInfo.label).equal(databaseServerTreeNodeLabel);
|
||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.databaseServer);
|
||||
should(nodeInfo.iconType).equal(AzureResourceItemType.databaseServer);
|
||||
});
|
||||
});
|
||||
@@ -1,62 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||
import { AzureResourceDatabase } from '../../../azureResource/models';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceDatabaseTreeNode } from '../../../azureResource/tree/databaseTreeNode';
|
||||
|
||||
// Mock services
|
||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
||||
|
||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
||||
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||
|
||||
// Mock test data
|
||||
const mockDatabase: AzureResourceDatabase = {
|
||||
name: 'mock database 1',
|
||||
serverName: 'mock server 1',
|
||||
serverFullName: 'mock server 1',
|
||||
loginName: 'mock user 1'
|
||||
};
|
||||
|
||||
describe('AzureResourceDatabaseTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockServicePool.contextService = mockContextService.object;
|
||||
});
|
||||
|
||||
it('Should be correct.', async function(): Promise<void> {
|
||||
const databaseTreeNode = new AzureResourceDatabaseTreeNode(mockDatabase, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
const databaseTreeNodeLabel = `${mockDatabase.name} (${mockDatabase.serverName})`;
|
||||
|
||||
should(databaseTreeNode.nodePathValue).equal(`database_${mockDatabase.name}`);
|
||||
|
||||
const treeItem = await databaseTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(databaseTreeNodeLabel);
|
||||
should(treeItem.contextValue).equal(AzureResourceItemType.database);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||
|
||||
const nodeInfo = databaseTreeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).true();
|
||||
should(nodeInfo.label).equal(databaseTreeNodeLabel);
|
||||
should(nodeInfo.nodeType).equal(AzureResourceItemType.database);
|
||||
should(nodeInfo.iconType).equal(AzureResourceItemType.database);
|
||||
});
|
||||
});
|
||||
@@ -10,20 +10,24 @@ import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { AppContext } from '../../../appContext';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
||||
import { azureResource } from '../../../azureResource/azure-resource';
|
||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
|
||||
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
|
||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
||||
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||
import { AzureResourceService } from '../../../azureResource/resourceService';
|
||||
import { AzureResourceResourceTreeNode } from '../../../azureResource/resourceTreeNode';
|
||||
import { IAzureResourceCacheService } from '../../../azureResource/interfaces';
|
||||
import { generateGuid } from '../../../azureResource/utils';
|
||||
|
||||
// Mock services
|
||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
||||
let mockAppContext: AppContext;
|
||||
|
||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||
|
||||
@@ -42,24 +46,60 @@ const mockAccount: sqlops.Account = {
|
||||
isStale: false
|
||||
};
|
||||
|
||||
const mockSubscription: AzureResourceSubscription = {
|
||||
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||
id: 'mock_subscription',
|
||||
name: 'mock subscription'
|
||||
};
|
||||
|
||||
const mockTenantId: string = 'mock_tenant';
|
||||
|
||||
let mockResourceTreeDataProvider1: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||
let mockResourceProvider1: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||
|
||||
let mockResourceTreeDataProvider2: TypeMoq.IMock<azureResource.IAzureResourceTreeDataProvider>;
|
||||
let mockResourceProvider2: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
|
||||
|
||||
const resourceService: AzureResourceService = AzureResourceService.getInstance();
|
||||
|
||||
describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
|
||||
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
|
||||
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockServicePool.contextService = mockContextService.object;
|
||||
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||
|
||||
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
|
||||
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
|
||||
|
||||
resourceService.clearResourceProviders();
|
||||
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||
resourceService.registerResourceProvider(mockResourceProvider2.object);
|
||||
|
||||
resourceService.areResourceProvidersLoaded = true;
|
||||
});
|
||||
|
||||
it('Should be correct when created.', async function(): Promise<void> {
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
|
||||
|
||||
should(subscriptionTreeNode.nodePathValue).equal(`subscription_${mockSubscription.id}`);
|
||||
should(subscriptionTreeNode.nodePathValue).equal(`account_${mockAccount.key.accountId}.subscription_${mockSubscription.id}.tenant_${mockTenantId}`);
|
||||
|
||||
const treeItem = await subscriptionTreeNode.getTreeItem();
|
||||
should(treeItem.label).equal(mockSubscription.name);
|
||||
@@ -76,16 +116,52 @@ describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
||||
|
||||
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
|
||||
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||
|
||||
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider1 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||
|
||||
mockResourceTreeDataProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider2.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<azureResource.IAzureResourceNode>().object]));
|
||||
mockResourceTreeDataProvider2.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider2 = TypeMoq.Mock.ofType<azureResource.IAzureResourceProvider>();
|
||||
mockResourceProvider2.setup((o) => o.providerId).returns(() => 'mockResourceProvider2');
|
||||
mockResourceProvider2.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider2.object);
|
||||
|
||||
resourceService.clearResourceProviders();
|
||||
resourceService.registerResourceProvider(mockResourceProvider1.object);
|
||||
resourceService.registerResourceProvider(mockResourceProvider2.object);
|
||||
|
||||
resourceService.areResourceProvidersLoaded = true;
|
||||
});
|
||||
|
||||
it('Should load database containers.', async function(): Promise<void> {
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
it('Should return resource containers.', async function(): Promise<void> {
|
||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
|
||||
const children = await subscriptionTreeNode.getChildren();
|
||||
|
||||
mockResourceTreeDataProvider1.verify((o) => o.getChildren(), TypeMoq.Times.once());
|
||||
|
||||
mockResourceTreeDataProvider2.verify((o) => o.getChildren(), TypeMoq.Times.once());
|
||||
|
||||
const expectedChildren = await resourceService.listResourceProviderIds();
|
||||
|
||||
should(children).Array();
|
||||
should(children.length).equal(2);
|
||||
should(children[0]).instanceof(AzureResourceDatabaseContainerTreeNode);
|
||||
should(children[1]).instanceof(AzureResourceDatabaseServerContainerTreeNode);
|
||||
should(children.length).equal(expectedChildren.length);
|
||||
for (const child of children) {
|
||||
should(child).instanceOf(AzureResourceResourceTreeNode);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,21 +5,28 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as sqlops from 'sqlops';
|
||||
import 'mocha';
|
||||
import { AppContext } from '../../../appContext';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
||||
import { IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
||||
import { IAzureResourceCacheService, IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
||||
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
|
||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||
import { AzureResourceAccountNotSignedInTreeNode } from '../../../azureResource/tree/accountNotSignedInTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
|
||||
import { AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||
import { generateGuid } from '../../../azureResource/utils';
|
||||
|
||||
// Mock services
|
||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
||||
let mockAppContext: AppContext;
|
||||
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||
let mockAccountService: TypeMoq.IMock<IAzureResourceAccountService>;
|
||||
|
||||
// Mock test data
|
||||
@@ -53,15 +60,23 @@ const mockAccounts = [mockAccount1, mockAccount2];
|
||||
|
||||
describe('AzureResourceTreeProvider.getChildren', function(): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||
mockAccountService = TypeMoq.Mock.ofType<IAzureResourceAccountService>();
|
||||
|
||||
mockServicePool.accountService = mockAccountService.object;
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
|
||||
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||
mockAppContext.registerService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService, mockAccountService.object);
|
||||
|
||||
mockCacheService.setup((o) => o.generateKey(TypeMoq.It.isAnyString())).returns(() => generateGuid());
|
||||
});
|
||||
|
||||
it('Should load accounts.', async function(): Promise<void> {
|
||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
|
||||
|
||||
const treeProvider = new AzureResourceTreeProvider();
|
||||
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||
treeProvider.isSystemInitialized = true;
|
||||
|
||||
const children = await treeProvider.getChildren(undefined);
|
||||
@@ -83,7 +98,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
|
||||
it('Should handle when there is no accounts.', async function(): Promise<void> {
|
||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
|
||||
|
||||
const treeProvider = new AzureResourceTreeProvider();
|
||||
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||
treeProvider.isSystemInitialized = true;
|
||||
|
||||
const children = await treeProvider.getChildren(undefined);
|
||||
@@ -97,7 +112,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
|
||||
const mockAccountError = 'Test account error';
|
||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
|
||||
|
||||
const treeProvider = new AzureResourceTreeProvider();
|
||||
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||
treeProvider.isSystemInitialized = true;
|
||||
|
||||
const children = await treeProvider.getChildren(undefined);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,7 +32,7 @@ export class DeployConfigPage extends DacFxConfigPage {
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
let serverComponent = await this.createServerDropdown(true);
|
||||
let serverComponent = await this.createServerDropdown(true);
|
||||
let fileBrowserComponent = await this.createFileBrowser();
|
||||
this.databaseComponent = await this.createDatabaseTextBox();
|
||||
this.databaseComponent.title = localize('dacFx.databaseNameTextBox', 'Database Name');
|
||||
@@ -131,7 +131,7 @@ export class DeployConfigPage extends DacFxConfigPage {
|
||||
this.model.database = this.databaseTextBox.value;
|
||||
});
|
||||
|
||||
// Initialize with upgrade existing true
|
||||
//Initialize with upgrade existing true
|
||||
upgradeRadioButton.checked = true;
|
||||
this.model.upgradeExisting = true;
|
||||
|
||||
@@ -149,10 +149,10 @@ export class DeployConfigPage extends DacFxConfigPage {
|
||||
}
|
||||
|
||||
protected async createDeployDatabaseDropdown(): Promise<sqlops.FormComponent> {
|
||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
// Handle database changes
|
||||
//Handle database changes
|
||||
this.databaseDropdown.onValueChanged(async () => {
|
||||
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
});
|
||||
@@ -172,7 +172,8 @@ export class DeployConfigPage extends DacFxConfigPage {
|
||||
}
|
||||
let values = await this.getDatabaseValues();
|
||||
|
||||
if (this.model.database === undefined) {
|
||||
//set the database to the first dropdown value if upgrading, otherwise it should get set to the textbox value
|
||||
if (this.model.upgradeExisting) {
|
||||
this.model.database = values[0].name;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"smoketest": "cd test/smoke && node test/index.js",
|
||||
"monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit",
|
||||
"download-builtin-extensions": "node build/lib/builtInExtensions.js",
|
||||
"check-monaco-editor-compilation": "tsc -p src/tsconfig.monaco.json --noEmit"
|
||||
"check-monaco-editor-compilation": "tsc -p src/tsconfig.monaco.json --noEmit",
|
||||
"tslint": "node node_modules/tslint/bin/tslint -c tslint-gci.json -p src/tsconfig.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "~4.1.3",
|
||||
@@ -154,6 +155,7 @@
|
||||
"source-map": "^0.4.4",
|
||||
"temp-write": "^3.4.0",
|
||||
"tslint": "^5.9.1",
|
||||
"tslint-microsoft-contrib": "^6.0.0",
|
||||
"typemoq": "^0.3.2",
|
||||
"typescript": "2.9.2",
|
||||
"typescript-formatter": "7.1.0",
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
"Microsoft.server-report",
|
||||
"Microsoft.sql-vnext",
|
||||
"Microsoft.whoisactive",
|
||||
"Redgate.sql-search"
|
||||
"Redgate.sql-search",
|
||||
"IDERA.sqldm-performance-insights"
|
||||
],
|
||||
"extensionsGallery": {
|
||||
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"
|
||||
|
||||
@@ -10,16 +10,16 @@ set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5%
|
||||
:: call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
call .\scripts\code.bat %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
call .\scripts\code.bat %~dp0\..\extensions\markdown-language-features\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
call .\scripts\code.bat %~dp0\..\extensions\azure\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azure --extensionTestsPath=%~dp0\..\extensions\azure\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
call .\scripts\code.bat %~dp0\..\extensions\azurecore\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\azurecore --extensionTestsPath=%~dp0\..\extensions\azurecore\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR%
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
call .\scripts\code.bat $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% .
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
:: call .\scripts\code.bat $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% .
|
||||
:: if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
:: Integration & performance tests in AMD
|
||||
call .\scripts\test.bat --runGlob **\*.integrationTest.js %*
|
||||
|
||||
# Tests in commonJS (HTML, CSS, JSON language server tests...)
|
||||
:: Tests in commonJS (HTML, CSS, JSON language server tests...)
|
||||
call .\scripts\node-electron.bat .\node_modules\mocha\bin\_mocha .\extensions\*\server\out\test\**\*.test.js
|
||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ cd $ROOT
|
||||
# ./scripts/code.sh $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/vscode-colorize-tests/test --extensionDevelopmentPath=$ROOT/extensions/vscode-colorize-tests --extensionTestsPath=$ROOT/extensions/vscode-colorize-tests/out --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/markdown-language-features/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/markdown-language-features --extensionTestsPath=$ROOT/extensions/markdown-language-features/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/azure/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azure --extensionTestsPath=$ROOT/extensions/azure/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
./scripts/code.sh $ROOT/extensions/azurecore/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/azurecore --extensionTestsPath=$ROOT/extensions/azurecore/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started
|
||||
|
||||
mkdir $ROOT/extensions/emmet/test-fixtures
|
||||
./scripts/code.sh $ROOT/extensions/emmet/test-fixtures --extensionDevelopmentPath=$ROOT/extensions/emmet --extensionTestsPath=$ROOT/extensions/emmet/out/test --disableExtensions --user-data-dir=$VSCODEUSERDATADIR --skip-getting-started .
|
||||
|
||||
@@ -440,6 +440,13 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
||||
size = typeof size === 'number' ? size : item.size;
|
||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||
item.size = size;
|
||||
this.updateSize(item.view.id, size);
|
||||
let top = item.top + item.size;
|
||||
for (let i = index + 1; i < this.viewItems.length; i++) {
|
||||
let currentItem = this.viewItems[i];
|
||||
this.updateTop(currentItem.view.id, top);
|
||||
top += currentItem.size;
|
||||
}
|
||||
this.relayout(index);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// Adapted from https://github.com/naresh-n/slickgrid-column-data-autosize/blob/master/src/slick.autocolumnsize.js
|
||||
|
||||
import { mixin, clone } from 'sql/base/common/objects';
|
||||
import { isInDOM } from 'vs/base/browser/dom';
|
||||
|
||||
export interface IAutoColumnSizeOptions extends Slick.PluginOptions {
|
||||
maxWidth?: number;
|
||||
autoSizeOnRender?: boolean;
|
||||
}
|
||||
|
||||
const defaultOptions: IAutoColumnSizeOptions = {
|
||||
maxWidth: 200
|
||||
maxWidth: 200,
|
||||
autoSizeOnRender: false
|
||||
};
|
||||
|
||||
export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
||||
@@ -15,6 +18,7 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
||||
private _$container: JQuery;
|
||||
private _context: CanvasRenderingContext2D;
|
||||
private _options: IAutoColumnSizeOptions;
|
||||
private onPostEventHandler = new Slick.EventHandler();
|
||||
|
||||
constructor(options: IAutoColumnSizeOptions = defaultOptions) {
|
||||
this._options = mixin(options, defaultOptions, false);
|
||||
@@ -23,8 +27,12 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this._grid = grid;
|
||||
|
||||
if (this._options.autoSizeOnRender) {
|
||||
this.onPostEventHandler.subscribe(this._grid.onRendered, () => this.onPostRender());
|
||||
}
|
||||
|
||||
this._$container = $(this._grid.getContainerNode());
|
||||
this._$container.on('dblclick.autosize', '.slick-resizable-handle', e => this.reSizeColumn(e));
|
||||
this._$container.on('dblclick.autosize', '.slick-resizable-handle', e => this.handleDoubleClick(e));
|
||||
this._context = document.createElement('canvas').getContext('2d');
|
||||
}
|
||||
|
||||
@@ -32,7 +40,66 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
||||
this._$container.off();
|
||||
}
|
||||
|
||||
private reSizeColumn(e: JQuery.Event<HTMLElement, string>) {
|
||||
private onPostRender() {
|
||||
// this doesn't do anything if the grid isn't on the dom
|
||||
if (!isInDOM(this._grid.getContainerNode())) {
|
||||
return;
|
||||
}
|
||||
|
||||
// since data can be async we want to only do this if we have the data to actual
|
||||
// work on since we are measuring the physical length of data
|
||||
let data = this._grid.getData();
|
||||
let item = data.getItem(0);
|
||||
if (item && Object.keys(item).length > 0) {
|
||||
let hasValue = false;
|
||||
for (let key in item) {
|
||||
if (item.hasOwnProperty(key)) {
|
||||
if (item[key]) {
|
||||
hasValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!hasValue) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
let headerColumnsQuery = $(this._grid.getContainerNode()).find('.slick-header-columns');
|
||||
if (headerColumnsQuery && headerColumnsQuery.length) {
|
||||
let headerColumns = headerColumnsQuery[0];
|
||||
let origCols = this._grid.getColumns();
|
||||
let allColumns = clone(origCols);
|
||||
allColumns.forEach((col, index) => {
|
||||
col.formatter = origCols[index].formatter;
|
||||
col.asyncPostRender = origCols[index].asyncPostRender;
|
||||
});
|
||||
let change = false;
|
||||
for (let i = 0; i <= headerColumns.children.length; i++) {
|
||||
let headerEl = $(headerColumns.children.item(i));
|
||||
let columnDef = headerEl.data('column');
|
||||
if (columnDef) {
|
||||
let headerWidth = this.getElementWidth(headerEl[0]);
|
||||
let colIndex = this._grid.getColumnIndex(columnDef.id);
|
||||
let column = allColumns[colIndex];
|
||||
let autoSizeWidth = Math.max(headerWidth, this.getMaxColumnTextWidth(columnDef, colIndex)) + 1;
|
||||
if (autoSizeWidth !== column.width) {
|
||||
allColumns[colIndex].width = autoSizeWidth;
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (change) {
|
||||
this.onPostEventHandler.unsubscribeAll();
|
||||
this._grid.setColumns(allColumns);
|
||||
this._grid.onColumnsResized.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleDoubleClick(e: JQuery.Event<HTMLElement, string>) {
|
||||
let headerEl = $(e.currentTarget).closest('.slick-header-column');
|
||||
let columnDef = headerEl.data('column');
|
||||
|
||||
@@ -43,6 +110,10 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.reSizeColumn(headerEl, columnDef);
|
||||
}
|
||||
|
||||
private reSizeColumn(headerEl: JQuery, columnDef: Slick.Column<T>) {
|
||||
let headerWidth = this.getElementWidth(headerEl[0]);
|
||||
let colIndex = this._grid.getColumnIndex(columnDef.id);
|
||||
let origCols = this._grid.getColumns();
|
||||
|
||||
@@ -47,7 +47,7 @@ export class CopyKeybind<T> implements Slick.Plugin<T> {
|
||||
if (!isUndefinedOrNull(this.grid.getColumns()[0].selectable) && !this.grid.getColumns()[0].selectable) {
|
||||
startColumn = 1;
|
||||
}
|
||||
ranges = [new Slick.Range(selectedRows[0], startColumn, selectedRows[selectedRows.length - 1], this.grid.getColumns().length)]
|
||||
ranges = [new Slick.Range(selectedRows[0], startColumn, selectedRows[selectedRows.length - 1], this.grid.getColumns().length)];
|
||||
}
|
||||
this._onCopy.fire(ranges);
|
||||
}
|
||||
|
||||
@@ -349,6 +349,7 @@ export class RowDetailView {
|
||||
//slick-cell to escape the cell overflow clipping.
|
||||
|
||||
//sneaky extra </div> inserted here-----------------v
|
||||
/* tslint:disable:no-unexternalized-strings */
|
||||
html.push("<div class='detailView-toggle collapse'></div></div>");
|
||||
|
||||
html.push("<div id='cellDetailView_", dataContext.id, "' class='dynamic-cell-detail' "); //apply custom css to detail
|
||||
@@ -356,6 +357,7 @@ export class RowDetailView {
|
||||
html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
|
||||
html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
|
||||
html.push("<div id='innerDetailView_", dataContext.id, "'>", escape(dataContext._detailContent), "</div></div>");
|
||||
/* tslint:enable:no-unexternalized-strings */
|
||||
//&omit a final closing detail container </div> that would come next
|
||||
|
||||
return html.join('');
|
||||
@@ -364,17 +366,21 @@ export class RowDetailView {
|
||||
}
|
||||
|
||||
public resizeDetailView(item) {
|
||||
if (!item) return;
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Grad each of the dom items
|
||||
let mainContainer = document.getElementById('detailViewContainer_' + item.id);
|
||||
let cellItem = document.getElementById('cellDetailView_' + item.id);
|
||||
let inner = document.getElementById('innerDetailView_' + item.id);
|
||||
|
||||
if (!mainContainer || !cellItem || !inner) return;
|
||||
if (!mainContainer || !cellItem || !inner) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let idx = 1; idx <= item._sizePadding; idx++) {
|
||||
this._dataView.deleteItem(item.id + "." + idx);
|
||||
this._dataView.deleteItem(item.id + '.' + idx);
|
||||
}
|
||||
|
||||
let rowHeight = this._grid.getOptions().rowHeight; // height of a row
|
||||
@@ -395,9 +401,9 @@ export class RowDetailView {
|
||||
this._grid.getOptions().minRowBuffer = item._sizePadding + 3;
|
||||
}
|
||||
|
||||
mainContainer.setAttribute("style", "max-height: " + item._height + "px");
|
||||
mainContainer.setAttribute('style', 'max-height: ' + item._height + 'px');
|
||||
if (cellItem) {
|
||||
cellItem.setAttribute("style", "height: " + item._height + "px;top:" + rowHeight + "px");
|
||||
cellItem.setAttribute('style', 'height: ' + item._height + 'px;top:' + rowHeight + 'px');
|
||||
}
|
||||
|
||||
let idxParent = this._dataView.getIdxById(item.id);
|
||||
|
||||
@@ -46,6 +46,9 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
private _onClick = new Emitter<ITableMouseEvent>();
|
||||
public readonly onClick: Event<ITableMouseEvent> = this._onClick.event;
|
||||
|
||||
private _onColumnResize = new Emitter<void>();
|
||||
public readonly onColumnResize = this._onColumnResize.event;
|
||||
|
||||
constructor(parent: HTMLElement, configuration?: ITableConfiguration<T>, options?: Slick.GridOptions<T>) {
|
||||
super();
|
||||
if (!configuration || !configuration.dataProvider || isArray(configuration.dataProvider)) {
|
||||
@@ -105,6 +108,7 @@ export class Table<T extends Slick.SlickData> extends Widget implements IThemabl
|
||||
|
||||
this.mapMouseEvent(this._grid.onContextMenu, this._onContextMenu);
|
||||
this.mapMouseEvent(this._grid.onClick, this._onClick);
|
||||
this._grid.onColumnsResized.subscribe(() => this._onColumnResize.fire());
|
||||
}
|
||||
|
||||
private mapMouseEvent(slickEvent: Slick.Event<any>, emitter: Emitter<ITableMouseEvent>) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Observer } from 'rxjs/Observer';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { compare as stringCompare } from 'vs/base/common/strings';
|
||||
|
||||
import { IDisposableDataProvider } from 'sql/base/browser/ui/table/interfaces';
|
||||
|
||||
@@ -19,7 +20,23 @@ export interface IFindPosition {
|
||||
function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
|
||||
let field = args.sortCol.field;
|
||||
let sign = args.sortAsc ? 1 : -1;
|
||||
return data.sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign);
|
||||
let comparer: (a, b) => number;
|
||||
if (types.isString(data[0][field])) {
|
||||
if (Number(data[0][field]) !== NaN) {
|
||||
comparer = (a: number, b: number) => {
|
||||
let anum = Number(a[field]);
|
||||
let bnum = Number(b[field]);
|
||||
return anum === bnum ? 0 : anum > bnum ? 1 : -1;
|
||||
};
|
||||
} else {
|
||||
comparer = stringCompare;
|
||||
}
|
||||
} else {
|
||||
comparer = (a: number, b: number) => {
|
||||
return a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1);
|
||||
};
|
||||
}
|
||||
return data.sort((a, b) => comparer(a, b) * sign);
|
||||
}
|
||||
|
||||
export class TableDataView<T extends Slick.SlickData> implements IDisposableDataProvider<T> {
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface ICommandLineProcessing {
|
||||
* Interprets the various Azure Data Studio-specific command line switches and
|
||||
* performs the requisite tasks such as connecting to a server
|
||||
*/
|
||||
processCommandLine() : void;
|
||||
processCommandLine() : Promise<void>;
|
||||
}
|
||||
|
||||
export const ICommandLineProcessing = createDecorator<ICommandLineProcessing>('commandLineService');
|
||||
@@ -15,10 +15,13 @@ import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions a
|
||||
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
|
||||
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { warn } from 'sql/base/common/log';
|
||||
|
||||
export class CommandLineService implements ICommandLineProcessing {
|
||||
private _connectionProfile: ConnectionProfile;
|
||||
private _showConnectionDialog: boolean;
|
||||
private _commandName:string;
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@@ -27,49 +30,96 @@ export class CommandLineService implements ICommandLineProcessing {
|
||||
@IQueryEditorService private _queryEditorService: IQueryEditorService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@IEditorService private _editorService: IEditorService,
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
let profile = null;
|
||||
if (this._environmentService && this._environmentService.args.server) {
|
||||
profile = new ConnectionProfile(_capabilitiesService, null);
|
||||
// We want connection store to use any matching password it finds
|
||||
profile.savePassword = true;
|
||||
profile.providerName = Constants.mssqlProviderName;
|
||||
profile.serverName = _environmentService.args.server;
|
||||
profile.databaseName = _environmentService.args.database ? _environmentService.args.database : '';
|
||||
profile.userName = _environmentService.args.user ? _environmentService.args.user : '';
|
||||
profile.authenticationType = _environmentService.args.integrated ? 'Integrated' : 'SqlLogin';
|
||||
profile.connectionName = '';
|
||||
profile.setOptionValue('applicationName', Constants.applicationName);
|
||||
profile.setOptionValue('databaseDisplayName', profile.databaseName);
|
||||
profile.setOptionValue('groupId', profile.groupId);
|
||||
if (this._environmentService) {
|
||||
if (this._commandService) {
|
||||
this._commandName = this._environmentService.args.command;
|
||||
}
|
||||
if (this._environmentService.args.server) {
|
||||
profile = new ConnectionProfile(_capabilitiesService, null);
|
||||
// We want connection store to use any matching password it finds
|
||||
profile.savePassword = true;
|
||||
profile.providerName = Constants.mssqlProviderName;
|
||||
profile.serverName = _environmentService.args.server;
|
||||
profile.databaseName = _environmentService.args.database ? _environmentService.args.database : '';
|
||||
profile.userName = _environmentService.args.user ? _environmentService.args.user : '';
|
||||
profile.authenticationType = _environmentService.args.integrated ? 'Integrated' : 'SqlLogin';
|
||||
profile.connectionName = '';
|
||||
profile.setOptionValue('applicationName', Constants.applicationName);
|
||||
profile.setOptionValue('databaseDisplayName', profile.databaseName);
|
||||
profile.setOptionValue('groupId', profile.groupId);
|
||||
}
|
||||
}
|
||||
this._connectionProfile = profile;
|
||||
const registry = platform.Registry.as<IConnectionProviderRegistry>(ConnectionProviderExtensions.ConnectionProviderContributions);
|
||||
let sqlProvider = registry.getProperties(Constants.mssqlProviderName);
|
||||
// We can't connect to object explorer until the MSSQL connection provider is registered
|
||||
if (sqlProvider) {
|
||||
this.processCommandLine();
|
||||
this.processCommandLine().catch(reason=>{warn('processCommandLine failed: ' + reason);});
|
||||
} else {
|
||||
registry.onNewProvider(e => {
|
||||
if (e.id === Constants.mssqlProviderName) {
|
||||
this.processCommandLine();
|
||||
this.processCommandLine().catch(reason=>{warn('processCommandLine failed: ' + reason);});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
public _serviceBrand: any;
|
||||
public processCommandLine(): void {
|
||||
if (!this._connectionProfile && !this._connectionManagementService.hasRegisteredServers()) {
|
||||
// prompt the user for a new connection on startup if no profiles are registered
|
||||
this._connectionManagementService.showConnectionDialog();
|
||||
} else if (this._connectionProfile) {
|
||||
this._connectionManagementService.connectIfNotConnected(this._connectionProfile, 'connection', true)
|
||||
.then(result => TaskUtilities.newQuery(this._connectionProfile,
|
||||
this._connectionManagementService,
|
||||
this._queryEditorService,
|
||||
this._objectExplorerService,
|
||||
this._editorService))
|
||||
.catch(() => { });
|
||||
}
|
||||
// We base our logic on the combination of (server, command) values.
|
||||
// (serverName, commandName) => Connect object explorer and execute the command, passing the connection profile to the command. Do not load query editor.
|
||||
// (null, commandName) => Launch the command with a null connection. If the command implementation needs a connection, it will need to create it.
|
||||
// (serverName, null) => Connect object explorer and open a new query editor
|
||||
// (null, null) => Prompt for a connection unless there are registered servers
|
||||
public processCommandLine(): Promise<void> {
|
||||
|
||||
let self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
|
||||
if (!self._commandName && !self._connectionProfile && !self._connectionManagementService.hasRegisteredServers()) {
|
||||
// prompt the user for a new connection on startup if no profiles are registered
|
||||
self._connectionManagementService.showConnectionDialog()
|
||||
.then(() => {
|
||||
resolve();
|
||||
},
|
||||
error => {
|
||||
reject(error);
|
||||
});
|
||||
} else if (self._connectionProfile) {
|
||||
if (!self._commandName) {
|
||||
self._connectionManagementService.connectIfNotConnected(self._connectionProfile, 'connection', true)
|
||||
.then(() => {
|
||||
TaskUtilities.newQuery(self._connectionProfile,
|
||||
self._connectionManagementService,
|
||||
self._queryEditorService,
|
||||
self._objectExplorerService,
|
||||
self._editorService)
|
||||
.then( () => {
|
||||
resolve();
|
||||
}, error => {
|
||||
// ignore query editor failing to open.
|
||||
// the tests don't mock this out
|
||||
warn('unable to open query editor ' + error);
|
||||
resolve();
|
||||
});
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
} else {
|
||||
self._connectionManagementService.connectIfNotConnected(self._connectionProfile, 'connection', true)
|
||||
.then(() => {
|
||||
self._commandService.executeCommand(self._commandName, self._connectionProfile).then(() => resolve(), error => reject(error));
|
||||
}, error => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
} else if (self._commandName) {
|
||||
self._commandService.executeCommand(self._commandName).then(() => resolve(), error => reject(error));
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ import { IQueryEditorOptions } from 'sql/parts/query/common/queryEditorService';
|
||||
import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput';
|
||||
import { NotebookInput, NotebookInputModel, NotebookInputValidator } from 'sql/parts/notebook/notebookInput';
|
||||
import { DEFAULT_NOTEBOOK_PROVIDER, INotebookService } from 'sql/services/notebook/notebookService';
|
||||
import { getProviderForFileName } from 'sql/parts/notebook/notebookUtils';
|
||||
import { getProvidersForFileName } from 'sql/parts/notebook/notebookUtils';
|
||||
import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput';
|
||||
|
||||
const fs = require('fs');
|
||||
@@ -63,13 +63,14 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
|
||||
if (uri && notebookValidator.isNotebookEnabled()) {
|
||||
return withService<INotebookService, NotebookInput>(instantiationService, INotebookService, notebookService => {
|
||||
let fileName: string = 'untitled';
|
||||
let providerId: string = DEFAULT_NOTEBOOK_PROVIDER;
|
||||
let providerIds: string[] = [DEFAULT_NOTEBOOK_PROVIDER];
|
||||
if (input) {
|
||||
fileName = input.getName();
|
||||
providerId = getProviderForFileName(fileName, notebookService);
|
||||
providerIds = getProvidersForFileName(fileName, notebookService);
|
||||
}
|
||||
let notebookInputModel = new NotebookInputModel(uri, undefined, false, undefined);
|
||||
notebookInputModel.providerId = providerId;
|
||||
notebookInputModel.providerId = providerIds.filter(provider => provider !== DEFAULT_NOTEBOOK_PROVIDER)[0];
|
||||
notebookInputModel.providers = providerIds;
|
||||
let notebookInput: NotebookInput = instantiationService.createInstance(NotebookInput, fileName, notebookInputModel);
|
||||
return notebookInput;
|
||||
});
|
||||
|
||||
@@ -88,7 +88,7 @@ export function parseNumAsTimeString(value: number, includeFraction: boolean = t
|
||||
return tempVal > 0 && includeFraction ? rs + '.' + mss : rs;
|
||||
}
|
||||
|
||||
export function generateUri(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): string {
|
||||
export function generateUri(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection' | 'notebook'): string {
|
||||
let prefix = purpose ? uriPrefixes[purpose] : uriPrefixes.default;
|
||||
let uri = generateUriWithPrefix(connection, prefix);
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import * as Constants from 'sql/parts/connection/common/constants';
|
||||
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
|
||||
import { attachButtonStyler, attachCheckboxStyler, attachEditableDropdownStyler, attachInputBoxStyler } from 'sql/common/theme/styler';
|
||||
import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
@@ -290,14 +289,14 @@ export class ConnectionWidget {
|
||||
|
||||
private registerListeners(): void {
|
||||
// Theme styler
|
||||
this._toDispose.push(attachInputBoxStyler(this._serverNameInputBox, this._themeService));
|
||||
this._toDispose.push(attachEditableDropdownStyler(this._databaseNameInputBox, this._themeService));
|
||||
this._toDispose.push(attachInputBoxStyler(this._connectionNameInputBox, this._themeService));
|
||||
this._toDispose.push(attachInputBoxStyler(this._userNameInputBox, this._themeService));
|
||||
this._toDispose.push(attachInputBoxStyler(this._passwordInputBox, this._themeService));
|
||||
this._toDispose.push(styler.attachInputBoxStyler(this._serverNameInputBox, this._themeService));
|
||||
this._toDispose.push(styler.attachEditableDropdownStyler(this._databaseNameInputBox, this._themeService));
|
||||
this._toDispose.push(styler.attachInputBoxStyler(this._connectionNameInputBox, this._themeService));
|
||||
this._toDispose.push(styler.attachInputBoxStyler(this._userNameInputBox, this._themeService));
|
||||
this._toDispose.push(styler.attachInputBoxStyler(this._passwordInputBox, this._themeService));
|
||||
this._toDispose.push(styler.attachSelectBoxStyler(this._serverGroupSelectBox, this._themeService));
|
||||
this._toDispose.push(attachButtonStyler(this._advancedButton, this._themeService));
|
||||
this._toDispose.push(attachCheckboxStyler(this._rememberPasswordCheckBox, this._themeService));
|
||||
this._toDispose.push(styler.attachButtonStyler(this._advancedButton, this._themeService));
|
||||
this._toDispose.push(styler.attachCheckboxStyler(this._rememberPasswordCheckBox, this._themeService));
|
||||
this._toDispose.push(styler.attachSelectBoxStyler(this._azureAccountDropdown, this._themeService));
|
||||
|
||||
if (this._authTypeSelectBox) {
|
||||
|
||||
@@ -90,6 +90,8 @@
|
||||
margin: 0px 13px;
|
||||
}
|
||||
|
||||
.vs-dark .connection-dialog .connection-history-actions .action-label.icon,
|
||||
.hc-black .connection-dialog .connection-history-actions .action-label.icon,
|
||||
.connection-dialog .connection-history-actions .action-label.icon {
|
||||
display: block;
|
||||
height: 20px;
|
||||
|
||||
@@ -17,7 +17,7 @@ import * as nls from 'vs/nls';
|
||||
export enum BreadcrumbClass {
|
||||
DatabasePage,
|
||||
ServerPage
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class BreadcrumbService implements IBreadcrumbService {
|
||||
|
||||
@@ -109,7 +109,7 @@ export class BackupUiService implements IBackupUiService {
|
||||
let self = this;
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
self.showBackupDialog(connection).then(() => {
|
||||
resolve();
|
||||
resolve(void 0);
|
||||
}, error => {
|
||||
reject();
|
||||
});
|
||||
@@ -145,7 +145,7 @@ export class BackupUiService implements IBackupUiService {
|
||||
}
|
||||
|
||||
let backupOptions = this.getOptions(this._currentProvider);
|
||||
return new TPromise<void>(() => {
|
||||
return new TPromise<void>((resolve) => {
|
||||
let uri = this._connectionManagementService.getConnectionUri(connection)
|
||||
+ ProviderConnectionInfo.idSeparator
|
||||
+ ConnectionUtils.ConnectionUriBackupIdAttributeName
|
||||
@@ -168,6 +168,7 @@ export class BackupUiService implements IBackupUiService {
|
||||
} else {
|
||||
(backupDialog as BackupDialog).open(connection);
|
||||
}
|
||||
resolve(void 0);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,25 +22,25 @@ export const recoveryModelSimple = 'Simple';
|
||||
export const recoveryModelFull = 'Full';
|
||||
|
||||
// Constants for UI strings
|
||||
export const labelDatabase = 'Database';
|
||||
export const labelFilegroup = 'Files and filegroups';
|
||||
export const labelFull = 'Full';
|
||||
export const labelDifferential = 'Differential';
|
||||
export const labelLog = 'Transaction Log';
|
||||
export const labelDisk = 'Disk';
|
||||
export const labelUrl = 'Url';
|
||||
export const labelDatabase = localize('backup.labelDatabase', 'Database');
|
||||
export const labelFilegroup = localize('backup.labelFilegroup', 'Files and filegroups');
|
||||
export const labelFull = localize('backup.labelFull', 'Full');
|
||||
export const labelDifferential = localize('backup.labelDifferential', 'Differential');
|
||||
export const labelLog = localize('backup.labelLog', 'Transaction Log');
|
||||
export const labelDisk = localize('backup.labelDisk', 'Disk');
|
||||
export const labelUrl = localize('backup.labelUrl', 'Url');
|
||||
|
||||
export const defaultCompression = 'Use the default server setting';
|
||||
export const compressionOn = 'Compress backup';
|
||||
export const compressionOff = 'Do not compress backup';
|
||||
export const defaultCompression = localize('backup.defaultCompression', 'Use the default server setting');
|
||||
export const compressionOn = localize('backup.compressBackup', 'Compress backup');
|
||||
export const compressionOff = localize('backup.doNotCompress', 'Do not compress backup');
|
||||
|
||||
export const aes128 = 'AES 128';
|
||||
export const aes192 = 'AES 192';
|
||||
export const aes256 = 'AES 256';
|
||||
export const tripleDES = 'Triple DES';
|
||||
|
||||
export const serverCertificate = "Server Certificate";
|
||||
export const asymmetricKey = "Asymmetric Key";
|
||||
export const serverCertificate = localize('backup.serverCertificate', "Server Certificate");
|
||||
export const asymmetricKey = localize('backup.asymmetricKey', "Asymmetric Key");
|
||||
|
||||
export const fileFiltersSet: {label: string, filters: string[]}[] = [
|
||||
{ label: localize('backup.filterBackupFiles', "Backup Files"), filters: ['*.bak', '*.trn', '*.log'] },
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
* List of services that provide file validation callback to file browser service
|
||||
*/
|
||||
|
||||
export const backup: string = "Backup";
|
||||
export const restore: string = "Restore";
|
||||
export const backup: string = 'Backup';
|
||||
export const restore: string = 'Restore';
|
||||
@@ -48,13 +48,13 @@ export function textFormatter(row: number, cell: any, value: any, columnDef: any
|
||||
if (!value.isNull) {
|
||||
valueToDisplay = value.displayValue.replace(/(\r\n|\n|\r)/g, ' ');
|
||||
valueToDisplay = escape(valueToDisplay.length > 250 ? valueToDisplay.slice(0, 250) + '...' : valueToDisplay);
|
||||
titleValue = value.displayValue;
|
||||
titleValue = valueToDisplay;
|
||||
} else {
|
||||
cellClasses += ' missing-value';
|
||||
}
|
||||
} else if (typeof value === 'string') {
|
||||
valueToDisplay = escape(value.length > 250 ? value.slice(0, 250) + '...' : value);
|
||||
titleValue = value;
|
||||
titleValue = valueToDisplay;
|
||||
}
|
||||
|
||||
return `<span title="${titleValue}" class="${cellClasses}">${valueToDisplay}</span>`;
|
||||
|
||||
@@ -343,6 +343,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
handleResultSet(self: EditDataComponent, event: any): void {
|
||||
// Clone the data before altering it to avoid impacting other subscribers
|
||||
let resultSet = Object.assign({}, event.data);
|
||||
if (!resultSet.complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add an extra 'new row'
|
||||
resultSet.rowCount++;
|
||||
|
||||
@@ -412,7 +412,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
|
||||
};
|
||||
sibling.onmouseleave = (e) => {
|
||||
targetChildren.removeClass('hovered');
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ export default class CardComponent extends ComponentWithIconBase implements ICom
|
||||
}
|
||||
|
||||
public get showRadioButton():boolean{
|
||||
return this.selectable && (this.selected || this._hasFocus)
|
||||
return this.selectable && (this.selected || this._hasFocus);
|
||||
}
|
||||
|
||||
public get showAsSelected(): boolean {
|
||||
|
||||
@@ -38,9 +38,9 @@ export class CodeCellComponent extends CellView implements OnInit, OnChanges {
|
||||
|
||||
ngOnInit() {
|
||||
if (this.cellModel) {
|
||||
this.cellModel.onOutputsChanged(() => {
|
||||
this._register(this.cellModel.onOutputsChanged(() => {
|
||||
this._changeRef.detectChanges();
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,9 @@ export class OutputAreaComponent extends AngularDisposable implements OnInit {
|
||||
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||
this.updateTheme(this.themeService.getColorTheme());
|
||||
if (this.cellModel) {
|
||||
this.cellModel.onOutputsChanged(() => {
|
||||
this._register(this.cellModel.onOutputsChanged(() => {
|
||||
this._changeRef.detectChanges();
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -91,9 +91,9 @@ export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
||||
this.setLoading(false);
|
||||
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||
this.updateTheme(this.themeService.getColorTheme());
|
||||
this.cellModel.onOutputsChanged(e => {
|
||||
this._register(this.cellModel.onOutputsChanged(e => {
|
||||
this.updatePreview();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||
|
||||
@@ -226,6 +226,9 @@ export class CellModel implements ICellModel {
|
||||
this._outputs.push(this.rewriteOutputUrls(output));
|
||||
this.fireOutputsChanged();
|
||||
}
|
||||
if (!this._future.inProgress) {
|
||||
this._future.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private rewriteOutputUrls(output: nb.ICellOutput): nb.ICellOutput {
|
||||
@@ -327,6 +330,7 @@ export class CellModel implements ICellModel {
|
||||
CellModel.LanguageMapping['pyspark3'] = 'python';
|
||||
CellModel.LanguageMapping['python'] = 'python';
|
||||
CellModel.LanguageMapping['scala'] = 'scala';
|
||||
CellModel.LanguageMapping['sql'] = 'sql';
|
||||
}
|
||||
|
||||
private get languageInfo(): nb.ILanguageInfo {
|
||||
|
||||
@@ -15,7 +15,7 @@ export declare type JSONValue = JSONPrimitive | JSONObject | JSONArray;
|
||||
* A type definition for a JSON object.
|
||||
*/
|
||||
export interface JSONObject {
|
||||
[key: string]: JSONValue;
|
||||
[key: string]: JSONValue;
|
||||
}
|
||||
/**
|
||||
* A type definition for a JSON array.
|
||||
@@ -26,7 +26,7 @@ export interface JSONArray extends Array<JSONValue> {
|
||||
* A type definition for a readonly JSON object.
|
||||
*/
|
||||
export interface ReadonlyJSONObject {
|
||||
readonly [key: string]: ReadonlyJSONValue;
|
||||
readonly [key: string]: ReadonlyJSONValue;
|
||||
}
|
||||
/**
|
||||
* A type definition for a readonly JSON array.
|
||||
@@ -45,10 +45,10 @@ export declare type ReadonlyJSONValue = JSONPrimitive | ReadonlyJSONObject | Rea
|
||||
* @returns `true` if the value is a primitive,`false` otherwise.
|
||||
*/
|
||||
export function isPrimitive(value: any): boolean {
|
||||
return (
|
||||
value === null ||
|
||||
typeof value === 'boolean' ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'string'
|
||||
);
|
||||
return (
|
||||
value === null ||
|
||||
typeof value === 'boolean' ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'string'
|
||||
);
|
||||
}
|
||||
@@ -249,9 +249,9 @@ export interface INotebookModel {
|
||||
readonly languageInfo: nb.ILanguageInfo;
|
||||
|
||||
/**
|
||||
* The notebook service used to call backend APIs
|
||||
* All notebook managers applicable for a given notebook
|
||||
*/
|
||||
readonly notebookManager: INotebookManager;
|
||||
readonly notebookManagers: INotebookManager[];
|
||||
|
||||
/**
|
||||
* Event fired on first initialization of the kernel and
|
||||
@@ -299,6 +299,11 @@ export interface INotebookModel {
|
||||
*/
|
||||
trustedMode: boolean;
|
||||
|
||||
/**
|
||||
* Current notebook provider id
|
||||
*/
|
||||
providerId: string;
|
||||
|
||||
/**
|
||||
* Change the current kernel from the Kernel dropdown
|
||||
* @param displayName kernel name (as displayed in Kernel dropdown)
|
||||
@@ -411,7 +416,8 @@ export interface INotebookModelOptions {
|
||||
*/
|
||||
factory: IModelFactory;
|
||||
|
||||
notebookManager: INotebookManager;
|
||||
notebookManagers: INotebookManager[];
|
||||
providerId: string;
|
||||
|
||||
notificationService: INotificationService;
|
||||
connectionService: IConnectionManagementService;
|
||||
|
||||
494
src/sql/parts/notebook/models/nbformat.ts
Normal file
494
src/sql/parts/notebook/models/nbformat.ts
Normal file
@@ -0,0 +1,494 @@
|
||||
// Copyright (c) Jupyter Development Team.
|
||||
// Distributed under the terms of the Modified BSD License.
|
||||
|
||||
// Notebook format interfaces
|
||||
// https://nbformat.readthedocs.io/en/latest/format_description.html
|
||||
// https://github.com/jupyter/nbformat/blob/master/nbformat/v4/nbformat.v4.schema.json
|
||||
|
||||
|
||||
import { JSONObject } from './jsonext';
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
/**
|
||||
* A namespace for nbformat interfaces.
|
||||
*/
|
||||
export namespace nbformat {
|
||||
/**
|
||||
* The major version of the notebook format.
|
||||
*/
|
||||
export const MAJOR_VERSION: number = 4;
|
||||
|
||||
/**
|
||||
* The minor version of the notebook format.
|
||||
*/
|
||||
export const MINOR_VERSION: number = 2;
|
||||
|
||||
/**
|
||||
* The kernelspec metadata.
|
||||
*/
|
||||
export interface IKernelspecMetadata extends JSONObject {
|
||||
name: string;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The language info metatda
|
||||
*/
|
||||
export interface ILanguageInfoMetadata extends JSONObject {
|
||||
name: string;
|
||||
codemirror_mode?: string | JSONObject;
|
||||
file_extension?: string;
|
||||
mimetype?: string;
|
||||
pygments_lexer?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default metadata for the notebook.
|
||||
*/
|
||||
export interface INotebookMetadata extends JSONObject {
|
||||
kernelspec?: IKernelspecMetadata;
|
||||
language_info?: ILanguageInfoMetadata;
|
||||
orig_nbformat: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The notebook content.
|
||||
*/
|
||||
export interface INotebookContent {
|
||||
metadata: INotebookMetadata;
|
||||
nbformat_minor: number;
|
||||
nbformat: number;
|
||||
cells: ICell[];
|
||||
}
|
||||
|
||||
/**
|
||||
* A multiline string.
|
||||
*/
|
||||
export type MultilineString = string | string[];
|
||||
|
||||
/**
|
||||
* A mime-type keyed dictionary of data.
|
||||
*/
|
||||
export interface IMimeBundle extends JSONObject {
|
||||
[key: string]: MultilineString | JSONObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Media attachments (e.g. inline images).
|
||||
*/
|
||||
export interface IAttachments {
|
||||
[key: string]: IMimeBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* The code cell's prompt number. Will be null if the cell has not been run.
|
||||
*/
|
||||
export type ExecutionCount = number | null;
|
||||
|
||||
/**
|
||||
* Cell output metadata.
|
||||
*/
|
||||
export type OutputMetadata = JSONObject;
|
||||
|
||||
/**
|
||||
* Validate a mime type/value pair.
|
||||
*
|
||||
* @param type - The mimetype name.
|
||||
*
|
||||
* @param value - The value associated with the type.
|
||||
*
|
||||
* @returns Whether the type/value pair are valid.
|
||||
*/
|
||||
export function validateMimeValue(
|
||||
type: string,
|
||||
value: MultilineString | JSONObject
|
||||
): boolean {
|
||||
// Check if "application/json" or "application/foo+json"
|
||||
const jsonTest = /^application\/(.*?)+\+json$/;
|
||||
const isJSONType = type === 'application/json' || jsonTest.test(type);
|
||||
|
||||
let isString = (x: any) => {
|
||||
return Object.prototype.toString.call(x) === '[object String]';
|
||||
};
|
||||
|
||||
// If it is an array, make sure if is not a JSON type and it is an
|
||||
// array of strings.
|
||||
if (Array.isArray(value)) {
|
||||
if (isJSONType) {
|
||||
return false;
|
||||
}
|
||||
let valid = true;
|
||||
(value as string[]).forEach(v => {
|
||||
if (!isString(v)) {
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
return valid;
|
||||
}
|
||||
|
||||
// If it is a string, make sure we are not a JSON type.
|
||||
if (isString(value)) {
|
||||
return !isJSONType;
|
||||
}
|
||||
|
||||
// It is not a string, make sure it is a JSON type.
|
||||
if (!isJSONType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// It is a JSON type, make sure it is a valid JSON object.
|
||||
// return JSONExt.isObject(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cell-level metadata.
|
||||
*/
|
||||
export interface IBaseCellMetadata extends JSONObject {
|
||||
/**
|
||||
* Whether the cell is trusted.
|
||||
*
|
||||
* #### Notes
|
||||
* This is not strictly part of the nbformat spec, but it is added by
|
||||
* the contents manager.
|
||||
*
|
||||
* See https://jupyter-notebook.readthedocs.io/en/latest/security.html.
|
||||
*/
|
||||
trusted: boolean;
|
||||
|
||||
/**
|
||||
* The cell's name. If present, must be a non-empty string.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The cell's tags. Tags must be unique, and must not contain commas.
|
||||
*/
|
||||
tags: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The base cell interface.
|
||||
*/
|
||||
export interface IBaseCell {
|
||||
/**
|
||||
* String identifying the type of cell.
|
||||
*/
|
||||
cell_type: string;
|
||||
|
||||
/**
|
||||
* Contents of the cell, represented as an array of lines.
|
||||
*/
|
||||
source: MultilineString;
|
||||
|
||||
/**
|
||||
* Cell-level metadata.
|
||||
*/
|
||||
metadata: Partial<ICellMetadata>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata for the raw cell.
|
||||
*/
|
||||
export interface IRawCellMetadata extends IBaseCellMetadata {
|
||||
/**
|
||||
* Raw cell metadata format for nbconvert.
|
||||
*/
|
||||
format: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A raw cell.
|
||||
*/
|
||||
export interface IRawCell extends IBaseCell {
|
||||
/**
|
||||
* String identifying the type of cell.
|
||||
*/
|
||||
cell_type: 'raw';
|
||||
|
||||
/**
|
||||
* Cell-level metadata.
|
||||
*/
|
||||
metadata: Partial<IRawCellMetadata>;
|
||||
|
||||
/**
|
||||
* Cell attachments.
|
||||
*/
|
||||
attachments?: IAttachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* A markdown cell.
|
||||
*/
|
||||
export interface IMarkdownCell extends IBaseCell {
|
||||
/**
|
||||
* String identifying the type of cell.
|
||||
*/
|
||||
cell_type: 'markdown';
|
||||
|
||||
/**
|
||||
* Cell attachments.
|
||||
*/
|
||||
attachments?: IAttachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata for a code cell.
|
||||
*/
|
||||
export interface ICodeCellMetadata extends IBaseCellMetadata {
|
||||
/**
|
||||
* Whether the cell is collapsed/expanded.
|
||||
*/
|
||||
collapsed: boolean;
|
||||
|
||||
/**
|
||||
* Whether the cell's output is scrolled, unscrolled, or autoscrolled.
|
||||
*/
|
||||
scrolled: boolean | 'auto';
|
||||
}
|
||||
|
||||
/**
|
||||
* A code cell.
|
||||
*/
|
||||
export interface ICodeCell extends IBaseCell {
|
||||
/**
|
||||
* String identifying the type of cell.
|
||||
*/
|
||||
cell_type: 'code';
|
||||
|
||||
/**
|
||||
* Cell-level metadata.
|
||||
*/
|
||||
metadata: Partial<ICodeCellMetadata>;
|
||||
|
||||
/**
|
||||
* Execution, display, or stream outputs.
|
||||
*/
|
||||
outputs: IOutput[];
|
||||
|
||||
/**
|
||||
* The code cell's prompt number. Will be null if the cell has not been run.
|
||||
*/
|
||||
execution_count: ExecutionCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* An unrecognized cell.
|
||||
*/
|
||||
export interface IUnrecognizedCell extends IBaseCell { }
|
||||
|
||||
/**
|
||||
* A cell union type.
|
||||
*/
|
||||
export type ICell = IRawCell | IMarkdownCell | ICodeCell | IUnrecognizedCell;
|
||||
|
||||
/**
|
||||
* Test whether a cell is a raw cell.
|
||||
*/
|
||||
export function isRaw(cell: ICell): cell is IRawCell {
|
||||
return cell.cell_type === 'raw';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a cell is a markdown cell.
|
||||
*/
|
||||
export function isMarkdown(cell: ICell): cell is IMarkdownCell {
|
||||
return cell.cell_type === 'markdown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a cell is a code cell.
|
||||
*/
|
||||
export function isCode(cell: ICell): cell is ICodeCell {
|
||||
return cell.cell_type === 'code';
|
||||
}
|
||||
|
||||
/**
|
||||
* A union metadata type.
|
||||
*/
|
||||
export type ICellMetadata =
|
||||
| IBaseCellMetadata
|
||||
| IRawCellMetadata
|
||||
| ICodeCellMetadata;
|
||||
|
||||
/**
|
||||
* The valid output types.
|
||||
*/
|
||||
export type OutputType =
|
||||
| 'execute_result'
|
||||
| 'display_data'
|
||||
| 'stream'
|
||||
| 'error'
|
||||
| 'update_display_data';
|
||||
|
||||
|
||||
/**
|
||||
* Result of executing a code cell.
|
||||
*/
|
||||
export interface IExecuteResult extends nb.ICellOutput {
|
||||
/**
|
||||
* Type of cell output.
|
||||
*/
|
||||
output_type: 'execute_result';
|
||||
|
||||
/**
|
||||
* A result's prompt number.
|
||||
*/
|
||||
execution_count: ExecutionCount;
|
||||
|
||||
/**
|
||||
* A mime-type keyed dictionary of data.
|
||||
*/
|
||||
data: IMimeBundle;
|
||||
|
||||
/**
|
||||
* Cell output metadata.
|
||||
*/
|
||||
metadata: OutputMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data displayed as a result of code cell execution.
|
||||
*/
|
||||
export interface IDisplayData extends nb.ICellOutput {
|
||||
/**
|
||||
* Type of cell output.
|
||||
*/
|
||||
output_type: 'display_data';
|
||||
|
||||
/**
|
||||
* A mime-type keyed dictionary of data.
|
||||
*/
|
||||
data: IMimeBundle;
|
||||
|
||||
/**
|
||||
* Cell output metadata.
|
||||
*/
|
||||
metadata: OutputMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data displayed as an update to existing display data.
|
||||
*/
|
||||
export interface IDisplayUpdate extends nb.ICellOutput {
|
||||
/**
|
||||
* Type of cell output.
|
||||
*/
|
||||
output_type: 'update_display_data';
|
||||
|
||||
/**
|
||||
* A mime-type keyed dictionary of data.
|
||||
*/
|
||||
data: IMimeBundle;
|
||||
|
||||
/**
|
||||
* Cell output metadata.
|
||||
*/
|
||||
metadata: OutputMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream output from a code cell.
|
||||
*/
|
||||
export interface IStream extends nb.ICellOutput {
|
||||
/**
|
||||
* Type of cell output.
|
||||
*/
|
||||
output_type: 'stream';
|
||||
|
||||
/**
|
||||
* The name of the stream.
|
||||
*/
|
||||
name: StreamType;
|
||||
|
||||
/**
|
||||
* The stream's text output.
|
||||
*/
|
||||
text: MultilineString;
|
||||
}
|
||||
|
||||
/**
|
||||
* An alias for a stream type.
|
||||
*/
|
||||
export type StreamType = 'stdout' | 'stderr';
|
||||
|
||||
/**
|
||||
* Output of an error that occurred during code cell execution.
|
||||
*/
|
||||
export interface IError extends nb.ICellOutput {
|
||||
/**
|
||||
* Type of cell output.
|
||||
*/
|
||||
output_type: 'error';
|
||||
|
||||
/**
|
||||
* The name of the error.
|
||||
*/
|
||||
ename: string;
|
||||
|
||||
/**
|
||||
* The value, or message, of the error.
|
||||
*/
|
||||
evalue: string;
|
||||
|
||||
/**
|
||||
* The error's traceback.
|
||||
*/
|
||||
traceback: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unrecognized output.
|
||||
*/
|
||||
export interface IUnrecognizedOutput extends nb.ICellOutput { }
|
||||
|
||||
/**
|
||||
* Test whether an output is an execute result.
|
||||
*/
|
||||
export function isExecuteResult(output: IOutput): output is IExecuteResult {
|
||||
return output.output_type === 'execute_result';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether an output is from display data.
|
||||
*/
|
||||
export function isDisplayData(output: IOutput): output is IDisplayData {
|
||||
return output.output_type === 'display_data';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether an output is from updated display data.
|
||||
*/
|
||||
export function isDisplayUpdate(output: IOutput): output is IDisplayUpdate {
|
||||
return output.output_type === 'update_display_data';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether an output is from a stream.
|
||||
*/
|
||||
export function isStream(output: IOutput): output is IStream {
|
||||
return output.output_type === 'stream';
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether an output is from a stream.
|
||||
*/
|
||||
export function isError(output: IOutput): output is IError {
|
||||
return output.output_type === 'error';
|
||||
}
|
||||
|
||||
/**
|
||||
* An output union type.
|
||||
*/
|
||||
export type IOutput =
|
||||
| IUnrecognizedOutput
|
||||
| IExecuteResult
|
||||
| IDisplayData
|
||||
| IStream
|
||||
| IError;
|
||||
}
|
||||
|
||||
export interface ICellOutputWithIdAndTrust extends nb.ICellOutput {
|
||||
id: number;
|
||||
trusted: boolean;
|
||||
}
|
||||
@@ -16,7 +16,7 @@ import { IClientSession, INotebookModel, IDefaultConnection, INotebookModelOptio
|
||||
import { NotebookChangeType, CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { nbversion } from '../notebookConstants';
|
||||
import * as notebookUtils from '../notebookUtils';
|
||||
import { INotebookManager } from 'sql/services/notebook/notebookService';
|
||||
import { INotebookManager, SQL_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
||||
import { SparkMagicContexts } from 'sql/parts/notebook/models/sparkMagicContexts';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { NotebookConnection } from 'sql/parts/notebook/models/notebookConnection';
|
||||
@@ -45,7 +45,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
private _contentChangedEmitter = new Emitter<NotebookContentChange>();
|
||||
private _kernelsChangedEmitter = new Emitter<nb.IKernelSpec>();
|
||||
private _inErrorState: boolean = false;
|
||||
private _clientSession: IClientSession;
|
||||
private _clientSessions: IClientSession[] = [];
|
||||
private _activeClientSession: IClientSession;
|
||||
private _sessionLoadFinished: Promise<void>;
|
||||
private _onClientSessionReady = new Emitter<IClientSession>();
|
||||
private _activeContexts: IDefaultConnection;
|
||||
@@ -60,20 +61,30 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
private _hadoopConnection: NotebookConnection;
|
||||
private _defaultKernel: nb.IKernelSpec;
|
||||
private _activeCell: ICellModel;
|
||||
private _providerId: string;
|
||||
|
||||
constructor(private notebookOptions: INotebookModelOptions, startSessionImmediately?: boolean, private connectionProfile?: IConnectionProfile) {
|
||||
super();
|
||||
if (!notebookOptions || !notebookOptions.notebookUri || !notebookOptions.notebookManager) {
|
||||
if (!notebookOptions || !notebookOptions.notebookUri || !notebookOptions.notebookManagers) {
|
||||
throw new Error('path or notebook service not defined');
|
||||
}
|
||||
if (startSessionImmediately) {
|
||||
this.backgroundStartSession();
|
||||
}
|
||||
this._trustedMode = false;
|
||||
this._providerId = notebookOptions.providerId;
|
||||
}
|
||||
|
||||
public get notebookManagers(): INotebookManager[] {
|
||||
let notebookManagers = this.notebookOptions.notebookManagers.filter(manager => manager.providerId !== DEFAULT_NOTEBOOK_PROVIDER);
|
||||
if (!notebookManagers.length) {
|
||||
return this.notebookOptions.notebookManagers;
|
||||
}
|
||||
return notebookManagers;
|
||||
}
|
||||
|
||||
public get notebookManager(): INotebookManager {
|
||||
return this.notebookOptions.notebookManager;
|
||||
return this.notebookManagers.find(manager => manager.providerId === this._providerId);
|
||||
}
|
||||
|
||||
public get notebookUri() : URI {
|
||||
@@ -93,7 +104,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
}
|
||||
|
||||
public get isSessionReady(): boolean {
|
||||
return !!this._clientSession;
|
||||
return !!this._activeClientSession;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,11 +113,11 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
* notebook environment
|
||||
*/
|
||||
public get clientSession(): IClientSession {
|
||||
return this._clientSession;
|
||||
return this._activeClientSession;
|
||||
}
|
||||
|
||||
public get kernelChanged(): Event<nb.IKernelChangedArgs> {
|
||||
return this.clientSession.kernelChanged;
|
||||
return this._activeClientSession.kernelChanged;
|
||||
}
|
||||
|
||||
public get kernelsChanged(): Event<nb.IKernelSpec> {
|
||||
@@ -130,7 +141,21 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
}
|
||||
|
||||
public get specs(): nb.IAllKernels | undefined {
|
||||
return this.notebookManager.sessionManager.specs;
|
||||
let specs: nb.IAllKernels = {
|
||||
defaultKernel: undefined,
|
||||
kernels: []
|
||||
};
|
||||
this.notebookManagers.forEach(manager => {
|
||||
if (manager.sessionManager && manager.sessionManager.specs && manager.sessionManager.specs.kernels) {
|
||||
manager.sessionManager.specs.kernels.forEach(kernel => {
|
||||
specs.kernels.push(kernel);
|
||||
});
|
||||
if (!specs.defaultKernel) {
|
||||
specs.defaultKernel = manager.sessionManager.specs.defaultKernel;
|
||||
}
|
||||
}
|
||||
});
|
||||
return specs;
|
||||
}
|
||||
|
||||
public get inErrorState(): boolean {
|
||||
@@ -145,6 +170,10 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
return this._trustedMode;
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return this._providerId;
|
||||
}
|
||||
|
||||
public set trustedMode(isTrusted: boolean) {
|
||||
this._trustedMode = isTrusted;
|
||||
if (this._cells) {
|
||||
@@ -178,11 +207,16 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
this._trustedMode = isTrusted;
|
||||
let contents = null;
|
||||
if (this.notebookOptions.notebookUri.scheme !== Schemas.untitled) {
|
||||
contents = await this.notebookManager.contentManager.getNotebookContents(this.notebookOptions.notebookUri);
|
||||
// TODO: separate ContentManager from NotebookManager
|
||||
contents = await this.notebookManagers[0].contentManager.getNotebookContents(this.notebookOptions.notebookUri);
|
||||
}
|
||||
let factory = this.notebookOptions.factory;
|
||||
// if cells already exist, create them with language info (if it is saved)
|
||||
this._cells = undefined;
|
||||
this._defaultLanguageInfo = {
|
||||
name: this._providerId === SQL_NOTEBOOK_PROVIDER ? 'sql' : 'python',
|
||||
version: ''
|
||||
};
|
||||
if (contents) {
|
||||
this._defaultLanguageInfo = this.getDefaultLanguageInfo(contents);
|
||||
this._savedKernelInfo = this.getSavedKernelInfo(contents);
|
||||
@@ -203,9 +237,9 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
return this._cells.findIndex((cell) => cell.equals(cellModel));
|
||||
}
|
||||
|
||||
public addCell(cellType: CellType, index?: number): void {
|
||||
public addCell(cellType: CellType, index?: number): ICellModel {
|
||||
if (this.inErrorState || !this._cells) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
let cell = this.createCell(cellType);
|
||||
|
||||
@@ -215,12 +249,18 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
this._cells.push(cell);
|
||||
index = undefined;
|
||||
}
|
||||
// Set newly created cell as active cell
|
||||
this._activeCell.active = false;
|
||||
this._activeCell = cell;
|
||||
this._activeCell.active = true;
|
||||
|
||||
this._contentChangedEmitter.fire({
|
||||
changeType: NotebookChangeType.CellsAdded,
|
||||
cells: [cell],
|
||||
cellIndex: index
|
||||
});
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
private createCell(cellType: CellType): ICellModel {
|
||||
@@ -282,29 +322,36 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
}
|
||||
|
||||
public backgroundStartSession(): void {
|
||||
this._clientSession = this.notebookOptions.factory.createClientSession({
|
||||
notebookUri: this.notebookOptions.notebookUri,
|
||||
notebookManager: this.notebookManager,
|
||||
notificationService: this.notebookOptions.notificationService
|
||||
});
|
||||
let profile = this.connectionProfile as IConnectionProfile;
|
||||
|
||||
if (this.isValidKnoxConnection(profile)) {
|
||||
this._hadoopConnection = new NotebookConnection(this.connectionProfile);
|
||||
} else {
|
||||
this._hadoopConnection = undefined;
|
||||
}
|
||||
|
||||
this._clientSession.initialize(this._hadoopConnection);
|
||||
this._sessionLoadFinished = this._clientSession.ready.then(async () => {
|
||||
if (this._clientSession.isInErrorState) {
|
||||
this.setErrorState(this._clientSession.errorMessage);
|
||||
} else {
|
||||
this._onClientSessionReady.fire(this._clientSession);
|
||||
// Once session is loaded, can use the session manager to retrieve useful info
|
||||
this.loadKernelInfo();
|
||||
await this.loadActiveContexts(undefined);
|
||||
// TODO: only one session should be active at a time, depending on the current provider
|
||||
this.notebookManagers.forEach(manager => {
|
||||
let clientSession = this.notebookOptions.factory.createClientSession({
|
||||
notebookUri: this.notebookOptions.notebookUri,
|
||||
notebookManager: manager,
|
||||
notificationService: this.notebookOptions.notificationService
|
||||
});
|
||||
this._clientSessions.push(clientSession);
|
||||
if (!this._activeClientSession) {
|
||||
this._activeClientSession = clientSession;
|
||||
}
|
||||
let profile = this.connectionProfile as IConnectionProfile;
|
||||
|
||||
if (this.isValidKnoxConnection(profile)) {
|
||||
this._hadoopConnection = new NotebookConnection(this.connectionProfile);
|
||||
} else {
|
||||
this._hadoopConnection = undefined;
|
||||
}
|
||||
|
||||
clientSession.initialize(this._hadoopConnection);
|
||||
this._sessionLoadFinished = clientSession.ready.then(async () => {
|
||||
if (clientSession.isInErrorState) {
|
||||
this.setErrorState(clientSession.errorMessage);
|
||||
} else {
|
||||
this._onClientSessionReady.fire(clientSession);
|
||||
// Once session is loaded, can use the session manager to retrieve useful info
|
||||
this.loadKernelInfo();
|
||||
await this.loadActiveContexts(undefined);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -328,7 +375,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
}
|
||||
|
||||
public doChangeKernel(kernelSpec: nb.IKernelSpec): Promise<void> {
|
||||
return this._clientSession.changeKernel(kernelSpec)
|
||||
this.findProviderIdForKernel(kernelSpec);
|
||||
return this._activeClientSession.changeKernel(kernelSpec)
|
||||
.then((kernel) => {
|
||||
kernel.ready.then(() => {
|
||||
if (kernel.info) {
|
||||
@@ -353,7 +401,7 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
SparkMagicContexts.configureContext(this.notebookOptions);
|
||||
this._hadoopConnection = new NotebookConnection(newConnection);
|
||||
this.refreshConnections(newConnection);
|
||||
this._clientSession.updateConnection(this._hadoopConnection);
|
||||
this._activeClientSession.updateConnection(this._hadoopConnection);
|
||||
} catch (err) {
|
||||
let msg = notebookUtils.getErrorMessage(err);
|
||||
this.notifyError(localize('changeContextFailed', 'Changing context failed: {0}', msg));
|
||||
@@ -375,20 +423,27 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
}
|
||||
|
||||
private loadKernelInfo(): void {
|
||||
this.clientSession.kernelChanged(async (e) => {
|
||||
await this.loadActiveContexts(e);
|
||||
this._clientSessions.forEach(clientSession => {
|
||||
clientSession.kernelChanged(async (e) => {
|
||||
await this.loadActiveContexts(e);
|
||||
});
|
||||
});
|
||||
if (!this.notebookManager) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let sessionManager = this.notebookManager.sessionManager;
|
||||
if (sessionManager) {
|
||||
let defaultKernel = SparkMagicContexts.getDefaultKernel(sessionManager.specs, this.connectionProfile, this._savedKernelInfo, this.notebookOptions.notificationService);
|
||||
this._defaultKernel = defaultKernel;
|
||||
this._clientSession.statusChanged(async (session) => {
|
||||
if (session && session.defaultKernelLoaded === true) {
|
||||
this._kernelsChangedEmitter.fire(defaultKernel);
|
||||
} else if (session && !session.defaultKernelLoaded) {
|
||||
this._kernelsChangedEmitter.fire({ name: notebookConstants.python3, display_name: notebookConstants.python3DisplayName });
|
||||
}
|
||||
this._clientSessions.forEach(clientSession => {
|
||||
clientSession.statusChanged(async (session) => {
|
||||
if (session && session.defaultKernelLoaded === true) {
|
||||
this._kernelsChangedEmitter.fire(defaultKernel);
|
||||
} else if (session && !session.defaultKernelLoaded) {
|
||||
this._kernelsChangedEmitter.fire({ name: notebookConstants.python3, display_name: notebookConstants.python3DisplayName });
|
||||
}
|
||||
});
|
||||
});
|
||||
this.doChangeKernel(defaultKernel);
|
||||
}
|
||||
@@ -402,9 +457,9 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
// Otherwise, default to python
|
||||
private getDefaultLanguageInfo(notebook: nb.INotebookContents): nb.ILanguageInfo {
|
||||
return notebook!.metadata!.language_info || {
|
||||
name: 'python',
|
||||
name: this._providerId === SQL_NOTEBOOK_PROVIDER ? 'sql' : 'python',
|
||||
version: '',
|
||||
mimetype: 'x-python'
|
||||
mimetype: this._providerId === SQL_NOTEBOOK_PROVIDER ? 'x-sql' : 'x-python'
|
||||
};
|
||||
}
|
||||
|
||||
@@ -438,9 +493,10 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
|
||||
public async handleClosed(): Promise<void> {
|
||||
try {
|
||||
if (this._clientSession) {
|
||||
await this._clientSession.shutdown();
|
||||
this._clientSession = undefined;
|
||||
if (this._activeClientSession) {
|
||||
await this._activeClientSession.shutdown();
|
||||
this._clientSessions = undefined;
|
||||
this._activeClientSession = undefined;
|
||||
}
|
||||
} catch (err) {
|
||||
this.notifyError(localize('shutdownError', 'An error occurred when closing the notebook: {0}', err));
|
||||
@@ -476,7 +532,8 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
if (!notebook) {
|
||||
return false;
|
||||
}
|
||||
await this.notebookManager.contentManager.save(this.notebookOptions.notebookUri, notebook);
|
||||
// TODO: refactor ContentManager out from NotebookManager
|
||||
await this.notebookManagers[0].contentManager.save(this.notebookOptions.notebookUri, notebook);
|
||||
this._contentChangedEmitter.fire({
|
||||
changeType: NotebookChangeType.DirtyStateChanged,
|
||||
isDirty: false
|
||||
@@ -498,6 +555,23 @@ export class NotebookModel extends Disposable implements INotebookModel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set _providerId and _activeClientSession based on a kernelSpec representing new kernel
|
||||
* @param kernelSpec KernelSpec for new kernel
|
||||
*/
|
||||
private findProviderIdForKernel(kernelSpec: nb.IKernelSpec): void {
|
||||
for (let i = 0; i < this.notebookManagers.length; i++) {
|
||||
if (this.notebookManagers[i].sessionManager && this.notebookManagers[i].sessionManager.specs && this.notebookManagers[i].sessionManager.specs.kernels) {
|
||||
let index = this.notebookManagers[i].sessionManager.specs.kernels.findIndex(kernel => kernel.name === kernelSpec.name);
|
||||
if (index >= 0) {
|
||||
this._activeClientSession = this._clientSessions[i];
|
||||
this._providerId = this.notebookManagers[i].providerId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Serialize the model to JSON.
|
||||
*/
|
||||
|
||||
@@ -34,7 +34,7 @@ import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { ICellModel, IModelFactory, notebookConstants, INotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_FILETYPE, DEFAULT_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
||||
import { INotebookService, INotebookParams, INotebookManager, INotebookEditor, DEFAULT_NOTEBOOK_FILETYPE, DEFAULT_NOTEBOOK_PROVIDER, SQL_NOTEBOOK_PROVIDER } from 'sql/services/notebook/notebookService';
|
||||
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
|
||||
@@ -63,14 +63,12 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
private _isInErrorState: boolean = false;
|
||||
private _errorMessage: string;
|
||||
protected _actionBar: Taskbar;
|
||||
private _activeCell: ICellModel;
|
||||
protected isLoading: boolean;
|
||||
private notebookManager: INotebookManager;
|
||||
private notebookManagers: INotebookManager[] = [];
|
||||
private _modelReadyDeferred = new Deferred<NotebookModel>();
|
||||
private _modelRegisteredDeferred = new Deferred<NotebookModel>();
|
||||
private profile: IConnectionProfile;
|
||||
private _trustedAction: TrustedAction;
|
||||
private _activeCellId: string;
|
||||
|
||||
|
||||
constructor(
|
||||
@@ -128,6 +126,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.dispose();
|
||||
if (this.notebookService) {
|
||||
this.notebookService.removeNotebookEditor(this);
|
||||
}
|
||||
@@ -138,7 +137,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
}
|
||||
|
||||
public get activeCellId(): string {
|
||||
return this._activeCellId;
|
||||
return this._model && this._model.activeCell ? this._model.activeCell.id : '';
|
||||
}
|
||||
|
||||
public get modelRegistered(): Promise<NotebookModel> {
|
||||
@@ -158,32 +157,28 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
if (event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
if (cell !== this._activeCell) {
|
||||
if (this._activeCell) {
|
||||
this._activeCell.active = false;
|
||||
if (cell !== this.model.activeCell) {
|
||||
if (this.model.activeCell) {
|
||||
this.model.activeCell.active = false;
|
||||
}
|
||||
this._activeCell = cell;
|
||||
this._activeCell.active = true;
|
||||
this._model.activeCell = this._activeCell;
|
||||
this._activeCellId = cell.id;
|
||||
this._model.activeCell = cell;
|
||||
this._model.activeCell.active = true;
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
public unselectActiveCell() {
|
||||
if (this._activeCell) {
|
||||
this._activeCell.active = false;
|
||||
if (this.model.activeCell) {
|
||||
this.model.activeCell.active = false;
|
||||
}
|
||||
this._activeCell = null;
|
||||
this._model.activeCell = null;
|
||||
this._activeCellId = null;
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
// Add cell based on cell type
|
||||
public addCell(cellType: CellType)
|
||||
{
|
||||
this._model.addCell(cellType);
|
||||
let newCell = this._model.addCell(cellType);
|
||||
this.selectCell(newCell);
|
||||
}
|
||||
|
||||
// Updates Notebook model's trust details
|
||||
@@ -201,12 +196,12 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
case 'ArrowRight':
|
||||
let nextIndex = (this.findCellIndex(this._activeCell) + 1) % this.cells.length;
|
||||
let nextIndex = (this.findCellIndex(this.model.activeCell) + 1) % this.cells.length;
|
||||
this.selectCell(this.cells[nextIndex]);
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
case 'ArrowLeft':
|
||||
let index = this.findCellIndex(this._activeCell);
|
||||
let index = this.findCellIndex(this.model.activeCell);
|
||||
if (index === 0) {
|
||||
index = this.cells.length;
|
||||
}
|
||||
@@ -236,22 +231,29 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
|
||||
private async loadModel(): Promise<void> {
|
||||
await this.awaitNonDefaultProvider();
|
||||
this.notebookManager = await this.notebookService.getOrCreateNotebookManager(this._notebookParams.providerId, this._notebookParams.notebookUri);
|
||||
for (let providerId of this._notebookParams.providers) {
|
||||
let notebookManager = await this.notebookService.getOrCreateNotebookManager(providerId, this._notebookParams.notebookUri);
|
||||
this.notebookManagers.push(notebookManager);
|
||||
}
|
||||
let model = new NotebookModel({
|
||||
factory: this.modelFactory,
|
||||
notebookUri: this._notebookParams.notebookUri,
|
||||
connectionService: this.connectionManagementService,
|
||||
notificationService: this.notificationService,
|
||||
notebookManager: this.notebookManager
|
||||
notebookManagers: this.notebookManagers,
|
||||
providerId: notebookUtils.sqlNotebooksEnabled() ? 'sql' : 'jupyter' // this is tricky; really should also depend on the connection profile
|
||||
}, false, this.profile);
|
||||
model.onError((errInfo: INotification) => this.handleModelError(errInfo));
|
||||
await model.requestModelLoad(this._notebookParams.isTrusted);
|
||||
model.contentChanged((change) => this.handleContentChanged(change));
|
||||
this._model = model;
|
||||
this._model = this._register(model);
|
||||
this.updateToolbarComponents(this._model.trustedMode);
|
||||
this._register(model);
|
||||
this._modelRegisteredDeferred.resolve(this._model);
|
||||
model.backgroundStartSession();
|
||||
// Set first cell as default active cell
|
||||
if (this._model && this._model.cells && this._model.cells[0]) {
|
||||
this.selectCell(model.cells[0]);
|
||||
}
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
|
||||
@@ -260,7 +262,9 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
await this.notebookService.registrationComplete;
|
||||
// Refresh the provider if we had been using default
|
||||
if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) {
|
||||
this._notebookParams.providerId = notebookUtils.getProviderForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService);
|
||||
let providers= notebookUtils.getProvidersForFileName(this._notebookParams.notebookUri.fsPath, this.notebookService);
|
||||
let tsqlProvider = providers.find(provider => provider === SQL_NOTEBOOK_PROVIDER);
|
||||
this._notebookParams.providerId = tsqlProvider ? SQL_NOTEBOOK_PROVIDER : providers[0];
|
||||
}
|
||||
if (DEFAULT_NOTEBOOK_PROVIDER === this._notebookParams.providerId) {
|
||||
// If it's still the default, warn them they should install an extension
|
||||
|
||||
@@ -88,6 +88,7 @@ export class NotebookEditor extends BaseEditor {
|
||||
notebookUri: input.notebookUri,
|
||||
input: input,
|
||||
providerId: input.providerId ? input.providerId : DEFAULT_NOTEBOOK_PROVIDER,
|
||||
providers: input.providers ? input.providers : [DEFAULT_NOTEBOOK_PROVIDER],
|
||||
isTrusted: input.isTrusted
|
||||
};
|
||||
bootstrapAngular(this.instantiationService,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user