mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-19 11:01:38 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59b2e706ca | ||
|
|
8bf835c531 | ||
|
|
087ed7c132 | ||
|
|
4c075df327 | ||
|
|
9ea8baca05 | ||
|
|
9b6784720e | ||
|
|
3761e1dd60 | ||
|
|
b3eb809550 | ||
|
|
cb72865dcc | ||
|
|
d646b4729b | ||
|
|
a2dd903d0d | ||
|
|
28ed378ee7 |
@@ -9,21 +9,20 @@
|
||||
"@types/mime": "0.0.29",
|
||||
"@types/minimatch": "^3.0.3",
|
||||
"@types/node": "8.0.33",
|
||||
"@types/request": "^2.47.0",
|
||||
"@types/xml2js": "0.0.33",
|
||||
"@types/request": "^2.47.0",
|
||||
"azure-storage": "^2.1.0",
|
||||
"decompress": "^4.2.0",
|
||||
"del": "^3.0.0",
|
||||
"documentdb": "1.13.0",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||
"fs-extra-promise": "^1.0.1",
|
||||
"github-releases": "^0.4.1",
|
||||
"mime": "^1.3.4",
|
||||
"minimist": "^1.2.0",
|
||||
"request": "^2.85.0",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||
"typescript": "2.9.2",
|
||||
"vscode": "^1.0.1",
|
||||
"xml2js": "^0.4.17"
|
||||
"xml2js": "^0.4.17",
|
||||
"github-releases": "^0.4.1",
|
||||
"request": "^2.85.0"
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "tsc -p tsconfig.build.json",
|
||||
@@ -31,4 +30,4 @@
|
||||
"postinstall": "npm run compile",
|
||||
"npmCheckJs": "tsc --noEmit"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -571,18 +571,6 @@ deep-assign@^1.0.0:
|
||||
dependencies:
|
||||
is-obj "^1.0.0"
|
||||
|
||||
del@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5"
|
||||
integrity sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=
|
||||
dependencies:
|
||||
globby "^6.1.0"
|
||||
is-path-cwd "^1.0.0"
|
||||
is-path-in-cwd "^1.0.0"
|
||||
p-map "^1.1.1"
|
||||
pify "^3.0.0"
|
||||
rimraf "^2.2.8"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
@@ -954,29 +942,6 @@ glob@^5.0.3:
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.0.3:
|
||||
version "7.1.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
||||
integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
globby@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c"
|
||||
integrity sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=
|
||||
dependencies:
|
||||
array-union "^1.0.1"
|
||||
glob "^7.0.3"
|
||||
object-assign "^4.0.1"
|
||||
pify "^2.0.0"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
glogg@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810"
|
||||
@@ -1348,25 +1313,6 @@ is-obj@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
|
||||
integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
|
||||
|
||||
is-path-cwd@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d"
|
||||
integrity sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=
|
||||
|
||||
is-path-in-cwd@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52"
|
||||
integrity sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==
|
||||
dependencies:
|
||||
is-path-inside "^1.0.0"
|
||||
|
||||
is-path-inside@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
|
||||
integrity sha1-jvW33lBDej/cprToZe96pVy0gDY=
|
||||
dependencies:
|
||||
path-is-inside "^1.0.1"
|
||||
|
||||
is-posix-bracket@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
|
||||
@@ -1832,11 +1778,6 @@ os-tmpdir@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
|
||||
|
||||
p-map@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b"
|
||||
integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==
|
||||
|
||||
parse-glob@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
|
||||
@@ -1857,11 +1798,6 @@ path-is-absolute@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
path-is-inside@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
|
||||
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
|
||||
|
||||
pause-stream@0.0.11:
|
||||
version "0.0.11"
|
||||
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
|
||||
@@ -1884,7 +1820,7 @@ performance-now@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
|
||||
|
||||
pify@^2.0.0, pify@^2.3.0:
|
||||
pify@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
|
||||
@@ -2182,7 +2118,7 @@ requires-port@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||
|
||||
rimraf@2, rimraf@^2.2.8:
|
||||
rimraf@2:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
|
||||
integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "agent",
|
||||
"displayName": "SQL Server Agent",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||
"version": "0.35.0",
|
||||
"version": "0.35.1",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||
|
||||
@@ -69,8 +69,8 @@ export class AzureAccountProvider implements sqlops.AccountProvider {
|
||||
return this._tokenCache.clear();
|
||||
}
|
||||
|
||||
public getSecurityToken(account: AzureAccount): Thenable<AzureAccountSecurityTokenCollection> {
|
||||
return this.doIfInitialized(() => this.getAccessTokens(account));
|
||||
public getSecurityToken(account: AzureAccount, resource: sqlops.AzureResource): Thenable<AzureAccountSecurityTokenCollection> {
|
||||
return this.doIfInitialized(() => this.getAccessTokens(account, resource));
|
||||
}
|
||||
|
||||
public initialize(restoredAccounts: sqlops.Account[]): Thenable<sqlops.Account[]> {
|
||||
@@ -90,7 +90,7 @@ export class AzureAccountProvider implements sqlops.AccountProvider {
|
||||
|
||||
// Attempt to get fresh tokens. If this fails then the account is stale.
|
||||
// NOTE: Based on ADAL implementation, getting tokens should use the refresh token if necessary
|
||||
let task = this.getAccessTokens(account)
|
||||
let task = this.getAccessTokens(account, sqlops.AzureResource.ResourceManagement)
|
||||
.then(
|
||||
() => {
|
||||
return account;
|
||||
@@ -161,9 +161,14 @@ export class AzureAccountProvider implements sqlops.AccountProvider {
|
||||
: Promise.reject(localize('accountProviderNotInitialized', 'Account provider not initialized, cannot perform action'));
|
||||
}
|
||||
|
||||
private getAccessTokens(account: AzureAccount): Thenable<AzureAccountSecurityTokenCollection> {
|
||||
private getAccessTokens(account: AzureAccount, resource: sqlops.AzureResource): Thenable<AzureAccountSecurityTokenCollection> {
|
||||
let self = this;
|
||||
|
||||
const resourceIdMap = new Map<sqlops.AzureResource, string>([
|
||||
[sqlops.AzureResource.ResourceManagement, self._metadata.settings.armResource.id],
|
||||
[sqlops.AzureResource.Sql, self._metadata.settings.sqlResource.id]
|
||||
]);
|
||||
|
||||
let accessTokenPromises: Thenable<void>[] = [];
|
||||
let tokenCollection: AzureAccountSecurityTokenCollection = {};
|
||||
for (let tenant of account.properties.tenants) {
|
||||
@@ -172,7 +177,7 @@ export class AzureAccountProvider implements sqlops.AccountProvider {
|
||||
let context = new adal.AuthenticationContext(authorityUrl, null, self._tokenCache);
|
||||
|
||||
context.acquireToken(
|
||||
self._metadata.settings.armResource.id,
|
||||
resourceIdMap.get(resource),
|
||||
tenant.userId,
|
||||
self._metadata.settings.clientId,
|
||||
(error: Error, response: adal.TokenResponse | adal.ErrorResponse) => {
|
||||
|
||||
@@ -81,6 +81,11 @@ export interface Settings {
|
||||
*/
|
||||
armResource?: Resource;
|
||||
|
||||
/**
|
||||
* Information that describes the SQL Azure resource
|
||||
*/
|
||||
sqlResource?: Resource;
|
||||
|
||||
/**
|
||||
* A list of tenant IDs to authenticate against. If defined, then these IDs will be used
|
||||
* instead of querying the tenants endpoint of the armResource
|
||||
|
||||
@@ -27,6 +27,10 @@ const publicAzureSettings: ProviderSettings = {
|
||||
id: 'https://management.core.windows.net/',
|
||||
endpoint: 'https://management.azure.com'
|
||||
},
|
||||
sqlResource: {
|
||||
id: 'https://database.windows.net/',
|
||||
endpoint: 'https://database.windows.net'
|
||||
},
|
||||
redirectUri: 'http://localhost/redirect'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,8 +212,8 @@ export class ApiWrapper {
|
||||
return sqlops.accounts.getAllAccounts();
|
||||
}
|
||||
|
||||
public getSecurityToken(account: sqlops.Account): Thenable<{}> {
|
||||
return sqlops.accounts.getSecurityToken(account);
|
||||
public getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> {
|
||||
return sqlops.accounts.getSecurityToken(account, resource);
|
||||
}
|
||||
|
||||
public readonly onDidChangeAccounts = sqlops.accounts.onDidChangeAccounts;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
'use strict';
|
||||
|
||||
import { window, QuickPickItem } from 'vscode';
|
||||
import { IConnectionProfile } from 'sqlops';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { generateGuid } from './utils';
|
||||
import { ApiWrapper } from '../apiWrapper';
|
||||
import { TreeNode } from '../treeNodes';
|
||||
@@ -30,7 +30,7 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
|
||||
|
||||
let subscriptions = await accountNode.getCachedSubscriptions();
|
||||
if (!subscriptions || subscriptions.length === 0) {
|
||||
const credentials = await servicePool.credentialService.getCredentials(accountNode.account);
|
||||
const credentials = await servicePool.credentialService.getCredentials(accountNode.account, sqlops.AzureResource.ResourceManagement);
|
||||
subscriptions = await servicePool.subscriptionService.getSubscriptions(accountNode.account, credentials);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export function registerAzureResourceCommands(apiWrapper: ApiWrapper, tree: Azur
|
||||
});
|
||||
|
||||
apiWrapper.registerCommand('azureresource.connectsqldb', async (node?: TreeNode) => {
|
||||
let connectionProfile: IConnectionProfile = {
|
||||
let connectionProfile: sqlops.IConnectionProfile = {
|
||||
id: generateGuid(),
|
||||
connectionName: undefined,
|
||||
serverName: undefined,
|
||||
|
||||
@@ -6,29 +6,29 @@
|
||||
'use strict';
|
||||
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { Account, DidChangeAccountsParams } from 'sqlops';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { Event } from 'vscode';
|
||||
|
||||
import { AzureResourceSubscription, AzureResourceDatabaseServer, AzureResourceDatabase } from './models';
|
||||
|
||||
export interface IAzureResourceAccountService {
|
||||
getAccounts(): Promise<Account[]>;
|
||||
getAccounts(): Promise<sqlops.Account[]>;
|
||||
|
||||
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
|
||||
readonly onDidChangeAccounts: Event<sqlops.DidChangeAccountsParams>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceCredentialService {
|
||||
getCredentials(account: Account): Promise<ServiceClientCredentials[]>;
|
||||
getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceSubscriptionService {
|
||||
getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]>;
|
||||
getSubscriptions(account: sqlops.Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceSubscriptionFilterService {
|
||||
getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]>;
|
||||
getSelectedSubscriptions(account: sqlops.Account): Promise<AzureResourceSubscription[]>;
|
||||
|
||||
saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void>;
|
||||
saveSelectedSubscriptions(account: sqlops.Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceDatabaseServerService {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Account } from 'sqlops';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { TokenCredentials, ServiceClientCredentials } from 'ms-rest';
|
||||
import { ApiWrapper } from '../../apiWrapper';
|
||||
import * as nls from 'vscode-nls';
|
||||
@@ -21,10 +21,10 @@ export class AzureResourceCredentialService implements IAzureResourceCredentialS
|
||||
this._apiWrapper = apiWrapper;
|
||||
}
|
||||
|
||||
public async getCredentials(account: Account): Promise<ServiceClientCredentials[]> {
|
||||
public async getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]> {
|
||||
try {
|
||||
let credentials: TokenCredentials[] = [];
|
||||
let tokens = await this._apiWrapper.getSecurityToken(account);
|
||||
let tokens = await this._apiWrapper.getSecurityToken(account, resource);
|
||||
|
||||
for (let tenant of account.properties.tenants) {
|
||||
let token = tokens[tenant.id].token;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Account } from 'sqlops';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { TreeNode } from '../../treeNodes';
|
||||
|
||||
@@ -28,7 +28,7 @@ export abstract class AzureResourceTreeNodeBase extends TreeNode {
|
||||
|
||||
export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTreeNodeBase {
|
||||
public constructor(
|
||||
public readonly account: Account,
|
||||
public readonly account: sqlops.Account,
|
||||
treeChangeHandler: IAzureResourceTreeChangeHandler,
|
||||
parent: TreeNode
|
||||
) {
|
||||
@@ -45,7 +45,7 @@ export abstract class AzureResourceContainerTreeNodeBase extends AzureResourceTr
|
||||
|
||||
protected async getCredentials(): Promise<ServiceClientCredentials[]> {
|
||||
try {
|
||||
return await this.servicePool.credentialService.getCredentials(this.account);
|
||||
return await this.servicePool.credentialService.getCredentials(this.account, sqlops.AzureResource.ResourceManagement);
|
||||
} catch (error) {
|
||||
if (error instanceof AzureResourceCredentialError) {
|
||||
this.servicePool.contextService.showErrorMessage(error.message);
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('AzureResourceAccountTreeNode.info', function(): void {
|
||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
||||
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount)).returns(() => Promise.resolve(mockCredentials));
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
||||
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);
|
||||
});
|
||||
@@ -164,7 +164,7 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
mockServicePool.subscriptionService = mockSubscriptionService.object;
|
||||
mockServicePool.subscriptionFilterService = mockSubscriptionFilterService.object;
|
||||
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount)).returns(() => Promise.resolve(mockCredentials));
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement)).returns(() => Promise.resolve(mockCredentials));
|
||||
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);
|
||||
});
|
||||
@@ -177,7 +177,7 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
@@ -213,7 +213,7 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
await accountTreeNode.getChildren();
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.exactly(1));
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.exactly(1));
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.exactly(1));
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.exactly(2));
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(1));
|
||||
@@ -267,7 +267,7 @@ describe('AzureResourceAccountTreeNode.getChildren', function(): void {
|
||||
|
||||
const children = await accountTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount, sqlops.AzureResource.ResourceManagement), TypeMoq.Times.once());
|
||||
mockSubscriptionService.verify((o) => o.getSubscriptions(mockAccount, mockCredentials), TypeMoq.Times.once());
|
||||
mockCacheService.verify((o) => o.get(TypeMoq.It.isAnyString()), TypeMoq.Times.never());
|
||||
mockCacheService.verify((o) => o.update(TypeMoq.It.isAnyString(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
||||
|
||||
@@ -118,7 +118,7 @@ describe('AzureResourceDatabaseContainerTreeNode.getChildren', function(): void
|
||||
mockServicePool.credentialService = mockCredentialService.object;
|
||||
mockServicePool.databaseService = mockDatabaseService.object;
|
||||
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount)).returns(() => Promise.resolve(mockCredentials));
|
||||
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);
|
||||
});
|
||||
@@ -130,7 +130,7 @@ describe('AzureResourceDatabaseContainerTreeNode.getChildren', function(): void
|
||||
|
||||
const children = await databaseContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
||||
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());
|
||||
@@ -160,7 +160,7 @@ describe('AzureResourceDatabaseContainerTreeNode.getChildren', function(): void
|
||||
await databaseContainerTreeNode.getChildren();
|
||||
const children = await databaseContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.exactly(1));
|
||||
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));
|
||||
@@ -193,7 +193,7 @@ describe('AzureResourceDatabaseContainerTreeNode.getChildren', function(): void
|
||||
const databaseContainerTreeNode = new AzureResourceDatabaseContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
const children = await databaseContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
||||
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());
|
||||
|
||||
@@ -118,7 +118,7 @@ describe('AzureResourceDatabaseServerContainerTreeNode.getChildren', function():
|
||||
mockServicePool.credentialService = mockCredentialService.object;
|
||||
mockServicePool.databaseServerService = mockDatabaseServerService.object;
|
||||
|
||||
mockCredentialService.setup((o) => o.getCredentials(mockAccount)).returns(() => Promise.resolve(mockCredentials));
|
||||
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);
|
||||
});
|
||||
@@ -130,7 +130,7 @@ describe('AzureResourceDatabaseServerContainerTreeNode.getChildren', function():
|
||||
|
||||
const children = await databaseServerContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
||||
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());
|
||||
@@ -160,7 +160,7 @@ describe('AzureResourceDatabaseServerContainerTreeNode.getChildren', function():
|
||||
await databaseServerContainerTreeNode.getChildren();
|
||||
const children = await databaseServerContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.exactly(1));
|
||||
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));
|
||||
@@ -193,7 +193,7 @@ describe('AzureResourceDatabaseServerContainerTreeNode.getChildren', function():
|
||||
const databaseServerContainerTreeNode = new AzureResourceDatabaseServerContainerTreeNode(mockSubscription, mockAccount, mockTreeChangeHandler.object, undefined);
|
||||
const children = await databaseServerContainerTreeNode.getChildren();
|
||||
|
||||
mockCredentialService.verify((o) => o.getCredentials(mockAccount), TypeMoq.Times.once());
|
||||
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());
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "import",
|
||||
"displayName": "SQL Server Import",
|
||||
"description": "SQL Server Import for Azure Data Studio supports importing CSV or JSON files into SQL Server.",
|
||||
"version": "0.4.0",
|
||||
"version": "0.4.2",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
@@ -33,6 +33,15 @@
|
||||
"light": "./images/light_icon.svg",
|
||||
"dark": "./images/dark_icon.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "dacFx.start",
|
||||
"title": "Data-tier Application Wizard",
|
||||
"category": "Data-tier Application",
|
||||
"icon": {
|
||||
"light": "./images/light_icon.svg",
|
||||
"dark": "./images/dark_icon.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
@@ -48,12 +57,17 @@
|
||||
"command": "flatFileImport.start",
|
||||
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
|
||||
"group": "import"
|
||||
},
|
||||
{
|
||||
"command": "dacFx.start",
|
||||
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
|
||||
"group": "export"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||
"vscode-extension-telemetry": "0.0.18",
|
||||
|
||||
@@ -13,6 +13,7 @@ import { FlatFileWizard } from '../wizard/flatFileWizard';
|
||||
import { ServiceClient } from '../services/serviceClient';
|
||||
import { ApiType, managerInstance } from '../services/serviceApiManager';
|
||||
import { FlatFileProvider } from '../services/contracts';
|
||||
import { DataTierApplicationWizard } from '../wizard/dataTierApplicationWizard';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
@@ -35,10 +36,15 @@ export default class MainController extends ControllerBase {
|
||||
this.initializeFlatFileProvider(provider);
|
||||
});
|
||||
|
||||
this.initializeDacFxWizard();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private initializeFlatFileProvider(provider: FlatFileProvider) {
|
||||
sqlops.tasks.registerTask('flatFileImport.start', (profile: sqlops.IConnectionProfile, ...args: any[]) => new FlatFileWizard(provider).start(profile, args));
|
||||
}
|
||||
|
||||
private initializeDacFxWizard() {
|
||||
sqlops.tasks.registerTask('dacFx.start', (profile: sqlops.IConnectionProfile, ...args: any[]) => new DataTierApplicationWizard().start(profile, args));
|
||||
}
|
||||
}
|
||||
|
||||
139
extensions/import/src/wizard/api/basePage.ts
Normal file
139
extensions/import/src/wizard/api/basePage.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { BaseDataModel } from './models';
|
||||
|
||||
export abstract class BasePage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly model: BaseDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
/**
|
||||
* This method constructs all the elements of the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract start(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is entering the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageEnter(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is leaving the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this method to cleanup what you don't need cached in the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async cleanup(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a navigation validator.
|
||||
* This will be called right before onPageEnter().
|
||||
*/
|
||||
public abstract setupNavigationValidator();
|
||||
|
||||
protected async getServerValues(): Promise<{ connection, displayName, name }[]> {
|
||||
let cons = await sqlops.connection.getActiveConnections();
|
||||
// This user has no active connections ABORT MISSION
|
||||
if (!cons || cons.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let count = -1;
|
||||
let idx = -1;
|
||||
|
||||
|
||||
let values = cons.map(c => {
|
||||
// Handle the code to remember what the user's choice was from before
|
||||
count++;
|
||||
if (idx === -1) {
|
||||
if (this.model.server && c.connectionId === this.model.server.connectionId) {
|
||||
idx = count;
|
||||
} else if (this.model.serverId && c.connectionId === this.model.serverId) {
|
||||
idx = count;
|
||||
}
|
||||
}
|
||||
|
||||
let db = c.options.databaseDisplayName;
|
||||
let usr = c.options.user;
|
||||
let srv = c.options.server;
|
||||
|
||||
if (!db) {
|
||||
db = '<default>';
|
||||
}
|
||||
|
||||
if (!usr) {
|
||||
usr = 'default';
|
||||
}
|
||||
|
||||
let finalName = `${srv}, ${db} (${usr})`;
|
||||
return {
|
||||
connection: c,
|
||||
displayName: finalName,
|
||||
name: c.connectionId
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
this.deleteServerValues();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
protected async getDatabaseValues(): Promise<{ displayName, name }[]> {
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
let values = (await sqlops.connection.listDatabases(this.model.server.connectionId)).map(db => {
|
||||
count++;
|
||||
if (this.model.database && db === this.model.database) {
|
||||
idx = count;
|
||||
}
|
||||
|
||||
return {
|
||||
displayName: db,
|
||||
name: db
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
this.deleteDatabaseValues();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
protected deleteServerValues() {
|
||||
delete this.model.server;
|
||||
delete this.model.serverId;
|
||||
delete this.model.database;
|
||||
}
|
||||
|
||||
protected deleteDatabaseValues() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
158
extensions/import/src/wizard/api/dacFxConfigPage.ts
Normal file
158
extensions/import/src/wizard/api/dacFxConfigPage.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
|
||||
import { DacFxDataModel } from './models';
|
||||
import { BasePage } from './basePage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export abstract class DacFxConfigPage extends BasePage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
protected serverDropdown: sqlops.DropDownComponent;
|
||||
protected databaseTextBox: sqlops.InputBoxComponent;
|
||||
protected databaseDropdown: sqlops.DropDownComponent;
|
||||
protected databaseLoader: sqlops.LoadingComponent;
|
||||
protected fileTextBox: sqlops.InputBoxComponent;
|
||||
protected fileButton: sqlops.ButtonComponent;
|
||||
protected fileExtension: string;
|
||||
|
||||
protected constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.ModelView) {
|
||||
super();
|
||||
this.instance = instance;
|
||||
this.wizardPage = wizardPage;
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator(() => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
protected async createServerDropdown(isTargetServer: boolean): Promise<sqlops.FormComponent> {
|
||||
this.serverDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
// Handle server changes
|
||||
this.serverDropdown.onValueChanged(async () => {
|
||||
this.model.server = (this.serverDropdown.value as ConnectionDropdownValue).connection;
|
||||
this.model.serverName = (this.serverDropdown.value as ConnectionDropdownValue).displayName;
|
||||
await this.populateDatabaseDropdown();
|
||||
});
|
||||
|
||||
let targetServerTitle = localize('dacFx.targetServerDropdownTitle', 'Target Server');
|
||||
let sourceServerTitle = localize('dacFx.sourceServerDropdownTitle', 'Source Server');
|
||||
|
||||
return {
|
||||
component: this.serverDropdown,
|
||||
title: isTargetServer ? targetServerTitle : sourceServerTitle
|
||||
};
|
||||
}
|
||||
|
||||
protected async populateServerDropdown(): Promise<boolean> {
|
||||
let values = await this.getServerValues();
|
||||
if (values === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.model.server = values[0].connection;
|
||||
this.model.serverName = values[0].displayName;
|
||||
|
||||
this.serverDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async createDatabaseTextBox(): Promise<sqlops.FormComponent> {
|
||||
this.databaseTextBox = this.view.modelBuilder.inputBox().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
this.databaseTextBox.onTextChanged(async () => {
|
||||
this.model.database = this.databaseTextBox.value;
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.databaseTextBox,
|
||||
title: localize('dacFx.databaseNameTextBox', 'Target Database')
|
||||
};
|
||||
}
|
||||
|
||||
protected async createDatabaseDropdown(): Promise<sqlops.FormComponent> {
|
||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
// Handle database changes
|
||||
this.databaseDropdown.onValueChanged(async () => {
|
||||
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
this.fileTextBox.value = this.generateFilePath();
|
||||
this.model.filePath = this.fileTextBox.value;
|
||||
});
|
||||
|
||||
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).component();
|
||||
|
||||
return {
|
||||
component: this.databaseLoader,
|
||||
title: localize('dacFx.sourceDatabaseDropdownTitle', 'Source Database')
|
||||
};
|
||||
}
|
||||
|
||||
protected async populateDatabaseDropdown(): Promise<boolean> {
|
||||
this.databaseLoader.loading = true;
|
||||
this.databaseDropdown.updateProperties({ values: [] });
|
||||
|
||||
if (!this.model.server) {
|
||||
this.databaseLoader.loading = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
let values = await this.getDatabaseValues();
|
||||
this.model.database = values[0].name;
|
||||
this.model.filePath = this.generateFilePath();
|
||||
this.fileTextBox.value = this.model.filePath;
|
||||
|
||||
this.databaseDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
this.databaseLoader.loading = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async createFileBrowserParts() {
|
||||
this.fileTextBox = this.view.modelBuilder.inputBox().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
this.fileButton = this.view.modelBuilder.button().withProperties({
|
||||
label: '•••',
|
||||
}).component();
|
||||
}
|
||||
|
||||
protected generateFilePath(): string {
|
||||
let now = new Date();
|
||||
let datetime = now.getFullYear() + '-' + (now.getMonth() + 1) + '-' + now.getDate() + '-' + now.getHours() + '-' + now.getMinutes();
|
||||
return path.join(os.homedir(), this.model.database + '-' + datetime + this.fileExtension);
|
||||
}
|
||||
}
|
||||
|
||||
interface ConnectionDropdownValue extends sqlops.CategoryValue {
|
||||
connection: sqlops.connection.Connection;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@ import { ImportDataModel } from './models';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
import { BasePage } from './basePage';
|
||||
|
||||
export abstract class ImportPage {
|
||||
export abstract class ImportPage extends BasePage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: FlatFileWizard;
|
||||
@@ -18,42 +19,11 @@ export abstract class ImportPage {
|
||||
protected readonly provider: FlatFileProvider;
|
||||
|
||||
protected constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super();
|
||||
this.instance = instance;
|
||||
this.wizardPage = wizardPage;
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method constructs all the elements of the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract start(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is entering the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageEnter(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is leaving the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageLeave(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Sets up a navigation validator.
|
||||
* This will be called right before onPageEnter().
|
||||
*/
|
||||
public abstract setupNavigationValidator();
|
||||
|
||||
/**
|
||||
* Override this method to cleanup what you don't need cached in the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async cleanup(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,19 @@
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export interface BaseDataModel {
|
||||
server: sqlops.connection.Connection;
|
||||
serverId: string;
|
||||
database: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main data model that communicates between the pages.
|
||||
*/
|
||||
export interface ImportDataModel {
|
||||
export interface ImportDataModel extends BaseDataModel {
|
||||
ownerUri: string;
|
||||
proseColumns: ColumnMetadata[];
|
||||
proseDataPreview: string[][];
|
||||
server: sqlops.connection.Connection;
|
||||
serverId: string;
|
||||
database: string;
|
||||
table: string;
|
||||
schema: string;
|
||||
@@ -31,3 +35,14 @@ export interface ColumnMetadata {
|
||||
primaryKey: boolean;
|
||||
nullable: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data model to communicate between DacFx pages
|
||||
*/
|
||||
export interface DacFxDataModel extends BaseDataModel {
|
||||
serverName: string;
|
||||
serverId: string;
|
||||
filePath: string;
|
||||
version: string;
|
||||
upgradeExisting: boolean;
|
||||
}
|
||||
|
||||
253
extensions/import/src/wizard/dataTierApplicationWizard.ts
Normal file
253
extensions/import/src/wizard/dataTierApplicationWizard.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { SelectOperationPage } from './pages/selectOperationpage';
|
||||
import { DeployConfigPage } from './pages/deployConfigPage';
|
||||
import { DacFxSummaryPage } from './pages/dacFxSummaryPage';
|
||||
import { ExportConfigPage } from './pages/exportConfigPage';
|
||||
import { ExtractConfigPage } from './pages/extractConfigPage';
|
||||
import { ImportConfigPage } from './pages/importConfigPage';
|
||||
import { DacFxDataModel } from './api/models';
|
||||
import { BasePage } from './api/basePage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
class Page {
|
||||
wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
dacFxPage: BasePage;
|
||||
|
||||
constructor(wizardPage: sqlops.window.modelviewdialog.WizardPage) {
|
||||
this.wizardPage = wizardPage;
|
||||
}
|
||||
}
|
||||
|
||||
export enum Operation {
|
||||
deploy,
|
||||
extract,
|
||||
import,
|
||||
export
|
||||
}
|
||||
|
||||
export class DataTierApplicationWizard {
|
||||
public wizard: sqlops.window.modelviewdialog.Wizard;
|
||||
private connection: sqlops.connection.Connection;
|
||||
private model: DacFxDataModel;
|
||||
public pages: Map<string, Page> = new Map<string, Page>();
|
||||
public selectedOperation: Operation;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public async start(p: any, ...args: any[]) {
|
||||
this.model = <DacFxDataModel>{};
|
||||
|
||||
let profile = p ? <sqlops.IConnectionProfile>p.connectionProfile : undefined;
|
||||
if (profile) {
|
||||
this.model.serverId = profile.id;
|
||||
this.model.database = profile.databaseName;
|
||||
}
|
||||
|
||||
this.connection = await sqlops.connection.getCurrentConnection();
|
||||
if (!this.connection) {
|
||||
this.connection = await sqlops.connection.openConnectionDialog();
|
||||
}
|
||||
|
||||
this.wizard = sqlops.window.modelviewdialog.createWizard('Data-tier Application Wizard');
|
||||
let selectOperationWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.selectOperationPageName', 'Select an Operation'));
|
||||
let deployConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.deployConfigPageName', 'Select Deploy Dacpac Settings'));
|
||||
let summaryWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.summaryPageName', 'Summary'));
|
||||
let extractConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.extractConfigPageName', 'Select Extract Dacpac Settings'));
|
||||
let importConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.importConfigPageName', 'Select Import Bacpac Settings'));
|
||||
let exportConfigWizardPage = sqlops.window.modelviewdialog.createWizardPage(localize('dacFx.exportConfigPageName', 'Select Export Bacpac Settings'));
|
||||
|
||||
this.pages.set('selectOperation', new Page(selectOperationWizardPage));
|
||||
this.pages.set('deployConfig', new Page(deployConfigWizardPage));
|
||||
this.pages.set('extractConfig', new Page(extractConfigWizardPage));
|
||||
this.pages.set('importConfig', new Page(importConfigWizardPage));
|
||||
this.pages.set('exportConfig', new Page(exportConfigWizardPage));
|
||||
this.pages.set('summary', new Page(summaryWizardPage));
|
||||
|
||||
selectOperationWizardPage.registerContent(async (view) => {
|
||||
let selectOperationDacFxPage = new SelectOperationPage(this, selectOperationWizardPage, this.model, view);
|
||||
this.pages.get('selectOperation').dacFxPage = selectOperationDacFxPage;
|
||||
await selectOperationDacFxPage.start().then(() => {
|
||||
selectOperationDacFxPage.setupNavigationValidator();
|
||||
selectOperationDacFxPage.onPageEnter();
|
||||
});
|
||||
});
|
||||
|
||||
deployConfigWizardPage.registerContent(async (view) => {
|
||||
let deployConfigDacFxPage = new DeployConfigPage(this, deployConfigWizardPage, this.model, view);
|
||||
this.pages.get('deployConfig').dacFxPage = deployConfigDacFxPage;
|
||||
await deployConfigDacFxPage.start();
|
||||
});
|
||||
|
||||
extractConfigWizardPage.registerContent(async (view) => {
|
||||
let extractConfigDacFxPage = new ExtractConfigPage(this, extractConfigWizardPage, this.model, view);
|
||||
this.pages.get('extractConfig').dacFxPage = extractConfigDacFxPage;
|
||||
await extractConfigDacFxPage.start();
|
||||
});
|
||||
|
||||
importConfigWizardPage.registerContent(async (view) => {
|
||||
let importConfigDacFxPage = new ImportConfigPage(this, importConfigWizardPage, this.model, view);
|
||||
this.pages.get('importConfig').dacFxPage = importConfigDacFxPage;
|
||||
await importConfigDacFxPage.start();
|
||||
});
|
||||
|
||||
exportConfigWizardPage.registerContent(async (view) => {
|
||||
let exportConfigDacFxPage = new ExportConfigPage(this, exportConfigWizardPage, this.model, view);
|
||||
this.pages.get('exportConfig').dacFxPage = exportConfigDacFxPage;
|
||||
await exportConfigDacFxPage.start();
|
||||
});
|
||||
|
||||
summaryWizardPage.registerContent(async (view) => {
|
||||
let summaryDacFxPage = new DacFxSummaryPage(this, summaryWizardPage, this.model, view);
|
||||
this.pages.get('summary').dacFxPage = summaryDacFxPage;
|
||||
await summaryDacFxPage.start();
|
||||
});
|
||||
|
||||
this.wizard.onPageChanged(async (event) => {
|
||||
let idx = event.newPage;
|
||||
let page: Page;
|
||||
|
||||
if (idx === 1) {
|
||||
switch (this.selectedOperation) {
|
||||
case Operation.deploy: {
|
||||
page = this.pages.get('deployConfig');
|
||||
break;
|
||||
}
|
||||
case Operation.extract: {
|
||||
page = this.pages.get('extractConfig');
|
||||
break;
|
||||
}
|
||||
case Operation.import: {
|
||||
page = this.pages.get('importConfig');
|
||||
break;
|
||||
}
|
||||
case Operation.export: {
|
||||
page = this.pages.get('exportConfig');
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (idx === 2) {
|
||||
page = this.pages.get('summary');
|
||||
}
|
||||
|
||||
if (page !== undefined) {
|
||||
page.dacFxPage.setupNavigationValidator();
|
||||
page.dacFxPage.onPageEnter();
|
||||
}
|
||||
});
|
||||
|
||||
this.wizard.pages = [selectOperationWizardPage, deployConfigWizardPage, summaryWizardPage];
|
||||
this.wizard.generateScriptButton.hidden = true;
|
||||
this.wizard.doneButton.onClick(async () => await this.executeOperation());
|
||||
|
||||
this.wizard.open();
|
||||
}
|
||||
|
||||
public registerNavigationValidator(validator: (pageChangeInfo: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean) {
|
||||
this.wizard.registerNavigationValidator(validator);
|
||||
}
|
||||
|
||||
public setDoneButton(operation: Operation): void {
|
||||
switch (operation) {
|
||||
case Operation.deploy: {
|
||||
this.wizard.doneButton.label = localize('dacFx.deployButton', 'Deploy');
|
||||
this.selectedOperation = Operation.deploy;
|
||||
break;
|
||||
}
|
||||
case Operation.extract: {
|
||||
this.wizard.doneButton.label = localize('dacFx.extractButton', 'Extract');
|
||||
this.selectedOperation = Operation.extract;
|
||||
break;
|
||||
}
|
||||
case Operation.import: {
|
||||
this.wizard.doneButton.label = localize('dacFx.importButton', 'Import');
|
||||
this.selectedOperation = Operation.import;
|
||||
break;
|
||||
}
|
||||
case Operation.export: {
|
||||
this.wizard.doneButton.label = localize('dacFx.exportButton', 'Export');
|
||||
this.selectedOperation = Operation.export;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async executeOperation() {
|
||||
switch (this.selectedOperation) {
|
||||
case Operation.deploy: {
|
||||
await this.deploy();
|
||||
break;
|
||||
}
|
||||
case Operation.extract: {
|
||||
await this.extract();
|
||||
break;
|
||||
}
|
||||
case Operation.import: {
|
||||
await this.import();
|
||||
break;
|
||||
}
|
||||
case Operation.export: {
|
||||
await this.export();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async deploy() {
|
||||
let service = await DataTierApplicationWizard.getService();
|
||||
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
|
||||
let result = await service.deployDacpac(this.model.filePath, this.model.database, this.model.upgradeExisting, ownerUri, sqlops.TaskExecutionMode.execute);
|
||||
if (!result || !result.success) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('alertData.deployErrorMessage', "Deploy failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
private async extract() {
|
||||
let service = await DataTierApplicationWizard.getService();
|
||||
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
|
||||
let result = await service.extractDacpac(this.model.database, this.model.filePath, this.model.database, this.model.version, ownerUri, sqlops.TaskExecutionMode.execute);
|
||||
if (!result || !result.success) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('alertData.extractErrorMessage', "Extract failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
private async export() {
|
||||
let service = await DataTierApplicationWizard.getService();
|
||||
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
|
||||
let result = await service.exportBacpac(this.model.database, this.model.filePath, ownerUri, sqlops.TaskExecutionMode.execute);
|
||||
if (!result || !result.success) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('alertData.exportErrorMessage', "Export failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
private async import() {
|
||||
let service = await DataTierApplicationWizard.getService();
|
||||
let ownerUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
|
||||
let result = await service.importBacpac(this.model.filePath, this.model.database, ownerUri, sqlops.TaskExecutionMode.execute);
|
||||
if (!result || !result.success) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('alertData.importErrorMessage', "Import failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
public static async getService(): Promise<sqlops.DacFxServicesProvider> {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
let service = sqlops.dataprotocol.getProvider<sqlops.DacFxServicesProvider>(currentConnection.providerName, sqlops.DataProviderType.DacFxServicesProvider);
|
||||
return service;
|
||||
}
|
||||
}
|
||||
112
extensions/import/src/wizard/pages/dacFxSummaryPage.ts
Normal file
112
extensions/import/src/wizard/pages/dacFxSummaryPage.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import { DacFxDataModel } from '../api/models';
|
||||
import { DataTierApplicationWizard, Operation } from '../dataTierApplicationWizard';
|
||||
import { BasePage } from '../api/basePage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class DacFxSummaryPage extends BasePage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private form: sqlops.FormContainer;
|
||||
private table: sqlops.TableComponent;
|
||||
private loader: sqlops.LoadingComponent;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.ModelView) {
|
||||
super();
|
||||
this.instance = instance;
|
||||
this.wizardPage = wizardPage;
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.table = this.view.modelBuilder.table().component();
|
||||
this.loader = this.view.modelBuilder.loadingComponent().withItem(this.table).component();
|
||||
this.form = this.view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: this.table,
|
||||
title: ''
|
||||
}
|
||||
]
|
||||
).component();
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
this.populateTable();
|
||||
this.loader.loading = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator(() => {
|
||||
if (this.loader.loading) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private populateTable() {
|
||||
let data = [];
|
||||
let targetServer = localize('dacfx.targetServerName', 'Target Server');
|
||||
let targetDatabase = localize('dacfx.targetDatabaseName', 'Target Database');
|
||||
let sourceServer = localize('dacfx.sourceServerName', 'Source Server');
|
||||
let sourceDatabase = localize('dacfx.sourceDatabaseName', 'Source Database');
|
||||
let fileLocation = localize('dacfx.fileLocation', 'File Location');
|
||||
|
||||
switch (this.instance.selectedOperation) {
|
||||
case Operation.deploy: {
|
||||
data = [
|
||||
[targetServer, this.model.serverName],
|
||||
[fileLocation, this.model.filePath],
|
||||
[targetDatabase, this.model.database]];
|
||||
break;
|
||||
}
|
||||
case Operation.extract: {
|
||||
data = [
|
||||
[sourceServer, this.model.serverName],
|
||||
[sourceDatabase, this.model.database],
|
||||
[localize('dacfxExtract.version', 'Version'), this.model.version],
|
||||
[fileLocation, this.model.filePath]];
|
||||
break;
|
||||
}
|
||||
case Operation.import: {
|
||||
data = [
|
||||
[targetServer, this.model.serverName],
|
||||
[fileLocation, this.model.filePath],
|
||||
[targetDatabase, this.model.database]];
|
||||
break;
|
||||
}
|
||||
case Operation.export: {
|
||||
data = [
|
||||
[sourceServer, this.model.serverName],
|
||||
[sourceDatabase, this.model.database],
|
||||
[fileLocation, this.model.filePath]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.table.updateProperties({
|
||||
data: data,
|
||||
columns: ['Setting', 'Value'],
|
||||
width: 600,
|
||||
height: 200
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
190
extensions/import/src/wizard/pages/deployConfigPage.ts
Normal file
190
extensions/import/src/wizard/pages/deployConfigPage.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { DacFxDataModel } from '../api/models';
|
||||
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
|
||||
import { DacFxConfigPage } from '../api/dacFxConfigPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class DeployConfigPage extends DacFxConfigPage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
private databaseDropdownComponent: sqlops.FormComponent;
|
||||
private databaseComponent: sqlops.FormComponent;
|
||||
private formBuilder: sqlops.FormBuilder;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.ModelView) {
|
||||
super(instance, wizardPage, model, view);
|
||||
this.fileExtension = '.bacpac';
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
let serverComponent = await this.createServerDropdown(true);
|
||||
let fileBrowserComponent = await this.createFileBrowser();
|
||||
this.databaseComponent = await this.createDatabaseTextBox();
|
||||
this.databaseComponent.title = localize('dacFx.databaseNameTextBox', 'Database Name');
|
||||
this.databaseDropdownComponent = await this.createDeployDatabaseDropdown();
|
||||
this.databaseDropdownComponent.title = localize('dacFx.databaseNameDropdown', 'Database Name');
|
||||
let radioButtons = await this.createRadiobuttons();
|
||||
|
||||
this.formBuilder = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[
|
||||
fileBrowserComponent,
|
||||
serverComponent,
|
||||
radioButtons,
|
||||
this.databaseDropdownComponent
|
||||
], {
|
||||
horizontal: true,
|
||||
componentWidth: 400
|
||||
});
|
||||
|
||||
this.form = this.formBuilder.component();
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
let r1 = await this.populateServerDropdown();
|
||||
let r2 = await this.populateDeployDatabaseDropdown();
|
||||
return r1 && r2;
|
||||
}
|
||||
|
||||
private async createFileBrowser(): Promise<sqlops.FormComponent> {
|
||||
this.createFileBrowserParts();
|
||||
|
||||
this.fileButton.onDidClick(async (click) => {
|
||||
let fileUris = await vscode.window.showOpenDialog(
|
||||
{
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
defaultUri: vscode.Uri.file(os.homedir()),
|
||||
openLabel: localize('dacFxDeploy.openFile', 'Open'),
|
||||
filters: {
|
||||
'dacpac Files': ['dacpac'],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fileUri = fileUris[0];
|
||||
this.fileTextBox.value = fileUri.fsPath;
|
||||
this.model.filePath = fileUri.fsPath;
|
||||
});
|
||||
|
||||
this.fileTextBox.onTextChanged(async () => {
|
||||
this.model.filePath = this.fileTextBox.value;
|
||||
this.databaseTextBox.value = this.generateDatabaseName(this.model.filePath);
|
||||
if (!this.model.upgradeExisting) {
|
||||
this.model.database = this.databaseTextBox.value;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.fileTextBox,
|
||||
title: localize('dacFxDeploy.fileTextboxTitle', 'File Location'),
|
||||
actions: [this.fileButton]
|
||||
};
|
||||
}
|
||||
|
||||
private async createRadiobuttons(): Promise<sqlops.FormComponent> {
|
||||
let upgradeRadioButton = this.view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
name: 'updateExisting',
|
||||
label: localize('dacFx.upgradeRadioButtonLabel', 'Upgrade Existing Database'),
|
||||
}).component();
|
||||
|
||||
let newRadioButton = this.view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
name: 'updateExisting',
|
||||
label: localize('dacFx.newRadioButtonLabel', 'New Database'),
|
||||
}).component();
|
||||
|
||||
upgradeRadioButton.onDidClick(() => {
|
||||
this.model.upgradeExisting = true;
|
||||
this.formBuilder.removeFormItem(this.databaseComponent);
|
||||
this.formBuilder.addFormItem(this.databaseDropdownComponent, { horizontal: true, componentWidth: 400 });
|
||||
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
});
|
||||
|
||||
newRadioButton.onDidClick(() => {
|
||||
this.model.upgradeExisting = false;
|
||||
this.formBuilder.removeFormItem(this.databaseDropdownComponent);
|
||||
this.formBuilder.addFormItem(this.databaseComponent, { horizontal: true, componentWidth: 400 });
|
||||
this.model.database = this.databaseTextBox.value;
|
||||
});
|
||||
|
||||
// Initialize with upgrade existing true
|
||||
upgradeRadioButton.checked = true;
|
||||
this.model.upgradeExisting = true;
|
||||
|
||||
let flexRadioButtonsModel = this.view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
}).withItems([
|
||||
upgradeRadioButton, newRadioButton]
|
||||
).component();
|
||||
|
||||
return {
|
||||
component: flexRadioButtonsModel,
|
||||
title: localize('dacFx.targetDatabaseRadioButtonsTitle', 'Target Database')
|
||||
};
|
||||
}
|
||||
|
||||
protected async createDeployDatabaseDropdown(): Promise<sqlops.FormComponent> {
|
||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
// Handle database changes
|
||||
this.databaseDropdown.onValueChanged(async () => {
|
||||
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
});
|
||||
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).component();
|
||||
return {
|
||||
component: this.databaseLoader,
|
||||
title: localize('dacFx.targetDatabaseDropdownTitle', 'Database Name')
|
||||
};
|
||||
}
|
||||
|
||||
protected async populateDeployDatabaseDropdown(): Promise<boolean> {
|
||||
this.databaseLoader.loading = true;
|
||||
this.databaseDropdown.updateProperties({ values: [] });
|
||||
if (!this.model.server) {
|
||||
this.databaseLoader.loading = false;
|
||||
return false;
|
||||
}
|
||||
let values = await this.getDatabaseValues();
|
||||
|
||||
if (this.model.database === undefined) {
|
||||
this.model.database = values[0].name;
|
||||
}
|
||||
|
||||
this.databaseDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
this.databaseLoader.loading = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private generateDatabaseName(filePath: string): string {
|
||||
let result = path.parse(filePath);
|
||||
return result.name;
|
||||
}
|
||||
}
|
||||
100
extensions/import/src/wizard/pages/exportConfigPage.ts
Normal file
100
extensions/import/src/wizard/pages/exportConfigPage.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import * as vscode from 'vscode';
|
||||
import { DacFxDataModel } from '../api/models';
|
||||
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
|
||||
import { DacFxConfigPage } from '../api/dacFxConfigPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ExportConfigPage extends DacFxConfigPage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.ModelView) {
|
||||
super(instance, wizardPage, model, view);
|
||||
this.fileExtension = '.bacpac';
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
let databaseComponent = await this.createDatabaseDropdown();
|
||||
let serverComponent = await this.createServerDropdown(false);
|
||||
let fileBrowserComponent = await this.createFileBrowser();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[
|
||||
serverComponent,
|
||||
databaseComponent,
|
||||
fileBrowserComponent,
|
||||
], {
|
||||
horizontal: true,
|
||||
componentWidth: 400
|
||||
}).component();
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
let r1 = await this.populateServerDropdown();
|
||||
let r2 = await this.populateDatabaseDropdown();
|
||||
return r1 && r2;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator(() => {
|
||||
if (this.databaseLoader.loading) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private async createFileBrowser(): Promise<sqlops.FormComponent> {
|
||||
this.createFileBrowserParts();
|
||||
|
||||
// default filepath
|
||||
this.fileTextBox.value = this.generateFilePath();
|
||||
this.model.filePath = this.fileTextBox.value;
|
||||
|
||||
this.fileButton.onDidClick(async (click) => {
|
||||
let fileUri = await vscode.window.showSaveDialog(
|
||||
{
|
||||
defaultUri: vscode.Uri.file(this.fileTextBox.value),
|
||||
saveLabel: localize('dacfxExport.saveFile', 'Save'),
|
||||
filters: {
|
||||
'bacpac Files': ['bacpac'],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!fileUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fileTextBox.value = fileUri.fsPath;
|
||||
this.model.filePath = fileUri.fsPath;
|
||||
});
|
||||
|
||||
this.fileTextBox.onTextChanged(async () => {
|
||||
this.model.filePath = this.fileTextBox.value;
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.fileTextBox,
|
||||
title: localize('dacFxExport.fileTextboxTitle', 'File Location'),
|
||||
actions: [this.fileButton]
|
||||
};
|
||||
}
|
||||
}
|
||||
122
extensions/import/src/wizard/pages/extractConfigPage.ts
Normal file
122
extensions/import/src/wizard/pages/extractConfigPage.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import * as vscode from 'vscode';
|
||||
import { DacFxDataModel } from '../api/models';
|
||||
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
|
||||
import { DacFxConfigPage } from '../api/dacFxConfigPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ExtractConfigPage extends DacFxConfigPage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private form: sqlops.FormContainer;
|
||||
private versionTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.ModelView) {
|
||||
super(instance, wizardPage, model, view);
|
||||
this.fileExtension = '.dacpac';
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
let databaseComponent = await this.createDatabaseDropdown();
|
||||
let serverComponent = await this.createServerDropdown(false);
|
||||
let fileBrowserComponent = await this.createFileBrowser();
|
||||
let versionComponent = await this.createVersionTextBox();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[
|
||||
serverComponent,
|
||||
databaseComponent,
|
||||
versionComponent,
|
||||
fileBrowserComponent,
|
||||
], {
|
||||
horizontal: true,
|
||||
componentWidth: 400
|
||||
}).component();
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
let r1 = await this.populateServerDropdown();
|
||||
let r2 = await this.populateDatabaseDropdown();
|
||||
return r1 && r2;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator(() => {
|
||||
if (this.databaseLoader.loading) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private async createFileBrowser(): Promise<sqlops.FormComponent> {
|
||||
this.createFileBrowserParts();
|
||||
|
||||
// default filepath
|
||||
this.fileTextBox.value = this.generateFilePath();
|
||||
this.model.filePath = this.fileTextBox.value;
|
||||
|
||||
this.fileButton.onDidClick(async (click) => {
|
||||
let fileUri = await vscode.window.showSaveDialog(
|
||||
{
|
||||
defaultUri: vscode.Uri.file(this.fileTextBox.value),
|
||||
saveLabel: localize('dacfxExtract.saveFile', 'Save'),
|
||||
filters: {
|
||||
'dacpac Files': ['dacpac'],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!fileUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.fileTextBox.value = fileUri.fsPath;
|
||||
this.model.filePath = fileUri.fsPath;
|
||||
});
|
||||
|
||||
this.fileTextBox.onTextChanged(async () => {
|
||||
this.model.filePath = this.fileTextBox.value;
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.fileTextBox,
|
||||
title: localize('dacFxExtract.fileTextboxTitle', 'File Location'),
|
||||
actions: [this.fileButton]
|
||||
};
|
||||
}
|
||||
|
||||
private async createVersionTextBox(): Promise<sqlops.FormComponent> {
|
||||
this.versionTextBox = this.view.modelBuilder.inputBox().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
// default filepath
|
||||
this.versionTextBox.value = '1.0.0.0';
|
||||
this.model.version = this.versionTextBox.value;
|
||||
|
||||
this.versionTextBox.onTextChanged(async () => {
|
||||
this.model.version = this.versionTextBox.value;
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.versionTextBox,
|
||||
title: localize('dacFxExtract.versionTextboxTitle', 'Version (use x.x.x.x where x is a number)'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -102,57 +102,9 @@ export class FileConfigPage extends ImportPage {
|
||||
}
|
||||
|
||||
private async populateServerDropdown(): Promise<boolean> {
|
||||
let cons = await sqlops.connection.getActiveConnections();
|
||||
// This user has no active connections ABORT MISSION
|
||||
if (!cons || cons.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
let count = -1;
|
||||
let idx = -1;
|
||||
|
||||
|
||||
let values = cons.map(c => {
|
||||
// Handle the code to remember what the user's choice was from before
|
||||
count++;
|
||||
if (idx === -1) {
|
||||
if (this.model.server && c.connectionId === this.model.server.connectionId) {
|
||||
idx = count;
|
||||
} else if (this.model.serverId && c.connectionId === this.model.serverId) {
|
||||
idx = count;
|
||||
}
|
||||
}
|
||||
|
||||
let db = c.options.databaseDisplayName;
|
||||
let usr = c.options.user;
|
||||
let srv = c.options.server;
|
||||
|
||||
if (!db) {
|
||||
db = '<default>';
|
||||
}
|
||||
|
||||
if (!usr) {
|
||||
usr = 'default';
|
||||
}
|
||||
|
||||
let finalName = `${srv}, ${db} (${usr})`;
|
||||
return {
|
||||
connection: c,
|
||||
displayName: finalName,
|
||||
name: c.connectionId
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
delete this.model.server;
|
||||
delete this.model.serverId;
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
let values = await this.getServerValues();
|
||||
if (values === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.model.server = values[0].connection;
|
||||
@@ -195,29 +147,7 @@ export class FileConfigPage extends ImportPage {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
let values = (await sqlops.connection.listDatabases(this.model.server.connectionId)).map(db => {
|
||||
count++;
|
||||
if (this.model.database && db === this.model.database) {
|
||||
idx = count;
|
||||
}
|
||||
|
||||
return {
|
||||
displayName: db,
|
||||
name: db
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
let values = await this.getDatabaseValues();
|
||||
|
||||
this.model.database = values[0].name;
|
||||
|
||||
@@ -377,6 +307,18 @@ export class FileConfigPage extends ImportPage {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected deleteServerValues() {
|
||||
delete this.model.server;
|
||||
delete this.model.serverId;
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
|
||||
protected deleteDatabaseValues() {
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
|
||||
// private async populateTableNames(): Promise<boolean> {
|
||||
// this.tableNames = [];
|
||||
// let databaseName = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
|
||||
101
extensions/import/src/wizard/pages/importConfigPage.ts
Normal file
101
extensions/import/src/wizard/pages/importConfigPage.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as vscode from 'vscode';
|
||||
import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { DacFxDataModel } from '../api/models';
|
||||
import { DataTierApplicationWizard } from '../dataTierApplicationWizard';
|
||||
import { DacFxConfigPage } from '../api/dacFxConfigPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ImportConfigPage extends DacFxConfigPage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.ModelView) {
|
||||
super(instance, wizardPage, model, view);
|
||||
this.fileExtension = '.bacpac';
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
let databaseComponent = await this.createDatabaseTextBox();
|
||||
let serverComponent = await this.createServerDropdown(true);
|
||||
let fileBrowserComponent = await this.createFileBrowser();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[
|
||||
fileBrowserComponent,
|
||||
serverComponent,
|
||||
databaseComponent,
|
||||
], {
|
||||
horizontal: true,
|
||||
componentWidth: 400
|
||||
}).component();
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
let r1 = await this.populateServerDropdown();
|
||||
return r1;
|
||||
}
|
||||
|
||||
private async createFileBrowser(): Promise<sqlops.FormComponent> {
|
||||
this.createFileBrowserParts();
|
||||
|
||||
this.fileButton.onDidClick(async (click) => {
|
||||
let fileUris = await vscode.window.showOpenDialog(
|
||||
{
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
defaultUri: vscode.Uri.file(os.homedir()),
|
||||
openLabel: localize('dacFxImport.openFile', 'Open'),
|
||||
filters: {
|
||||
'bacpac Files': ['bacpac'],
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fileUri = fileUris[0];
|
||||
this.fileTextBox.value = fileUri.fsPath;
|
||||
this.model.filePath = fileUri.fsPath;
|
||||
this.model.database = this.generateDatabaseName(this.model.filePath);
|
||||
this.databaseTextBox.value = this.model.database;
|
||||
});
|
||||
|
||||
this.fileTextBox.onTextChanged(async () => {
|
||||
this.model.filePath = this.fileTextBox.value;
|
||||
this.model.database = this.generateDatabaseName(this.model.filePath);
|
||||
this.databaseTextBox.value = this.model.database;
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.fileTextBox,
|
||||
title: localize('dacFxImport.fileTextboxTitle', 'File Location'),
|
||||
actions: [this.fileButton]
|
||||
};
|
||||
}
|
||||
|
||||
private generateDatabaseName(filePath: string): string {
|
||||
let result = path.parse(filePath);
|
||||
return result.name;
|
||||
}
|
||||
}
|
||||
174
extensions/import/src/wizard/pages/selectOperationpage.ts
Normal file
174
extensions/import/src/wizard/pages/selectOperationpage.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as nls from 'vscode-nls';
|
||||
import { DacFxDataModel } from '../api/models';
|
||||
import { DataTierApplicationWizard, Operation } from '../DataTierApplicationWizard';
|
||||
import { BasePage } from '../api/basePage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class SelectOperationPage extends BasePage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: DataTierApplicationWizard;
|
||||
protected readonly model: DacFxDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
|
||||
private deployRadioButton: sqlops.RadioButtonComponent;
|
||||
private extractRadioButton: sqlops.RadioButtonComponent;
|
||||
private importRadioButton: sqlops.RadioButtonComponent;
|
||||
private exportRadioButton: sqlops.RadioButtonComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: DataTierApplicationWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: DacFxDataModel, view: sqlops.ModelView) {
|
||||
super();
|
||||
this.instance = instance;
|
||||
this.wizardPage = wizardPage;
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
let deployComponent = await this.createDeployRadioButton();
|
||||
let extractComponent = await this.createExtractRadioButton();
|
||||
let importComponent = await this.createImportRadioButton();
|
||||
let exportComponent = await this.createExportRadioButton();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[
|
||||
deployComponent,
|
||||
extractComponent,
|
||||
importComponent,
|
||||
exportComponent
|
||||
], {
|
||||
horizontal: true
|
||||
}).component();
|
||||
await this.view.initializeModel(this.form);
|
||||
|
||||
// default have the first radio button checked
|
||||
this.deployRadioButton.checked = true;
|
||||
this.instance.setDoneButton(Operation.deploy);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
let numPages = this.instance.wizard.pages.length;
|
||||
for (let i = numPages - 1; i > 2; --i) {
|
||||
await this.instance.wizard.removePage(i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async createDeployRadioButton(): Promise<sqlops.FormComponent> {
|
||||
this.deployRadioButton = this.view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
name: 'selectedOperation',
|
||||
label: localize('dacFx.deployRadioButtonLabel', 'Deploy a data-tier application .dacpac file to an instance of SQL Server [Deploy Dacpac]'),
|
||||
}).component();
|
||||
|
||||
this.deployRadioButton.onDidClick(() => {
|
||||
// remove the previous page
|
||||
this.instance.wizard.removePage(1);
|
||||
|
||||
// add deploy page
|
||||
let page = this.instance.pages.get('deployConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.deploy);
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.deployRadioButton,
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
|
||||
private async createExtractRadioButton(): Promise<sqlops.FormComponent> {
|
||||
this.extractRadioButton = this.view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
name: 'selectedOperation',
|
||||
label: localize('dacFx.extractRadioButtonLabel', 'Extract a data-tier application from an instance of SQL Server to a .dacpac file [Extract Dacpac]'),
|
||||
}).component();
|
||||
|
||||
this.extractRadioButton.onDidClick(() => {
|
||||
// remove the previous pages
|
||||
this.instance.wizard.removePage(1);
|
||||
|
||||
// add the extract page
|
||||
let page = this.instance.pages.get('extractConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.extract);
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.extractRadioButton,
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
|
||||
private async createImportRadioButton(): Promise<sqlops.FormComponent> {
|
||||
this.importRadioButton = this.view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
name: 'selectedOperation',
|
||||
label: localize('dacFx.importRadioButtonLabel', 'Create a database from a .bacpac file [Import Bacpac]'),
|
||||
}).component();
|
||||
|
||||
this.importRadioButton.onDidClick(() => {
|
||||
// remove the previous page
|
||||
this.instance.wizard.removePage(1);
|
||||
|
||||
// add the import page
|
||||
let page = this.instance.pages.get('importConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.import);
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.importRadioButton,
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
|
||||
private async createExportRadioButton(): Promise<sqlops.FormComponent> {
|
||||
this.exportRadioButton = this.view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
name: 'selectedOperation',
|
||||
label: localize('dacFx.exportRadioButtonLabel', 'Export the schema and data from a database to the logical .bacpac file format [Export Bacpac]'),
|
||||
}).component();
|
||||
|
||||
this.exportRadioButton.onDidClick(() => {
|
||||
// remove the 2 previous pages
|
||||
this.instance.wizard.removePage(1);
|
||||
|
||||
// add the export pages
|
||||
let page = this.instance.pages.get('exportConfig');
|
||||
this.instance.wizard.addPage(page.wizardPage, 1);
|
||||
|
||||
// change button text and operation
|
||||
this.instance.setDoneButton(Operation.export);
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.exportRadioButton,
|
||||
title: ''
|
||||
};
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator(() => {
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ agent-base@4, agent-base@^4.1.0:
|
||||
|
||||
applicationinsights@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
||||
resolved "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
||||
integrity sha1-U0Rrgw/o1dYZ7uKieLMdPSUDCSc=
|
||||
dependencies:
|
||||
diagnostic-channel "0.2.0"
|
||||
@@ -36,7 +36,7 @@ buffer-alloc-unsafe@^1.1.0:
|
||||
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.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
||||
@@ -75,26 +75,19 @@ core-util-is@~1.0.0:
|
||||
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":
|
||||
version "0.2.10"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.7":
|
||||
version "0.2.6"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/85653d8b305af8aef334728d71f07bdc240dfcb7"
|
||||
dependencies:
|
||||
vscode-languageclient "3.5.1"
|
||||
|
||||
debug@3.1.0:
|
||||
debug@3.1.0, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.1.0:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||
@@ -150,12 +143,12 @@ decompress@^4.2.0:
|
||||
|
||||
diagnostic-channel-publishers@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.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
||||
integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
|
||||
|
||||
diagnostic-channel@0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
||||
resolved "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
|
||||
integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
|
||||
dependencies:
|
||||
semver "^5.3.0"
|
||||
@@ -168,9 +161,9 @@ end-of-stream@^1.0.0:
|
||||
once "^1.4.0"
|
||||
|
||||
es6-promise@^4.0.3:
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
|
||||
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
|
||||
integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==
|
||||
|
||||
es6-promisify@^5.0.0:
|
||||
version "5.0.0"
|
||||
@@ -220,9 +213,9 @@ get-stream@^2.2.0:
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
graceful-fs@^4.1.10:
|
||||
version "4.1.15"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||
version "4.1.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
||||
integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
|
||||
|
||||
"graceful-readlink@>= 1.0.0":
|
||||
version "1.0.1"
|
||||
@@ -294,11 +287,6 @@ ms@2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.1"
|
||||
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:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
@@ -312,9 +300,9 @@ once@^1.4.0:
|
||||
wrappy "1"
|
||||
|
||||
opener@^1.4.3:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
||||
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
|
||||
integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=
|
||||
|
||||
os-tmpdir@~1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -380,7 +368,7 @@ seek-bzip@^1.0.5:
|
||||
|
||||
semver@^5.3.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||
resolved "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
|
||||
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
||||
@@ -409,16 +397,16 @@ strip-dirs@^2.0.0:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
tar-stream@^1.5.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
|
||||
integrity sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.2.0"
|
||||
buffer-alloc "^1.1.0"
|
||||
end-of-stream "^1.0.0"
|
||||
fs-constants "^1.0.0"
|
||||
readable-stream "^2.3.0"
|
||||
to-buffer "^1.1.1"
|
||||
to-buffer "^1.1.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
through@^2.3.6:
|
||||
@@ -433,15 +421,15 @@ tmp@^0.0.33:
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
to-buffer@^1.1.1:
|
||||
to-buffer@^1.1.0:
|
||||
version "1.1.1"
|
||||
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:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
||||
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
|
||||
integrity sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==
|
||||
dependencies:
|
||||
buffer "^3.0.1"
|
||||
through "^2.3.6"
|
||||
@@ -453,7 +441,7 @@ util-deprecate@~1.0.1:
|
||||
|
||||
vscode-extension-telemetry@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.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
|
||||
integrity sha512-Vw3Sr+dZwl+c6PlsUwrTtCOJkgrmvS3OUVDQGcmpXWAgq9xGq6as0K4pUx+aGqTjzLAESmWSrs6HlJm6J6Khcg==
|
||||
dependencies:
|
||||
applicationinsights "1.0.1"
|
||||
@@ -484,9 +472,9 @@ vscode-languageserver-types@3.5.0:
|
||||
integrity sha1-5I15li8LjgLelV4/UkkI4rGcA3Q=
|
||||
|
||||
vscode-nls@^3.2.1:
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4"
|
||||
integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw==
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
||||
integrity sha512-FTjdqa4jDDoBjJqr36O8lmmZf/55kQ2w4ZY/+GL6K92fq765BqO3aYw21atnXUno/P04V5DWagNl4ybDIndJsw==
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
@@ -508,5 +496,5 @@ yauzl@^2.4.2:
|
||||
|
||||
zone.js@0.7.6:
|
||||
version "0.7.6"
|
||||
resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
|
||||
resolved "https://registry.npmjs.org/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"
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.10",
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.9",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||
"vscode-extension-telemetry": "^0.0.15"
|
||||
@@ -285,6 +285,10 @@
|
||||
{
|
||||
"displayName": "Windows Authentication",
|
||||
"name": "Integrated"
|
||||
},
|
||||
{
|
||||
"displayName": "Azure Active Directory - Universal with MFA support",
|
||||
"name": "AzureMFA"
|
||||
}
|
||||
],
|
||||
"isRequired": true,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.5.0-alpha.58",
|
||||
"version": "1.5.0-alpha.59",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||
|
||||
@@ -291,3 +291,60 @@ export namespace DeleteAgentJobScheduleRequest {
|
||||
}
|
||||
|
||||
// ------------------------------- < Agent Management > ------------------------------------
|
||||
|
||||
// ------------------------------- < DacFx > ------------------------------------
|
||||
|
||||
export enum TaskExecutionMode {
|
||||
execute = 0,
|
||||
script = 1,
|
||||
executeAndScript = 2,
|
||||
}
|
||||
export interface ExportParams {
|
||||
databaseName: string;
|
||||
packageFilePath: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface ImportParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
|
||||
export interface ExtractParams {
|
||||
databaseName: string;
|
||||
packageFilePath: string;
|
||||
applicationName: string;
|
||||
applicationVersion: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface DeployParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
upgradeExisting: boolean;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export namespace ExportRequest {
|
||||
export const type = new RequestType<ExportParams, sqlops.DacFxResult, void, void>('dacfx/export');
|
||||
}
|
||||
|
||||
export namespace ImportRequest {
|
||||
export const type = new RequestType<ImportParams, sqlops.DacFxResult, void, void>('dacfx/import');
|
||||
}
|
||||
|
||||
export namespace ExtractRequest {
|
||||
export const type = new RequestType<ExtractParams, sqlops.DacFxResult, void, void>('dacfx/extract');
|
||||
}
|
||||
|
||||
export namespace DeployRequest {
|
||||
export const type = new RequestType<DeployParams, sqlops.DacFxResult, void, void>('dacfx/deploy');
|
||||
}
|
||||
|
||||
// ------------------------------- < DacFx > ------------------------------------
|
||||
@@ -8,7 +8,7 @@ import { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
|
||||
import { ClientCapabilities, StaticFeature, RPCMessageType, ServerCapabilities } from 'vscode-languageclient';
|
||||
import { Disposable } from 'vscode';
|
||||
import { Telemetry } from './telemetry';
|
||||
import * as contracts from './contracts';
|
||||
import * as contracts from './contracts';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as Utils from './utils';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
@@ -28,6 +28,94 @@ export class TelemetryFeature implements StaticFeature {
|
||||
}
|
||||
}
|
||||
|
||||
export class DacFxServicesFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messageTypes: RPCMessageType[] = [
|
||||
contracts.ExportRequest.type,
|
||||
contracts.ImportRequest.type,
|
||||
contracts.ExtractRequest.type,
|
||||
contracts.DeployRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, DacFxServicesFeature.messageTypes);
|
||||
}
|
||||
|
||||
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
}
|
||||
|
||||
public initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
let self = this;
|
||||
|
||||
let exportBacpac = (databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> => {
|
||||
let params: contracts.ExportParams = { databaseName: databaseName, packageFilePath: packageFilePath, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||
return client.sendRequest(contracts.ExportRequest.type, params).then(
|
||||
r => {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.ExportRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let importBacpac = (packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> => {
|
||||
let params: contracts.ImportParams = { packageFilePath: packageFilePath, databaseName: databaseName, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||
return client.sendRequest(contracts.ImportRequest.type, params).then(
|
||||
r => {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.ImportRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let extractDacpac = (databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> => {
|
||||
let params: contracts.ExtractParams = { databaseName: databaseName, packageFilePath: packageFilePath, applicationName: applicationName, applicationVersion: applicationVersion, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||
return client.sendRequest(contracts.ExtractRequest.type, params).then(
|
||||
r => {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.ExtractRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let deployDacpac = (packageFilePath: string, targetDatabaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> => {
|
||||
let params: contracts.DeployParams = { packageFilePath: packageFilePath, databaseName: targetDatabaseName, upgradeExisting: upgradeExisting, ownerUri: ownerUri, taskExecutionMode: taskExecutionMode };
|
||||
return client.sendRequest(contracts.DeployRequest.type, params).then(
|
||||
r => {
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(contracts.DeployRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return sqlops.dataprotocol.registerDacFxServicesProvider({
|
||||
providerId: client.providerId,
|
||||
exportBacpac,
|
||||
importBacpac,
|
||||
extractDacpac,
|
||||
deployDacpac
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
contracts.AgentJobsRequest.type,
|
||||
@@ -229,7 +317,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
|
||||
// Alert management methods
|
||||
let getAlerts = (ownerUri: string): Thenable<sqlops.AgentAlertsResult> => {
|
||||
let getAlerts = (ownerUri: string): Thenable<sqlops.AgentAlertsResult> => {
|
||||
let params: contracts.AgentAlertsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
@@ -299,7 +387,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
|
||||
// Operator management methods
|
||||
let getOperators = (ownerUri: string): Thenable<sqlops.AgentOperatorsResult> => {
|
||||
let getOperators = (ownerUri: string): Thenable<sqlops.AgentOperatorsResult> => {
|
||||
let params: contracts.AgentOperatorsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
@@ -369,7 +457,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
|
||||
// Proxy management methods
|
||||
let getProxies = (ownerUri: string): Thenable<sqlops.AgentProxiesResult> => {
|
||||
let getProxies = (ownerUri: string): Thenable<sqlops.AgentProxiesResult> => {
|
||||
let params: contracts.AgentProxiesParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
@@ -439,7 +527,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
|
||||
// Agent Credential Method
|
||||
let getCredentials = (ownerUri: string): Thenable<sqlops.GetCredentialsResult> => {
|
||||
let getCredentials = (ownerUri: string): Thenable<sqlops.GetCredentialsResult> => {
|
||||
let params: contracts.GetCredentialsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
@@ -455,7 +543,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
|
||||
|
||||
// Job Schedule management methods
|
||||
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
|
||||
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
|
||||
let params: contracts.AgentJobScheduleParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ import { CredentialStore } from './credentialstore/credentialstore';
|
||||
import { AzureResourceProvider } from './resourceProvider/resourceProvider';
|
||||
import * as Utils from './utils';
|
||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||
import { TelemetryFeature, AgentServicesFeature } from './features';
|
||||
import { TelemetryFeature, AgentServicesFeature, DacFxServicesFeature } from './features';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
|
||||
@@ -55,7 +55,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
// we only want to add new features
|
||||
...SqlOpsDataClient.defaultFeatures,
|
||||
TelemetryFeature,
|
||||
AgentServicesFeature
|
||||
AgentServicesFeature,
|
||||
DacFxServicesFeature,
|
||||
],
|
||||
outputChannel: new CustomOutputChannel()
|
||||
};
|
||||
|
||||
@@ -36,7 +36,7 @@ buffer-alloc-unsafe@^1.1.0:
|
||||
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.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
integrity sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==
|
||||
@@ -75,26 +75,19 @@ core-util-is@~1.0.0:
|
||||
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":
|
||||
version "0.2.10"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/4de3f7caf0eba54159911b977ddb4f5d7c0a9ca8"
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.9":
|
||||
version "0.2.9"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/0a3c0f22940d1c67bb567171508ccb1169c6313a"
|
||||
dependencies:
|
||||
vscode-languageclient "3.5.1"
|
||||
|
||||
debug@3.1.0:
|
||||
debug@3.1.0, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@^3.1.0:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
|
||||
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
|
||||
dependencies:
|
||||
ms "^2.1.1"
|
||||
|
||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||
@@ -168,9 +161,9 @@ end-of-stream@^1.0.0:
|
||||
once "^1.4.0"
|
||||
|
||||
es6-promise@^4.0.3:
|
||||
version "4.2.5"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.5.tgz#da6d0d5692efb461e082c14817fe2427d8f5d054"
|
||||
integrity sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg==
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
|
||||
integrity sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==
|
||||
|
||||
es6-promisify@^5.0.0:
|
||||
version "5.0.0"
|
||||
@@ -220,9 +213,9 @@ get-stream@^2.2.0:
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
graceful-fs@^4.1.10:
|
||||
version "4.1.15"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
|
||||
version "4.1.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
||||
integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=
|
||||
|
||||
"graceful-readlink@>= 1.0.0":
|
||||
version "1.0.1"
|
||||
@@ -294,11 +287,6 @@ ms@2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||
|
||||
ms@^2.1.1:
|
||||
version "2.1.1"
|
||||
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:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
@@ -312,9 +300,9 @@ once@^1.4.0:
|
||||
wrappy "1"
|
||||
|
||||
opener@^1.4.3:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed"
|
||||
integrity sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
|
||||
integrity sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=
|
||||
|
||||
os-tmpdir@~1.0.2:
|
||||
version "1.0.2"
|
||||
@@ -379,9 +367,9 @@ seek-bzip@^1.0.5:
|
||||
commander "~2.8.1"
|
||||
|
||||
semver@^5.3.0:
|
||||
version "5.6.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
|
||||
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||
integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==
|
||||
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.5":
|
||||
version "0.1.5"
|
||||
@@ -409,16 +397,16 @@ strip-dirs@^2.0.0:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
tar-stream@^1.5.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||
integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
|
||||
integrity sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.2.0"
|
||||
buffer-alloc "^1.1.0"
|
||||
end-of-stream "^1.0.0"
|
||||
fs-constants "^1.0.0"
|
||||
readable-stream "^2.3.0"
|
||||
to-buffer "^1.1.1"
|
||||
to-buffer "^1.1.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
through@^2.3.6:
|
||||
@@ -433,15 +421,15 @@ tmp@^0.0.33:
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
to-buffer@^1.1.1:
|
||||
to-buffer@^1.1.0:
|
||||
version "1.1.1"
|
||||
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:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
||||
integrity sha512-fIZnvdjblYs7Cru/xC6tCPVhz7JkYcVQQkePwMLyQELzYTds2Xn8QefPVnvdVhhZqubxNA1cASXEH5wcK0Bucw==
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
|
||||
integrity sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==
|
||||
dependencies:
|
||||
buffer "^3.0.1"
|
||||
through "^2.3.6"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "profiler",
|
||||
"displayName": "SQL Server Profiler",
|
||||
"description": "SQL Server Profiler for Azure Data Studio",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||
|
||||
File diff suppressed because one or more lines are too long
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "azuredatastudio",
|
||||
"version": "1.3.3",
|
||||
"version": "1.3.4",
|
||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
@@ -68,7 +68,7 @@
|
||||
"rxjs": "5.4.0",
|
||||
"sanitize-html": "^1.19.1",
|
||||
"semver": "^5.5.0",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.29",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.28",
|
||||
"spdlog": "0.7.1",
|
||||
"sudo-prompt": "8.2.0",
|
||||
"svg.js": "^2.2.5",
|
||||
@@ -102,12 +102,12 @@
|
||||
"documentdb": "^1.5.1",
|
||||
"electron-mksnapshot": "~1.7.0",
|
||||
"eslint": "^3.4.0",
|
||||
"event-stream": "^3.3.4",
|
||||
"event-stream": "3.3.4",
|
||||
"express": "^4.13.1",
|
||||
"glob": "^5.0.13",
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-atom-electron": "^1.16.1",
|
||||
"gulp-azure-storage": "^0.7.0",
|
||||
"gulp-atom-electron": "^1.19.2",
|
||||
"gulp-azure-storage": "^0.8.2",
|
||||
"gulp-bom": "^1.0.0",
|
||||
"gulp-buffer": "0.0.2",
|
||||
"gulp-cli": "^2.0.1",
|
||||
@@ -120,7 +120,7 @@
|
||||
"gulp-json-editor": "^2.2.1",
|
||||
"gulp-mocha": "^2.1.3",
|
||||
"gulp-plumber": "^1.2.0",
|
||||
"gulp-remote-src": "^0.4.0",
|
||||
"gulp-remote-src": "^0.4.4",
|
||||
"gulp-rename": "^1.2.0",
|
||||
"gulp-replace": "^0.5.4",
|
||||
"gulp-shell": "^0.5.2",
|
||||
@@ -129,7 +129,7 @@
|
||||
"gulp-tslint": "^8.1.2",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
"gulp-util": "^3.0.6",
|
||||
"gulp-vinyl-zip": "^1.2.2",
|
||||
"gulp-vinyl-zip": "^2.1.2",
|
||||
"husky": "^0.13.1",
|
||||
"innosetup-compiler": "^5.5.60",
|
||||
"is": "^3.1.0",
|
||||
|
||||
@@ -12,9 +12,10 @@ import * as types from 'vs/base/common/types';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export function appendRow(container: Builder, label: string, labelClass: string, cellContainerClass: string): Builder {
|
||||
export function appendRow(container: Builder, label: string, labelClass: string, cellContainerClass: string, rowContainerClass?: string): Builder {
|
||||
let cellContainer: Builder;
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
let rowAttributes = rowContainerClass ? { class: rowContainerClass } : {};
|
||||
container.element('tr', rowAttributes, (rowContainer) => {
|
||||
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
|
||||
labelCellContainer.div({}, (labelContainer) => {
|
||||
labelContainer.text(label);
|
||||
|
||||
@@ -75,7 +75,12 @@ export class SelectBox extends vsSelectBox {
|
||||
|
||||
// explicitly set the accessible role so that the screen readers can read the control type properly
|
||||
this.selectElement.setAttribute('role', 'combobox');
|
||||
|
||||
this._selectBoxOptions = selectBoxOptions;
|
||||
var focusTracker = dom.trackFocus(this.selectElement);
|
||||
this._register(focusTracker);
|
||||
this._register(focusTracker.onDidBlur(() => this._hideMessage()));
|
||||
this._register(focusTracker.onDidFocus(() => this._showMessage()));
|
||||
}
|
||||
|
||||
public style(styles: ISelectBoxStyles): void {
|
||||
@@ -142,6 +147,10 @@ export class SelectBox extends vsSelectBox {
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public hasFocus(): boolean {
|
||||
return document.activeElement === this.selectElement;
|
||||
}
|
||||
|
||||
public showMessage(message: IMessage): void {
|
||||
this.message = message;
|
||||
|
||||
@@ -163,7 +172,9 @@ export class SelectBox extends vsSelectBox {
|
||||
|
||||
aria.alert(alertText);
|
||||
|
||||
this._showMessage();
|
||||
if (this.hasFocus()) {
|
||||
this._showMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public _showMessage(): void {
|
||||
|
||||
@@ -9,27 +9,36 @@ export interface IObservableCollection<T> {
|
||||
getLength(): number;
|
||||
at(index: number): T;
|
||||
getRange(start: number, end: number): T[];
|
||||
setCollectionChangedCallback(callback: (startIndex: number, count: number) => void): void;
|
||||
setLength(number): void;
|
||||
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface IGridDataRow {
|
||||
row?: number;
|
||||
values: any[];
|
||||
}
|
||||
|
||||
export enum CollectionChange {
|
||||
ItemsReplaced
|
||||
}
|
||||
|
||||
class LoadCancellationToken {
|
||||
isCancelled: boolean;
|
||||
}
|
||||
|
||||
class DataWindow<T> {
|
||||
private _data: T[];
|
||||
class DataWindow<TData> {
|
||||
private _data: TData[];
|
||||
private _length: number = 0;
|
||||
private _offsetFromDataSource: number = -1;
|
||||
|
||||
private lastLoadCancellationToken: LoadCancellationToken;
|
||||
|
||||
constructor(
|
||||
private loadFunction: (offset: number, count: number) => Thenable<T[]>,
|
||||
private placeholderItemGenerator: (index: number) => T,
|
||||
private loadFunction: (offset: number, count: number) => Thenable<TData[]>,
|
||||
private placeholderItemGenerator: (index: number) => TData,
|
||||
private loadCompleteCallback: (start: number, end: number) => void
|
||||
) { }
|
||||
) {
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._data = undefined;
|
||||
@@ -41,26 +50,26 @@ class DataWindow<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public getStartIndex(): number {
|
||||
getStartIndex(): number {
|
||||
return this._offsetFromDataSource;
|
||||
}
|
||||
|
||||
public getEndIndex(): number {
|
||||
getEndIndex(): number {
|
||||
return this._offsetFromDataSource + this._length;
|
||||
}
|
||||
|
||||
public contains(dataSourceIndex: number): boolean {
|
||||
contains(dataSourceIndex: number): boolean {
|
||||
return dataSourceIndex >= this.getStartIndex() && dataSourceIndex < this.getEndIndex();
|
||||
}
|
||||
|
||||
public getItem(index: number): T {
|
||||
getItem(index: number): TData {
|
||||
if (!this._data) {
|
||||
return this.placeholderItemGenerator(index);
|
||||
}
|
||||
return this._data[index - this._offsetFromDataSource];
|
||||
}
|
||||
|
||||
public positionWindow(offset: number, length: number): void {
|
||||
positionWindow(offset: number, length: number): void {
|
||||
this._offsetFromDataSource = offset;
|
||||
this._length = length;
|
||||
this._data = undefined;
|
||||
@@ -83,28 +92,34 @@ class DataWindow<T> {
|
||||
}
|
||||
}
|
||||
|
||||
export class VirtualizedCollection<T extends Slick.SlickData> implements IObservableCollection<T> {
|
||||
private _bufferWindowBefore: DataWindow<T>;
|
||||
private _window: DataWindow<T>;
|
||||
private _bufferWindowAfter: DataWindow<T>;
|
||||
export class VirtualizedCollection<TData> implements IObservableCollection<TData> {
|
||||
|
||||
private collectionChangedCallback: (startIndex: number, count: number) => void;
|
||||
private _length: number;
|
||||
private _windowSize: number;
|
||||
private _bufferWindowBefore: DataWindow<TData>;
|
||||
private _window: DataWindow<TData>;
|
||||
private _bufferWindowAfter: DataWindow<TData>;
|
||||
|
||||
private collectionChangedCallback: (change: CollectionChange, startIndex: number, count: number) => void;
|
||||
|
||||
constructor(
|
||||
private readonly windowSize: number,
|
||||
private placeHolderGenerator: (index: number) => T,
|
||||
private length: number,
|
||||
loadFn: (offset: number, count: number) => Thenable<T[]>
|
||||
windowSize: number,
|
||||
length: number,
|
||||
loadFn: (offset: number, count: number) => Thenable<TData[]>,
|
||||
private _placeHolderGenerator: (index: number) => TData
|
||||
) {
|
||||
this._windowSize = windowSize;
|
||||
this._length = length;
|
||||
|
||||
let loadCompleteCallback = (start: number, end: number) => {
|
||||
if (this.collectionChangedCallback) {
|
||||
this.collectionChangedCallback(start, end - start);
|
||||
this.collectionChangedCallback(CollectionChange.ItemsReplaced, start, end - start);
|
||||
}
|
||||
};
|
||||
|
||||
this._bufferWindowBefore = new DataWindow(loadFn, placeHolderGenerator, loadCompleteCallback);
|
||||
this._window = new DataWindow(loadFn, placeHolderGenerator, loadCompleteCallback);
|
||||
this._bufferWindowAfter = new DataWindow(loadFn, placeHolderGenerator, loadCompleteCallback);
|
||||
this._bufferWindowBefore = new DataWindow(loadFn, _placeHolderGenerator, loadCompleteCallback);
|
||||
this._window = new DataWindow(loadFn, _placeHolderGenerator, loadCompleteCallback);
|
||||
this._bufferWindowAfter = new DataWindow(loadFn, _placeHolderGenerator, loadCompleteCallback);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
@@ -113,23 +128,19 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
|
||||
this._window.dispose();
|
||||
}
|
||||
|
||||
public setCollectionChangedCallback(callback: (startIndex: number, count: number) => void): void {
|
||||
setCollectionChangedCallback(callback: (change: CollectionChange, startIndex: number, count: number) => void): void {
|
||||
this.collectionChangedCallback = callback;
|
||||
}
|
||||
|
||||
public getLength(): number {
|
||||
return this.length;
|
||||
getLength(): number {
|
||||
return this._length;
|
||||
}
|
||||
|
||||
setLength(number: any): void {
|
||||
this.length = number;
|
||||
}
|
||||
|
||||
public at(index: number): T {
|
||||
at(index: number): TData {
|
||||
return this.getRange(index, index + 1)[0];
|
||||
}
|
||||
|
||||
public getRange(start: number, end: number): T[] {
|
||||
getRange(start: number, end: number): TData[] {
|
||||
|
||||
// current data may contain placeholders
|
||||
let currentData = this.getRangeFromCurrent(start, end);
|
||||
@@ -144,7 +155,7 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
|
||||
this._bufferWindowAfter = this._window;
|
||||
this._window = this._bufferWindowBefore;
|
||||
this._bufferWindowBefore = windowToRecycle;
|
||||
let newWindowOffset = Math.max(0, this._window.getStartIndex() - this.windowSize);
|
||||
let newWindowOffset = Math.max(0, this._window.getStartIndex() - this._windowSize);
|
||||
|
||||
this._bufferWindowBefore.positionWindow(newWindowOffset, this._window.getStartIndex() - newWindowOffset);
|
||||
} else if (start >= this._bufferWindowAfter.getStartIndex()) {
|
||||
@@ -153,8 +164,8 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
|
||||
this._bufferWindowBefore = this._window;
|
||||
this._window = this._bufferWindowAfter;
|
||||
this._bufferWindowAfter = windowToRecycle;
|
||||
let newWindowOffset = Math.min(this._window.getStartIndex() + this.windowSize, this.length);
|
||||
let newWindowLength = Math.min(this.length - newWindowOffset, this.windowSize);
|
||||
let newWindowOffset = Math.min(this._window.getStartIndex() + this._windowSize, this._length);
|
||||
let newWindowLength = Math.min(this._length - newWindowOffset, this._windowSize);
|
||||
|
||||
this._bufferWindowAfter.positionWindow(newWindowOffset, newWindowLength);
|
||||
}
|
||||
@@ -162,7 +173,7 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
|
||||
return currentData;
|
||||
}
|
||||
|
||||
private getRangeFromCurrent(start: number, end: number): T[] {
|
||||
private getRangeFromCurrent(start: number, end: number): TData[] {
|
||||
let currentData = [];
|
||||
for (let i = 0; i < end - start; i++) {
|
||||
currentData.push(this.getDataFromCurrent(start + i));
|
||||
@@ -171,7 +182,7 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
|
||||
return currentData;
|
||||
}
|
||||
|
||||
private getDataFromCurrent(index: number): T {
|
||||
private getDataFromCurrent(index: number): TData {
|
||||
if (this._bufferWindowBefore.contains(index)) {
|
||||
return this._bufferWindowBefore.getItem(index);
|
||||
} else if (this._bufferWindowAfter.contains(index)) {
|
||||
@@ -180,47 +191,39 @@ export class VirtualizedCollection<T extends Slick.SlickData> implements IObserv
|
||||
return this._window.getItem(index);
|
||||
}
|
||||
|
||||
return this.placeHolderGenerator(index);
|
||||
return this._placeHolderGenerator(index);
|
||||
}
|
||||
|
||||
private resetWindowsAroundIndex(index: number): void {
|
||||
|
||||
let bufferWindowBeforeStart = Math.max(0, index - this.windowSize * 1.5);
|
||||
let bufferWindowBeforeEnd = Math.max(0, index - this.windowSize / 2);
|
||||
let bufferWindowBeforeStart = Math.max(0, index - this._windowSize * 1.5);
|
||||
let bufferWindowBeforeEnd = Math.max(0, index - this._windowSize / 2);
|
||||
this._bufferWindowBefore.positionWindow(bufferWindowBeforeStart, bufferWindowBeforeEnd - bufferWindowBeforeStart);
|
||||
|
||||
let mainWindowStart = bufferWindowBeforeEnd;
|
||||
let mainWindowEnd = Math.min(mainWindowStart + this.windowSize, this.length);
|
||||
let mainWindowEnd = Math.min(mainWindowStart + this._windowSize, this._length);
|
||||
this._window.positionWindow(mainWindowStart, mainWindowEnd - mainWindowStart);
|
||||
|
||||
let bufferWindowAfterStart = mainWindowEnd;
|
||||
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this.windowSize, this.length);
|
||||
let bufferWindowAfterEnd = Math.min(bufferWindowAfterStart + this._windowSize, this._length);
|
||||
this._bufferWindowAfter.positionWindow(bufferWindowAfterStart, bufferWindowAfterEnd - bufferWindowAfterStart);
|
||||
}
|
||||
}
|
||||
|
||||
export class AsyncDataProvider<T extends Slick.SlickData> implements IDisposableDataProvider<T> {
|
||||
export class AsyncDataProvider<TData extends IGridDataRow> implements IDisposableDataProvider<TData> {
|
||||
|
||||
constructor(public dataRows: IObservableCollection<T>) { }
|
||||
constructor(private dataRows: IObservableCollection<TData>) { }
|
||||
|
||||
public getLength(): number {
|
||||
return this.dataRows.getLength();
|
||||
return this.dataRows ? this.dataRows.getLength() : 0;
|
||||
}
|
||||
|
||||
public getItem(index: number): T {
|
||||
return this.dataRows.at(index);
|
||||
public getItem(index: number): TData {
|
||||
return !this.dataRows ? undefined : this.dataRows.at(index);
|
||||
}
|
||||
|
||||
public getRange(start: number, end: number): T[] {
|
||||
return this.dataRows.getRange(start, end);
|
||||
}
|
||||
|
||||
public set length(length: number) {
|
||||
this.dataRows.setLength(length);
|
||||
}
|
||||
|
||||
public get length(): number {
|
||||
return this.dataRows.getLength();
|
||||
public getRange(start: number, end: number): TData[] {
|
||||
return !this.dataRows ? undefined : this.dataRows.getRange(start, end);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { range } from 'vs/base/common/arrays';
|
||||
|
||||
export interface IRowNumberColumnOptions {
|
||||
numberOfRows: number;
|
||||
cssClass?: string;
|
||||
@@ -15,7 +17,7 @@ const sizePerDigit = 15;
|
||||
export class RowNumberColumn<T> implements Slick.Plugin<T> {
|
||||
private handler = new Slick.EventHandler();
|
||||
private grid: Slick.Grid<T>;
|
||||
private currentColumnWidth: number;
|
||||
|
||||
|
||||
constructor(private options: IRowNumberColumnOptions) {
|
||||
}
|
||||
@@ -50,25 +52,19 @@ export class RowNumberColumn<T> implements Slick.Plugin<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public updateRowCount(rowNum: number) {
|
||||
this.options.numberOfRows = rowNum;
|
||||
let columnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
|
||||
if (columnWidth !== this.currentColumnWidth) {
|
||||
this.grid.setColumnWidths([this.getColumnDefinition()]);
|
||||
}
|
||||
}
|
||||
|
||||
public getColumnDefinition(): Slick.Column<T> {
|
||||
// that smallest we can make it is 22 due to padding and margins in the cells
|
||||
this.currentColumnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
|
||||
let columnWidth = Math.max(this.options.numberOfRows.toString().length * sizePerDigit, 22);
|
||||
return {
|
||||
id: 'rowNumber',
|
||||
name: '',
|
||||
field: 'rowNumber',
|
||||
width: this.currentColumnWidth,
|
||||
width: columnWidth,
|
||||
minWidth: columnWidth,
|
||||
maxWidth: columnWidth,
|
||||
resizable: false,
|
||||
cssClass: this.options.cssClass,
|
||||
focusable: true,
|
||||
focusable: false,
|
||||
selectable: false,
|
||||
formatter: (r, c, v, cd, dc) => this.formatter(r, c, v, cd, dc)
|
||||
};
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as sqlops from 'sqlops';
|
||||
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
|
||||
import { IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { FirewallRuleDialog } from 'sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialog';
|
||||
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
|
||||
import { IAccountManagementService, AzureResource } from 'sql/services/accountManagement/interfaces';
|
||||
import { IResourceProviderService } from 'sql/parts/accountManagement/common/interfaces';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
|
||||
@@ -61,7 +61,7 @@ export class FirewallRuleDialogController {
|
||||
private handleOnCreateFirewallRule(): void {
|
||||
let resourceProviderId = this._resourceProviderId;
|
||||
|
||||
this._accountManagementService.getSecurityToken(this._firewallRuleDialog.viewModel.selectedAccount).then(tokenMappings => {
|
||||
this._accountManagementService.getSecurityToken(this._firewallRuleDialog.viewModel.selectedAccount, AzureResource.ResourceManagement).then(tokenMappings => {
|
||||
let firewallRuleInfo: sqlops.FirewallRuleInfo = {
|
||||
startIpAddress: this._firewallRuleDialog.viewModel.isIPAddressSelected ? this._firewallRuleDialog.viewModel.defaultIPAddress : this._firewallRuleDialog.viewModel.fromSubnetIPRange,
|
||||
endIpAddress: this._firewallRuleDialog.viewModel.isIPAddressSelected ? this._firewallRuleDialog.viewModel.defaultIPAddress : this._firewallRuleDialog.viewModel.toSubnetIPRange,
|
||||
|
||||
@@ -34,12 +34,13 @@ import { Deferred } from 'sql/base/common/promise';
|
||||
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { values } from 'sql/base/common/objects';
|
||||
import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions as ConnectionProviderExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
|
||||
import { IAccountManagementService, AzureResource } from 'sql/services/accountManagement/interfaces';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import * as platform from 'vs/platform/registry/common/platform';
|
||||
@@ -58,7 +59,6 @@ import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { EditorGroup } from 'vs/workbench/common/editor/editorGroup';
|
||||
|
||||
export class ConnectionManagementService extends Disposable implements IConnectionManagementService {
|
||||
|
||||
@@ -100,7 +100,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
@IStatusbarService private _statusBarService: IStatusbarService,
|
||||
@IResourceProviderService private _resourceProviderService: IResourceProviderService,
|
||||
@IViewletService private _viewletService: IViewletService,
|
||||
@IAngularEventingService private _angularEventing: IAngularEventingService
|
||||
@IAngularEventingService private _angularEventing: IAngularEventingService,
|
||||
@IAccountManagementService private _accountManagementService: IAccountManagementService
|
||||
) {
|
||||
super();
|
||||
if (this._instantiationService) {
|
||||
@@ -248,7 +249,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
* Load the password for the profile
|
||||
* @param connectionProfile Connection Profile
|
||||
*/
|
||||
public addSavedPassword(connectionProfile: IConnectionProfile): Promise<IConnectionProfile> {
|
||||
public async addSavedPassword(connectionProfile: IConnectionProfile): Promise<IConnectionProfile> {
|
||||
await this.fillInAzureTokenIfNeeded(connectionProfile);
|
||||
return this._connectionStore.addSavedPassword(connectionProfile).then(result => result.profile);
|
||||
}
|
||||
|
||||
@@ -274,7 +276,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
let self = this;
|
||||
return new Promise<IConnectionResult>((resolve, reject) => {
|
||||
// Load the password if it's not already loaded
|
||||
self._connectionStore.addSavedPassword(connection).then(result => {
|
||||
self._connectionStore.addSavedPassword(connection).then(async result => {
|
||||
let newConnection = result.profile;
|
||||
let foundPassword = result.savedCred;
|
||||
|
||||
@@ -286,8 +288,12 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
foundPassword = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in the Azure account token if needed and open the connection dialog if it fails
|
||||
let tokenFillSuccess = await self.fillInAzureTokenIfNeeded(newConnection);
|
||||
|
||||
// If the password is required and still not loaded show the dialog
|
||||
if (!foundPassword && self._connectionStore.isPasswordRequired(newConnection) && !newConnection.password) {
|
||||
if ((!foundPassword && self._connectionStore.isPasswordRequired(newConnection) && !newConnection.password) || !tokenFillSuccess) {
|
||||
resolve(self.showConnectionDialogOnError(connection, owner, { connected: false, errorMessage: undefined, callStack: undefined, errorCode: undefined }, options));
|
||||
} else {
|
||||
// Try to connect
|
||||
@@ -449,10 +455,14 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
showFirewallRuleOnError: true
|
||||
};
|
||||
}
|
||||
return new Promise<IConnectionResult>((resolve, reject) => {
|
||||
return new Promise<IConnectionResult>(async (resolve, reject) => {
|
||||
if (callbacks.onConnectStart) {
|
||||
callbacks.onConnectStart();
|
||||
}
|
||||
let tokenFillSuccess = await this.fillInAzureTokenIfNeeded(connection);
|
||||
if (!tokenFillSuccess) {
|
||||
throw new Error(nls.localize('connection.noAzureAccount', 'Failed to get Azure account token for connection'));
|
||||
}
|
||||
this.createNewConnection(uri, connection).then(connectionResult => {
|
||||
if (connectionResult && connectionResult.connected) {
|
||||
if (callbacks.onConnectSuccess) {
|
||||
@@ -743,8 +753,33 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
}
|
||||
}
|
||||
|
||||
private async fillInAzureTokenIfNeeded(connection: IConnectionProfile): Promise<boolean> {
|
||||
if (connection.authenticationType !== Constants.azureMFA || connection.options['azureAccountToken']) {
|
||||
return true;
|
||||
}
|
||||
let accounts = await this._accountManagementService.getAccountsForProvider('azurePublicCloud');
|
||||
if (accounts && accounts.length > 0) {
|
||||
let account = accounts.find(account => account.key.accountId === connection.userName);
|
||||
if (account) {
|
||||
if (account.isStale) {
|
||||
try {
|
||||
account = await this._accountManagementService.refreshAccount(account);
|
||||
} catch {
|
||||
// refreshAccount throws an error if the user cancels the dialog
|
||||
return false;
|
||||
}
|
||||
}
|
||||
let tokens = await this._accountManagementService.getSecurityToken(account, AzureResource.Sql);
|
||||
connection.options['azureAccountToken'] = Object.values(tokens)[0].token;
|
||||
connection.options['password'] = '';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Request Senders
|
||||
private sendConnectRequest(connection: IConnectionProfile, uri: string): Thenable<boolean> {
|
||||
private async sendConnectRequest(connection: IConnectionProfile, uri: string): Promise<boolean> {
|
||||
let connectionInfo = Object.assign({}, {
|
||||
options: connection.options
|
||||
});
|
||||
|
||||
@@ -33,4 +33,5 @@ export const passwordChars = '***************';
|
||||
/* authentication types */
|
||||
export const sqlLogin = 'SqlLogin';
|
||||
export const integrated = 'Integrated';
|
||||
export const azureMFA = 'AzureMFA';
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ import { Dropdown } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
|
||||
import { ConnectionProfile } from '../common/connectionProfile';
|
||||
import * as styler from 'sql/common/theme/styler';
|
||||
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
@@ -30,7 +32,6 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
|
||||
import { localize } from 'vs/nls';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import * as styler from 'vs/platform/theme/common/styler';
|
||||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { Builder, $ } from 'vs/base/browser/builder';
|
||||
import { MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
@@ -50,6 +51,11 @@ export class ConnectionWidget {
|
||||
private _passwordInputBox: InputBox;
|
||||
private _password: string;
|
||||
private _rememberPasswordCheckBox: Checkbox;
|
||||
private _azureAccountDropdown: SelectBox;
|
||||
private _refreshCredentialsLinkBuilder: Builder;
|
||||
private _addAzureAccountMessage: string = localize('connectionWidget.AddAzureAccount', 'Add an account...');
|
||||
private readonly _azureProviderId = 'azurePublicCloud';
|
||||
private _azureAccountList: sqlops.Account[];
|
||||
private _advancedButton: Button;
|
||||
private _callbacks: IConnectionComponentCallbacks;
|
||||
private _authTypeSelectBox: SelectBox;
|
||||
@@ -59,7 +65,7 @@ export class ConnectionWidget {
|
||||
private _focusedBeforeHandleOnConnection: HTMLElement;
|
||||
private _providerName: string;
|
||||
private _authTypeMap: { [providerName: string]: AuthenticationType[] } = {
|
||||
[Constants.mssqlProviderName]: [new AuthenticationType(Constants.integrated, false), new AuthenticationType(Constants.sqlLogin, true)]
|
||||
[Constants.mssqlProviderName]: [AuthenticationType.SqlLogin, AuthenticationType.Integrated, AuthenticationType.AzureMFA]
|
||||
};
|
||||
private _saveProfile: boolean;
|
||||
private _databaseDropdownExpanded: boolean = false;
|
||||
@@ -96,7 +102,8 @@ export class ConnectionWidget {
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||
@IClipboardService private _clipboardService: IClipboardService,
|
||||
@IConfigurationService private _configurationService: IConfigurationService
|
||||
@IConfigurationService private _configurationService: IConfigurationService,
|
||||
@IAccountManagementService private _accountManagementService: IAccountManagementService
|
||||
) {
|
||||
this._callbacks = callbacks;
|
||||
this._toDispose = [];
|
||||
@@ -109,9 +116,9 @@ export class ConnectionWidget {
|
||||
var authTypeOption = this._optionsMaps[ConnectionOptionSpecialType.authType];
|
||||
if (authTypeOption) {
|
||||
if (OS === OperatingSystem.Windows) {
|
||||
authTypeOption.defaultValue = this.getAuthTypeDisplayName(Constants.integrated);
|
||||
authTypeOption.defaultValue = this.getAuthTypeDisplayName(AuthenticationType.Integrated);
|
||||
} else {
|
||||
authTypeOption.defaultValue = this.getAuthTypeDisplayName(Constants.sqlLogin);
|
||||
authTypeOption.defaultValue = this.getAuthTypeDisplayName(AuthenticationType.SqlLogin);
|
||||
}
|
||||
this._authTypeSelectBox = new SelectBox(authTypeOption.categoryValues.map(c => c.displayName), authTypeOption.defaultValue, this._contextViewService, undefined, { ariaLabel: authTypeOption.displayName });
|
||||
}
|
||||
@@ -182,7 +189,7 @@ export class ConnectionWidget {
|
||||
// Username
|
||||
let self = this;
|
||||
let userNameOption = this._optionsMaps[ConnectionOptionSpecialType.userName];
|
||||
let userNameBuilder = DialogHelper.appendRow(this._tableContainer, userNameOption.displayName, 'connection-label', 'connection-input');
|
||||
let userNameBuilder = DialogHelper.appendRow(this._tableContainer, userNameOption.displayName, 'connection-label', 'connection-input', 'username-password-row');
|
||||
this._userNameInputBox = new InputBox(userNameBuilder.getHTMLElement(), this._contextViewService, {
|
||||
validationOptions: {
|
||||
validation: (value: string) => self.validateUsername(value, userNameOption.isRequired) ? ({ type: MessageType.ERROR, content: localize('connectionWidget.missingRequireField', '{0} is required.', userNameOption.displayName) }) : null
|
||||
@@ -191,14 +198,22 @@ export class ConnectionWidget {
|
||||
});
|
||||
// Password
|
||||
let passwordOption = this._optionsMaps[ConnectionOptionSpecialType.password];
|
||||
let passwordBuilder = DialogHelper.appendRow(this._tableContainer, passwordOption.displayName, 'connection-label', 'connection-input');
|
||||
let passwordBuilder = DialogHelper.appendRow(this._tableContainer, passwordOption.displayName, 'connection-label', 'connection-input', 'username-password-row');
|
||||
this._passwordInputBox = new InputBox(passwordBuilder.getHTMLElement(), this._contextViewService, { ariaLabel: passwordOption.displayName });
|
||||
this._passwordInputBox.inputElement.type = 'password';
|
||||
this._password = '';
|
||||
|
||||
// Remember password
|
||||
let rememberPasswordLabel = localize('rememberPassword', 'Remember password');
|
||||
this._rememberPasswordCheckBox = this.appendCheckbox(this._tableContainer, rememberPasswordLabel, 'connection-checkbox', 'connection-input', false);
|
||||
this._rememberPasswordCheckBox = this.appendCheckbox(this._tableContainer, rememberPasswordLabel, 'connection-checkbox', 'connection-input', 'username-password-row', false);
|
||||
|
||||
// Azure account picker
|
||||
let accountLabel = localize('connection.azureAccountDropdownLabel', 'Account');
|
||||
let accountDropdownBuilder = DialogHelper.appendRow(this._tableContainer, accountLabel, 'connection-label', 'connection-input', 'azure-account-row');
|
||||
this._azureAccountDropdown = new SelectBox([], undefined, this._contextViewService, accountDropdownBuilder.getContainer(), { ariaLabel: accountLabel });
|
||||
DialogHelper.appendInputSelectBox(accountDropdownBuilder, this._azureAccountDropdown);
|
||||
let refreshCredentialsBuilder = DialogHelper.appendRow(this._tableContainer, '', 'connection-label', 'connection-input', 'azure-account-row refresh-credentials-link');
|
||||
this._refreshCredentialsLinkBuilder = refreshCredentialsBuilder.a({ href: '#' }).text(localize('connectionWidget.refreshAzureCredentials', 'Refresh account credentials'));
|
||||
|
||||
// Database
|
||||
let databaseOption = this._optionsMaps[ConnectionOptionSpecialType.databaseName];
|
||||
@@ -228,7 +243,7 @@ export class ConnectionWidget {
|
||||
|
||||
private validateUsername(value: string, isOptionRequired: boolean): boolean {
|
||||
let currentAuthType = this._authTypeSelectBox ? this.getMatchingAuthType(this._authTypeSelectBox.value) : undefined;
|
||||
if (!currentAuthType || currentAuthType.showUsernameAndPassword) {
|
||||
if (!currentAuthType || currentAuthType === AuthenticationType.SqlLogin) {
|
||||
if (!value && isOptionRequired) {
|
||||
return true;
|
||||
}
|
||||
@@ -254,9 +269,9 @@ export class ConnectionWidget {
|
||||
return button;
|
||||
}
|
||||
|
||||
private appendCheckbox(container: Builder, label: string, checkboxClass: string, cellContainerClass: string, isChecked: boolean): Checkbox {
|
||||
private appendCheckbox(container: Builder, label: string, checkboxClass: string, cellContainerClass: string, rowContainerClass: string, isChecked: boolean): Checkbox {
|
||||
let checkbox: Checkbox;
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
container.element('tr', { class: rowContainerClass }, (rowContainer) => {
|
||||
rowContainer.element('td');
|
||||
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
|
||||
checkbox = new Checkbox(inputCellContainer.getHTMLElement(), { label, checked: isChecked, ariaLabel: label });
|
||||
@@ -275,6 +290,7 @@ export class ConnectionWidget {
|
||||
this._toDispose.push(styler.attachSelectBoxStyler(this._serverGroupSelectBox, this._themeService));
|
||||
this._toDispose.push(attachButtonStyler(this._advancedButton, this._themeService));
|
||||
this._toDispose.push(attachCheckboxStyler(this._rememberPasswordCheckBox, this._themeService));
|
||||
this._toDispose.push(styler.attachSelectBoxStyler(this._azureAccountDropdown, this._themeService));
|
||||
|
||||
if (this._authTypeSelectBox) {
|
||||
// Theme styler
|
||||
@@ -285,6 +301,23 @@ export class ConnectionWidget {
|
||||
}));
|
||||
}
|
||||
|
||||
if (this._azureAccountDropdown) {
|
||||
this._toDispose.push(styler.attachSelectBoxStyler(this._azureAccountDropdown, this._themeService));
|
||||
this._toDispose.push(this._azureAccountDropdown.onDidSelect(() => {
|
||||
this.onAzureAccountSelected();
|
||||
}));
|
||||
}
|
||||
|
||||
if (this._refreshCredentialsLinkBuilder) {
|
||||
this._toDispose.push(this._refreshCredentialsLinkBuilder.on(DOM.EventType.CLICK, async () => {
|
||||
let account = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
|
||||
if (account) {
|
||||
await this._accountManagementService.refreshAccount(account);
|
||||
this.fillInAzureAccountOptions();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
this._toDispose.push(this._serverGroupSelectBox.onDidSelect(selectedGroup => {
|
||||
this.onGroupSelected(selectedGroup.selected);
|
||||
}));
|
||||
@@ -342,7 +375,7 @@ export class ConnectionWidget {
|
||||
private setConnectButton(): void {
|
||||
let showUsernameAndPassword: boolean = true;
|
||||
if (this.authType) {
|
||||
showUsernameAndPassword = this.authType.showUsernameAndPassword;
|
||||
showUsernameAndPassword = this.authType === AuthenticationType.SqlLogin;
|
||||
}
|
||||
showUsernameAndPassword ? this._callbacks.onSetConnectButton(!!this.serverName && !!this.userName) :
|
||||
this._callbacks.onSetConnectButton(!!this.serverName);
|
||||
@@ -350,7 +383,7 @@ export class ConnectionWidget {
|
||||
|
||||
private onAuthTypeSelected(selectedAuthType: string) {
|
||||
let currentAuthType = this.getMatchingAuthType(selectedAuthType);
|
||||
if (!currentAuthType.showUsernameAndPassword) {
|
||||
if (currentAuthType !== AuthenticationType.SqlLogin) {
|
||||
this._userNameInputBox.disable();
|
||||
this._passwordInputBox.disable();
|
||||
this._userNameInputBox.hideMessage();
|
||||
@@ -366,6 +399,68 @@ export class ConnectionWidget {
|
||||
this._passwordInputBox.enable();
|
||||
this._rememberPasswordCheckBox.enabled = true;
|
||||
}
|
||||
|
||||
if (currentAuthType === AuthenticationType.AzureMFA) {
|
||||
this.fillInAzureAccountOptions();
|
||||
this._azureAccountDropdown.enable();
|
||||
let tableContainer = this._tableContainer.getContainer();
|
||||
tableContainer.classList.add('hide-username-password');
|
||||
tableContainer.classList.remove('hide-azure-accounts');
|
||||
} else {
|
||||
this._azureAccountDropdown.disable();
|
||||
let tableContainer = this._tableContainer.getContainer();
|
||||
tableContainer.classList.remove('hide-username-password');
|
||||
tableContainer.classList.add('hide-azure-accounts');
|
||||
this._azureAccountDropdown.hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private async fillInAzureAccountOptions(): Promise<void> {
|
||||
let oldSelection = this._azureAccountDropdown.value;
|
||||
this._azureAccountList = await this._accountManagementService.getAccountsForProvider(this._azureProviderId);
|
||||
let accountDropdownOptions = this._azureAccountList.map(account => account.key.accountId);
|
||||
if (accountDropdownOptions.length === 0) {
|
||||
// If there are no accounts add a blank option so that add account isn't automatically selected
|
||||
accountDropdownOptions.unshift('');
|
||||
}
|
||||
accountDropdownOptions.push(this._addAzureAccountMessage);
|
||||
this._azureAccountDropdown.setOptions(accountDropdownOptions);
|
||||
this._azureAccountDropdown.selectWithOptionName(oldSelection);
|
||||
this.updateRefreshCredentialsLink();
|
||||
}
|
||||
|
||||
private async updateRefreshCredentialsLink(): Promise<void> {
|
||||
let chosenAccount = this._azureAccountList.find(account => account.key.accountId === this._azureAccountDropdown.value);
|
||||
if (chosenAccount && chosenAccount.isStale) {
|
||||
this._tableContainer.getContainer().classList.remove('hide-refresh-link');
|
||||
} else {
|
||||
this._tableContainer.getContainer().classList.add('hide-refresh-link');
|
||||
}
|
||||
}
|
||||
|
||||
private async onAzureAccountSelected(): Promise<void> {
|
||||
// Reset the dropdown's validation message if the old selection was not valid but the new one is
|
||||
this.validateAzureAccountSelection(false);
|
||||
this._refreshCredentialsLinkBuilder.display('none');
|
||||
|
||||
// Open the add account dialog if needed, then select the added account
|
||||
if (this._azureAccountDropdown.value === this._addAzureAccountMessage) {
|
||||
let oldAccountIds = this._azureAccountList.map(account => account.key.accountId);
|
||||
await this._accountManagementService.addAccount(this._azureProviderId);
|
||||
|
||||
// Refresh the dropdown's list to include the added account
|
||||
await this.fillInAzureAccountOptions();
|
||||
|
||||
// If a new account was added find it and select it, otherwise select the first account
|
||||
let newAccount = this._azureAccountList.find(option => !oldAccountIds.some(oldId => oldId === option.key.accountId));
|
||||
if (newAccount) {
|
||||
this._azureAccountDropdown.selectWithOptionName(newAccount.key.accountId);
|
||||
} else {
|
||||
this._azureAccountDropdown.select(0);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateRefreshCredentialsLink();
|
||||
}
|
||||
|
||||
private serverNameChanged(serverName: string) {
|
||||
@@ -407,6 +502,7 @@ export class ConnectionWidget {
|
||||
private clearValidationMessages(): void {
|
||||
this._serverNameInputBox.hideMessage();
|
||||
this._userNameInputBox.hideMessage();
|
||||
this._azureAccountDropdown.hideMessage();
|
||||
}
|
||||
|
||||
private getModelValue(value: string): string {
|
||||
@@ -449,8 +545,8 @@ export class ConnectionWidget {
|
||||
|
||||
if (this._authTypeSelectBox) {
|
||||
this.onAuthTypeSelected(this._authTypeSelectBox.value);
|
||||
|
||||
}
|
||||
|
||||
// Disable connect button if -
|
||||
// 1. Authentication type is SQL Login and no username is provided
|
||||
// 2. No server name is provided
|
||||
@@ -513,7 +609,7 @@ export class ConnectionWidget {
|
||||
currentAuthType = this.getMatchingAuthType(this._authTypeSelectBox.value);
|
||||
}
|
||||
|
||||
if (!currentAuthType || currentAuthType.showUsernameAndPassword) {
|
||||
if (!currentAuthType || currentAuthType === AuthenticationType.SqlLogin) {
|
||||
this._userNameInputBox.enable();
|
||||
this._passwordInputBox.enable();
|
||||
this._rememberPasswordCheckBox.enabled = true;
|
||||
@@ -537,7 +633,7 @@ export class ConnectionWidget {
|
||||
}
|
||||
|
||||
public get userName(): string {
|
||||
return this._userNameInputBox.value;
|
||||
return this.authenticationType === AuthenticationType.AzureMFA ? this._azureAccountDropdown.value : this._userNameInputBox.value;
|
||||
}
|
||||
|
||||
public get password(): string {
|
||||
@@ -548,6 +644,27 @@ export class ConnectionWidget {
|
||||
return this._authTypeSelectBox ? this.getAuthTypeName(this._authTypeSelectBox.value) : undefined;
|
||||
}
|
||||
|
||||
private validateAzureAccountSelection(showMessage: boolean = true): boolean {
|
||||
if (this.authType !== AuthenticationType.AzureMFA) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let selected = this._azureAccountDropdown.value;
|
||||
if (selected === '' || selected === this._addAzureAccountMessage) {
|
||||
if (showMessage) {
|
||||
this._azureAccountDropdown.showMessage({
|
||||
content: localize('connectionWidget.invalidAzureAccount', 'You must select an account'),
|
||||
type: MessageType.ERROR
|
||||
});
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
this._azureAccountDropdown.hideMessage();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private validateInputs(): boolean {
|
||||
let isFocused = false;
|
||||
let validateServerName = this._serverNameInputBox.validate();
|
||||
@@ -565,7 +682,12 @@ export class ConnectionWidget {
|
||||
this._passwordInputBox.focus();
|
||||
isFocused = true;
|
||||
}
|
||||
return validateServerName && validateUserName && validatePassword;
|
||||
let validateAzureAccount = this.validateAzureAccountSelection();
|
||||
if (!validateAzureAccount && !isFocused) {
|
||||
this._azureAccountDropdown.focus();
|
||||
isFocused = true;
|
||||
}
|
||||
return validateServerName && validateUserName && validatePassword && validateAzureAccount;
|
||||
}
|
||||
|
||||
public connect(model: IConnectionProfile): boolean {
|
||||
@@ -613,7 +735,7 @@ export class ConnectionWidget {
|
||||
|
||||
private getMatchingAuthType(displayName: string): AuthenticationType {
|
||||
const authType = this._authTypeMap[this._providerName];
|
||||
return authType ? authType.find(authType => this.getAuthTypeDisplayName(authType.name) === displayName) : undefined;
|
||||
return authType ? authType.find(authType => this.getAuthTypeDisplayName(authType) === displayName) : undefined;
|
||||
}
|
||||
|
||||
public closeDatabaseDropdown(): void {
|
||||
@@ -634,18 +756,14 @@ export class ConnectionWidget {
|
||||
}
|
||||
|
||||
private focusPasswordIfNeeded(): void {
|
||||
if (this.authType && this.authType.showUsernameAndPassword && this.userName && !this.password) {
|
||||
if (this.authType && this.authType === AuthenticationType.SqlLogin && this.userName && !this.password) {
|
||||
this._passwordInputBox.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuthenticationType {
|
||||
public name: string;
|
||||
public showUsernameAndPassword: boolean;
|
||||
|
||||
constructor(name: string, showUsernameAndPassword: boolean) {
|
||||
this.name = name;
|
||||
this.showUsernameAndPassword = showUsernameAndPassword;
|
||||
}
|
||||
enum AuthenticationType {
|
||||
SqlLogin = 'SqlLogin',
|
||||
Integrated = 'Integrated',
|
||||
AzureMFA = 'AzureMFA'
|
||||
}
|
||||
@@ -28,11 +28,12 @@
|
||||
overflow: hidden;
|
||||
margin: 0px 11px;
|
||||
}
|
||||
|
||||
.connection-dialog .tabBody {
|
||||
overflow: hidden;
|
||||
flex: 1 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.connection-recent, .connection-saved {
|
||||
@@ -114,4 +115,16 @@
|
||||
margin: 5px 0px;
|
||||
padding: 5px 15px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.hide-azure-accounts .azure-account-row {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-username-password .username-password-row {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hide-refresh-link .azure-account-row.refresh-credentials-link {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
|
||||
export class DBCellValue {
|
||||
@@ -40,24 +41,20 @@ export function hyperLinkFormatter(row: number, cell: any, value: any, columnDef
|
||||
*/
|
||||
export function textFormatter(row: number, cell: any, value: any, columnDef: any, dataContext: any): string {
|
||||
let cellClasses = 'grid-cell-value-container';
|
||||
let valueToDisplay = '';
|
||||
let titleValue = '';
|
||||
let valueToDisplay: string = '';
|
||||
|
||||
if (DBCellValue.isDBCellValue(value)) {
|
||||
valueToDisplay = 'NULL';
|
||||
if (!value.isNull) {
|
||||
valueToDisplay = value.displayValue.replace(/(\r\n|\n|\r)/g, ' ');
|
||||
valueToDisplay = escape(valueToDisplay.length > 250 ? valueToDisplay.slice(0, 250) + '...' : valueToDisplay);
|
||||
titleValue = value.displayValue;
|
||||
valueToDisplay = escape(value.displayValue.replace(/(\r\n|\n|\r)/g, ' '));
|
||||
} else {
|
||||
cellClasses += ' missing-value';
|
||||
}
|
||||
} else if (typeof value === 'string') {
|
||||
valueToDisplay = escape(value.length > 250 ? value.slice(0, 250) + '...' : value);
|
||||
titleValue = value;
|
||||
valueToDisplay = escape(value);
|
||||
}
|
||||
|
||||
return `<span title="${titleValue}" class="${cellClasses}">${valueToDisplay}</span>`;
|
||||
return `<span title="${valueToDisplay}" class="${cellClasses}">${valueToDisplay}</span>`;
|
||||
}
|
||||
|
||||
/** The following code is a rewrite over the both formatter function using dom builder
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./code';
|
||||
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Output, EventEmitter } from '@angular/core';
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
||||
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
@@ -29,7 +29,7 @@ import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { ICellModel } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { RunCellAction, DeleteCellAction, AddCellAction, CellContext } from 'sql/parts/notebook/cellViews/codeActions';
|
||||
import { RunCellAction, CellContext, NotebookCellToggleMoreActon } from 'sql/parts/notebook/cellViews/codeActions';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
|
||||
import { CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||
@@ -41,11 +41,12 @@ export const CODE_SELECTOR: string = 'code-component';
|
||||
selector: CODE_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./code.component.html'))
|
||||
})
|
||||
export class CodeComponent extends AngularDisposable implements OnInit {
|
||||
export class CodeComponent extends AngularDisposable implements OnInit, OnChanges {
|
||||
@ViewChild('toolbar', { read: ElementRef }) private toolbarElement: ElementRef;
|
||||
@ViewChild('moreactions', { read: ElementRef }) private moreactionsElement: ElementRef;
|
||||
@ViewChild('moreactions', { read: ElementRef }) private moreActionsElementRef: ElementRef;
|
||||
@ViewChild('editor', { read: ElementRef }) private codeElement: ElementRef;
|
||||
@Input() cellModel: ICellModel;
|
||||
@Input() hideVerticalToolbar: boolean = false;
|
||||
|
||||
@Output() public onContentChanged = new EventEmitter<void>();
|
||||
|
||||
@@ -53,14 +54,19 @@ export class CodeComponent extends AngularDisposable implements OnInit {
|
||||
this._model = value;
|
||||
}
|
||||
|
||||
@Input() set activeCellId(value: string) {
|
||||
this._activeCellId = value;
|
||||
}
|
||||
|
||||
protected _actionBar: Taskbar;
|
||||
protected _moreActions: ActionBar;
|
||||
private readonly _minimumHeight = 30;
|
||||
private _editor: QueryTextEditor;
|
||||
private _editorInput: UntitledEditorInput;
|
||||
private _editorModel: ITextModel;
|
||||
private _uri: string;
|
||||
private _model: NotebookModel;
|
||||
private _activeCellId: string;
|
||||
private _toggleMoreActions: NotebookCellToggleMoreActon;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
|
||||
@@ -79,12 +85,26 @@ export class CodeComponent extends AngularDisposable implements OnInit {
|
||||
ngOnInit() {
|
||||
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
|
||||
this.updateTheme(this.themeService.getColorTheme());
|
||||
this.initActionBar();
|
||||
if (!this.hideVerticalToolbar) {
|
||||
this.initActionBar();
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
ngAfterViewInit() {
|
||||
this._toggleMoreActions = new NotebookCellToggleMoreActon(
|
||||
this._instantiationService,
|
||||
this.contextMenuService,
|
||||
this.notificationService,
|
||||
this.moreActionsElementRef,
|
||||
this.model);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||
this.updateLanguageMode();
|
||||
this.updateModel();
|
||||
if (this._toggleMoreActions) {
|
||||
this._toggleMoreActions.onChange(this.cellModel, changes);
|
||||
}
|
||||
}
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
@@ -98,6 +118,10 @@ export class CodeComponent extends AngularDisposable implements OnInit {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
get activeCellId(): string {
|
||||
return this._activeCellId;
|
||||
}
|
||||
|
||||
private createEditor(): void {
|
||||
let instantiationService = this._instantiationService.createChild(new ServiceCollection([IProgressService, new SimpleProgressService()]));
|
||||
this._editor = instantiationService.createInstance(QueryTextEditor);
|
||||
@@ -139,21 +163,9 @@ export class CodeComponent extends AngularDisposable implements OnInit {
|
||||
this._actionBar.setContent([
|
||||
{ action: runCellAction }
|
||||
]);
|
||||
|
||||
let moreActionsElement = <HTMLElement>this.moreactionsElement.nativeElement;
|
||||
this._moreActions = new ActionBar(moreActionsElement, { orientation: ActionsOrientation.VERTICAL });
|
||||
this._moreActions.context = { target: moreActionsElement };
|
||||
|
||||
let actions: Action[] = [];
|
||||
actions.push(this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false));
|
||||
actions.push(this._instantiationService.createInstance(AddCellAction, 'codeAfter', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true));
|
||||
actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false));
|
||||
actions.push(this._instantiationService.createInstance(AddCellAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true));
|
||||
actions.push(this._instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete')));
|
||||
|
||||
this._moreActions.push(this._instantiationService.createInstance(ToggleMoreWidgetAction, actions, context), { icon: true, label: false });
|
||||
}
|
||||
|
||||
|
||||
private createUri(): URI {
|
||||
let uri = URI.from({ scheme: Schemas.untitled, path: `notebook-editor-${this.cellModel.id}` });
|
||||
// Use this to set the internal (immutable) and public (shared with extension) uri properties
|
||||
@@ -180,7 +192,7 @@ export class CodeComponent extends AngularDisposable implements OnInit {
|
||||
let toolbarEl = <HTMLElement>this.toolbarElement.nativeElement;
|
||||
toolbarEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
||||
|
||||
let moreactionsEl = <HTMLElement>this.moreactionsElement.nativeElement;
|
||||
let moreactionsEl = <HTMLElement>this.moreActionsElementRef.nativeElement;
|
||||
moreactionsEl.style.borderRightColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ code-component {
|
||||
|
||||
code-component .toolbar {
|
||||
border-right-width: 1px;
|
||||
border-right-style: solid;
|
||||
}
|
||||
|
||||
code-component .toolbarIconRun {
|
||||
|
||||
@@ -5,13 +5,20 @@
|
||||
|
||||
import { nb } from 'sqlops';
|
||||
|
||||
import { ElementRef, SimpleChange } from '@angular/core';
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { CellType } from 'sql/parts/notebook/models/contracts';
|
||||
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ToggleMoreWidgetAction } from 'sql/parts/dashboard/common/actions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
|
||||
import { CellType, CellTypes } from 'sql/parts/notebook/models/contracts';
|
||||
import { NotebookModel } from 'sql/parts/notebook/models/notebookModel';
|
||||
import { getErrorMessage } from 'sql/parts/notebook/notebookUtils';
|
||||
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||
import { ICellModel, FutureInternal } from 'sql/parts/notebook/models/modelInterfaces';
|
||||
import { ToggleableAction } from 'sql/parts/notebook/notebookActions';
|
||||
|
||||
@@ -173,7 +180,7 @@ export class DeleteCellAction extends CellActionBase {
|
||||
|
||||
runCellAction(context: CellContext): Promise<void> {
|
||||
try {
|
||||
context.model.deleteCell(context.cell);
|
||||
context.model.deleteCell(context.cell);
|
||||
} catch (error) {
|
||||
let message = getErrorMessage(error);
|
||||
|
||||
@@ -184,4 +191,51 @@ export class DeleteCellAction extends CellActionBase {
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookCellToggleMoreActon {
|
||||
private _actions: Action[] = [];
|
||||
private _moreActions: ActionBar;
|
||||
|
||||
constructor (
|
||||
private _instantiationService: IInstantiationService,
|
||||
private contextMenuService: IContextMenuService,
|
||||
private notificationService: INotificationService,
|
||||
private moreActionElementRef: ElementRef,
|
||||
private model: NotebookModel
|
||||
) {
|
||||
this._actions.push(
|
||||
this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeBefore', 'Insert Code before'), CellTypes.Code, false, this.notificationService),
|
||||
this._instantiationService.createInstance(AddCellAction, 'codeBefore', localize('codeAfter', 'Insert Code after'), CellTypes.Code, true, this.notificationService),
|
||||
this._instantiationService.createInstance(AddCellAction, 'markdownBefore', localize('markdownBefore', 'Insert Markdown before'), CellTypes.Markdown, false, this.notificationService),
|
||||
this._instantiationService.createInstance(AddCellAction, 'markdownAfter', localize('markdownAfter', 'Insert Markdown after'), CellTypes.Markdown, true, this.notificationService),
|
||||
this._instantiationService.createInstance(DeleteCellAction, 'delete', localize('delete', 'Delete'), this.notificationService)
|
||||
);
|
||||
let moreActionsElement = <HTMLElement>this.moreActionElementRef.nativeElement;
|
||||
this._moreActions = new ActionBar(moreActionsElement, { orientation: ActionsOrientation.VERTICAL });
|
||||
this._moreActions.context = { target: moreActionsElement };
|
||||
}
|
||||
|
||||
toggle(showIcon: boolean): void {
|
||||
if (showIcon) {
|
||||
this._moreActions.push(this._instantiationService.createInstance(ToggleMoreWidgetAction, this._actions, this.model, this.contextMenuService), { icon: showIcon, label: false });
|
||||
} else if (this._moreActions) {
|
||||
this._moreActions.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public onChange(cellModel: ICellModel, changes: { [propKey: string]: SimpleChange }): void {
|
||||
for (let propName in changes) {
|
||||
if (propName === 'activeCellId') {
|
||||
let changedProp = changes[propName];
|
||||
if (cellModel.id === changedProp.currentValue) {
|
||||
this.toggle(true);
|
||||
}
|
||||
else {
|
||||
this.toggle(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
-->
|
||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
||||
<div class="notebook-code" style="flex: 0 0 auto;">
|
||||
<code-component [cellModel]="cellModel" [model]="model"></code-component>
|
||||
<code-component [cellModel]="cellModel" [model]="model" [activeCellId]="activeCellId"></code-component>
|
||||
</div>
|
||||
<div #codeCellOutput class="notebook-output" style="flex: 0 0 auto;">
|
||||
<output-area-component *ngIf="cellModel.outputs && cellModel.outputs.length > 0" [cellModel]="cellModel">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./codeCell';
|
||||
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, SimpleChange, OnChanges } from '@angular/core';
|
||||
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
@@ -21,13 +21,19 @@ export const CODE_SELECTOR: string = 'code-cell-component';
|
||||
selector: CODE_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./codeCell.component.html'))
|
||||
})
|
||||
export class CodeCellComponent extends CellView implements OnInit {
|
||||
export class CodeCellComponent extends CellView implements OnInit, OnChanges {
|
||||
@ViewChild('codeCellOutput', { read: ElementRef }) private outputPreview: ElementRef;
|
||||
private _model: NotebookModel;
|
||||
|
||||
@Input() cellModel: ICellModel;
|
||||
@Input() set model(value: NotebookModel) {
|
||||
this._model = value;
|
||||
}
|
||||
@Input() set activeCellId(value: string) {
|
||||
this._activeCellId = value;
|
||||
}
|
||||
|
||||
private _model: NotebookModel;
|
||||
private _activeCellId: string;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
|
||||
@@ -46,6 +52,24 @@ export class CodeCellComponent extends CellView implements OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||
for (let propName in changes) {
|
||||
if (propName === 'activeCellId') {
|
||||
let changedProp = changes[propName];
|
||||
this._activeCellId = changedProp.currentValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get model(): NotebookModel {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
get activeCellId(): string {
|
||||
return this._activeCellId;
|
||||
}
|
||||
|
||||
// Todo: implement layout
|
||||
public layout() {
|
||||
|
||||
@@ -56,8 +80,4 @@ export class CodeCellComponent extends CellView implements OnInit {
|
||||
outputElement.style.borderTopColor = theme.getColor(themeColors.SIDE_BAR_BACKGROUND, true).toString();
|
||||
}
|
||||
|
||||
get model(): NotebookModel {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
-->
|
||||
<div style="overflow: hidden; width: 100%; height: 100%; display: flex; flex-flow: column">
|
||||
<div class="notebook-text" style="flex: 0 0 auto;">
|
||||
<code-component *ngIf="isEditMode" [cellModel]="cellModel" (onContentChanged)="handleContentChanged()"></code-component>
|
||||
<code-component *ngIf="isEditMode" [cellModel]="cellModel" (onContentChanged)="handleContentChanged()" [activeCellId]="activeCellId" [hideVerticalToolbar]=1></code-component>
|
||||
</div>
|
||||
<div #preview class="notebook-preview" style="flex: 0 0 auto;" (dblclick)="toggleEditMode()">
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./textCell';
|
||||
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { OnInit, Component, Input, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, OnChanges, SimpleChange } from '@angular/core';
|
||||
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { CellView } from 'sql/parts/notebook/cellViews/interfaces';
|
||||
@@ -22,12 +22,16 @@ export const TEXT_SELECTOR: string = 'text-cell-component';
|
||||
selector: TEXT_SELECTOR,
|
||||
templateUrl: decodeURI(require.toUrl('./textCell.component.html'))
|
||||
})
|
||||
export class TextCellComponent extends CellView implements OnInit {
|
||||
export class TextCellComponent extends CellView implements OnInit, OnChanges {
|
||||
@ViewChild('preview', { read: ElementRef }) private output: ElementRef;
|
||||
@Input() cellModel: ICellModel;
|
||||
@Input() set activeCellId(value: string) {
|
||||
this._activeCellId = value;
|
||||
}
|
||||
private _content: string;
|
||||
private isEditMode: boolean;
|
||||
private _sanitizer: ISanitizer;
|
||||
private _activeCellId: string;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => CommonServiceInterface)) private _bootstrapService: CommonServiceInterface,
|
||||
@@ -39,8 +43,15 @@ export class TextCellComponent extends CellView implements OnInit {
|
||||
this.isEditMode = false;
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||
this.updatePreview();
|
||||
for (let propName in changes) {
|
||||
if (propName === 'activeCellId') {
|
||||
let changedProp = changes[propName];
|
||||
this._activeCellId = changedProp.currentValue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Gets sanitizer from ISanitizer interface
|
||||
@@ -51,6 +62,10 @@ export class TextCellComponent extends CellView implements OnInit {
|
||||
return this._sanitizer = defaultSanitizer;
|
||||
}
|
||||
|
||||
get activeCellId(): string {
|
||||
return this._activeCellId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the preview of markdown component with latest changes
|
||||
* If content is empty and in non-edit mode, default it to 'Double-click to edit'
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
<div class="scrollable" style="flex: 1 1 auto; position: relative">
|
||||
<loading-spinner [loading]="isLoading"></loading-spinner>
|
||||
<div class="notebook-cell" *ngFor="let cell of cells" (click)="selectCell(cell)" [class.active]="cell.active" (keydown)="onKeyDown($event)">
|
||||
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model">
|
||||
<code-cell-component *ngIf="cell.cellType === 'code'" [cellModel]="cell" [model]="model" [activeCellId]="activeCellId">
|
||||
</code-cell-component>
|
||||
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell">
|
||||
<text-cell-component *ngIf="cell.cellType === 'markdown'" [cellModel]="cell" [activeCellId]="activeCellId">
|
||||
</text-cell-component>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,6 +54,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
||||
private _modelRegisteredDeferred = new Deferred<NotebookModel>();
|
||||
private profile: IConnectionProfile;
|
||||
private _trustedAction: TrustedAction;
|
||||
private _activeCellId: string;
|
||||
|
||||
|
||||
constructor(
|
||||
@@ -85,6 +86,10 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
||||
return this._model;
|
||||
}
|
||||
|
||||
public get activeCellId(): string {
|
||||
return this._activeCellId;
|
||||
}
|
||||
|
||||
public get modelRegistered(): Promise<NotebookModel> {
|
||||
return this._modelRegisteredDeferred.promise;
|
||||
}
|
||||
@@ -106,6 +111,7 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
|
||||
this._activeCell = cell;
|
||||
this._activeCell.active = true;
|
||||
this._model.activeCell = this._activeCell;
|
||||
this._activeCellId = cell.id;
|
||||
this._changeRef.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
return this._activeObjectExplorerNodes[connection.id];
|
||||
}
|
||||
|
||||
public createNewSession(providerId: string, connection: ConnectionProfile): Thenable<sqlops.ObjectExplorerSessionResponse> {
|
||||
public async createNewSession(providerId: string, connection: ConnectionProfile): Promise<sqlops.ObjectExplorerSessionResponse> {
|
||||
let self = this;
|
||||
return new Promise<sqlops.ObjectExplorerSessionResponse>((resolve, reject) => {
|
||||
let provider = this._providers[providerId];
|
||||
|
||||
@@ -29,7 +29,7 @@ export interface IQueryManagementService {
|
||||
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
|
||||
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
|
||||
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
|
||||
parseSyntax(ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult>;
|
||||
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>;
|
||||
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
|
||||
disposeQuery(ownerUri: string): Thenable<void>;
|
||||
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
|
||||
@@ -38,8 +38,7 @@ export interface IQueryManagementService {
|
||||
onQueryComplete(result: sqlops.QueryExecuteCompleteNotificationResult): void;
|
||||
onBatchStart(batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
|
||||
onBatchComplete(batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
|
||||
onResultSetAvailable(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
|
||||
onResultSetUpdated(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
|
||||
onResultSetComplete(resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void;
|
||||
onMessage(message: sqlops.QueryExecuteMessageParams): void;
|
||||
|
||||
// Edit Data Callbacks
|
||||
@@ -66,7 +65,7 @@ export interface IQueryRequestHandler {
|
||||
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
|
||||
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
|
||||
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
|
||||
parseSyntax(ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult>;
|
||||
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>;
|
||||
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
|
||||
disposeQuery(ownerUri: string): Thenable<void>;
|
||||
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
|
||||
@@ -245,15 +244,9 @@ export class QueryManagementService implements IQueryManagementService {
|
||||
});
|
||||
}
|
||||
|
||||
public onResultSetAvailable(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
|
||||
public onResultSetComplete(resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void {
|
||||
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleResultSetAvailable(resultSetInfo);
|
||||
});
|
||||
}
|
||||
|
||||
public onResultSetUpdated(resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
|
||||
this._notify(resultSetInfo.ownerUri, (runner: QueryRunner) => {
|
||||
runner.handleResultSetUpdated(resultSetInfo);
|
||||
runner.handleResultSetComplete(resultSetInfo);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as pretty from 'pretty-data';
|
||||
|
||||
import { attachTableStyler } from 'sql/common/theme/styler';
|
||||
import QueryRunner from 'sql/parts/query/execution/queryRunner';
|
||||
import { VirtualizedCollection, AsyncDataProvider } from 'sql/base/browser/ui/table/asyncDataView';
|
||||
@@ -14,7 +12,7 @@ import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scr
|
||||
import { MouseWheelSupport } from 'sql/base/browser/ui/table/plugins/mousewheelTableScroll.plugin';
|
||||
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
|
||||
import { SaveFormat } from 'sql/parts/grid/common/interfaces';
|
||||
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction } from 'sql/parts/query/editor/actions';
|
||||
import { IGridActionContext, SaveResultAction, CopyResultAction, SelectAllGridAction, MaximizeTableAction, RestoreTableAction, ChartDataAction, ShowQueryPlanAction } from 'sql/parts/query/editor/actions';
|
||||
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
|
||||
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
|
||||
import { escape } from 'sql/base/common/strings';
|
||||
@@ -22,9 +20,9 @@ import { hyperLinkFormatter, textFormatter } from 'sql/parts/grid/services/share
|
||||
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
|
||||
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
|
||||
import { ITableStyles, ITableMouseEvent } from 'sql/base/browser/ui/table/interfaces';
|
||||
import { warn } from 'sql/base/common/log';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as pretty from 'pretty-data';
|
||||
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -40,7 +38,7 @@ import { $ } from 'vs/base/browser/builder';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { Separator, ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { isInDOM } from 'vs/base/browser/dom';
|
||||
import { Dimension, getContentWidth, isInDOM } from 'vs/base/browser/dom';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
@@ -177,8 +175,7 @@ export class GridPanel extends ViewletPanel {
|
||||
this.reset();
|
||||
this.queryRunnerDisposables = [];
|
||||
this.runner = runner;
|
||||
this.queryRunnerDisposables.push(this.runner.onResultSet(this.onResultSet, this));
|
||||
this.queryRunnerDisposables.push(this.runner.onResultSetUpdate(this.updateResultSet, this));
|
||||
this.queryRunnerDisposables.push(this.runner.onResultSet(e => this.onResultSet(e)));
|
||||
this.queryRunnerDisposables.push(this.runner.onQueryStart(() => {
|
||||
if (this.state) {
|
||||
this.state.tableStates = [];
|
||||
@@ -203,33 +200,6 @@ export class GridPanel extends ViewletPanel {
|
||||
}
|
||||
}
|
||||
|
||||
private updateResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
|
||||
|
||||
let resultsToUpdate: sqlops.ResultSetSummary[];
|
||||
if (!Array.isArray(resultSet)) {
|
||||
resultsToUpdate = [resultSet];
|
||||
} else {
|
||||
resultsToUpdate = resultSet;
|
||||
}
|
||||
|
||||
for (let set of resultsToUpdate) {
|
||||
let table = this.tables.find(t => t.resultSet.batchId === set.batchId && t.resultSet.id === set.id);
|
||||
if (table) {
|
||||
table.updateResult(set);
|
||||
} else {
|
||||
warn('Got result set update request for non-existant table');
|
||||
}
|
||||
}
|
||||
|
||||
this.maximumBodySize = this.tables.reduce((p, c) => {
|
||||
return p + c.maximumSize;
|
||||
}, 0);
|
||||
|
||||
if (this.state && this.state.scrollPosition) {
|
||||
this.splitView.setScrollPosition(this.state.scrollPosition);
|
||||
}
|
||||
}
|
||||
|
||||
private addResultSet(resultSet: sqlops.ResultSetSummary | sqlops.ResultSetSummary[]) {
|
||||
let resultsToAdd: sqlops.ResultSetSummary[];
|
||||
if (!Array.isArray(resultSet)) {
|
||||
@@ -353,12 +323,9 @@ class GridTable<T> extends Disposable implements IView {
|
||||
private selectionModel = new CellSelectionModel();
|
||||
private styles: ITableStyles;
|
||||
private currentHeight: number;
|
||||
private dataProvider: AsyncDataProvider<T>;
|
||||
|
||||
private columns: Slick.Column<T>[];
|
||||
|
||||
private rowNumberColumn: RowNumberColumn<T>;
|
||||
|
||||
private _onDidChange = new Emitter<number>();
|
||||
public readonly onDidChange: Event<number> = this._onDidChange.event;
|
||||
|
||||
@@ -368,20 +335,13 @@ class GridTable<T> extends Disposable implements IView {
|
||||
private _state: GridTableState;
|
||||
|
||||
private scrolled = false;
|
||||
private visible = false;
|
||||
|
||||
public get resultSet(): sqlops.ResultSetSummary {
|
||||
return this._resultSet;
|
||||
}
|
||||
|
||||
// this handles if the row count is small, like 4-5 rows
|
||||
private get maxSize(): number {
|
||||
return ((this.resultSet.rowCount) * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
|
||||
}
|
||||
private readonly maxSize = ((this.resultSet.rowCount) * ROW_HEIGHT) + HEADER_HEIGHT + ESTIMATED_SCROLL_BAR_HEIGHT;
|
||||
|
||||
constructor(
|
||||
private runner: QueryRunner,
|
||||
private _resultSet: sqlops.ResultSetSummary,
|
||||
public readonly resultSet: sqlops.ResultSetSummary,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private instantiationService: IInstantiationService,
|
||||
@IEditorService private editorService: IEditorService,
|
||||
@@ -407,31 +367,7 @@ class GridTable<T> extends Disposable implements IView {
|
||||
});
|
||||
}
|
||||
|
||||
public onAdd() {
|
||||
this.visible = true;
|
||||
let collection = new VirtualizedCollection(
|
||||
50,
|
||||
index => this.placeholdGenerator(index),
|
||||
this.resultSet.rowCount,
|
||||
(offset, count) => this.loadData(offset, count)
|
||||
);
|
||||
collection.setCollectionChangedCallback((startIndex, count) => {
|
||||
this.renderGridDataRowsRange(startIndex, count);
|
||||
});
|
||||
this.dataProvider.dataRows = collection;
|
||||
this.table.updateRowCount();
|
||||
}
|
||||
|
||||
public onRemove() {
|
||||
this.visible = false;
|
||||
let collection = new VirtualizedCollection(
|
||||
50,
|
||||
index => this.placeholdGenerator(index),
|
||||
0,
|
||||
() => TPromise.as([])
|
||||
);
|
||||
this.dataProvider.dataRows = collection;
|
||||
this.table.updateRowCount();
|
||||
// when we are removed slickgrid acts badly so we need to account for that
|
||||
this.scrolled = false;
|
||||
}
|
||||
@@ -443,38 +379,34 @@ class GridTable<T> extends Disposable implements IView {
|
||||
private build(): void {
|
||||
let tableContainer = document.createElement('div');
|
||||
tableContainer.style.display = 'inline-block';
|
||||
tableContainer.style.width = `calc(100% - ${ACTIONBAR_WIDTH}px)`;
|
||||
|
||||
this.container.appendChild(tableContainer);
|
||||
|
||||
let collection = new VirtualizedCollection(
|
||||
50,
|
||||
index => this.placeholdGenerator(index),
|
||||
0,
|
||||
() => TPromise.as([])
|
||||
let collection = new VirtualizedCollection(50, this.resultSet.rowCount,
|
||||
(offset, count) => this.loadData(offset, count),
|
||||
index => this.placeholdGenerator(index)
|
||||
);
|
||||
collection.setCollectionChangedCallback((startIndex, count) => {
|
||||
collection.setCollectionChangedCallback((change, startIndex, count) => {
|
||||
this.renderGridDataRowsRange(startIndex, count);
|
||||
});
|
||||
this.rowNumberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount });
|
||||
let numberColumn = new RowNumberColumn({ numberOfRows: this.resultSet.rowCount });
|
||||
let copyHandler = new CopyKeybind();
|
||||
copyHandler.onCopy(e => {
|
||||
new CopyResultAction(CopyResultAction.COPY_ID, CopyResultAction.COPY_LABEL, false).run(this.generateContext());
|
||||
});
|
||||
this.columns.unshift(this.rowNumberColumn.getColumnDefinition());
|
||||
this.columns.unshift(numberColumn.getColumnDefinition());
|
||||
let tableOptions: Slick.GridOptions<T> = {
|
||||
rowHeight: ROW_HEIGHT,
|
||||
showRowNumber: true,
|
||||
forceFitColumns: false,
|
||||
defaultColumnWidth: 120
|
||||
};
|
||||
this.dataProvider = new AsyncDataProvider(collection);
|
||||
this.table = this._register(new Table(tableContainer, { dataProvider: this.dataProvider, columns: this.columns }, tableOptions));
|
||||
this.table = this._register(new Table(tableContainer, { dataProvider: new AsyncDataProvider(collection), columns: this.columns }, tableOptions));
|
||||
this.table.setSelectionModel(this.selectionModel);
|
||||
this.table.registerPlugin(new MouseWheelSupport());
|
||||
this.table.registerPlugin(new AutoColumnSize());
|
||||
this.table.registerPlugin(copyHandler);
|
||||
this.table.registerPlugin(this.rowNumberColumn);
|
||||
this.table.registerPlugin(numberColumn);
|
||||
this.table.registerPlugin(new AdditionalKeyBindings());
|
||||
this._register(this.table.onContextMenu(this.contextMenu, this));
|
||||
this._register(this.table.onClick(this.onTableClick, this));
|
||||
@@ -575,7 +507,7 @@ class GridTable<T> extends Disposable implements IView {
|
||||
} catch (e) {
|
||||
// If Xml fails to parse, fall back on original Xml content
|
||||
}
|
||||
} else {
|
||||
} else {
|
||||
let jsonContent: string = undefined;
|
||||
try {
|
||||
jsonContent = JSON.parse(content);
|
||||
@@ -594,15 +526,6 @@ class GridTable<T> extends Disposable implements IView {
|
||||
}
|
||||
}
|
||||
|
||||
public updateResult(resultSet: sqlops.ResultSetSummary) {
|
||||
this._resultSet = resultSet;
|
||||
if (this.table && this.visible) {
|
||||
this.dataProvider.length = resultSet.rowCount;
|
||||
this.table.updateRowCount();
|
||||
}
|
||||
this.rowNumberColumn.updateRowCount(resultSet.rowCount);
|
||||
}
|
||||
|
||||
private generateContext(cell?: Slick.Cell): IGridActionContext {
|
||||
const selection = this.selectionModel.getSelectedRanges();
|
||||
return <IGridActionContext>{
|
||||
@@ -654,7 +577,12 @@ class GridTable<T> extends Disposable implements IView {
|
||||
} else {
|
||||
this.currentHeight = size;
|
||||
}
|
||||
this.table.layout(size, Orientation.VERTICAL);
|
||||
this.table.layout(
|
||||
new Dimension(
|
||||
getContentWidth(this.container) - ACTIONBAR_WIDTH,
|
||||
size
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public get minimumSize(): number {
|
||||
@@ -668,9 +596,6 @@ class GridTable<T> extends Disposable implements IView {
|
||||
|
||||
private loadData(offset: number, count: number): Thenable<T[]> {
|
||||
return this.runner.getQueryRows(offset, count, this.resultSet.batchId, this.resultSet.id).then(response => {
|
||||
if (!response.resultSubset) {
|
||||
return [];
|
||||
}
|
||||
return response.resultSubset.rows.map(r => {
|
||||
let dataWithSchema = {};
|
||||
// skip the first column since its a number column
|
||||
|
||||
@@ -100,18 +100,6 @@ export default class QueryRunner extends Disposable {
|
||||
private _echoedResultSet = echo(this._debouncedResultSet.event);
|
||||
public readonly onResultSet = this._echoedResultSet.event;
|
||||
|
||||
private _onResultSetUpdate = this._register(new Emitter<sqlops.ResultSetSummary>());
|
||||
private _debouncedResultSetUpdate = debounceEvent<sqlops.ResultSetSummary, sqlops.ResultSetSummary[]>(this._onResultSetUpdate.event, (l, e) => {
|
||||
// on first run
|
||||
if (types.isUndefinedOrNull(l)) {
|
||||
return [e];
|
||||
} else {
|
||||
return l.concat(e);
|
||||
}
|
||||
});
|
||||
private _echoedResultSetUpdate = echo(this._debouncedResultSetUpdate.event);
|
||||
public readonly onResultSetUpdate = this._echoedResultSetUpdate.event;
|
||||
|
||||
private _onQueryStart = this._register(new Emitter<void>());
|
||||
public readonly onQueryStart: Event<void> = this._onQueryStart.event;
|
||||
|
||||
@@ -348,7 +336,7 @@ export default class QueryRunner extends Disposable {
|
||||
/**
|
||||
* Handle a ResultSetComplete from the service layer
|
||||
*/
|
||||
public handleResultSetAvailable(result: sqlops.QueryExecuteResultSetNotificationParams): void {
|
||||
public handleResultSetComplete(result: sqlops.QueryExecuteResultSetCompleteNotificationParams): void {
|
||||
if (result && result.resultSetSummary) {
|
||||
let resultSet = result.resultSetSummary;
|
||||
let batchSet: sqlops.BatchSummary;
|
||||
@@ -386,27 +374,6 @@ export default class QueryRunner extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public handleResultSetUpdated(result: sqlops.QueryExecuteResultSetNotificationParams): void {
|
||||
if (result && result.resultSetSummary) {
|
||||
let resultSet = result.resultSetSummary;
|
||||
let batchSet: sqlops.BatchSummary;
|
||||
batchSet = this.batchSets[resultSet.batchId];
|
||||
// handle getting queryPlanxml if we need too
|
||||
if (this.isQueryPlan) {
|
||||
// check if this result has show plan, this needs work, it won't work for any other provider
|
||||
let hasShowPlan = !!result.resultSetSummary.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
|
||||
if (hasShowPlan) {
|
||||
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
|
||||
}
|
||||
}
|
||||
if (batchSet) {
|
||||
// Store the result set in the batch and emit that a result set has completed
|
||||
batchSet.resultSetSummaries[resultSet.id] = resultSet;
|
||||
this._onResultSetUpdate.fire(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a Mssage from the service layer
|
||||
*/
|
||||
|
||||
@@ -249,9 +249,9 @@ export class QueryEditorService implements IQueryEditorService {
|
||||
QueryEditorService.editorService.openEditor(newEditorInput, options, group).then((editor) => {
|
||||
resolve(QueryEditorService._onEditorOpened(editor, uri.toString(), undefined, options.pinned));
|
||||
},
|
||||
(error) => {
|
||||
reject(error);
|
||||
});
|
||||
(error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ import { AccountDialogController } from 'sql/parts/accountManagement/accountDial
|
||||
import { AutoOAuthDialogController } from 'sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialogController';
|
||||
import { AccountListStatusbarItem } from 'sql/parts/accountManagement/accountListStatusbar/accountListStatusbarItem';
|
||||
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes';
|
||||
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
|
||||
import { IAccountManagementService, AzureResource } from 'sql/services/accountManagement/interfaces';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
|
||||
export class AccountManagementService implements IAccountManagementService {
|
||||
@@ -217,11 +217,12 @@ export class AccountManagementService implements IAccountManagementService {
|
||||
/**
|
||||
* Generates a security token by asking the account's provider
|
||||
* @param {Account} account Account to generate security token for
|
||||
* @param {AzureResource} resource The resource to get the security token for
|
||||
* @return {Thenable<{}>} Promise to return the security token
|
||||
*/
|
||||
public getSecurityToken(account: sqlops.Account): Thenable<{}> {
|
||||
public getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> {
|
||||
return this.doWithProvider(account.key.providerId, provider => {
|
||||
return provider.provider.getSecurityToken(account);
|
||||
return provider.provider.getSecurityToken(account, resource);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ export interface IAccountManagementService {
|
||||
addAccount(providerId: string): Thenable<void>;
|
||||
getAccountProviderMetadata(): Thenable<sqlops.AccountProviderMetadata[]>;
|
||||
getAccountsForProvider(providerId: string): Thenable<sqlops.Account[]>;
|
||||
getSecurityToken(account: sqlops.Account): Thenable<{}>;
|
||||
getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}>;
|
||||
removeAccount(accountKey: sqlops.AccountKey): Thenable<boolean>;
|
||||
refreshAccount(account: sqlops.Account): Thenable<sqlops.Account>;
|
||||
|
||||
@@ -44,6 +44,12 @@ export interface IAccountManagementService {
|
||||
readonly updateAccountListEvent: Event<UpdateAccountListEventParams>;
|
||||
}
|
||||
|
||||
// Enum matching the AzureResource enum from sqlops.d.ts
|
||||
export enum AzureResource {
|
||||
ResourceManagement = 0,
|
||||
Sql = 1
|
||||
}
|
||||
|
||||
export interface IAccountStore {
|
||||
/**
|
||||
* Adds the provided account if the account doesn't exist. Updates the account if it already exists
|
||||
|
||||
78
src/sql/services/dacfx/dacFxService.ts
Normal file
78
src/sql/services/dacfx/dacFxService.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { localize } from 'vs/nls';
|
||||
import { data } from 'vs/base/test/common/filters.perf.data';
|
||||
|
||||
export const SERVICE_ID = 'dacFxService';
|
||||
export const IDacFxService = createDecorator<IDacFxService>(SERVICE_ID);
|
||||
|
||||
export interface IDacFxService {
|
||||
_serviceBrand: any;
|
||||
|
||||
registerProvider(providerId: string, provider: sqlops.DacFxServicesProvider): void;
|
||||
exportBacpac(sourceDatabaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
importBacpac(packageFilePath: string, targetDatabaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
extractDacpac(sourceDatabaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
deployDacpac(packageFilePath: string, targetDatabaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): void;
|
||||
}
|
||||
|
||||
export class DacFxService implements IDacFxService {
|
||||
_serviceBrand: any;
|
||||
private _providers: { [handle: string]: sqlops.DacFxServicesProvider; } = Object.create(null);
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService
|
||||
) {
|
||||
}
|
||||
|
||||
registerProvider(providerId: string, provider: sqlops.DacFxServicesProvider): void {
|
||||
this._providers[providerId] = provider;
|
||||
}
|
||||
|
||||
exportBacpac(databasesName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.exportBacpac(databasesName, packageFilePath, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
importBacpac(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.importBacpac(packageFilePath, databaseName, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
extractDacpac(databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.extractDacpac(databaseName, packageFilePath, applicationName, applicationVersion, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
deployDacpac(packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return this._runAction(ownerUri, (runner) => {
|
||||
return runner.deployDacpac(packageFilePath, databaseName, upgradeExisting, ownerUri, taskExecutionMode);
|
||||
});
|
||||
}
|
||||
|
||||
private _runAction<T>(uri: string, action: (handler: sqlops.DacFxServicesProvider) => Thenable<T>): Thenable<T> {
|
||||
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
|
||||
|
||||
if (!providerId) {
|
||||
return TPromise.wrapError(new Error(localize('providerIdNotValidError', "Connection is required in order to interact with DacFxService")));
|
||||
}
|
||||
let handler = this._providers[providerId];
|
||||
if (handler) {
|
||||
return action(handler);
|
||||
} else {
|
||||
return TPromise.wrapError(new Error(localize('noHandlerRegistered', "No Handler Registered")));
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/sql/sqlops.d.ts
vendored
64
src/sql/sqlops.d.ts
vendored
@@ -37,6 +37,8 @@ declare module 'sqlops' {
|
||||
|
||||
export function registerCapabilitiesServiceProvider(provider: CapabilitiesProvider): vscode.Disposable;
|
||||
|
||||
export function registerDacFxServicesProvider(provider: DacFxServicesProvider): vscode.Disposable;
|
||||
|
||||
/**
|
||||
* An [event](#Event) which fires when the specific flavor of a language used in DMP
|
||||
* connections has changed. And example is for a SQL connection, the flavor changes
|
||||
@@ -692,8 +694,7 @@ declare module 'sqlops' {
|
||||
registerOnQueryComplete(handler: (result: QueryExecuteCompleteNotificationResult) => any): void;
|
||||
registerOnBatchStart(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any): void;
|
||||
registerOnBatchComplete(handler: (batchInfo: QueryExecuteBatchNotificationParams) => any): void;
|
||||
registerOnResultSetAvailable(handler: (resultSetInfo: QueryExecuteResultSetNotificationParams) => any): void;
|
||||
registerOnResultSetUpdated(handler: (resultSetInfo: QueryExecuteResultSetNotificationParams) => any): void;
|
||||
registerOnResultSetComplete(handler: (resultSetInfo: QueryExecuteResultSetCompleteNotificationParams) => any): void;
|
||||
registerOnMessage(handler: (message: QueryExecuteMessageParams) => any): void;
|
||||
|
||||
// Edit Data Requests
|
||||
@@ -768,7 +769,6 @@ declare module 'sqlops' {
|
||||
batchId: number;
|
||||
rowCount: number;
|
||||
columnInfo: IDbColumn[];
|
||||
complete: boolean;
|
||||
}
|
||||
|
||||
export interface BatchSummary {
|
||||
@@ -837,7 +837,7 @@ declare module 'sqlops' {
|
||||
}
|
||||
|
||||
|
||||
export interface QueryExecuteResultSetNotificationParams {
|
||||
export interface QueryExecuteResultSetCompleteNotificationParams {
|
||||
resultSetSummary: ResultSetSummary;
|
||||
ownerUri: string;
|
||||
}
|
||||
@@ -1585,6 +1585,49 @@ declare module 'sqlops' {
|
||||
registerOnUpdated(handler: () => any): void;
|
||||
}
|
||||
|
||||
// DacFx interfaces -----------------------------------------------------------------------
|
||||
export interface DacFxResult extends ResultStatus {
|
||||
operationId: string;
|
||||
}
|
||||
|
||||
export interface ExportParams {
|
||||
databaseName: string;
|
||||
packageFilePath: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface ImportParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface ExtractParams {
|
||||
databaseName: string;
|
||||
packageFilePath: string;
|
||||
applicationName: string;
|
||||
applicationVersion: string;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface DeployParams {
|
||||
packageFilePath: string;
|
||||
databaseName: string;
|
||||
upgradeExisting: boolean;
|
||||
ownerUri: string;
|
||||
taskExecutionMode: TaskExecutionMode;
|
||||
}
|
||||
|
||||
export interface DacFxServicesProvider extends DataProvider {
|
||||
exportBacpac(databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<DacFxResult>;
|
||||
importBacpac(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<DacFxResult>;
|
||||
extractDacpac(databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<DacFxResult>;
|
||||
deployDacpac(packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: TaskExecutionMode): Thenable<DacFxResult>;
|
||||
}
|
||||
|
||||
// Security service interfaces ------------------------------------------------------------------------
|
||||
export interface CredentialInfo {
|
||||
id: number;
|
||||
@@ -1914,10 +1957,11 @@ declare module 'sqlops' {
|
||||
|
||||
/**
|
||||
* Generates a security token by asking the account's provider
|
||||
* @param {Account} account Account to generate security token for
|
||||
* @param {Account} account Account to generate security token for (defaults to
|
||||
* AzureResource.ResourceManagement if not given)
|
||||
* @return {Thenable<{}>} Promise to return the security token
|
||||
*/
|
||||
export function getSecurityToken(account: Account): Thenable<{}>;
|
||||
export function getSecurityToken(account: Account, resource?: AzureResource): Thenable<{}>;
|
||||
|
||||
/**
|
||||
* An [event](#Event) which fires when the accounts have changed.
|
||||
@@ -1990,6 +2034,11 @@ declare module 'sqlops' {
|
||||
isStale: boolean;
|
||||
}
|
||||
|
||||
export enum AzureResource {
|
||||
ResourceManagement = 0,
|
||||
Sql = 1
|
||||
}
|
||||
|
||||
export interface DidChangeAccountsParams {
|
||||
// Updated accounts
|
||||
accounts: Account[];
|
||||
@@ -2047,9 +2096,10 @@ declare module 'sqlops' {
|
||||
/**
|
||||
* Generates a security token for the provided account
|
||||
* @param {Account} account The account to generate a security token for
|
||||
* @param {AzureResource} resource The resource to get the token for
|
||||
* @return {Thenable<{}>} Promise to return a security token object
|
||||
*/
|
||||
getSecurityToken(account: Account): Thenable<{}>;
|
||||
getSecurityToken(account: Account, resource: AzureResource): Thenable<{}>;
|
||||
|
||||
/**
|
||||
* Prompts the user to enter account information.
|
||||
|
||||
3
src/sql/sqlops.proposed.d.ts
vendored
3
src/sql/sqlops.proposed.d.ts
vendored
@@ -1232,7 +1232,8 @@ declare module 'sqlops' {
|
||||
QueryProvider = 'QueryProvider',
|
||||
AdminServicesProvider = 'AdminServicesProvider',
|
||||
AgentServicesProvider = 'AgentServicesProvider',
|
||||
CapabilitiesProvider = 'CapabilitiesProvider'
|
||||
CapabilitiesProvider = 'CapabilitiesProvider',
|
||||
DacFxServicesProvider = 'DacFxServicesProvider',
|
||||
}
|
||||
|
||||
export namespace dataprotocol {
|
||||
|
||||
@@ -285,7 +285,8 @@ export enum DataProviderType {
|
||||
QueryProvider = 'QueryProvider',
|
||||
AdminServicesProvider = 'AdminServicesProvider',
|
||||
AgentServicesProvider = 'AgentServicesProvider',
|
||||
CapabilitiesProvider = 'CapabilitiesProvider'
|
||||
CapabilitiesProvider = 'CapabilitiesProvider',
|
||||
DacFxServicesProvider = 'DacFxServicesProvider',
|
||||
}
|
||||
|
||||
export enum DeclarativeDataType {
|
||||
@@ -313,6 +314,11 @@ export class TreeComponentItem extends TreeItem {
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export enum AzureResource {
|
||||
ResourceManagement = 0,
|
||||
Sql = 1
|
||||
}
|
||||
|
||||
export class SqlThemeIcon {
|
||||
|
||||
static readonly Folder = new SqlThemeIcon('Folder');
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
MainThreadAccountManagementShape,
|
||||
SqlMainContext,
|
||||
} from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { AzureResource } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
@@ -89,12 +90,15 @@ export class ExtHostAccountManagement extends ExtHostAccountManagementShape {
|
||||
return Promise.all(promises).then(() => resultAccounts);
|
||||
}
|
||||
|
||||
public $getSecurityToken(account: sqlops.Account): Thenable<{}> {
|
||||
public $getSecurityToken(account: sqlops.Account, resource?: sqlops.AzureResource): Thenable<{}> {
|
||||
if (resource === undefined) {
|
||||
resource = AzureResource.ResourceManagement;
|
||||
}
|
||||
return this.$getAllAccounts().then(() => {
|
||||
for (const handle in this._accounts) {
|
||||
const providerHandle = parseInt(handle);
|
||||
if (this._accounts[handle].findIndex((acct) => acct.key.accountId === account.key.accountId) !== -1) {
|
||||
return this._withProvider(providerHandle, (provider: sqlops.AccountProvider) => provider.getSecurityToken(account));
|
||||
return this._withProvider(providerHandle, (provider: sqlops.AccountProvider) => provider.getSecurityToken(account, resource));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,6 +156,12 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
||||
return rt;
|
||||
}
|
||||
|
||||
$registerDacFxServiceProvider(provider: sqlops.DacFxServicesProvider): vscode.Disposable {
|
||||
let rt = this.registerProvider(provider, DataProviderType.DacFxServicesProvider);
|
||||
this._proxy.$registerDacFxServicesProvider(provider.providerId, provider.handle);
|
||||
return rt;
|
||||
}
|
||||
|
||||
// Capabilities Discovery handlers
|
||||
$getServerCapabilities(handle: number, client: sqlops.DataProtocolClientCapabilities): Thenable<sqlops.DataProtocolServerCapabilities> {
|
||||
return this._resolveProvider<sqlops.CapabilitiesProvider>(handle).getServerCapabilities(client);
|
||||
@@ -259,11 +265,8 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
||||
$onBatchComplete(handle: number, batchInfo: sqlops.QueryExecuteBatchNotificationParams): void {
|
||||
this._proxy.$onBatchComplete(handle, batchInfo);
|
||||
}
|
||||
$onResultSetAvailable(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
|
||||
this._proxy.$onResultSetAvailable(handle, resultSetInfo);
|
||||
}
|
||||
$onResultSetUpdated(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
|
||||
this._proxy.$onResultSetUpdated(handle, resultSetInfo);
|
||||
$onResultSetComplete(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void {
|
||||
this._proxy.$onResultSetComplete(handle, resultSetInfo);
|
||||
}
|
||||
$onQueryMessage(handle: number, message: sqlops.QueryExecuteMessageParams): void {
|
||||
this._proxy.$onQueryMessage(handle, message);
|
||||
|
||||
@@ -76,8 +76,8 @@ export class MainThreadAccountManagement implements MainThreadAccountManagementS
|
||||
clear(accountKey: sqlops.AccountKey): Thenable<void> {
|
||||
return self._proxy.$clear(handle, accountKey);
|
||||
},
|
||||
getSecurityToken(account: sqlops.Account): Thenable<{}> {
|
||||
return self._proxy.$getSecurityToken(account);
|
||||
getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> {
|
||||
return self._proxy.$getSecurityToken(account, resource);
|
||||
},
|
||||
initialize(restoredAccounts: sqlops.Account[]): Thenable<sqlops.Account[]> {
|
||||
return self._proxy.$initialize(handle, restoredAccounts);
|
||||
|
||||
@@ -26,6 +26,7 @@ import { IProfilerService } from 'sql/parts/profiler/service/interfaces';
|
||||
import { ISerializationService } from 'sql/services/serialization/serializationService';
|
||||
import { IFileBrowserService } from 'sql/parts/fileBrowser/common/interfaces';
|
||||
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
|
||||
import { IDacFxService } from 'sql/services/dacfx/dacFxService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
|
||||
|
||||
/**
|
||||
@@ -55,7 +56,8 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
||||
@ITaskService private _taskService: ITaskService,
|
||||
@IProfilerService private _profilerService: IProfilerService,
|
||||
@ISerializationService private _serializationService: ISerializationService,
|
||||
@IFileBrowserService private _fileBrowserService: IFileBrowserService
|
||||
@IFileBrowserService private _fileBrowserService: IFileBrowserService,
|
||||
@IDacFxService private _dacFxService: IDacFxService,
|
||||
) {
|
||||
if (extHostContext) {
|
||||
this._proxy = extHostContext.getProxy(SqlExtHostContext.ExtHostDataProtocol);
|
||||
@@ -399,6 +401,26 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public $registerDacFxServicesProvider(providerId: string, handle: number): TPromise<any> {
|
||||
const self = this;
|
||||
this._dacFxService.registerProvider(providerId, <sqlops.DacFxServicesProvider>{
|
||||
exportBacpac(databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return self._proxy.$exportBacpac(handle, databaseName, packageFilePath, ownerUri, taskExecutionMode);
|
||||
},
|
||||
importBacpac(packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return self._proxy.$importBacpac(handle, packageFilePath, databaseName, ownerUri, taskExecutionMode);
|
||||
},
|
||||
extractDacpac(databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return self._proxy.$extractDacpac(handle, databaseName, packageFilePath, applicationName, applicationVersion, ownerUri, taskExecutionMode);
|
||||
},
|
||||
deployDacpac(packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> {
|
||||
return self._proxy.$deployDacpac(handle, packageFilePath, databaseName, upgradeExisting, ownerUri, taskExecutionMode);
|
||||
}
|
||||
});
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Connection Management handlers
|
||||
public $onConnectionComplete(handle: number, connectionInfoSummary: sqlops.ConnectionInfoSummary): void {
|
||||
this._connectionManagementService.onConnectionComplete(handle, connectionInfoSummary);
|
||||
@@ -422,11 +444,8 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
||||
public $onBatchComplete(handle: number, batchInfo: sqlops.QueryExecuteBatchNotificationParams): void {
|
||||
this._queryManagementService.onBatchComplete(batchInfo);
|
||||
}
|
||||
public $onResultSetAvailable(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
|
||||
this._queryManagementService.onResultSetAvailable(resultSetInfo);
|
||||
}
|
||||
public $onResultSetUpdated(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void {
|
||||
this._queryManagementService.onResultSetUpdated(resultSetInfo);
|
||||
public $onResultSetComplete(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void {
|
||||
this._queryManagementService.onResultSetComplete(resultSetInfo);
|
||||
}
|
||||
public $onQueryMessage(handle: number, message: sqlops.QueryExecuteMessageParams): void {
|
||||
this._queryManagementService.onMessage(message);
|
||||
|
||||
@@ -97,8 +97,8 @@ export function createApiFactory(
|
||||
getAllAccounts(): Thenable<sqlops.Account[]> {
|
||||
return extHostAccountManagement.$getAllAccounts();
|
||||
},
|
||||
getSecurityToken(account: sqlops.Account): Thenable<{}> {
|
||||
return extHostAccountManagement.$getSecurityToken(account);
|
||||
getSecurityToken(account: sqlops.Account, resource?: sqlops.AzureResource): Thenable<{}> {
|
||||
return extHostAccountManagement.$getSecurityToken(account, resource);
|
||||
},
|
||||
onDidChangeAccounts(listener: (e: sqlops.DidChangeAccountsParams) => void, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {
|
||||
return extHostAccountManagement.onDidChangeAccounts(listener, thisArgs, disposables);
|
||||
@@ -197,12 +197,8 @@ export function createApiFactory(
|
||||
extHostDataProvider.$onBatchComplete(provider.handle, batchInfo);
|
||||
});
|
||||
|
||||
provider.registerOnResultSetAvailable((resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams) => {
|
||||
extHostDataProvider.$onResultSetAvailable(provider.handle, resultSetInfo);
|
||||
});
|
||||
|
||||
provider.registerOnResultSetUpdated((resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams) => {
|
||||
extHostDataProvider.$onResultSetUpdated(provider.handle, resultSetInfo);
|
||||
provider.registerOnResultSetComplete((resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams) => {
|
||||
extHostDataProvider.$onResultSetComplete(provider.handle, resultSetInfo);
|
||||
});
|
||||
|
||||
provider.registerOnMessage((message: sqlops.QueryExecuteMessageParams) => {
|
||||
@@ -314,6 +310,10 @@ export function createApiFactory(
|
||||
return extHostDataProvider.$registerAgentServiceProvider(provider);
|
||||
};
|
||||
|
||||
let registerDacFxServicesProvider = (provider: sqlops.DacFxServicesProvider): vscode.Disposable => {
|
||||
return extHostDataProvider.$registerDacFxServiceProvider(provider);
|
||||
};
|
||||
|
||||
// namespace: dataprotocol
|
||||
const dataprotocol: typeof sqlops.dataprotocol = {
|
||||
registerBackupProvider,
|
||||
@@ -329,6 +329,7 @@ export function createApiFactory(
|
||||
registerAdminServicesProvider,
|
||||
registerAgentServicesProvider,
|
||||
registerCapabilitiesServiceProvider,
|
||||
registerDacFxServicesProvider,
|
||||
onDidChangeLanguageFlavor(listener: (e: sqlops.DidChangeLanguageFlavorParams) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {
|
||||
return extHostDataProvider.onDidChangeLanguageFlavor(listener, thisArgs, disposables);
|
||||
},
|
||||
@@ -456,7 +457,8 @@ export function createApiFactory(
|
||||
Orientation: sqlExtHostTypes.Orientation,
|
||||
SqlThemeIcon: sqlExtHostTypes.SqlThemeIcon,
|
||||
TreeComponentItem: sqlExtHostTypes.TreeComponentItem,
|
||||
nb: nb
|
||||
nb: nb,
|
||||
AzureResource: sqlExtHostTypes.AzureResource
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
export abstract class ExtHostAccountManagementShape {
|
||||
$autoOAuthCancelled(handle: number): Thenable<void> { throw ni(); }
|
||||
$clear(handle: number, accountKey: sqlops.AccountKey): Thenable<void> { throw ni(); }
|
||||
$getSecurityToken(account: sqlops.Account): Thenable<{}> { throw ni(); }
|
||||
$getSecurityToken(account: sqlops.Account, resource?: sqlops.AzureResource): Thenable<{}> { throw ni(); }
|
||||
$initialize(handle: number, restoredAccounts: sqlops.Account[]): Thenable<sqlops.Account[]> { throw ni(); }
|
||||
$prompt(handle: number): Thenable<sqlops.Account> { throw ni(); }
|
||||
$refresh(handle: number, account: sqlops.Account): Thenable<sqlops.Account> { throw ni(); }
|
||||
@@ -193,11 +193,7 @@ export abstract class ExtHostDataProtocolShape {
|
||||
/**
|
||||
* Callback when a result set has been returned from query execution and can be displayed
|
||||
*/
|
||||
$onResultSetAvailable(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void { throw ni(); }
|
||||
/**
|
||||
* Callback when a result set has been returned from query execution and can be displayed
|
||||
*/
|
||||
$onResultSetUpdate(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void { throw ni(); }
|
||||
$onResultSetComplete(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void { throw ni(); }
|
||||
/**
|
||||
* Callback when a message generated during query execution is issued
|
||||
*/
|
||||
@@ -413,6 +409,26 @@ export abstract class ExtHostDataProtocolShape {
|
||||
* Get Agent Credentials list
|
||||
*/
|
||||
$getCredentials(handle: number, connectionUri: string): Thenable<sqlops.GetCredentialsResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* DacFx export bacpac
|
||||
*/
|
||||
$exportBacpac(handle: number, databaseName: string, packageFilePath: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* DacFx import bacpac
|
||||
*/
|
||||
$importBacpac(handle: number, packageFilePath: string, databaseName: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* DacFx extract dacpac
|
||||
*/
|
||||
$extractDacpac(handle: number, databaseName: string, packageFilePath: string, applicationName: string, applicationVersion: string, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> { throw ni(); }
|
||||
|
||||
/**
|
||||
* DacFx deploy dacpac
|
||||
*/
|
||||
$deployDacpac(handle: number, packageFilePath: string, databaseName: string, upgradeExisting: boolean, ownerUri: string, taskExecutionMode: sqlops.TaskExecutionMode): Thenable<sqlops.DacFxResult> { throw ni(); }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -480,6 +496,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
|
||||
$registerCapabilitiesServiceProvider(providerId: string, handle: number): TPromise<any>;
|
||||
$registerAdminServicesProvider(providerId: string, handle: number): TPromise<any>;
|
||||
$registerAgentServicesProvider(providerId: string, handle: number): TPromise<any>;
|
||||
$registerDacFxServicesProvider(providerId: string, handle: number): TPromise<any>;
|
||||
$unregisterProvider(handle: number): TPromise<any>;
|
||||
$onConnectionComplete(handle: number, connectionInfoSummary: sqlops.ConnectionInfoSummary): void;
|
||||
$onIntelliSenseCacheComplete(handle: number, connectionUri: string): void;
|
||||
@@ -487,8 +504,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
|
||||
$onQueryComplete(handle: number, result: sqlops.QueryExecuteCompleteNotificationResult): void;
|
||||
$onBatchStart(handle: number, batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
|
||||
$onBatchComplete(handle: number, batchInfo: sqlops.QueryExecuteBatchNotificationParams): void;
|
||||
$onResultSetAvailable(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
|
||||
$onResultSetUpdated(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetNotificationParams): void;
|
||||
$onResultSetComplete(handle: number, resultSetInfo: sqlops.QueryExecuteResultSetCompleteNotificationParams): void;
|
||||
$onQueryMessage(handle: number, message: sqlops.QueryExecuteMessageParams): void;
|
||||
$onObjectExplorerSessionCreated(handle: number, message: sqlops.ObjectExplorerSession): void;
|
||||
$onObjectExplorerSessionDisconnected(handle: number, message: sqlops.ObjectExplorerSession): void;
|
||||
|
||||
@@ -136,7 +136,7 @@ suite('Firewall rule dialog controller tests', () => {
|
||||
|
||||
// Then: it should get security token from account management service and call create firewall rule in resource provider
|
||||
deferredPromise.promise.then(() => {
|
||||
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockResourceProvider.verify(x => x.createFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockFirewallRuleDialog.verify(x => x.close(), TypeMoq.Times.once());
|
||||
mockFirewallRuleDialog.verify(x => x.onServiceComplete(), TypeMoq.Times.once());
|
||||
@@ -165,7 +165,7 @@ suite('Firewall rule dialog controller tests', () => {
|
||||
|
||||
// Then: it should get security token from account management service and an error dialog should have been opened
|
||||
deferredPromise.promise.then(() => {
|
||||
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockResourceProvider.verify(x => x.createFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.never());
|
||||
done();
|
||||
@@ -193,7 +193,7 @@ suite('Firewall rule dialog controller tests', () => {
|
||||
|
||||
// Then: it should get security token from account management service and an error dialog should have been opened
|
||||
deferredPromise.promise.then(() => {
|
||||
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockResourceProvider.verify(x => x.createFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
done();
|
||||
@@ -221,7 +221,7 @@ suite('Firewall rule dialog controller tests', () => {
|
||||
|
||||
// Then: it should get security token from account management service and an error dialog should have been opened
|
||||
deferredPromise.promise.then(() => {
|
||||
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockAccountManagementService.verify(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockResourceProvider.verify(x => x.createFirewallRule(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
mockErrorMessageService.verify(x => x.showDialog(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
done();
|
||||
@@ -232,7 +232,7 @@ suite('Firewall rule dialog controller tests', () => {
|
||||
function getMockAccountManagementService(resolveSecurityToken: boolean): TypeMoq.Mock<AccountManagementTestService> {
|
||||
let accountManagementTestService = new AccountManagementTestService();
|
||||
let mockAccountManagementService = TypeMoq.Mock.ofInstance(accountManagementTestService);
|
||||
mockAccountManagementService.setup(x => x.getSecurityToken(TypeMoq.It.isAny()))
|
||||
mockAccountManagementService.setup(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns(() => resolveSecurityToken ? Promise.resolve({}) : Promise.reject(null).then());
|
||||
return mockAccountManagementService;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import * as assert from 'assert';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/parts/connection/common/connectionProfileGroup';
|
||||
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
|
||||
import { AccountManagementTestService } from 'sqltest/stubs/accountManagementStubs';
|
||||
|
||||
suite('SQL ConnectionManagementService tests', () => {
|
||||
|
||||
@@ -46,6 +47,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
let mssqlConnectionProvider: TypeMoq.Mock<ConnectionProviderStub>;
|
||||
let workspaceConfigurationServiceMock: TypeMoq.Mock<WorkspaceConfigurationTestService>;
|
||||
let resourceProviderStubMock: TypeMoq.Mock<ResourceProviderStub>;
|
||||
let accountManagementService: TypeMoq.Mock<AccountManagementTestService>;
|
||||
|
||||
let none: void;
|
||||
|
||||
@@ -88,6 +90,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
mssqlConnectionProvider = TypeMoq.Mock.ofType(ConnectionProviderStub);
|
||||
let resourceProviderStub = new ResourceProviderStub();
|
||||
resourceProviderStubMock = TypeMoq.Mock.ofInstance(resourceProviderStub);
|
||||
accountManagementService = TypeMoq.Mock.ofType(AccountManagementTestService);
|
||||
let root = new ConnectionProfileGroup(ConnectionProfileGroup.RootGroupName, undefined, ConnectionProfileGroup.RootGroupName, undefined, undefined);
|
||||
root.connections = [ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile)];
|
||||
|
||||
@@ -162,7 +165,8 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
undefined,
|
||||
resourceProviderStubMock.object,
|
||||
undefined,
|
||||
undefined
|
||||
undefined,
|
||||
accountManagementService.object
|
||||
);
|
||||
return connectionManagementService;
|
||||
}
|
||||
@@ -837,4 +841,44 @@ suite('SQL ConnectionManagementService tests', () => {
|
||||
// Then undefined is returned
|
||||
assert.equal(foundUri, undefined);
|
||||
});
|
||||
|
||||
test('addSavedPassword fills in Azure access tokens for Azure accounts', async () => {
|
||||
// Set up a connection profile that uses Azure
|
||||
let azureConnectionProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile);
|
||||
azureConnectionProfile.authenticationType = 'AzureMFA';
|
||||
let username = 'testuser@microsoft.com';
|
||||
azureConnectionProfile.userName = username;
|
||||
let servername = 'test-database.database.windows.net';
|
||||
azureConnectionProfile.serverName = servername;
|
||||
|
||||
// Set up the account management service to return a token for the given user
|
||||
accountManagementService.setup(x => x.getAccountsForProvider(TypeMoq.It.isAny())).returns(providerId => Promise.resolve<sqlops.Account[]>([
|
||||
{
|
||||
key: {
|
||||
accountId: username,
|
||||
providerId: providerId
|
||||
},
|
||||
displayInfo: undefined,
|
||||
isStale: false,
|
||||
properties: undefined
|
||||
}
|
||||
]));
|
||||
let testToken = 'testToken';
|
||||
accountManagementService.setup(x => x.getSecurityToken(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve({
|
||||
azurePublicCloud: {
|
||||
token: testToken
|
||||
}
|
||||
}));
|
||||
connectionStore.setup(x => x.addSavedPassword(TypeMoq.It.is(profile => profile.authenticationType === 'AzureMFA'))).returns(profile => Promise.resolve({
|
||||
profile: profile,
|
||||
savedCred: false
|
||||
}));
|
||||
|
||||
// If I call addSavedPassword
|
||||
let profileWithCredentials = await connectionManagementService.addSavedPassword(azureConnectionProfile);
|
||||
|
||||
// Then the returned profile has the account token set
|
||||
assert.equal(profileWithCredentials.userName, username);
|
||||
assert.equal(profileWithCredentials.options['azureAccountToken'], testToken);
|
||||
});
|
||||
});
|
||||
@@ -349,7 +349,7 @@ suite('SQL QueryEditor Tests', () => {
|
||||
queryActionInstantiationService.setup(x => x.createInstance(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny()))
|
||||
.returns((definition, editor, action, selectBox) => {
|
||||
if (definition.ID === 'listDatabaseQueryActionItem') {
|
||||
let item = new ListDatabasesActionItem(editor, action, queryConnectionService.object, undefined, undefined, undefined, configurationService.object);
|
||||
let item = new ListDatabasesActionItem(editor, action, queryConnectionService.object, undefined, undefined, undefined,configurationService.object);
|
||||
return item;
|
||||
}
|
||||
// Default
|
||||
|
||||
@@ -50,7 +50,7 @@ export class AccountManagementTestService implements IAccountManagementService {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getSecurityToken(account: sqlops.Account): Thenable<{}> {
|
||||
getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ export class AccountProviderStub implements sqlops.AccountProvider {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
getSecurityToken(account: sqlops.Account): Thenable<{}> {
|
||||
getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/
|
||||
import { IRPCProtocol } from 'vs/workbench/services/extensions/node/proxyIdentifier';
|
||||
import { SqlMainContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
|
||||
import { MainThreadAccountManagement } from 'sql/workbench/api/node/mainThreadAccountManagement';
|
||||
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
|
||||
import { IAccountManagementService, AzureResource } from 'sql/services/accountManagement/interfaces';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
const IRPCProtocol = createDecorator<IRPCProtocol>('rpcProtocol');
|
||||
@@ -366,7 +366,7 @@ suite('ExtHostAccountManagement', () => {
|
||||
extHost.$getAllAccounts()
|
||||
.then((accounts) => {
|
||||
// If: I get security token it will not throw
|
||||
return extHost.$getSecurityToken(mockAccount1);
|
||||
return extHost.$getSecurityToken(mockAccount1, AzureResource.ResourceManagement);
|
||||
}
|
||||
).then(() => done(), (err) => done(new Error(err)));
|
||||
});
|
||||
@@ -417,7 +417,7 @@ suite('ExtHostAccountManagement', () => {
|
||||
|
||||
extHost.$getAllAccounts()
|
||||
.then(accounts => {
|
||||
return extHost.$getSecurityToken(mockAccount2);
|
||||
return extHost.$getSecurityToken(mockAccount2, AzureResource.ResourceManagement);
|
||||
})
|
||||
.then((noError) => {
|
||||
done(new Error('Expected getSecurityToken to throw'));
|
||||
@@ -447,7 +447,7 @@ function getMockAccountManagementService(accounts: sqlops.Account[]): TypeMoq.Mo
|
||||
|
||||
mockAccountManagementService.setup(x => x.getAccountsForProvider(TypeMoq.It.isAny()))
|
||||
.returns(() => Promise.resolve(accounts));
|
||||
mockAccountManagementService.setup(x => x.getSecurityToken(TypeMoq.It.isValue(accounts[0])))
|
||||
mockAccountManagementService.setup(x => x.getSecurityToken(TypeMoq.It.isValue(accounts[0]), TypeMoq.It.isAny()))
|
||||
.returns(() => Promise.resolve({}));
|
||||
mockAccountManagementService.setup(x => x.updateAccountListEvent)
|
||||
.returns(() => () => { return undefined; } );
|
||||
|
||||
2
src/typings/globals/slickgrid/index.d.ts
vendored
2
src/typings/globals/slickgrid/index.d.ts
vendored
@@ -830,7 +830,7 @@ declare namespace Slick {
|
||||
* @param newData New databinding source using a regular JavaScript array..
|
||||
* @param scrollToTop If true, the grid will reset the vertical scroll position to the top of the grid.
|
||||
**/
|
||||
public setData(newData: T[], scrollToTop?: boolean): void;
|
||||
public setData(newData: T[], scrollToTop: boolean): void;
|
||||
|
||||
/**
|
||||
* Sets a new source for databinding and removes all rendered rows. Note that this doesn't render the new rows - you can follow it with a call to render() to do that.
|
||||
|
||||
@@ -137,6 +137,7 @@ import { IScriptingService, ScriptingService } from 'sql/services/scripting/scri
|
||||
import { IAdminService, AdminService } from 'sql/parts/admin/common/adminService';
|
||||
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
|
||||
import { JobManagementService } from 'sql/parts/jobManagement/common/jobManagementService';
|
||||
import { IDacFxService, DacFxService } from 'sql/services/dacfx/dacFxService';
|
||||
import { IBackupService, IBackupUiService } from 'sql/parts/disasterRecovery/backup/common/backupService';
|
||||
import { BackupService, BackupUiService } from 'sql/parts/disasterRecovery/backup/common/backupServiceImp';
|
||||
import { IRestoreDialogController, IRestoreService } from 'sql/parts/disasterRecovery/restore/common/restoreService';
|
||||
@@ -557,6 +558,8 @@ export class Workbench extends Disposable implements IPartService {
|
||||
serviceCollection.set(IServerGroupController, this.instantiationService.createInstance(ServerGroupController));
|
||||
serviceCollection.set(ICredentialsService, this.instantiationService.createInstance(CredentialsService));
|
||||
serviceCollection.set(IResourceProviderService, this.instantiationService.createInstance(ResourceProviderService));
|
||||
let accountManagementService = this.instantiationService.createInstance(AccountManagementService, undefined);
|
||||
serviceCollection.set(IAccountManagementService, accountManagementService);
|
||||
let connectionManagementService = this.instantiationService.createInstance(ConnectionManagementService, undefined, undefined);
|
||||
serviceCollection.set(IConnectionManagementService, connectionManagementService);
|
||||
serviceCollection.set(ISerializationService, this.instantiationService.createInstance(SerializationService));
|
||||
@@ -577,8 +580,6 @@ export class Workbench extends Disposable implements IPartService {
|
||||
serviceCollection.set(IFileBrowserService, this.instantiationService.createInstance(FileBrowserService));
|
||||
serviceCollection.set(IFileBrowserDialogController, this.instantiationService.createInstance(FileBrowserDialogController));
|
||||
serviceCollection.set(IInsightsDialogService, this.instantiationService.createInstance(InsightsDialogService));
|
||||
let accountManagementService = this.instantiationService.createInstance(AccountManagementService, undefined);
|
||||
serviceCollection.set(IAccountManagementService, accountManagementService);
|
||||
let notebookService = this.instantiationService.createInstance(NotebookService);
|
||||
serviceCollection.set(INotebookService, notebookService);
|
||||
serviceCollection.set(IAccountPickerService, this.instantiationService.createInstance(AccountPickerService));
|
||||
@@ -586,6 +587,8 @@ export class Workbench extends Disposable implements IPartService {
|
||||
// {{SQL CARBON EDIT}}
|
||||
serviceCollection.set(ICommandLineProcessing, this.instantiationService.createInstance(CommandLineService));
|
||||
// {{SQL CARBON EDIT}}
|
||||
serviceCollection.set(IDacFxService, this.instantiationService.createInstance(DacFxService));
|
||||
|
||||
this._register(toDisposable(() => connectionManagementService.shutdown()));
|
||||
this._register(toDisposable(() => accountManagementService.shutdown()));
|
||||
this._register(toDisposable(() => notebookService.shutdown()));
|
||||
|
||||
Reference in New Issue
Block a user