mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Connection URI with complete options (finalized) (#22735)
* Connection URI made to include every option available instead of basic details (#22045) * Revert "Merge remote-tracking branch 'origin' into feat/connectionUri" This reverts commit 11b2d31bf99e216daee823f732254f69a017fee1, reversing changes made to 36e4db8c0744f81565efdfd2f56a3ae3c0026896. * Revert "Revert "Merge remote-tracking branch 'origin' into feat/connectionUri"" This reverts commit f439673c2693e1144c52e04c14e82cd8566c13a6. * Added changes and fixes for feat connectionuri (#22706) * add title generation at start * added await to refreshConnectionTreeTitles
This commit is contained in:
@@ -789,6 +789,7 @@
|
|||||||
"connectionProvider": {
|
"connectionProvider": {
|
||||||
"providerId": "MSSQL",
|
"providerId": "MSSQL",
|
||||||
"displayName": "%mssql.provider.displayName%",
|
"displayName": "%mssql.provider.displayName%",
|
||||||
|
"useFullOptions": true,
|
||||||
"isExecutionPlanProvider": true,
|
"isExecutionPlanProvider": true,
|
||||||
"azureResource": "Sql",
|
"azureResource": "Sql",
|
||||||
"supportedExecutionPlanFileExtensions": [
|
"supportedExecutionPlanFileExtensions": [
|
||||||
|
|||||||
@@ -26,11 +26,18 @@ export function registerTableDesignerCommands(appContext: AppContext) {
|
|||||||
if (!connectionString) {
|
if (!connectionString) {
|
||||||
throw new Error(FailedToGetConnectionStringError);
|
throw new Error(FailedToGetConnectionStringError);
|
||||||
}
|
}
|
||||||
|
let titleString = `${context.connectionProfile!.serverName} - ${context.connectionProfile!.databaseName} - ${NewTableText}`;
|
||||||
|
// append non default options to end to let users know exact connection.
|
||||||
|
let nonDefaultOptions = await azdata.connection.getEditorConnectionProfileTitle(context.connectionProfile, true);
|
||||||
|
nonDefaultOptions = nonDefaultOptions.replace('(', '[').replace(')', ']');
|
||||||
|
if (nonDefaultOptions !== '') {
|
||||||
|
titleString += `${nonDefaultOptions}`;
|
||||||
|
}
|
||||||
const tableIcon = context.nodeInfo!.nodeSubType as azdata.designers.TableIcon;
|
const tableIcon = context.nodeInfo!.nodeSubType as azdata.designers.TableIcon;
|
||||||
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
|
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
|
||||||
await azdata.designers.openTableDesigner(sqlProviderName, {
|
await azdata.designers.openTableDesigner(sqlProviderName, {
|
||||||
title: NewTableText,
|
title: NewTableText,
|
||||||
tooltip: `${context.connectionProfile!.serverName} - ${context.connectionProfile!.databaseName} - ${NewTableText}`,
|
tooltip: titleString,
|
||||||
server: context.connectionProfile!.serverName,
|
server: context.connectionProfile!.serverName,
|
||||||
database: context.connectionProfile!.databaseName,
|
database: context.connectionProfile!.databaseName,
|
||||||
isNewTable: true,
|
isNewTable: true,
|
||||||
@@ -56,11 +63,18 @@ export function registerTableDesignerCommands(appContext: AppContext) {
|
|||||||
if (!connectionString) {
|
if (!connectionString) {
|
||||||
throw new Error(FailedToGetConnectionStringError);
|
throw new Error(FailedToGetConnectionStringError);
|
||||||
}
|
}
|
||||||
|
let titleString = `${server} - ${database} - ${schema}.${name}`;
|
||||||
|
// append non default options to end to let users know exact connection.
|
||||||
|
let nonDefaultOptions = await azdata.connection.getEditorConnectionProfileTitle(context.connectionProfile, true);
|
||||||
|
nonDefaultOptions = nonDefaultOptions.replace('(', '[').replace(')', ']');
|
||||||
|
if (nonDefaultOptions !== '') {
|
||||||
|
titleString += `${nonDefaultOptions}`;
|
||||||
|
}
|
||||||
const tableIcon = context.nodeInfo!.nodeSubType as azdata.designers.TableIcon;
|
const tableIcon = context.nodeInfo!.nodeSubType as azdata.designers.TableIcon;
|
||||||
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
|
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
|
||||||
await azdata.designers.openTableDesigner(sqlProviderName, {
|
await azdata.designers.openTableDesigner(sqlProviderName, {
|
||||||
title: `${schema}.${name}`,
|
title: `${schema}.${name}`,
|
||||||
tooltip: `${server} - ${database} - ${schema}.${name}`,
|
tooltip: titleString,
|
||||||
server: server,
|
server: server,
|
||||||
database: database,
|
database: database,
|
||||||
isNewTable: false,
|
isNewTable: false,
|
||||||
|
|||||||
8
src/sql/azdata.proposed.d.ts
vendored
8
src/sql/azdata.proposed.d.ts
vendored
@@ -508,6 +508,14 @@ declare module 'azdata' {
|
|||||||
* @returns The new password that is returned from the operation or undefined if unsuccessful.
|
* @returns The new password that is returned from the operation or undefined if unsuccessful.
|
||||||
*/
|
*/
|
||||||
export function openChangePasswordDialog(profile: IConnectionProfile): Thenable<string | undefined>;
|
export function openChangePasswordDialog(profile: IConnectionProfile): Thenable<string | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the formatted title of the connection profile for display
|
||||||
|
* @param profile The connection profile we want to get the full display info for (without non default options).
|
||||||
|
* @param getNonDefaultsOnly Provide if you only want to get the non default options string (for some titles).
|
||||||
|
* @returns The title formatted with connection name in front, server info in the middle, with non default options at the end.
|
||||||
|
*/
|
||||||
|
export function getEditorConnectionProfileTitle(profile: IConnectionProfile, getNonDefaultsOnly?: boolean): Thenable<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -67,6 +67,12 @@ export interface ConnectionProviderProperties {
|
|||||||
*/
|
*/
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable to use all connection properties for URI generation (ServiceLayer requires the same options as well.)
|
||||||
|
* If not specified, only IsIdentity options will be used instead (URI with basic info).
|
||||||
|
*/
|
||||||
|
useFullOptions?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Alias to be used for the kernel in notebooks
|
* Alias to be used for the kernel in notebooks
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export class TestCapabilitiesService implements ICapabilitiesService {
|
|||||||
providerId: mssqlProviderName,
|
providerId: mssqlProviderName,
|
||||||
displayName: 'MSSQL',
|
displayName: 'MSSQL',
|
||||||
connectionOptions: connectionProvider,
|
connectionOptions: connectionProvider,
|
||||||
|
useFullOptions: true,
|
||||||
};
|
};
|
||||||
let pgSQLCapabilities = {
|
let pgSQLCapabilities = {
|
||||||
providerId: this.pgsqlProviderName,
|
providerId: this.pgsqlProviderName,
|
||||||
|
|||||||
@@ -70,6 +70,51 @@ export class ConnectionConfig {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to make sure that the profile that is being edited is not identical to another profile.
|
||||||
|
*/
|
||||||
|
public isDuplicateEdit(profile: IConnectionProfile, matcher: ProfileMatcher = ConnectionProfile.matchesProfile): Promise<boolean> {
|
||||||
|
let profiles = deepClone(this.configurationService.inspect<IConnectionProfileStore[]>(CONNECTIONS_CONFIG_KEY).userValue as IConnectionProfileStore[]);
|
||||||
|
if (!profiles) {
|
||||||
|
profiles = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.addGroupFromProfile(profile).then(groupId => {
|
||||||
|
let connectionProfile = this.getConnectionProfileInstance(profile, groupId);
|
||||||
|
// Profile to be stored during an edit, used to check for duplicate profile edits.
|
||||||
|
let firstMatchProfile = undefined;
|
||||||
|
|
||||||
|
profiles.find(value => {
|
||||||
|
const providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService);
|
||||||
|
const match = matcher(providerConnectionProfile, connectionProfile);
|
||||||
|
// If we have a profile match, and the matcher is an edit, we must store this match.
|
||||||
|
if (match && (matcher.toString() !== ConnectionProfile.matchesProfile.toString())) {
|
||||||
|
firstMatchProfile = value;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
|
||||||
|
// If a profile edit, we must now check to see it does not match the other profiles available.
|
||||||
|
if (firstMatchProfile) {
|
||||||
|
// Copy over profile list so that we can remove the actual profile we want to edit.
|
||||||
|
const index = profiles.indexOf(firstMatchProfile);
|
||||||
|
if (index > -1) {
|
||||||
|
profiles.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the regular profile matching here to find if edit is duplicate.
|
||||||
|
let matchesExistingProfile = profiles.find(value => {
|
||||||
|
const providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService);
|
||||||
|
const match = ConnectionProfile.matchesProfile(providerConnectionProfile, connectionProfile);
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.resolve(matchesExistingProfile !== undefined);
|
||||||
|
}
|
||||||
|
return Promise.resolve(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new connection to the connection config.
|
* Add a new connection to the connection config.
|
||||||
*/
|
*/
|
||||||
@@ -317,10 +362,18 @@ export class ConnectionConfig {
|
|||||||
p.options.database === profile.options.database &&
|
p.options.database === profile.options.database &&
|
||||||
p.options.server === profile.options.server &&
|
p.options.server === profile.options.server &&
|
||||||
p.options.user === profile.options.user &&
|
p.options.user === profile.options.user &&
|
||||||
p.groupId === newGroupID);
|
p.options.connectionName === profile.options.connectionName &&
|
||||||
|
p.groupId === newGroupID &&
|
||||||
|
this.checkIfNonDefaultOptionsMatch(p, profile));
|
||||||
return existingProfile === undefined;
|
return existingProfile === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private checkIfNonDefaultOptionsMatch(profileStore: IConnectionProfileStore, profile: ConnectionProfile): boolean {
|
||||||
|
let tempProfile = ConnectionProfile.createFromStoredProfile(profileStore, this._capabilitiesService);
|
||||||
|
let result = profile.getNonDefaultOptionsString() === tempProfile.getNonDefaultOptionsString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves the connection under the target group with the new ID.
|
* Moves the connection under the target group with the new ID.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -378,6 +378,14 @@ export interface IConnectionManagementService {
|
|||||||
* @returns the new valid password that is entered, or undefined if cancelled or errored.
|
* @returns the new valid password that is entered, or undefined if cancelled or errored.
|
||||||
*/
|
*/
|
||||||
openChangePasswordDialog(profile: IConnectionProfile): Promise<string | undefined>;
|
openChangePasswordDialog(profile: IConnectionProfile): Promise<string | undefined>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the formatted title of the connection profile for display.
|
||||||
|
* @param profile The connection profile to change the password.
|
||||||
|
* @param getNonDefaultsOnly Provide if you only want to get the non default options string (for some titles).
|
||||||
|
* @returns the new valid password that is entered, or undefined if cancelled or errored.
|
||||||
|
*/
|
||||||
|
getEditorConnectionProfileTitle(profile: IConnectionProfile, getNonDefaultsOnly?: boolean): string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum RunQueryOnConnectionMode {
|
export enum RunQueryOnConnectionMode {
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
|
|||||||
|
|
||||||
public isDisconnecting: boolean = false;
|
public isDisconnecting: boolean = false;
|
||||||
|
|
||||||
|
// title from ProviderConnectionInfo cannot be changed, in order to show different dynamic options appended, we must override the title with our own.
|
||||||
|
private _title?: string;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
capabilitiesService: ICapabilitiesService,
|
capabilitiesService: ICapabilitiesService,
|
||||||
model: string | azdata.IConnectionProfile | azdata.connection.ConnectionProfile | undefined) {
|
model: string | azdata.IConnectionProfile | azdata.connection.ConnectionProfile | undefined) {
|
||||||
@@ -191,6 +194,33 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
|
|||||||
return (this._groupName === ConnectionProfile.RootGroupName);
|
return (this._groupName === ConnectionProfile.RootGroupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override get title(): string {
|
||||||
|
if (this._title) {
|
||||||
|
return this._title;
|
||||||
|
}
|
||||||
|
return this.getOriginalTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOriginalTitle(): string {
|
||||||
|
return super.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEditorFullTitleWithOptions(): string {
|
||||||
|
let textResult = '';
|
||||||
|
if (this.connectionName) {
|
||||||
|
textResult += `${this.connectionName}: `;
|
||||||
|
}
|
||||||
|
textResult += this.serverInfo;
|
||||||
|
if (textResult.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return textResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override set title(value: string) {
|
||||||
|
this._title = value;
|
||||||
|
}
|
||||||
|
|
||||||
public override clone(): ConnectionProfile {
|
public override clone(): ConnectionProfile {
|
||||||
let instance = new ConnectionProfile(this.capabilitiesService, this);
|
let instance = new ConnectionProfile(this.capabilitiesService, this);
|
||||||
return instance;
|
return instance;
|
||||||
@@ -219,24 +249,33 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
|
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
|
||||||
|
* and all the other properties if useFullOptions is enabled for the provider.
|
||||||
* This key uniquely identifies a connection in a group
|
* This key uniquely identifies a connection in a group
|
||||||
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
||||||
|
* @param getOriginalOptions will return the original URI format regardless if useFullOptions was set or not. (used for retrieving passwords)
|
||||||
*/
|
*/
|
||||||
public override getOptionsKey(): string {
|
public override getOptionsKey(getOriginalOptions?: boolean): string {
|
||||||
let id = super.getOptionsKey();
|
let id = super.getOptionsKey(getOriginalOptions);
|
||||||
let databaseDisplayName: string = this.options['databaseDisplayName'];
|
let databaseDisplayName: string = this.options['databaseDisplayName'];
|
||||||
if (databaseDisplayName) {
|
if (databaseDisplayName) {
|
||||||
id += ProviderConnectionInfo.idSeparator + 'databaseDisplayName' + ProviderConnectionInfo.nameValueSeparator + databaseDisplayName;
|
id += ProviderConnectionInfo.idSeparator + 'databaseDisplayName' + ProviderConnectionInfo.nameValueSeparator + databaseDisplayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
return id + ProviderConnectionInfo.idSeparator + 'group' + ProviderConnectionInfo.nameValueSeparator + this.groupId;
|
return id + ProviderConnectionInfo.idSeparator + 'groupId' + ProviderConnectionInfo.nameValueSeparator + this.groupId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the unique id for the connection that doesn't include the group name
|
* Returns the unique id for the connection that doesn't include the group name.
|
||||||
|
* Used primarily for retrieving shared passwords among different connections in default state.
|
||||||
|
* @param getOriginalOptions will return the original URI format regardless if useFullOptions was set or not. (used for retrieving passwords)
|
||||||
*/
|
*/
|
||||||
public getConnectionInfoId(): string {
|
public getConnectionInfoId(getOriginalOptions = true): string {
|
||||||
return super.getOptionsKey();
|
let id = super.getOptionsKey(getOriginalOptions);
|
||||||
|
let databaseDisplayName: string = this.options['databaseDisplayName'];
|
||||||
|
if (databaseDisplayName && !getOriginalOptions && this.serverCapabilities?.useFullOptions) {
|
||||||
|
id += ProviderConnectionInfo.idSeparator + 'databaseDisplayName' + ProviderConnectionInfo.nameValueSeparator + databaseDisplayName;
|
||||||
|
}
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public toIConnectionProfile(): interfaces.IConnectionProfile {
|
public toIConnectionProfile(): interfaces.IConnectionProfile {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ export class ConnectionStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cred.push(CRED_ITEMTYPE_PREFIX.concat(itemType));
|
cred.push(CRED_ITEMTYPE_PREFIX.concat(itemType));
|
||||||
|
// Use basic info for credentials so that passwords can be shared among similar profiles for now.
|
||||||
cred.push(CRED_ID_PREFIX.concat(connectionProfileInstance.getConnectionInfoId()));
|
cred.push(CRED_ID_PREFIX.concat(connectionProfileInstance.getConnectionInfoId()));
|
||||||
return cred.join(CRED_SEPARATOR);
|
return cred.join(CRED_SEPARATOR);
|
||||||
}
|
}
|
||||||
@@ -149,6 +150,17 @@ export class ConnectionStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks to see if a connection profile edit is not identical to an existing saved profile.
|
||||||
|
*
|
||||||
|
* @param profile the profile group that is being edited.
|
||||||
|
* @param matcher the profile matching function for the actual connection we want to edit.
|
||||||
|
* @returns a boolean value indicating if there's an identical profile to the edit.
|
||||||
|
*/
|
||||||
|
public isDuplicateEdit(profile: IConnectionProfile, matcher?: ProfileMatcher): Promise<boolean> {
|
||||||
|
return this.connectionConfig.isDuplicateEdit(profile, matcher);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the list of recently used connections. These will not include the password - a separate call to
|
* Gets the list of recently used connections. These will not include the password - a separate call to
|
||||||
* {addSavedPassword} is needed to fill that before connecting
|
* {addSavedPassword} is needed to fill that before connecting
|
||||||
@@ -217,7 +229,7 @@ export class ConnectionStore {
|
|||||||
|
|
||||||
// Remove the connection from the list if it already exists
|
// Remove the connection from the list if it already exists
|
||||||
list = list.filter(value => {
|
list = list.filter(value => {
|
||||||
let equal = value && value.getConnectionInfoId() === savedProfile.getConnectionInfoId();
|
let equal = value && value.getConnectionInfoId(false) === savedProfile.getConnectionInfoId(false);
|
||||||
if (equal && savedProfile.saveProfile) {
|
if (equal && savedProfile.saveProfile) {
|
||||||
equal = value.groupId === savedProfile.groupId ||
|
equal = value.groupId === savedProfile.groupId ||
|
||||||
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
|
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
|
||||||
@@ -235,7 +247,7 @@ export class ConnectionStore {
|
|||||||
|
|
||||||
// Remove the connection from the list if it already exists
|
// Remove the connection from the list if it already exists
|
||||||
list = list.filter(value => {
|
list = list.filter(value => {
|
||||||
let equal = value && value.getConnectionInfoId() === savedProfile.getConnectionInfoId();
|
let equal = value && value.getConnectionInfoId(false) === savedProfile.getConnectionInfoId(false);
|
||||||
if (equal && savedProfile.saveProfile) {
|
if (equal && savedProfile.saveProfile) {
|
||||||
equal = value.groupId === savedProfile.groupId ||
|
equal = value.groupId === savedProfile.groupId ||
|
||||||
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
|
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
|
||||||
@@ -270,6 +282,8 @@ export class ConnectionStore {
|
|||||||
|
|
||||||
private doSavePassword(conn: IConnectionProfile): Promise<boolean> {
|
private doSavePassword(conn: IConnectionProfile): Promise<boolean> {
|
||||||
if (conn.password) {
|
if (conn.password) {
|
||||||
|
// Credentials are currently shared between profiles with the same basic details.
|
||||||
|
// Credentials are currently not cleared upon deletion of a profile.
|
||||||
const credentialId = this.formatCredentialId(conn);
|
const credentialId = this.formatCredentialId(conn);
|
||||||
return this.credentialService.saveCredential(credentialId, conn.password);
|
return this.credentialService.saveCredential(credentialId, conn.password);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as azdata from 'azdata';
|
|||||||
export type ProfileMatcher = (a: IConnectionProfile, b: IConnectionProfile) => boolean;
|
export type ProfileMatcher = (a: IConnectionProfile, b: IConnectionProfile) => boolean;
|
||||||
|
|
||||||
export interface IConnectionProfile extends azdata.IConnectionProfile {
|
export interface IConnectionProfile extends azdata.IConnectionProfile {
|
||||||
getOptionsKey(): string;
|
getOptionsKey(getOriginalOptions?: boolean): string;
|
||||||
matches(profile: azdata.IConnectionProfile): boolean;
|
matches(profile: azdata.IConnectionProfile): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { isString } from 'vs/base/common/types';
|
|||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as Constants from 'sql/platform/connection/common/constants';
|
import * as Constants from 'sql/platform/connection/common/constants';
|
||||||
import { ICapabilitiesService, ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
|
import { ICapabilitiesService, ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||||
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/platform/connection/common/interfaces';
|
import { ConnectionOptionSpecialType } from 'sql/platform/connection/common/interfaces';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
|
|
||||||
type SettableProperty = 'serverName' | 'authenticationType' | 'databaseName' | 'password' | 'connectionName' | 'userName';
|
type SettableProperty = 'serverName' | 'authenticationType' | 'databaseName' | 'password' | 'connectionName' | 'userName';
|
||||||
@@ -29,7 +29,7 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
this.providerName = isString(model) ? model : 'providerName' in model ? model.providerName : model.providerId;
|
this.providerName = isString(model) ? model : 'providerName' in model ? model.providerName : model.providerId;
|
||||||
|
|
||||||
if (!isString(model)) {
|
if (!isString(model)) {
|
||||||
if (model.options && this.serverCapabilities) {
|
if (model.options && this.hasServerCapabilities()) {
|
||||||
this.serverCapabilities.connectionOptions.forEach(option => {
|
this.serverCapabilities.connectionOptions.forEach(option => {
|
||||||
let value = model.options[option.name];
|
let value = model.options[option.name];
|
||||||
this.options[option.name] = value;
|
this.options[option.name] = value;
|
||||||
@@ -136,7 +136,7 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
|
|
||||||
private getServerInfo() {
|
private getServerInfo() {
|
||||||
let title = '';
|
let title = '';
|
||||||
if (this.serverCapabilities) {
|
if (this.hasServerCapabilities()) {
|
||||||
title = this.serverName;
|
title = this.serverName;
|
||||||
// Only show database name if the provider supports it.
|
// Only show database name if the provider supports it.
|
||||||
if (this.serverCapabilities.connectionOptions?.find(option => option.specialValueType === ConnectionOptionSpecialType.databaseName)) {
|
if (this.serverCapabilities.connectionOptions?.find(option => option.specialValueType === ConnectionOptionSpecialType.databaseName)) {
|
||||||
@@ -153,7 +153,7 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
public get title(): string {
|
public get title(): string {
|
||||||
let label = '';
|
let label = '';
|
||||||
|
|
||||||
if (this.serverCapabilities) {
|
if (this.hasServerCapabilities()) {
|
||||||
if (this.connectionName) {
|
if (this.connectionName) {
|
||||||
label = this.connectionName;
|
label = this.connectionName;
|
||||||
} else {
|
} else {
|
||||||
@@ -161,7 +161,7 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The provider capabilities are registered at the same time at load time, we can assume all providers are registered as long as the collection is not empty.
|
// The provider capabilities are registered at the same time at load time, we can assume all providers are registered as long as the collection is not empty.
|
||||||
else if (Object.keys(this.capabilitiesService.providers).length > 0) {
|
else if (this.hasLoaded()) {
|
||||||
return localize('connection.unsupported', "Unsupported connection");
|
return localize('connection.unsupported', "Unsupported connection");
|
||||||
} else {
|
} else {
|
||||||
return localize('loading', "Loading...");
|
return localize('loading', "Loading...");
|
||||||
@@ -169,13 +169,25 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public hasServerCapabilities(): boolean {
|
||||||
|
return (this.serverCapabilities !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasLoaded(): boolean {
|
||||||
|
return Object.keys(this.capabilitiesService.providers).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
public get serverInfo(): string {
|
public get serverInfo(): string {
|
||||||
return this.getServerInfo();
|
let value = this.getServerInfo();
|
||||||
|
if (this.serverCapabilities?.useFullOptions) {
|
||||||
|
value += this.getNonDefaultOptionsString();
|
||||||
|
}
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public isPasswordRequired(): boolean {
|
public isPasswordRequired(): boolean {
|
||||||
// if there is no provider capabilities metadata assume a password is not required
|
// if there is no provider capabilities metadata assume a password is not required
|
||||||
if (!this.serverCapabilities) {
|
if (!this.hasServerCapabilities()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,16 +210,23 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
|
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
|
||||||
|
* and all the other properties if useFullOptions is enabled for the provider.
|
||||||
* This key uniquely identifies a connection in a group
|
* This key uniquely identifies a connection in a group
|
||||||
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
||||||
|
* @param getOriginalOptions will return the original URI format regardless if useFullOptions was set or not. (used for retrieving passwords)
|
||||||
*/
|
*/
|
||||||
public getOptionsKey(): string {
|
public getOptionsKey(getOriginalOptions?: boolean): string {
|
||||||
|
let useFullOptions = false;
|
||||||
let idNames = [];
|
let idNames = [];
|
||||||
if (this.serverCapabilities) {
|
if (this.hasServerCapabilities()) {
|
||||||
|
useFullOptions = this.serverCapabilities.useFullOptions;
|
||||||
idNames = this.serverCapabilities.connectionOptions.map(o => {
|
idNames = this.serverCapabilities.connectionOptions.map(o => {
|
||||||
if ((o.specialValueType || o.isIdentity)
|
// All options enabled, use every property besides password.
|
||||||
&& o.specialValueType !== ConnectionOptionSpecialType.password
|
let newProperty = useFullOptions && o.specialValueType !== ConnectionOptionSpecialType.password && !getOriginalOptions;
|
||||||
&& o.specialValueType !== ConnectionOptionSpecialType.connectionName) {
|
// Fallback to original base IsIdentity properties otherwise.
|
||||||
|
let originalProperty = (o.specialValueType || o.isIdentity) && o.specialValueType !== ConnectionOptionSpecialType.password
|
||||||
|
&& o.specialValueType !== ConnectionOptionSpecialType.connectionName;
|
||||||
|
if (newProperty || originalProperty) {
|
||||||
return o.name;
|
return o.name;
|
||||||
} else {
|
} else {
|
||||||
return undefined;
|
return undefined;
|
||||||
@@ -226,14 +245,45 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
let idValues: string[] = [];
|
let idValues: string[] = [];
|
||||||
for (let index = 0; index < idNames.length; index++) {
|
for (let index = 0; index < idNames.length; index++) {
|
||||||
let value = this.options[idNames[index]!];
|
let value = this.options[idNames[index]!];
|
||||||
value = value ? value : '';
|
// If we're using the new URI format, we do not include any values that are empty or are default.
|
||||||
idValues.push(`${idNames[index]}${ProviderConnectionInfo.nameValueSeparator}${value}`);
|
let isFullOptions = useFullOptions && !getOriginalOptions;
|
||||||
|
if (isFullOptions) {
|
||||||
|
let finalValue = undefined;
|
||||||
|
let options = this.serverCapabilities.connectionOptions.filter(value => value.name === idNames[index]!);
|
||||||
|
if (options.length > 0 && value) {
|
||||||
|
finalValue = value !== options[0].defaultValue ? value : undefined;
|
||||||
|
}
|
||||||
|
value = finalValue;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
value = value ? value : '';
|
||||||
|
}
|
||||||
|
if ((isFullOptions && value !== undefined) || !isFullOptions) {
|
||||||
|
idValues.push(`${idNames[index]}${ProviderConnectionInfo.nameValueSeparator}${value}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProviderConnectionInfo.ProviderPropertyName + ProviderConnectionInfo.nameValueSeparator +
|
return ProviderConnectionInfo.ProviderPropertyName + ProviderConnectionInfo.nameValueSeparator +
|
||||||
this.providerName + ProviderConnectionInfo.idSeparator + idValues.join(ProviderConnectionInfo.idSeparator);
|
this.providerName + ProviderConnectionInfo.idSeparator + idValues.join(ProviderConnectionInfo.idSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a more readable version of the options key intended for display areas, replaces the regular separators with display separators
|
||||||
|
* @param optionsKey options key in the original format.
|
||||||
|
*/
|
||||||
|
public static getDisplayOptionsKey(optionsKey: string) {
|
||||||
|
let ids: string[] = optionsKey.split(ProviderConnectionInfo.idSeparator);
|
||||||
|
ids = ids.map(id => {
|
||||||
|
let idParts = id.split(ProviderConnectionInfo.nameValueSeparator);
|
||||||
|
let result = idParts[0] + ProviderConnectionInfo.displayNameValueSeparator;
|
||||||
|
if (idParts.length >= 2) {
|
||||||
|
result += idParts.slice(1).join(ProviderConnectionInfo.nameValueSeparator);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
return ids.join(ProviderConnectionInfo.displayIdSeparator);
|
||||||
|
}
|
||||||
|
|
||||||
public static getProviderFromOptionsKey(optionsKey: string) {
|
public static getProviderFromOptionsKey(optionsKey: string) {
|
||||||
let providerId: string = '';
|
let providerId: string = '';
|
||||||
if (optionsKey) {
|
if (optionsKey) {
|
||||||
@@ -249,7 +299,7 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getSpecialTypeOptionName(type: string): string | undefined {
|
public getSpecialTypeOptionName(type: string): string | undefined {
|
||||||
if (this.serverCapabilities) {
|
if (this.hasServerCapabilities()) {
|
||||||
let optionMetadata = this.serverCapabilities.connectionOptions.find(o => o.specialValueType === type);
|
let optionMetadata = this.serverCapabilities.connectionOptions.find(o => o.specialValueType === type);
|
||||||
return !!optionMetadata ? optionMetadata.name : undefined;
|
return !!optionMetadata ? optionMetadata.name : undefined;
|
||||||
} else {
|
} else {
|
||||||
@@ -265,7 +315,7 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get authenticationTypeDisplayName(): string {
|
public get authenticationTypeDisplayName(): string {
|
||||||
let optionMetadata = this.serverCapabilities ? this.serverCapabilities.connectionOptions.find(o => o.specialValueType === ConnectionOptionSpecialType.authType) : undefined;
|
let optionMetadata = this.hasServerCapabilities() ? this.serverCapabilities.connectionOptions.find(o => o.specialValueType === ConnectionOptionSpecialType.authType) : undefined;
|
||||||
let authType = this.authenticationType;
|
let authType = this.authenticationType;
|
||||||
let displayName: string = authType;
|
let displayName: string = authType;
|
||||||
|
|
||||||
@@ -291,28 +341,68 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||||||
return ':';
|
return ':';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get titleParts(): string[] {
|
public static get displayIdSeparator(): string {
|
||||||
let parts: string[] = [];
|
return '; ';
|
||||||
// Always put these three on top. TODO: maybe only for MSSQL?
|
}
|
||||||
parts.push(this.serverName);
|
|
||||||
parts.push(this.databaseName);
|
|
||||||
parts.push(this.authenticationTypeDisplayName);
|
|
||||||
|
|
||||||
if (this.serverCapabilities) {
|
public static get displayNameValueSeparator(): string {
|
||||||
|
return '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all non specialValueType (or if distinct connections share same connection name, everything but connectionName and password).
|
||||||
|
* Also allows for getting the non default options for this profile. (this function is used for changing the title).
|
||||||
|
* @param needSpecial include all the special options key besides connection name or password in case we have multiple
|
||||||
|
* distinct connections sharing the same connection name.
|
||||||
|
* @param getNonDefault get only the non default options (for individual connections) to be used for identfying different properties
|
||||||
|
* among connections sharing the same title.
|
||||||
|
*/
|
||||||
|
public getConnectionOptionsList(needSpecial: boolean, getNonDefault: boolean): azdata.ConnectionOption[] {
|
||||||
|
let connectionOptions: azdata.ConnectionOption[] = [];
|
||||||
|
|
||||||
|
if (this.hasServerCapabilities()) {
|
||||||
this.serverCapabilities.connectionOptions.forEach(element => {
|
this.serverCapabilities.connectionOptions.forEach(element => {
|
||||||
if (element.specialValueType !== ConnectionOptionSpecialType.serverName &&
|
if (((!needSpecial && element.specialValueType !== ConnectionOptionSpecialType.serverName &&
|
||||||
element.specialValueType !== ConnectionOptionSpecialType.databaseName &&
|
element.specialValueType !== ConnectionOptionSpecialType.databaseName &&
|
||||||
element.specialValueType !== ConnectionOptionSpecialType.authType &&
|
element.specialValueType !== ConnectionOptionSpecialType.authType &&
|
||||||
element.specialValueType !== ConnectionOptionSpecialType.password &&
|
element.specialValueType !== ConnectionOptionSpecialType.userName) || needSpecial) &&
|
||||||
element.specialValueType !== ConnectionOptionSpecialType.connectionName &&
|
element.specialValueType !== ConnectionOptionSpecialType.connectionName &&
|
||||||
element.isIdentity && element.valueType === ServiceOptionType.string) {
|
element.specialValueType !== ConnectionOptionSpecialType.password) {
|
||||||
let value = this.getOptionValue(element.name);
|
if (getNonDefault) {
|
||||||
if (value) {
|
let value = this.getOptionValue(element.name);
|
||||||
parts.push(value);
|
if (value && value !== element.defaultValue) {
|
||||||
|
connectionOptions.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
connectionOptions.push(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
//Need to sort for consistency.
|
||||||
|
connectionOptions.sort();
|
||||||
|
return connectionOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append all non default options to tooltip string if useFullOptions is enabled.
|
||||||
|
*/
|
||||||
|
public getNonDefaultOptionsString(): string {
|
||||||
|
let parts: string = "";
|
||||||
|
let nonDefaultOptions = this.getConnectionOptionsList(false, true);
|
||||||
|
nonDefaultOptions.forEach(element => {
|
||||||
|
let value = this.getOptionValue(element.name);
|
||||||
|
if (parts.length === 0) {
|
||||||
|
parts = " (";
|
||||||
|
}
|
||||||
|
let addValue = element.name + ProviderConnectionInfo.displayNameValueSeparator + `${value}`;
|
||||||
|
parts += parts === " (" ? addValue : (ProviderConnectionInfo.displayIdSeparator + addValue);
|
||||||
|
});
|
||||||
|
if (parts.length > 0) {
|
||||||
|
parts += ")";
|
||||||
|
}
|
||||||
|
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,6 +181,30 @@ suite('ConnectionConfig', () => {
|
|||||||
isRequired: true,
|
isRequired: true,
|
||||||
specialValueType: ConnectionOptionSpecialType.password,
|
specialValueType: ConnectionOptionSpecialType.password,
|
||||||
valueType: ServiceOptionType.string
|
valueType: ServiceOptionType.string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'testProperty1',
|
||||||
|
displayName: undefined!,
|
||||||
|
description: undefined!,
|
||||||
|
groupName: undefined!,
|
||||||
|
categoryValues: undefined!,
|
||||||
|
defaultValue: "default",
|
||||||
|
isIdentity: true,
|
||||||
|
isRequired: true,
|
||||||
|
specialValueType: undefined!,
|
||||||
|
valueType: ServiceOptionType.string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'testProperty2',
|
||||||
|
displayName: undefined!,
|
||||||
|
description: undefined!,
|
||||||
|
groupName: undefined!,
|
||||||
|
categoryValues: undefined!,
|
||||||
|
defaultValue: "10",
|
||||||
|
isIdentity: true,
|
||||||
|
isRequired: true,
|
||||||
|
specialValueType: undefined!,
|
||||||
|
valueType: ServiceOptionType.number
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
@@ -677,6 +701,183 @@ suite('ConnectionConfig', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('change group for connection should accept similar connection with different options', async () => {
|
||||||
|
let changingProfile: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {
|
||||||
|
'testProperty1': 'nonDefault',
|
||||||
|
'testProperty2': '10',
|
||||||
|
},
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3-2',
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
let existingProfile = ConnectionProfile.convertToProfileStore(capabilitiesService.object, {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'test',
|
||||||
|
groupId: 'test',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { 'testProperty2': '15' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3',
|
||||||
|
connectionName: undefined!
|
||||||
|
});
|
||||||
|
|
||||||
|
let _testConnections = [...deepClone(testConnections), existingProfile, changingProfile];
|
||||||
|
|
||||||
|
let configurationService = new TestConfigurationService();
|
||||||
|
configurationService.updateValue('datasource.connections', _testConnections, ConfigurationTarget.USER);
|
||||||
|
|
||||||
|
let connectionProfile = new ConnectionProfile(capabilitiesService.object, changingProfile);
|
||||||
|
|
||||||
|
let config = new ConnectionConfig(configurationService, capabilitiesService.object);
|
||||||
|
|
||||||
|
await config.changeGroupIdForConnection(connectionProfile, 'test');
|
||||||
|
|
||||||
|
let editedConnections = configurationService.inspect<IConnectionProfileStore[]>('datasource.connections').userValue!;
|
||||||
|
|
||||||
|
assert.strictEqual(editedConnections.length, _testConnections.length);
|
||||||
|
let editedConnection = editedConnections.find(con => con.id === 'server3-2');
|
||||||
|
assert.ok(editedConnection);
|
||||||
|
assert.strictEqual(editedConnection!.groupId, 'test');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('change group for connection should not accept similar connection with default options same as another', async () => {
|
||||||
|
let changingProfile: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {
|
||||||
|
'testProperty1': 'nonDefault',
|
||||||
|
'testProperty2': '10',
|
||||||
|
},
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3-2',
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
let existingProfile = ConnectionProfile.convertToProfileStore(capabilitiesService.object, {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'test',
|
||||||
|
groupId: 'test',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { 'testProperty1': 'nonDefault' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3',
|
||||||
|
connectionName: undefined!
|
||||||
|
});
|
||||||
|
|
||||||
|
let _testConnections = [...deepClone(testConnections), existingProfile, changingProfile];
|
||||||
|
|
||||||
|
let configurationService = new TestConfigurationService();
|
||||||
|
configurationService.updateValue('datasource.connections', _testConnections, ConfigurationTarget.USER);
|
||||||
|
|
||||||
|
let connectionProfile = new ConnectionProfile(capabilitiesService.object, changingProfile);
|
||||||
|
|
||||||
|
let config = new ConnectionConfig(configurationService, capabilitiesService.object);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await config.changeGroupIdForConnection(connectionProfile, 'test');
|
||||||
|
assert.fail();
|
||||||
|
} catch (e) {
|
||||||
|
let editedConnections = configurationService.inspect<IConnectionProfileStore[]>('datasource.connections').userValue!;
|
||||||
|
// two
|
||||||
|
assert.strictEqual(editedConnections.length, _testConnections.length);
|
||||||
|
let editedConnection = editedConnections.find(con => con.id === 'server3-2');
|
||||||
|
assert.ok(!!editedConnection);
|
||||||
|
assert.strictEqual(editedConnection!.groupId, 'g3');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('change group for connection should accept similar connection with a distinguishing option', async () => {
|
||||||
|
let changingProfile: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {
|
||||||
|
'testProperty1': 'nonDefault',
|
||||||
|
'testProperty2': '15',
|
||||||
|
},
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3-2',
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
let existingProfile = ConnectionProfile.convertToProfileStore(capabilitiesService.object, {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'test',
|
||||||
|
groupId: 'test',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { 'testProperty2': '15' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3',
|
||||||
|
connectionName: undefined!
|
||||||
|
});
|
||||||
|
|
||||||
|
let _testConnections = [...deepClone(testConnections), existingProfile, changingProfile];
|
||||||
|
|
||||||
|
let configurationService = new TestConfigurationService();
|
||||||
|
configurationService.updateValue('datasource.connections', _testConnections, ConfigurationTarget.USER);
|
||||||
|
|
||||||
|
let connectionProfile = new ConnectionProfile(capabilitiesService.object, changingProfile);
|
||||||
|
|
||||||
|
let config = new ConnectionConfig(configurationService, capabilitiesService.object);
|
||||||
|
|
||||||
|
await config.changeGroupIdForConnection(connectionProfile, 'test');
|
||||||
|
|
||||||
|
let editedConnections = configurationService.inspect<IConnectionProfileStore[]>('datasource.connections').userValue!;
|
||||||
|
|
||||||
|
assert.strictEqual(editedConnections.length, _testConnections.length);
|
||||||
|
let editedConnection = editedConnections.find(con => con.id === 'server3-2');
|
||||||
|
assert.ok(editedConnection);
|
||||||
|
assert.strictEqual(editedConnection!.groupId, 'test');
|
||||||
|
});
|
||||||
|
|
||||||
test('change group(parent) for connection', async () => {
|
test('change group(parent) for connection', async () => {
|
||||||
let newProfile: IConnectionProfile = {
|
let newProfile: IConnectionProfile = {
|
||||||
serverName: 'server3',
|
serverName: 'server3',
|
||||||
@@ -778,4 +979,140 @@ suite('ConnectionConfig', () => {
|
|||||||
assert.strictEqual(editGroups.length, testGroups.length);
|
assert.strictEqual(editGroups.length, testGroups.length);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('isDuplicateEdit should return true if an edit profile matches an existing profile', async () => {
|
||||||
|
let originalProfile: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3-2',
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
let changedProfile: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'test',
|
||||||
|
groupId: 'test',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3-2',
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
let existingProfile = ConnectionProfile.convertToProfileStore(capabilitiesService.object, {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'test',
|
||||||
|
groupId: 'test',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3',
|
||||||
|
connectionName: undefined!
|
||||||
|
});
|
||||||
|
|
||||||
|
let _testConnections = [...deepClone(testConnections), existingProfile, originalProfile];
|
||||||
|
|
||||||
|
let configurationService = new TestConfigurationService();
|
||||||
|
configurationService.updateValue('datasource.connections', _testConnections, ConfigurationTarget.USER);
|
||||||
|
|
||||||
|
let connectionProfile = new ConnectionProfile(capabilitiesService.object, changedProfile);
|
||||||
|
|
||||||
|
let config = new ConnectionConfig(configurationService, capabilitiesService.object);
|
||||||
|
|
||||||
|
let matcher = (a: IConnectionProfile, b: IConnectionProfile) => a.id === originalProfile.id;
|
||||||
|
let result = await config.isDuplicateEdit(connectionProfile, matcher);
|
||||||
|
|
||||||
|
assert(result, 'Matcher did not find a match for identical edit');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isDuplicateEdit should return false if an edit profile has different properties', async () => {
|
||||||
|
let originalProfile: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'test',
|
||||||
|
groupId: 'test',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3-2',
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
let changedProfile: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: 'Integrated',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'test',
|
||||||
|
groupId: 'test',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3-2',
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
let existingProfile = ConnectionProfile.convertToProfileStore(capabilitiesService.object, {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'test',
|
||||||
|
groupId: 'test',
|
||||||
|
getOptionsKey: () => { return 'connectionId'; },
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: 'server3',
|
||||||
|
connectionName: undefined!
|
||||||
|
});
|
||||||
|
|
||||||
|
let _testConnections = [...deepClone(testConnections), existingProfile, originalProfile];
|
||||||
|
|
||||||
|
let configurationService = new TestConfigurationService();
|
||||||
|
configurationService.updateValue('datasource.connections', _testConnections, ConfigurationTarget.USER);
|
||||||
|
|
||||||
|
let connectionProfile = new ConnectionProfile(capabilitiesService.object, changedProfile);
|
||||||
|
|
||||||
|
let config = new ConnectionConfig(configurationService, capabilitiesService.object);
|
||||||
|
|
||||||
|
let matcher = (a: IConnectionProfile, b: IConnectionProfile) => a.id === originalProfile.id;
|
||||||
|
let result = await config.isDuplicateEdit(connectionProfile, matcher);
|
||||||
|
|
||||||
|
assert(!result, 'Matcher matched the profile even when it had a different property');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -171,7 +171,8 @@ suite('SQL ConnectionProfileInfo tests', () => {
|
|||||||
msSQLCapabilities = {
|
msSQLCapabilities = {
|
||||||
providerId: mssqlProviderName,
|
providerId: mssqlProviderName,
|
||||||
displayName: 'MSSQL',
|
displayName: 'MSSQL',
|
||||||
connectionOptions: connectionProvider
|
connectionOptions: connectionProvider,
|
||||||
|
useFullOptions: true
|
||||||
};
|
};
|
||||||
capabilitiesService = new TestCapabilitiesService();
|
capabilitiesService = new TestCapabilitiesService();
|
||||||
capabilitiesService.capabilities[mssqlProviderName] = { connection: msSQLCapabilities };
|
capabilitiesService.capabilities[mssqlProviderName] = { connection: msSQLCapabilities };
|
||||||
@@ -234,7 +235,7 @@ suite('SQL ConnectionProfileInfo tests', () => {
|
|||||||
|
|
||||||
test('getOptionsKey should create a valid unique id', () => {
|
test('getOptionsKey should create a valid unique id', () => {
|
||||||
let conn = new ConnectionProfile(capabilitiesService, iConnectionProfile);
|
let conn = new ConnectionProfile(capabilitiesService, iConnectionProfile);
|
||||||
let expectedId = 'providerName:MSSQL|authenticationType:|databaseName:database|serverName:new server|userName:user|databaseDisplayName:database|group:group id';
|
let expectedId = 'providerName:MSSQL|connectionName:new name|databaseName:database|serverName:new server|userName:user|databaseDisplayName:database|groupId:group id';
|
||||||
let id = conn.getOptionsKey();
|
let id = conn.getOptionsKey();
|
||||||
assert.strictEqual(id, expectedId);
|
assert.strictEqual(id, expectedId);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
setup(() => {
|
setup(() => {
|
||||||
let capabilities: azdata.DataProtocolServerCapabilities[] = [];
|
|
||||||
let connectionProvider: azdata.ConnectionOption[] = [
|
let connectionProvider: azdata.ConnectionOption[] = [
|
||||||
{
|
{
|
||||||
name: 'connectionName',
|
name: 'connectionName',
|
||||||
@@ -125,8 +124,8 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||||||
providerId: mssqlProviderName,
|
providerId: mssqlProviderName,
|
||||||
displayName: 'MSSQL',
|
displayName: 'MSSQL',
|
||||||
connectionOptions: connectionProvider,
|
connectionOptions: connectionProvider,
|
||||||
|
useFullOptions: true
|
||||||
};
|
};
|
||||||
capabilities.push(msSQLCapabilities);
|
|
||||||
capabilitiesService = new TestCapabilitiesService();
|
capabilitiesService = new TestCapabilitiesService();
|
||||||
capabilitiesService.capabilities[mssqlProviderName] = { connection: msSQLCapabilities };
|
capabilitiesService.capabilities[mssqlProviderName] = { connection: msSQLCapabilities };
|
||||||
});
|
});
|
||||||
@@ -230,15 +229,37 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('getOptionsKey should create a valid unique id', () => {
|
test('getOptionsKey should create a valid unique id', () => {
|
||||||
|
// Test the new option key format
|
||||||
let conn = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
let conn = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
||||||
// **IMPORTANT** This should NEVER change without thorough review and consideration of side effects. This key controls
|
let expectedId = 'providerName:MSSQL|connectionName:name|databaseName:database|serverName:new server|userName:user';
|
||||||
// things like how passwords are saved, which means if its changed then serious side effects will occur.
|
|
||||||
let expectedId = 'providerName:MSSQL|authenticationType:|databaseName:database|serverName:new server|userName:user';
|
|
||||||
let id = conn.getOptionsKey();
|
let id = conn.getOptionsKey();
|
||||||
assert.strictEqual(id, expectedId);
|
assert.strictEqual(id, expectedId);
|
||||||
|
|
||||||
|
// Test for original options key (used for retrieving passwords and as a fallback for unsupported providers)
|
||||||
|
// **IMPORTANT** The original format option key should NEVER change without thorough review and consideration of side effects. This version of the key controls
|
||||||
|
// things like how passwords are saved, which means if its changed then serious side effects will occur.
|
||||||
|
expectedId = 'providerName:MSSQL|authenticationType:|databaseName:database|serverName:new server|userName:user';
|
||||||
|
id = conn.getOptionsKey(true);
|
||||||
|
assert.strictEqual(id, expectedId);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('getOptionsKey should create the same ID regardless of optional options', () => {
|
test('getOptionsKey should return original formatted ID if useFullOptions is not supported', () => {
|
||||||
|
// Test the new option key format
|
||||||
|
let originalCapabilitiesConnection = capabilitiesService.capabilities[mssqlProviderName].connection;
|
||||||
|
originalCapabilitiesConnection.useFullOptions = false;
|
||||||
|
let newCapabilitiesService = new TestCapabilitiesService();
|
||||||
|
newCapabilitiesService.capabilities[mssqlProviderName] = { connection: originalCapabilitiesConnection }
|
||||||
|
let conn = new ProviderConnectionInfo(newCapabilitiesService, connectionProfile);
|
||||||
|
let expectedId = 'providerName:MSSQL|authenticationType:|databaseName:database|serverName:new server|userName:user';
|
||||||
|
let id = conn.getOptionsKey();
|
||||||
|
assert.strictEqual(id, expectedId);
|
||||||
|
|
||||||
|
// Should be the same when getOriginalOptions is true.
|
||||||
|
id = conn.getOptionsKey(true);
|
||||||
|
assert.strictEqual(id, expectedId);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getOptionsKey should create different keys based on optional options', () => {
|
||||||
const conn1 = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
const conn1 = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
||||||
let id1 = conn1.getOptionsKey();
|
let id1 = conn1.getOptionsKey();
|
||||||
|
|
||||||
@@ -248,6 +269,19 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||||||
const conn2 = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
const conn2 = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
||||||
const id2 = conn2.getOptionsKey();
|
const id2 = conn2.getOptionsKey();
|
||||||
|
|
||||||
|
assert.notEqual(id1, id2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getOptionsKey should have the same key if original options is used', () => {
|
||||||
|
const conn1 = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
||||||
|
let id1 = conn1.getOptionsKey(true);
|
||||||
|
|
||||||
|
connectionProfile.options = {
|
||||||
|
'encrypt': true
|
||||||
|
};
|
||||||
|
const conn2 = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
||||||
|
const id2 = conn2.getOptionsKey(true);
|
||||||
|
|
||||||
assert.strictEqual(id1, id2);
|
assert.strictEqual(id1, id2);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -258,16 +292,6 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||||||
assert.notStrictEqual(conn.getOptionsKey(), conn2.getOptionsKey());
|
assert.notStrictEqual(conn.getOptionsKey(), conn2.getOptionsKey());
|
||||||
});
|
});
|
||||||
|
|
||||||
test('titleParts should return server, database and auth type as first items', () => {
|
|
||||||
let conn = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
|
||||||
let titleParts = conn.titleParts;
|
|
||||||
assert.strictEqual(titleParts.length, 4);
|
|
||||||
assert.strictEqual(titleParts[0], connectionProfile.serverName);
|
|
||||||
assert.strictEqual(titleParts[1], connectionProfile.databaseName);
|
|
||||||
assert.strictEqual(titleParts[2], connectionProfile.authenticationType);
|
|
||||||
assert.strictEqual(titleParts[3], connectionProfile.userName);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('getProviderFromOptionsKey should return the provider name from the options key successfully', () => {
|
test('getProviderFromOptionsKey should return the provider name from the options key successfully', () => {
|
||||||
let optionsKey = `providerName:${mssqlProviderName}|authenticationType:|databaseName:database|serverName:new server|userName:user`;
|
let optionsKey = `providerName:${mssqlProviderName}|authenticationType:|databaseName:database|serverName:new server|userName:user`;
|
||||||
let actual = ProviderConnectionInfo.getProviderFromOptionsKey(optionsKey);
|
let actual = ProviderConnectionInfo.getProviderFromOptionsKey(optionsKey);
|
||||||
|
|||||||
@@ -354,6 +354,10 @@ export class TestConnectionManagementService implements IConnectionManagementSer
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEditorConnectionProfileTitle(profile: IConnectionProfile, getNonDefaultsOnly?: boolean): string {
|
||||||
|
return undefined!;
|
||||||
|
}
|
||||||
|
|
||||||
openCustomErrorDialog(options: azdata.window.IErrorDialogOptions): Promise<string | undefined> {
|
openCustomErrorDialog(options: azdata.window.IErrorDialogOptions): Promise<string | undefined> {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,6 +186,11 @@ export class MainThreadConnectionManagement extends Disposable implements MainTh
|
|||||||
return this._connectionManagementService.openChangePasswordDialog(convertedProfile);
|
return this._connectionManagementService.openChangePasswordDialog(convertedProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $getEditorConnectionProfileTitle(profile: IConnectionProfile, getNonDefaultsOnly?: boolean): Thenable<string | undefined> {
|
||||||
|
// Need to have access to getOptionsKey, so recreate profile from details.
|
||||||
|
return Promise.resolve(this._connectionManagementService.getEditorConnectionProfileTitle(profile, getNonDefaultsOnly));
|
||||||
|
}
|
||||||
|
|
||||||
public async $listDatabases(connectionId: string): Promise<string[]> {
|
public async $listDatabases(connectionId: string): Promise<string[]> {
|
||||||
let connectionUri = await this.$getUriForConnection(connectionId);
|
let connectionUri = await this.$getUriForConnection(connectionId);
|
||||||
let result = await this._connectionManagementService.listDatabases(connectionUri);
|
let result = await this._connectionManagementService.listDatabases(connectionUri);
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap
|
|||||||
return this._proxy.$openChangePasswordDialog(profile);
|
return this._proxy.$openChangePasswordDialog(profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $getEditorConnectionProfileTitle(profile: azdata.IConnectionProfile, getNonDefaultsOnly?: boolean): Thenable<string> {
|
||||||
|
return this._proxy.$getEditorConnectionProfileTitle(profile, getNonDefaultsOnly);
|
||||||
|
}
|
||||||
|
|
||||||
public $listDatabases(connectionId: string): Thenable<string[]> {
|
public $listDatabases(connectionId: string): Thenable<string[]> {
|
||||||
return this._proxy.$listDatabases(connectionId);
|
return this._proxy.$listDatabases(connectionId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
|
|||||||
openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined> {
|
openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined> {
|
||||||
return extHostConnectionManagement.$openChangePasswordDialog(profile);
|
return extHostConnectionManagement.$openChangePasswordDialog(profile);
|
||||||
},
|
},
|
||||||
|
getEditorConnectionProfileTitle(profile: azdata.IConnectionProfile, getNonDefaultsOnly?: boolean): Thenable<string> {
|
||||||
|
return extHostConnectionManagement.$getEditorConnectionProfileTitle(profile, getNonDefaultsOnly);
|
||||||
|
},
|
||||||
listDatabases(connectionId: string): Thenable<string[]> {
|
listDatabases(connectionId: string): Thenable<string[]> {
|
||||||
return extHostConnectionManagement.$listDatabases(connectionId);
|
return extHostConnectionManagement.$listDatabases(connectionId);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -724,6 +724,7 @@ export interface MainThreadConnectionManagementShape extends IDisposable {
|
|||||||
$getServerInfo(connectedId: string): Thenable<azdata.ServerInfo>;
|
$getServerInfo(connectedId: string): Thenable<azdata.ServerInfo>;
|
||||||
$openConnectionDialog(providers: string[], initialConnectionProfile?: azdata.IConnectionProfile, connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable<azdata.connection.Connection>;
|
$openConnectionDialog(providers: string[], initialConnectionProfile?: azdata.IConnectionProfile, connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable<azdata.connection.Connection>;
|
||||||
$openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined>;
|
$openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined>;
|
||||||
|
$getEditorConnectionProfileTitle(profile: azdata.IConnectionProfile, getNonDefaultsOnly?: boolean): Thenable<string>;
|
||||||
$listDatabases(connectionId: string): Thenable<string[]>;
|
$listDatabases(connectionId: string): Thenable<string[]>;
|
||||||
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
|
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
|
||||||
$getUriForConnection(connectionId: string): Thenable<string>;
|
$getUriForConnection(connectionId: string): Thenable<string>;
|
||||||
|
|||||||
@@ -243,11 +243,18 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
|
|||||||
title = this._description + ' ';
|
title = this._description + ' ';
|
||||||
}
|
}
|
||||||
if (profile) {
|
if (profile) {
|
||||||
title += `${profile.serverName}`;
|
let fullTitleText = this.connectionManagementService.getEditorConnectionProfileTitle(profile);
|
||||||
if (profile.databaseName) {
|
if (fullTitleText.length !== 0) {
|
||||||
title += `.${profile.databaseName}`;
|
title += fullTitleText;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
title += `${profile.serverName}`;
|
||||||
|
if (profile.databaseName) {
|
||||||
|
title += `.${profile.databaseName}`;
|
||||||
|
}
|
||||||
|
title += ` (${profile.userName || profile.authenticationType})`;
|
||||||
|
title += profile.getOptionsKey();
|
||||||
}
|
}
|
||||||
title += ` (${profile.userName || profile.authenticationType})`;
|
|
||||||
} else {
|
} else {
|
||||||
title += localize('disconnected', "disconnected");
|
title += localize('disconnected', "disconnected");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,23 +68,38 @@ export class ConnectionStatusbarItem extends Disposable implements IWorkbenchCon
|
|||||||
|
|
||||||
// Set connection info to connection status bar
|
// Set connection info to connection status bar
|
||||||
private _setConnectionText(connectionProfile: IConnectionProfile): void {
|
private _setConnectionText(connectionProfile: IConnectionProfile): void {
|
||||||
let text: string = connectionProfile.serverName;
|
let text: string = undefined;
|
||||||
if (text) {
|
let fullEditorText: string = this.connectionManagementService.getEditorConnectionProfileTitle(connectionProfile);
|
||||||
if (connectionProfile.databaseName && connectionProfile.databaseName !== '') {
|
if (fullEditorText.length === 0) {
|
||||||
text = text + ' : ' + connectionProfile.databaseName;
|
text = connectionProfile.serverName;
|
||||||
} else {
|
if (text) {
|
||||||
text = text + ' : ' + '<default>';
|
if (connectionProfile.databaseName && connectionProfile.databaseName !== '') {
|
||||||
|
text = text + ' : ' + connectionProfile.databaseName;
|
||||||
|
} else {
|
||||||
|
text = text + ' : ' + '<default>';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
let tooltip: string =
|
text = fullEditorText;
|
||||||
'Server: ' + connectionProfile.serverName + '\r\n' +
|
|
||||||
'Database: ' + (connectionProfile.databaseName ? connectionProfile.databaseName : '<default>') + '\r\n';
|
|
||||||
|
|
||||||
if (connectionProfile.userName && connectionProfile.userName !== '') {
|
|
||||||
tooltip = tooltip + 'Login: ' + connectionProfile.userName + '\r\n';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let tooltip: string = undefined;
|
||||||
|
|
||||||
|
if (!fullEditorText) {
|
||||||
|
tooltip = 'Server: ' + connectionProfile.serverName + '\r\n' +
|
||||||
|
'Database: ' + (connectionProfile.databaseName ? connectionProfile.databaseName : '<default>') + '\r\n';
|
||||||
|
|
||||||
|
if (connectionProfile.userName && connectionProfile.userName !== '') {
|
||||||
|
tooltip = tooltip + 'Login: ' + connectionProfile.userName + '\r\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// It is difficult to have every possible option that is different displayed as above with consistent naming, therefore the tooltip will show the full string.
|
||||||
|
tooltip = fullEditorText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.statusItem.update({
|
this.statusItem.update({
|
||||||
name: this.name,
|
name: this.name,
|
||||||
text: text,
|
text: text,
|
||||||
|
|||||||
@@ -731,7 +731,10 @@ export class AttachToDropdown extends SelectBox {
|
|||||||
} else {
|
} else {
|
||||||
let connections: string[] = [];
|
let connections: string[] = [];
|
||||||
if (model.context && model.context.title && (connProviderIds.includes(this.model.context.providerName))) {
|
if (model.context && model.context.title && (connProviderIds.includes(this.model.context.providerName))) {
|
||||||
connections.push(model.context.title);
|
let textResult = model.context.title;
|
||||||
|
let fullTitleText = this._connectionManagementService.getEditorConnectionProfileTitle(model.context);
|
||||||
|
textResult = fullTitleText.length !== 0 ? fullTitleText : textResult;
|
||||||
|
connections.push(textResult);
|
||||||
} else if (this._configurationService.getValue(saveConnectionNameConfigName) && model.savedConnectionName) {
|
} else if (this._configurationService.getValue(saveConnectionNameConfigName) && model.savedConnectionName) {
|
||||||
connections.push(model.savedConnectionName);
|
connections.push(model.savedConnectionName);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
// get the full ConnectionProfiles with the server info updated properly
|
// get the full ConnectionProfiles with the server info updated properly
|
||||||
const treeInput = TreeUpdateUtils.getTreeInput(this._connectionManagementService)!;
|
const treeInput = TreeUpdateUtils.getTreeInput(this._connectionManagementService)!;
|
||||||
await this._tree.setInput(treeInput);
|
await this._tree.setInput(treeInput);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||||
} else {
|
} else {
|
||||||
if (this._connectionManagementService.hasRegisteredServers()) {
|
if (this._connectionManagementService.hasRegisteredServers()) {
|
||||||
@@ -263,6 +264,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
if (connectionParentGroup) {
|
if (connectionParentGroup) {
|
||||||
connectionParentGroup.addOrReplaceConnection(newConnection);
|
connectionParentGroup.addOrReplaceConnection(newConnection);
|
||||||
await this._tree.updateChildren(connectionParentGroup);
|
await this._tree.updateChildren(connectionParentGroup);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
await this._tree.revealSelectFocusElement(newConnection);
|
await this._tree.revealSelectFocusElement(newConnection);
|
||||||
await this._tree.expand(newConnection);
|
await this._tree.expand(newConnection);
|
||||||
}
|
}
|
||||||
@@ -277,6 +279,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
await this._tree.rerender(connectionInTree);
|
await this._tree.rerender(connectionInTree);
|
||||||
await this._tree.revealSelectFocusElement(connectionInTree);
|
await this._tree.revealSelectFocusElement(connectionInTree);
|
||||||
await this._tree.updateChildren(connectionInTree);
|
await this._tree.updateChildren(connectionInTree);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
await this._tree.expand(connectionInTree);
|
await this._tree.expand(connectionInTree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,6 +292,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
if (parentGroup) {
|
if (parentGroup) {
|
||||||
parentGroup.removeConnections([e]);
|
parentGroup.removeConnections([e]);
|
||||||
await this._tree.updateChildren(parentGroup);
|
await this._tree.updateChildren(parentGroup);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
await this._tree.revealSelectFocusElement(parentGroup);
|
await this._tree.revealSelectFocusElement(parentGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,12 +310,14 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
const newProfileParent = <ConnectionProfileGroup>this._tree.getElementById(e.profile.groupId);
|
const newProfileParent = <ConnectionProfileGroup>this._tree.getElementById(e.profile.groupId);
|
||||||
newProfileParent.addOrReplaceConnection(e.profile);
|
newProfileParent.addOrReplaceConnection(e.profile);
|
||||||
await this._tree.updateChildren(newProfileParent);
|
await this._tree.updateChildren(newProfileParent);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
await this._tree.revealSelectFocusElement(e.profile);
|
await this._tree.revealSelectFocusElement(e.profile);
|
||||||
await this._tree.expand(e.profile);
|
await this._tree.expand(e.profile);
|
||||||
} else {
|
} else {
|
||||||
// If the profile was not moved to a different group then just update the profile in the group.
|
// If the profile was not moved to a different group then just update the profile in the group.
|
||||||
oldProfileParent.replaceConnection(e.profile, e.oldProfileId);
|
oldProfileParent.replaceConnection(e.profile, e.oldProfileId);
|
||||||
await this._tree.updateChildren(oldProfileParent)
|
await this._tree.updateChildren(oldProfileParent)
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
await this._tree.revealSelectFocusElement(e.profile);
|
await this._tree.revealSelectFocusElement(e.profile);
|
||||||
await this._tree.expand(e.profile);
|
await this._tree.expand(e.profile);
|
||||||
}
|
}
|
||||||
@@ -333,6 +339,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
newParent.addOrReplaceConnection(movedConnection);
|
newParent.addOrReplaceConnection(movedConnection);
|
||||||
await this._tree.updateChildren(newParent);
|
await this._tree.updateChildren(newParent);
|
||||||
}
|
}
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
const newConnection = this._tree.getElementById(movedConnection.id);
|
const newConnection = this._tree.getElementById(movedConnection.id);
|
||||||
if (newConnection) {
|
if (newConnection) {
|
||||||
await this._tree.revealSelectFocusElement(newConnection);
|
await this._tree.revealSelectFocusElement(newConnection);
|
||||||
@@ -347,6 +354,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
const parent = <ConnectionProfileGroup>this._tree.getElementById(e.parentId);
|
const parent = <ConnectionProfileGroup>this._tree.getElementById(e.parentId);
|
||||||
parent.children = parent.children.filter(c => c.id !== e.id);
|
parent.children = parent.children.filter(c => c.id !== e.id);
|
||||||
await this._tree.updateChildren(parent);
|
await this._tree.updateChildren(parent);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
await this._tree.revealSelectFocusElement(parent);
|
await this._tree.revealSelectFocusElement(parent);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -369,6 +377,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
e.parent = parent;
|
e.parent = parent;
|
||||||
e.parentId = parent.id;
|
e.parentId = parent.id;
|
||||||
await this._tree.updateChildren(parent);
|
await this._tree.updateChildren(parent);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
await this._tree.revealSelectFocusElement(e);
|
await this._tree.revealSelectFocusElement(e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -379,6 +388,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
if (newParent) {
|
if (newParent) {
|
||||||
newParent.children[newParent.children.findIndex(c => c.id === e.id)] = e;
|
newParent.children[newParent.children.findIndex(c => c.id === e.id)] = e;
|
||||||
await this._tree.updateChildren(newParent);
|
await this._tree.updateChildren(newParent);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
await this._tree.revealSelectFocusElement(e);
|
await this._tree.revealSelectFocusElement(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -397,6 +407,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
(<ConnectionProfileGroup>movedGroup).parent = newParent;
|
(<ConnectionProfileGroup>movedGroup).parent = newParent;
|
||||||
(<ConnectionProfileGroup>movedGroup).parentId = newParent.id;
|
(<ConnectionProfileGroup>movedGroup).parentId = newParent.id;
|
||||||
await this._tree.updateChildren(newParent);
|
await this._tree.updateChildren(newParent);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
await this._tree.revealSelectFocusElement(movedGroup);
|
await this._tree.revealSelectFocusElement(movedGroup);
|
||||||
// Expanding the previously expanded children of the moved group after the move.
|
// Expanding the previously expanded children of the moved group after the move.
|
||||||
this._tree.expandElements(profileExpandedState);
|
this._tree.expandElements(profileExpandedState);
|
||||||
@@ -636,6 +647,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
if (this._tree instanceof AsyncServerTree) {
|
if (this._tree instanceof AsyncServerTree) {
|
||||||
await this._tree.setInput(treeInput!);
|
await this._tree.setInput(treeInput!);
|
||||||
await this._tree.updateChildren(treeInput!);
|
await this._tree.updateChildren(treeInput!);
|
||||||
|
await this.refreshConnectionTreeTitles();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await this._tree.setInput(treeInput!);
|
await this._tree.setInput(treeInput!);
|
||||||
@@ -897,4 +909,11 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||||||
}
|
}
|
||||||
return actionContext;
|
return actionContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async refreshConnectionTreeTitles(): Promise<void> {
|
||||||
|
let treeInput = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
|
||||||
|
let treeArray = TreeUpdateUtils.alterTreeChildrenTitles([treeInput]);
|
||||||
|
treeInput = treeArray[0];
|
||||||
|
await this._tree!.setInput(treeInput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -540,6 +540,22 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
|||||||
|
|
||||||
let isEdit = options?.params?.isEditConnection ?? false;
|
let isEdit = options?.params?.isEditConnection ?? false;
|
||||||
|
|
||||||
|
let matcher: interfaces.ProfileMatcher;
|
||||||
|
if (isEdit) {
|
||||||
|
matcher = (a: interfaces.IConnectionProfile, b: interfaces.IConnectionProfile) => a.id === options.params.oldProfileId;
|
||||||
|
|
||||||
|
//Check to make sure the edits are not identical to another connection.
|
||||||
|
await this._connectionStore.isDuplicateEdit(connection, matcher).then(result => {
|
||||||
|
if (result) {
|
||||||
|
// Must get connection group name here as it may not always be initialized and causes problems when deleting when included with options.
|
||||||
|
this._logService.error(`Profile edit for '${connection.id}' exactly matches an existing profile with data: '${ConnectionProfile.getDisplayOptionsKey(connection.getOptionsKey())}'`);
|
||||||
|
throw new Error(`Cannot save profile, the selected connection options are identical to an existing profile with details: \n
|
||||||
|
${ConnectionProfile.getDisplayOptionsKey(connection.getOptionsKey())}${(connection.groupFullName !== undefined && connection.groupFullName !== '' && connection.groupFullName !== '/') ?
|
||||||
|
ConnectionProfile.displayIdSeparator + 'groupName' + ConnectionProfile.displayNameValueSeparator + connection.groupFullName : ''}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!uri) {
|
if (!uri) {
|
||||||
uri = Utils.generateUri(connection);
|
uri = Utils.generateUri(connection);
|
||||||
}
|
}
|
||||||
@@ -588,11 +604,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
|||||||
callbacks.onConnectSuccess(options.params, connectionResult.connectionProfile);
|
callbacks.onConnectSuccess(options.params, connectionResult.connectionProfile);
|
||||||
}
|
}
|
||||||
if (options.saveTheConnection || isEdit) {
|
if (options.saveTheConnection || isEdit) {
|
||||||
let matcher: interfaces.ProfileMatcher;
|
|
||||||
if (isEdit) {
|
|
||||||
matcher = (a: interfaces.IConnectionProfile, b: interfaces.IConnectionProfile) => a.id === options.params.oldProfileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.saveToSettings(uri, connection, matcher).then(value => {
|
await this.saveToSettings(uri, connection, matcher).then(value => {
|
||||||
this._onAddConnectionProfile.fire(connection);
|
this._onAddConnectionProfile.fire(connection);
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
@@ -700,6 +711,20 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getEditorConnectionProfileTitle(profile: interfaces.IConnectionProfile, getNonDefaultsOnly?: boolean): string {
|
||||||
|
let result = '';
|
||||||
|
if (profile) {
|
||||||
|
let tempProfile = new ConnectionProfile(this._capabilitiesService, profile);
|
||||||
|
if (!getNonDefaultsOnly) {
|
||||||
|
result = tempProfile.getEditorFullTitleWithOptions();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
result = tempProfile.getNonDefaultOptionsString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private doActionsAfterConnectionComplete(uri: string, options: IConnectionCompletionOptions): void {
|
private doActionsAfterConnectionComplete(uri: string, options: IConnectionCompletionOptions): void {
|
||||||
let connectionManagementInfo = this._connectionStatusManager.findConnection(uri);
|
let connectionManagementInfo = this._connectionStatusManager.findConnection(uri);
|
||||||
if (!connectionManagementInfo) {
|
if (!connectionManagementInfo) {
|
||||||
@@ -1265,7 +1290,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
|||||||
this._connectionGlobalStatus.setStatusToConnected(info.connectionSummary);
|
this._connectionGlobalStatus.setStatusToConnected(info.connectionSummary);
|
||||||
}
|
}
|
||||||
|
|
||||||
const connectionUniqueId = connection.connectionProfile.getConnectionInfoId();
|
const connectionUniqueId = connection.connectionProfile.getOptionsKey();
|
||||||
if (info.isSupportedVersion === false
|
if (info.isSupportedVersion === false
|
||||||
&& this._connectionsGotUnsupportedVersionWarning.indexOf(connectionUniqueId) === -1
|
&& this._connectionsGotUnsupportedVersionWarning.indexOf(connectionUniqueId) === -1
|
||||||
&& this._configurationService.getValue<boolean>('connection.showUnsupportedServerVersionWarning')) {
|
&& this._configurationService.getValue<boolean>('connection.showUnsupportedServerVersionWarning')) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import * as Constants from 'sql/platform/connection/common/constants';
|
|||||||
import * as Utils from 'sql/platform/connection/common/utils';
|
import * as Utils from 'sql/platform/connection/common/utils';
|
||||||
import { IHandleFirewallRuleResult } from 'sql/workbench/services/resourceProvider/common/resourceProviderService';
|
import { IHandleFirewallRuleResult } from 'sql/workbench/services/resourceProvider/common/resourceProviderService';
|
||||||
|
|
||||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
import { IConnectionProfile, ServiceOptionType } from 'sql/platform/connection/common/interfaces';
|
||||||
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
|
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
|
||||||
import { TestConnectionProvider } from 'sql/platform/connection/test/common/testConnectionProvider';
|
import { TestConnectionProvider } from 'sql/platform/connection/test/common/testConnectionProvider';
|
||||||
import { TestResourceProvider } from 'sql/workbench/services/resourceProvider/test/common/testResourceProviderService';
|
import { TestResourceProvider } from 'sql/workbench/services/resourceProvider/test/common/testResourceProviderService';
|
||||||
@@ -514,7 +514,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||||||
assert.ok(called, 'expected changeGroupIdForConnectionGroup to be called on ConnectionStore');
|
assert.ok(called, 'expected changeGroupIdForConnectionGroup to be called on ConnectionStore');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('findExistingConnection should find connection for connectionProfile with same info', async () => {
|
test('findExistingConnection should find connection for connectionProfile with same basic info', async () => {
|
||||||
let profile = <ConnectionProfile>Object.assign({}, connectionProfile);
|
let profile = <ConnectionProfile>Object.assign({}, connectionProfile);
|
||||||
let uri1 = 'connection:connectionId';
|
let uri1 = 'connection:connectionId';
|
||||||
let options: IConnectionCompletionOptions = {
|
let options: IConnectionCompletionOptions = {
|
||||||
@@ -1013,7 +1013,15 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||||||
showFirewallRuleOnError: true
|
showFirewallRuleOnError: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let originalProfileKey = '';
|
||||||
|
connectionStore.setup(x => x.isDuplicateEdit(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((inputProfile, matcher) => {
|
||||||
|
let newProfile = ConnectionProfile.fromIConnectionProfile(new TestCapabilitiesService(), inputProfile);
|
||||||
|
let result = newProfile.getOptionsKey() === originalProfileKey;
|
||||||
|
return Promise.resolve(result);
|
||||||
|
});
|
||||||
await connect(uri1, options, true, profile);
|
await connect(uri1, options, true, profile);
|
||||||
|
let originalProfile = ConnectionProfile.fromIConnectionProfile(new TestCapabilitiesService(), connectionProfile);
|
||||||
|
originalProfileKey = originalProfile.getOptionsKey();
|
||||||
let newProfile = Object.assign({}, connectionProfile);
|
let newProfile = Object.assign({}, connectionProfile);
|
||||||
newProfile.connectionName = newname;
|
newProfile.connectionName = newname;
|
||||||
options.params.isEditConnection = true;
|
options.params.isEditConnection = true;
|
||||||
@@ -1047,6 +1055,8 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||||||
showFirewallRuleOnError: true
|
showFirewallRuleOnError: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// In an actual edit situation, the profile options would be different for different URIs, as a placeholder, we check the test uris instead here.
|
||||||
|
connectionStore.setup(x => x.isDuplicateEdit(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(uri1 === uri2));
|
||||||
await connect(uri1, options, true, profile);
|
await connect(uri1, options, true, profile);
|
||||||
options.params.isEditConnection = true;
|
options.params.isEditConnection = true;
|
||||||
await connect(uri2, options, true, profile);
|
await connect(uri2, options, true, profile);
|
||||||
@@ -1055,6 +1065,51 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||||||
assert.strictEqual(uri1info.connectionProfile.id, uri2info.connectionProfile.id);
|
assert.strictEqual(uri1info.connectionProfile.id, uri2info.connectionProfile.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Edit Connection - Connecting with an already connected profile via edit should throw an error', async () => {
|
||||||
|
let uri1 = 'test_uri1';
|
||||||
|
let profile = Object.assign({}, connectionProfile);
|
||||||
|
profile.id = '0451';
|
||||||
|
let options: IConnectionCompletionOptions = {
|
||||||
|
params: {
|
||||||
|
connectionType: ConnectionType.editor,
|
||||||
|
input: {
|
||||||
|
onConnectSuccess: undefined,
|
||||||
|
onConnectReject: undefined,
|
||||||
|
onConnectStart: undefined,
|
||||||
|
onDisconnect: undefined,
|
||||||
|
onConnectCanceled: undefined,
|
||||||
|
uri: uri1
|
||||||
|
},
|
||||||
|
queryRange: undefined,
|
||||||
|
runQueryOnCompletion: RunQueryOnConnectionMode.none,
|
||||||
|
isEditConnection: true
|
||||||
|
},
|
||||||
|
saveTheConnection: true,
|
||||||
|
showDashboard: false,
|
||||||
|
showConnectionDialogOnError: true,
|
||||||
|
showFirewallRuleOnError: true
|
||||||
|
};
|
||||||
|
|
||||||
|
let originalProfileKey = '';
|
||||||
|
connectionStore.setup(x => x.isDuplicateEdit(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((inputProfile, matcher) => {
|
||||||
|
let newProfile = ConnectionProfile.fromIConnectionProfile(new TestCapabilitiesService(), inputProfile);
|
||||||
|
let result = newProfile.getOptionsKey() === originalProfileKey;
|
||||||
|
return Promise.resolve(result)
|
||||||
|
});
|
||||||
|
|
||||||
|
await connect(uri1, options, true, profile);
|
||||||
|
let originalProfile = ConnectionProfile.fromIConnectionProfile(new TestCapabilitiesService(), connectionProfile);
|
||||||
|
originalProfileKey = originalProfile.getOptionsKey();
|
||||||
|
let newProfile = Object.assign({}, connectionProfile);
|
||||||
|
options.params.isEditConnection = true;
|
||||||
|
try {
|
||||||
|
await connect(uri1, options, true, newProfile);
|
||||||
|
assert.fail;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
test('failed firewall rule should open the firewall rule dialog', async () => {
|
test('failed firewall rule should open the firewall rule dialog', async () => {
|
||||||
handleFirewallRuleResult.canHandleFirewallRule = true;
|
handleFirewallRuleResult.canHandleFirewallRule = true;
|
||||||
@@ -1992,6 +2047,117 @@ test('clearRecentConnection and ConnectionsList should call connectionStore func
|
|||||||
assert(called);
|
assert(called);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('getEditorConnectionProfileTitle should return a correctly formatted title for a connection profile', () => {
|
||||||
|
let profile: IConnectionProfile = {
|
||||||
|
connectionName: 'new name',
|
||||||
|
serverName: 'new server',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: Constants.AuthenticationType.Integrated,
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g2/g2-2',
|
||||||
|
groupId: 'group id',
|
||||||
|
getOptionsKey: () => { return ''; },
|
||||||
|
matches: undefined,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
let capabilitiesService = new TestCapabilitiesService();
|
||||||
|
const testOption1 = {
|
||||||
|
name: 'testOption1',
|
||||||
|
displayName: 'testOption1',
|
||||||
|
description: 'test description',
|
||||||
|
groupName: 'test group name',
|
||||||
|
valueType: ServiceOptionType.string,
|
||||||
|
specialValueType: undefined,
|
||||||
|
defaultValue: '',
|
||||||
|
categoryValues: undefined,
|
||||||
|
isIdentity: false,
|
||||||
|
isRequired: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const testOption2 = {
|
||||||
|
name: 'testOption2',
|
||||||
|
displayName: 'testOption2',
|
||||||
|
description: 'test description',
|
||||||
|
groupName: 'test group name',
|
||||||
|
valueType: ServiceOptionType.number,
|
||||||
|
specialValueType: undefined,
|
||||||
|
defaultValue: '10',
|
||||||
|
categoryValues: undefined,
|
||||||
|
isIdentity: false,
|
||||||
|
isRequired: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const testOption3 = {
|
||||||
|
name: 'testOption3',
|
||||||
|
displayName: 'testOption3',
|
||||||
|
description: 'test description',
|
||||||
|
groupName: 'test group name',
|
||||||
|
valueType: ServiceOptionType.string,
|
||||||
|
specialValueType: undefined,
|
||||||
|
defaultValue: 'default',
|
||||||
|
categoryValues: undefined,
|
||||||
|
isIdentity: false,
|
||||||
|
isRequired: false
|
||||||
|
};
|
||||||
|
|
||||||
|
profile.options['testOption1'] = 'test value';
|
||||||
|
profile.options['testOption2'] = '50';
|
||||||
|
profile.options['testOption3'] = 'default';
|
||||||
|
|
||||||
|
let mainProvider = capabilitiesService.capabilities['MSSQL'];
|
||||||
|
let mainProperties = mainProvider.connection;
|
||||||
|
let mainOptions = mainProperties.connectionOptions;
|
||||||
|
|
||||||
|
mainOptions.push(testOption1);
|
||||||
|
mainOptions.push(testOption2);
|
||||||
|
mainOptions.push(testOption3);
|
||||||
|
|
||||||
|
mainProperties.connectionOptions = mainOptions;
|
||||||
|
mainProvider.connection = mainProperties;
|
||||||
|
|
||||||
|
capabilitiesService.capabilities['MSSQL'] = mainProvider;
|
||||||
|
|
||||||
|
const connectionStoreMock = TypeMoq.Mock.ofType(ConnectionStore, TypeMoq.MockBehavior.Loose, new TestStorageService());
|
||||||
|
const testInstantiationService = new TestInstantiationService();
|
||||||
|
testInstantiationService.stub(IStorageService, new TestStorageService());
|
||||||
|
sinon.stub(testInstantiationService, 'createInstance').withArgs(ConnectionStore).returns(connectionStoreMock.object);
|
||||||
|
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, capabilitiesService, undefined, undefined, undefined, new TestErrorDiagnosticsService(), undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
|
||||||
|
|
||||||
|
// We should expect that non default options are returned when we try to get the nonDefaultOptions string.
|
||||||
|
let result = connectionManagementService.getEditorConnectionProfileTitle(profile, true);
|
||||||
|
let expectedNonDefaultOption = ' (testOption1=test value; testOption2=50)';
|
||||||
|
assert.strictEqual(result, expectedNonDefaultOption, `Profile non default options contained incorrect options`);
|
||||||
|
|
||||||
|
// We should expect that the string contains the connection name and the server info (with all non default options appended).
|
||||||
|
let generatedProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, profile);
|
||||||
|
let profileServerInfo = generatedProfile.serverInfo;
|
||||||
|
result = connectionManagementService.getEditorConnectionProfileTitle(profile);
|
||||||
|
|
||||||
|
assert.strictEqual(result, `${profile.connectionName}: ${profileServerInfo}`, `getEditorConnectionProfileTitle does not return the correct string for ${profile.connectionName}`);
|
||||||
|
|
||||||
|
// We should expect that the string contains only the server info (with non default options) if there is no connection name.
|
||||||
|
profile.connectionName = undefined;
|
||||||
|
|
||||||
|
result = connectionManagementService.getEditorConnectionProfileTitle(profile);
|
||||||
|
|
||||||
|
assert.strictEqual(result, `${profileServerInfo}`, `getEditorConnectionProfileTitle included a connection name when it shouldn't`);
|
||||||
|
|
||||||
|
// We should expect that the string only contains the server info without any non default options if no such options exist.
|
||||||
|
profile.options['testOption1'] = undefined;
|
||||||
|
profile.options['testOption2'] = undefined;
|
||||||
|
profile.options['testOption3'] = undefined;
|
||||||
|
|
||||||
|
result = connectionManagementService.getEditorConnectionProfileTitle(profile);
|
||||||
|
|
||||||
|
assert.notEqual(result, `${profileServerInfo}`, `getEditorConnectionProfileTitle included non default connection options when it shouldn't`);
|
||||||
|
});
|
||||||
|
|
||||||
export function createConnectionProfile(id: string, password?: string): ConnectionProfile {
|
export function createConnectionProfile(id: string, password?: string): ConnectionProfile {
|
||||||
const capabilitiesService = new TestCapabilitiesService();
|
const capabilitiesService = new TestCapabilitiesService();
|
||||||
return new ConnectionProfile(capabilitiesService, {
|
return new ConnectionProfile(capabilitiesService, {
|
||||||
|
|||||||
@@ -77,6 +77,8 @@ export class TreeUpdateUtils {
|
|||||||
}
|
}
|
||||||
const previousTreeInput = tree.getInput();
|
const previousTreeInput = tree.getInput();
|
||||||
if (treeInput) {
|
if (treeInput) {
|
||||||
|
let treeArray = TreeUpdateUtils.alterTreeChildrenTitles([treeInput]);
|
||||||
|
treeInput = treeArray[0];
|
||||||
await tree.setInput(treeInput);
|
await tree.setInput(treeInput);
|
||||||
}
|
}
|
||||||
if (previousTreeInput instanceof Disposable) {
|
if (previousTreeInput instanceof Disposable) {
|
||||||
@@ -94,13 +96,29 @@ export class TreeUpdateUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls alterConnectionTitles on all levels of the Object Explorer Tree
|
||||||
|
* so that profiles in connection groups can have distinguishing titles too.
|
||||||
|
*/
|
||||||
|
public static alterTreeChildrenTitles(inputGroups: ConnectionProfileGroup[]): ConnectionProfileGroup[] {
|
||||||
|
inputGroups.forEach(group => {
|
||||||
|
group.children = this.alterTreeChildrenTitles(group.children);
|
||||||
|
let connections = group.connections;
|
||||||
|
TreeUpdateUtils.alterConnectionTitles(connections);
|
||||||
|
group.connections = connections;
|
||||||
|
});
|
||||||
|
return inputGroups;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set input for the registered servers tree.
|
* Set input for the registered servers tree.
|
||||||
*/
|
*/
|
||||||
public static async registeredServerUpdate(tree: ITree | AsyncServerTree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): Promise<void> {
|
public static async registeredServerUpdate(tree: ITree | AsyncServerTree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): Promise<void> {
|
||||||
if (tree instanceof AsyncServerTree) {
|
if (tree instanceof AsyncServerTree) {
|
||||||
const treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||||
if (treeInput) {
|
if (treeInput) {
|
||||||
|
let treeArray = TreeUpdateUtils.alterTreeChildrenTitles([treeInput]);
|
||||||
|
treeInput = treeArray[0];
|
||||||
await tree.setInput(treeInput);
|
await tree.setInput(treeInput);
|
||||||
}
|
}
|
||||||
tree.rerender();
|
tree.rerender();
|
||||||
@@ -129,6 +147,8 @@ export class TreeUpdateUtils {
|
|||||||
|
|
||||||
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||||
if (treeInput) {
|
if (treeInput) {
|
||||||
|
let treeArray = TreeUpdateUtils.alterTreeChildrenTitles([treeInput]);
|
||||||
|
treeInput = treeArray[0];
|
||||||
const originalInput = tree.getInput();
|
const originalInput = tree.getInput();
|
||||||
if (treeInput !== originalInput) {
|
if (treeInput !== originalInput) {
|
||||||
return tree.setInput(treeInput).then(async () => {
|
return tree.setInput(treeInput).then(async () => {
|
||||||
@@ -375,4 +395,85 @@ export class TreeUpdateUtils {
|
|||||||
}
|
}
|
||||||
return connectionProfile;
|
return connectionProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the connection title to display only the unique properties among profiles.
|
||||||
|
*/
|
||||||
|
private static alterConnectionTitles(inputList: ConnectionProfile[]): void {
|
||||||
|
let profileListMap = new Map<string, number[]>();
|
||||||
|
|
||||||
|
// Map the indices of profiles that share the same connection name.
|
||||||
|
for (let i = 0; i < inputList.length; i++) {
|
||||||
|
// do not add if the profile is still loading as that will result in erroneous entries.
|
||||||
|
if (inputList[i].hasServerCapabilities && inputList[i].hasLoaded()) {
|
||||||
|
let titleKey = inputList[i].getOriginalTitle();
|
||||||
|
if (profileListMap.has(titleKey)) {
|
||||||
|
let profilesForKey = profileListMap.get(titleKey);
|
||||||
|
profilesForKey.push(i);
|
||||||
|
profileListMap.set(titleKey, profilesForKey);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
profileListMap.set(titleKey, [i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
profileListMap.forEach(function (indexes, titleString) {
|
||||||
|
if (profileListMap.get(titleString)?.length > 1) {
|
||||||
|
let combinedOptions = [];
|
||||||
|
let needSpecial = false;
|
||||||
|
if (titleString === inputList[indexes[0]].connectionName) {
|
||||||
|
// check for potential connections with the same name but technically different connections.
|
||||||
|
let listOfDuplicates = indexes.filter(item => inputList[item].getOptionsKey() !== inputList[indexes[0]].getOptionsKey());
|
||||||
|
if (listOfDuplicates.length > 0) {
|
||||||
|
// if we do find duplicates, we will need to include the special properties.
|
||||||
|
needSpecial = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
indexes.forEach((indexValue) => {
|
||||||
|
// Add all possible options across all profiles with the same title to an option list.
|
||||||
|
let valueOptions = inputList[indexValue].getConnectionOptionsList(needSpecial, false);
|
||||||
|
combinedOptions = combinedOptions.concat(valueOptions.filter(item => combinedOptions.indexOf(item) < 0));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate list of non default option keys for each profile that shares the same basic connection name.
|
||||||
|
let optionKeyMap = new Map<ConnectionProfile, string[]>();
|
||||||
|
let optionValueOccuranceMap = new Map<string, number>();
|
||||||
|
for (let p = 0; p < indexes.length; p++) {
|
||||||
|
optionKeyMap.set(inputList[indexes[p]], []);
|
||||||
|
for (let i = 0; i < combinedOptions.length; i++) {
|
||||||
|
// See if the option is not default for the inputList profile or is.
|
||||||
|
if (inputList[indexes[p]].getConnectionOptionsList(needSpecial, true).indexOf(combinedOptions[i]) > -1) {
|
||||||
|
let optionValue = inputList[indexes[p]].getOptionValue(combinedOptions[i].name);
|
||||||
|
let currentArray = optionKeyMap.get(inputList[indexes[p]]);
|
||||||
|
let valueString = combinedOptions[i].name + ConnectionProfile.displayNameValueSeparator + optionValue;
|
||||||
|
if (!optionValueOccuranceMap.get(valueString)) {
|
||||||
|
optionValueOccuranceMap.set(valueString, 0);
|
||||||
|
}
|
||||||
|
optionValueOccuranceMap.set(valueString, optionValueOccuranceMap.get(valueString) + 1);
|
||||||
|
currentArray.push(valueString);
|
||||||
|
optionKeyMap.set(inputList[indexes[p]], currentArray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter out options that are found in ALL the entries with the same basic name.
|
||||||
|
optionValueOccuranceMap.forEach(function (count, optionValue) {
|
||||||
|
optionKeyMap.forEach(function (connOptionValues, profile) {
|
||||||
|
if (count === optionKeyMap.size) {
|
||||||
|
optionKeyMap.set(profile, connOptionValues.filter(value => value !== optionValue));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate the final unique connection string for each profile in the list.
|
||||||
|
optionKeyMap.forEach(function (connOptionValues, profile) {
|
||||||
|
let uniqueOptionString = connOptionValues.join(ConnectionProfile.displayIdSeparator);
|
||||||
|
if (uniqueOptionString.length > 0) {
|
||||||
|
profile.title += ' (' + uniqueOptionString + ')';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,606 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||||
|
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||||
|
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||||
|
import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
|
||||||
|
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
|
||||||
|
import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
|
||||||
|
import * as assert from 'assert';
|
||||||
|
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
|
||||||
|
suite('treeUpdateUtils alterConnection', () => {
|
||||||
|
|
||||||
|
let capabilitiesService: TestCapabilitiesService;
|
||||||
|
|
||||||
|
const testOption1 = {
|
||||||
|
name: 'testOption1',
|
||||||
|
displayName: 'testOption1',
|
||||||
|
description: 'test description',
|
||||||
|
groupName: 'test group name',
|
||||||
|
valueType: 'string',
|
||||||
|
specialValueType: undefined,
|
||||||
|
defaultValue: '',
|
||||||
|
categoryValues: undefined,
|
||||||
|
isIdentity: false,
|
||||||
|
isRequired: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const testOption2 = {
|
||||||
|
name: 'testOption2',
|
||||||
|
displayName: 'testOption2',
|
||||||
|
description: 'test description',
|
||||||
|
groupName: 'test group name',
|
||||||
|
valueType: 'number',
|
||||||
|
specialValueType: undefined,
|
||||||
|
defaultValue: '10',
|
||||||
|
categoryValues: undefined,
|
||||||
|
isIdentity: false,
|
||||||
|
isRequired: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const testOption3 = {
|
||||||
|
name: 'testOption3',
|
||||||
|
displayName: 'testOption3',
|
||||||
|
description: 'test description',
|
||||||
|
groupName: 'test group name',
|
||||||
|
valueType: 'string',
|
||||||
|
specialValueType: undefined,
|
||||||
|
defaultValue: 'default',
|
||||||
|
categoryValues: undefined,
|
||||||
|
isIdentity: false,
|
||||||
|
isRequired: false
|
||||||
|
};
|
||||||
|
|
||||||
|
setup(() => {
|
||||||
|
capabilitiesService = new TestCapabilitiesService();
|
||||||
|
let mainProvider = capabilitiesService.capabilities[mssqlProviderName];
|
||||||
|
let mainProperties = mainProvider.connection;
|
||||||
|
let mainOptions = mainProperties.connectionOptions;
|
||||||
|
|
||||||
|
mainOptions.push((testOption1 as azdata.ConnectionOption));
|
||||||
|
mainOptions.push((testOption2 as azdata.ConnectionOption));
|
||||||
|
mainOptions.push((testOption3 as azdata.ConnectionOption));
|
||||||
|
|
||||||
|
mainProperties.connectionOptions = mainOptions;
|
||||||
|
mainProvider.connection = mainProperties;
|
||||||
|
|
||||||
|
capabilitiesService.capabilities['MSSQL'] = mainProvider;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Default properties should not be added to the altered title', async () => {
|
||||||
|
let profile1: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption3: 'default', testOption2: '10' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile2: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption3: 'nonDefault' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let connectionProfile1 = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile2 = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
|
||||||
|
|
||||||
|
let connectionProfileGroup = new ConnectionProfileGroup('g3', undefined, 'g3', undefined, undefined);
|
||||||
|
connectionProfileGroup.addConnections([connectionProfile1, connectionProfile2]);
|
||||||
|
|
||||||
|
let updatedProfileGroup = TreeUpdateUtils.alterTreeChildrenTitles([connectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedTitleMap = updatedProfileGroup[0].connections.map(profile => profile.title);
|
||||||
|
|
||||||
|
assert.equal(connectionProfile1.title, updatedTitleMap[0]);
|
||||||
|
assert.equal(connectionProfile1.title + ' (testOption3=nonDefault)', updatedTitleMap[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Similar connections should have different titles based on all differing properties', async () => {
|
||||||
|
let profile1: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption2: '15', testOption1: 'test string 1', testOption3: 'nonDefault' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile2: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption2: '50', testOption1: 'test string 1', testOption3: 'nonDefault' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile3: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption2: '15', testOption1: 'test string 2', testOption3: 'nonDefault' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile4: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption2: '50', testOption1: 'test string 2', testOption3: 'nonDefault' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let defaultProfile: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption3: 'nonDefault' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let defaultConnectionProfile = new ConnectionProfile(capabilitiesService, defaultProfile);
|
||||||
|
let connectionProfile1 = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile2 = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
let connectionProfile3 = new ConnectionProfile(capabilitiesService, profile3);
|
||||||
|
let connectionProfile4 = new ConnectionProfile(capabilitiesService, profile4);
|
||||||
|
|
||||||
|
|
||||||
|
let connectionProfileGroup = new ConnectionProfileGroup('g3', undefined, 'g3', undefined, undefined);
|
||||||
|
let originalTitle = defaultConnectionProfile.title;
|
||||||
|
connectionProfileGroup.addConnections([defaultConnectionProfile, connectionProfile1, connectionProfile2, connectionProfile3, connectionProfile4]);
|
||||||
|
|
||||||
|
let updatedProfileGroup = TreeUpdateUtils.alterTreeChildrenTitles([connectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedTitleMap = updatedProfileGroup[0].connections.map(profile => profile.title);
|
||||||
|
|
||||||
|
assert.equal(originalTitle, updatedTitleMap[0]);
|
||||||
|
assert.equal(originalTitle + ' (testOption1=test string 1; testOption2=15)', updatedTitleMap[1]);
|
||||||
|
assert.equal(originalTitle + ' (testOption1=test string 1; testOption2=50)', updatedTitleMap[2]);
|
||||||
|
assert.equal(originalTitle + ' (testOption1=test string 2; testOption2=15)', updatedTitleMap[3]);
|
||||||
|
assert.equal(originalTitle + ' (testOption1=test string 2; testOption2=50)', updatedTitleMap[4]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('identical connections should have same title if on different levels', async () => {
|
||||||
|
let profile1: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile2: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3-1',
|
||||||
|
groupId: 'g3-1',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile3: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3-2',
|
||||||
|
groupId: 'g3-2',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let connectionProfile1 = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile2 = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
let connectionProfile3 = new ConnectionProfile(capabilitiesService, profile3);
|
||||||
|
|
||||||
|
let connectionProfileGroup = new ConnectionProfileGroup('g3', undefined, 'g3', undefined, undefined);
|
||||||
|
let childConnectionProfileGroup = new ConnectionProfileGroup('g3-1', undefined, 'g3-1', undefined, undefined);
|
||||||
|
let grandChildConnectionProfileGroup = new ConnectionProfileGroup('g3-2', undefined, 'g3-2', undefined, undefined);
|
||||||
|
childConnectionProfileGroup.addConnections([connectionProfile2]);
|
||||||
|
connectionProfileGroup.addConnections([connectionProfile1]);
|
||||||
|
grandChildConnectionProfileGroup.addConnections([connectionProfile3]);
|
||||||
|
childConnectionProfileGroup.addGroups([grandChildConnectionProfileGroup]);
|
||||||
|
connectionProfileGroup.addGroups([childConnectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedProfileGroup = TreeUpdateUtils.alterTreeChildrenTitles([connectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedTitleMap = updatedProfileGroup[0].connections.map(profile => profile.title);
|
||||||
|
let updatedChildTitleMap = updatedProfileGroup[0].children[0].connections.map(profile => profile.title);
|
||||||
|
let updatedGrandChildTitleMap = updatedProfileGroup[0].children[0].children[0].connections.map(profile => profile.title);
|
||||||
|
|
||||||
|
// Titles should be the same if they're in different levels.
|
||||||
|
assert.equal(updatedTitleMap[0], updatedChildTitleMap[0]);
|
||||||
|
assert.equal(updatedTitleMap[0], updatedGrandChildTitleMap[0]);
|
||||||
|
assert.equal(updatedChildTitleMap[0], updatedGrandChildTitleMap[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('connections should not affect connections on a different level', async () => {
|
||||||
|
let profile1: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption1: 'value1' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile1a: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption1: 'value2' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile2: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3-1',
|
||||||
|
groupId: 'g3-1',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let connectionProfile1 = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile1a = new ConnectionProfile(capabilitiesService, profile1a);
|
||||||
|
let connectionProfile2 = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
|
||||||
|
let connectionProfileGroup = new ConnectionProfileGroup('g3', undefined, 'g3', undefined, undefined);
|
||||||
|
let childConnectionProfileGroup = new ConnectionProfileGroup('g3-1', undefined, 'g3-1', undefined, undefined);
|
||||||
|
|
||||||
|
childConnectionProfileGroup.addConnections([connectionProfile2]);
|
||||||
|
connectionProfileGroup.addConnections([connectionProfile1, connectionProfile1a]);
|
||||||
|
connectionProfileGroup.addGroups([childConnectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedProfileGroup = TreeUpdateUtils.alterTreeChildrenTitles([connectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedTitleMap = updatedProfileGroup[0].connections.map(profile => profile.title);
|
||||||
|
let updatedChildTitleMap = updatedProfileGroup[0].children[0].connections.map(profile => profile.title);
|
||||||
|
|
||||||
|
// Titles should be altered for the first group only.
|
||||||
|
assert.equal(updatedChildTitleMap[0] + ' (testOption1=value1)', updatedTitleMap[0]);
|
||||||
|
assert.equal(updatedChildTitleMap[0] + ' (testOption1=value2)', updatedTitleMap[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('non default options should only be appended to the connection with non default options', async () => {
|
||||||
|
let profile1: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile2: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption1: 'value1', testOption2: '15' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let connectionProfile1 = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile2 = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
|
||||||
|
let connectionProfileGroup = new ConnectionProfileGroup('g3', undefined, 'g3', undefined, undefined);
|
||||||
|
|
||||||
|
connectionProfileGroup.addConnections([connectionProfile1, connectionProfile2]);
|
||||||
|
|
||||||
|
let updatedProfileGroup = TreeUpdateUtils.alterTreeChildrenTitles([connectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedTitleMap = updatedProfileGroup[0].connections.map(profile => profile.title);
|
||||||
|
|
||||||
|
//Title for second profile should be the same as the first but with non default options appended.
|
||||||
|
assert.equal(updatedTitleMap[0] + ' (testOption1=value1; testOption2=15)', updatedTitleMap[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('identical profiles added into one group and separate groups should have the same options appended', async () => {
|
||||||
|
let profile1: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption1: 'value1', testOption2: '15' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile2: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3',
|
||||||
|
groupId: 'g3',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption1: 'value2', testOption2: '30' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let connectionProfile1Base = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile2Base = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
|
||||||
|
let connectionProfileGroup = new ConnectionProfileGroup('g3', undefined, 'g3', undefined, undefined);
|
||||||
|
|
||||||
|
connectionProfileGroup.addConnections([connectionProfile1Base, connectionProfile2Base]);
|
||||||
|
|
||||||
|
profile1.groupFullName = 'g3-1';
|
||||||
|
profile1.groupId = 'g3-1';
|
||||||
|
profile2.groupFullName = 'g3-1';
|
||||||
|
profile2.groupId = 'g3-1';
|
||||||
|
|
||||||
|
let connectionProfile1Child = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile2Child = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
|
||||||
|
let childConnectionProfileGroup = new ConnectionProfileGroup('g3-1', undefined, 'g3-1', undefined, undefined);
|
||||||
|
|
||||||
|
childConnectionProfileGroup.addConnections([connectionProfile1Child, connectionProfile2Child]);
|
||||||
|
|
||||||
|
profile1.groupFullName = 'g3-2';
|
||||||
|
profile1.groupId = 'g3-2';
|
||||||
|
profile2.groupFullName = 'g3-2';
|
||||||
|
profile2.groupId = 'g3-2';
|
||||||
|
|
||||||
|
let connectionProfile1Grandchild = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile2Grandchild = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
|
||||||
|
let grandchildConnectionProfileGroup = new ConnectionProfileGroup('g3-2', undefined, 'g3-2', undefined, undefined);
|
||||||
|
|
||||||
|
grandchildConnectionProfileGroup.addConnections([connectionProfile1Grandchild, connectionProfile2Grandchild]);
|
||||||
|
|
||||||
|
childConnectionProfileGroup.addGroups([grandchildConnectionProfileGroup]);
|
||||||
|
|
||||||
|
connectionProfileGroup.addGroups([childConnectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedProfileGroup = TreeUpdateUtils.alterTreeChildrenTitles([connectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedTitleMap = updatedProfileGroup[0].connections.map(profile => profile.title);
|
||||||
|
let updatedChildTitleMap = updatedProfileGroup[0].children[0].connections.map(profile => profile.title);
|
||||||
|
let updatedGrandchildTitleMap = updatedProfileGroup[0].children[0].children[0].connections.map(profile => profile.title);
|
||||||
|
|
||||||
|
//Titles for the same profile in different groups should be identical
|
||||||
|
assert.equal(updatedTitleMap[0], updatedChildTitleMap[0]);
|
||||||
|
assert.equal(updatedTitleMap[0], updatedGrandchildTitleMap[0]);
|
||||||
|
assert.equal(updatedTitleMap[1], updatedChildTitleMap[1]);
|
||||||
|
assert.equal(updatedTitleMap[1], updatedGrandchildTitleMap[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('profiles in adjacent groups on the same layer should not affect titles on nearby groups', async () => {
|
||||||
|
let profile1: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3a',
|
||||||
|
groupId: 'g3a',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: {},
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let profile2: IConnectionProfile = {
|
||||||
|
serverName: 'server3',
|
||||||
|
databaseName: 'database',
|
||||||
|
userName: 'user',
|
||||||
|
password: 'password',
|
||||||
|
authenticationType: '',
|
||||||
|
savePassword: true,
|
||||||
|
groupFullName: 'g3a',
|
||||||
|
groupId: 'g3a',
|
||||||
|
getOptionsKey: undefined!,
|
||||||
|
matches: undefined!,
|
||||||
|
providerName: 'MSSQL',
|
||||||
|
options: { testOption1: 'value2', testOption2: '30' },
|
||||||
|
saveProfile: true,
|
||||||
|
id: undefined!,
|
||||||
|
connectionName: undefined!
|
||||||
|
};
|
||||||
|
|
||||||
|
let connectionProfile1a = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile2a = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
|
||||||
|
let connectionProfileGroup = new ConnectionProfileGroup('g3', undefined, 'g3', undefined, undefined);
|
||||||
|
|
||||||
|
let childConnectionProfileGroup1 = new ConnectionProfileGroup('g3a', undefined, 'g3a', undefined, undefined);
|
||||||
|
childConnectionProfileGroup1.addConnections([connectionProfile1a, connectionProfile2a]);
|
||||||
|
|
||||||
|
profile1.groupFullName = 'g3b';
|
||||||
|
profile1.groupId = 'g3b';
|
||||||
|
profile2.groupFullName = 'g3b';
|
||||||
|
profile2.groupId = 'g3b';
|
||||||
|
|
||||||
|
let connectionProfile1b = new ConnectionProfile(capabilitiesService, profile1);
|
||||||
|
let connectionProfile2b = new ConnectionProfile(capabilitiesService, profile2);
|
||||||
|
|
||||||
|
let childConnectionProfileGroup2 = new ConnectionProfileGroup('g3b', undefined, 'g3b', undefined, undefined);
|
||||||
|
|
||||||
|
childConnectionProfileGroup2.addConnections([connectionProfile1b, connectionProfile2b]);
|
||||||
|
|
||||||
|
connectionProfileGroup.addGroups([childConnectionProfileGroup1]);
|
||||||
|
|
||||||
|
connectionProfileGroup.addGroups([childConnectionProfileGroup2]);
|
||||||
|
|
||||||
|
let updatedProfileGroup = TreeUpdateUtils.alterTreeChildrenTitles([connectionProfileGroup]);
|
||||||
|
|
||||||
|
let updatedChildATitleMap = updatedProfileGroup[0].children[0].connections.map(profile => profile.title);
|
||||||
|
let updatedChildBTitleMap = updatedProfileGroup[0].children[1].connections.map(profile => profile.title);
|
||||||
|
|
||||||
|
//Check that titles are generated properly for the first group.
|
||||||
|
assert.equal(updatedChildATitleMap[0] + ' (testOption1=value2; testOption2=30)', updatedChildATitleMap[1]);
|
||||||
|
|
||||||
|
//Titles for the same profile in adjacent groups should be identical
|
||||||
|
assert.equal(updatedChildATitleMap[0], updatedChildBTitleMap[0]);
|
||||||
|
assert.equal(updatedChildATitleMap[1], updatedChildBTitleMap[1]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user