mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-19 11:01:38 -05:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
637dc9b9b2 | ||
|
|
1de16d4715 | ||
|
|
49090d774d | ||
|
|
9a695b5cdd | ||
|
|
e0339b50c0 | ||
|
|
d0c584672f | ||
|
|
27816acaeb | ||
|
|
4de3cc8a09 | ||
|
|
5c16ceb2fa | ||
|
|
9db3f73413 | ||
|
|
e0ceddce09 | ||
|
|
6dc4096299 | ||
|
|
1fa03b5c74 | ||
|
|
f8f57a93c3 | ||
|
|
960fe63312 | ||
|
|
7545b94128 | ||
|
|
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}",
|
"webRoot": "${workspaceFolder}",
|
||||||
"timeout": 45000
|
"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",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
|
|||||||
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,5 +1,27 @@
|
|||||||
# Change Log
|
# 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
|
## Version 1.2.4
|
||||||
* Release date: November 6, 2018
|
* Release date: November 6, 2018
|
||||||
* Release status: General Availability
|
* 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
|
Platform | Link
|
||||||
-- | --
|
-- | --
|
||||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2038320
|
Windows User Installer | https://go.microsoft.com/fwlink/?linkid=2049972
|
||||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2038323
|
Windows System Installer | https://go.microsoft.com/fwlink/?linkid=2049975
|
||||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2038327
|
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2050146
|
||||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2038332
|
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2049981
|
||||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2038401
|
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2049986
|
||||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2038405
|
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.
|
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"
|
## 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:
|
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`
|
* rdaniels6813 for `Add query plan theme support #3031`
|
||||||
* Ruturaj123 for `Fixed some typos and grammatical errors #3027`
|
* Ruturaj123 for `Fixed some typos and grammatical errors #3027`
|
||||||
* PromoFaux for `Use emoji shortcodes in CONTRIBUTING.md instead of <20> #3009`
|
* PromoFaux for `Use emoji shortcodes in CONTRIBUTING.md instead of <20> #3009`
|
||||||
|
|||||||
@@ -36,3 +36,7 @@ steps:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFiles: '**/test-results.xml'
|
testResultsFiles: '**/test-results.xml'
|
||||||
condition: succeededOrFailed()
|
condition: succeededOrFailed()
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
yarn run tslint
|
||||||
|
displayName: 'Run TSLint'
|
||||||
@@ -24,3 +24,7 @@ steps:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFiles: 'test-results.xml'
|
testResultsFiles: 'test-results.xml'
|
||||||
condition: succeededOrFailed()
|
condition: succeededOrFailed()
|
||||||
|
|
||||||
|
- script: |
|
||||||
|
yarn run tslint
|
||||||
|
displayName: 'Run TSLint'
|
||||||
@@ -17,12 +17,12 @@
|
|||||||
"configuration": [
|
"configuration": [
|
||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "%azure.config.title%",
|
"title": "%azure.resource.config.title%",
|
||||||
"properties": {
|
"properties": {
|
||||||
"azureResource.resourceFilter": {
|
"azure.resource.config.filter": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"default": null,
|
"default": null,
|
||||||
"description": "%azure.resourceFilter.description%"
|
"description": "%azure.resource.config.filter.description%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -61,39 +61,47 @@
|
|||||||
"category": "Azure Accounts"
|
"category": "Azure Accounts"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.refreshall",
|
"command": "azure.resource.refreshall",
|
||||||
"title": "%azureresource.refreshall%",
|
"title": "%azure.resource.refreshall.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/refresh_inverse.svg",
|
"dark": "resources/dark/refresh_inverse.svg",
|
||||||
"light": "resources/light/refresh.svg"
|
"light": "resources/light/refresh.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.refresh",
|
"command": "azure.resource.refresh",
|
||||||
"title": "%azureresource.refresh%",
|
"title": "%azure.resource.refresh.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/refresh_inverse.svg",
|
"dark": "resources/dark/refresh_inverse.svg",
|
||||||
"light": "resources/light/refresh.svg"
|
"light": "resources/light/refresh.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.signin",
|
"command": "azure.resource.signin",
|
||||||
"title": "%azureresource.signin%"
|
"title": "%azure.resource.signin.title%"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.connectsqldb",
|
"command": "azure.resource.selectsubscriptions",
|
||||||
"title": "%azureresource.connectsqldb%",
|
"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": {
|
"icon": {
|
||||||
"dark": "resources/dark/connect_to_inverse.svg",
|
"dark": "resources/dark/connect_to_inverse.svg",
|
||||||
"light": "resources/light/connect_to.svg"
|
"light": "resources/light/connect_to.svg"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.selectsubscriptions",
|
"command": "azure.resource.connectsqldb",
|
||||||
"title": "%azureresource.selectsubscriptions%",
|
"title": "%azure.resource.connectsqldb.title%",
|
||||||
"icon": {
|
"icon": {
|
||||||
"dark": "resources/dark/filter_inverse.svg",
|
"dark": "resources/dark/connect_to_inverse.svg",
|
||||||
"light": "resources/light/filter.svg"
|
"light": "resources/light/connect_to.svg"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -110,46 +118,47 @@
|
|||||||
"azureResource": [
|
"azureResource": [
|
||||||
{
|
{
|
||||||
"id": "azureResourceExplorer",
|
"id": "azureResourceExplorer",
|
||||||
"name": "%azure.resourceExplorer.title%"
|
"name": "%azure.resource.explorer.title%"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"menus": {
|
"menus": {
|
||||||
"view/title": [
|
"view/title": [
|
||||||
{
|
{
|
||||||
"command": "azureresource.refreshall",
|
"command": "azure.resource.refreshall",
|
||||||
"when": "view == azureResourceExplorer",
|
"when": "view == azureResourceExplorer",
|
||||||
"group": "navigation@1"
|
"group": "navigation@1"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"view/item/context": [
|
"view/item/context": [
|
||||||
{
|
{
|
||||||
"command": "azureresource.connectsqldb",
|
"command": "azure.resource.selectsubscriptions",
|
||||||
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
|
"when": "viewItem == azure.resource.itemType.account",
|
||||||
"group": "1azureresource@1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"command": "azureresource.connectsqldb",
|
|
||||||
"when": "viewItem =~ /^azureResource\\.itemType\\.database(?:Server){0,1}$/",
|
|
||||||
"group": "inline"
|
"group": "inline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.selectsubscriptions",
|
"command": "azure.resource.refresh",
|
||||||
"when": "viewItem == azureResource.itemType.account",
|
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
|
||||||
"group": "inline"
|
"group": "inline"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"command": "azureresource.refresh",
|
"command": "azure.resource.connectsqlserver",
|
||||||
"when": "viewItem =~ /^azureResource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
|
"when": "viewItem == azure.resource.itemType.databaseServer",
|
||||||
|
"group": "inline"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "azure.resource.connectsqldb",
|
||||||
|
"when": "viewItem == azure.resource.itemType.database",
|
||||||
"group": "inline"
|
"group": "inline"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"hasAzureResourceProviders": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"request": "2.88.0",
|
|
||||||
"azure-arm-resource": "^7.0.0",
|
"azure-arm-resource": "^7.0.0",
|
||||||
"azure-arm-sql": "^5.0.1",
|
"azure-arm-sql": "^5.0.1",
|
||||||
|
"request": "2.88.0",
|
||||||
"vscode-nls": "^4.0.0"
|
"vscode-nls": "^4.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -157,6 +166,7 @@
|
|||||||
"@types/node": "^8.0.24",
|
"@types/node": "^8.0.24",
|
||||||
"mocha": "^5.2.0",
|
"mocha": "^5.2.0",
|
||||||
"should": "^13.2.1",
|
"should": "^13.2.1",
|
||||||
|
"vscode": "^1.1.26",
|
||||||
"typemoq": "^2.1.0"
|
"typemoq": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,20 @@
|
|||||||
{
|
{
|
||||||
"azure.displayName": "Azure (Core)",
|
"azure.displayName": "Azure (Core)",
|
||||||
"azure.description": "Browse and work with Azure resources",
|
"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.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",
|
"accounts.clearTokenCache": "Clear Azure Account Token Cache",
|
||||||
|
|
||||||
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
|
||||||
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",
|
||||||
"config.enableChinaCloudDescription": "Should Azure China 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';
|
'use strict';
|
||||||
|
|
||||||
import { window, QuickPickItem } from 'vscode';
|
import { window, QuickPickItem } from 'vscode';
|
||||||
import * as sqlops from 'sqlops';
|
import { AzureResource } from 'sqlops';
|
||||||
import { generateGuid } from './utils';
|
import { TokenCredentials } from 'ms-rest';
|
||||||
import { ApiWrapper } from '../apiWrapper';
|
import { AppContext } from '../appContext';
|
||||||
import { TreeNode } from '../treeNodes';
|
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 { AzureResourceTreeProvider } from './tree/treeProvider';
|
||||||
import { AzureResourceDatabaseServerTreeNode } from './tree/databaseServerTreeNode';
|
|
||||||
import { AzureResourceDatabaseTreeNode } from './tree/databaseTreeNode';
|
|
||||||
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from './tree/accountTreeNode';
|
||||||
import { AzureResourceServicePool } from './servicePool';
|
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService } from '../azureResource/interfaces';
|
||||||
import { AzureResourceSubscription } from './models';
|
import { AzureResourceServiceNames } from './constants';
|
||||||
|
|
||||||
export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: AzureResourceTreeProvider): void {
|
export function registerAzureResourceCommands(appContext: AppContext, tree: AzureResourceTreeProvider): void {
|
||||||
apiWrapper.registerCommand('azureresource.selectsubscriptions', async (node?: TreeNode) => {
|
appContext.apiWrapper.registerCommand('azure.resource.selectsubscriptions', async (node?: TreeNode) => {
|
||||||
if (!(node instanceof AzureResourceAccountTreeNode)) {
|
if (!(node instanceof AzureResourceAccountTreeNode)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const subscriptionService = appContext.getService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService);
|
||||||
|
const subscriptionFilterService = appContext.getService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService);
|
||||||
|
|
||||||
const accountNode = node as AzureResourceAccountTreeNode;
|
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();
|
for (const tenant of this.account.properties.tenants) {
|
||||||
if (!subscriptions || subscriptions.length === 0) {
|
const token = tokens[tenant.id].token;
|
||||||
const credentials = await servicePool.credentialService.getCredentials(accountNode.account, sqlops.AzureResource.ResourceManagement);
|
const tokenType = tokens[tenant.id].tokenType;
|
||||||
subscriptions = await servicePool.subscriptionService.getSubscriptions(accountNode.account, credentials);
|
|
||||||
|
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[] = [];
|
const selectedSubscriptionIds: string[] = [];
|
||||||
if (selectedSubscriptions.length > 0) {
|
if (selectedSubscriptions.length > 0) {
|
||||||
selectedSubscriptionIds.push(...selectedSubscriptions.map((subscription) => subscription.id));
|
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));
|
selectedSubscriptionIds.push(...subscriptions.map((subscription) => subscription.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubscriptionQuickPickItem extends QuickPickItem {
|
interface AzureResourceSubscriptionQuickPickItem extends QuickPickItem {
|
||||||
subscription: AzureResourceSubscription;
|
subscription: azureResource.AzureResourceSubscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
const subscriptionItems: SubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
const subscriptionQuickPickItems: AzureResourceSubscriptionQuickPickItem[] = subscriptions.map((subscription) => {
|
||||||
return {
|
return {
|
||||||
label: subscription.name,
|
label: subscription.name,
|
||||||
picked: selectedSubscriptionIds.indexOf(subscription.id) !== -1,
|
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 }));
|
const selectedSubscriptionQuickPickItems = (await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true }));
|
||||||
if (pickedSubscriptionItems && pickedSubscriptionItems.length > 0) {
|
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
|
||||||
tree.refresh(node, false);
|
tree.refresh(node, false);
|
||||||
|
|
||||||
const pickedSubscriptions = pickedSubscriptionItems.map((subscriptionItem) => subscriptionItem.subscription);
|
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
|
||||||
await servicePool.subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, pickedSubscriptions);
|
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);
|
tree.refresh(node, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
apiWrapper.registerCommand('azureresource.connectsqldb', async (node?: TreeNode) => {
|
appContext.apiWrapper.registerCommand('azure.resource.signin', async (node?: TreeNode) => {
|
||||||
let connectionProfile: sqlops.IConnectionProfile = {
|
appContext.apiWrapper.executeCommand('sql.action.accounts.manageLinkedAccount');
|
||||||
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');
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,19 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
export enum AzureResourceItemType {
|
export enum AzureResourceItemType {
|
||||||
account = 'azureResource.itemType.account',
|
account = 'azure.resource.itemType.account',
|
||||||
subscription = 'azureResource.itemType.subscription',
|
subscription = 'azure.resource.itemType.subscription',
|
||||||
databaseContainer = 'azureResource.itemType.databaseContainer',
|
databaseContainer = 'azure.resource.itemType.databaseContainer',
|
||||||
database = 'azureResource.itemType.database',
|
database = 'azure.resource.itemType.database',
|
||||||
databaseServerContainer = 'azureResource.itemType.databaseServerContainer',
|
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
|
||||||
databaseServer = 'azureResource.itemType.databaseServer',
|
databaseServer = 'azure.resource.itemType.databaseServer',
|
||||||
message = 'azureResource.itemType.message'
|
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 {
|
export class AzureResourceCredentialError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
public innerError: Error
|
public readonly innerError: Error
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,49 +6,40 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
import * as sqlops from 'sqlops';
|
import { Account, DidChangeAccountsParams } from 'sqlops';
|
||||||
import { Event } from 'vscode';
|
import { Event } from 'vscode';
|
||||||
|
|
||||||
import { AzureResourceSubscription, AzureResourceDatabaseServer, AzureResourceDatabase } from './models';
|
import { azureResource } from './azure-resource';
|
||||||
|
|
||||||
export interface IAzureResourceAccountService {
|
export interface IAzureResourceAccountService {
|
||||||
getAccounts(): Promise<sqlops.Account[]>;
|
getAccounts(): Promise<Account[]>;
|
||||||
|
|
||||||
readonly onDidChangeAccounts: Event<sqlops.DidChangeAccountsParams>;
|
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAzureResourceCredentialService {
|
|
||||||
getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceSubscriptionService {
|
export interface IAzureResourceSubscriptionService {
|
||||||
getSubscriptions(account: sqlops.Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]>;
|
getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceSubscriptionFilterService {
|
export interface IAzureResourceSubscriptionFilterService {
|
||||||
getSelectedSubscriptions(account: sqlops.Account): Promise<AzureResourceSubscription[]>;
|
getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]>;
|
||||||
|
|
||||||
saveSelectedSubscriptions(account: sqlops.Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void>;
|
saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAzureResourceDatabaseServerService {
|
|
||||||
getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAzureResourceDatabaseService {
|
|
||||||
getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceCacheService {
|
export interface IAzureResourceCacheService {
|
||||||
|
generateKey(id: string): string;
|
||||||
|
|
||||||
get<T>(key: string): T | undefined;
|
get<T>(key: string): T | undefined;
|
||||||
|
|
||||||
update<T>(key: string, value: T): void;
|
update<T>(key: string, value: T): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAzureResourceContextService {
|
export interface IAzureResourceTenantService {
|
||||||
getAbsolutePath(relativePath: string): string;
|
getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string>;
|
||||||
|
}
|
||||||
executeCommand(commandId: string, ...args: any[]): void;
|
|
||||||
|
export interface IAzureResourceNodeWithProviderId {
|
||||||
showErrorMessage(errorMessage: string): void;
|
resourceProviderId: string;
|
||||||
|
resourceNode: azureResource.IAzureResourceNode;
|
||||||
}
|
}
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { NodeInfo } from 'sqlops';
|
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 {
|
export class AzureResourceMessageTreeNode extends TreeNode {
|
||||||
public constructor(
|
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';
|
'use strict';
|
||||||
|
|
||||||
export interface AzureResourceSubscription {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AzureResourceDatabaseServer {
|
export interface AzureResourceDatabaseServer {
|
||||||
name: string;
|
name: string;
|
||||||
fullName: string;
|
fullName: string;
|
||||||
loginName: string;
|
loginName: string;
|
||||||
defaultDatabaseName: 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';
|
'use strict';
|
||||||
|
|
||||||
import { ExtensionContext } from "vscode";
|
import { ExtensionContext } from 'vscode';
|
||||||
|
|
||||||
import { IAzureResourceCacheService } from "../interfaces";
|
import { IAzureResourceCacheService } from '../interfaces';
|
||||||
|
|
||||||
export class AzureResourceCacheService implements IAzureResourceCacheService {
|
export class AzureResourceCacheService implements IAzureResourceCacheService {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly context: ExtensionContext
|
context: ExtensionContext
|
||||||
) {
|
) {
|
||||||
|
this._context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get<T>(key: string): T | undefined {
|
public generateKey(id: string): string {
|
||||||
return this.context.workspaceState.get(key);
|
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 {
|
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 { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
|
||||||
import { Account } from 'sqlops';
|
import { Account } from 'sqlops';
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
||||||
import { AzureResourceSubscription } from '../models';
|
|
||||||
|
|
||||||
interface AzureResourceSelectedSubscriptionsCache {
|
interface AzureResourceSelectedSubscriptionsCache {
|
||||||
selectedSubscriptions: { [accountId: string]: AzureResourceSubscription[]};
|
selectedSubscriptions: { [accountId: string]: azureResource.AzureResourceSubscription[]};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
||||||
@@ -20,12 +20,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
cacheService: IAzureResourceCacheService
|
cacheService: IAzureResourceCacheService
|
||||||
) {
|
) {
|
||||||
this._cacheService = cacheService;
|
this._cacheService = cacheService;
|
||||||
|
|
||||||
|
this._cacheKey = this._cacheService.generateKey('selectedSubscriptions');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]> {
|
public async getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
let selectedSubscriptions: AzureResourceSubscription[] = [];
|
let selectedSubscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||||
|
|
||||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
||||||
}
|
}
|
||||||
@@ -33,10 +35,10 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
return selectedSubscriptions;
|
return selectedSubscriptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void> {
|
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
|
||||||
let selectedSubscriptionsCache: { [accountId: string]: AzureResourceSubscription[]} = {};
|
let selectedSubscriptionsCache: { [accountId: string]: azureResource.AzureResourceSubscription[]} = {};
|
||||||
|
|
||||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||||
if (cache) {
|
if (cache) {
|
||||||
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
||||||
}
|
}
|
||||||
@@ -47,14 +49,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
|||||||
|
|
||||||
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
||||||
|
|
||||||
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||||
|
|
||||||
const filters: string[] = [];
|
const filters: string[] = [];
|
||||||
for (const accountId in selectedSubscriptionsCache) {
|
for (const accountId in selectedSubscriptionsCache) {
|
||||||
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
|
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;
|
let configTarget = ConfigurationTarget.Global;
|
||||||
if (resourceFilterConfig) {
|
if (resourceFilterConfig) {
|
||||||
if (resourceFilterConfig.workspaceFolderValue) {
|
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 _config: WorkspaceConfiguration = undefined;
|
||||||
private _cacheService: IAzureResourceCacheService = undefined;
|
private _cacheService: IAzureResourceCacheService = undefined;
|
||||||
|
private _cacheKey: string = undefined;
|
||||||
|
|
||||||
private static readonly FilterConfigName = 'resourceFilter';
|
private static readonly filterConfigName = 'azure.resource.config.filter';
|
||||||
private static readonly CacheKey = 'azureResource.cache.selectedSubscriptions';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,24 +9,19 @@ import { Account } from 'sqlops';
|
|||||||
import { ServiceClientCredentials } from 'ms-rest';
|
import { ServiceClientCredentials } from 'ms-rest';
|
||||||
import { SubscriptionClient } from 'azure-arm-resource';
|
import { SubscriptionClient } from 'azure-arm-resource';
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
import { IAzureResourceSubscriptionService } from '../interfaces';
|
import { IAzureResourceSubscriptionService } from '../interfaces';
|
||||||
import { AzureResourceSubscription } from '../models';
|
|
||||||
|
|
||||||
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
||||||
public async getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]> {
|
public async getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
let subscriptions: AzureResourceSubscription[] = [];
|
const subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||||
for (let cred of credentials) {
|
|
||||||
let subClient = new SubscriptionClient.SubscriptionClient(cred);
|
const subClient = new SubscriptionClient.SubscriptionClient(credential);
|
||||||
try {
|
const subs = await subClient.subscriptions.list();
|
||||||
let subs = await subClient.subscriptions.list();
|
subs.forEach((sub) => subscriptions.push({
|
||||||
subs.forEach((sub) => subscriptions.push({
|
id: sub.subscriptionId,
|
||||||
id: sub.subscriptionId,
|
name: sub.displayName
|
||||||
name: sub.displayName
|
}));
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
// Swallow the exception here.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return subscriptions;
|
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 { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { NodeInfo } from 'sqlops';
|
import { NodeInfo } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { TreeNode } from '../treeNode';
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { AzureResourceItemType } from '../constants';
|
||||||
|
|
||||||
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
||||||
@@ -19,11 +19,11 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
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.contextValue = AzureResourceItemType.message;
|
||||||
item.command = {
|
item.command = {
|
||||||
title: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
title: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||||
command: 'azureresource.signin',
|
command: 'azure.resource.signin',
|
||||||
arguments: [this]
|
arguments: [this]
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
@@ -31,7 +31,7 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
|||||||
|
|
||||||
public getNodeInfo(): NodeInfo {
|
public getNodeInfo(): NodeInfo {
|
||||||
return {
|
return {
|
||||||
label: AzureResourceAccountNotSignedInTreeNode.SignInLabel,
|
label: AzureResourceAccountNotSignedInTreeNode.signInLabel,
|
||||||
isLeaf: true,
|
isLeaf: true,
|
||||||
errorMessage: undefined,
|
errorMessage: undefined,
|
||||||
metadata: undefined,
|
metadata: undefined,
|
||||||
@@ -47,5 +47,5 @@ export class AzureResourceAccountNotSignedInTreeNode extends TreeNode {
|
|||||||
return 'message_accountNotSignedIn';
|
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';
|
'use strict';
|
||||||
|
|
||||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
import { Account, NodeInfo, AzureResource } from 'sqlops';
|
||||||
import { TreeNode } from '../../treeNodes';
|
import { TokenCredentials } from 'ms-rest';
|
||||||
|
import { AppContext } from '../../appContext';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { azureResource } from '../azure-resource';
|
||||||
|
import { TreeNode } from '../treeNode';
|
||||||
|
import { AzureResourceCredentialError } from '../errors';
|
||||||
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||||
import { AzureResourceItemType } from '../constants';
|
import { AzureResourceItemType, AzureResourceServiceNames } from '../constants';
|
||||||
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
import { AzureResourceSubscriptionTreeNode } from './subscriptionTreeNode';
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
import { AzureResourceSubscription } from '../models';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeProvider';
|
import { IAzureResourceSubscriptionService, IAzureResourceSubscriptionFilterService, IAzureResourceTenantService } from '../../azureResource/interfaces';
|
||||||
|
|
||||||
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNodeBase {
|
||||||
public constructor(
|
public constructor(
|
||||||
account: Account,
|
public readonly account: Account,
|
||||||
|
appContext: AppContext,
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler
|
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._id = `account_${this.account.key.accountId}`;
|
||||||
|
this.setCacheKey(`${this._id}.subscriptions`);
|
||||||
this._label = this.generateLabel();
|
this._label = this.generateLabel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
public async getChildren(): Promise<TreeNode[]> {
|
||||||
try {
|
try {
|
||||||
let subscriptions: AzureResourceSubscription[] = [];
|
let subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||||
|
|
||||||
if (this._isClearingCache) {
|
if (this._isClearingCache) {
|
||||||
const credentials = await this.getCredentials();
|
try {
|
||||||
subscriptions = (await this.servicePool.subscriptionService.getSubscriptions(this.account, credentials)) || <AzureResourceSubscription[]>[];
|
const tokens = await this.appContext.apiWrapper.getSecurityToken(this.account, AzureResource.ResourceManagement);
|
||||||
|
|
||||||
let cache = this.getCache<AzureResourceSubscriptionsCache>();
|
for (const tenant of this.account.properties.tenants) {
|
||||||
if (!cache) {
|
const token = tokens[tenant.id].token;
|
||||||
cache = { subscriptions: { } };
|
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;
|
this._isClearingCache = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -52,8 +67,8 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
|
|
||||||
this._totalSubscriptionCount = subscriptions.length;
|
this._totalSubscriptionCount = subscriptions.length;
|
||||||
|
|
||||||
let selectedSubscriptions = await this.servicePool.subscriptionFilterService.getSelectedSubscriptions(this.account);
|
const selectedSubscriptions = await this._subscriptionFilterService.getSelectedSubscriptions(this.account);
|
||||||
let selectedSubscriptionIds = (selectedSubscriptions || <AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
const selectedSubscriptionIds = (selectedSubscriptions || <azureResource.AzureResourceSubscription[]>[]).map((subscription) => subscription.id);
|
||||||
if (selectedSubscriptionIds.length > 0) {
|
if (selectedSubscriptionIds.length > 0) {
|
||||||
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
subscriptions = subscriptions.filter((subscription) => selectedSubscriptionIds.indexOf(subscription.id) !== -1);
|
||||||
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
this._selectedSubscriptionCount = selectedSubscriptionIds.length;
|
||||||
@@ -65,31 +80,36 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
this.refreshLabel();
|
this.refreshLabel();
|
||||||
|
|
||||||
if (subscriptions.length === 0) {
|
if (subscriptions.length === 0) {
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.NoSubscriptions, this)];
|
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
|
||||||
} else {
|
} 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) {
|
} 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[]> {
|
public async getCachedSubscriptions(): Promise<azureResource.AzureResourceSubscription[]> {
|
||||||
const subscriptions: AzureResourceSubscription[] = [];
|
return this.getCache<azureResource.AzureResourceSubscription[]>();
|
||||||
const cache = this.getCache<AzureResourceSubscriptionsCache>();
|
|
||||||
if (cache) {
|
|
||||||
subscriptions.push(...cache.subscriptions[this.account.key.accountId]);
|
|
||||||
}
|
|
||||||
return subscriptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTreeItem(): TreeItem | Promise<TreeItem> {
|
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.id = this._id;
|
||||||
item.contextValue = AzureResourceItemType.account;
|
item.contextValue = AzureResourceItemType.account;
|
||||||
item.iconPath = {
|
item.iconPath = {
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/account_inverse.svg'),
|
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/account_inverse.svg'),
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/account.svg')
|
light: this.appContext.extensionContext.asAbsolutePath('resources/light/account.svg')
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -128,10 +148,6 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected get cacheKey(): string {
|
|
||||||
return 'azureResource.cache.subscriptions';
|
|
||||||
}
|
|
||||||
|
|
||||||
private generateLabel(): string {
|
private generateLabel(): string {
|
||||||
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
|
let label = `${this.account.displayInfo.displayName} (${this.account.key.accountId})`;
|
||||||
|
|
||||||
@@ -142,14 +158,14 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _subscriptionService: IAzureResourceSubscriptionService = undefined;
|
||||||
|
private _subscriptionFilterService: IAzureResourceSubscriptionFilterService = undefined;
|
||||||
|
private _tenantService: IAzureResourceTenantService = undefined;
|
||||||
|
|
||||||
private _id: string = undefined;
|
private _id: string = undefined;
|
||||||
private _label: string = undefined;
|
private _label: string = undefined;
|
||||||
private _totalSubscriptionCount = 0;
|
private _totalSubscriptionCount = 0;
|
||||||
private _selectedSubscriptionCount = 0;
|
private _selectedSubscriptionCount = 0;
|
||||||
|
|
||||||
private static readonly NoSubscriptions = localize('azureResource.tree.accountTreeNode.noSubscriptions', 'No Subscriptions found.');
|
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', 'No Subscriptions found.');
|
||||||
}
|
|
||||||
|
|
||||||
interface AzureResourceSubscriptionsCache {
|
|
||||||
subscriptions: { [accountId: string]: AzureResourceSubscription[] };
|
|
||||||
}
|
}
|
||||||
@@ -5,16 +5,16 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import * as sqlops from 'sqlops';
|
import { AppContext } from '../../appContext';
|
||||||
import { ServiceClientCredentials } from 'ms-rest';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../servicePool';
|
import { TreeNode } from '../treeNode';
|
||||||
import { AzureResourceCredentialError } from '../errors';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
|
import { IAzureResourceCacheService } from '../../azureResource/interfaces';
|
||||||
|
import { AzureResourceServiceNames } from '../constants';
|
||||||
|
|
||||||
export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
||||||
public constructor(
|
public constructor(
|
||||||
|
public readonly appContext: AppContext,
|
||||||
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
|
public readonly treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||||
parent: TreeNode
|
parent: TreeNode
|
||||||
) {
|
) {
|
||||||
@@ -22,17 +22,17 @@ export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
|||||||
|
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly servicePool = AzureResourceServicePool.getInstance();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
|
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
|
||||||
public constructor(
|
public constructor(
|
||||||
public readonly account: sqlops.Account,
|
appContext: AppContext,
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||||
parent: TreeNode
|
parent: TreeNode
|
||||||
) {
|
) {
|
||||||
super(treeChangeHandler, parent);
|
super(appContext, treeChangeHandler, parent);
|
||||||
|
|
||||||
|
this._cacheService = this.appContext.getService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clearCache(): void {
|
public clearCache(): void {
|
||||||
@@ -43,29 +43,19 @@ export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTr
|
|||||||
return this._isClearingCache;
|
return this._isClearingCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getCredentials(): Promise<ServiceClientCredentials[]> {
|
protected setCacheKey(id: string): void {
|
||||||
try {
|
this._cacheKey = this._cacheService.generateKey(id);
|
||||||
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 updateCache<T>(cache: T): void {
|
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 {
|
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;
|
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 { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||||
import { Account, NodeInfo } from 'sqlops';
|
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 { AzureResourceItemType } from '../constants';
|
||||||
import { AzureResourceDatabaseContainerTreeNode } from './databaseContainerTreeNode';
|
|
||||||
import { AzureResourceDatabaseServerContainerTreeNode } from './databaseServerContainerTreeNode';
|
|
||||||
import { AzureResourceSubscription } from '../models';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
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 constructor(
|
||||||
public readonly subscription: AzureResourceSubscription,
|
public readonly account: Account,
|
||||||
account: Account,
|
public readonly subscription: azureResource.AzureResourceSubscription,
|
||||||
|
public readonly tenatId: string,
|
||||||
|
appContext: AppContext,
|
||||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||||
parent: TreeNode
|
parent: TreeNode
|
||||||
) {
|
) {
|
||||||
super(treeChangeHandler, parent);
|
super(appContext, treeChangeHandler, parent);
|
||||||
|
|
||||||
this._children.push(new AzureResourceDatabaseContainerTreeNode(subscription, account, treeChangeHandler, this));
|
this._id = `account_${this.account.key.accountId}.subscription_${this.subscription.id}.tenant_${this.tenatId}`;
|
||||||
this._children.push(new AzureResourceDatabaseServerContainerTreeNode(subscription, account, treeChangeHandler, this));
|
this.setCacheKey(`${this._id}.resources`);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChildren(): Promise<TreeNode[]> {
|
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> {
|
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.contextValue = AzureResourceItemType.subscription;
|
||||||
item.iconPath = {
|
item.iconPath = {
|
||||||
dark: this.servicePool.contextService.getAbsolutePath('resources/dark/subscription_inverse.svg'),
|
dark: this.appContext.extensionContext.asAbsolutePath('resources/dark/subscription_inverse.svg'),
|
||||||
light: this.servicePool.contextService.getAbsolutePath('resources/light/subscription.svg')
|
light: this.appContext.extensionContext.asAbsolutePath('resources/light/subscription.svg')
|
||||||
};
|
};
|
||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
@@ -58,8 +86,10 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceTreeNodeBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get nodePathValue(): string {
|
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';
|
'use strict';
|
||||||
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
import { TreeNode } from '../treeNode';
|
||||||
|
|
||||||
export interface IAzureResourceTreeChangeHandler {
|
export interface IAzureResourceTreeChangeHandler {
|
||||||
notifyNodeChanged(node: TreeNode): void;
|
notifyNodeChanged(node: TreeNode): void;
|
||||||
|
|||||||
@@ -6,26 +6,25 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
|
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
|
||||||
import { DidChangeAccountsParams } from 'sqlops';
|
|
||||||
import { TreeNode } from '../../treeNodes';
|
|
||||||
import { setInterval, clearInterval } from 'timers';
|
import { setInterval, clearInterval } from 'timers';
|
||||||
|
import { AppContext } from '../../appContext';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../servicePool';
|
import { TreeNode } from '../treeNode';
|
||||||
import { AzureResourceAccountTreeNode } from './accountTreeNode';
|
import { AzureResourceAccountTreeNode } from './accountTreeNode';
|
||||||
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
|
import { AzureResourceAccountNotSignedInTreeNode } from './accountNotSignedInTreeNode';
|
||||||
import { AzureResourceMessageTreeNode } from './messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../messageTreeNode';
|
||||||
import { AzureResourceContainerTreeNodeBase, AzureResourceTreeNodeBase } from './baseTreeNodes';
|
import { AzureResourceContainerTreeNodeBase } from './baseTreeNodes';
|
||||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||||
|
import { IAzureResourceTreeChangeHandler } from './treeChangeHandler';
|
||||||
export interface IAzureResourceTreeChangeHandler {
|
import { IAzureResourceAccountService } from '../../azureResource/interfaces';
|
||||||
notifyNodeChanged(node: TreeNode): void;
|
import { AzureResourceServiceNames } from '../constants';
|
||||||
}
|
|
||||||
|
|
||||||
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
|
export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IAzureResourceTreeChangeHandler {
|
||||||
public constructor() {
|
public constructor(
|
||||||
AzureResourceServicePool.getInstance().accountService.onDidChangeAccounts((e: DidChangeAccountsParams) => { this._onDidChangeTreeData.fire(undefined); });
|
public readonly appContext: AppContext
|
||||||
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
|
||||||
@@ -37,7 +36,7 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
|||||||
this._loadingTimer = setInterval(async () => {
|
this._loadingTimer = setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
// Call sqlops.accounts.getAllAccounts() to determine whether the system has been initialized.
|
// 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
|
// System has been initialized
|
||||||
this.isSystemInitialized = true;
|
this.isSystemInitialized = true;
|
||||||
@@ -51,16 +50,16 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
|||||||
// System not initialized yet
|
// System not initialized yet
|
||||||
this.isSystemInitialized = false;
|
this.isSystemInitialized = false;
|
||||||
}
|
}
|
||||||
}, AzureResourceTreeProvider.LoadingTimerInterval);
|
}, AzureResourceTreeProvider.loadingTimerInterval);
|
||||||
|
|
||||||
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.Loading, undefined)];
|
return [AzureResourceMessageTreeNode.create(AzureResourceTreeProvider.loadingLabel, undefined)];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accounts = await AzureResourceServicePool.getInstance().accountService.getAccounts();
|
const accounts = await this.appContext.getService<IAzureResourceAccountService>(AzureResourceServiceNames.accountService).getAccounts();
|
||||||
|
|
||||||
if (accounts && accounts.length > 0) {
|
if (accounts && accounts.length > 0) {
|
||||||
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this));
|
return accounts.map((account) => new AzureResourceAccountTreeNode(account, this.appContext, this));
|
||||||
} else {
|
} else {
|
||||||
return [new AzureResourceAccountNotSignedInTreeNode()];
|
return [new AzureResourceAccountNotSignedInTreeNode()];
|
||||||
}
|
}
|
||||||
@@ -96,6 +95,6 @@ export class AzureResourceTreeProvider implements TreeDataProvider<TreeNode>, IA
|
|||||||
private _loadingTimer: NodeJS.Timer = undefined;
|
private _loadingTimer: NodeJS.Timer = undefined;
|
||||||
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
|
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
|
||||||
|
|
||||||
private static readonly Loading = localize('azureResource.tree.treeProvider.loading', 'Loading ...');
|
private static readonly loadingLabel = localize('azure.resource.tree.treeProvider.loadingLabel', 'Loading ...');
|
||||||
private static readonly LoadingTimerInterval = 5000;
|
private static readonly loadingTimerInterval = 5000;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,16 +11,6 @@ import * as vscode from 'vscode';
|
|||||||
type TreeNodePredicate = (node: TreeNode) => boolean;
|
type TreeNodePredicate = (node: TreeNode) => boolean;
|
||||||
|
|
||||||
export abstract class TreeNode {
|
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 {
|
public generateNodePath(): string {
|
||||||
let path = undefined;
|
let path = undefined;
|
||||||
if (this.parent) {
|
if (this.parent) {
|
||||||
@@ -65,13 +55,23 @@ export abstract class TreeNode {
|
|||||||
return undefined;
|
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
|
* The value to use for this node in the node path
|
||||||
*/
|
*/
|
||||||
public abstract get nodePathValue(): string;
|
public abstract get nodePathValue(): string;
|
||||||
|
|
||||||
abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
private _parent: TreeNode = undefined;
|
||||||
abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
|
||||||
|
|
||||||
abstract getNodeInfo(): sqlops.NodeInfo;
|
|
||||||
}
|
}
|
||||||
@@ -12,10 +12,9 @@ export function getErrorMessage(error: Error | string): string {
|
|||||||
return (error instanceof Error) ? error.message : error;
|
return (error instanceof Error) ? error.message : error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export class AzureResourceErrorMessageUtil {
|
export class AzureResourceErrorMessageUtil {
|
||||||
public static getErrorMessage(error: Error | string): string {
|
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 os from 'os';
|
||||||
import * as constants from './constants';
|
import * as constants from './constants';
|
||||||
|
|
||||||
import MainController from './controllers/mainController';
|
import AzureResourceController from './controllers/azureResourceController';
|
||||||
import { AppContext } from './appContext';
|
import { AppContext } from './appContext';
|
||||||
import ControllerBase from './controllers/controllerBase';
|
import ControllerBase from './controllers/controllerBase';
|
||||||
import { ApiWrapper } from './apiWrapper';
|
import { ApiWrapper } from './apiWrapper';
|
||||||
import { AzureAccountProviderService } from './account-provider/azureAccountProviderService';
|
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[] = [];
|
let controllers: ControllerBase[] = [];
|
||||||
|
|
||||||
|
|
||||||
@@ -35,7 +40,8 @@ export function getDefaultLogLocation() {
|
|||||||
// this method is called when your extension is activated
|
// this method is called when your extension is activated
|
||||||
// your extension is activated the very first time the command is executed
|
// your extension is activated the very first time the command is executed
|
||||||
export function activate(extensionContext: vscode.ExtensionContext) {
|
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>[] = [];
|
let activations: Thenable<boolean>[] = [];
|
||||||
|
|
||||||
// Create the folder for storing the token caches
|
// Create the folder for storing the token caches
|
||||||
@@ -56,21 +62,19 @@ export function activate(extensionContext: vscode.ExtensionContext) {
|
|||||||
extensionContext.subscriptions.push(accountProviderService);
|
extensionContext.subscriptions.push(accountProviderService);
|
||||||
accountProviderService.activate();
|
accountProviderService.activate();
|
||||||
|
|
||||||
// Start the main controller
|
const azureResourceController = new AzureResourceController(appContext);
|
||||||
let mainController = new MainController(appContext);
|
controllers.push(azureResourceController);
|
||||||
controllers.push(mainController);
|
extensionContext.subscriptions.push(azureResourceController);
|
||||||
extensionContext.subscriptions.push(mainController);
|
activations.push(azureResourceController.activate());
|
||||||
activations.push(mainController.activate());
|
|
||||||
|
|
||||||
return Promise.all(activations)
|
return {
|
||||||
.then((results: boolean[]) => {
|
provideResources() {
|
||||||
for (let result of results) {
|
return [
|
||||||
if (!result) {
|
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), apiWrapper, extensionContext),
|
||||||
return false;
|
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext)
|
||||||
}
|
];
|
||||||
}
|
}
|
||||||
return true;
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method is called when your extension is deactivated
|
// this method is called when your extension is deactivated
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import * as should from 'should';
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
import { AzureResourceItemType } from '../../azureResource/constants';
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../../azureResource/messageTreeNode';
|
||||||
|
|
||||||
describe('AzureResourceMessageTreeNode.info', function(): void {
|
describe('AzureResourceMessageTreeNode.info', function(): void {
|
||||||
it('Should be correct when created.', async function(): Promise<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.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||||
should(treeItem.command).not.undefined();
|
should(treeItem.command).not.undefined();
|
||||||
should(treeItem.command.title).equal(label);
|
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();
|
const nodeInfo = treeNode.getNodeInfo();
|
||||||
should(nodeInfo.isLeaf).true();
|
should(nodeInfo.isLeaf).true();
|
||||||
|
|||||||
@@ -10,35 +10,38 @@ import * as TypeMoq from 'typemoq';
|
|||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import 'mocha';
|
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 {
|
import {
|
||||||
IAzureResourceCacheService,
|
IAzureResourceCacheService,
|
||||||
IAzureResourceContextService,
|
|
||||||
IAzureResourceCredentialService,
|
|
||||||
IAzureResourceSubscriptionService,
|
IAzureResourceSubscriptionService,
|
||||||
IAzureResourceSubscriptionFilterService
|
IAzureResourceSubscriptionFilterService,
|
||||||
|
IAzureResourceTenantService
|
||||||
} from '../../../azureResource/interfaces';
|
} from '../../../azureResource/interfaces';
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||||
import { AzureResourceMessageTreeNode } from '../../../azureResource/tree/messageTreeNode';
|
import { AzureResourceMessageTreeNode } from '../../../azureResource/messageTreeNode';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
import { generateGuid } from '../../../azureResource/utils';
|
||||||
|
|
||||||
// Mock services
|
// Mock services
|
||||||
const mockServicePool = AzureResourceServicePool.getInstance();
|
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||||
|
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||||
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
let mockCacheService: TypeMoq.IMock<IAzureResourceCacheService>;
|
||||||
let mockContextService: TypeMoq.IMock<IAzureResourceContextService>;
|
|
||||||
let mockCredentialService: TypeMoq.IMock<IAzureResourceCredentialService>;
|
|
||||||
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
let mockSubscriptionService: TypeMoq.IMock<IAzureResourceSubscriptionService>;
|
||||||
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
|
let mockSubscriptionFilterService: TypeMoq.IMock<IAzureResourceSubscriptionFilterService>;
|
||||||
|
let mockTenantService: TypeMoq.IMock<IAzureResourceTenantService>;
|
||||||
|
let mockAppContext: AppContext;
|
||||||
|
|
||||||
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||||
|
|
||||||
// Mock test data
|
// Mock test data
|
||||||
|
const mockTenantId = 'mock_tenant_id';
|
||||||
|
|
||||||
const mockAccount: sqlops.Account = {
|
const mockAccount: sqlops.Account = {
|
||||||
key: {
|
key: {
|
||||||
accountId: 'mock_account',
|
accountId: 'mock_account',
|
||||||
@@ -49,51 +52,68 @@ const mockAccount: sqlops.Account = {
|
|||||||
accountType: 'Microsoft',
|
accountType: 'Microsoft',
|
||||||
contextualDisplayName: 'test'
|
contextualDisplayName: 'test'
|
||||||
},
|
},
|
||||||
properties: undefined,
|
properties: {
|
||||||
|
tenants: [
|
||||||
|
{
|
||||||
|
id: mockTenantId
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
isStale: false
|
isStale: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockCredential = TypeMoq.Mock.ofType<ServiceClientCredentials>().object;
|
const mockSubscription1: azureResource.AzureResourceSubscription = {
|
||||||
const mockCredentials = [mockCredential];
|
|
||||||
|
|
||||||
const mockSubscription1: AzureResourceSubscription = {
|
|
||||||
id: 'mock_subscription_1',
|
id: 'mock_subscription_1',
|
||||||
name: 'mock subscription 1'
|
name: 'mock subscription 1'
|
||||||
};
|
};
|
||||||
const mockSubscription2: AzureResourceSubscription = {
|
|
||||||
|
const mockSubscription2: azureResource.AzureResourceSubscription = {
|
||||||
id: 'mock_subscription_2',
|
id: 'mock_subscription_2',
|
||||||
name: 'mock subscription 2'
|
name: 'mock subscription 2'
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockSubscriptions = [mockSubscription1, mockSubscription2];
|
const mockSubscriptions = [mockSubscription1, mockSubscription2];
|
||||||
|
|
||||||
const mockFilteredSubscriptions = [mockSubscription1];
|
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 {
|
describe('AzureResourceAccountTreeNode.info', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
mockContextService = TypeMoq.Mock.ofType<IAzureResourceContextService>();
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||||
|
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
mockSubscriptionCache = { subscriptions: {} };
|
mockSubscriptionCache = [];
|
||||||
|
|
||||||
mockServicePool.contextService = mockContextService.object;
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionFilterService>(AzureResourceServiceNames.subscriptionFilterService, mockSubscriptionFilterService.object);
|
||||||
mockServicePool.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.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> {
|
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 accountTreeNodeId = `account_${mockAccount.key.accountId}`;
|
||||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${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> {
|
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));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
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();
|
const treeItem = await accountTreeNode.getTreeItem();
|
||||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
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> {
|
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));
|
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(mockFilteredSubscriptions));
|
||||||
|
|
||||||
const accountTreeNodeLabel = `${mockAccount.displayInfo.displayName} (${mockAccount.key.accountId}) (${mockFilteredSubscriptions.length} / ${mockSubscriptions.length} subscriptions)`;
|
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();
|
const treeItem = await accountTreeNode.getTreeItem();
|
||||||
should(treeItem.label).equal(accountTreeNodeLabel);
|
should(treeItem.label).equal(accountTreeNodeLabel);
|
||||||
@@ -150,36 +176,41 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
|||||||
|
|
||||||
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
mockCredentialService = TypeMoq.Mock.ofType<IAzureResourceCredentialService>();
|
|
||||||
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
mockSubscriptionService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionService>();
|
||||||
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
mockSubscriptionFilterService = TypeMoq.Mock.ofType<IAzureResourceSubscriptionFilterService>();
|
||||||
|
mockTenantService = TypeMoq.Mock.ofType<IAzureResourceTenantService>();
|
||||||
|
|
||||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
mockTreeChangeHandler = TypeMoq.Mock.ofType<IAzureResourceTreeChangeHandler>();
|
||||||
|
|
||||||
mockSubscriptionCache = { subscriptions: {} };
|
mockSubscriptionCache = [];
|
||||||
|
|
||||||
mockServicePool.cacheService = mockCacheService.object;
|
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||||
mockServicePool.credentialService = mockCredentialService.object;
|
mockAppContext.registerService<IAzureResourceCacheService>(AzureResourceServiceNames.cacheService, mockCacheService.object);
|
||||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
mockAppContext.registerService<IAzureResourceSubscriptionService>(AzureResourceServiceNames.subscriptionService, mockSubscriptionService.object);
|
||||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.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.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> {
|
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));
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionFilterService.setup((o) => o.getSelectedSubscriptions(mockAccount)).returns(() => Promise.resolve(undefined));
|
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();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), 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.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(0));
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), 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).Array();
|
||||||
should(children.length).equal(mockSubscriptions.length);
|
should(children.length).equal(mockSubscriptions.length);
|
||||||
|
|
||||||
should(Object.keys(mockSubscriptionCache.subscriptions)).deepEqual([mockAccount.key.accountId]);
|
should(mockSubscriptionCache).deepEqual(mockSubscriptions);
|
||||||
should(mockSubscriptionCache.subscriptions[mockAccount.key.accountId]).deepEqual(mockSubscriptions);
|
|
||||||
|
|
||||||
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
|
for (let ix = 0; ix < mockSubscriptions.length; ix++) {
|
||||||
const child = children[ix];
|
const child = children[ix];
|
||||||
const subscription = mockSubscriptions[ix];
|
const subscription = mockSubscriptions[ix];
|
||||||
|
|
||||||
should(child).instanceof(AzureResourceSubscriptionTreeNode);
|
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> {
|
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));
|
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();
|
await accountTreeNode.getChildren();
|
||||||
const children = await accountTreeNode.getChildren();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
|
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.exactly(1));
|
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredential), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
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++) {
|
for (let ix = 0; ix < mockSubscriptionCache.length; ix++) {
|
||||||
should(children[ix].nodePathValue).equal(`subscription_${mockSubscriptionCache.subscriptions[mockAccount.key.accountId][ix].id}`);
|
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> {
|
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();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
@@ -242,10 +272,10 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Should honor subscription filtering.', async function(): Promise<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));
|
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();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
@@ -255,23 +285,25 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
should(children.length).equal(mockFilteredSubscriptions.length);
|
should(children.length).equal(mockFilteredSubscriptions.length);
|
||||||
|
|
||||||
for (let ix = 0; ix < mockFilteredSubscriptions.length; ix++) {
|
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> {
|
it('Should handle errors.', async function(): Promise<void> {
|
||||||
const mockError = 'Test error';
|
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredential)).returns(() => Promise.resolve(mockSubscriptions));
|
||||||
mockSubscriptionService.setup((o) => o.getSubscriptions(mockAccount, mockCredentials)).returns(() => { throw new Error(mockError); });
|
|
||||||
|
|
||||||
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();
|
const children = await accountTreeNode.getChildren();
|
||||||
|
|
||||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
mockApiWrapper.verify((o) => o.getSecurityToken(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), 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.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
||||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||||
mockSubscriptionFilterService.verify((o) => o.getSelectedSubscriptions(mockAccount), TypeMoq.Times.never());
|
|
||||||
|
|
||||||
should(children).Array();
|
should(children).Array();
|
||||||
should(children.length).equal(1);
|
should(children.length).equal(1);
|
||||||
@@ -283,11 +315,32 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
|||||||
|
|
||||||
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
|
describe('AzureResourceAccountTreeNode.clearCache', function() : void {
|
||||||
beforeEach(() => {
|
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>();
|
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> {
|
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();
|
accountTreeNode.clearCache();
|
||||||
should(accountTreeNode.isClearingCache).true();
|
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 sqlops from 'sqlops';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
import { azureResource } from '../../../azureResource/azure-resource';
|
||||||
import { IAzureResourceContextService } from '../../../azureResource/interfaces';
|
|
||||||
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
import { IAzureResourceTreeChangeHandler } from '../../../azureResource/tree/treeChangeHandler';
|
||||||
import { AzureResourceSubscription } from '../../../azureResource/models';
|
|
||||||
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
import { AzureResourceSubscriptionTreeNode } from '../../../azureResource/tree/subscriptionTreeNode';
|
||||||
import { AzureResourceDatabaseContainerTreeNode } from '../../../azureResource/tree/databaseContainerTreeNode';
|
import { AzureResourceItemType, AzureResourceServiceNames } from '../../../azureResource/constants';
|
||||||
import { AzureResourceDatabaseServerContainerTreeNode } from '../../../azureResource/tree/databaseServerContainerTreeNode';
|
import { AzureResourceService } from '../../../azureResource/resourceService';
|
||||||
import { AzureResourceItemType } from '../../../azureResource/constants';
|
import { AzureResourceResourceTreeNode } from '../../../azureResource/resourceTreeNode';
|
||||||
|
import { IAzureResourceCacheService } from '../../../azureResource/interfaces';
|
||||||
|
import { generateGuid } from '../../../azureResource/utils';
|
||||||
|
|
||||||
// Mock services
|
// 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>;
|
let mockTreeChangeHandler: TypeMoq.IMock<IAzureResourceTreeChangeHandler>;
|
||||||
|
|
||||||
@@ -42,24 +46,60 @@ const mockAccount: sqlops.Account = {
|
|||||||
isStale: false
|
isStale: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockSubscription: AzureResourceSubscription = {
|
const mockSubscription: azureResource.AzureResourceSubscription = {
|
||||||
id: 'mock_subscription',
|
id: 'mock_subscription',
|
||||||
name: '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 {
|
describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
||||||
beforeEach(() => {
|
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>();
|
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> {
|
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();
|
const treeItem = await subscriptionTreeNode.getTreeItem();
|
||||||
should(treeItem.label).equal(mockSubscription.name);
|
should(treeItem.label).equal(mockSubscription.name);
|
||||||
@@ -76,16 +116,52 @@ describe('AzureResourceSubscriptionTreeNode.info', function(): void {
|
|||||||
|
|
||||||
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
|
describe('AzureResourceSubscriptionTreeNode.getChildren', function(): void {
|
||||||
beforeEach(() => {
|
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>();
|
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> {
|
it('Should return resource containers.', async function(): Promise<void> {
|
||||||
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
const subscriptionTreeNode = new AzureResourceSubscriptionTreeNode(mockAccount, mockSubscription, mockTenantId, mockAppContext, mockTreeChangeHandler.object, undefined);
|
||||||
const children = await subscriptionTreeNode.getChildren();
|
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).Array();
|
||||||
should(children.length).equal(2);
|
should(children.length).equal(expectedChildren.length);
|
||||||
should(children[0]).instanceof(AzureResourceDatabaseContainerTreeNode);
|
for (const child of children) {
|
||||||
should(children[1]).instanceof(AzureResourceDatabaseServerContainerTreeNode);
|
should(child).instanceOf(AzureResourceResourceTreeNode);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,21 +5,28 @@
|
|||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
import * as should from 'should';
|
import * as should from 'should';
|
||||||
import * as TypeMoq from 'typemoq';
|
import * as TypeMoq from 'typemoq';
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
|
import { AppContext } from '../../../appContext';
|
||||||
|
import { ApiWrapper } from '../../../apiWrapper';
|
||||||
|
|
||||||
import { AzureResourceServicePool } from '../../../azureResource/servicePool';
|
import { IAzureResourceCacheService, IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
||||||
import { IAzureResourceAccountService } from '../../../azureResource/interfaces';
|
|
||||||
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
|
import { AzureResourceTreeProvider } from '../../../azureResource/tree/treeProvider';
|
||||||
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
import { AzureResourceAccountTreeNode } from '../../../azureResource/tree/accountTreeNode';
|
||||||
import { AzureResourceAccountNotSignedInTreeNode } from '../../../azureResource/tree/accountNotSignedInTreeNode';
|
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
|
// 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>;
|
let mockAccountService: TypeMoq.IMock<IAzureResourceAccountService>;
|
||||||
|
|
||||||
// Mock test data
|
// Mock test data
|
||||||
@@ -53,15 +60,23 @@ const mockAccounts = [mockAccount1, mockAccount2];
|
|||||||
|
|
||||||
describe('AzureResourceTreeProvider.getChildren', function(): void {
|
describe('AzureResourceTreeProvider.getChildren', function(): void {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||||
|
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||||
|
mockCacheService = TypeMoq.Mock.ofType<IAzureResourceCacheService>();
|
||||||
mockAccountService = TypeMoq.Mock.ofType<IAzureResourceAccountService>();
|
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> {
|
it('Should load accounts.', async function(): Promise<void> {
|
||||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
|
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(mockAccounts));
|
||||||
|
|
||||||
const treeProvider = new AzureResourceTreeProvider();
|
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||||
treeProvider.isSystemInitialized = true;
|
treeProvider.isSystemInitialized = true;
|
||||||
|
|
||||||
const children = await treeProvider.getChildren(undefined);
|
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> {
|
it('Should handle when there is no accounts.', async function(): Promise<void> {
|
||||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
|
mockAccountService.setup((o) => o.getAccounts()).returns(() => Promise.resolve(undefined));
|
||||||
|
|
||||||
const treeProvider = new AzureResourceTreeProvider();
|
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||||
treeProvider.isSystemInitialized = true;
|
treeProvider.isSystemInitialized = true;
|
||||||
|
|
||||||
const children = await treeProvider.getChildren(undefined);
|
const children = await treeProvider.getChildren(undefined);
|
||||||
@@ -97,7 +112,7 @@ describe('AzureResourceTreeProvider.getChildren', function(): void {
|
|||||||
const mockAccountError = 'Test account error';
|
const mockAccountError = 'Test account error';
|
||||||
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
|
mockAccountService.setup((o) => o.getAccounts()).returns(() => { throw new Error(mockAccountError); });
|
||||||
|
|
||||||
const treeProvider = new AzureResourceTreeProvider();
|
const treeProvider = new AzureResourceTreeProvider(mockAppContext);
|
||||||
treeProvider.isSystemInitialized = true;
|
treeProvider.isSystemInitialized = true;
|
||||||
|
|
||||||
const children = await treeProvider.getChildren(undefined);
|
const children = await treeProvider.getChildren(undefined);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -67,7 +67,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
|
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.11",
|
||||||
"opener": "^1.4.3",
|
"opener": "^1.4.3",
|
||||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||||
"vscode-extension-telemetry": "0.0.18",
|
"vscode-extension-telemetry": "0.0.18",
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export class DeployConfigPage extends DacFxConfigPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async start(): Promise<boolean> {
|
async start(): Promise<boolean> {
|
||||||
let serverComponent = await this.createServerDropdown(true);
|
let serverComponent = await this.createServerDropdown(true);
|
||||||
let fileBrowserComponent = await this.createFileBrowser();
|
let fileBrowserComponent = await this.createFileBrowser();
|
||||||
this.databaseComponent = await this.createDatabaseTextBox();
|
this.databaseComponent = await this.createDatabaseTextBox();
|
||||||
this.databaseComponent.title = localize('dacFx.databaseNameTextBox', 'Database Name');
|
this.databaseComponent.title = localize('dacFx.databaseNameTextBox', 'Database Name');
|
||||||
@@ -131,7 +131,7 @@ export class DeployConfigPage extends DacFxConfigPage {
|
|||||||
this.model.database = this.databaseTextBox.value;
|
this.model.database = this.databaseTextBox.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize with upgrade existing true
|
//Initialize with upgrade existing true
|
||||||
upgradeRadioButton.checked = true;
|
upgradeRadioButton.checked = true;
|
||||||
this.model.upgradeExisting = true;
|
this.model.upgradeExisting = true;
|
||||||
|
|
||||||
@@ -149,10 +149,10 @@ export class DeployConfigPage extends DacFxConfigPage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async createDeployDatabaseDropdown(): Promise<sqlops.FormComponent> {
|
protected async createDeployDatabaseDropdown(): Promise<sqlops.FormComponent> {
|
||||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||||
required: true
|
required: true
|
||||||
}).component();
|
}).component();
|
||||||
// Handle database changes
|
//Handle database changes
|
||||||
this.databaseDropdown.onValueChanged(async () => {
|
this.databaseDropdown.onValueChanged(async () => {
|
||||||
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||||
});
|
});
|
||||||
@@ -172,7 +172,8 @@ export class DeployConfigPage extends DacFxConfigPage {
|
|||||||
}
|
}
|
||||||
let values = await this.getDatabaseValues();
|
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;
|
this.model.database = values[0].name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,14 +5,12 @@
|
|||||||
agent-base@4, agent-base@^4.1.0:
|
agent-base@4, agent-base@^4.1.0:
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||||
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-promisify "^5.0.0"
|
es6-promisify "^5.0.0"
|
||||||
|
|
||||||
applicationinsights@1.0.1:
|
applicationinsights@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
||||||
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
diagnostic-channel "0.2.0"
|
diagnostic-channel "0.2.0"
|
||||||
diagnostic-channel-publishers "0.2.1"
|
diagnostic-channel-publishers "0.2.1"
|
||||||
@@ -21,12 +19,10 @@ applicationinsights@1.0.1:
|
|||||||
base64-js@0.0.8:
|
base64-js@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||||
integrity sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=
|
|
||||||
|
|
||||||
bl@^1.0.0:
|
bl@^1.0.0:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||||
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
readable-stream "^2.3.5"
|
readable-stream "^2.3.5"
|
||||||
safe-buffer "^5.1.1"
|
safe-buffer "^5.1.1"
|
||||||
@@ -34,12 +30,10 @@ bl@^1.0.0:
|
|||||||
buffer-alloc-unsafe@^1.1.0:
|
buffer-alloc-unsafe@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||||
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
|
|
||||||
|
|
||||||
buffer-alloc@^1.2.0:
|
buffer-alloc@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer-alloc-unsafe "^1.1.0"
|
buffer-alloc-unsafe "^1.1.0"
|
||||||
buffer-fill "^1.0.0"
|
buffer-fill "^1.0.0"
|
||||||
@@ -47,17 +41,14 @@ buffer-alloc@^1.2.0:
|
|||||||
buffer-crc32@~0.2.3:
|
buffer-crc32@~0.2.3:
|
||||||
version "0.2.13"
|
version "0.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
|
||||||
|
|
||||||
buffer-fill@^1.0.0:
|
buffer-fill@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||||
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
|
|
||||||
|
|
||||||
buffer@^3.0.1:
|
buffer@^3.0.1:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
||||||
integrity sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
base64-js "0.0.8"
|
base64-js "0.0.8"
|
||||||
ieee754 "^1.1.4"
|
ieee754 "^1.1.4"
|
||||||
@@ -66,39 +57,34 @@ buffer@^3.0.1:
|
|||||||
commander@~2.8.1:
|
commander@~2.8.1:
|
||||||
version "2.8.1"
|
version "2.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||||
integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-readlink ">= 1.0.0"
|
graceful-readlink ">= 1.0.0"
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
core-util-is@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
|
||||||
|
|
||||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.10":
|
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.11":
|
||||||
version "0.2.10"
|
version "0.2.11"
|
||||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
|
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/bc80d2226699d23f45a2ec26129cbcdee4781ca9"
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-languageclient "3.5.1"
|
vscode-languageclient "3.5.1"
|
||||||
|
|
||||||
debug@3.1.0:
|
debug@3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@^3.1.0:
|
debug@^3.1.0:
|
||||||
version "3.2.6"
|
version "3.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||||
integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
file-type "^5.2.0"
|
file-type "^5.2.0"
|
||||||
is-stream "^1.1.0"
|
is-stream "^1.1.0"
|
||||||
@@ -107,7 +93,6 @@ decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
|||||||
decompress-tarbz2@^4.0.0:
|
decompress-tarbz2@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
||||||
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-tar "^4.1.0"
|
decompress-tar "^4.1.0"
|
||||||
file-type "^6.1.0"
|
file-type "^6.1.0"
|
||||||
@@ -118,7 +103,6 @@ decompress-tarbz2@^4.0.0:
|
|||||||
decompress-targz@^4.0.0:
|
decompress-targz@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
||||||
integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-tar "^4.1.1"
|
decompress-tar "^4.1.1"
|
||||||
file-type "^5.2.0"
|
file-type "^5.2.0"
|
||||||
@@ -127,7 +111,6 @@ decompress-targz@^4.0.0:
|
|||||||
decompress-unzip@^4.0.1:
|
decompress-unzip@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
||||||
integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
file-type "^3.8.0"
|
file-type "^3.8.0"
|
||||||
get-stream "^2.2.0"
|
get-stream "^2.2.0"
|
||||||
@@ -137,7 +120,6 @@ decompress-unzip@^4.0.1:
|
|||||||
decompress@^4.2.0:
|
decompress@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
||||||
integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-tar "^4.0.0"
|
decompress-tar "^4.0.0"
|
||||||
decompress-tarbz2 "^4.0.0"
|
decompress-tarbz2 "^4.0.0"
|
||||||
@@ -151,70 +133,58 @@ decompress@^4.2.0:
|
|||||||
diagnostic-channel-publishers@0.2.1:
|
diagnostic-channel-publishers@0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
||||||
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
|
|
||||||
|
|
||||||
diagnostic-channel@0.2.0:
|
diagnostic-channel@0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
||||||
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.3.0"
|
semver "^5.3.0"
|
||||||
|
|
||||||
end-of-stream@^1.0.0:
|
end-of-stream@^1.0.0:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
||||||
integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
once "^1.4.0"
|
once "^1.4.0"
|
||||||
|
|
||||||
es6-promise@^4.0.3:
|
es6-promise@^4.0.3:
|
||||||
version "4.2.5"
|
version "4.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
|
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
|
||||||
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
|
|
||||||
|
|
||||||
es6-promisify@^5.0.0:
|
es6-promisify@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
||||||
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-promise "^4.0.3"
|
es6-promise "^4.0.3"
|
||||||
|
|
||||||
eventemitter2@^5.0.1:
|
eventemitter2@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
||||||
integrity sha1-YZegldX7a1folC9v1+qtY6CclFI=
|
|
||||||
|
|
||||||
fd-slicer@~1.1.0:
|
fd-slicer@~1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pend "~1.2.0"
|
pend "~1.2.0"
|
||||||
|
|
||||||
file-type@^3.8.0:
|
file-type@^3.8.0:
|
||||||
version "3.9.0"
|
version "3.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||||
integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
|
|
||||||
|
|
||||||
file-type@^5.2.0:
|
file-type@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
||||||
integrity sha1-LdvqfHP/42No365J3DOMBYwritY=
|
|
||||||
|
|
||||||
file-type@^6.1.0:
|
file-type@^6.1.0:
|
||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||||
integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
|
|
||||||
|
|
||||||
fs-constants@^1.0.0:
|
fs-constants@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
|
||||||
|
|
||||||
get-stream@^2.2.0:
|
get-stream@^2.2.0:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||||
integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
pinkie-promise "^2.0.0"
|
pinkie-promise "^2.0.0"
|
||||||
@@ -222,17 +192,14 @@ get-stream@^2.2.0:
|
|||||||
graceful-fs@^4.1.10:
|
graceful-fs@^4.1.10:
|
||||||
version "4.1.15"
|
version "4.1.15"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
|
||||||
|
|
||||||
"graceful-readlink@>= 1.0.0":
|
"graceful-readlink@>= 1.0.0":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||||
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
|
|
||||||
|
|
||||||
http-proxy-agent@^2.1.0:
|
http-proxy-agent@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||||
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base "4"
|
agent-base "4"
|
||||||
debug "3.1.0"
|
debug "3.1.0"
|
||||||
@@ -240,7 +207,6 @@ http-proxy-agent@^2.1.0:
|
|||||||
https-proxy-agent@^2.2.1:
|
https-proxy-agent@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||||
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base "^4.1.0"
|
agent-base "^4.1.0"
|
||||||
debug "^3.1.0"
|
debug "^3.1.0"
|
||||||
@@ -248,115 +214,94 @@ https-proxy-agent@^2.2.1:
|
|||||||
ieee754@^1.1.4:
|
ieee754@^1.1.4:
|
||||||
version "1.1.12"
|
version "1.1.12"
|
||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||||
integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==
|
|
||||||
|
|
||||||
inherits@~2.0.3:
|
inherits@~2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
|
||||||
|
|
||||||
is-natural-number@^4.0.1:
|
is-natural-number@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
||||||
integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
|
|
||||||
|
|
||||||
is-stream@^1.1.0:
|
is-stream@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
|
||||||
|
|
||||||
isarray@^1.0.0, isarray@~1.0.0:
|
isarray@^1.0.0, isarray@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
|
||||||
|
|
||||||
make-dir@^1.0.0:
|
make-dir@^1.0.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||||
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pify "^3.0.0"
|
pify "^3.0.0"
|
||||||
|
|
||||||
minimist@0.0.8:
|
minimist@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
|
||||||
|
|
||||||
mkdirp@^0.5.1:
|
mkdirp@^0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||||
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
|
||||||
|
|
||||||
ms@^2.1.1:
|
ms@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
|
||||||
|
|
||||||
object-assign@^4.0.1:
|
object-assign@^4.0.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
|
||||||
|
|
||||||
once@^1.4.0:
|
once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
opener@^1.4.3:
|
opener@^1.4.3:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
||||||
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
|
|
||||||
|
|
||||||
os-tmpdir@~1.0.2:
|
os-tmpdir@~1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||||
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
|
|
||||||
|
|
||||||
pend@~1.2.0:
|
pend@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
|
||||||
|
|
||||||
pify@^2.3.0:
|
pify@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
|
|
||||||
|
|
||||||
pify@^3.0.0:
|
pify@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||||
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
|
|
||||||
|
|
||||||
pinkie-promise@^2.0.0:
|
pinkie-promise@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||||
integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pinkie "^2.0.0"
|
pinkie "^2.0.0"
|
||||||
|
|
||||||
pinkie@^2.0.0:
|
pinkie@^2.0.0:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||||
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
|
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||||
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
|
||||||
|
|
||||||
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||||
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
core-util-is "~1.0.0"
|
core-util-is "~1.0.0"
|
||||||
inherits "~2.0.3"
|
inherits "~2.0.3"
|
||||||
@@ -369,19 +314,16 @@ readable-stream@^2.3.0, readable-stream@^2.3.5:
|
|||||||
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
|
||||||
|
|
||||||
seek-bzip@^1.0.5:
|
seek-bzip@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
||||||
integrity sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
commander "~2.8.1"
|
commander "~2.8.1"
|
||||||
|
|
||||||
semver@^5.3.0:
|
semver@^5.3.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||||
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
|
|
||||||
|
|
||||||
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
||||||
version "0.1.5"
|
version "0.1.5"
|
||||||
@@ -397,21 +339,18 @@ semver@^5.3.0:
|
|||||||
string_decoder@~1.1.1:
|
string_decoder@~1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
strip-dirs@^2.0.0:
|
strip-dirs@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
||||||
integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
is-natural-number "^4.0.1"
|
is-natural-number "^4.0.1"
|
||||||
|
|
||||||
tar-stream@^1.5.2:
|
tar-stream@^1.5.2:
|
||||||
version "1.6.2"
|
version "1.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
bl "^1.0.0"
|
bl "^1.0.0"
|
||||||
buffer-alloc "^1.2.0"
|
buffer-alloc "^1.2.0"
|
||||||
@@ -424,24 +363,20 @@ tar-stream@^1.5.2:
|
|||||||
through@^2.3.6:
|
through@^2.3.6:
|
||||||
version "2.3.8"
|
version "2.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
|
||||||
|
|
||||||
tmp@^0.0.33:
|
tmp@^0.0.33:
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||||
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
os-tmpdir "~1.0.2"
|
os-tmpdir "~1.0.2"
|
||||||
|
|
||||||
to-buffer@^1.1.1:
|
to-buffer@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||||
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
|
|
||||||
|
|
||||||
unbzip2-stream@^1.0.9:
|
unbzip2-stream@^1.0.9:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
||||||
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer "^3.0.1"
|
buffer "^3.0.1"
|
||||||
through "^2.3.6"
|
through "^2.3.6"
|
||||||
@@ -449,31 +384,26 @@ unbzip2-stream@^1.0.9:
|
|||||||
util-deprecate@~1.0.1:
|
util-deprecate@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
|
||||||
|
|
||||||
vscode-extension-telemetry@0.0.18:
|
vscode-extension-telemetry@0.0.18:
|
||||||
version "0.0.18"
|
version "0.0.18"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
|
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
|
||||||
integrity sha512-Vw3Sr+dZwl+c6PlsUwrTtCOJkgrmvS3OUVDQGcmpXWAgq9xGq6as0K4pUx+aGqTjzLAESmWSrs6HlJm6J6Khcg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
applicationinsights "1.0.1"
|
applicationinsights "1.0.1"
|
||||||
|
|
||||||
vscode-jsonrpc@3.5.0:
|
vscode-jsonrpc@3.5.0:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
||||||
integrity sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=
|
|
||||||
|
|
||||||
vscode-languageclient@3.5.1:
|
vscode-languageclient@3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
||||||
integrity sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-languageserver-protocol "3.5.1"
|
vscode-languageserver-protocol "3.5.1"
|
||||||
|
|
||||||
vscode-languageserver-protocol@3.5.1:
|
vscode-languageserver-protocol@3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
||||||
integrity sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-jsonrpc "3.5.0"
|
vscode-jsonrpc "3.5.0"
|
||||||
vscode-languageserver-types "3.5.0"
|
vscode-languageserver-types "3.5.0"
|
||||||
@@ -481,27 +411,22 @@ vscode-languageserver-protocol@3.5.1:
|
|||||||
vscode-languageserver-types@3.5.0:
|
vscode-languageserver-types@3.5.0:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
||||||
integrity sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=
|
|
||||||
|
|
||||||
vscode-nls@^3.2.1:
|
vscode-nls@^3.2.1:
|
||||||
version "3.2.5"
|
version "3.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
|
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
|
||||||
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
|
|
||||||
|
|
||||||
wrappy@1:
|
wrappy@1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
|
||||||
|
|
||||||
xtend@^4.0.0:
|
xtend@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||||
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
|
||||||
|
|
||||||
yauzl@^2.4.2:
|
yauzl@^2.4.2:
|
||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer-crc32 "~0.2.3"
|
buffer-crc32 "~0.2.3"
|
||||||
fd-slicer "~1.1.0"
|
fd-slicer "~1.1.0"
|
||||||
@@ -509,4 +434,3 @@ yauzl@^2.4.2:
|
|||||||
zone.js@0.7.6:
|
zone.js@0.7.6:
|
||||||
version "0.7.6"
|
version "0.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
|
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
|
||||||
integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@
|
|||||||
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
|
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
|
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.11",
|
||||||
"opener": "^1.4.3",
|
"opener": "^1.4.3",
|
||||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||||
"vscode-extension-telemetry": "^0.0.15"
|
"vscode-extension-telemetry": "^0.0.15"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||||
"version": "1.5.0-alpha.63",
|
"version": "1.5.0-alpha.65",
|
||||||
"downloadFileNames": {
|
"downloadFileNames": {
|
||||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||||
|
|||||||
@@ -5,14 +5,12 @@
|
|||||||
agent-base@4, agent-base@^4.1.0:
|
agent-base@4, agent-base@^4.1.0:
|
||||||
version "4.2.1"
|
version "4.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||||
integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-promisify "^5.0.0"
|
es6-promisify "^5.0.0"
|
||||||
|
|
||||||
applicationinsights@1.0.1:
|
applicationinsights@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
||||||
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
diagnostic-channel "0.2.0"
|
diagnostic-channel "0.2.0"
|
||||||
diagnostic-channel-publishers "0.2.1"
|
diagnostic-channel-publishers "0.2.1"
|
||||||
@@ -21,12 +19,10 @@ applicationinsights@1.0.1:
|
|||||||
base64-js@0.0.8:
|
base64-js@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||||
integrity sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=
|
|
||||||
|
|
||||||
bl@^1.0.0:
|
bl@^1.0.0:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||||
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
readable-stream "^2.3.5"
|
readable-stream "^2.3.5"
|
||||||
safe-buffer "^5.1.1"
|
safe-buffer "^5.1.1"
|
||||||
@@ -34,12 +30,10 @@ bl@^1.0.0:
|
|||||||
buffer-alloc-unsafe@^1.1.0:
|
buffer-alloc-unsafe@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||||
integrity sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==
|
|
||||||
|
|
||||||
buffer-alloc@^1.2.0:
|
buffer-alloc@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer-alloc-unsafe "^1.1.0"
|
buffer-alloc-unsafe "^1.1.0"
|
||||||
buffer-fill "^1.0.0"
|
buffer-fill "^1.0.0"
|
||||||
@@ -47,17 +41,14 @@ buffer-alloc@^1.2.0:
|
|||||||
buffer-crc32@~0.2.3:
|
buffer-crc32@~0.2.3:
|
||||||
version "0.2.13"
|
version "0.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||||
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
|
|
||||||
|
|
||||||
buffer-fill@^1.0.0:
|
buffer-fill@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||||
integrity sha1-+PeLdniYiO858gXNY39o5wISKyw=
|
|
||||||
|
|
||||||
buffer@^3.0.1:
|
buffer@^3.0.1:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
||||||
integrity sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
base64-js "0.0.8"
|
base64-js "0.0.8"
|
||||||
ieee754 "^1.1.4"
|
ieee754 "^1.1.4"
|
||||||
@@ -66,39 +57,34 @@ buffer@^3.0.1:
|
|||||||
commander@~2.8.1:
|
commander@~2.8.1:
|
||||||
version "2.8.1"
|
version "2.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||||
integrity sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-readlink ">= 1.0.0"
|
graceful-readlink ">= 1.0.0"
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
core-util-is@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
|
||||||
|
|
||||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.10":
|
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.11":
|
||||||
version "0.2.10"
|
version "0.2.11"
|
||||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
|
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/bc80d2226699d23f45a2ec26129cbcdee4781ca9"
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-languageclient "3.5.1"
|
vscode-languageclient "3.5.1"
|
||||||
|
|
||||||
debug@3.1.0:
|
debug@3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@^3.1.0:
|
debug@^3.1.0:
|
||||||
version "3.2.6"
|
version "3.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||||
integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
file-type "^5.2.0"
|
file-type "^5.2.0"
|
||||||
is-stream "^1.1.0"
|
is-stream "^1.1.0"
|
||||||
@@ -107,7 +93,6 @@ decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
|||||||
decompress-tarbz2@^4.0.0:
|
decompress-tarbz2@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
||||||
integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-tar "^4.1.0"
|
decompress-tar "^4.1.0"
|
||||||
file-type "^6.1.0"
|
file-type "^6.1.0"
|
||||||
@@ -118,7 +103,6 @@ decompress-tarbz2@^4.0.0:
|
|||||||
decompress-targz@^4.0.0:
|
decompress-targz@^4.0.0:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
||||||
integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-tar "^4.1.1"
|
decompress-tar "^4.1.1"
|
||||||
file-type "^5.2.0"
|
file-type "^5.2.0"
|
||||||
@@ -127,7 +111,6 @@ decompress-targz@^4.0.0:
|
|||||||
decompress-unzip@^4.0.1:
|
decompress-unzip@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
||||||
integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
file-type "^3.8.0"
|
file-type "^3.8.0"
|
||||||
get-stream "^2.2.0"
|
get-stream "^2.2.0"
|
||||||
@@ -137,7 +120,6 @@ decompress-unzip@^4.0.1:
|
|||||||
decompress@^4.2.0:
|
decompress@^4.2.0:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
||||||
integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
decompress-tar "^4.0.0"
|
decompress-tar "^4.0.0"
|
||||||
decompress-tarbz2 "^4.0.0"
|
decompress-tarbz2 "^4.0.0"
|
||||||
@@ -151,70 +133,58 @@ decompress@^4.2.0:
|
|||||||
diagnostic-channel-publishers@0.2.1:
|
diagnostic-channel-publishers@0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
||||||
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
|
|
||||||
|
|
||||||
diagnostic-channel@0.2.0:
|
diagnostic-channel@0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
||||||
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.3.0"
|
semver "^5.3.0"
|
||||||
|
|
||||||
end-of-stream@^1.0.0:
|
end-of-stream@^1.0.0:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
||||||
integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
once "^1.4.0"
|
once "^1.4.0"
|
||||||
|
|
||||||
es6-promise@^4.0.3:
|
es6-promise@^4.0.3:
|
||||||
version "4.2.5"
|
version "4.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
|
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
|
||||||
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
|
|
||||||
|
|
||||||
es6-promisify@^5.0.0:
|
es6-promisify@^5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
||||||
integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
es6-promise "^4.0.3"
|
es6-promise "^4.0.3"
|
||||||
|
|
||||||
eventemitter2@^5.0.1:
|
eventemitter2@^5.0.1:
|
||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
||||||
integrity sha1-YZegldX7a1folC9v1+qtY6CclFI=
|
|
||||||
|
|
||||||
fd-slicer@~1.1.0:
|
fd-slicer@~1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||||
integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pend "~1.2.0"
|
pend "~1.2.0"
|
||||||
|
|
||||||
file-type@^3.8.0:
|
file-type@^3.8.0:
|
||||||
version "3.9.0"
|
version "3.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||||
integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek=
|
|
||||||
|
|
||||||
file-type@^5.2.0:
|
file-type@^5.2.0:
|
||||||
version "5.2.0"
|
version "5.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
||||||
integrity sha1-LdvqfHP/42No365J3DOMBYwritY=
|
|
||||||
|
|
||||||
file-type@^6.1.0:
|
file-type@^6.1.0:
|
||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||||
integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg==
|
|
||||||
|
|
||||||
fs-constants@^1.0.0:
|
fs-constants@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||||
integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
|
|
||||||
|
|
||||||
get-stream@^2.2.0:
|
get-stream@^2.2.0:
|
||||||
version "2.3.1"
|
version "2.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||||
integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
pinkie-promise "^2.0.0"
|
pinkie-promise "^2.0.0"
|
||||||
@@ -222,17 +192,14 @@ get-stream@^2.2.0:
|
|||||||
graceful-fs@^4.1.10:
|
graceful-fs@^4.1.10:
|
||||||
version "4.1.15"
|
version "4.1.15"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
|
||||||
|
|
||||||
"graceful-readlink@>= 1.0.0":
|
"graceful-readlink@>= 1.0.0":
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||||
integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=
|
|
||||||
|
|
||||||
http-proxy-agent@^2.1.0:
|
http-proxy-agent@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||||
integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base "4"
|
agent-base "4"
|
||||||
debug "3.1.0"
|
debug "3.1.0"
|
||||||
@@ -240,7 +207,6 @@ http-proxy-agent@^2.1.0:
|
|||||||
https-proxy-agent@^2.2.1:
|
https-proxy-agent@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||||
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base "^4.1.0"
|
agent-base "^4.1.0"
|
||||||
debug "^3.1.0"
|
debug "^3.1.0"
|
||||||
@@ -248,115 +214,94 @@ https-proxy-agent@^2.2.1:
|
|||||||
ieee754@^1.1.4:
|
ieee754@^1.1.4:
|
||||||
version "1.1.12"
|
version "1.1.12"
|
||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||||
integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==
|
|
||||||
|
|
||||||
inherits@~2.0.3:
|
inherits@~2.0.3:
|
||||||
version "2.0.3"
|
version "2.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
|
||||||
|
|
||||||
is-natural-number@^4.0.1:
|
is-natural-number@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
||||||
integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg=
|
|
||||||
|
|
||||||
is-stream@^1.1.0:
|
is-stream@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||||
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
|
|
||||||
|
|
||||||
isarray@^1.0.0, isarray@~1.0.0:
|
isarray@^1.0.0, isarray@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
|
||||||
|
|
||||||
make-dir@^1.0.0:
|
make-dir@^1.0.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||||
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pify "^3.0.0"
|
pify "^3.0.0"
|
||||||
|
|
||||||
minimist@0.0.8:
|
minimist@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
|
|
||||||
|
|
||||||
mkdirp@^0.5.1:
|
mkdirp@^0.5.1:
|
||||||
version "0.5.1"
|
version "0.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||||
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
minimist "0.0.8"
|
minimist "0.0.8"
|
||||||
|
|
||||||
ms@2.0.0:
|
ms@2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
|
||||||
|
|
||||||
ms@^2.1.1:
|
ms@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
|
||||||
|
|
||||||
object-assign@^4.0.1:
|
object-assign@^4.0.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
|
||||||
|
|
||||||
once@^1.4.0:
|
once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
wrappy "1"
|
wrappy "1"
|
||||||
|
|
||||||
opener@^1.4.3:
|
opener@^1.4.3:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
||||||
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
|
|
||||||
|
|
||||||
os-tmpdir@~1.0.2:
|
os-tmpdir@~1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||||
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
|
|
||||||
|
|
||||||
pend@~1.2.0:
|
pend@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||||
integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
|
|
||||||
|
|
||||||
pify@^2.3.0:
|
pify@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
|
|
||||||
|
|
||||||
pify@^3.0.0:
|
pify@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||||
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
|
|
||||||
|
|
||||||
pinkie-promise@^2.0.0:
|
pinkie-promise@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||||
integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
pinkie "^2.0.0"
|
pinkie "^2.0.0"
|
||||||
|
|
||||||
pinkie@^2.0.0:
|
pinkie@^2.0.0:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||||
integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA=
|
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||||
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
|
||||||
|
|
||||||
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||||
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
core-util-is "~1.0.0"
|
core-util-is "~1.0.0"
|
||||||
inherits "~2.0.3"
|
inherits "~2.0.3"
|
||||||
@@ -369,19 +314,16 @@ readable-stream@^2.3.0, readable-stream@^2.3.5:
|
|||||||
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
|
||||||
|
|
||||||
seek-bzip@^1.0.5:
|
seek-bzip@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
||||||
integrity sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
commander "~2.8.1"
|
commander "~2.8.1"
|
||||||
|
|
||||||
semver@^5.3.0:
|
semver@^5.3.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||||
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
|
|
||||||
|
|
||||||
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
||||||
version "0.1.5"
|
version "0.1.5"
|
||||||
@@ -397,21 +339,18 @@ semver@^5.3.0:
|
|||||||
string_decoder@~1.1.1:
|
string_decoder@~1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "~5.1.0"
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
strip-dirs@^2.0.0:
|
strip-dirs@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
||||||
integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
is-natural-number "^4.0.1"
|
is-natural-number "^4.0.1"
|
||||||
|
|
||||||
tar-stream@^1.5.2:
|
tar-stream@^1.5.2:
|
||||||
version "1.6.2"
|
version "1.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
bl "^1.0.0"
|
bl "^1.0.0"
|
||||||
buffer-alloc "^1.2.0"
|
buffer-alloc "^1.2.0"
|
||||||
@@ -424,24 +363,20 @@ tar-stream@^1.5.2:
|
|||||||
through@^2.3.6:
|
through@^2.3.6:
|
||||||
version "2.3.8"
|
version "2.3.8"
|
||||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||||
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
|
|
||||||
|
|
||||||
tmp@^0.0.33:
|
tmp@^0.0.33:
|
||||||
version "0.0.33"
|
version "0.0.33"
|
||||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||||
integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
os-tmpdir "~1.0.2"
|
os-tmpdir "~1.0.2"
|
||||||
|
|
||||||
to-buffer@^1.1.1:
|
to-buffer@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||||
integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==
|
|
||||||
|
|
||||||
unbzip2-stream@^1.0.9:
|
unbzip2-stream@^1.0.9:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
||||||
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer "^3.0.1"
|
buffer "^3.0.1"
|
||||||
through "^2.3.6"
|
through "^2.3.6"
|
||||||
@@ -449,31 +384,26 @@ unbzip2-stream@^1.0.9:
|
|||||||
util-deprecate@~1.0.1:
|
util-deprecate@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
|
||||||
|
|
||||||
vscode-extension-telemetry@^0.0.15:
|
vscode-extension-telemetry@^0.0.15:
|
||||||
version "0.0.15"
|
version "0.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856"
|
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856"
|
||||||
integrity sha512-Yf6dL9r2x2GISI1xh22XsAaydSTQG/4aBitu8sGBwGr42n2TyOsIXGtXSDgqQBNZgYD6+P1EHqrrzetn9ekWTQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
applicationinsights "1.0.1"
|
applicationinsights "1.0.1"
|
||||||
|
|
||||||
vscode-jsonrpc@3.5.0:
|
vscode-jsonrpc@3.5.0:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
||||||
integrity sha1-hyOdnhZrLXNSJFuKgTWXgEwdY6o=
|
|
||||||
|
|
||||||
vscode-languageclient@3.5.1:
|
vscode-languageclient@3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
||||||
integrity sha512-GTQ+hSq/o4c/y6GYmyP9XNrVoIu0NFZ67KltSkqN+tO0eUNDIlrVNX+3DJzzyLhSsrctuGzuYWm3t87mNAcBmQ==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-languageserver-protocol "3.5.1"
|
vscode-languageserver-protocol "3.5.1"
|
||||||
|
|
||||||
vscode-languageserver-protocol@3.5.1:
|
vscode-languageserver-protocol@3.5.1:
|
||||||
version "3.5.1"
|
version "3.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
||||||
integrity sha512-1fPDIwsAv1difCV+8daOrJEGunClNJWqnUHq/ncWrjhitKWXgGmRCjlwZ3gDUTt54yRcvXz1PXJDaRNvNH6pYA==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
vscode-jsonrpc "3.5.0"
|
vscode-jsonrpc "3.5.0"
|
||||||
vscode-languageserver-types "3.5.0"
|
vscode-languageserver-types "3.5.0"
|
||||||
@@ -481,22 +411,18 @@ vscode-languageserver-protocol@3.5.1:
|
|||||||
vscode-languageserver-types@3.5.0:
|
vscode-languageserver-types@3.5.0:
|
||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
||||||
integrity sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=
|
|
||||||
|
|
||||||
wrappy@1:
|
wrappy@1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
|
||||||
|
|
||||||
xtend@^4.0.0:
|
xtend@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||||
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
|
||||||
|
|
||||||
yauzl@^2.4.2:
|
yauzl@^2.4.2:
|
||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||||
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
|
|
||||||
dependencies:
|
dependencies:
|
||||||
buffer-crc32 "~0.2.3"
|
buffer-crc32 "~0.2.3"
|
||||||
fd-slicer "~1.1.0"
|
fd-slicer "~1.1.0"
|
||||||
@@ -504,4 +430,3 @@ yauzl@^2.4.2:
|
|||||||
zone.js@0.7.6:
|
zone.js@0.7.6:
|
||||||
version "0.7.6"
|
version "0.7.6"
|
||||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
|
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
|
||||||
integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=
|
|
||||||
|
|||||||
@@ -21,6 +21,11 @@
|
|||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": true,
|
"default": true,
|
||||||
"description": "%notebook.enabled.description%"
|
"description": "%notebook.enabled.description%"
|
||||||
|
},
|
||||||
|
"notebook.pythonPath": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "%notebook.pythonPath.description%"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
"description": "Defines the Data-procotol based Notebook contribution and many Notebook commands and contributions.",
|
"description": "Defines the Data-procotol based Notebook contribution and many Notebook commands and contributions.",
|
||||||
"notebook.configuration.title": "Notebook configuration",
|
"notebook.configuration.title": "Notebook configuration",
|
||||||
"notebook.enabled.description": "Enable viewing notebook files using built-in notebook editor.",
|
"notebook.enabled.description": "Enable viewing notebook files using built-in notebook editor.",
|
||||||
|
"notebook.pythonPath.description": "Local path to python installation used by Notebooks.",
|
||||||
"notebook.command.new": "New Notebook",
|
"notebook.command.new": "New Notebook",
|
||||||
"notebook.command.open": "Open Notebook"
|
"notebook.command.open": "Open Notebook"
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "azuredatastudio",
|
"name": "azuredatastudio",
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
"smoketest": "cd test/smoke && node test/index.js",
|
"smoketest": "cd test/smoke && node test/index.js",
|
||||||
"monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit",
|
"monaco-compile-check": "tsc -p src/tsconfig.monaco.json --noEmit",
|
||||||
"download-builtin-extensions": "node build/lib/builtInExtensions.js",
|
"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": {
|
"dependencies": {
|
||||||
"@angular/animations": "~4.1.3",
|
"@angular/animations": "~4.1.3",
|
||||||
@@ -154,6 +155,7 @@
|
|||||||
"source-map": "^0.4.4",
|
"source-map": "^0.4.4",
|
||||||
"temp-write": "^3.4.0",
|
"temp-write": "^3.4.0",
|
||||||
"tslint": "^5.9.1",
|
"tslint": "^5.9.1",
|
||||||
|
"tslint-microsoft-contrib": "^6.0.0",
|
||||||
"typemoq": "^0.3.2",
|
"typemoq": "^0.3.2",
|
||||||
"typescript": "2.9.2",
|
"typescript": "2.9.2",
|
||||||
"typescript-formatter": "7.1.0",
|
"typescript-formatter": "7.1.0",
|
||||||
|
|||||||
@@ -42,7 +42,8 @@
|
|||||||
"Microsoft.server-report",
|
"Microsoft.server-report",
|
||||||
"Microsoft.sql-vnext",
|
"Microsoft.sql-vnext",
|
||||||
"Microsoft.whoisactive",
|
"Microsoft.whoisactive",
|
||||||
"Redgate.sql-search"
|
"Redgate.sql-search",
|
||||||
|
"IDERA.sqldm-performance-insights"
|
||||||
],
|
],
|
||||||
"extensionsGallery": {
|
"extensionsGallery": {
|
||||||
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"
|
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"
|
||||||
|
|||||||
@@ -62,6 +62,12 @@
|
|||||||
<trans-unit id="sql.saveAsCsv.encoding">
|
<trans-unit id="sql.saveAsCsv.encoding">
|
||||||
<source xml:lang="en">[Optional] File encoding used when saving results as CSV</source>
|
<source xml:lang="en">[Optional] File encoding used when saving results as CSV</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="sql.saveAsXml.formatted">
|
||||||
|
<source xml:lang="en">[Optional] When true, XML output will be formatted when saving results as XML</source>
|
||||||
|
</trans-unit>
|
||||||
|
<trans-unit id="sql.saveAsXml.encoding">
|
||||||
|
<source xml:lang="en">[Optional] File encoding used when saving results as XML</source>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="sql.copyIncludeHeaders">
|
<trans-unit id="sql.copyIncludeHeaders">
|
||||||
<source xml:lang="en">[Optional] Configuration options for copying results from the Results View</source>
|
<source xml:lang="en">[Optional] Configuration options for copying results from the Results View</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -596,6 +602,9 @@
|
|||||||
<trans-unit id="saveAsExcel">
|
<trans-unit id="saveAsExcel">
|
||||||
<source xml:lang="en">Save As Excel</source>
|
<source xml:lang="en">Save As Excel</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="saveAsXml">
|
||||||
|
<source xml:lang="en">Save As XML</source>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="copySelection">
|
<trans-unit id="copySelection">
|
||||||
<source xml:lang="en">Copy</source>
|
<source xml:lang="en">Copy</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
@@ -1142,6 +1151,9 @@
|
|||||||
<trans-unit id="saveExcelLabel">
|
<trans-unit id="saveExcelLabel">
|
||||||
<source xml:lang="en">Save as Excel</source>
|
<source xml:lang="en">Save as Excel</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
<trans-unit id="saveXMLLabel">
|
||||||
|
<source xml:lang="en">Save as XML</source>
|
||||||
|
</trans-unit>
|
||||||
<trans-unit id="viewChartLabel">
|
<trans-unit id="viewChartLabel">
|
||||||
<source xml:lang="en">View as Chart</source>
|
<source xml:lang="en">View as Chart</source>
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|||||||
@@ -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-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\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\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%
|
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% .
|
:: 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%
|
:: if %errorlevel% neq 0 exit /b %errorlevel%
|
||||||
|
|
||||||
:: Integration & performance tests in AMD
|
:: Integration & performance tests in AMD
|
||||||
call .\scripts\test.bat --runGlob **\*.integrationTest.js %*
|
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
|
call .\scripts\node-electron.bat .\node_modules\mocha\bin\_mocha .\extensions\*\server\out\test\**\*.test.js
|
||||||
if %errorlevel% neq 0 exit /b %errorlevel%
|
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-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/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/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
|
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 .
|
./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 .
|
||||||
|
|||||||
@@ -414,17 +414,17 @@ export abstract class Modal extends Disposable implements IThemable {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this._resizeListener = DOM.addDisposableListener(window, DOM.EventType.RESIZE, (e: Event) => {
|
this._resizeListener = DOM.addDisposableListener(window, DOM.EventType.RESIZE, (e: Event) => {
|
||||||
this.layout(DOM.getTotalHeight(this._builder.getHTMLElement()));
|
this.layout(DOM.getTotalHeight(this._modalBodySection));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.layout(DOM.getTotalHeight(this._builder.getHTMLElement()));
|
this.layout(DOM.getTotalHeight(this._modalBodySection));
|
||||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ModalDialogOpened, { name: this._name });
|
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ModalDialogOpened, { name: this._name });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Required to be implemented so that scrolling and other functions operate correctly. Should re-layout controls in the modal
|
* Required to be implemented so that scrolling and other functions operate correctly. Should re-layout controls in the modal
|
||||||
*/
|
*/
|
||||||
protected abstract layout(height?: number): void;
|
protected abstract layout(height: number): void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides the modal and removes key listeners
|
* Hides the modal and removes key listeners
|
||||||
|
|||||||
@@ -6,19 +6,20 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import 'vs/css!./media/optionsDialog';
|
import 'vs/css!./media/optionsDialog';
|
||||||
import { Button } from 'sql/base/browser/ui/button/button';
|
|
||||||
import { FixedCollapsibleView } from 'sql/platform/views/fixedCollapsibleView';
|
|
||||||
import * as DialogHelper from './dialogHelper';
|
import * as DialogHelper from './dialogHelper';
|
||||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
|
||||||
import { IModalOptions, Modal } from './modal';
|
import { IModalOptions, Modal } from './modal';
|
||||||
import * as OptionsDialogHelper from './optionsDialogHelper';
|
import * as OptionsDialogHelper from './optionsDialogHelper';
|
||||||
import { attachButtonStyler, attachModalDialogStyler } from 'sql/common/theme/styler';
|
import { attachButtonStyler, attachModalDialogStyler, attachPanelStyler } from 'sql/common/theme/styler';
|
||||||
|
import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
|
import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scrollableSplitview';
|
||||||
|
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
|
|
||||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
@@ -26,58 +27,56 @@ import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/theme
|
|||||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import * as styler from 'vs/platform/theme/common/styler';
|
import * as styler from 'vs/platform/theme/common/styler';
|
||||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||||
import { SplitView, CollapsibleState } from 'sql/base/browser/ui/splitview/splitview';
|
|
||||||
import { Builder, $ } from 'vs/base/browser/builder';
|
import { Builder, $ } from 'vs/base/browser/builder';
|
||||||
import { Widget } from 'vs/base/browser/ui/widget';
|
import { Widget } from 'vs/base/browser/ui/widget';
|
||||||
import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
|
||||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||||
|
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||||
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
|
||||||
export class CategoryView extends FixedCollapsibleView {
|
export class CategoryView extends ViewletPanel {
|
||||||
private _treecontainer: HTMLElement;
|
|
||||||
private _collapsed: CollapsibleState;
|
|
||||||
|
|
||||||
constructor(private viewTitle: string, private _bodyContainer: HTMLElement, collapsed: boolean, initialBodySize: number, headerSize: number) {
|
constructor(
|
||||||
super(
|
private contentElement: HTMLElement,
|
||||||
initialBodySize,
|
private size: number,
|
||||||
{
|
options: IViewletPanelOptions,
|
||||||
expandedBodySize: initialBodySize,
|
@IKeybindingService keybindingService: IKeybindingService,
|
||||||
sizing: headerSize,
|
@IContextMenuService contextMenuService: IContextMenuService,
|
||||||
initialState: collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED,
|
@IConfigurationService configurationService: IConfigurationService
|
||||||
ariaHeaderLabel: viewTitle
|
) {
|
||||||
});
|
super(options, keybindingService, contextMenuService, configurationService);
|
||||||
this._collapsed = collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderHeader(container: HTMLElement): void {
|
// we want a fixed size, so when we render to will measure our content and set that to be our
|
||||||
const titleDiv = $('div.title').appendTo(container);
|
// minimum and max size
|
||||||
$('span').text(this.viewTitle).appendTo(titleDiv);
|
protected renderBody(container: HTMLElement): void {
|
||||||
|
container.appendChild(this.contentElement);
|
||||||
|
this.maximumBodySize = this.size;
|
||||||
|
this.minimumBodySize = this.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public renderBody(container: HTMLElement): void {
|
protected layoutBody(size: number): void {
|
||||||
this._treecontainer = document.createElement('div');
|
//
|
||||||
container.appendChild(this._treecontainer);
|
|
||||||
this._treecontainer.appendChild(this._bodyContainer);
|
|
||||||
this.changeState(this._collapsed);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public layoutBody(size: number): void {
|
export interface IOptionsDialogOptions extends IModalOptions {
|
||||||
this._treecontainer.style.height = size + 'px';
|
cancelLabel?: string;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OptionsDialog extends Modal {
|
export class OptionsDialog extends Modal {
|
||||||
private _body: HTMLElement;
|
private _body: HTMLElement;
|
||||||
private _optionGroups: HTMLElement;
|
private _optionGroups: HTMLElement;
|
||||||
private _dividerBuilder: Builder;
|
private _dividerBuilder: Builder;
|
||||||
private _okButton: Button;
|
|
||||||
private _closeButton: Button;
|
|
||||||
private _optionTitle: Builder;
|
private _optionTitle: Builder;
|
||||||
private _optionDescription: Builder;
|
private _optionDescription: Builder;
|
||||||
private _optionElements: { [optionName: string]: OptionsDialogHelper.IOptionElement } = {};
|
private _optionElements: { [optionName: string]: OptionsDialogHelper.IOptionElement } = {};
|
||||||
private _optionValues: { [optionName: string]: string };
|
private _optionValues: { [optionName: string]: string };
|
||||||
private _optionRowSize = 31;
|
private _optionRowSize = 31;
|
||||||
private _optionCategoryPadding = 30;
|
private _optionCategoryPadding = 30;
|
||||||
private _categoryHeaderSize = 22;
|
private height: number;
|
||||||
|
private splitview: ScrollableSplitView;
|
||||||
|
|
||||||
private _onOk = new Emitter<void>();
|
private _onOk = new Emitter<void>();
|
||||||
public onOk: Event<void> = this._onOk.event;
|
public onOk: Event<void> = this._onOk.event;
|
||||||
@@ -85,16 +84,14 @@ export class OptionsDialog extends Modal {
|
|||||||
private _onCloseEvent = new Emitter<void>();
|
private _onCloseEvent = new Emitter<void>();
|
||||||
public onCloseEvent: Event<void> = this._onCloseEvent.event;
|
public onCloseEvent: Event<void> = this._onCloseEvent.event;
|
||||||
|
|
||||||
public okLabel: string = localize('optionsDialog.ok', 'OK');
|
|
||||||
public cancelLabel: string = localize('optionsDialog.cancel', 'Cancel');
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
title: string,
|
title: string,
|
||||||
name: string,
|
name: string,
|
||||||
options: IModalOptions,
|
private options: IOptionsDialogOptions,
|
||||||
@IPartService partService: IPartService,
|
@IPartService partService: IPartService,
|
||||||
@IWorkbenchThemeService private _workbenchThemeService: IWorkbenchThemeService,
|
@IWorkbenchThemeService private _workbenchThemeService: IWorkbenchThemeService,
|
||||||
@IContextViewService private _contextViewService: IContextViewService,
|
@IContextViewService private _contextViewService: IContextViewService,
|
||||||
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
@ITelemetryService telemetryService: ITelemetryService,
|
@ITelemetryService telemetryService: ITelemetryService,
|
||||||
@IContextKeyService contextKeyService: IContextKeyService,
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
@IClipboardService clipboardService: IClipboardService
|
@IClipboardService clipboardService: IClipboardService
|
||||||
@@ -109,14 +106,13 @@ export class OptionsDialog extends Modal {
|
|||||||
this.backButton.onDidClick(() => this.cancel());
|
this.backButton.onDidClick(() => this.cancel());
|
||||||
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
|
attachButtonStyler(this.backButton, this._themeService, { buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND });
|
||||||
}
|
}
|
||||||
this._okButton = this.addFooterButton(this.okLabel, () => this.ok());
|
let okButton = this.addFooterButton(localize('optionsDialog.ok', 'OK'), () => this.ok());
|
||||||
this._closeButton = this.addFooterButton(this.cancelLabel, () => this.cancel());
|
let closeButton = this.addFooterButton(this.options.cancelLabel || localize('optionsDialog.cancel', 'Cancel'), () => this.cancel());
|
||||||
// Theme styler
|
// Theme styler
|
||||||
attachButtonStyler(this._okButton, this._themeService);
|
attachButtonStyler(okButton, this._themeService);
|
||||||
attachButtonStyler(this._closeButton, this._themeService);
|
attachButtonStyler(closeButton, this._themeService);
|
||||||
let self = this;
|
this._register(this._workbenchThemeService.onDidColorThemeChange(e => this.updateTheme(e)));
|
||||||
this._register(self._workbenchThemeService.onDidColorThemeChange(e => self.updateTheme(e)));
|
this.updateTheme(this._workbenchThemeService.getColorTheme());
|
||||||
self.updateTheme(self._workbenchThemeService.getColorTheme());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected renderBody(container: HTMLElement) {
|
protected renderBody(container: HTMLElement) {
|
||||||
@@ -151,24 +147,24 @@ export class OptionsDialog extends Modal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private onOptionLinkClicked(optionName: string): void {
|
private onOptionLinkClicked(optionName: string): void {
|
||||||
var option = this._optionElements[optionName].option;
|
let option = this._optionElements[optionName].option;
|
||||||
this._optionTitle.text(option.displayName);
|
this._optionTitle.text(option.displayName);
|
||||||
this._optionDescription.text(option.description);
|
this._optionDescription.text(option.description);
|
||||||
}
|
}
|
||||||
|
|
||||||
private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void {
|
private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void {
|
||||||
for (var i = 0; i < options.length; i++) {
|
for (let i = 0; i < options.length; i++) {
|
||||||
var option: sqlops.ServiceOption = options[i];
|
let option: sqlops.ServiceOption = options[i];
|
||||||
var rowContainer = DialogHelper.appendRow(container, option.displayName, 'optionsDialog-label', 'optionsDialog-input');
|
let rowContainer = DialogHelper.appendRow(container, option.displayName, 'optionsDialog-label', 'optionsDialog-input');
|
||||||
OptionsDialogHelper.createOptionElement(option, rowContainer, this._optionValues, this._optionElements, this._contextViewService, (name) => this.onOptionLinkClicked(name));
|
OptionsDialogHelper.createOptionElement(option, rowContainer, this._optionValues, this._optionElements, this._contextViewService, (name) => this.onOptionLinkClicked(name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerStyling(): void {
|
private registerStyling(): void {
|
||||||
// Theme styler
|
// Theme styler
|
||||||
for (var optionName in this._optionElements) {
|
for (let optionName in this._optionElements) {
|
||||||
var widget: Widget = this._optionElements[optionName].optionWidget;
|
let widget: Widget = this._optionElements[optionName].optionWidget;
|
||||||
var option = this._optionElements[optionName].option;
|
let option = this._optionElements[optionName].option;
|
||||||
switch (option.valueType) {
|
switch (option.valueType) {
|
||||||
case ServiceOptionType.category:
|
case ServiceOptionType.category:
|
||||||
case ServiceOptionType.boolean:
|
case ServiceOptionType.boolean:
|
||||||
@@ -225,47 +221,51 @@ export class OptionsDialog extends Modal {
|
|||||||
|
|
||||||
public open(options: sqlops.ServiceOption[], optionValues: { [name: string]: any }) {
|
public open(options: sqlops.ServiceOption[], optionValues: { [name: string]: any }) {
|
||||||
this._optionValues = optionValues;
|
this._optionValues = optionValues;
|
||||||
var firstOption: string;
|
let firstOption: string;
|
||||||
var containerGroup: Builder;
|
let containerGroup: Builder;
|
||||||
var layoutSize = 0;
|
let optionsContentBuilder: Builder = $().div({ class: 'optionsDialog-options-groups monaco-panel-view' }, (container) => {
|
||||||
var optionsContentBuilder: Builder = $().div({ class: 'optionsDialog-options-groups' }, (container) => {
|
|
||||||
containerGroup = container;
|
containerGroup = container;
|
||||||
this._optionGroups = container.getHTMLElement();
|
this._optionGroups = container.getHTMLElement();
|
||||||
});
|
});
|
||||||
var splitview = new SplitView(containerGroup.getHTMLElement());
|
this.splitview = new ScrollableSplitView(containerGroup.getHTMLElement(), { enableResizing: false, scrollDebounce: 0 });
|
||||||
let categoryMap = OptionsDialogHelper.groupOptionsByCategory(options);
|
let categoryMap = OptionsDialogHelper.groupOptionsByCategory(options);
|
||||||
for (var category in categoryMap) {
|
for (let category in categoryMap) {
|
||||||
var serviceOptions: sqlops.ServiceOption[] = categoryMap[category];
|
let serviceOptions: sqlops.ServiceOption[] = categoryMap[category];
|
||||||
var bodyContainer = $().element('table', { class: 'optionsDialog-table' }, (tableContainer: Builder) => {
|
let bodyContainer = $().element('table', { class: 'optionsDialog-table' }, (tableContainer: Builder) => {
|
||||||
this.fillInOptions(tableContainer, serviceOptions);
|
this.fillInOptions(tableContainer, serviceOptions);
|
||||||
});
|
});
|
||||||
|
|
||||||
var viewSize = this._optionCategoryPadding + serviceOptions.length * this._optionRowSize;
|
let viewSize = this._optionCategoryPadding + serviceOptions.length * this._optionRowSize;
|
||||||
layoutSize += (viewSize + this._categoryHeaderSize);
|
let categoryView = this._instantiationService.createInstance(CategoryView, bodyContainer.getHTMLElement(), viewSize, { title: category, ariaHeaderLabel: category, id: category });
|
||||||
var categoryView = new CategoryView(category, bodyContainer.getHTMLElement(), false, viewSize, this._categoryHeaderSize);
|
this.splitview.addView(categoryView, viewSize);
|
||||||
splitview.addView(categoryView);
|
categoryView.render();
|
||||||
|
attachPanelStyler(categoryView, this._themeService);
|
||||||
|
|
||||||
if (!firstOption) {
|
if (!firstOption) {
|
||||||
firstOption = serviceOptions[0].name;
|
firstOption = serviceOptions[0].name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
splitview.layout(layoutSize);
|
if (this.height) {
|
||||||
|
this.splitview.layout(this.height - 120);
|
||||||
|
}
|
||||||
let body = new Builder(this._body);
|
let body = new Builder(this._body);
|
||||||
body.append(optionsContentBuilder.getHTMLElement(), 0);
|
body.append(optionsContentBuilder.getHTMLElement(), 0);
|
||||||
this.show();
|
this.show();
|
||||||
var firstOptionWidget = this._optionElements[firstOption].optionWidget;
|
let firstOptionWidget = this._optionElements[firstOption].optionWidget;
|
||||||
this.registerStyling();
|
this.registerStyling();
|
||||||
firstOptionWidget.focus();
|
firstOptionWidget.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected layout(height?: number): void {
|
protected layout(height?: number): void {
|
||||||
// Nothing currently laid out in this class
|
this.height = height;
|
||||||
|
// account for padding and the details view
|
||||||
|
this.splitview.layout(this.height - 120 - 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
for (var optionName in this._optionElements) {
|
for (let optionName in this._optionElements) {
|
||||||
var widget: Widget = this._optionElements[optionName].optionWidget;
|
let widget: Widget = this._optionElements[optionName].optionWidget;
|
||||||
widget.dispose();
|
widget.dispose();
|
||||||
delete this._optionElements[optionName];
|
delete this._optionElements[optionName];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { INextIterator } from 'vs/base/common/iterator';
|
import { INextIterator } from 'vs/base/common/iterator';
|
||||||
|
|
||||||
export interface IView {
|
export interface IView {
|
||||||
id: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IViewItem {
|
export interface IViewItem {
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
.monaco-scroll-split-view {
|
.split-view-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.monaco-scroll-split-view {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -6,35 +6,49 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import 'vs/css!./scrollableSplitview';
|
import 'vs/css!./scrollableSplitview';
|
||||||
|
import { HeightMap, IView as HeightIView, IViewItem as HeightIViewItem } from './heightMap';
|
||||||
|
|
||||||
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { mapEvent, Emitter, Event, debounceEvent } from 'vs/base/common/event';
|
import { mapEvent, Emitter, Event, debounceEvent } from 'vs/base/common/event';
|
||||||
import * as types from 'vs/base/common/types';
|
import * as types from 'vs/base/common/types';
|
||||||
import * as dom from 'vs/base/browser/dom';
|
import * as dom from 'vs/base/browser/dom';
|
||||||
import { clamp } from 'vs/base/common/numbers';
|
import { clamp } from 'vs/base/common/numbers';
|
||||||
import { range, firstIndex } from 'vs/base/common/arrays';
|
import { range, firstIndex, pushToStart } from 'vs/base/common/arrays';
|
||||||
import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash';
|
import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash';
|
||||||
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||||
import { HeightMap, IView as HeightIView, IViewItem as HeightIViewItem } from './heightMap';
|
|
||||||
import { ArrayIterator } from 'vs/base/common/iterator';
|
import { ArrayIterator } from 'vs/base/common/iterator';
|
||||||
import { mixin } from 'vs/base/common/objects';
|
import { mixin } from 'vs/base/common/objects';
|
||||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||||
|
import { ISplitViewStyles, Sizing } from 'vs/base/browser/ui/splitview/splitview';
|
||||||
|
import { Color } from 'vs/base/common/color';
|
||||||
|
import { domEvent } from 'vs/base/browser/event';
|
||||||
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||||
|
|
||||||
export interface ISplitViewOptions {
|
export interface ISplitViewOptions {
|
||||||
orientation?: Orientation; // default Orientation.VERTICAL
|
orientation?: Orientation; // default Orientation.VERTICAL
|
||||||
|
styles?: ISplitViewStyles;
|
||||||
|
orthogonalStartSash?: Sash;
|
||||||
|
orthogonalEndSash?: Sash;
|
||||||
|
inverseAltBehavior?: boolean;
|
||||||
enableResizing?: boolean;
|
enableResizing?: boolean;
|
||||||
|
scrollDebounce?: number;
|
||||||
verticalScrollbarVisibility?: ScrollbarVisibility;
|
verticalScrollbarVisibility?: ScrollbarVisibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultStyles: ISplitViewStyles = {
|
||||||
|
separatorBorder: Color.transparent
|
||||||
|
};
|
||||||
|
|
||||||
const defaultOptions: ISplitViewOptions = {
|
const defaultOptions: ISplitViewOptions = {
|
||||||
enableResizing: true
|
enableResizing: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface IView extends HeightIView {
|
export interface IView extends HeightIView {
|
||||||
|
readonly element: HTMLElement;
|
||||||
readonly minimumSize: number;
|
readonly minimumSize: number;
|
||||||
readonly maximumSize: number;
|
readonly maximumSize: number;
|
||||||
readonly onDidChange: Event<number | undefined>;
|
readonly onDidChange: Event<number | undefined>;
|
||||||
render(container: HTMLElement, orientation: Orientation): void;
|
|
||||||
layout(size: number, orientation: Orientation): void;
|
layout(size: number, orientation: Orientation): void;
|
||||||
onAdd?(): void;
|
onAdd?(): void;
|
||||||
onRemove?(): void;
|
onRemove?(): void;
|
||||||
@@ -44,6 +58,7 @@ interface ISashEvent {
|
|||||||
sash: Sash;
|
sash: Sash;
|
||||||
start: number;
|
start: number;
|
||||||
current: number;
|
current: number;
|
||||||
|
alt: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IViewItem extends HeightIViewItem {
|
interface IViewItem extends HeightIViewItem {
|
||||||
@@ -64,7 +79,12 @@ interface ISashItem {
|
|||||||
interface ISashDragState {
|
interface ISashDragState {
|
||||||
index: number;
|
index: number;
|
||||||
start: number;
|
start: number;
|
||||||
|
current: number;
|
||||||
sizes: number[];
|
sizes: number[];
|
||||||
|
minDelta: number;
|
||||||
|
maxDelta: number;
|
||||||
|
alt: boolean;
|
||||||
|
disposable: IDisposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum State {
|
enum State {
|
||||||
@@ -95,24 +115,28 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
|
|
||||||
private orientation: Orientation;
|
private orientation: Orientation;
|
||||||
private el: HTMLElement;
|
private el: HTMLElement;
|
||||||
|
private sashContainer: HTMLElement;
|
||||||
|
private viewContainer: HTMLElement;
|
||||||
|
private scrollable: ScrollableElement;
|
||||||
private size = 0;
|
private size = 0;
|
||||||
private contentSize = 0;
|
private contentSize = 0;
|
||||||
|
private proportions: undefined | number[] = undefined;
|
||||||
private viewItems: IViewItem[] = [];
|
private viewItems: IViewItem[] = [];
|
||||||
private sashItems: ISashItem[] = [];
|
private sashItems: ISashItem[] = [];
|
||||||
private sashDragState: ISashDragState;
|
private sashDragState: ISashDragState;
|
||||||
private state: State = State.Idle;
|
private state: State = State.Idle;
|
||||||
private scrollable: ScrollableElement;
|
private inverseAltBehavior: boolean;
|
||||||
|
|
||||||
|
private lastRenderHeight: number;
|
||||||
|
private lastRenderTop: number;
|
||||||
|
|
||||||
private options: ISplitViewOptions;
|
private options: ISplitViewOptions;
|
||||||
|
|
||||||
private dirtyState = false;
|
private dirtyState = false;
|
||||||
|
|
||||||
private lastRenderTop: number;
|
private _onDidSashChange = new Emitter<number>();
|
||||||
private lastRenderHeight: number;
|
|
||||||
|
|
||||||
private _onDidSashChange = new Emitter<void>();
|
|
||||||
readonly onDidSashChange = this._onDidSashChange.event;
|
readonly onDidSashChange = this._onDidSashChange.event;
|
||||||
private _onDidSashReset = new Emitter<void>();
|
private _onDidSashReset = new Emitter<number>();
|
||||||
readonly onDidSashReset = this._onDidSashReset.event;
|
readonly onDidSashReset = this._onDidSashReset.event;
|
||||||
|
|
||||||
private _onScroll = new Emitter<number>();
|
private _onScroll = new Emitter<number>();
|
||||||
@@ -122,15 +146,48 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
return this.viewItems.length;
|
return this.viewItems.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get minimumSize(): number {
|
||||||
|
return this.viewItems.reduce((r, item) => r + item.view.minimumSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
get maximumSize(): number {
|
||||||
|
return this.length === 0 ? Number.POSITIVE_INFINITY : this.viewItems.reduce((r, item) => r + item.view.maximumSize, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _orthogonalStartSash: Sash | undefined;
|
||||||
|
get orthogonalStartSash(): Sash | undefined { return this._orthogonalStartSash; }
|
||||||
|
set orthogonalStartSash(sash: Sash | undefined) {
|
||||||
|
for (const sashItem of this.sashItems) {
|
||||||
|
sashItem.sash.orthogonalStartSash = sash;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._orthogonalStartSash = sash;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _orthogonalEndSash: Sash | undefined;
|
||||||
|
get orthogonalEndSash(): Sash | undefined { return this._orthogonalEndSash; }
|
||||||
|
set orthogonalEndSash(sash: Sash | undefined) {
|
||||||
|
for (const sashItem of this.sashItems) {
|
||||||
|
sashItem.sash.orthogonalEndSash = sash;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._orthogonalEndSash = sash;
|
||||||
|
}
|
||||||
|
|
||||||
|
get sashes(): Sash[] {
|
||||||
|
return this.sashItems.map(s => s.sash);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
|
constructor(container: HTMLElement, options: ISplitViewOptions = {}) {
|
||||||
super();
|
super();
|
||||||
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
|
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
|
||||||
|
this.inverseAltBehavior = !!options.inverseAltBehavior;
|
||||||
|
|
||||||
this.options = mixin(options, defaultOptions, false);
|
this.options = mixin(options, defaultOptions, false);
|
||||||
|
|
||||||
this.el = document.createElement('div');
|
this.el = document.createElement('div');
|
||||||
this.scrollable = new ScrollableElement(this.el, { vertical: options.verticalScrollbarVisibility });
|
this.scrollable = new ScrollableElement(this.el, { vertical: options.verticalScrollbarVisibility });
|
||||||
debounceEvent(this.scrollable.onScroll, (l, e) => e, 25)(e => {
|
debounceEvent(this.scrollable.onScroll, (l, e) => e, types.isNumber(this.options.scrollDebounce) ? this.options.scrollDebounce : 25)(e => {
|
||||||
this.render(e.scrollTop, e.height);
|
this.render(e.scrollTop, e.height);
|
||||||
this.relayout();
|
this.relayout();
|
||||||
this._onScroll.fire(e.scrollTop);
|
this._onScroll.fire(e.scrollTop);
|
||||||
@@ -140,9 +197,24 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
dom.addClass(domNode, 'monaco-split-view2');
|
dom.addClass(domNode, 'monaco-split-view2');
|
||||||
dom.addClass(domNode, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
|
dom.addClass(domNode, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
|
||||||
container.appendChild(domNode);
|
container.appendChild(domNode);
|
||||||
|
|
||||||
|
this.sashContainer = dom.append(this.el, dom.$('.sash-container'));
|
||||||
|
this.viewContainer = dom.append(this.el, dom.$('.split-view-container'));
|
||||||
|
|
||||||
|
this.style(options.styles || defaultStyles);
|
||||||
}
|
}
|
||||||
|
|
||||||
addViews(views: IView[], sizes: number[], index = this.viewItems.length): void {
|
style(styles: ISplitViewStyles): void {
|
||||||
|
if (styles.separatorBorder.isTransparent()) {
|
||||||
|
dom.removeClass(this.el, 'separator-border');
|
||||||
|
this.el.style.removeProperty('--separator-border');
|
||||||
|
} else {
|
||||||
|
dom.addClass(this.el, 'separator-border');
|
||||||
|
this.el.style.setProperty('--separator-border', styles.separatorBorder.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addViews(views: IView[], sizes: number[] | Sizing, index = this.viewItems.length): void {
|
||||||
if (this.state !== State.Idle) {
|
if (this.state !== State.Idle) {
|
||||||
throw new Error('Cant modify splitview');
|
throw new Error('Cant modify splitview');
|
||||||
}
|
}
|
||||||
@@ -150,16 +222,23 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
this.state = State.Busy;
|
this.state = State.Busy;
|
||||||
|
|
||||||
for (let i = 0; i < views.length; i++) {
|
for (let i = 0; i < views.length; i++) {
|
||||||
let viewIndex = index + i;
|
let size: number | Sizing;
|
||||||
let view = views[i], size = sizes[i];
|
if (Array.isArray(sizes)) {
|
||||||
|
size = sizes[i];
|
||||||
|
} else {
|
||||||
|
size = sizes;
|
||||||
|
}
|
||||||
|
const view = views[i];
|
||||||
|
view.id = view.id || generateUuid();
|
||||||
// Add view
|
// Add view
|
||||||
const container = dom.$('.split-view-view');
|
const container = dom.$('.split-view-view');
|
||||||
|
|
||||||
|
// removed default adding of the view directly to the container
|
||||||
|
|
||||||
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
||||||
const containerDisposable = toDisposable(() => {
|
const containerDisposable = toDisposable(() => {
|
||||||
if (container.parentElement) {
|
if (container.parentElement) {
|
||||||
this.el.removeChild(container);
|
this.viewContainer.removeChild(container);
|
||||||
}
|
}
|
||||||
this.onRemoveItems(new ArrayIterator([item.view.id]));
|
this.onRemoveItems(new ArrayIterator([item.view.id]));
|
||||||
});
|
});
|
||||||
@@ -169,65 +248,91 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
||||||
|
|
||||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||||
? size => item.container.style.height = `${item.size}px`
|
? () => item.container.style.height = `${item.size}px`
|
||||||
: size => item.container.style.width = `${item.size}px`;
|
: () => item.container.style.width = `${item.size}px`;
|
||||||
|
|
||||||
const layout = () => {
|
const layout = () => {
|
||||||
layoutContainer(item.size);
|
layoutContainer();
|
||||||
item.view.layout(item.size, this.orientation);
|
item.view.layout(item.size, this.orientation);
|
||||||
};
|
};
|
||||||
|
|
||||||
size = Math.round(size);
|
let viewSize: number;
|
||||||
const item: IViewItem = { onRemove, onAdd, view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
|
||||||
this.viewItems.splice(viewIndex, 0, item);
|
|
||||||
|
|
||||||
this.onInsertItems(new ArrayIterator([item]), viewIndex > 0 ? this.viewItems[viewIndex - 1].view.id : undefined);
|
if (typeof size === 'number') {
|
||||||
|
viewSize = size;
|
||||||
|
} else if (size.type === 'split') {
|
||||||
|
viewSize = this.getViewSize(size.index) / 2;
|
||||||
|
} else {
|
||||||
|
viewSize = view.minimumSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item: IViewItem = { onAdd, onRemove, view, container, size: viewSize, layout, disposable, height: viewSize, top: 0, width: 0 };
|
||||||
|
this.viewItems.splice(index, 0, item);
|
||||||
|
|
||||||
|
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
||||||
|
|
||||||
// Add sash
|
// Add sash
|
||||||
if (this.options.enableResizing && this.viewItems.length > 1) {
|
if (this.options.enableResizing && this.viewItems.length > 1) {
|
||||||
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
||||||
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
|
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
|
||||||
const sash = new Sash(this.el, layoutProvider, { orientation });
|
const sash = new Sash(this.sashContainer, layoutProvider, {
|
||||||
|
orientation,
|
||||||
|
orthogonalStartSash: this.orthogonalStartSash,
|
||||||
|
orthogonalEndSash: this.orthogonalEndSash
|
||||||
|
});
|
||||||
|
|
||||||
const sashEventMapper = this.orientation === Orientation.VERTICAL
|
const sashEventMapper = this.orientation === Orientation.VERTICAL
|
||||||
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
|
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey } as ISashEvent)
|
||||||
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
|
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey } as ISashEvent);
|
||||||
|
|
||||||
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
||||||
const onStartDisposable = onStart(this.onSashStart, this);
|
const onStartDisposable = onStart(this.onSashStart, this);
|
||||||
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
||||||
const onSashChangeDisposable = onChange(this.onSashChange, this);
|
const onChangeDisposable = onChange(this.onSashChange, this);
|
||||||
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
|
const onEnd = mapEvent(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash));
|
||||||
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
|
const onEndDisposable = onEnd(this.onSashEnd, this);
|
||||||
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
|
const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash)));
|
||||||
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
|
|
||||||
|
|
||||||
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
const disposable = combinedDisposable([onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
||||||
const sashItem: ISashItem = { sash, disposable };
|
const sashItem: ISashItem = { sash, disposable };
|
||||||
|
|
||||||
this.sashItems.splice(viewIndex - 1, 0, sashItem);
|
this.sashItems.splice(index - 1, 0, sashItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
view.render(container, this.orientation);
|
container.appendChild(view.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.relayout();
|
let highPriorityIndex: number | undefined;
|
||||||
|
|
||||||
|
if (!types.isArray(sizes) && sizes.type === 'split') {
|
||||||
|
highPriorityIndex = sizes.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.relayout(index, highPriorityIndex);
|
||||||
this.state = State.Idle;
|
this.state = State.Idle;
|
||||||
|
|
||||||
|
if (!types.isArray(sizes) && sizes.type === 'distribute') {
|
||||||
|
this.distributeViewSizes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addView(view: IView, size: number, index = this.viewItems.length): void {
|
addView(view: IView, size: number | Sizing, index = this.viewItems.length): void {
|
||||||
if (this.state !== State.Idle) {
|
if (this.state !== State.Idle) {
|
||||||
throw new Error('Cant modify splitview');
|
throw new Error('Cant modify splitview');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = State.Busy;
|
this.state = State.Busy;
|
||||||
|
|
||||||
|
view.id = view.id || generateUuid();
|
||||||
// Add view
|
// Add view
|
||||||
const container = dom.$('.split-view-view');
|
const container = dom.$('.split-view-view');
|
||||||
|
|
||||||
|
// removed default adding of the view directly to the container
|
||||||
|
|
||||||
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
const onChangeDisposable = view.onDidChange(size => this.onViewChange(item, size));
|
||||||
const containerDisposable = toDisposable(() => {
|
const containerDisposable = toDisposable(() => {
|
||||||
if (container.parentElement) {
|
if (container.parentElement) {
|
||||||
this.el.removeChild(container);
|
this.viewContainer.removeChild(container);
|
||||||
}
|
}
|
||||||
this.onRemoveItems(new ArrayIterator([item.view.id]));
|
this.onRemoveItems(new ArrayIterator([item.view.id]));
|
||||||
});
|
});
|
||||||
@@ -237,16 +342,25 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
const onRemove = view.onRemove ? () => view.onRemove() : () => { };
|
||||||
|
|
||||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||||
? size => item.container.style.height = `${item.size}px`
|
? () => item.container.style.height = `${item.size}px`
|
||||||
: size => item.container.style.width = `${item.size}px`;
|
: () => item.container.style.width = `${item.size}px`;
|
||||||
|
|
||||||
const layout = () => {
|
const layout = () => {
|
||||||
layoutContainer(item.size);
|
layoutContainer();
|
||||||
item.view.layout(item.size, this.orientation);
|
item.view.layout(item.size, this.orientation);
|
||||||
};
|
};
|
||||||
|
|
||||||
size = Math.round(size);
|
let viewSize: number;
|
||||||
const item: IViewItem = { onAdd, onRemove, view, container, size, layout, disposable, height: size, top: 0, width: 0 };
|
|
||||||
|
if (typeof size === 'number') {
|
||||||
|
viewSize = size;
|
||||||
|
} else if (size.type === 'split') {
|
||||||
|
viewSize = this.getViewSize(size.index) / 2;
|
||||||
|
} else {
|
||||||
|
viewSize = view.minimumSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item: IViewItem = { onAdd, onRemove, view, container, size: viewSize, layout, disposable, height: viewSize, top: 0, width: 0 };
|
||||||
this.viewItems.splice(index, 0, item);
|
this.viewItems.splice(index, 0, item);
|
||||||
|
|
||||||
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
this.onInsertItems(new ArrayIterator([item]), index > 0 ? this.viewItems[index - 1].view.id : undefined);
|
||||||
@@ -255,33 +369,47 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
if (this.options.enableResizing && this.viewItems.length > 1) {
|
if (this.options.enableResizing && this.viewItems.length > 1) {
|
||||||
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
||||||
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
|
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
|
||||||
const sash = new Sash(this.el, layoutProvider, { orientation });
|
const sash = new Sash(this.sashContainer, layoutProvider, {
|
||||||
|
orientation,
|
||||||
|
orthogonalStartSash: this.orthogonalStartSash,
|
||||||
|
orthogonalEndSash: this.orthogonalEndSash
|
||||||
|
});
|
||||||
|
|
||||||
const sashEventMapper = this.orientation === Orientation.VERTICAL
|
const sashEventMapper = this.orientation === Orientation.VERTICAL
|
||||||
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
|
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY, alt: e.altKey } as ISashEvent)
|
||||||
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
|
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX, alt: e.altKey } as ISashEvent);
|
||||||
|
|
||||||
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
const onStart = mapEvent(sash.onDidStart, sashEventMapper);
|
||||||
const onStartDisposable = onStart(this.onSashStart, this);
|
const onStartDisposable = onStart(this.onSashStart, this);
|
||||||
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
const onChange = mapEvent(sash.onDidChange, sashEventMapper);
|
||||||
const onSashChangeDisposable = onChange(this.onSashChange, this);
|
const onChangeDisposable = onChange(this.onSashChange, this);
|
||||||
const onEnd = mapEvent<void, void>(sash.onDidEnd, () => null);
|
const onEnd = mapEvent(sash.onDidEnd, () => firstIndex(this.sashItems, item => item.sash === sash));
|
||||||
const onEndDisposable = onEnd(() => this._onDidSashChange.fire());
|
const onEndDisposable = onEnd(this.onSashEnd, this);
|
||||||
const onDidReset = mapEvent<void, void>(sash.onDidReset, () => null);
|
const onDidResetDisposable = sash.onDidReset(() => this._onDidSashReset.fire(firstIndex(this.sashItems, item => item.sash === sash)));
|
||||||
const onDidResetDisposable = onDidReset(() => this._onDidSashReset.fire());
|
|
||||||
|
|
||||||
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
const disposable = combinedDisposable([onStartDisposable, onChangeDisposable, onEndDisposable, onDidResetDisposable, sash]);
|
||||||
const sashItem: ISashItem = { sash, disposable };
|
const sashItem: ISashItem = { sash, disposable };
|
||||||
|
|
||||||
sash.hide();
|
|
||||||
this.sashItems.splice(index - 1, 0, sashItem);
|
this.sashItems.splice(index - 1, 0, sashItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
view.render(container, this.orientation);
|
container.appendChild(view.element);
|
||||||
this.relayout(index);
|
|
||||||
|
let highPriorityIndex: number | undefined;
|
||||||
|
|
||||||
|
if (typeof size !== 'number' && size.type === 'split') {
|
||||||
|
highPriorityIndex = size.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.relayout(index, highPriorityIndex);
|
||||||
this.state = State.Idle;
|
this.state = State.Idle;
|
||||||
|
|
||||||
|
if (typeof size !== 'number' && size.type === 'distribute') {
|
||||||
|
this.distributeViewSizes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
removeView(index: number): void {
|
removeView(index: number, sizing?: Sizing): IView {
|
||||||
if (this.state !== State.Idle) {
|
if (this.state !== State.Idle) {
|
||||||
throw new Error('Cant modify splitview');
|
throw new Error('Cant modify splitview');
|
||||||
}
|
}
|
||||||
@@ -289,7 +417,7 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
this.state = State.Busy;
|
this.state = State.Busy;
|
||||||
|
|
||||||
if (index < 0 || index >= this.viewItems.length) {
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
return;
|
throw new Error('Index out of bounds');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove view
|
// Remove view
|
||||||
@@ -301,12 +429,16 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
const sashIndex = Math.max(index - 1, 0);
|
const sashIndex = Math.max(index - 1, 0);
|
||||||
const sashItem = this.sashItems.splice(sashIndex, 1)[0];
|
const sashItem = this.sashItems.splice(sashIndex, 1)[0];
|
||||||
sashItem.disposable.dispose();
|
sashItem.disposable.dispose();
|
||||||
} else {
|
|
||||||
this.lastRenderHeight = NaN, this.lastRenderTop = NaN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.relayout();
|
this.relayout();
|
||||||
this.state = State.Idle;
|
this.state = State.Idle;
|
||||||
|
|
||||||
|
if (sizing && sizing.type === 'distribute') {
|
||||||
|
this.distributeViewSizes();
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewItem.view;
|
||||||
}
|
}
|
||||||
|
|
||||||
moveView(from: number, to: number): void {
|
moveView(from: number, to: number): void {
|
||||||
@@ -314,36 +446,36 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
throw new Error('Cant modify splitview');
|
throw new Error('Cant modify splitview');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = State.Busy;
|
const size = this.getViewSize(from);
|
||||||
|
const view = this.removeView(from);
|
||||||
if (from < 0 || from >= this.viewItems.length) {
|
this.addView(view, size, to);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (to < 0 || to >= this.viewItems.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (from === to) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const viewItem = this.viewItems.splice(from, 1)[0];
|
|
||||||
this.viewItems.splice(to, 0, viewItem);
|
|
||||||
|
|
||||||
if (to + 1 < this.viewItems.length) {
|
|
||||||
this.el.insertBefore(viewItem.container, this.viewItems[to + 1].container);
|
|
||||||
} else {
|
|
||||||
this.el.appendChild(viewItem.container);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.layoutViews();
|
|
||||||
this.state = State.Idle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private relayout(lowPriorityIndex?: number): void {
|
swapViews(from: number, to: number): void {
|
||||||
|
if (this.state !== State.Idle) {
|
||||||
|
throw new Error('Cant modify splitview');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from > to) {
|
||||||
|
return this.swapViews(to, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromSize = this.getViewSize(from);
|
||||||
|
const toSize = this.getViewSize(to);
|
||||||
|
const toView = this.removeView(to);
|
||||||
|
const fromView = this.removeView(from);
|
||||||
|
|
||||||
|
this.addView(toView, fromSize, from);
|
||||||
|
this.addView(fromView, toSize, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
private relayout(lowPriorityIndex?: number, highPriorityIndex?: number): void {
|
||||||
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex);
|
|
||||||
|
this.resize(this.viewItems.length - 1, this.size - contentSize, undefined, lowPriorityIndex, highPriorityIndex);
|
||||||
|
this.distributeEmptySpace();
|
||||||
|
this.layoutViews();
|
||||||
|
this.saveProportions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setScrollPosition(position: number) {
|
public setScrollPosition(position: number) {
|
||||||
@@ -351,14 +483,188 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
layout(size: number): void {
|
layout(size: number): void {
|
||||||
const previousSize = this.size;
|
const previousSize = Math.max(this.size, this.contentSize);
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.contentSize = 0;
|
this.contentSize = 0;
|
||||||
this.lastRenderHeight = undefined;
|
this.lastRenderHeight = undefined;
|
||||||
this.lastRenderTop = undefined;
|
this.lastRenderTop = undefined;
|
||||||
this.resize(this.viewItems.length - 1, size - previousSize);
|
|
||||||
|
if (!this.proportions) {
|
||||||
|
this.resize(this.viewItems.length - 1, size - previousSize);
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < this.viewItems.length; i++) {
|
||||||
|
const item = this.viewItems[i];
|
||||||
|
item.size = clamp(Math.round(this.proportions[i] * size), item.view.minimumSize, item.view.maximumSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.distributeEmptySpace();
|
||||||
|
this.layoutViews();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private saveProportions(): void {
|
||||||
|
if (this.contentSize > 0) {
|
||||||
|
this.proportions = this.viewItems.map(i => i.size / this.contentSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSashStart({ sash, start, alt }: ISashEvent): void {
|
||||||
|
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
||||||
|
|
||||||
|
// This way, we can press Alt while we resize a sash, macOS style!
|
||||||
|
const disposable = combinedDisposable([
|
||||||
|
domEvent(document.body, 'keydown')(e => resetSashDragState(this.sashDragState.current, e.altKey)),
|
||||||
|
domEvent(document.body, 'keyup')(() => resetSashDragState(this.sashDragState.current, false))
|
||||||
|
]);
|
||||||
|
|
||||||
|
const resetSashDragState = (start: number, alt: boolean) => {
|
||||||
|
const sizes = this.viewItems.map(i => i.size);
|
||||||
|
let minDelta = Number.NEGATIVE_INFINITY;
|
||||||
|
let maxDelta = Number.POSITIVE_INFINITY;
|
||||||
|
|
||||||
|
if (this.inverseAltBehavior) {
|
||||||
|
alt = !alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alt) {
|
||||||
|
// When we're using the last sash with Alt, we're resizing
|
||||||
|
// the view to the left/up, instead of right/down as usual
|
||||||
|
// Thus, we must do the inverse of the usual
|
||||||
|
const isLastSash = index === this.sashItems.length - 1;
|
||||||
|
|
||||||
|
if (isLastSash) {
|
||||||
|
const viewItem = this.viewItems[index];
|
||||||
|
minDelta = (viewItem.view.minimumSize - viewItem.size) / 2;
|
||||||
|
maxDelta = (viewItem.view.maximumSize - viewItem.size) / 2;
|
||||||
|
} else {
|
||||||
|
const viewItem = this.viewItems[index + 1];
|
||||||
|
minDelta = (viewItem.size - viewItem.view.maximumSize) / 2;
|
||||||
|
maxDelta = (viewItem.size - viewItem.view.minimumSize) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sashDragState = { start, current: start, index, sizes, minDelta, maxDelta, alt, disposable };
|
||||||
|
};
|
||||||
|
|
||||||
|
resetSashDragState(start, alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSashChange({ current }: ISashEvent): void {
|
||||||
|
const { index, start, sizes, alt, minDelta, maxDelta } = this.sashDragState;
|
||||||
|
this.sashDragState.current = current;
|
||||||
|
|
||||||
|
const delta = current - start;
|
||||||
|
const newDelta = this.resize(index, delta, sizes, undefined, undefined, minDelta, maxDelta);
|
||||||
|
|
||||||
|
if (alt) {
|
||||||
|
const isLastSash = index === this.sashItems.length - 1;
|
||||||
|
const newSizes = this.viewItems.map(i => i.size);
|
||||||
|
const viewItemIndex = isLastSash ? index : index + 1;
|
||||||
|
const viewItem = this.viewItems[viewItemIndex];
|
||||||
|
const newMinDelta = viewItem.size - viewItem.view.maximumSize;
|
||||||
|
const newMaxDelta = viewItem.size - viewItem.view.minimumSize;
|
||||||
|
const resizeIndex = isLastSash ? index - 1 : index + 1;
|
||||||
|
|
||||||
|
this.resize(resizeIndex, -newDelta, newSizes, undefined, undefined, newMinDelta, newMaxDelta);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.distributeEmptySpace();
|
||||||
|
this.layoutViews();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSashEnd(index: number): void {
|
||||||
|
this._onDidSashChange.fire(index);
|
||||||
|
this.sashDragState.disposable.dispose();
|
||||||
|
this.saveProportions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onViewChange(item: IViewItem, size: number | undefined): void {
|
||||||
|
const index = this.viewItems.indexOf(item);
|
||||||
|
|
||||||
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
size = typeof size === 'number' ? size : item.size;
|
||||||
|
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
|
||||||
|
if (this.inverseAltBehavior && index > 0) {
|
||||||
|
// In this case, we want the view to grow or shrink both sides equally
|
||||||
|
// so we just resize the "left" side by half and let `resize` do the clamping magic
|
||||||
|
this.resize(index - 1, Math.floor((item.size - size) / 2));
|
||||||
|
this.distributeEmptySpace();
|
||||||
|
this.layoutViews();
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeView(index: number, size: number): void {
|
||||||
|
if (this.state !== State.Idle) {
|
||||||
|
throw new Error('Cant modify splitview');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.state = State.Busy;
|
||||||
|
|
||||||
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = this.viewItems[index];
|
||||||
|
size = Math.round(size);
|
||||||
|
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
let delta = size - item.size;
|
||||||
|
|
||||||
|
if (delta !== 0 && index < this.viewItems.length - 1) {
|
||||||
|
const downIndexes = range(index + 1, this.viewItems.length);
|
||||||
|
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||||
|
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||||
|
const deltaDown = clamp(delta, -expandDown, collapseDown);
|
||||||
|
|
||||||
|
this.resize(index, deltaDown);
|
||||||
|
delta -= deltaDown;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta !== 0 && index > 0) {
|
||||||
|
const upIndexes = range(index - 1, -1);
|
||||||
|
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||||
|
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||||
|
const deltaUp = clamp(-delta, -collapseUp, expandUp);
|
||||||
|
|
||||||
|
this.resize(index - 1, deltaUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.distributeEmptySpace();
|
||||||
|
this.layoutViews();
|
||||||
|
this.saveProportions();
|
||||||
|
this.state = State.Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
distributeViewSizes(): void {
|
||||||
|
const size = Math.floor(this.size / this.viewItems.length);
|
||||||
|
|
||||||
|
for (let i = 0; i < this.viewItems.length - 1; i++) {
|
||||||
|
this.resizeView(i, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getViewSize(index: number): number {
|
||||||
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.viewItems[index].size;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private render(scrollTop: number, viewHeight: number): void {
|
private render(scrollTop: number, viewHeight: number): void {
|
||||||
let i: number;
|
let i: number;
|
||||||
let stop: number;
|
let stop: number;
|
||||||
@@ -398,89 +704,13 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
let topItem = this.itemAtIndex(this.indexAt(renderTop));
|
let topItem = this.itemAtIndex(this.indexAt(renderTop));
|
||||||
|
|
||||||
if (topItem) {
|
if (topItem) {
|
||||||
this.el.style.top = (topItem.top - renderTop) + 'px';
|
this.viewContainer.style.top = (topItem.top - renderTop) + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastRenderTop = renderTop;
|
this.lastRenderTop = renderTop;
|
||||||
this.lastRenderHeight = renderBottom - renderTop;
|
this.lastRenderHeight = renderBottom - renderTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSashStart({ sash, start }: ISashEvent): void {
|
|
||||||
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
|
||||||
const sizes = this.viewItems.map(i => i.size);
|
|
||||||
|
|
||||||
// const upIndexes = range(index, -1);
|
|
||||||
// const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
|
||||||
// const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
|
||||||
|
|
||||||
// const downIndexes = range(index + 1, this.viewItems.length);
|
|
||||||
// const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
|
||||||
// const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
|
||||||
|
|
||||||
// const minDelta = -Math.min(collapseUp, expandDown);
|
|
||||||
// const maxDelta = Math.min(collapseDown, expandUp);
|
|
||||||
|
|
||||||
this.sashDragState = { start, index, sizes };
|
|
||||||
}
|
|
||||||
|
|
||||||
private onSashChange({ sash, current }: ISashEvent): void {
|
|
||||||
const { index, start, sizes } = this.sashDragState;
|
|
||||||
const delta = current - start;
|
|
||||||
|
|
||||||
this.resize(index, delta, sizes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onViewChange(item: IViewItem, size: number | undefined): void {
|
|
||||||
const index = this.viewItems.indexOf(item);
|
|
||||||
|
|
||||||
if (index < 0 || index >= this.viewItems.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
size = typeof size === 'number' ? size : item.size;
|
|
||||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
|
||||||
item.size = size;
|
|
||||||
this.relayout(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeView(index: number, size: number): void {
|
|
||||||
if (this.state !== State.Idle) {
|
|
||||||
throw new Error('Cant modify splitview');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = State.Busy;
|
|
||||||
|
|
||||||
if (index < 0 || index >= this.viewItems.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = this.viewItems[index];
|
|
||||||
size = Math.round(size);
|
|
||||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
|
||||||
let delta = size - item.size;
|
|
||||||
|
|
||||||
if (delta !== 0 && index < this.viewItems.length - 1) {
|
|
||||||
const downIndexes = range(index + 1, this.viewItems.length);
|
|
||||||
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
|
||||||
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
|
||||||
const deltaDown = clamp(delta, -expandDown, collapseDown);
|
|
||||||
|
|
||||||
this.resize(index, deltaDown);
|
|
||||||
delta -= deltaDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delta !== 0 && index > 0) {
|
|
||||||
const upIndexes = range(index - 1, -1);
|
|
||||||
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
|
||||||
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
|
||||||
const deltaUp = clamp(-delta, -collapseUp, expandUp);
|
|
||||||
|
|
||||||
this.resize(index - 1, deltaUp);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state = State.Idle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DOM changes
|
// DOM changes
|
||||||
|
|
||||||
private insertItemInDOM(item: IViewItem): boolean {
|
private insertItemInDOM(item: IViewItem): boolean {
|
||||||
@@ -496,13 +726,13 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (elementAfter === null) {
|
if (elementAfter === null) {
|
||||||
this.el.appendChild(item.container);
|
this.viewContainer.appendChild(item.container);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
this.el.insertBefore(item.container, elementAfter);
|
this.viewContainer.insertBefore(item.container, elementAfter);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.warn('Failed to locate previous tree element');
|
// console.warn('Failed to locate previous tree element');
|
||||||
this.el.appendChild(item.container);
|
this.viewContainer.appendChild(item.container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -517,85 +747,94 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.el.removeChild(item.container);
|
this.viewContainer.removeChild(item.container);
|
||||||
|
|
||||||
item.onRemove();
|
item.onRemove();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
getViewSize(index: number): number {
|
private resize(
|
||||||
|
index: number,
|
||||||
|
delta: number,
|
||||||
|
sizes = this.viewItems.map(i => i.size),
|
||||||
|
lowPriorityIndex?: number,
|
||||||
|
highPriorityIndex?: number,
|
||||||
|
overloadMinDelta: number = Number.NEGATIVE_INFINITY,
|
||||||
|
overloadMaxDelta: number = Number.POSITIVE_INFINITY
|
||||||
|
): number {
|
||||||
if (index < 0 || index >= this.viewItems.length) {
|
if (index < 0 || index >= this.viewItems.length) {
|
||||||
return -1;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.viewItems[index].size;
|
const upIndexes = range(index, -1);
|
||||||
|
const downIndexes = range(index + 1, this.viewItems.length);
|
||||||
|
|
||||||
|
if (typeof highPriorityIndex === 'number') {
|
||||||
|
pushToStart(upIndexes, highPriorityIndex);
|
||||||
|
pushToStart(downIndexes, highPriorityIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof lowPriorityIndex === 'number') {
|
||||||
|
pushToEnd(upIndexes, lowPriorityIndex);
|
||||||
|
pushToEnd(downIndexes, lowPriorityIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
const upItems = upIndexes.map(i => this.viewItems[i]);
|
||||||
|
const upSizes = upIndexes.map(i => sizes[i]);
|
||||||
|
|
||||||
|
const downItems = downIndexes.map(i => this.viewItems[i]);
|
||||||
|
const downSizes = downIndexes.map(i => sizes[i]);
|
||||||
|
|
||||||
|
const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.minimumSize - sizes[i]), 0);
|
||||||
|
const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
||||||
|
const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
||||||
|
const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.maximumSize), 0);
|
||||||
|
const minDelta = Math.max(minDeltaUp, minDeltaDown, overloadMinDelta);
|
||||||
|
const maxDelta = Math.min(maxDeltaDown, maxDeltaUp, overloadMaxDelta);
|
||||||
|
|
||||||
|
delta = clamp(delta, minDelta, maxDelta);
|
||||||
|
|
||||||
|
for (let i = 0, deltaUp = delta; i < upItems.length; i++) {
|
||||||
|
const item = upItems[i];
|
||||||
|
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
const viewDelta = size - upSizes[i];
|
||||||
|
|
||||||
|
deltaUp -= viewDelta;
|
||||||
|
item.size = size;
|
||||||
|
this.dirtyState = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0, deltaDown = delta; i < downItems.length; i++) {
|
||||||
|
const item = downItems[i];
|
||||||
|
const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize);
|
||||||
|
const viewDelta = size - downSizes[i];
|
||||||
|
|
||||||
|
deltaDown += viewDelta;
|
||||||
|
item.size = size;
|
||||||
|
this.dirtyState = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return delta;
|
||||||
}
|
}
|
||||||
|
|
||||||
private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size), lowPriorityIndex?: number): void {
|
private distributeEmptySpace(): void {
|
||||||
if (index < 0 || index >= this.viewItems.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delta !== 0) {
|
|
||||||
let upIndexes = range(index, -1);
|
|
||||||
let downIndexes = range(index + 1, this.viewItems.length);
|
|
||||||
|
|
||||||
if (typeof lowPriorityIndex === 'number') {
|
|
||||||
upIndexes = pushToEnd(upIndexes, lowPriorityIndex);
|
|
||||||
downIndexes = pushToEnd(downIndexes, lowPriorityIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
const upItems = upIndexes.map(i => this.viewItems[i]);
|
|
||||||
const upSizes = upIndexes.map(i => sizes[i]);
|
|
||||||
|
|
||||||
const downItems = downIndexes.map(i => this.viewItems[i]);
|
|
||||||
const downSizes = downIndexes.map(i => sizes[i]);
|
|
||||||
|
|
||||||
for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < upItems.length; i++) {
|
|
||||||
const item = upItems[i];
|
|
||||||
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
|
|
||||||
const viewDelta = size - upSizes[i];
|
|
||||||
|
|
||||||
deltaUp -= viewDelta;
|
|
||||||
item.size = size;
|
|
||||||
this.dirtyState = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < downItems.length; i++) {
|
|
||||||
const item = downItems[i];
|
|
||||||
const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize);
|
|
||||||
const viewDelta = size - downSizes[i];
|
|
||||||
|
|
||||||
deltaDown += viewDelta;
|
|
||||||
item.size = size;
|
|
||||||
this.dirtyState = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
let emptyDelta = this.size - contentSize;
|
let emptyDelta = this.size - contentSize;
|
||||||
|
|
||||||
for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) {
|
for (let i = this.viewItems.length - 1; emptyDelta !== 0 && i >= 0; i--) {
|
||||||
const item = this.viewItems[i];
|
const item = this.viewItems[i];
|
||||||
const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize);
|
const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize);
|
||||||
const viewDelta = size - item.size;
|
const viewDelta = size - item.size;
|
||||||
|
|
||||||
emptyDelta -= viewDelta;
|
emptyDelta -= viewDelta;
|
||||||
item.size = size;
|
item.size = size;
|
||||||
this.dirtyState = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
|
||||||
|
|
||||||
this.scrollable.setScrollDimensions({
|
|
||||||
scrollHeight: this.contentSize,
|
|
||||||
height: this.size
|
|
||||||
});
|
|
||||||
|
|
||||||
this.layoutViews();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private layoutViews(): void {
|
private layoutViews(): void {
|
||||||
|
// Save new content size
|
||||||
|
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||||
|
|
||||||
if (this.dirtyState) {
|
if (this.dirtyState) {
|
||||||
for (let i = this.indexAt(this.lastRenderTop); i <= this.indexAfter(this.lastRenderTop + this.lastRenderHeight) - 1; i++) {
|
for (let i = this.indexAt(this.lastRenderTop); i <= this.indexAfter(this.lastRenderTop + this.lastRenderHeight) - 1; i++) {
|
||||||
this.viewItems[i].layout();
|
this.viewItems[i].layout();
|
||||||
@@ -606,27 +845,10 @@ export class ScrollableSplitView extends HeightMap implements IDisposable {
|
|||||||
this.dirtyState = false;
|
this.dirtyState = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update sashes enablement
|
this.scrollable.setScrollDimensions({
|
||||||
// let previous = false;
|
scrollHeight: this.contentSize,
|
||||||
// const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous);
|
height: this.size
|
||||||
|
});
|
||||||
// previous = false;
|
|
||||||
// const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous);
|
|
||||||
|
|
||||||
// const reverseViews = [...this.viewItems].reverse();
|
|
||||||
// previous = false;
|
|
||||||
// const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse();
|
|
||||||
|
|
||||||
// previous = false;
|
|
||||||
// const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse();
|
|
||||||
|
|
||||||
// this.sashItems.forEach((s, i) => {
|
|
||||||
// if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) {
|
|
||||||
// s.sash.enable();
|
|
||||||
// } else {
|
|
||||||
// s.sash.disable();
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSashPosition(sash: Sash): number {
|
private getSashPosition(sash: Sash): number {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 151 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M6 4v8l4-4-4-4zm1 2.414l1.586 1.586-1.586 1.586v-3.172z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 151 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e8e8e8" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 131 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#646465" d="M11 10.07h-5.656l5.656-5.656v5.656z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 131 B |
@@ -1,94 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
.monaco-split-view {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-split-view > .split-view-view {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-split-view.vertical > .split-view-view {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-split-view.horizontal > .split-view-view {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-split-view > .split-view-view > .header {
|
|
||||||
position: relative;
|
|
||||||
line-height: 22px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
|
||||||
padding-left: 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-split-view > .split-view-view > .header.hide {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bold font style does not go well with CJK fonts */
|
|
||||||
.monaco-split-view:lang(zh-Hans) > .split-view-view > .header,
|
|
||||||
.monaco-split-view:lang(zh-Hant) > .split-view-view > .header,
|
|
||||||
.monaco-split-view:lang(ja) > .split-view-view > .header,
|
|
||||||
.monaco-split-view:lang(ko) > .split-view-view > .header { font-weight: normal; }
|
|
||||||
|
|
||||||
.monaco-split-view > .split-view-view > .header.collapsible {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-split-view > .split-view-view > .header.collapsible {
|
|
||||||
background-image: url('arrow-collapse.svg');
|
|
||||||
background-position: 2px center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) {
|
|
||||||
background-image: url('arrow-expand.svg');
|
|
||||||
background-position: 2px center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs-dark .monaco-split-view > .split-view-view > .header.collapsible {
|
|
||||||
background-image: url('arrow-collapse-dark.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
.vs-dark .monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) {
|
|
||||||
background-image: url('arrow-expand-dark.svg');
|
|
||||||
background-position: 2px center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Animation */
|
|
||||||
|
|
||||||
.monaco-split-view.animated > .split-view-view {
|
|
||||||
transition-duration: 0.15s;
|
|
||||||
-webkit-transition-duration: 0.15s;
|
|
||||||
-moz-transition-duration: 0.15s;
|
|
||||||
transition-timing-function: ease-out;
|
|
||||||
-webkit-transition-timing-function: ease-out;
|
|
||||||
-moz-transition-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-split-view.vertical.animated > .split-view-view {
|
|
||||||
transition-property: height;
|
|
||||||
-webkit-transition-property: height;
|
|
||||||
-moz-transition-property: height;
|
|
||||||
}
|
|
||||||
|
|
||||||
.monaco-split-view.horizontal.animated > .split-view-view {
|
|
||||||
transition-property: width;
|
|
||||||
-webkit-transition-property: width;
|
|
||||||
-moz-transition-property: width;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hc-black .split-view-view > .header .action-label:before {
|
|
||||||
top: 4px !important;
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,16 @@
|
|||||||
// Adapted from https://github.com/naresh-n/slickgrid-column-data-autosize/blob/master/src/slick.autocolumnsize.js
|
// 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 { mixin, clone } from 'sql/base/common/objects';
|
||||||
|
import { isInDOM } from 'vs/base/browser/dom';
|
||||||
|
|
||||||
export interface IAutoColumnSizeOptions extends Slick.PluginOptions {
|
export interface IAutoColumnSizeOptions extends Slick.PluginOptions {
|
||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
|
autoSizeOnRender?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: IAutoColumnSizeOptions = {
|
const defaultOptions: IAutoColumnSizeOptions = {
|
||||||
maxWidth: 200
|
maxWidth: 200,
|
||||||
|
autoSizeOnRender: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
||||||
@@ -15,6 +18,7 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
|||||||
private _$container: JQuery;
|
private _$container: JQuery;
|
||||||
private _context: CanvasRenderingContext2D;
|
private _context: CanvasRenderingContext2D;
|
||||||
private _options: IAutoColumnSizeOptions;
|
private _options: IAutoColumnSizeOptions;
|
||||||
|
private onPostEventHandler = new Slick.EventHandler();
|
||||||
|
|
||||||
constructor(options: IAutoColumnSizeOptions = defaultOptions) {
|
constructor(options: IAutoColumnSizeOptions = defaultOptions) {
|
||||||
this._options = mixin(options, defaultOptions, false);
|
this._options = mixin(options, defaultOptions, false);
|
||||||
@@ -23,8 +27,12 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
|||||||
public init(grid: Slick.Grid<T>) {
|
public init(grid: Slick.Grid<T>) {
|
||||||
this._grid = grid;
|
this._grid = grid;
|
||||||
|
|
||||||
|
if (this._options.autoSizeOnRender) {
|
||||||
|
this.onPostEventHandler.subscribe(this._grid.onRendered, () => this.onPostRender());
|
||||||
|
}
|
||||||
|
|
||||||
this._$container = $(this._grid.getContainerNode());
|
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');
|
this._context = document.createElement('canvas').getContext('2d');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +40,66 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
|||||||
this._$container.off();
|
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 headerEl = $(e.currentTarget).closest('.slick-header-column');
|
||||||
let columnDef = headerEl.data('column');
|
let columnDef = headerEl.data('column');
|
||||||
|
|
||||||
@@ -43,6 +110,10 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
this.reSizeColumn(headerEl, columnDef);
|
||||||
|
}
|
||||||
|
|
||||||
|
private reSizeColumn(headerEl: JQuery, columnDef: Slick.Column<T>) {
|
||||||
let headerWidth = this.getElementWidth(headerEl[0]);
|
let headerWidth = this.getElementWidth(headerEl[0]);
|
||||||
let colIndex = this._grid.getColumnIndex(columnDef.id);
|
let colIndex = this._grid.getColumnIndex(columnDef.id);
|
||||||
let origCols = this._grid.getColumns();
|
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) {
|
if (!isUndefinedOrNull(this.grid.getColumns()[0].selectable) && !this.grid.getColumns()[0].selectable) {
|
||||||
startColumn = 1;
|
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);
|
this._onCopy.fire(ranges);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -349,6 +349,7 @@ export class RowDetailView {
|
|||||||
//slick-cell to escape the cell overflow clipping.
|
//slick-cell to escape the cell overflow clipping.
|
||||||
|
|
||||||
//sneaky extra </div> inserted here-----------------v
|
//sneaky extra </div> inserted here-----------------v
|
||||||
|
/* tslint:disable:no-unexternalized-strings */
|
||||||
html.push("<div class='detailView-toggle collapse'></div></div>");
|
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
|
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("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='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>");
|
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
|
//&omit a final closing detail container </div> that would come next
|
||||||
|
|
||||||
return html.join('');
|
return html.join('');
|
||||||
@@ -364,17 +366,21 @@ export class RowDetailView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public resizeDetailView(item) {
|
public resizeDetailView(item) {
|
||||||
if (!item) return;
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Grad each of the dom items
|
// Grad each of the dom items
|
||||||
let mainContainer = document.getElementById('detailViewContainer_' + item.id);
|
let mainContainer = document.getElementById('detailViewContainer_' + item.id);
|
||||||
let cellItem = document.getElementById('cellDetailView_' + item.id);
|
let cellItem = document.getElementById('cellDetailView_' + item.id);
|
||||||
let inner = document.getElementById('innerDetailView_' + 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++) {
|
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
|
let rowHeight = this._grid.getOptions().rowHeight; // height of a row
|
||||||
@@ -395,9 +401,9 @@ export class RowDetailView {
|
|||||||
this._grid.getOptions().minRowBuffer = item._sizePadding + 3;
|
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) {
|
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);
|
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>();
|
private _onClick = new Emitter<ITableMouseEvent>();
|
||||||
public readonly onClick: Event<ITableMouseEvent> = this._onClick.event;
|
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>) {
|
constructor(parent: HTMLElement, configuration?: ITableConfiguration<T>, options?: Slick.GridOptions<T>) {
|
||||||
super();
|
super();
|
||||||
if (!configuration || !configuration.dataProvider || isArray(configuration.dataProvider)) {
|
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.onContextMenu, this._onContextMenu);
|
||||||
this.mapMouseEvent(this._grid.onClick, this._onClick);
|
this.mapMouseEvent(this._grid.onClick, this._onClick);
|
||||||
|
this._grid.onColumnsResized.subscribe(() => this._onColumnResize.fire());
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapMouseEvent(slickEvent: Slick.Event<any>, emitter: Emitter<ITableMouseEvent>) {
|
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 { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
import * as types from 'vs/base/common/types';
|
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';
|
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> {
|
function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
|
||||||
let field = args.sortCol.field;
|
let field = args.sortCol.field;
|
||||||
let sign = args.sortAsc ? 1 : -1;
|
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> {
|
export class TableDataView<T extends Slick.SlickData> implements IDisposableDataProvider<T> {
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { Table } from './table';
|
|
||||||
import { TableDataView } from './tableDataView';
|
|
||||||
import { View, Orientation, AbstractCollapsibleView, HeaderView, ICollapsibleViewOptions, IViewOptions, CollapsibleState } from 'sql/base/browser/ui/splitview/splitview';
|
|
||||||
import { $ } from 'vs/base/browser/builder';
|
|
||||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
|
||||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
|
||||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
|
||||||
|
|
||||||
export class TableBasicView<T> extends View {
|
|
||||||
private _table: Table<T>;
|
|
||||||
private _container: HTMLElement;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
viewOpts: IViewOptions,
|
|
||||||
data?: Array<T> | TableDataView<T>,
|
|
||||||
columns?: Slick.Column<T>[],
|
|
||||||
tableOpts?: Slick.GridOptions<T>
|
|
||||||
) {
|
|
||||||
super(undefined, viewOpts);
|
|
||||||
this._container = document.createElement('div');
|
|
||||||
this._container.className = 'table-view';
|
|
||||||
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get table(): Table<T> {
|
|
||||||
return this._table;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(container: HTMLElement, orientation: Orientation): void {
|
|
||||||
container.appendChild(this._container);
|
|
||||||
}
|
|
||||||
|
|
||||||
focus(): void {
|
|
||||||
this._table.focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
layout(size: number, orientation: Orientation): void {
|
|
||||||
this._table.layout(size, orientation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TableHeaderView<T> extends HeaderView {
|
|
||||||
private _table: Table<T>;
|
|
||||||
private _container: HTMLElement;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private _viewTitle: string,
|
|
||||||
viewOpts: IViewOptions,
|
|
||||||
data?: Array<T> | TableDataView<T>,
|
|
||||||
columns?: Slick.Column<T>[],
|
|
||||||
tableOpts?: Slick.GridOptions<T>
|
|
||||||
) {
|
|
||||||
super(undefined, viewOpts);
|
|
||||||
this._container = document.createElement('div');
|
|
||||||
this._container.className = 'table-view';
|
|
||||||
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get table(): Table<T> {
|
|
||||||
return this._table;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderHeader(container: HTMLElement): void {
|
|
||||||
const titleDiv = $('div.title').appendTo(container);
|
|
||||||
$('span').text(this._viewTitle).appendTo(titleDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderBody(container: HTMLElement): void {
|
|
||||||
container.appendChild(this._container);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected layoutBody(size: number): void {
|
|
||||||
this._table.layout(size, Orientation.VERTICAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
focus(): void {
|
|
||||||
this._table.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TableCollapsibleView<T> extends AbstractCollapsibleView {
|
|
||||||
private _table: Table<T>;
|
|
||||||
private _container: HTMLElement;
|
|
||||||
private _headerTabListener: lifecycle.IDisposable;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private _viewTitle: string,
|
|
||||||
viewOpts: ICollapsibleViewOptions,
|
|
||||||
data?: Array<T> | TableDataView<T>,
|
|
||||||
columns?: Slick.Column<T>[],
|
|
||||||
tableOpts?: Slick.GridOptions<T>
|
|
||||||
) {
|
|
||||||
super(undefined, viewOpts);
|
|
||||||
this._container = document.createElement('div');
|
|
||||||
this._container.className = 'table-view';
|
|
||||||
this._table = new Table<T>(this._container, { dataProvider: data, columns }, tableOpts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(container: HTMLElement, orientation: Orientation): void {
|
|
||||||
super.render(container, orientation);
|
|
||||||
this._headerTabListener = DOM.addDisposableListener(this.header, DOM.EventType.KEY_DOWN, (e) => {
|
|
||||||
let event = new StandardKeyboardEvent(e);
|
|
||||||
if (event.equals(KeyCode.Tab) && this.state === CollapsibleState.EXPANDED) {
|
|
||||||
let element = this._table.getSelectedRows();
|
|
||||||
if (!element || element.length === 0) {
|
|
||||||
this._table.setSelectedRows([0]);
|
|
||||||
this._table.setActiveCell(0, 1);
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public dispose(): void {
|
|
||||||
if (this._headerTabListener) {
|
|
||||||
this._headerTabListener.dispose();
|
|
||||||
this._headerTabListener = null;
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
public addContainerClass(className: string) {
|
|
||||||
this._container.classList.add(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
public get table(): Table<T> {
|
|
||||||
return this._table;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderHeader(container: HTMLElement): void {
|
|
||||||
const titleDiv = $('div.title').appendTo(container);
|
|
||||||
$('span').text(this._viewTitle).appendTo(titleDiv);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderBody(container: HTMLElement): void {
|
|
||||||
container.appendChild(this._container);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected layoutBody(size: number): void {
|
|
||||||
this._table.layout(size, Orientation.VERTICAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
.custom-view-tree-node-item {
|
|
||||||
display: flex;
|
|
||||||
height: 22px;
|
|
||||||
line-height: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-view-tree-node-item > .custom-view-tree-node-item-icon {
|
|
||||||
background-size: 16px;
|
|
||||||
background-position: left center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
padding-right: 6px;
|
|
||||||
width: 16px;
|
|
||||||
height: 22px;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-view-tree-node-item > .custom-view-tree-node-item-label {
|
|
||||||
flex: 1;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import * as nls from 'vs/nls';
|
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
|
||||||
import { IThemable } from 'vs/platform/theme/common/styler';
|
|
||||||
import * as errors from 'vs/base/common/errors';
|
|
||||||
import { $ } from 'vs/base/browser/builder';
|
|
||||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
|
||||||
import { IAction, IActionRunner } from 'vs/base/common/actions';
|
|
||||||
import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
|
||||||
import { prepareActions } from 'vs/workbench/browser/actions';
|
|
||||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
|
||||||
import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
|
||||||
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
|
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
|
||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
|
||||||
import { AbstractCollapsibleView, CollapsibleState, IView as IBaseView, SplitView, ViewSizing } from 'sql/base/browser/ui/splitview/splitview';
|
|
||||||
|
|
||||||
export interface IViewOptions {
|
|
||||||
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
actionRunner: IActionRunner;
|
|
||||||
|
|
||||||
collapsed: boolean;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IViewConstructorSignature {
|
|
||||||
|
|
||||||
new(initialSize: number, options: IViewOptions, ...services: { _serviceBrand: any; }[]): IView;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IView extends IBaseView, IThemable {
|
|
||||||
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
getHeaderElement(): HTMLElement;
|
|
||||||
|
|
||||||
create(): TPromise<void>;
|
|
||||||
|
|
||||||
setVisible(visible: boolean): TPromise<void>;
|
|
||||||
|
|
||||||
isVisible(): boolean;
|
|
||||||
|
|
||||||
getActions(): IAction[];
|
|
||||||
|
|
||||||
getSecondaryActions(): IAction[];
|
|
||||||
|
|
||||||
getActionItem(action: IAction): IActionItem;
|
|
||||||
|
|
||||||
getActionsContext(): any;
|
|
||||||
|
|
||||||
showHeader(): boolean;
|
|
||||||
|
|
||||||
hideHeader(): boolean;
|
|
||||||
|
|
||||||
focusBody(): void;
|
|
||||||
|
|
||||||
isExpanded(): boolean;
|
|
||||||
|
|
||||||
expand(): void;
|
|
||||||
|
|
||||||
collapse(): void;
|
|
||||||
|
|
||||||
getOptimalWidth(): number;
|
|
||||||
|
|
||||||
shutdown(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICollapsibleViewOptions extends IViewOptions {
|
|
||||||
|
|
||||||
ariaHeaderLabel?: string;
|
|
||||||
|
|
||||||
sizing: ViewSizing;
|
|
||||||
|
|
||||||
initialBodySize?: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class CollapsibleView extends AbstractCollapsibleView implements IView {
|
|
||||||
|
|
||||||
readonly id: string;
|
|
||||||
readonly name: string;
|
|
||||||
|
|
||||||
protected treeContainer: HTMLElement;
|
|
||||||
protected tree: ITree;
|
|
||||||
protected toDispose: IDisposable[];
|
|
||||||
protected toolBar: ToolBar;
|
|
||||||
protected actionRunner: IActionRunner;
|
|
||||||
protected isDisposed: boolean;
|
|
||||||
|
|
||||||
private _isVisible: boolean;
|
|
||||||
|
|
||||||
private dragHandler: DelayedDragHandler;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
initialSize: number,
|
|
||||||
options: ICollapsibleViewOptions,
|
|
||||||
protected keybindingService: IKeybindingService,
|
|
||||||
protected contextMenuService: IContextMenuService
|
|
||||||
) {
|
|
||||||
super(initialSize, {
|
|
||||||
ariaHeaderLabel: options.ariaHeaderLabel,
|
|
||||||
sizing: options.sizing,
|
|
||||||
bodySize: options.initialBodySize ? options.initialBodySize : 4 * 22,
|
|
||||||
initialState: options.collapsed ? CollapsibleState.COLLAPSED : CollapsibleState.EXPANDED,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.id = options.id;
|
|
||||||
this.name = options.name;
|
|
||||||
this.actionRunner = options.actionRunner;
|
|
||||||
this.toDispose = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
protected changeState(state: CollapsibleState): void {
|
|
||||||
this.updateTreeVisibility(this.tree, state === CollapsibleState.EXPANDED);
|
|
||||||
|
|
||||||
super.changeState(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
get draggableLabel(): string { return this.name; }
|
|
||||||
|
|
||||||
public create(): TPromise<void> {
|
|
||||||
return TPromise.as(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
getHeaderElement(): HTMLElement {
|
|
||||||
return this.header;
|
|
||||||
}
|
|
||||||
|
|
||||||
public renderHeader(container: HTMLElement): void {
|
|
||||||
|
|
||||||
// Tool bar
|
|
||||||
this.toolBar = new ToolBar($('div.actions').appendTo(container).getHTMLElement(), this.contextMenuService, {
|
|
||||||
orientation: ActionsOrientation.HORIZONTAL,
|
|
||||||
actionItemProvider: (action) => this.getActionItem(action),
|
|
||||||
ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.name),
|
|
||||||
getKeyBinding: (action) => this.keybindingService.lookupKeybinding(action.id)
|
|
||||||
});
|
|
||||||
this.toolBar.actionRunner = this.actionRunner;
|
|
||||||
this.updateActions();
|
|
||||||
|
|
||||||
// Expand on drag over
|
|
||||||
this.dragHandler = new DelayedDragHandler(container, () => {
|
|
||||||
if (!this.isExpanded()) {
|
|
||||||
this.expand();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected updateActions(): void {
|
|
||||||
this.toolBar.setActions(prepareActions(this.getActions()), prepareActions(this.getSecondaryActions()))();
|
|
||||||
this.toolBar.context = this.getActionsContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected renderViewTree(container: HTMLElement): HTMLElement {
|
|
||||||
const treeContainer = document.createElement('div');
|
|
||||||
container.appendChild(treeContainer);
|
|
||||||
|
|
||||||
return treeContainer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getViewer(): ITree {
|
|
||||||
return this.tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public isVisible(): boolean {
|
|
||||||
return this._isVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setVisible(visible: boolean): TPromise<void> {
|
|
||||||
if (this._isVisible !== visible) {
|
|
||||||
this._isVisible = visible;
|
|
||||||
this.updateTreeVisibility(this.tree, visible && this.state === CollapsibleState.EXPANDED);
|
|
||||||
}
|
|
||||||
|
|
||||||
return TPromise.as(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public focusBody(): void {
|
|
||||||
this.focusTree();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected reveal(element: any, relativeTop?: number): TPromise<void> {
|
|
||||||
if (!this.tree) {
|
|
||||||
return TPromise.as(null); // return early if viewlet has not yet been created
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.tree.reveal(element, relativeTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
public layoutBody(size: number): void {
|
|
||||||
if (this.tree) {
|
|
||||||
this.treeContainer.style.height = size + 'px';
|
|
||||||
this.tree.layout(size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getActions(): IAction[] {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSecondaryActions(): IAction[] {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getActionItem(action: IAction): IActionItem {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getActionsContext(): any {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
public shutdown(): void {
|
|
||||||
// Subclass to implement
|
|
||||||
}
|
|
||||||
|
|
||||||
public getOptimalWidth(): number {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public dispose(): void {
|
|
||||||
this.isDisposed = true;
|
|
||||||
this.treeContainer = null;
|
|
||||||
|
|
||||||
if (this.tree) {
|
|
||||||
this.tree.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.dragHandler) {
|
|
||||||
this.dragHandler.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toDispose = dispose(this.toDispose);
|
|
||||||
|
|
||||||
if (this.toolBar) {
|
|
||||||
this.toolBar.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateTreeVisibility(tree: ITree, isVisible: boolean): void {
|
|
||||||
if (!tree) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isVisible) {
|
|
||||||
$(tree.getHTMLElement()).show();
|
|
||||||
} else {
|
|
||||||
$(tree.getHTMLElement()).hide(); // make sure the tree goes out of the tabindex world by hiding it
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isVisible) {
|
|
||||||
tree.onVisible();
|
|
||||||
} else {
|
|
||||||
tree.onHidden();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private focusTree(): void {
|
|
||||||
if (!this.tree) {
|
|
||||||
return; // return early if viewlet has not yet been created
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure the current selected element is revealed
|
|
||||||
const selection = this.tree.getSelection();
|
|
||||||
if (selection.length > 0) {
|
|
||||||
this.reveal(selection[0], 0.5).done(null, errors.onUnexpectedError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass Focus to Viewer
|
|
||||||
this.tree.domFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IViewletViewOptions extends IViewOptions {
|
|
||||||
|
|
||||||
viewletSettings: object;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IViewState {
|
|
||||||
|
|
||||||
collapsed: boolean;
|
|
||||||
|
|
||||||
size: number | undefined;
|
|
||||||
|
|
||||||
isHidden: boolean;
|
|
||||||
|
|
||||||
order: number;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { TPromise } from 'vs/base/common/winjs.base';
|
|
||||||
import { Event } from 'vs/base/common/event';
|
|
||||||
import { Command } from 'vs/editor/common/modes';
|
|
||||||
|
|
||||||
export type TreeViewItemHandleArg = {
|
|
||||||
$treeViewId: string,
|
|
||||||
$treeItemHandle: number
|
|
||||||
};
|
|
||||||
|
|
||||||
export enum TreeItemCollapsibleState {
|
|
||||||
None = 0,
|
|
||||||
Collapsed = 1,
|
|
||||||
Expanded = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITreeItem {
|
|
||||||
|
|
||||||
handle: number;
|
|
||||||
|
|
||||||
label: string;
|
|
||||||
|
|
||||||
icon?: string;
|
|
||||||
|
|
||||||
iconDark?: string;
|
|
||||||
|
|
||||||
contextValue?: string;
|
|
||||||
|
|
||||||
command?: Command;
|
|
||||||
|
|
||||||
children?: ITreeItem[];
|
|
||||||
|
|
||||||
collapsibleState?: TreeItemCollapsibleState;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITreeViewDataProvider {
|
|
||||||
|
|
||||||
onDidChange: Event<ITreeItem[] | undefined | null>;
|
|
||||||
|
|
||||||
onDispose: Event<void>;
|
|
||||||
|
|
||||||
getElements(): TPromise<ITreeItem[]>;
|
|
||||||
|
|
||||||
getChildren(element: ITreeItem): TPromise<ITreeItem[]>;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
|
|||||||
import * as cr from 'vs/platform/theme/common/colorRegistry';
|
import * as cr from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { IThemable, attachStyler } from 'vs/platform/theme/common/styler';
|
import { IThemable, attachStyler } from 'vs/platform/theme/common/styler';
|
||||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
import { SIDE_BAR_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||||
|
import { IPanelColors } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||||
|
|
||||||
export function attachModalDialogStyler(widget: IThemable, themeService: IThemeService, style?:
|
export function attachModalDialogStyler(widget: IThemable, themeService: IThemeService, style?:
|
||||||
{
|
{
|
||||||
@@ -262,3 +263,12 @@ export function attachCheckboxStyler(widget: IThemable, themeService: IThemeServ
|
|||||||
disabledCheckboxForeground: (style && style.disabledCheckboxForeground) || sqlcolors.disabledCheckboxForeground
|
disabledCheckboxForeground: (style && style.disabledCheckboxForeground) || sqlcolors.disabledCheckboxForeground
|
||||||
}, widget);
|
}, widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function attachPanelStyler(widget: IThemable, themeService: IThemeService) {
|
||||||
|
return attachStyler<IPanelColors>(themeService, {
|
||||||
|
headerForeground: SIDE_BAR_SECTION_HEADER_FOREGROUND,
|
||||||
|
headerBackground: SIDE_BAR_SECTION_HEADER_BACKGROUND,
|
||||||
|
// headerHighContrastBorder: index === 0 ? null : contrastBorder,
|
||||||
|
dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND
|
||||||
|
}, widget);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,35 +8,87 @@
|
|||||||
import 'vs/css!./media/accountDialog';
|
import 'vs/css!./media/accountDialog';
|
||||||
import 'vs/css!sql/parts/accountManagement/common/media/accountActions';
|
import 'vs/css!sql/parts/accountManagement/common/media/accountActions';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { SplitView } from 'sql/base/browser/ui/splitview/splitview';
|
|
||||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||||
import { IListService, ListService } from 'vs/platform/list/browser/listService';
|
|
||||||
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
import { IPartService } from 'vs/workbench/services/part/common/partService';
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||||
import { ActionRunner } from 'vs/base/common/actions';
|
import { IAction } from 'vs/base/common/actions';
|
||||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
import { SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview';
|
||||||
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||||
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { values } from 'vs/base/common/map';
|
||||||
|
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
|
|
||||||
import { Button } from 'sql/base/browser/ui/button/button';
|
import { Button } from 'sql/base/browser/ui/button/button';
|
||||||
import { Modal } from 'sql/base/browser/ui/modal/modal';
|
import { Modal } from 'sql/base/browser/ui/modal/modal';
|
||||||
import { attachModalDialogStyler, attachButtonStyler } from 'sql/common/theme/styler';
|
import { attachModalDialogStyler, attachButtonStyler, attachPanelStyler } from 'sql/common/theme/styler';
|
||||||
import { AccountViewModel } from 'sql/parts/accountManagement/accountDialog/accountViewModel';
|
import { AccountViewModel } from 'sql/parts/accountManagement/accountDialog/accountViewModel';
|
||||||
import { AddAccountAction } from 'sql/parts/accountManagement/common/accountActions';
|
import { AddAccountAction } from 'sql/parts/accountManagement/common/accountActions';
|
||||||
import { AccountListRenderer, AccountListDelegate } from 'sql/parts/accountManagement/common/accountListRenderer';
|
import { AccountListRenderer, AccountListDelegate } from 'sql/parts/accountManagement/common/accountListRenderer';
|
||||||
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes';
|
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes';
|
||||||
import { FixedListView } from 'sql/platform/views/fixedListView';
|
|
||||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|
||||||
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService';
|
||||||
|
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||||
|
|
||||||
|
class AccountPanel extends ViewletPanel {
|
||||||
|
public index: number;
|
||||||
|
private accountList: List<sqlops.Account>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private options: IViewletPanelOptions,
|
||||||
|
@IKeybindingService keybindingService: IKeybindingService,
|
||||||
|
@IContextMenuService contextMenuService: IContextMenuService,
|
||||||
|
@IConfigurationService configurationService: IConfigurationService,
|
||||||
|
@IInstantiationService private instantiationService: IInstantiationService,
|
||||||
|
@IThemeService private themeService: IThemeService
|
||||||
|
) {
|
||||||
|
super(options, keybindingService, contextMenuService, configurationService);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected renderBody(container: HTMLElement): void {
|
||||||
|
this.accountList = new List<sqlops.Account>(container, new AccountListDelegate(AccountDialog.ACCOUNTLIST_HEIGHT), [this.instantiationService.createInstance(AccountListRenderer)]);
|
||||||
|
this.disposables.push(attachListStyler(this.accountList, this.themeService));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected layoutBody(size: number): void {
|
||||||
|
if (this.accountList) {
|
||||||
|
this.accountList.layout(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get length(): number {
|
||||||
|
return this.accountList.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public focus() {
|
||||||
|
this.accountList.domFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public updateAccounts(accounts: sqlops.Account[]) {
|
||||||
|
this.accountList.splice(0, this.accountList.length, accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSelection(indexes: number[]) {
|
||||||
|
this.accountList.setSelection(indexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getActions(): IAction[] {
|
||||||
|
return [this.instantiationService.createInstance(
|
||||||
|
AddAccountAction,
|
||||||
|
this.options.id
|
||||||
|
)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface IProviderViewUiComponent {
|
export interface IProviderViewUiComponent {
|
||||||
view: FixedListView<sqlops.Account>;
|
view: AccountPanel;
|
||||||
addAccountAction: AddAccountAction;
|
addAccountAction: AddAccountAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,13 +98,10 @@ export class AccountDialog extends Modal {
|
|||||||
public viewModel: AccountViewModel;
|
public viewModel: AccountViewModel;
|
||||||
|
|
||||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||||
private _providerViews: { [providerId: string]: IProviderViewUiComponent } = {};
|
private _providerViewsMap = new Map<string, IProviderViewUiComponent>();
|
||||||
|
|
||||||
private _closeButton: Button;
|
private _closeButton: Button;
|
||||||
private _addAccountButton: Button;
|
private _addAccountButton: Button;
|
||||||
private _delegate: AccountListDelegate;
|
|
||||||
private _accountRenderer: AccountListRenderer;
|
|
||||||
private _actionRunner: ActionRunner;
|
|
||||||
private _splitView: SplitView;
|
private _splitView: SplitView;
|
||||||
private _container: HTMLElement;
|
private _container: HTMLElement;
|
||||||
private _splitViewContainer: HTMLElement;
|
private _splitViewContainer: HTMLElement;
|
||||||
@@ -68,10 +117,10 @@ export class AccountDialog extends Modal {
|
|||||||
constructor(
|
constructor(
|
||||||
@IPartService partService: IPartService,
|
@IPartService partService: IPartService,
|
||||||
@IThemeService themeService: IThemeService,
|
@IThemeService themeService: IThemeService,
|
||||||
@IListService private _listService: IListService,
|
|
||||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
@IContextMenuService private _contextMenuService: IContextMenuService,
|
@IContextMenuService private _contextMenuService: IContextMenuService,
|
||||||
@IKeybindingService private _keybindingService: IKeybindingService,
|
@IKeybindingService private _keybindingService: IKeybindingService,
|
||||||
|
@IConfigurationService private _configurationService: IConfigurationService,
|
||||||
@ITelemetryService telemetryService: ITelemetryService,
|
@ITelemetryService telemetryService: ITelemetryService,
|
||||||
@IContextKeyService contextKeyService: IContextKeyService,
|
@IContextKeyService contextKeyService: IContextKeyService,
|
||||||
@IClipboardService clipboardService: IClipboardService
|
@IClipboardService clipboardService: IClipboardService
|
||||||
@@ -86,11 +135,6 @@ export class AccountDialog extends Modal {
|
|||||||
contextKeyService,
|
contextKeyService,
|
||||||
{ hasSpinner: true }
|
{ hasSpinner: true }
|
||||||
);
|
);
|
||||||
let self = this;
|
|
||||||
|
|
||||||
this._delegate = new AccountListDelegate(AccountDialog.ACCOUNTLIST_HEIGHT);
|
|
||||||
this._accountRenderer = this._instantiationService.createInstance(AccountListRenderer);
|
|
||||||
this._actionRunner = new ActionRunner();
|
|
||||||
|
|
||||||
// Setup the event emitters
|
// Setup the event emitters
|
||||||
this._onAddAccountErrorEmitter = new Emitter<string>();
|
this._onAddAccountErrorEmitter = new Emitter<string>();
|
||||||
@@ -98,28 +142,25 @@ export class AccountDialog extends Modal {
|
|||||||
|
|
||||||
// Create the view model and wire up the events
|
// Create the view model and wire up the events
|
||||||
this.viewModel = this._instantiationService.createInstance(AccountViewModel);
|
this.viewModel = this._instantiationService.createInstance(AccountViewModel);
|
||||||
this.viewModel.addProviderEvent(arg => { self.addProvider(arg); });
|
this.viewModel.addProviderEvent(arg => { this.addProvider(arg); });
|
||||||
this.viewModel.removeProviderEvent(arg => { self.removeProvider(arg); });
|
this.viewModel.removeProviderEvent(arg => { this.removeProvider(arg); });
|
||||||
this.viewModel.updateAccountListEvent(arg => { self.updateProviderAccounts(arg); });
|
this.viewModel.updateAccountListEvent(arg => { this.updateProviderAccounts(arg); });
|
||||||
|
|
||||||
// Load the initial contents of the view model
|
// Load the initial contents of the view model
|
||||||
this.viewModel.initialize()
|
this.viewModel.initialize()
|
||||||
.then(addedProviders => {
|
.then(addedProviders => {
|
||||||
for (let addedProvider of addedProviders) {
|
for (let addedProvider of addedProviders) {
|
||||||
self.addProvider(addedProvider);
|
this.addProvider(addedProvider);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// MODAL OVERRIDE METHODS //////////////////////////////////////////////
|
// MODAL OVERRIDE METHODS //////////////////////////////////////////////
|
||||||
protected layout(height?: number): void {
|
protected layout(height?: number): void {
|
||||||
// Ignore height as it's a subcomponent being laid out
|
|
||||||
this._splitView.layout(DOM.getContentHeight(this._container));
|
this._splitView.layout(DOM.getContentHeight(this._container));
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
let self = this;
|
|
||||||
|
|
||||||
super.render();
|
super.render();
|
||||||
attachModalDialogStyler(this, this._themeService);
|
attachModalDialogStyler(this, this._themeService);
|
||||||
this._closeButton = this.addFooterButton(localize('accountDialog.close', 'Close'), () => this.close());
|
this._closeButton = this.addFooterButton(localize('accountDialog.close', 'Close'), () => this.close());
|
||||||
@@ -128,7 +169,7 @@ export class AccountDialog extends Modal {
|
|||||||
|
|
||||||
protected renderBody(container: HTMLElement) {
|
protected renderBody(container: HTMLElement) {
|
||||||
this._container = container;
|
this._container = container;
|
||||||
this._splitViewContainer = DOM.$('div.account-view');
|
this._splitViewContainer = DOM.$('div.account-view.monaco-panel-view');
|
||||||
DOM.append(container, this._splitViewContainer);
|
DOM.append(container, this._splitViewContainer);
|
||||||
this._splitView = new SplitView(this._splitViewContainer);
|
this._splitView = new SplitView(this._splitViewContainer);
|
||||||
|
|
||||||
@@ -143,7 +184,7 @@ export class AccountDialog extends Modal {
|
|||||||
this._addAccountButton = new Button(buttonSection);
|
this._addAccountButton = new Button(buttonSection);
|
||||||
this._addAccountButton.label = localize('accountDialog.addConnection', 'Add an account');
|
this._addAccountButton.label = localize('accountDialog.addConnection', 'Add an account');
|
||||||
this._register(this._addAccountButton.onDidClick(() => {
|
this._register(this._addAccountButton.onDidClick(() => {
|
||||||
(<IProviderViewUiComponent>Object.values(this._providerViews)[0]).addAccountAction.run();
|
(<IProviderViewUiComponent>values(this._providerViewsMap)[0]).addAccountAction.run();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
DOM.append(container, this._noaccountViewContainer);
|
DOM.append(container, this._noaccountViewContainer);
|
||||||
@@ -189,20 +230,19 @@ export class AccountDialog extends Modal {
|
|||||||
private showSplitView() {
|
private showSplitView() {
|
||||||
this._splitViewContainer.hidden = false;
|
this._splitViewContainer.hidden = false;
|
||||||
this._noaccountViewContainer.hidden = true;
|
this._noaccountViewContainer.hidden = true;
|
||||||
let views = this._splitView.getViews();
|
if (values(this._providerViewsMap).length > 0) {
|
||||||
if (views && views.length > 0) {
|
let firstView = values(this._providerViewsMap)[0];
|
||||||
let firstView = views[0];
|
if (firstView instanceof AccountPanel) {
|
||||||
if (firstView instanceof FixedListView) {
|
firstView.setSelection([0]);
|
||||||
firstView.list.setSelection([0]);
|
firstView.focus();
|
||||||
firstView.list.domFocus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isEmptyLinkedAccount(): boolean {
|
private isEmptyLinkedAccount(): boolean {
|
||||||
for (var providerId in this._providerViews) {
|
for (let provider of values(this._providerViewsMap)) {
|
||||||
var listView = this._providerViews[providerId].view;
|
let listView = provider.view;
|
||||||
if (listView && listView.list.length > 0) {
|
if (listView && listView.length > 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,23 +251,21 @@ export class AccountDialog extends Modal {
|
|||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
for (let key in this._providerViews) {
|
for (let provider of values(this._providerViewsMap)) {
|
||||||
if (this._providerViews[key].addAccountAction) {
|
if (provider.addAccountAction) {
|
||||||
this._providerViews[key].addAccountAction.dispose();
|
provider.addAccountAction.dispose();
|
||||||
}
|
}
|
||||||
if (this._providerViews[key].view) {
|
if (provider.view) {
|
||||||
this._providerViews[key].view.dispose();
|
provider.view.dispose();
|
||||||
}
|
}
|
||||||
delete this._providerViews[key];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
// PRIVATE HELPERS /////////////////////////////////////////////////////
|
||||||
private addProvider(newProvider: AccountProviderAddedEventParams) {
|
private addProvider(newProvider: AccountProviderAddedEventParams) {
|
||||||
let self = this;
|
|
||||||
|
|
||||||
// Skip adding the provider if it already exists
|
// Skip adding the provider if it already exists
|
||||||
if (this._providerViews[newProvider.addedProvider.id]) {
|
if (this._providerViewsMap.get(newProvider.addedProvider.id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,37 +275,35 @@ export class AccountDialog extends Modal {
|
|||||||
AddAccountAction,
|
AddAccountAction,
|
||||||
newProvider.addedProvider.id
|
newProvider.addedProvider.id
|
||||||
);
|
);
|
||||||
addAccountAction.addAccountCompleteEvent(() => { self.hideSpinner(); });
|
addAccountAction.addAccountCompleteEvent(() => { this.hideSpinner(); });
|
||||||
addAccountAction.addAccountErrorEvent(msg => { self._onAddAccountErrorEmitter.fire(msg); });
|
addAccountAction.addAccountErrorEvent(msg => { this._onAddAccountErrorEmitter.fire(msg); });
|
||||||
addAccountAction.addAccountStartEvent(() => { self.showSpinner(); });
|
addAccountAction.addAccountStartEvent(() => { this.showSpinner(); });
|
||||||
|
|
||||||
// Create a fixed list view for the account provider
|
let providerView = new AccountPanel(
|
||||||
let providerViewContainer = DOM.$('.provider-view');
|
{
|
||||||
let accountList = new List<sqlops.Account>(providerViewContainer, this._delegate, [this._accountRenderer]);
|
id: newProvider.addedProvider.id,
|
||||||
let providerView = new FixedListView<sqlops.Account>(
|
title: newProvider.addedProvider.displayName,
|
||||||
undefined,
|
ariaHeaderLabel: newProvider.addedProvider.displayName
|
||||||
false,
|
},
|
||||||
newProvider.addedProvider.displayName,
|
|
||||||
accountList,
|
|
||||||
providerViewContainer,
|
|
||||||
22,
|
|
||||||
[addAccountAction],
|
|
||||||
this._actionRunner,
|
|
||||||
this._contextMenuService,
|
|
||||||
this._keybindingService,
|
this._keybindingService,
|
||||||
|
this._contextMenuService,
|
||||||
|
this._configurationService,
|
||||||
|
this._instantiationService,
|
||||||
this._themeService
|
this._themeService
|
||||||
);
|
);
|
||||||
|
|
||||||
// Append the list view to the split view
|
attachPanelStyler(providerView, this._themeService);
|
||||||
this._splitView.addView(providerView);
|
|
||||||
this._register(attachListStyler(accountList, this._themeService));
|
const insertIndex = this._splitView.length;
|
||||||
|
// Append the list view to the split view
|
||||||
|
this._splitView.addView(providerView, Sizing.Distribute, insertIndex);
|
||||||
|
providerView.render();
|
||||||
|
providerView.index = insertIndex;
|
||||||
|
|
||||||
let listService = <ListService>this._listService;
|
|
||||||
this._register(listService.register(accountList));
|
|
||||||
this._splitView.layout(DOM.getContentHeight(this._container));
|
this._splitView.layout(DOM.getContentHeight(this._container));
|
||||||
|
|
||||||
// Set the initial items of the list
|
// Set the initial items of the list
|
||||||
providerView.updateList(newProvider.initialAccounts);
|
providerView.updateAccounts(newProvider.initialAccounts);
|
||||||
|
|
||||||
if (newProvider.initialAccounts.length > 0 && this._splitViewContainer.hidden) {
|
if (newProvider.initialAccounts.length > 0 && this._splitViewContainer.hidden) {
|
||||||
this.showSplitView();
|
this.showSplitView();
|
||||||
@@ -276,31 +312,31 @@ export class AccountDialog extends Modal {
|
|||||||
this.layout();
|
this.layout();
|
||||||
|
|
||||||
// Store the view for the provider and action
|
// Store the view for the provider and action
|
||||||
this._providerViews[newProvider.addedProvider.id] = { view: providerView, addAccountAction: addAccountAction };
|
this._providerViewsMap.set(newProvider.addedProvider.id, { view: providerView, addAccountAction: addAccountAction });
|
||||||
}
|
}
|
||||||
|
|
||||||
private removeProvider(removedProvider: sqlops.AccountProviderMetadata) {
|
private removeProvider(removedProvider: sqlops.AccountProviderMetadata) {
|
||||||
// Skip removing the provider if it doesn't exist
|
// Skip removing the provider if it doesn't exist
|
||||||
let providerView = this._providerViews[removedProvider.id];
|
let providerView = this._providerViewsMap.get(removedProvider.id);
|
||||||
if (!providerView || !providerView.view) {
|
if (!providerView || !providerView.view) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the list view from the split view
|
// Remove the list view from the split view
|
||||||
this._splitView.removeView(providerView.view);
|
this._splitView.removeView(providerView.view.index);
|
||||||
this._splitView.layout(DOM.getContentHeight(this._container));
|
this._splitView.layout(DOM.getContentHeight(this._container));
|
||||||
|
|
||||||
// Remove the list view from our internal map
|
// Remove the list view from our internal map
|
||||||
delete this._providerViews[removedProvider.id];
|
this._providerViewsMap.delete(removedProvider.id);
|
||||||
this.layout();
|
this.layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateProviderAccounts(args: UpdateAccountListEventParams) {
|
private updateProviderAccounts(args: UpdateAccountListEventParams) {
|
||||||
let providerMapping = this._providerViews[args.providerId];
|
let providerMapping = this._providerViewsMap.get(args.providerId);
|
||||||
if (!providerMapping || !providerMapping.view) {
|
if (!providerMapping || !providerMapping.view) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
providerMapping.view.updateList(args.accountList);
|
providerMapping.view.updateAccounts(args.accountList);
|
||||||
|
|
||||||
if (args.accountList.length > 0 && this._splitViewContainer.hidden) {
|
if (args.accountList.length > 0 && this._splitViewContainer.hidden) {
|
||||||
this.showSplitView();
|
this.showSplitView();
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user