mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-18 02:51:36 -05:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09b1554040 | ||
|
|
b790d70089 | ||
|
|
4f341a0989 | ||
|
|
094562ea9c | ||
|
|
fd7ab62a77 | ||
|
|
b98983758f | ||
|
|
56f8aa8b85 | ||
|
|
11304d9090 | ||
|
|
9f9d84b4ee | ||
|
|
d4a9878c29 | ||
|
|
d1b18a2ee6 | ||
|
|
a8d851b1ed |
@@ -14,9 +14,7 @@ import { NetworkUtils } from './networkUtils';
|
||||
*/
|
||||
export enum HttpMethod {
|
||||
GET = 'get',
|
||||
POST = 'post',
|
||||
PUT = 'put',
|
||||
DELETE = 'delete'
|
||||
POST = 'post'
|
||||
}
|
||||
|
||||
export enum HttpStatus {
|
||||
@@ -83,40 +81,6 @@ export class HttpClient implements INetworkModule {
|
||||
return networkRequestViaHttps(url, HttpMethod.POST, options, this.customAgentOptions as https.AgentOptions, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Http Put request
|
||||
* @param url
|
||||
* @param options
|
||||
*/
|
||||
async sendPutRequestAsync<T>(
|
||||
url: string,
|
||||
options?: NetworkRequestOptions,
|
||||
cancellationToken?: number
|
||||
): Promise<NetworkResponse<T>> {
|
||||
if (this.proxyUrl) {
|
||||
return networkRequestViaProxy(url, this.proxyUrl, HttpMethod.PUT, options, this.customAgentOptions as http.AgentOptions, cancellationToken);
|
||||
} else {
|
||||
return networkRequestViaHttps(url, HttpMethod.PUT, options, this.customAgentOptions as https.AgentOptions, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Http Delete request
|
||||
* @param url
|
||||
* @param options
|
||||
*/
|
||||
async sendDeleteRequestAsync<T>(
|
||||
url: string,
|
||||
options?: NetworkRequestOptions
|
||||
): Promise<NetworkResponse<T>> {
|
||||
if (this.proxyUrl) {
|
||||
return networkRequestViaProxy(url, this.proxyUrl, HttpMethod.DELETE, options, this.customAgentOptions as http.AgentOptions);
|
||||
} else {
|
||||
return networkRequestViaHttps(url, HttpMethod.DELETE, options, this.customAgentOptions as https.AgentOptions);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const networkRequestViaProxy = <T>(
|
||||
@@ -150,7 +114,7 @@ const networkRequestViaProxy = <T>(
|
||||
|
||||
// compose a request string for the socket
|
||||
let postRequestStringContent: string = '';
|
||||
if (httpMethod === HttpMethod.POST || httpMethod === HttpMethod.PUT) {
|
||||
if (httpMethod === HttpMethod.POST) {
|
||||
const body = options?.body || '';
|
||||
postRequestStringContent =
|
||||
'Content-Type: application/x-www-form-urlencoded\r\n' +
|
||||
@@ -283,7 +247,6 @@ const networkRequestViaHttps = <T>(
|
||||
timeout?: number
|
||||
): Promise<NetworkResponse<T>> => {
|
||||
const isPostRequest = httpMethod === HttpMethod.POST;
|
||||
const isPutRequest = httpMethod === HttpMethod.PUT;
|
||||
const body: string = options?.body || '';
|
||||
const url = new URL(urlString);
|
||||
const optionHeaders = options?.headers || {} as Record<string, string>;
|
||||
@@ -301,7 +264,7 @@ const networkRequestViaHttps = <T>(
|
||||
customOptions.agent = new https.Agent(agentOptions);
|
||||
}
|
||||
|
||||
if (isPostRequest || isPutRequest) {
|
||||
if (isPostRequest) {
|
||||
// needed for post request to work
|
||||
customOptions.headers = {
|
||||
...customOptions.headers,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
|
||||
import { TokenCredentials } from '@azure/ms-rest-js';
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import * as azdata from 'azdata';
|
||||
import { AzureRestResponse, GetResourceGroupsResult, GetSubscriptionsResult, ResourceQueryResult, GetBlobContainersResult, GetFileSharesResult, HttpRequestMethod, GetLocationsResult, GetManagedDatabasesResult, CreateResourceGroupResult, GetBlobsResult, GetStorageAccountAccessKeyResult, AzureAccount, azureResource, AzureAccountProviderMetadata } from 'azurecore';
|
||||
import { EOL } from 'os';
|
||||
@@ -17,19 +18,9 @@ import { AzureResourceGroupService } from './providers/resourceGroup/resourceGro
|
||||
import { BlobServiceClient, StorageSharedKeyCredential } from '@azure/storage-blob';
|
||||
import providerSettings from '../account-provider/providerSettings';
|
||||
import * as Constants from '../constants';
|
||||
import { getProxyEnabledHttpClient } from '../utils';
|
||||
import { HttpClient } from '../account-provider/auths/httpClient';
|
||||
import { NetworkRequestOptions } from '@azure/msal-common';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export interface HttpClientResponse {
|
||||
body: any;
|
||||
headers: any;
|
||||
status: Number;
|
||||
error: any;
|
||||
}
|
||||
|
||||
function getErrorMessage(error: Error | string): string {
|
||||
return (error instanceof Error) ? error.message : error;
|
||||
}
|
||||
@@ -171,7 +162,7 @@ export async function getLocations(appContext: AppContext, account?: AzureAccoun
|
||||
const path = `/subscriptions/${subscription.id}/locations?api-version=2020-01-01`;
|
||||
const host = getProviderMetadataForAccount(account).settings.armResource.endpoint;
|
||||
const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors, host);
|
||||
result.locations.push(...response.response.body.value);
|
||||
result.locations.push(...response.response.data.value);
|
||||
result.errors.push(...response.errors);
|
||||
} catch (err) {
|
||||
const error = new Error(localize('azure.accounts.getLocations.queryError', "Error fetching locations for account {0} ({1}) subscription {2} ({3}) tenant {4} : {5}",
|
||||
@@ -350,7 +341,6 @@ export async function getSelectedSubscriptions(appContext: AppContext, account?:
|
||||
*/
|
||||
export async function makeHttpRequest(account: AzureAccount, subscription: azureResource.AzureResourceSubscription, path: string, requestType: HttpRequestMethod, requestBody?: any, ignoreErrors: boolean = false, host: string = 'https://management.azure.com', requestHeaders: { [key: string]: string } = {}): Promise<AzureRestResponse> {
|
||||
const result: AzureRestResponse = { response: {}, errors: [] };
|
||||
const httpClient: HttpClient = getProxyEnabledHttpClient();
|
||||
|
||||
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants)) {
|
||||
const error = new Error(invalidAzureAccount);
|
||||
@@ -390,15 +380,15 @@ export async function makeHttpRequest(account: AzureAccount, subscription: azure
|
||||
return result;
|
||||
}
|
||||
|
||||
let reqHeaders = {
|
||||
const reqHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${securityToken.token}`,
|
||||
...requestHeaders
|
||||
}
|
||||
};
|
||||
|
||||
let networkRequestOptions: NetworkRequestOptions = {
|
||||
const config: AxiosRequestConfig = {
|
||||
headers: reqHeaders,
|
||||
body: requestBody
|
||||
validateStatus: () => true // Never throw
|
||||
};
|
||||
|
||||
// Adding '/' if path does not begin with it.
|
||||
@@ -413,23 +403,19 @@ export async function makeHttpRequest(account: AzureAccount, subscription: azure
|
||||
requestUrl = `${account.properties.providerSettings.settings.armResource.endpoint}${path}`;
|
||||
}
|
||||
|
||||
let response;
|
||||
let response: AxiosResponse | undefined;
|
||||
switch (requestType) {
|
||||
case HttpRequestMethod.GET:
|
||||
response = await httpClient.sendGetRequestAsync<any>(requestUrl, {
|
||||
headers: reqHeaders
|
||||
});
|
||||
response = await axios.get(requestUrl, config);
|
||||
break;
|
||||
case HttpRequestMethod.POST:
|
||||
response = await httpClient.sendPostRequestAsync<HttpClientResponse>(requestUrl, networkRequestOptions);
|
||||
response = await axios.post(requestUrl, requestBody, config);
|
||||
break;
|
||||
case HttpRequestMethod.PUT:
|
||||
response = await httpClient.sendPutRequestAsync<HttpClientResponse>(requestUrl, networkRequestOptions);
|
||||
response = await axios.put(requestUrl, requestBody, config);
|
||||
break;
|
||||
case HttpRequestMethod.DELETE:
|
||||
response = await httpClient.sendDeleteRequestAsync<any>(requestUrl, {
|
||||
headers: reqHeaders
|
||||
});
|
||||
response = await axios.delete(requestUrl, config);
|
||||
break;
|
||||
default:
|
||||
const error = new Error(`Unknown RequestType "${requestType}"`);
|
||||
@@ -448,8 +434,9 @@ export async function makeHttpRequest(account: AzureAccount, subscription: azure
|
||||
} else if (response.status < 200 || response.status > 299) {
|
||||
let errorMessage: string[] = [];
|
||||
errorMessage.push(response.status.toString());
|
||||
if (response.body && response.body.error) {
|
||||
errorMessage.push(`${response.body.error.code} : ${response.body.error.message}`);
|
||||
errorMessage.push(response.statusText);
|
||||
if (response.data && response.data.error) {
|
||||
errorMessage.push(`${response.data.error.code} : ${response.data.error.message}`);
|
||||
}
|
||||
const error = new Error(errorMessage.join(EOL));
|
||||
if (!ignoreErrors) {
|
||||
@@ -468,7 +455,7 @@ export async function getManagedDatabases(account: AzureAccount, subscription: a
|
||||
const host = getProviderMetadataForAccount(account).settings.armResource.endpoint;
|
||||
const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors, host);
|
||||
return {
|
||||
databases: response?.response?.body?.value ?? [],
|
||||
databases: response?.response?.data?.value ?? [],
|
||||
errors: response.errors ? response.errors : []
|
||||
};
|
||||
}
|
||||
@@ -478,7 +465,7 @@ export async function getBlobContainers(account: AzureAccount, subscription: azu
|
||||
const host = getProviderMetadataForAccount(account).settings.armResource.endpoint;
|
||||
const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors, host);
|
||||
return {
|
||||
blobContainers: response?.response?.body?.value ?? [],
|
||||
blobContainers: response?.response?.data?.value ?? [],
|
||||
errors: response.errors ? response.errors : []
|
||||
};
|
||||
}
|
||||
@@ -488,7 +475,7 @@ export async function getFileShares(account: AzureAccount, subscription: azureRe
|
||||
const host = getProviderMetadataForAccount(account).settings.armResource.endpoint;
|
||||
const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors, host);
|
||||
return {
|
||||
fileShares: response?.response?.body?.value ?? [],
|
||||
fileShares: response?.response?.data?.value ?? [],
|
||||
errors: response.errors ? response.errors : []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "4.6.0.24",
|
||||
"version": "4.6.1.4",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-net7.0.zip",
|
||||
"Windows_64": "win-x64-net7.0.zip",
|
||||
|
||||
@@ -286,14 +286,17 @@ export abstract class ObjectManagementDialogBase<ObjectInfoType extends ObjectMa
|
||||
protected createDropdown(ariaLabel: string, values: string[], value: string | undefined, enabled: boolean = true, width: number = DefaultInputWidth): azdata.DropDownComponent {
|
||||
// Automatically add an empty item to the beginning of the list if the current value is not specified.
|
||||
// This is needed when no meaningful default value can be provided.
|
||||
// Create a new array so that the original array isn't modified.
|
||||
const dropdownValues = [];
|
||||
dropdownValues.push(...values);
|
||||
if (!value) {
|
||||
values.unshift('');
|
||||
dropdownValues.unshift('');
|
||||
}
|
||||
return this.modelView.modelBuilder.dropDown().withProps({
|
||||
ariaLabel: ariaLabel,
|
||||
values: values,
|
||||
values: dropdownValues,
|
||||
value: value,
|
||||
width: DefaultInputWidth,
|
||||
width: width,
|
||||
enabled: enabled
|
||||
}).component();
|
||||
}
|
||||
|
||||
@@ -1,27 +1,49 @@
|
||||
# Microsoft SQL Server Database Projects for Azure Data Studio and VS Code
|
||||
# SQL Database Projects for Azure Data Studio and VS Code
|
||||
|
||||
## Overview
|
||||
|
||||
Microsoft SQL Server Database Projects for Azure Data Studio and VS Code provides a way to design, edit, and publish schemas for SQL databases from a source controlled project.
|
||||
SQL Database Projects for Azure Data Studio and VS Code provides a way to design, edit, and publish schemas for SQL databases from a source controlled project. For a complete development workflow, build and deploy your database projects in CI/CD pipelines, such as [GitHub Actions](https://github.com/azure/sql-action) or Azure DevOps.
|
||||
|
||||
### VS Code
|
||||
Learn more about the SQL Database Projects extension in the documentation: https://aka.ms/azuredatastudio-sqlprojects
|
||||
|
||||
This extension is bundled into the `SQL Server (MSSQL)` extension for VS Code and will be installed automatically when that extension is updated or installed.
|
||||
## Features
|
||||
|
||||
### Azure Data Studio
|
||||
- Develop database objects using T-SQL
|
||||
- Store the database schema in source control
|
||||
- Validate object relationships with project build
|
||||
- Publish the database objects to a SQL Server or Azure SQL instance
|
||||
- Publish the database objects to a local development container
|
||||
- Update the database project from a database
|
||||
|
||||
This extension is provided as a separate extension in the marketplace.
|
||||
### Preview Features
|
||||
|
||||
- Microsoft.Build.Sql SDK-style projects
|
||||
- Generate SQL projects from OpenAPI/Swagger specs
|
||||
|
||||
|
||||
Please report issues and feature requests [here.](https://github.com/microsoft/azuredatastudio/issues)
|
||||

|
||||
|
||||
|
||||
## Getting Started with Database Projects
|
||||
|
||||
* Create a new database project by going to the `Database Projects` view or by searching `Database Projects: New` in the command palette.
|
||||
* Existing database projects can be opened by going to the `Database Projects` view or by searching `Database Projects: Open existing` in the command palette.
|
||||
* Start from an existing database by using the `Create Project from Database` from the command palette or database context menu.
|
||||
* Start from an existing database by using `Create Project from Database` from the command palette or database context menu.
|
||||
* Start from an OpenAPI/Swagger spec by using the `Generate SQL Project from OpenAPI/Swagger spec` command (Preview).
|
||||
|
||||
## Settings
|
||||
|
||||
### General Settings
|
||||
- `sqlDatabaseProjects.dotnetSDK Location`: The path to the folder containing the `dotnet` folder for the .NET SDK. If not set, the extension will attempt to find the .NET SDK on the system.
|
||||
- `sqlDatabaseProjects.microsoftBuildSqlVersion`: Version of Microsoft.Build.Sql binaries used when building SQL projects that are not SDK-style SQL projects. If not set, the extension will use Microsoft.Build.Sql 0.1.9-preview.
|
||||
- `sqlDatabaseProjects.netCoreDoNotAsk`: When true, no longer prompts to install .NET SDK when a supported installation is not found.
|
||||
- `sqlDatabaseProjects.collapseProjectNodes`: Option to set the default state of the project nodes in the database projects view to collapsed. If not set, the extension will default to expanded.
|
||||
|
||||
### AutoRest Settings (preview)
|
||||
|
||||
- `sqlDatabaseProjects.nodejsDoNotAsk`: When true, no longer prompts to install Node.js when a supported installation is not found.
|
||||
- `sqlDatabaseProjects.autorestSqlVersion`: Version of AutoRest.sql to use for generating SQL projects from OpenAPI/Swagger specs. If not set, the extension will use the latest version.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
@@ -34,6 +56,10 @@ This extensions collects telemetry data, which is used to help understand how to
|
||||
|
||||
To learn more about our Privacy Statement visit [this link](https://go.microsoft.com/fwlink/?LinkID=824704).
|
||||
|
||||
## Feedback
|
||||
|
||||
Please report issues and feature requests [here.](https://github.com/microsoft/azuredatastudio/issues)
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
BIN
extensions/sql-database-projects/images/readme-sqlproj.png
Normal file
BIN
extensions/sql-database-projects/images/readme-sqlproj.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
@@ -2,7 +2,7 @@
|
||||
"name": "sql-database-projects",
|
||||
"displayName": "SQL Database Projects",
|
||||
"description": "Enables users to develop and publish database schemas for MSSQL Databases",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.1",
|
||||
"publisher": "Microsoft",
|
||||
"preview": false,
|
||||
"engines": {
|
||||
|
||||
@@ -298,12 +298,17 @@ export class ProjectsController {
|
||||
argument: this.buildHelper.constructBuildArguments(project.projectFilePath, this.buildHelper.extensionBuildDirPath, project.sqlProjStyle)
|
||||
};
|
||||
|
||||
const crossPlatCompatible: boolean = await Project.checkPromptCrossPlatStatus(project, true /* blocking prompt */);
|
||||
try {
|
||||
const crossPlatCompatible: boolean = await Project.checkPromptCrossPlatStatus(project, true /* blocking prompt */);
|
||||
|
||||
if (!crossPlatCompatible) {
|
||||
// user rejected updating for cross-plat
|
||||
void vscode.window.showErrorMessage(constants.projectNeedsUpdatingForCrossPlat(project.projectFileName));
|
||||
return ''
|
||||
if (!crossPlatCompatible) {
|
||||
// user rejected updating for cross-plat
|
||||
void vscode.window.showErrorMessage(constants.projectNeedsUpdatingForCrossPlat(project.projectFileName));
|
||||
return ''
|
||||
}
|
||||
} catch (error) {
|
||||
void vscode.window.showErrorMessage(utils.getErrorMessage(error));
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -10,6 +10,7 @@ import type * as azdataType from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as mssql from 'mssql';
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import { Uri, window } from 'vscode';
|
||||
import { EntryType, IDatabaseReferenceProjectEntry, ISqlProject, ItemType } from 'sqldbproj';
|
||||
import { DataSource } from './dataSources/dataSources';
|
||||
@@ -116,6 +117,10 @@ export class Project implements ISqlProject {
|
||||
return this._sqlProjStyle;
|
||||
}
|
||||
|
||||
public get sqlProjStyleName(): string {
|
||||
return this.sqlProjStyle === ProjectType.SdkStyle ? 'SdkStyle' : 'LegacyStyle';
|
||||
}
|
||||
|
||||
public get isCrossPlatformCompatible(): boolean {
|
||||
return this._isCrossPlatformCompatible;
|
||||
}
|
||||
@@ -184,11 +189,17 @@ export class Project implements ISqlProject {
|
||||
}
|
||||
} else {
|
||||
// use "void" with a .then() to not block the UI thread while prompting the user
|
||||
void window.showErrorMessage(constants.updateProjectForCrossPlatform(project.projectFileName), constants.yesString, constants.noString).then(async (result) => {
|
||||
if (result === constants.yesString) {
|
||||
await project.updateProjectForCrossPlatform();
|
||||
void window.showErrorMessage(constants.updateProjectForCrossPlatform(project.projectFileName), constants.yesString, constants.noString).then(
|
||||
async (result) => {
|
||||
if (result === constants.yesString) {
|
||||
try {
|
||||
await project.updateProjectForCrossPlatform();
|
||||
} catch (error) {
|
||||
void window.showErrorMessage(utils.getErrorMessage(utils.getErrorMessage(error)));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
return project.isCrossPlatformCompatible;
|
||||
@@ -439,6 +450,18 @@ export class Project implements ISqlProject {
|
||||
|
||||
TelemetryReporter.sendActionEvent(TelemetryViews.ProjectController, TelemetryActions.updateProjectForRoundtrip);
|
||||
|
||||
// due to bug in DacFx.Projects, if a backup file already exists this will fail
|
||||
// workaround is to rename the existing backup
|
||||
|
||||
if (await utils.exists(this.projectFilePath + '_backup')) {
|
||||
let counter = 2;
|
||||
while (await utils.exists(this.projectFilePath + '_backup' + counter)) {
|
||||
counter++;
|
||||
}
|
||||
|
||||
await fs.rename(this.projectFilePath + '_backup', this.projectFilePath + '_backup' + counter);
|
||||
}
|
||||
|
||||
const result = await this.sqlProjService.updateProjectForCrossPlatform(this.projectFilePath);
|
||||
this.throwIfFailed(result);
|
||||
|
||||
@@ -475,18 +498,23 @@ export class Project implements ISqlProject {
|
||||
|
||||
//#region SQL object scripts
|
||||
|
||||
public async addSqlObjectScript(relativePath: string): Promise<void> {
|
||||
public async addSqlObjectScript(relativePath: string, reloadAfter: boolean = true): Promise<void> {
|
||||
const result = await this.sqlProjService.addSqlObjectScript(this.projectFilePath, relativePath);
|
||||
this.throwIfFailed(result);
|
||||
|
||||
await this.readFilesInProject();
|
||||
await this.readFolders();
|
||||
if (reloadAfter) {
|
||||
await this.readFilesInProject();
|
||||
await this.readFolders();
|
||||
}
|
||||
}
|
||||
|
||||
public async addSqlObjectScripts(relativePaths: string[]): Promise<void> {
|
||||
for (const path of relativePaths) {
|
||||
await this.addSqlObjectScript(path);
|
||||
await this.addSqlObjectScript(path, false /* reloadAfter */);
|
||||
}
|
||||
|
||||
await this.readFilesInProject();
|
||||
await this.readFolders();
|
||||
}
|
||||
|
||||
public async deleteSqlObjectScript(relativePath: string): Promise<void> {
|
||||
|
||||
@@ -215,6 +215,11 @@ declare module 'sqldbproj' {
|
||||
*/
|
||||
getDatabaseDefaultCollation(): string;
|
||||
|
||||
/**
|
||||
* Type of .sqlproj file, either "SdkStyle" or "LegacyStyle"
|
||||
*/
|
||||
readonly sqlProjStyleName: string;
|
||||
|
||||
/**
|
||||
* Path where dacpac is output to after a successful build
|
||||
*/
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ProjectType } from 'mssql';
|
||||
|
||||
const buildDirectory = 'BuildDirectory';
|
||||
const sdkName = 'Microsoft.Build.Sql';
|
||||
const microsoftBuildSqlDefaultVersion = '0.1.9-preview'; // default version of Microsoft.Build.Sql nuget to use for building legacy style projects
|
||||
const microsoftBuildSqlDefaultVersion = '0.1.9-preview'; // default version of Microsoft.Build.Sql nuget to use for building legacy style projects, update in README when updating this
|
||||
|
||||
const buildFiles: string[] = [
|
||||
'Microsoft.Data.SqlClient.dll',
|
||||
|
||||
@@ -43,7 +43,7 @@ export async function getLocations(account: azdata.Account, subscription: Subscr
|
||||
|
||||
const path = `/subscriptions/${subscription.id}/providers/Microsoft.DataMigration?api-version=${ARM_MGMT_API_VERSION}`;
|
||||
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
||||
const dataMigrationResourceProvider = (await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host))?.response?.body;
|
||||
const dataMigrationResourceProvider = (await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host))?.response?.data;
|
||||
const sqlMigratonResource = dataMigrationResourceProvider?.resourceTypes?.find((r: any) => r.resourceType === 'SqlMigrationServices');
|
||||
const sqlMigrationResourceLocations = sqlMigratonResource?.locations ?? [];
|
||||
if (response.errors?.length > 0) {
|
||||
@@ -245,8 +245,8 @@ export async function getAvailableSqlDatabaseServers(account: azdata.Account, su
|
||||
.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
sortResourceArrayByName(response.response.body.value);
|
||||
return response.response.body.value;
|
||||
sortResourceArrayByName(response.response.data.value);
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function getAvailableSqlDatabases(account: azdata.Account, subscription: Subscription, resourceGroupName: string, serverName: string): Promise<AzureSqlDatabase[]> {
|
||||
@@ -260,8 +260,8 @@ export async function getAvailableSqlDatabases(account: azdata.Account, subscrip
|
||||
.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
sortResourceArrayByName(response.response.body.value);
|
||||
return response.response.body.value;
|
||||
sortResourceArrayByName(response.response.data.value);
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function getAvailableSqlVMs(account: azdata.Account, subscription: Subscription): Promise<SqlVMServer[]> {
|
||||
@@ -276,8 +276,8 @@ export async function getAvailableSqlVMs(account: azdata.Account, subscription:
|
||||
.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
sortResourceArrayByName(response.response.body.value);
|
||||
return response.response.body.value;
|
||||
sortResourceArrayByName(response.response.data.value);
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function getVMInstanceView(sqlVm: SqlVMServer, account: azdata.Account, subscription: Subscription): Promise<VirtualMachineInstanceView> {
|
||||
@@ -294,7 +294,7 @@ export async function getVMInstanceView(sqlVm: SqlVMServer, account: azdata.Acco
|
||||
|
||||
}
|
||||
|
||||
return response.response.body;
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function getAzureResourceGivenId(account: azdata.Account, subscription: Subscription, id: string, apiVersion: string): Promise<any> {
|
||||
@@ -311,7 +311,7 @@ export async function getAzureResourceGivenId(account: azdata.Account, subscript
|
||||
|
||||
}
|
||||
|
||||
return response.response.body;
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function getComputeVM(sqlVm: SqlVMServer, account: azdata.Account, subscription: Subscription): Promise<any> {
|
||||
@@ -367,8 +367,8 @@ export async function getSqlMigrationServiceById(account: azdata.Account, subscr
|
||||
.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
response.response.body.properties.resourceGroup = getResourceGroupFromId(response.response.body.id);
|
||||
return response.response.body;
|
||||
response.response.data.properties.resourceGroup = getResourceGroupFromId(response.response.data.id);
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function getSqlMigrationServicesByResourceGroup(account: azdata.Account, subscription: Subscription, resouceGroupName: string): Promise<SqlMigrationService[]> {
|
||||
@@ -382,11 +382,11 @@ export async function getSqlMigrationServicesByResourceGroup(account: azdata.Acc
|
||||
.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
sortResourceArrayByName(response.response.body.value);
|
||||
response.response.body.value.forEach((sms: SqlMigrationService) => {
|
||||
sortResourceArrayByName(response.response.data.value);
|
||||
response.response.data.value.forEach((sms: SqlMigrationService) => {
|
||||
sms.properties.resourceGroup = getResourceGroupFromId(sms.id);
|
||||
});
|
||||
return response.response.body.value;
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function getSqlMigrationServices(account: azdata.Account, subscription: Subscription): Promise<SqlMigrationService[]> {
|
||||
@@ -400,11 +400,11 @@ export async function getSqlMigrationServices(account: azdata.Account, subscript
|
||||
.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
sortResourceArrayByName(response.response.body.value);
|
||||
response.response.body.value.forEach((sms: SqlMigrationService) => {
|
||||
sortResourceArrayByName(response.response.data.value);
|
||||
response.response.data.value.forEach((sms: SqlMigrationService) => {
|
||||
sms.properties.resourceGroup = getResourceGroupFromId(sms.id);
|
||||
});
|
||||
return response.response.body.value;
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function createSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, sessionId: string): Promise<SqlMigrationService> {
|
||||
@@ -428,7 +428,7 @@ export async function createSqlMigrationService(account: azdata.Account, subscri
|
||||
let i = 0;
|
||||
for (i = 0; i < maxRetry; i++) {
|
||||
const asyncResponse = await api.makeAzureRestRequest(account, subscription, asyncPath, azurecore.HttpRequestMethod.GET, undefined, true, host);
|
||||
const creationStatus = asyncResponse.response.body.status;
|
||||
const creationStatus = asyncResponse.response.data.status;
|
||||
if (creationStatus === constants.ProvisioningState.Succeeded) {
|
||||
break;
|
||||
} else if (creationStatus === constants.ProvisioningState.Failed) {
|
||||
@@ -439,7 +439,7 @@ export async function createSqlMigrationService(account: azdata.Account, subscri
|
||||
if (i === maxRetry) {
|
||||
throw new Error(constants.DMS_PROVISIONING_FAILED);
|
||||
}
|
||||
return response.response.body;
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function getSqlMigrationServiceAuthKeys(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise<SqlMigrationServiceAuthenticationKeys> {
|
||||
@@ -454,8 +454,8 @@ export async function getSqlMigrationServiceAuthKeys(account: azdata.Account, su
|
||||
throw new Error(message);
|
||||
}
|
||||
return {
|
||||
authKey1: response?.response?.body?.authKey1 ?? '',
|
||||
authKey2: response?.response?.body?.authKey2 ?? ''
|
||||
authKey1: response?.response?.data?.authKey1 ?? '',
|
||||
authKey2: response?.response?.data?.authKey2 ?? ''
|
||||
};
|
||||
}
|
||||
|
||||
@@ -475,8 +475,8 @@ export async function regenerateSqlMigrationServiceAuthKey(account: azdata.Accou
|
||||
throw new Error(message);
|
||||
}
|
||||
return {
|
||||
authKey1: response?.response?.body?.authKey1 ?? '',
|
||||
authKey2: response?.response?.body?.authKey2 ?? ''
|
||||
authKey1: response?.response?.data?.authKey1 ?? '',
|
||||
authKey2: response?.response?.data?.authKey2 ?? ''
|
||||
};
|
||||
}
|
||||
|
||||
@@ -506,7 +506,7 @@ export async function getSqlMigrationServiceMonitoringData(account: azdata.Accou
|
||||
.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
return response.response.body;
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function startDatabaseMigration(
|
||||
@@ -532,7 +532,7 @@ export async function startDatabaseMigration(
|
||||
return {
|
||||
asyncUrl: asyncUrl,
|
||||
status: response.response.status,
|
||||
databaseMigration: response.response.body
|
||||
databaseMigration: response.response.data
|
||||
};
|
||||
}
|
||||
|
||||
@@ -552,7 +552,7 @@ export async function getMigrationDetails(account: azdata.Account, subscription:
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return response.response.body;
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function getServiceMigrations(account: azdata.Account, subscription: Subscription, resourceId: string): Promise<DatabaseMigration[]> {
|
||||
@@ -567,7 +567,7 @@ export async function getServiceMigrations(account: azdata.Account, subscription
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return response.response.body.value;
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function getMigrationTargetInstance(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration): Promise<SqlManagedInstance | SqlVMServer> {
|
||||
@@ -583,7 +583,7 @@ export async function getMigrationTargetInstance(account: azdata.Account, subscr
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return response.response.body;
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function getMigrationAsyncOperationDetails(account: azdata.Account, subscription: Subscription, url: string): Promise<AzureAsyncOperationResource> {
|
||||
@@ -597,7 +597,7 @@ export async function getMigrationAsyncOperationDetails(account: azdata.Account,
|
||||
.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
return response.response.body;
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function startMigrationCutover(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration): Promise<any> {
|
||||
@@ -612,7 +612,7 @@ export async function startMigrationCutover(account: azdata.Account, subscriptio
|
||||
.join(', ');
|
||||
throw new Error(message);
|
||||
}
|
||||
return response.response.body.value;
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function stopMigration(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration): Promise<void> {
|
||||
@@ -718,7 +718,7 @@ export async function validateIrSqlDatabaseMigrationSettings(
|
||||
if (response.errors.length > 0) {
|
||||
throw new Error(response.errors.map(e => e.message).join(','));
|
||||
}
|
||||
return response.response.body;
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function validateIrDatabaseMigrationSettings(
|
||||
@@ -785,7 +785,7 @@ export async function validateIrDatabaseMigrationSettings(
|
||||
if (response.errors.length > 0) {
|
||||
throw new Error(response.errors.map(e => e.message).join(','));
|
||||
}
|
||||
return response.response.body;
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
type SortableAzureResources = AzureProduct | azurecore.azureResource.FileShare | azurecore.azureResource.BlobContainer | azurecore.azureResource.Blob | azurecore.azureResource.AzureResourceSubscription | SqlMigrationService;
|
||||
|
||||
@@ -85,17 +85,6 @@ export const SERVICE_ID = 'connectionManagementService';
|
||||
|
||||
export const IConnectionManagementService = createDecorator<IConnectionManagementService>(SERVICE_ID);
|
||||
|
||||
export interface ConnectionElementMovedParams {
|
||||
source: ConnectionProfile | ConnectionProfileGroup;
|
||||
oldGroupId: string;
|
||||
newGroupId: string;
|
||||
}
|
||||
|
||||
export interface ConnectionProfileEditedParams {
|
||||
profile: ConnectionProfile;
|
||||
oldProfileId: string;
|
||||
}
|
||||
|
||||
export interface IConnectionManagementService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
@@ -107,25 +96,6 @@ export interface IConnectionManagementService {
|
||||
onConnectionChanged: Event<IConnectionParams>;
|
||||
onLanguageFlavorChanged: Event<azdata.DidChangeLanguageFlavorParams>;
|
||||
|
||||
// Event Emitters for async tree
|
||||
/**
|
||||
* Connection Profile events.
|
||||
*/
|
||||
onConnectionProfileCreated: Event<ConnectionProfile>;
|
||||
onConnectionProfileEdited: Event<ConnectionProfileEditedParams>;
|
||||
onConnectionProfileDeleted: Event<ConnectionProfile>;
|
||||
onConnectionProfileMoved: Event<ConnectionElementMovedParams>;
|
||||
onConnectionProfileConnected: Event<ConnectionProfile>;
|
||||
onConnectionProfileDisconnected: Event<ConnectionProfile>;
|
||||
/**
|
||||
* Connection Profile Group events.
|
||||
*/
|
||||
onConnectionProfileGroupCreated: Event<ConnectionProfileGroup>;
|
||||
onConnectionProfileGroupEdited: Event<ConnectionProfileGroup>;
|
||||
onConnectionProfileGroupDeleted: Event<ConnectionProfileGroup>;
|
||||
onConnectionProfileGroupMoved: Event<ConnectionElementMovedParams>;
|
||||
// End of Event Emitters for async tree
|
||||
|
||||
// Properties
|
||||
providerNameToDisplayNameMap: { [providerDisplayName: string]: string };
|
||||
|
||||
@@ -189,8 +159,6 @@ export interface IConnectionManagementService {
|
||||
|
||||
getConnectionGroups(providers?: string[]): ConnectionProfileGroup[];
|
||||
|
||||
getConnectionGroupById(id: string): ConnectionProfileGroup | undefined;
|
||||
|
||||
getRecentConnections(providers?: string[]): ConnectionProfile[];
|
||||
|
||||
clearRecentConnectionsList(): void;
|
||||
|
||||
@@ -126,8 +126,6 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro
|
||||
public getChildren(): (ConnectionProfile | ConnectionProfileGroup)[] {
|
||||
let allChildren: (ConnectionProfile | ConnectionProfileGroup)[] = [];
|
||||
this._childConnections.forEach((conn) => {
|
||||
conn.parent = this;
|
||||
conn.groupId = this.id;
|
||||
allChildren.push(conn);
|
||||
});
|
||||
|
||||
@@ -236,8 +234,4 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro
|
||||
}
|
||||
return subgroups;
|
||||
}
|
||||
|
||||
public static createConnectionProfileGroup(group: IConnectionProfileGroup, parentGroup: ConnectionProfileGroup | undefined): ConnectionProfileGroup {
|
||||
return new ConnectionProfileGroup(group.name, parentGroup, group.id, group.color, group.description);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,17 +26,6 @@ export class TestConnectionManagementService implements IConnectionManagementSer
|
||||
onDeleteConnectionProfile = undefined!;
|
||||
onLanguageFlavorChanged = undefined!;
|
||||
|
||||
public onConnectionProfileCreated: Event<any> = Event.None;
|
||||
public onConnectionProfileEdited: Event<any> = Event.None;
|
||||
public onConnectionProfileDeleted: Event<any> = Event.None;
|
||||
public onConnectionProfileMoved: Event<any> = Event.None;
|
||||
public onConnectionProfileConnected: Event<any> = Event.None;
|
||||
public onConnectionProfileDisconnected: Event<any> = Event.None;
|
||||
public onConnectionProfileGroupCreated: Event<any> = Event.None;
|
||||
public onConnectionProfileGroupEdited: Event<any> = Event.None;
|
||||
public onConnectionProfileGroupDeleted: Event<any> = Event.None;
|
||||
public onConnectionProfileGroupMoved: Event<any> = Event.None;
|
||||
|
||||
public get onConnect(): Event<any> {
|
||||
return Event.None;
|
||||
}
|
||||
@@ -101,10 +90,6 @@ export class TestConnectionManagementService implements IConnectionManagementSer
|
||||
return [];
|
||||
}
|
||||
|
||||
getConnectionGroupById(id: string): ConnectionProfileGroup | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getActiveConnections(providers?: string[]): ConnectionProfile[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -173,10 +173,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
this._register(this._tree.onContextMenu(e => this.onTreeNodeContextMenu(e)));
|
||||
this._register(this._tree.onMouseDblClick(async e => { await this.onTreeNodeDoubleClick(e.element); }));
|
||||
this._register(this._connectionManagementService.onConnectionChanged(() => {
|
||||
// No need to refresh AsyncServerTree when a connection is edited or added
|
||||
if (!(this._tree instanceof AsyncServerTree)) {
|
||||
this.refreshTree().catch(err => errors.onUnexpectedError);
|
||||
}
|
||||
this.refreshTree().catch(err => errors.onUnexpectedError);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -188,19 +185,11 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
this.handleAddConnectionProfile(newProfile).catch(errors.onUnexpectedError);
|
||||
}));
|
||||
this._register(this._connectionManagementService.onDeleteConnectionProfile(() => {
|
||||
// No need to refresh AsyncServerTree when a connection is deleted
|
||||
if (!(this._tree instanceof AsyncServerTree)) {
|
||||
this.refreshTree().catch(errors.onUnexpectedError);
|
||||
}
|
||||
this.refreshTree().catch(errors.onUnexpectedError);
|
||||
}));
|
||||
|
||||
this._register(this._connectionManagementService.onDisconnect(async (connectionParams) => {
|
||||
if (!(this._tree instanceof AsyncServerTree)) {
|
||||
if (this.isObjectExplorerConnectionUri(connectionParams.connectionUri)) {
|
||||
this.deleteObjectExplorerNodeAndRefreshTree(connectionParams.connectionProfile).catch(errors.onUnexpectedError);
|
||||
}
|
||||
} else {
|
||||
await this.disconnectConnection(<ConnectionProfile>connectionParams.connectionProfile);
|
||||
this._register(this._connectionManagementService.onDisconnect((connectionParams) => {
|
||||
if (this.isObjectExplorerConnectionUri(connectionParams.connectionUri)) {
|
||||
this.deleteObjectExplorerNodeAndRefreshTree(connectionParams.connectionProfile).catch(errors.onUnexpectedError);
|
||||
}
|
||||
}));
|
||||
this._register(this._configurationService.onDidChangeConfiguration(e => {
|
||||
@@ -213,163 +202,13 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
this._register(this._objectExplorerService.onUpdateObjectExplorerNodes(args => {
|
||||
if (args.errorMessage) {
|
||||
this.showError(args.errorMessage);
|
||||
} else if (args.connection) {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
// Rerendering the node to update the badge
|
||||
this._tree.rerender(<ConnectionProfile>args.connection);
|
||||
}
|
||||
}
|
||||
if (args.connection) {
|
||||
this.onObjectExplorerSessionCreated(args.connection).catch(err => errors.onUnexpectedError);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// Add connection profile to parent group and update group children. Then reveal and expand the new connection
|
||||
this._register(this._connectionManagementService.onConnectionProfileCreated(async (newConnection) => {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
const connectionParentGroup = this._tree.getElementById(newConnection.groupId) as ConnectionProfileGroup;
|
||||
if (connectionParentGroup) {
|
||||
connectionParentGroup.connections.push(newConnection);
|
||||
newConnection.parent = connectionParentGroup;
|
||||
newConnection.groupId = connectionParentGroup.id;
|
||||
await this._tree.updateChildren(connectionParentGroup);
|
||||
await this._tree.revealSelectFocusElement(newConnection);
|
||||
await this._tree.expand(newConnection);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Rerender the connection in the tree to update the badge and update the children of the connection.
|
||||
this._register(this._connectionManagementService.onConnectionProfileConnected(async (connectedConnection) => {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
const connectionInTree = this._tree.getElementById(connectedConnection.id);
|
||||
if (connectionInTree) {
|
||||
await this._tree.rerender(connectionInTree);
|
||||
await this._tree.revealSelectFocusElement(connectionInTree);
|
||||
await this._tree.updateChildren(connectionInTree);
|
||||
await this._tree.expand(connectionInTree);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Remove the connection from the parent group and update the parent's children.
|
||||
this._register(this._connectionManagementService.onConnectionProfileDeleted(async (e) => {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
const parentGroup = <ConnectionProfileGroup>this._tree.getElementById(e.groupId);
|
||||
if (parentGroup) {
|
||||
parentGroup.connections = parentGroup.connections.filter(c => c.id !== e.id);
|
||||
await this._tree.updateChildren(parentGroup);
|
||||
await this._tree.revealSelectFocusElement(parentGroup);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
this._register(this._connectionManagementService.onConnectionProfileEdited(async (e) => {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
const oldProfile = <ConnectionProfile>this._tree.getElementById(e.oldProfileId);
|
||||
const oldProfileParent = <ConnectionProfileGroup>this._tree.getElementById(oldProfile.groupId);
|
||||
if (oldProfileParent.id !== e.profile.groupId) {
|
||||
// If the profile was moved to a different group then remove it from the old group and add it to the new group.
|
||||
oldProfileParent.connections = oldProfileParent.connections.filter(c => c.id !== oldProfile.id);
|
||||
await this._tree.updateChildren(oldProfileParent);
|
||||
const newProfileParent = <ConnectionProfileGroup>this._tree.getElementById(e.profile.groupId);
|
||||
newProfileParent.connections.push(e.profile);
|
||||
e.profile.parent = newProfileParent;
|
||||
e.profile.groupId = newProfileParent.id;
|
||||
await this._tree.updateChildren(newProfileParent);
|
||||
await this._tree.revealSelectFocusElement(e.profile);
|
||||
await this._tree.expand(e.profile);
|
||||
} else {
|
||||
// If the profile was not moved to a different group then just update the profile in the group.
|
||||
oldProfileParent.connections[oldProfileParent.connections.findIndex(c => c.id === e.oldProfileId)] = e.profile;
|
||||
e.profile.parent = oldProfileParent;
|
||||
e.profile.groupId = oldProfileParent.id;
|
||||
await this._tree.updateChildren(oldProfileParent)
|
||||
await this._tree.revealSelectFocusElement(e.profile);
|
||||
await this._tree.expand(e.profile);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connectionManagementService.onConnectionProfileMoved(async (e) => {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
const movedConnection = <ConnectionProfile>e.source;
|
||||
const oldParent = <ConnectionProfileGroup>this._tree.getElementById(e.oldGroupId);
|
||||
const newParent = <ConnectionProfileGroup>this._tree.getElementById(e.newGroupId);
|
||||
// Storing the expanded state of children of the moved connection so that they can be expanded after the move.
|
||||
const profileExpandedState = this._tree.getExpandedState(movedConnection);
|
||||
if (oldParent) {
|
||||
oldParent.connections = oldParent.connections.filter(c => c.id !== e.source.id);
|
||||
await this._tree.updateChildren(oldParent);
|
||||
}
|
||||
if (newParent) {
|
||||
newParent.connections.push(movedConnection);
|
||||
movedConnection.parent = newParent;
|
||||
movedConnection.groupId = newParent.id;
|
||||
await this._tree.updateChildren(newParent);
|
||||
}
|
||||
const newConnection = this._tree.getElementById(movedConnection.id);
|
||||
if (newConnection) {
|
||||
await this._tree.revealSelectFocusElement(newConnection);
|
||||
// Expanding the previously expanded children of the moved connection after the move.
|
||||
await this._tree.expandElements(profileExpandedState);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connectionManagementService.onConnectionProfileGroupDeleted(async (e) => {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
const parent = <ConnectionProfileGroup>this._tree.getElementById(e.parentId);
|
||||
parent.children = parent.children.filter(c => c.id !== e.id);
|
||||
await this._tree.updateChildren(parent);
|
||||
await this._tree.revealSelectFocusElement(parent);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connectionManagementService.onConnectionProfileGroupCreated(async (e) => {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
let parent = <ConnectionProfileGroup>this._tree.getElementById(e.parentId);
|
||||
if (!parent) {
|
||||
parent = this._tree.getInput(); // If the parent is not found then add the group to the root.
|
||||
}
|
||||
parent.children.push(e);
|
||||
e.parent = parent;
|
||||
e.parentId = parent.id;
|
||||
await this._tree.updateChildren(parent);
|
||||
await this._tree.revealSelectFocusElement(e);
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connectionManagementService.onConnectionProfileGroupEdited(async (e) => {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
const newParent = <ConnectionProfileGroup>this._tree.getElementById(e.parentId);
|
||||
if (newParent) {
|
||||
newParent.children[newParent.children.findIndex(c => c.id === e.id)] = e;
|
||||
await this._tree.updateChildren(newParent);
|
||||
await this._tree.revealSelectFocusElement(e);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._connectionManagementService.onConnectionProfileGroupMoved(async (e) => {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
const movedGroup = <ConnectionProfileGroup>e.source;
|
||||
const oldParent = <ConnectionProfileGroup>this._tree.getElementById(e.oldGroupId);
|
||||
const newParent = <ConnectionProfileGroup>this._tree.getElementById(e.newGroupId);
|
||||
// Storing the expanded state of children of the moved group so that they can be expanded after the move.
|
||||
const profileExpandedState = this._tree.getExpandedState(movedGroup);
|
||||
oldParent.children = oldParent.children.filter(c => c.id !== movedGroup.id);
|
||||
await this._tree.updateChildren(oldParent);
|
||||
newParent.children.push(movedGroup);
|
||||
(<ConnectionProfileGroup>movedGroup).parent = newParent;
|
||||
(<ConnectionProfileGroup>movedGroup).parentId = newParent.id;
|
||||
await this._tree.updateChildren(newParent);
|
||||
await this._tree.revealSelectFocusElement(movedGroup);
|
||||
// Expanding the previously expanded children of the moved group after the move.
|
||||
this._tree.expandElements(profileExpandedState);
|
||||
}
|
||||
}));
|
||||
|
||||
return new Promise<void>(async (resolve, reject) => {
|
||||
await this.refreshTree();
|
||||
const root = <ConnectionProfileGroup>this._tree!.getInput();
|
||||
@@ -377,10 +216,9 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
const expandGroups = this._configurationService.getValue<{ autoExpand: boolean }>(SERVER_GROUP_CONFIG).autoExpand;
|
||||
if (expandGroups) {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
const subGroups = ConnectionProfileGroup.getSubgroups(root);
|
||||
for (let group of subGroups) {
|
||||
await this._tree.expand(group);
|
||||
}
|
||||
await Promise.all(ConnectionProfileGroup.getSubgroups(root).map(subgroup => {
|
||||
return this._tree!.expand(subgroup);
|
||||
}));
|
||||
} else {
|
||||
await this._tree!.expandAll(ConnectionProfileGroup.getSubgroups(root));
|
||||
}
|
||||
@@ -408,6 +246,26 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
}
|
||||
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
// When new connection groups are added the event is fired with undefined so
|
||||
// we still want to refresh the tree in that case to pick up the changes
|
||||
await this.refreshTree();
|
||||
if (newProfile) {
|
||||
const currentSelections = this._tree.getSelection();
|
||||
const currentSelectedElement = currentSelections && currentSelections.length >= 1 ? currentSelections[0] : undefined;
|
||||
const newProfileIsSelected = currentSelectedElement && currentSelectedElement.id === newProfile.id;
|
||||
// Clear any other selected elements first
|
||||
if (currentSelectedElement && !newProfileIsSelected) {
|
||||
this._tree.setSelection([]);
|
||||
}
|
||||
const newConnectionProfile = this.getConnectionInTreeInput(newProfile.id);
|
||||
if (newConnectionProfile) {
|
||||
// Re-render to update the connection status badge
|
||||
this._tree.rerender(newConnectionProfile);
|
||||
this._tree.setSelection([newConnectionProfile]);
|
||||
this._tree.expand(newConnectionProfile);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (newProfile) {
|
||||
const groups = this._connectionManagementService.getConnectionGroups();
|
||||
@@ -466,44 +324,17 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
}
|
||||
}
|
||||
|
||||
private async disconnectConnection(profile: ConnectionProfile, deleteConnFromConnectionService: boolean = false): Promise<void> {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
if (deleteConnFromConnectionService) {
|
||||
await this._connectionManagementService.deleteConnection(profile);
|
||||
}
|
||||
const connectionProfile = this.getConnectionInTreeInput(profile.id);
|
||||
|
||||
// For the connection profile, we need to clear the password from the last session if the user doesn't want to save it
|
||||
if (!connectionProfile.savePassword) {
|
||||
connectionProfile.password = '';
|
||||
}
|
||||
// Delete the node from the tree
|
||||
await this._objectExplorerService.deleteObjectExplorerNode(connectionProfile);
|
||||
// Collapse the node
|
||||
await this._tree.collapse(connectionProfile);
|
||||
// Rerendering node to turn the badge red
|
||||
await this._tree.rerender(connectionProfile);
|
||||
connectionProfile.isDisconnecting = true;
|
||||
await this._tree.updateChildren(connectionProfile);
|
||||
connectionProfile.isDisconnecting = false;
|
||||
// Make the connection dirty so that the next expansion will refresh the node
|
||||
await this._tree.makeElementDirty(connectionProfile);
|
||||
await this._tree.revealSelectFocusElement(connectionProfile);
|
||||
}
|
||||
}
|
||||
|
||||
private async onObjectExplorerSessionCreated(connection: IConnectionProfile): Promise<void> {
|
||||
const element = this.getConnectionInTreeInput(connection.id);
|
||||
if (element) {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
await this._tree.rerender(element);
|
||||
await this._tree.revealSelectFocusElement(element);
|
||||
this._tree.rerender(element);
|
||||
} else {
|
||||
await this._tree!.refresh(element);
|
||||
await this._tree!.expand(element);
|
||||
await this._tree!.reveal(element, 0.5);
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
}
|
||||
await this._tree!.expand(element);
|
||||
await this._tree!.reveal(element, 0.5);
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -523,8 +354,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
// Collapse the node before refreshing so the refresh doesn't try to fetch
|
||||
// the children again (which causes it to try and connect)
|
||||
this._tree.collapse(conn);
|
||||
this._tree.rerender(conn);
|
||||
this._tree.makeElementDirty(conn);
|
||||
await this.refreshTree();
|
||||
} else {
|
||||
await this._tree!.collapse(conn);
|
||||
return this._tree!.refresh(conn);
|
||||
@@ -586,7 +416,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
/**
|
||||
* Set tree elements based on the view (recent/active)
|
||||
*/
|
||||
public async showFilteredTree(view: ServerTreeViewView): Promise<void> {
|
||||
public showFilteredTree(view: ServerTreeViewView): void {
|
||||
hide(this.messages!);
|
||||
this._viewKey.set(view);
|
||||
const root = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
|
||||
@@ -600,29 +430,24 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
||||
} else {
|
||||
treeInput = filteredResults[0];
|
||||
}
|
||||
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
await this._tree.setInput(treeInput!);
|
||||
await this._tree.updateChildren(treeInput!);
|
||||
return;
|
||||
}
|
||||
await this._tree.setInput(treeInput!);
|
||||
if (isHidden(this.messages!)) {
|
||||
this._tree.getFocus();
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
for (const subgroup of ConnectionProfileGroup.getSubgroups(treeInput)) {
|
||||
await this._tree.expand(subgroup);
|
||||
this._tree!.setInput(treeInput!).then(async () => {
|
||||
if (isHidden(this.messages!)) {
|
||||
this._tree!.getFocus();
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
await Promise.all(ConnectionProfileGroup.getSubgroups(treeInput!).map(subgroup => {
|
||||
this._tree!.expand(subgroup);
|
||||
}));
|
||||
} else {
|
||||
await this._tree!.expandAll(ConnectionProfileGroup.getSubgroups(treeInput!));
|
||||
}
|
||||
} else {
|
||||
await this._tree!.expandAll(ConnectionProfileGroup.getSubgroups(treeInput!));
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
this._tree.setFocus([]);
|
||||
} else {
|
||||
this._tree!.clearFocus();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
this._tree.setFocus([]);
|
||||
} else {
|
||||
this._tree!.clearFocus();
|
||||
}
|
||||
}
|
||||
}, errors.onUnexpectedError);
|
||||
} else {
|
||||
//no op
|
||||
}
|
||||
|
||||
@@ -270,9 +270,6 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
showFirewallRuleOnError: true
|
||||
};
|
||||
|
||||
if (params && options.params === undefined) {
|
||||
options.params = params;
|
||||
}
|
||||
try {
|
||||
const connectionResult = await this._connectionManagementService.connectAndSaveProfile(connection, uri, options, params && params.input);
|
||||
this._connecting = false;
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils';
|
||||
import {
|
||||
IConnectionManagementService, INewConnectionParams,
|
||||
ConnectionType, IConnectableInput, IConnectionCompletionOptions, IConnectionCallbacks,
|
||||
IConnectionParams, IConnectionResult, RunQueryOnConnectionMode, ConnectionElementMovedParams, ConnectionProfileEditedParams
|
||||
IConnectionParams, IConnectionResult, RunQueryOnConnectionMode
|
||||
} from 'sql/platform/connection/common/connectionManagement';
|
||||
import { ConnectionStore } from 'sql/platform/connection/common/connectionStore';
|
||||
import { ConnectionManagementInfo } from 'sql/platform/connection/common/connectionManagementInfo';
|
||||
@@ -76,17 +76,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
private _connectionGlobalStatus = new ConnectionGlobalStatus(this._notificationService);
|
||||
private _uriToReconnectPromiseMap: { [uri: string]: Promise<IConnectionResult> } = {};
|
||||
|
||||
private _onConnectionProfileCreated = new Emitter<ConnectionProfile>();
|
||||
private _onConnectionProfileDeleted = new Emitter<ConnectionProfile>();
|
||||
private _onConnectionProfileEdited = new Emitter<ConnectionProfileEditedParams>();
|
||||
private _onConnectionProfileMoved = new Emitter<ConnectionElementMovedParams>();
|
||||
private _onConnectionProfileConnected = new Emitter<ConnectionProfile>();
|
||||
private _onConnectionProfileDisconnected = new Emitter<ConnectionProfile>();
|
||||
private _onConnectionProfileGroupCreated = new Emitter<ConnectionProfileGroup>();
|
||||
private _onConnectionProfileGroupDeleted = new Emitter<ConnectionProfileGroup>();
|
||||
private _onConnectionProfileGroupEdited = new Emitter<ConnectionProfileGroup>();
|
||||
private _onConnectionProfileGroupMoved = new Emitter<ConnectionElementMovedParams>();
|
||||
|
||||
private _mementoContext: Memento;
|
||||
private _mementoObj: MementoObject;
|
||||
private _connectionStore: ConnectionStore;
|
||||
@@ -202,49 +191,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
return this._onLanguageFlavorChanged.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Async tree event emitters
|
||||
*/
|
||||
public get onConnectionProfileCreated(): Event<ConnectionProfile> {
|
||||
return this._onConnectionProfileCreated.event;
|
||||
}
|
||||
|
||||
public get onConnectionProfileEdited(): Event<ConnectionProfileEditedParams> {
|
||||
return this._onConnectionProfileEdited.event;
|
||||
}
|
||||
|
||||
public get onConnectionProfileDeleted(): Event<ConnectionProfile> {
|
||||
return this._onConnectionProfileDeleted.event;
|
||||
}
|
||||
|
||||
public get onConnectionProfileMoved(): Event<ConnectionElementMovedParams> {
|
||||
return this._onConnectionProfileMoved.event;
|
||||
}
|
||||
|
||||
public get onConnectionProfileConnected(): Event<ConnectionProfile> {
|
||||
return this._onConnectionProfileConnected.event;
|
||||
}
|
||||
|
||||
public get onConnectionProfileDisconnected(): Event<ConnectionProfile> {
|
||||
return this._onConnectionProfileDisconnected.event;
|
||||
}
|
||||
|
||||
public get onConnectionProfileGroupCreated(): Event<ConnectionProfileGroup> {
|
||||
return this._onConnectionProfileGroupCreated.event;
|
||||
}
|
||||
|
||||
public get onConnectionProfileGroupDeleted(): Event<ConnectionProfileGroup> {
|
||||
return this._onConnectionProfileGroupDeleted.event;
|
||||
}
|
||||
|
||||
public get onConnectionProfileGroupEdited(): Event<ConnectionProfileGroup> {
|
||||
return this._onConnectionProfileGroupEdited.event;
|
||||
}
|
||||
|
||||
public get onConnectionProfileGroupMoved(): Event<ConnectionElementMovedParams> {
|
||||
return this._onConnectionProfileGroupMoved.event;
|
||||
}
|
||||
|
||||
public get providerNameToDisplayNameMap(): { readonly [providerDisplayName: string]: string } {
|
||||
return this._providerNameToDisplayNameMap;
|
||||
}
|
||||
@@ -416,9 +362,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
connectionType: this._connectionStatusManager.isEditorTypeUri(owner.uri) ? ConnectionType.editor : ConnectionType.default,
|
||||
input: owner,
|
||||
runQueryOnCompletion: RunQueryOnConnectionMode.none,
|
||||
showDashboard: options.showDashboard,
|
||||
isEditConnection: true,
|
||||
oldProfileId: connection.id
|
||||
showDashboard: options.showDashboard
|
||||
};
|
||||
return this.showConnectionDialog(params, options, connection, connectionResult).then(() => {
|
||||
return connectionResult;
|
||||
@@ -595,18 +539,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
|
||||
await this.saveToSettings(uri, connection, matcher).then(value => {
|
||||
this._onAddConnectionProfile.fire(connection);
|
||||
if (isEdit) {
|
||||
this._onConnectionProfileEdited.fire({
|
||||
oldProfileId: options.params.oldProfileId,
|
||||
profile: <ConnectionProfile>connection
|
||||
});
|
||||
} else {
|
||||
if (options.params === undefined) {
|
||||
this._onConnectionProfileConnected.fire(<ConnectionProfile>connection);
|
||||
} else {
|
||||
this._onConnectionProfileCreated.fire(<ConnectionProfile>connection);
|
||||
}
|
||||
}
|
||||
this.doActionsAfterConnectionComplete(value, options);
|
||||
});
|
||||
} else {
|
||||
@@ -784,22 +716,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
}
|
||||
|
||||
public getConnectionGroups(providers?: string[]): ConnectionProfileGroup[] {
|
||||
const groups = this._connectionStore.getConnectionProfileGroups(false, providers);
|
||||
return groups;
|
||||
}
|
||||
|
||||
public getConnectionGroupById(id: string): ConnectionProfileGroup | undefined {
|
||||
const groups = this.getConnectionGroups();
|
||||
for (let group of groups) {
|
||||
if (group.id === id) {
|
||||
return group;
|
||||
}
|
||||
const subgroup = ConnectionProfileGroup.getSubgroups(group).find(g => g.id === id);
|
||||
if (subgroup) {
|
||||
return subgroup;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
return this._connectionStore.getConnectionProfileGroups(false, providers);
|
||||
}
|
||||
|
||||
public getRecentConnections(providers?: string[]): ConnectionProfile[] {
|
||||
@@ -828,15 +745,10 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
}
|
||||
}
|
||||
|
||||
public saveProfileGroup(group: IConnectionProfileGroup): Promise<string> {
|
||||
public saveProfileGroup(profile: IConnectionProfileGroup): Promise<string> {
|
||||
this._telemetryService.sendActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.AddServerGroup);
|
||||
return this._connectionStore.saveProfileGroup(group).then(groupId => {
|
||||
return this._connectionStore.saveProfileGroup(profile).then(groupId => {
|
||||
this._onAddConnectionProfile.fire(undefined);
|
||||
//Getting id for the new profile group
|
||||
group.id = groupId;
|
||||
const parentGroup = this.getConnectionGroupById(group.parentId);
|
||||
this._onConnectionProfileGroupCreated.fire(ConnectionProfileGroup.createConnectionProfileGroup(group, parentGroup));
|
||||
|
||||
return groupId;
|
||||
});
|
||||
}
|
||||
@@ -1312,30 +1224,21 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
public onIntelliSenseCacheComplete(handle: number, connectionUri: string): void {
|
||||
}
|
||||
|
||||
public async changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void> {
|
||||
public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void> {
|
||||
this._telemetryService.sendActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.MoveServerConnection);
|
||||
await this._connectionStore.changeGroupIdForConnectionGroup(source, target);
|
||||
this._onConnectionProfileGroupMoved.fire({
|
||||
source: source,
|
||||
oldGroupId: source.parentId,
|
||||
newGroupId: target.id
|
||||
});
|
||||
return this._connectionStore.changeGroupIdForConnectionGroup(source, target);
|
||||
}
|
||||
|
||||
public async changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise<void> {
|
||||
const oldProfileId = source.groupId;
|
||||
public changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise<void> {
|
||||
let id = Utils.generateUri(source);
|
||||
this._telemetryService.sendActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.MoveServerGroup);
|
||||
await this._connectionStore.changeGroupIdForConnection(source, targetGroupId)
|
||||
this._onAddConnectionProfile.fire(source);
|
||||
if (id && targetGroupId) {
|
||||
source.groupId = targetGroupId;
|
||||
}
|
||||
this.changeConnectionUri(Utils.generateUri(source), id);
|
||||
this._onConnectionProfileMoved.fire({
|
||||
source: source,
|
||||
oldGroupId: oldProfileId,
|
||||
newGroupId: targetGroupId,
|
||||
return this._connectionStore.changeGroupIdForConnection(source, targetGroupId).then(result => {
|
||||
this._onAddConnectionProfile.fire(source);
|
||||
if (id && targetGroupId) {
|
||||
source.groupId = targetGroupId;
|
||||
}
|
||||
// change the connection uri with the new group so that connection does not appear as disconnected in OE.
|
||||
this.changeConnectionUri(Utils.generateUri(source), id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1605,7 +1508,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
public editGroup(group: ConnectionProfileGroup): Promise<void> {
|
||||
return this._connectionStore.editGroup(group).then(groupId => {
|
||||
this._onAddConnectionProfile.fire(undefined);
|
||||
this._onConnectionProfileGroupEdited.fire(group);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1613,7 +1515,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
* Deletes a connection from registered servers.
|
||||
* Disconnects a connection before removing from settings.
|
||||
*/
|
||||
public async deleteConnection(connection: ConnectionProfile): Promise<boolean> {
|
||||
public deleteConnection(connection: ConnectionProfile): Promise<boolean> {
|
||||
this._telemetryService.createActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.TelemetryAction.DeleteConnection)
|
||||
.withAdditionalProperties({
|
||||
provider: connection.providerName
|
||||
@@ -1626,7 +1528,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
// Remove profile from configuration
|
||||
return this._connectionStore.deleteConnectionFromConfiguration(connection).then(() => {
|
||||
this._onDeleteConnectionProfile.fire();
|
||||
this._onConnectionProfileDeleted.fire(connection);
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -1638,7 +1539,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
// Remove disconnected profile from settings
|
||||
return this._connectionStore.deleteConnectionFromConfiguration(connection).then(() => {
|
||||
this._onDeleteConnectionProfile.fire();
|
||||
this._onConnectionProfileDeleted.fire(connection);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
@@ -1667,7 +1567,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
// Remove profiles and groups from config
|
||||
return this._connectionStore.deleteGroupFromConfiguration(group).then(() => {
|
||||
this._onDeleteConnectionProfile.fire();
|
||||
this._onConnectionProfileGroupDeleted.fire(group);
|
||||
return true;
|
||||
});
|
||||
}).catch(() => false);
|
||||
|
||||
@@ -4,157 +4,18 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||
import { IListService, IWorkbenchAsyncDataTreeOptions, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { FuzzyScore } from 'vs/base/common/filters';
|
||||
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
|
||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||
import { IAsyncDataTreeNode, IAsyncDataTreeUpdateChildrenOptions, IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { IAsyncDataSource, ITreeRenderer } from 'vs/base/browser/ui/tree/tree';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
|
||||
export class AsyncServerTree extends WorkbenchAsyncDataTree<ConnectionProfileGroup, ServerTreeElement, FuzzyScore> {
|
||||
|
||||
constructor(
|
||||
user: string,
|
||||
container: HTMLElement,
|
||||
delegate: IListVirtualDelegate<ServerTreeElement>,
|
||||
renderers: ITreeRenderer<ServerTreeElement, FuzzyScore, any>[],
|
||||
dataSource: IAsyncDataSource<ConnectionProfileGroup, ServerTreeElement>,
|
||||
options: IWorkbenchAsyncDataTreeOptions<ServerTreeElement, FuzzyScore>,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService listService: IListService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IAccessibilityService accessibilityService: IAccessibilityService,
|
||||
) {
|
||||
super(
|
||||
user, container, delegate,
|
||||
renderers, dataSource, options,
|
||||
contextKeyService, listService,
|
||||
themeService, configurationService, keybindingService, accessibilityService);
|
||||
|
||||
// Adding support for expand/collapse on enter/space
|
||||
this.onKeyDown(e => {
|
||||
const standardKeyboardEvent = new StandardKeyboardEvent(e);
|
||||
if (standardKeyboardEvent.keyCode === KeyCode.Enter || standardKeyboardEvent.keyCode === KeyCode.Space) {
|
||||
const selectedElement = this.getSelection()[0];
|
||||
if (selectedElement) {
|
||||
if (this.isCollapsed(selectedElement)) {
|
||||
this.expand(selectedElement);
|
||||
} else {
|
||||
this.collapse(selectedElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Overriding the setInput method to dispose the original input when a new input is set
|
||||
override async setInput(input: ConnectionProfileGroup, viewState?: IAsyncDataTreeViewState): Promise<void> {
|
||||
const originalInput = this.getInput();
|
||||
await super.setInput(input, viewState);
|
||||
originalInput?.dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* The original implementation of getDataNode compares refrences of the elements to find the node.
|
||||
* This is not working for our case as we are creating new elements everytime we refresh the tree.
|
||||
* This method overrides the original implementation to find the node by comparing the ids of the elements.
|
||||
* If the node is not found in the original implementation, we search for the node in the nodes map by ids.
|
||||
*/
|
||||
public override getDataNode(element: ServerTreeElement, throwError: boolean = true): IAsyncDataTreeNode<ConnectionProfileGroup, ServerTreeElement> | undefined {
|
||||
try {
|
||||
const node = super.getDataNode(element);
|
||||
return node;
|
||||
} catch (e) {
|
||||
let node = this.getDataNodeById(element?.id);
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
if (throwError) {
|
||||
throw e;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the element by id in the tree
|
||||
*/
|
||||
public getElementById(id: string): ServerTreeElement | undefined {
|
||||
if (this.getInput().id === id) {
|
||||
return this.getInput();
|
||||
}
|
||||
return this.getDataNodeById(id)?.element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of expanded elements in the tree
|
||||
*/
|
||||
public getExpandedState(element: ServerTreeElement): ServerTreeElement[] {
|
||||
const node = this.getDataNode(element);
|
||||
const stack = [node];
|
||||
const expanded: ServerTreeElement[] = [];
|
||||
while (stack.length > 0) {
|
||||
const node = stack.pop();
|
||||
if (node) {
|
||||
if (!this.isCollapsed(node.element)) {
|
||||
expanded.push(node.element);
|
||||
if (node.children) {
|
||||
node.children.forEach(child => stack.push(child));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return expanded;
|
||||
}
|
||||
|
||||
private getDataNodeById(id: string): IAsyncDataTreeNode<ConnectionProfileGroup, ServerTreeElement> | undefined {
|
||||
let node = undefined;
|
||||
this.nodes.forEach((v, k) => {
|
||||
if (id === v?.id) {
|
||||
node = v;
|
||||
}
|
||||
});
|
||||
return node;
|
||||
}
|
||||
|
||||
public override async updateChildren(element?: ServerTreeElement, recursive?: boolean, rerender?: boolean, options?: IAsyncDataTreeUpdateChildrenOptions<ServerTreeElement>): Promise<void> {
|
||||
const expandedChildren = this.getExpandedState(element);
|
||||
await super.updateChildren(element, recursive, rerender, options);
|
||||
await this.expandElements(expandedChildren);
|
||||
}
|
||||
|
||||
public async expandElements(elements: ServerTreeElement[]): Promise<void> {
|
||||
for (let element of elements) {
|
||||
const node = this.getDataNode(element, false);
|
||||
if (node) {
|
||||
await this.expand(node.element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the element as dirty so that it will be refreshed when it is expanded next time
|
||||
* @param element The element to mark as dirty
|
||||
*/
|
||||
public async makeElementDirty(element: ServerTreeElement) {
|
||||
this.getDataNode(element).stale = true;
|
||||
}
|
||||
|
||||
public async revealSelectFocusElement(element: ServerTreeElement) {
|
||||
await this.reveal(element);
|
||||
await this.setSelection([element]);
|
||||
this.setFocus([element]);
|
||||
}
|
||||
}
|
||||
|
||||
export type ServerTreeElement = ConnectionProfile | ConnectionProfileGroup | TreeNode;
|
||||
|
||||
@@ -167,6 +167,7 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
|
||||
// to avoid creating a circular structure.
|
||||
canDragOver = source.id !== targetConnectionProfileGroup.id && !source.isAncestorOf(targetConnectionProfileGroup);
|
||||
}
|
||||
|
||||
return canDragOver;
|
||||
}
|
||||
|
||||
@@ -178,41 +179,33 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
|
||||
public onDragOver(tree: AsyncServerTree | ITree, data: IDragAndDropData, targetElement: any, originalEvent: DragMouseEvent): IDragOverReaction {
|
||||
let canDragOver: boolean = true;
|
||||
|
||||
const source = data.getData()[0];
|
||||
if (source instanceof ConnectionProfileGroup) {
|
||||
if (targetElement instanceof ConnectionProfileGroup) {
|
||||
// If target group is parent of the source connection, then don't allow drag over
|
||||
canDragOver = this.canDragToConnectionProfileGroup(source, targetElement);
|
||||
} else if (targetElement instanceof ConnectionProfile) {
|
||||
canDragOver = source.parentId !== targetElement.groupId;
|
||||
} else if (targetElement instanceof TreeNode) {
|
||||
const treeNodeParentGroupId = this.getTreeNodeParentGroup(targetElement).id;
|
||||
canDragOver = source.parentId !== treeNodeParentGroupId && source.id !== treeNodeParentGroupId;
|
||||
if (targetElement instanceof ConnectionProfile || targetElement instanceof ConnectionProfileGroup) {
|
||||
let targetConnectionProfileGroup = this.getTargetGroup(targetElement);
|
||||
// Verify if the connection can be moved to the target group
|
||||
const source = data.getData()[0];
|
||||
if (source instanceof ConnectionProfile) {
|
||||
if (!this._connectionManagementService.canChangeConnectionConfig(source, targetConnectionProfileGroup.id!)) {
|
||||
canDragOver = false;
|
||||
}
|
||||
} else if (source instanceof ConnectionProfileGroup) {
|
||||
// Dropping a group to itself or its descendants nodes is not allowed
|
||||
// to avoid creating a circular structure.
|
||||
canDragOver = source.id !== targetElement.id && !source.isAncestorOf(targetElement);
|
||||
}
|
||||
} else if (source instanceof ConnectionProfile) {
|
||||
if (targetElement instanceof ConnectionProfileGroup) {
|
||||
canDragOver = this.canDragToConnectionProfileGroup(source, targetElement);
|
||||
} else if (targetElement instanceof ConnectionProfile) {
|
||||
canDragOver = source.groupId !== targetElement.groupId &&
|
||||
this._connectionManagementService.canChangeConnectionConfig(source, targetElement.groupId);
|
||||
} else if (targetElement instanceof TreeNode) {
|
||||
canDragOver = source.groupId !== this.getTreeNodeParentGroup(targetElement).id;
|
||||
}
|
||||
} else if (source instanceof TreeNode) {
|
||||
canDragOver = false;
|
||||
} else {
|
||||
canDragOver = true;
|
||||
}
|
||||
|
||||
if (canDragOver) {
|
||||
if (targetElement instanceof ConnectionProfile) {
|
||||
const isConnected = this._connectionManagementService.isProfileConnected(targetElement);
|
||||
// Don't auto-expand disconnected connections - doing so will try to connect the connection
|
||||
// when expanded which is not something we want to support currently
|
||||
return DRAG_OVER_ACCEPT_BUBBLE_DOWN(isConnected);
|
||||
} else if (targetElement instanceof ConnectionProfileGroup) {
|
||||
return DRAG_OVER_ACCEPT_BUBBLE_DOWN(true);
|
||||
} else {
|
||||
// Don't auto-expand treeNodes as we don't support drag and drop on them
|
||||
return DRAG_OVER_ACCEPT_BUBBLE_DOWN(false);
|
||||
}
|
||||
// Auto-expand other elements (groups, tree nodes) so their children can be
|
||||
// exposed for further dragging
|
||||
return DRAG_OVER_ACCEPT_BUBBLE_DOWN(true);
|
||||
} else {
|
||||
return DRAG_OVER_REJECT;
|
||||
}
|
||||
@@ -231,32 +224,23 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
|
||||
let oldParent: ConnectionProfileGroup = source.getParent();
|
||||
const self = this;
|
||||
if (this.isDropAllowed(targetConnectionProfileGroup, oldParent, source)) {
|
||||
if (tree instanceof AsyncServerTree) {
|
||||
if (oldParent && source && targetConnectionProfileGroup) {
|
||||
if (source instanceof ConnectionProfileGroup) {
|
||||
this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup);
|
||||
} else if (source instanceof ConnectionProfile) {
|
||||
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id!);
|
||||
|
||||
if (source instanceof ConnectionProfile) {
|
||||
// Change group id of profile
|
||||
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id!).then(() => {
|
||||
if (tree) {
|
||||
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
if (source instanceof ConnectionProfile) {
|
||||
// Change group id of profile
|
||||
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id!).then(async () => {
|
||||
if (tree) {
|
||||
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
|
||||
}
|
||||
});
|
||||
} else if (source instanceof ConnectionProfileGroup) {
|
||||
// Change parent id of group
|
||||
this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(() => {
|
||||
if (tree) {
|
||||
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
|
||||
}
|
||||
|
||||
});
|
||||
} else if (source instanceof ConnectionProfileGroup) {
|
||||
// Change parent id of group
|
||||
this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(async () => {
|
||||
if (tree) {
|
||||
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -266,17 +250,13 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
|
||||
TreeUpdateUtils.isInDragAndDrop = false;
|
||||
}
|
||||
|
||||
private getTargetGroup(targetElement: ConnectionProfileGroup | ConnectionProfile | TreeNode): ConnectionProfileGroup {
|
||||
private getTargetGroup(targetElement: ConnectionProfileGroup | ConnectionProfile): ConnectionProfileGroup {
|
||||
let targetConnectionProfileGroup: ConnectionProfileGroup;
|
||||
if (targetElement instanceof ConnectionProfile) {
|
||||
targetConnectionProfileGroup = targetElement.getParent()!;
|
||||
} else if (targetElement instanceof ConnectionProfileGroup) {
|
||||
}
|
||||
else {
|
||||
targetConnectionProfileGroup = targetElement;
|
||||
} else if (targetElement instanceof TreeNode) {
|
||||
targetConnectionProfileGroup = this.getTreeNodeParentGroup(targetElement);
|
||||
if (!targetConnectionProfileGroup) {
|
||||
throw new Error('Cannot find parent for the node');
|
||||
}
|
||||
}
|
||||
|
||||
return targetConnectionProfileGroup;
|
||||
@@ -291,20 +271,6 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
|
||||
let isUnsavedDrag = source && (source instanceof ConnectionProfileGroup) && (source.id === UNSAVED_GROUP_ID);
|
||||
return (!isDropToSameLevel && !isDropToItself && !isUnsavedDrag);
|
||||
}
|
||||
|
||||
private getTreeNodeParentGroup(element: TreeNode): ConnectionProfileGroup | undefined {
|
||||
let treeNode = element;
|
||||
while (!treeNode?.connection) {
|
||||
treeNode = treeNode.parent;
|
||||
}
|
||||
if (treeNode) {
|
||||
const groupId = treeNode.connection.groupId;
|
||||
if (groupId) {
|
||||
return this._connectionManagementService.getConnectionGroupById(groupId);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -179,9 +179,6 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
|
||||
private _connectionsWaitingForSession: Map<string, boolean> = new Map<string, boolean>();
|
||||
|
||||
// Cache of tree nodes for each connection by session ids
|
||||
private _treeNodeCache: Map<string, Map<string, TreeNode>> = new Map<string, Map<string, TreeNode>>();
|
||||
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IAdsTelemetryService private _telemetryService: IAdsTelemetryService,
|
||||
@@ -236,7 +233,6 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
this._treeNodeCache.delete(session.sessionId);
|
||||
await this.closeSession(connection.providerName, session);
|
||||
delete this._activeObjectExplorerNodes[connectionUri];
|
||||
delete this._sessions[session.sessionId!];
|
||||
@@ -344,8 +340,6 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
try {
|
||||
if (session.success && session.rootNode) {
|
||||
let server = this.toTreeNode(session.rootNode, undefined);
|
||||
this._treeNodeCache.set(sessionId, new Map<string, TreeNode>());
|
||||
this._treeNodeCache.get(sessionId)!.set(this.getTreeNodeCacheKey(server.toNodeInfo()), server);
|
||||
server.connection = connection;
|
||||
server.session = session;
|
||||
this._activeObjectExplorerNodes[connection!.id] = server;
|
||||
@@ -736,31 +730,9 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
throw new Error('Failed to expand node - no provider name');
|
||||
}
|
||||
const expandResult = await this.callExpandOrRefreshFromService(providerName, session, parentTree, refresh);
|
||||
const sessionTreeNodeCache = this._treeNodeCache.get(session.sessionId!);
|
||||
if (expandResult && expandResult.nodes) {
|
||||
// In case of refresh, we want to clear the cache of the descendants of the node being refreshed
|
||||
if (refresh && parentTree?.children) {
|
||||
const stack = [...parentTree.children];
|
||||
while (stack.length > 0) {
|
||||
const currentTreeNode = stack.pop();
|
||||
if (currentTreeNode) {
|
||||
sessionTreeNodeCache.delete(this.getTreeNodeCacheKey(currentTreeNode.toNodeInfo()));
|
||||
if (currentTreeNode.children) {
|
||||
stack.push(...currentTreeNode.children);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const children = expandResult.nodes.map(node => {
|
||||
const cacheKey = this.getTreeNodeCacheKey(node);
|
||||
// In case of refresh, we want to update the existing node in the cache
|
||||
if (!refresh && sessionTreeNodeCache.has(cacheKey)) {
|
||||
return sessionTreeNodeCache.get(cacheKey);
|
||||
} else {
|
||||
const treeNode = this.toTreeNode(node, parentTree);
|
||||
sessionTreeNodeCache.set(cacheKey, treeNode);
|
||||
return treeNode;
|
||||
}
|
||||
return this.toTreeNode(node, parentTree);
|
||||
});
|
||||
parentTree.children = children.filter(c => c !== undefined);
|
||||
return children;
|
||||
@@ -1032,8 +1004,4 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
||||
public getObjectExplorerTimeout(): number {
|
||||
return this._configurationService.getValue<number>(NODE_EXPANSION_CONFIG);
|
||||
}
|
||||
|
||||
private getTreeNodeCacheKey(node: azdata.NodeInfo): string {
|
||||
return node.nodePath;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,8 +305,8 @@ export class TreeUpdateUtils {
|
||||
|
||||
// If the node update takes too long, reject the promise
|
||||
const nodeUpdateTimeout = () => {
|
||||
cleanup();
|
||||
reject(new Error(nls.localize('objectExplorerTimeout', "Object Explorer expansion timed out for '{0}'", connection.databaseName)));
|
||||
cleanup();
|
||||
}
|
||||
const nodeUpdateTimer = setTimeout(nodeUpdateTimeout, expansionTimeoutValueSec * 1000);
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||
import { IThemable } from 'vs/base/common/styler';
|
||||
import { isIterable } from 'vs/base/common/types';
|
||||
|
||||
export interface IAsyncDataTreeNode<TInput, T> { // {{SQL CARBON EDIT}} - exporting interface
|
||||
interface IAsyncDataTreeNode<TInput, T> {
|
||||
element: TInput | T;
|
||||
readonly parent: IAsyncDataTreeNode<TInput, T> | null;
|
||||
readonly children: IAsyncDataTreeNode<TInput, T>[];
|
||||
@@ -311,7 +311,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
protected readonly tree: ObjectTree<IAsyncDataTreeNode<TInput, T>, TFilterData>;
|
||||
protected readonly root: IAsyncDataTreeNode<TInput, T>;
|
||||
protected readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>(); // {{SQL CARBON EDIT}}} making protected to access in subclass
|
||||
private readonly nodes = new Map<null | T, IAsyncDataTreeNode<TInput, T>>();
|
||||
private readonly sorter?: ITreeSorter<T>;
|
||||
private readonly collapseByDefault?: { (e: T): boolean };
|
||||
|
||||
@@ -706,7 +706,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
// Implementation
|
||||
|
||||
protected getDataNode(element: TInput | T): IAsyncDataTreeNode<TInput, T> { // {{SQL CARBON EDIT}} making protected to override
|
||||
private getDataNode(element: TInput | T): IAsyncDataTreeNode<TInput, T> {
|
||||
const node: IAsyncDataTreeNode<TInput, T> | undefined = this.nodes.get((element === this.root.element ? null : element) as T);
|
||||
|
||||
if (!node) {
|
||||
|
||||
Reference in New Issue
Block a user