Changed connection title generation to use getConnections (including recent and saved). (#22954)
* remove advanced options for table designer and notebook Actions title (unnecessary in usage context) * Revert "remove advanced options for table designer and notebook Actions title (unnecessary in usage context)" This reverts commite30aee5151
. * added changes based on feedback * added null check and updated tests * WIP change to connection title generation * WIP connection management service update * fix to connectionManagementService test * fix editorConnectionTitles * renamed nondefault to distinguishing options * use stored connections for options * removed erroneous connection name set to null * added title by ID search for title generation * Add recent connection title * added fix for stub bug * added child title options appended * WIP rework of getEditorTitle * more work done * WIP changes for 5-2-2023 * moved server info to generate titles. * added reworked title generation * added working active connection title generation and cleanup * added comments to argument * remove unnecessary spaces * added id fix assign * added fromEditor save * Revert "Revert new connection string format (#22997)" This reverts commit898bb73a34
. * added small fix to tests and exclude empty properties * made small fixes for tests * update expected ID * added support for old password key and removed empty options * added in authenticationType core property * fix for whitespace indentation * added connection save profile to thing * made some small fixes to connection options * added small change to connectionDialogService * added nullcheck for saveProfile * added negation for connection saveProfile * remove duplicate editor title generation * added edit profile handling for titles * Cleanup serverCapabilities property * fixed dependency issues * removed connectionproviderproperties * added fix for treeupdateutils * made update to title generation * added recent connections change * Revert "Cleanup serverCapabilities property" This reverts commit2c7b94f98c
. * added dashboard text and fix for connection store test * added group name to end also temporary added dashboard changes based on feedback * added in new SQL version * added fix to edit connections * added clarifying information to title generation --------- Co-authored-by: Cheena Malhotra <cmalhotra@microsoft.com>
This commit is contained in:
Родитель
996d45bfa7
Коммит
ef8164e5c2
|
@ -814,6 +814,7 @@
|
|||
"connectionProvider": {
|
||||
"providerId": "MSSQL",
|
||||
"displayName": "%mssql.provider.displayName%",
|
||||
"useFullOptions": true,
|
||||
"isExecutionPlanProvider": true,
|
||||
"supportCopyResultsToClipboard": true,
|
||||
"azureResource": "Sql",
|
||||
|
@ -952,7 +953,7 @@
|
|||
"description": "%mssql.connectionOptions.applicationName.description%",
|
||||
"groupName": "%mssql.connectionOptions.groupName.initialization%",
|
||||
"valueType": "string",
|
||||
"defaultValue": null,
|
||||
"defaultValue": "azdata",
|
||||
"objectType": null,
|
||||
"categoryValues": null,
|
||||
"isRequired": false,
|
||||
|
|
|
@ -26,11 +26,18 @@ export function registerTableDesignerCommands(appContext: AppContext) {
|
|||
if (!connectionString) {
|
||||
throw new Error(FailedToGetConnectionStringError);
|
||||
}
|
||||
let titleString = `${context.connectionProfile!.serverName} - ${context.connectionProfile!.databaseName} - ${NewTableText}`;
|
||||
// append distinguishing options to end to let users know exact connection.
|
||||
let distinguishingOptions = await azdata.connection.getEditorConnectionProfileTitle(context.connectionProfile, true);
|
||||
if (distinguishingOptions !== '') {
|
||||
distinguishingOptions = distinguishingOptions.replace('(', '[').replace(')', ']');
|
||||
titleString += `${distinguishingOptions}`;
|
||||
}
|
||||
const tableIcon = context.nodeInfo!.nodeSubType as azdata.designers.TableIcon;
|
||||
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
|
||||
await azdata.designers.openTableDesigner(sqlProviderName, {
|
||||
title: NewTableText,
|
||||
tooltip: `${context.connectionProfile!.serverName} - ${context.connectionProfile!.databaseName} - ${NewTableText}`,
|
||||
tooltip: titleString,
|
||||
server: context.connectionProfile!.serverName,
|
||||
database: context.connectionProfile!.databaseName,
|
||||
isNewTable: true,
|
||||
|
@ -56,11 +63,18 @@ export function registerTableDesignerCommands(appContext: AppContext) {
|
|||
if (!connectionString) {
|
||||
throw new Error(FailedToGetConnectionStringError);
|
||||
}
|
||||
let titleString = `${server} - ${database} - ${schema}.${name}`;
|
||||
// append distinguishing options to end to let users know exact connection.
|
||||
let distinguishingOptions = await azdata.connection.getEditorConnectionProfileTitle(context.connectionProfile, true);
|
||||
if (distinguishingOptions !== '') {
|
||||
distinguishingOptions = distinguishingOptions.replace('(', '[').replace(')', ']');
|
||||
titleString += `${distinguishingOptions}`;
|
||||
}
|
||||
const tableIcon = context.nodeInfo!.nodeSubType as azdata.designers.TableIcon;
|
||||
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
|
||||
await azdata.designers.openTableDesigner(sqlProviderName, {
|
||||
title: `${schema}.${name}`,
|
||||
tooltip: `${server} - ${database} - ${schema}.${name}`,
|
||||
tooltip: titleString,
|
||||
server: server,
|
||||
database: database,
|
||||
isNewTable: false,
|
||||
|
|
|
@ -508,6 +508,15 @@ declare module 'azdata' {
|
|||
* @returns The new password that is returned from the operation or undefined if unsuccessful.
|
||||
*/
|
||||
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.
|
||||
* @param getOptionsOnly Provide if you only want to get the differing advanced options (for some titles).
|
||||
* @param includeGroupName Provide if you want to include the groupName as well (in areas that do not display the groupName).
|
||||
* @returns The title formatted with server info in front, with non default options at the end.
|
||||
*/
|
||||
export function getEditorConnectionProfileTitle(profile: IConnectionProfile, getOptionsOnly?: boolean, includeGroupName?: boolean): Thenable<string>;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -67,6 +67,12 @@ export interface ConnectionProviderProperties {
|
|||
*/
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -115,6 +115,7 @@ export class TestCapabilitiesService implements ICapabilitiesService {
|
|||
providerId: mssqlProviderName,
|
||||
displayName: 'MSSQL',
|
||||
connectionOptions: connectionProvider.concat(mssqlAdvancedOptions),
|
||||
useFullOptions: true,
|
||||
};
|
||||
let pgSQLCapabilities = {
|
||||
providerId: this.pgsqlProviderName,
|
||||
|
|
|
@ -363,10 +363,18 @@ export class ConnectionConfig {
|
|||
p.options.database === profile.options.database &&
|
||||
p.options.server === profile.options.server &&
|
||||
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;
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
|
|
@ -381,6 +381,15 @@ export interface IConnectionManagementService {
|
|||
* @returns the new valid password that is entered, or undefined if cancelled or errored.
|
||||
*/
|
||||
openChangePasswordDialog(profile: IConnectionProfile): Promise<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.
|
||||
* @param getOptionsOnly Provide if you only want to get the differing advanced options (for some titles).
|
||||
* @param includeGroupName Provide if you want to include the groupName as well (in areas that do not display the groupName).
|
||||
* @returns The title formatted with server info in front, with non default options at the end.
|
||||
*/
|
||||
getEditorConnectionProfileTitle(profile: IConnectionProfile, getOptionsOnly?: boolean, includeGroupName?: boolean): string;
|
||||
}
|
||||
|
||||
export enum RunQueryOnConnectionMode {
|
||||
|
|
|
@ -39,6 +39,9 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
|
|||
|
||||
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(
|
||||
capabilitiesService: ICapabilitiesService,
|
||||
model: string | azdata.IConnectionProfile | azdata.connection.ConnectionProfile | undefined) {
|
||||
|
@ -199,6 +202,21 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
|
|||
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 override set title(value: string) {
|
||||
this._title = value;
|
||||
}
|
||||
|
||||
public override clone(): ConnectionProfile {
|
||||
let instance = new ConnectionProfile(this.capabilitiesService, this);
|
||||
return instance;
|
||||
|
@ -227,24 +245,39 @@ export class ConnectionProfile extends ProviderConnectionInfo implements interfa
|
|||
|
||||
/**
|
||||
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
|
||||
* and all the other properties (except empty ones) if useFullOptions is enabled for the provider.
|
||||
* This key uniquely identifies a connection in a group
|
||||
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
||||
* Example (original format): "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
||||
* Example (new format): "providerName:MSSQL|databaseName:database|serverName:server3|userName:user|groupId:testid"
|
||||
* @param getOriginalOptions will return the original URI format regardless if useFullOptions was set or not. (used for retrieving passwords)
|
||||
*/
|
||||
public override getOptionsKey(): string {
|
||||
let id = super.getOptionsKey();
|
||||
public override getOptionsKey(getOriginalOptions?: boolean): string {
|
||||
let id = super.getOptionsKey(getOriginalOptions);
|
||||
let databaseDisplayName: string = this.options['databaseDisplayName'];
|
||||
if (databaseDisplayName) {
|
||||
id += ProviderConnectionInfo.idSeparator + 'databaseDisplayName' + ProviderConnectionInfo.nameValueSeparator + databaseDisplayName;
|
||||
}
|
||||
|
||||
return id + ProviderConnectionInfo.idSeparator + 'group' + ProviderConnectionInfo.nameValueSeparator + this.groupId;
|
||||
let groupProp = 'group'
|
||||
if (!getOriginalOptions && this.serverCapabilities && this.serverCapabilities.useFullOptions) {
|
||||
groupProp = 'groupId'
|
||||
}
|
||||
|
||||
return id + ProviderConnectionInfo.idSeparator + groupProp + 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 {
|
||||
return super.getOptionsKey();
|
||||
public getConnectionInfoId(getOriginalOptions = true): string {
|
||||
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 {
|
||||
|
|
|
@ -67,6 +67,7 @@ export class ConnectionStore {
|
|||
}
|
||||
|
||||
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()));
|
||||
return cred.join(CRED_SEPARATOR);
|
||||
}
|
||||
|
@ -248,7 +249,8 @@ export class ConnectionStore {
|
|||
|
||||
// Remove the connection from the list if it already exists
|
||||
list = list.filter(value => {
|
||||
let equal = value && value.getConnectionInfoId() === savedProfile.getConnectionInfoId();
|
||||
let equal = value && value.connectionName === savedProfile.connectionName;
|
||||
equal = equal && value.getConnectionInfoId(false) === savedProfile.getConnectionInfoId(false);
|
||||
if (equal && savedProfile.saveProfile) {
|
||||
equal = value.groupId === savedProfile.groupId ||
|
||||
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
|
||||
|
@ -266,7 +268,8 @@ export class ConnectionStore {
|
|||
|
||||
// Remove the connection from the list if it already exists
|
||||
list = list.filter(value => {
|
||||
let equal = value && value.getConnectionInfoId() === savedProfile.getConnectionInfoId();
|
||||
let equal = value && value.connectionName === savedProfile.connectionName;
|
||||
equal = equal && value.getConnectionInfoId(false) === savedProfile.getConnectionInfoId(false);
|
||||
if (equal && savedProfile.saveProfile) {
|
||||
equal = value.groupId === savedProfile.groupId ||
|
||||
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
|
||||
|
@ -301,6 +304,8 @@ export class ConnectionStore {
|
|||
|
||||
private doSavePassword(conn: IConnectionProfile): Promise<boolean> {
|
||||
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);
|
||||
return this.credentialService.saveCredential(credentialId, conn.password);
|
||||
} else {
|
||||
|
@ -321,6 +326,12 @@ export class ConnectionStore {
|
|||
return this.convertToConnectionGroup(groups, profilesInConfiguration);
|
||||
}
|
||||
|
||||
public getAllConnectionsFromConfig(): ConnectionProfile[] {
|
||||
let profilesInConfiguration: ConnectionProfile[] | undefined;
|
||||
profilesInConfiguration = this.connectionConfig.getConnections(true);
|
||||
return profilesInConfiguration;
|
||||
}
|
||||
|
||||
private convertToConnectionGroup(groups: IConnectionProfileGroup[], connections?: ConnectionProfile[], parent?: ConnectionProfileGroup): ConnectionProfileGroup[] {
|
||||
const result: ConnectionProfileGroup[] = [];
|
||||
const children = groups.filter(g => g.parentId === (parent ? parent.id : undefined));
|
||||
|
|
|
@ -11,7 +11,7 @@ export type ProfileMatcher = (a: IConnectionProfile, b: IConnectionProfile) => b
|
|||
|
||||
export interface IConnectionProfile extends azdata.IConnectionProfile {
|
||||
serverCapabilities: ConnectionProviderProperties | undefined;
|
||||
getOptionsKey(): string;
|
||||
getOptionsKey(getOriginalOptions?: boolean): string;
|
||||
getOptionKeyIdNames(): string[];
|
||||
matches(profile: azdata.IConnectionProfile): boolean;
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||
this.options[name] = value;
|
||||
}
|
||||
|
||||
private getServerInfo() {
|
||||
public getServerInfo() {
|
||||
let title = '';
|
||||
if (this.serverCapabilities) {
|
||||
title = this.serverName;
|
||||
|
@ -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.
|
||||
else if (Object.keys(this.capabilitiesService.providers).length > 0) {
|
||||
else if (this.hasLoaded()) {
|
||||
return localize('connection.unsupported', "Unsupported connection");
|
||||
} else {
|
||||
return localize('loading', "Loading...");
|
||||
|
@ -169,8 +169,16 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||
return label;
|
||||
}
|
||||
|
||||
public hasLoaded(): boolean {
|
||||
return Object.keys(this.capabilitiesService.providers).length > 0;
|
||||
}
|
||||
|
||||
public get serverInfo(): string {
|
||||
return this.getServerInfo();
|
||||
let value = this.getServerInfo();
|
||||
if (this.serverCapabilities?.useFullOptions) {
|
||||
value += this.getNonDefaultOptionsString();
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public isPasswordRequired(): boolean {
|
||||
|
@ -198,11 +206,14 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||
|
||||
/**
|
||||
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
|
||||
* and all the other properties (except empty ones) if useFullOptions is enabled for the provider.
|
||||
* This key uniquely identifies a connection in a group
|
||||
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
||||
* Example (original format): "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
|
||||
* Example (new format): "providerName:MSSQL|databaseName:database|serverName:server3|userName:user|groupId:testid"
|
||||
* @param getOriginalOptions will return the original URI format regardless if useFullOptions was set or not. (used for retrieving passwords)
|
||||
*/
|
||||
public getOptionsKey(): string {
|
||||
let idNames = this.getOptionKeyIdNames();
|
||||
public getOptionsKey(getOriginalOptions?: boolean): string {
|
||||
let idNames = this.getOptionKeyIdNames(getOriginalOptions);
|
||||
idNames = idNames.filter(x => x !== undefined);
|
||||
|
||||
//Sort to make sure using names in the same order every time otherwise the ids would be different
|
||||
|
@ -211,8 +222,32 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||
let idValues: string[] = [];
|
||||
for (let index = 0; index < idNames.length; index++) {
|
||||
let value = this.options[idNames[index]!];
|
||||
value = value ? value : '';
|
||||
idValues.push(`${idNames[index]}${ProviderConnectionInfo.nameValueSeparator}${value}`);
|
||||
|
||||
// If we're using the new URI format, we do not include any values that are empty or are default.
|
||||
let useFullOptions = (this.serverCapabilities && this.serverCapabilities.useFullOptions)
|
||||
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;
|
||||
if (options[0].specialValueType === 'appName' && this.providerName === Constants.mssqlProviderName) {
|
||||
finalValue = (value as string).startsWith('azdata') ? undefined : finalValue
|
||||
}
|
||||
}
|
||||
else if (options.length > 0 && options[0].specialValueType === 'authType') {
|
||||
// Include default auth type as it is a required part of the option key.
|
||||
finalValue = '';
|
||||
}
|
||||
value = finalValue;
|
||||
}
|
||||
else {
|
||||
value = value ? value : '';
|
||||
}
|
||||
if ((isFullOptions && value !== undefined) || !isFullOptions) {
|
||||
idValues.push(`${idNames[index]}${ProviderConnectionInfo.nameValueSeparator}${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
return ProviderConnectionInfo.ProviderPropertyName + ProviderConnectionInfo.nameValueSeparator +
|
||||
|
@ -222,13 +257,18 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||
/**
|
||||
* @returns Array of option key names
|
||||
*/
|
||||
public getOptionKeyIdNames(): string[] {
|
||||
public getOptionKeyIdNames(getOriginalOptions?: boolean): string[] {
|
||||
let useFullOptions = false;
|
||||
let idNames = [];
|
||||
if (this.serverCapabilities) {
|
||||
useFullOptions = this.serverCapabilities.useFullOptions;
|
||||
idNames = this.serverCapabilities.connectionOptions.map(o => {
|
||||
if ((o.specialValueType || o.isIdentity)
|
||||
&& o.specialValueType !== ConnectionOptionSpecialType.password
|
||||
&& o.specialValueType !== ConnectionOptionSpecialType.connectionName) {
|
||||
// All options enabled, use every property besides password.
|
||||
let newProperty = useFullOptions && o.specialValueType !== ConnectionOptionSpecialType.password && !getOriginalOptions;
|
||||
// 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;
|
||||
} else {
|
||||
return undefined;
|
||||
|
@ -327,4 +367,63 @@ export class ProviderConnectionInfo implements azdata.ConnectionInfo {
|
|||
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 (for connection trees mainly).
|
||||
* @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.serverCapabilities) {
|
||||
this.serverCapabilities.connectionOptions.forEach(element => {
|
||||
if (((!needSpecial && element.specialValueType !== ConnectionOptionSpecialType.serverName &&
|
||||
element.specialValueType !== ConnectionOptionSpecialType.databaseName &&
|
||||
element.specialValueType !== ConnectionOptionSpecialType.authType &&
|
||||
element.specialValueType !== ConnectionOptionSpecialType.userName) || needSpecial) &&
|
||||
element.specialValueType !== ConnectionOptionSpecialType.connectionName &&
|
||||
element.specialValueType !== ConnectionOptionSpecialType.password) {
|
||||
if (getNonDefault) {
|
||||
let value = this.getOptionValue(element.name);
|
||||
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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -181,6 +181,30 @@ suite('ConnectionConfig', () => {
|
|||
isRequired: true,
|
||||
specialValueType: ConnectionOptionSpecialType.password,
|
||||
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
|
||||
}
|
||||
]
|
||||
};
|
||||
|
@ -693,6 +717,195 @@ 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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 () => {
|
||||
let newProfile: IConnectionProfile = {
|
||||
serverName: 'server3',
|
||||
|
@ -807,8 +1020,8 @@ suite('ConnectionConfig', () => {
|
|||
savePassword: true,
|
||||
groupFullName: 'g3',
|
||||
groupId: 'g3',
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
matches: undefined!,
|
||||
providerName: 'MSSQL',
|
||||
|
@ -826,8 +1039,8 @@ suite('ConnectionConfig', () => {
|
|||
savePassword: true,
|
||||
groupFullName: 'test',
|
||||
groupId: 'test',
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
matches: undefined!,
|
||||
providerName: 'MSSQL',
|
||||
|
@ -870,4 +1083,78 @@ suite('ConnectionConfig', () => {
|
|||
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return 'connectionId'; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -173,7 +173,8 @@ suite('SQL ConnectionProfileInfo tests', () => {
|
|||
msSQLCapabilities = {
|
||||
providerId: mssqlProviderName,
|
||||
displayName: 'MSSQL',
|
||||
connectionOptions: connectionProvider
|
||||
connectionOptions: connectionProvider,
|
||||
useFullOptions: true
|
||||
};
|
||||
capabilitiesService = new TestCapabilitiesService();
|
||||
capabilitiesService.capabilities[mssqlProviderName] = { connection: msSQLCapabilities };
|
||||
|
@ -236,7 +237,7 @@ suite('SQL ConnectionProfileInfo tests', () => {
|
|||
|
||||
test('getOptionsKey should create a valid unique id', () => {
|
||||
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|authenticationType:|connectionName:new name|databaseName:database|serverName:new server|userName:user|databaseDisplayName:database|groupId:group id';
|
||||
let id = conn.getOptionsKey();
|
||||
assert.strictEqual(id, expectedId);
|
||||
});
|
||||
|
|
|
@ -23,8 +23,8 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||
authenticationType: '',
|
||||
savePassword: true,
|
||||
groupFullName: 'g2/g2-2',
|
||||
serverCapabilities: undefined,
|
||||
groupId: undefined,
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: undefined!,
|
||||
matches: undefined!,
|
||||
|
@ -35,7 +35,6 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||
};
|
||||
|
||||
setup(() => {
|
||||
let capabilities: azdata.DataProtocolServerCapabilities[] = [];
|
||||
let connectionProvider: azdata.ConnectionOption[] = [
|
||||
{
|
||||
name: 'connectionName',
|
||||
|
@ -127,8 +126,8 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||
providerId: mssqlProviderName,
|
||||
displayName: 'MSSQL',
|
||||
connectionOptions: connectionProvider,
|
||||
useFullOptions: true
|
||||
};
|
||||
capabilities.push(msSQLCapabilities);
|
||||
capabilitiesService = new TestCapabilitiesService();
|
||||
capabilitiesService.capabilities[mssqlProviderName] = { connection: msSQLCapabilities };
|
||||
});
|
||||
|
@ -232,15 +231,37 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||
});
|
||||
|
||||
test('getOptionsKey should create a valid unique id', () => {
|
||||
// Test the new option key format
|
||||
let conn = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
||||
// **IMPORTANT** This should NEVER change without thorough review and consideration of side effects. This key controls
|
||||
// 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 expectedId = 'providerName:MSSQL|authenticationType:|connectionName:name|databaseName:database|serverName:new server|userName:user';
|
||||
let id = conn.getOptionsKey();
|
||||
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);
|
||||
let id1 = conn1.getOptionsKey();
|
||||
|
||||
|
@ -250,6 +271,19 @@ suite('SQL ProviderConnectionInfo tests', () => {
|
|||
const conn2 = new ProviderConnectionInfo(capabilitiesService, connectionProfile);
|
||||
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);
|
||||
});
|
||||
|
||||
|
|
|
@ -355,6 +355,10 @@ export class TestConnectionManagementService implements IConnectionManagementSer
|
|||
return undefined;
|
||||
}
|
||||
|
||||
getEditorConnectionProfileTitle(profile: IConnectionProfile, getOptionsOnly?: boolean, includeGroupName?: boolean): string {
|
||||
return undefined!;
|
||||
}
|
||||
|
||||
openCustomErrorDialog(options: azdata.window.IErrorDialogOptions): Promise<string | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -185,6 +185,10 @@ export class MainThreadConnectionManagement extends Disposable implements MainTh
|
|||
return this._connectionManagementService.openChangePasswordDialog(convertedProfile);
|
||||
}
|
||||
|
||||
public $getEditorConnectionProfileTitle(profile: IConnectionProfile, getOptionsOnly?: boolean, includeGroupName?: boolean): Thenable<string | undefined> {
|
||||
return Promise.resolve(this._connectionManagementService.getEditorConnectionProfileTitle(profile, getOptionsOnly, includeGroupName));
|
||||
}
|
||||
|
||||
public async $listDatabases(connectionId: string): Promise<string[]> {
|
||||
let connectionUri = await this.$getUriForConnection(connectionId);
|
||||
let result = await this._connectionManagementService.listDatabases(connectionUri);
|
||||
|
|
|
@ -78,6 +78,10 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap
|
|||
return this._proxy.$openChangePasswordDialog(profile);
|
||||
}
|
||||
|
||||
public $getEditorConnectionProfileTitle(profile: azdata.IConnectionProfile, getOptionsOnly?: boolean, includeGroupName?: boolean): Thenable<string> {
|
||||
return this._proxy.$getEditorConnectionProfileTitle(profile, getOptionsOnly, includeGroupName);
|
||||
}
|
||||
|
||||
public $listDatabases(connectionId: string): Thenable<string[]> {
|
||||
return this._proxy.$listDatabases(connectionId);
|
||||
}
|
||||
|
|
|
@ -141,6 +141,9 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
|
|||
openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined> {
|
||||
return extHostConnectionManagement.$openChangePasswordDialog(profile);
|
||||
},
|
||||
getEditorConnectionProfileTitle(profile: azdata.IConnectionProfile, getOptionsOnly?: boolean, includeGroupName?: boolean): Thenable<string> {
|
||||
return extHostConnectionManagement.$getEditorConnectionProfileTitle(profile, getOptionsOnly, includeGroupName);
|
||||
},
|
||||
listDatabases(connectionId: string): Thenable<string[]> {
|
||||
return extHostConnectionManagement.$listDatabases(connectionId);
|
||||
},
|
||||
|
|
|
@ -729,6 +729,7 @@ export interface MainThreadConnectionManagementShape extends IDisposable {
|
|||
$getServerInfo(connectedId: string): Thenable<azdata.ServerInfo>;
|
||||
$openConnectionDialog(providers: string[], initialConnectionProfile?: azdata.IConnectionProfile, connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable<azdata.connection.Connection>;
|
||||
$openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined>;
|
||||
$getEditorConnectionProfileTitle(profile: azdata.IConnectionProfile, getOptionsOnly?: boolean, includeGroupName?: boolean): Thenable<string>;
|
||||
$listDatabases(connectionId: string): Thenable<string[]>;
|
||||
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
|
||||
$getUriForConnection(connectionId: string): Thenable<string>;
|
||||
|
|
|
@ -84,12 +84,15 @@ export class DashboardInput extends EditorInput {
|
|||
return '';
|
||||
}
|
||||
|
||||
let name = this.connectionProfile.connectionName ? this.connectionProfile.connectionName : this.connectionProfile.serverName;
|
||||
if (this.connectionProfile.databaseName
|
||||
let name = this.connectionProfile.connectionName ? this.connectionProfile.connectionName : this.connectionProfile.serverName
|
||||
|
||||
if (!this.connectionProfile.connectionName && this.connectionProfile.databaseName
|
||||
&& !this.isMasterMssql()) {
|
||||
// Only add DB name if this is a non-default, non-master connection
|
||||
name = name + ':' + this.connectionProfile.databaseName;
|
||||
}
|
||||
// Append any differing options if needed.
|
||||
name += this._connectionService.getEditorConnectionProfileTitle(this.connectionProfile, true, true)
|
||||
return name;
|
||||
}
|
||||
|
||||
|
|
|
@ -243,11 +243,17 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
|
|||
title = this._description + ' ';
|
||||
}
|
||||
if (profile) {
|
||||
title += `${profile.serverName}`;
|
||||
if (profile.databaseName) {
|
||||
title += `.${profile.databaseName}`;
|
||||
let distinguishedTitle = this.connectionManagementService.getEditorConnectionProfileTitle(profile);
|
||||
if (distinguishedTitle !== '') {
|
||||
title += distinguishedTitle;
|
||||
}
|
||||
else {
|
||||
title += `${profile.serverName}`;
|
||||
if (profile.databaseName) {
|
||||
title += `.${profile.databaseName}`;
|
||||
}
|
||||
title += ` (${profile.userName || profile.authenticationType})`;
|
||||
}
|
||||
title += ` (${profile.userName || profile.authenticationType})`;
|
||||
} else {
|
||||
title += localize('disconnected', "disconnected");
|
||||
}
|
||||
|
|
|
@ -68,21 +68,30 @@ export class ConnectionStatusbarItem extends Disposable implements IWorkbenchCon
|
|||
|
||||
// Set connection info to connection status bar
|
||||
private _setConnectionText(connectionProfile: IConnectionProfile): void {
|
||||
let text: string = connectionProfile.serverName;
|
||||
if (text) {
|
||||
if (connectionProfile.databaseName && connectionProfile.databaseName !== '') {
|
||||
text = text + ' : ' + connectionProfile.databaseName;
|
||||
} else {
|
||||
text = text + ' : ' + '<default>';
|
||||
let distinguishedTitle = this.connectionManagementService.getEditorConnectionProfileTitle(connectionProfile);
|
||||
let text: string = '';
|
||||
let tooltip: string = '';
|
||||
if (distinguishedTitle === '') {
|
||||
text = connectionProfile.serverName;
|
||||
if (text) {
|
||||
if (connectionProfile.databaseName && connectionProfile.databaseName !== '') {
|
||||
text = text + ' : ' + connectionProfile.databaseName;
|
||||
} else {
|
||||
text = text + ' : ' + '<default>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
let tooltip: string =
|
||||
'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 {
|
||||
text = distinguishedTitle;
|
||||
tooltip = (connectionProfile as any).serverInfo;
|
||||
}
|
||||
|
||||
this.statusItem.update({
|
||||
|
|
|
@ -57,7 +57,9 @@ export class BreadcrumbService implements IBreadcrumbService {
|
|||
}
|
||||
|
||||
private getServerBreadcrumb(profile: ConnectionProfile): MenuItem {
|
||||
return profile.connectionName ? { label: profile.connectionName, routerLink: ['server-dashboard'] } : { label: profile.serverName, routerLink: ['server-dashboard'] };
|
||||
let formattedProfileName = profile.connectionName ? profile.connectionName : profile.serverName;
|
||||
formattedProfileName += this.commonService.connectionManagementService.getEditorConnectionProfileTitle(profile, true, true);
|
||||
return { label: formattedProfileName, routerLink: ['server-dashboard'] };
|
||||
}
|
||||
|
||||
private getDbBreadcrumb(profile: ConnectionProfile): MenuItem {
|
||||
|
|
|
@ -752,7 +752,10 @@ export class AttachToDropdown extends SelectBox {
|
|||
} else {
|
||||
let connections: string[] = [];
|
||||
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) {
|
||||
connections.push(model.savedConnectionName);
|
||||
} else {
|
||||
|
|
|
@ -125,6 +125,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
// get the full ConnectionProfiles with the server info updated properly
|
||||
const treeInput = TreeUpdateUtils.getTreeInput(this._connectionManagementService)!;
|
||||
await this._tree.setInput(treeInput);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
this._treeSelectionHandler.onTreeActionStateChange(false);
|
||||
} else {
|
||||
if (this._connectionManagementService.hasRegisteredServers()) {
|
||||
|
@ -272,6 +273,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
if (connectionParentGroup) {
|
||||
connectionParentGroup.addOrReplaceConnection(newConnection);
|
||||
await this._tree.updateChildren(connectionParentGroup);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
await this._tree.revealSelectFocusElement(newConnection);
|
||||
await this._tree.expand(newConnection);
|
||||
}
|
||||
|
@ -286,6 +288,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
await this._tree.rerender(connectionInTree);
|
||||
await this._tree.revealSelectFocusElement(connectionInTree);
|
||||
await this._tree.updateChildren(connectionInTree);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
await this._tree.expand(connectionInTree);
|
||||
}
|
||||
}
|
||||
|
@ -298,6 +301,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
if (parentGroup) {
|
||||
parentGroup.removeConnections([e]);
|
||||
await this._tree.updateChildren(parentGroup);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
await this._tree.revealSelectFocusElement(parentGroup);
|
||||
}
|
||||
}
|
||||
|
@ -315,12 +319,14 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
const newProfileParent = <ConnectionProfileGroup>this._tree.getElementById(e.profile.groupId);
|
||||
newProfileParent.addOrReplaceConnection(e.profile);
|
||||
await this._tree.updateChildren(newProfileParent);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
await this._tree.revealSelectFocusElement(e.profile);
|
||||
await this._tree.expand(e.profile);
|
||||
} else {
|
||||
// If the profile was not moved to a different group then just update the profile in the group.
|
||||
oldProfileParent.replaceConnection(e.profile, e.oldProfileId);
|
||||
await this._tree.updateChildren(oldProfileParent)
|
||||
await this.refreshConnectionTreeTitles();
|
||||
await this._tree.revealSelectFocusElement(e.profile);
|
||||
await this._tree.expand(e.profile);
|
||||
}
|
||||
|
@ -345,6 +351,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
await this._tree.updateChildren(newParent);
|
||||
await this._tree.expand(newParent);
|
||||
}
|
||||
await this.refreshConnectionTreeTitles();
|
||||
const newConnection = this._tree.getElementById(movedConnection.id);
|
||||
if (newConnection) {
|
||||
await this._tree.revealSelectFocusElement(newConnection);
|
||||
|
@ -359,6 +366,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
const parent = <ConnectionProfileGroup>this._tree.getElementById(e.parentId);
|
||||
parent.children = parent.children.filter(c => c.id !== e.id);
|
||||
await this._tree.updateChildren(parent);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
await this._tree.revealSelectFocusElement(parent);
|
||||
}
|
||||
}));
|
||||
|
@ -381,6 +389,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
e.parent = parent;
|
||||
e.parentId = parent.id;
|
||||
await this._tree.updateChildren(parent);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
await this._tree.revealSelectFocusElement(e);
|
||||
}
|
||||
}));
|
||||
|
@ -391,6 +400,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
if (newParent) {
|
||||
newParent.children[newParent.children.findIndex(c => c.id === e.id)] = e;
|
||||
await this._tree.updateChildren(newParent);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
await this._tree.revealSelectFocusElement(e);
|
||||
}
|
||||
}
|
||||
|
@ -409,6 +419,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
(<ConnectionProfileGroup>movedGroup).parent = newParent;
|
||||
(<ConnectionProfileGroup>movedGroup).parentId = newParent.id;
|
||||
await this._tree.updateChildren(newParent);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
await this._tree.revealSelectFocusElement(movedGroup);
|
||||
// Expanding the previously expanded children of the moved group after the move.
|
||||
this._tree.expandElements(profileExpandedState);
|
||||
|
@ -695,6 +706,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
return;
|
||||
}
|
||||
await this._tree.setInput(treeInput!);
|
||||
await this.refreshConnectionTreeTitles();
|
||||
if (isHidden(this.messages!)) {
|
||||
this._tree.getFocus();
|
||||
if (this._tree instanceof AsyncServerTree) {
|
||||
|
@ -960,6 +972,13 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
|
|||
return actionContext;
|
||||
}
|
||||
|
||||
private async refreshConnectionTreeTitles(): Promise<void> {
|
||||
let treeInput = this._tree.getInput();
|
||||
let treeArray = TreeUpdateUtils.alterTreeChildrenTitles([treeInput], this._connectionManagementService, false);
|
||||
treeInput = treeArray[0];
|
||||
await this._tree!.setInput(treeInput);
|
||||
}
|
||||
|
||||
public collapseAllConnections(): void {
|
||||
const root = TreeUpdateUtils.getTreeInput(this._connectionManagementService)!;
|
||||
const connections = ConnectionProfileGroup.getConnectionsInGroup(root);
|
||||
|
|
|
@ -61,6 +61,10 @@ export class SingleConnectionManagementService {
|
|||
public get connectionInfo(): ConnectionManagementInfo {
|
||||
return this._connectionService.getConnectionInfo(this._uri);
|
||||
}
|
||||
|
||||
public getEditorConnectionProfileTitle(profile: IConnectionProfile, getOptionsOnly?: boolean, includeGroupName?: boolean): string {
|
||||
return this._connectionService.getEditorConnectionProfileTitle(profile, getOptionsOnly, includeGroupName);
|
||||
}
|
||||
}
|
||||
|
||||
export class SingleAdminService {
|
||||
|
|
|
@ -553,7 +553,12 @@ class SavedConnectionNode {
|
|||
}
|
||||
|
||||
getChildren() {
|
||||
return this.dataSource.getChildren(TreeUpdateUtils.getTreeInput(this.connectionManagementService));
|
||||
let input = TreeUpdateUtils.getTreeInput(this.connectionManagementService);
|
||||
let newInput = [input];
|
||||
if (input instanceof ConnectionProfileGroup) {
|
||||
newInput = TreeUpdateUtils.alterTreeChildrenTitles([input], this.connectionManagementService);
|
||||
}
|
||||
return this.dataSource.getChildren(newInput[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -257,7 +257,8 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
|||
return;
|
||||
}
|
||||
let fromEditor = params && params.connectionType === ConnectionType.editor;
|
||||
let isTemporaryConnection = params && params.connectionType === ConnectionType.temporary;
|
||||
let hasSaveProfile = connection && connection.hasOwnProperty('saveProfile');
|
||||
let isTemporaryConnection = (params && params.connectionType === ConnectionType.temporary) || (hasSaveProfile && !connection.saveProfile);
|
||||
let uri: string = undefined;
|
||||
if (fromEditor && params && params.input) {
|
||||
uri = params.input.uri;
|
||||
|
|
|
@ -724,6 +724,183 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
|||
return result;
|
||||
}
|
||||
|
||||
public getEditorConnectionProfileTitle(profile: interfaces.IConnectionProfile, getOptionsOnly?: boolean, includeGroupName: boolean = true): string {
|
||||
let result = '';
|
||||
if (profile) {
|
||||
let tempProfile = new ConnectionProfile(this._capabilitiesService, profile);
|
||||
let trimTitle = tempProfile.getOriginalTitle();
|
||||
let idToFind = tempProfile.id;
|
||||
let isChild = false;
|
||||
let totalConnections: ConnectionProfile[] = [];
|
||||
let allConnections = this.getConnections();
|
||||
|
||||
totalConnections = totalConnections.concat(allConnections);
|
||||
|
||||
let initialSearch = totalConnections.filter(inputProfile => {
|
||||
return inputProfile.id === tempProfile.id;
|
||||
});
|
||||
|
||||
let secondarySearch = totalConnections.filter(inputProfile => {
|
||||
return inputProfile.matches(tempProfile)
|
||||
});
|
||||
|
||||
if (initialSearch.length === 0) {
|
||||
if (secondarySearch.length === 1) {
|
||||
// Sometimes the connection id will change for an object explorer connection, especially when connecting from dashboard,
|
||||
// and it will identify as different from the stored one even without changes. Get the info for the stored version as it's the same profile.
|
||||
idToFind = secondarySearch[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle case where a profile may have been an edited existing connection (the one retrieved may not be up to date)
|
||||
if (initialSearch.length === 1 && !initialSearch[0].matches(tempProfile)) {
|
||||
// Remove the old profile with outdated information.
|
||||
totalConnections = totalConnections.filter(inputProfile => {
|
||||
return inputProfile.id !== tempProfile.id;
|
||||
});
|
||||
// Replace with up to date version of the profile.
|
||||
totalConnections = totalConnections.concat(tempProfile);
|
||||
}
|
||||
|
||||
let newConnectionTitles = [];
|
||||
|
||||
this.generateEditorConnectionTitles(totalConnections);
|
||||
if (includeGroupName) {
|
||||
this.appendGroupName(totalConnections);
|
||||
}
|
||||
newConnectionTitles = totalConnections;
|
||||
|
||||
let searchResult = newConnectionTitles.filter(inputProfile => inputProfile.id === idToFind);
|
||||
let finalTitle = searchResult[0]?.title;
|
||||
if (finalTitle) {
|
||||
let optionsAppend = finalTitle.substring(trimTitle.length);
|
||||
if (getOptionsOnly) {
|
||||
finalTitle = optionsAppend;
|
||||
}
|
||||
else if (!getOptionsOnly && isChild) {
|
||||
finalTitle = tempProfile.getOriginalTitle() + optionsAppend;
|
||||
}
|
||||
else {
|
||||
finalTitle = searchResult[0].getOriginalTitle() + optionsAppend;
|
||||
}
|
||||
result = finalTitle;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the connection title to display only the unique properties among profiles provided. (used for editors)
|
||||
*/
|
||||
private generateEditorConnectionTitles(inputList: ConnectionProfile[]): void {
|
||||
let profileListMap = new Map<string, number[]>();
|
||||
// Need to reset title to when it was before (as it may have contained a previously generated title)
|
||||
for (let i = 0; i < inputList.length; i++) {
|
||||
inputList[i].title = inputList[i].getOriginalTitle();
|
||||
}
|
||||
|
||||
// Map the indices of profiles that share the same server info
|
||||
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].serverCapabilities && 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 server info.
|
||||
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 server info.
|
||||
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 = profile.getOriginalTitle() + ' (' + uniqueOptionString + ')';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private appendGroupName(inputList: ConnectionProfile[]): void {
|
||||
let profileListMap = new Map<string, number[]>();
|
||||
|
||||
// Map the indices of profiles that share the same server group
|
||||
for (let i = 0; i < inputList.length; i++) {
|
||||
let groupName = inputList[i].groupFullName;
|
||||
if (profileListMap.has(groupName)) {
|
||||
let profilesForKey = profileListMap.get(groupName);
|
||||
profilesForKey.push(i);
|
||||
profileListMap.set(groupName, profilesForKey);
|
||||
}
|
||||
else {
|
||||
profileListMap.set(groupName, [i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (profileListMap.size > 1) {
|
||||
profileListMap.forEach(function (indexes, groupName) {
|
||||
for (let t = 0; t < indexes.length; t++) {
|
||||
if (groupName !== '') {
|
||||
inputList[indexes[t]].title += nls.localize('connection.connTitleGroupSection', ' (Group: {0})', groupName);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private doActionsAfterConnectionComplete(uri: string, options: IConnectionCompletionOptions): void {
|
||||
let connectionManagementInfo = this._connectionStatusManager.findConnection(uri);
|
||||
if (!connectionManagementInfo) {
|
||||
|
@ -1298,7 +1475,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
|||
this._connectionGlobalStatus.setStatusToConnected(info.connectionSummary);
|
||||
}
|
||||
|
||||
const connectionUniqueId = connection.connectionProfile.getConnectionInfoId();
|
||||
const connectionUniqueId = connection.connectionProfile.getOptionsKey();
|
||||
if (info.isSupportedVersion === false
|
||||
&& this._connectionsGotUnsupportedVersionWarning.indexOf(connectionUniqueId) === -1
|
||||
&& 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 { 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 { TestConnectionProvider } from 'sql/platform/connection/test/common/testConnectionProvider';
|
||||
import { TestResourceProvider } from 'sql/workbench/services/resourceProvider/test/common/testResourceProviderService';
|
||||
|
@ -516,7 +516,7 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||
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 uri1 = 'connection:connectionId';
|
||||
let options: IConnectionCompletionOptions = {
|
||||
|
@ -1015,12 +1015,16 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||
showFirewallRuleOnError: true
|
||||
};
|
||||
|
||||
connectionStore.setup(x => x.isDuplicateEdit(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => {
|
||||
//In a real scenario this would be false as it would match the first instance and not find a duplicate.
|
||||
return Promise.resolve(false);
|
||||
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);
|
||||
});
|
||||
profile.getOptionsKey = () => { return 'test_uri1'; };
|
||||
await connect(uri1, options, true, profile);
|
||||
let originalProfile = ConnectionProfile.fromIConnectionProfile(new TestCapabilitiesService(), connectionProfile);
|
||||
originalProfileKey = originalProfile.getOptionsKey();
|
||||
let newProfile = Object.assign({}, connectionProfile);
|
||||
newProfile.connectionName = newname;
|
||||
newProfile.getOptionsKey = () => { return 'test_uri1'; };
|
||||
|
@ -1110,7 +1114,6 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||
});
|
||||
|
||||
|
||||
|
||||
test('failed firewall rule should open the firewall rule dialog', async () => {
|
||||
handleFirewallRuleResult.canHandleFirewallRule = true;
|
||||
resolveHandleFirewallRuleDialog = true;
|
||||
|
@ -2047,6 +2050,184 @@ suite('SQL ConnectionManagementService tests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// TODO - need to rework test to match new format.
|
||||
test.skip('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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: () => { return ''; },
|
||||
getOptionKeyIdNames: undefined!,
|
||||
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 connectionStatusManagerMock = TypeMoq.Mock.ofType(ConnectionStatusManager, TypeMoq.MockBehavior.Loose);
|
||||
connectionStatusManagerMock.setup(x => x.getActiveConnectionProfiles(undefined)).returns(() => {
|
||||
return [];
|
||||
});
|
||||
const testInstantiationService = new TestInstantiationService();
|
||||
testInstantiationService.stub(IStorageService, new TestStorageService());
|
||||
sinon.stub(testInstantiationService, 'createInstance').withArgs(ConnectionStore).returns(connectionStoreMock.object).withArgs(ConnectionStatusManager).returns(connectionStatusManagerMock.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 options by themselves are empty if no other profiles exist.
|
||||
let result = connectionManagementService.getEditorConnectionProfileTitle(profile, true);
|
||||
assert.strictEqual(result, '', `Options appeared when they should not have.`);
|
||||
|
||||
// We should expect that the string contains only the server info (basic) if there is no other connection with the same server info.
|
||||
result = connectionManagementService.getEditorConnectionProfileTitle(profile);
|
||||
|
||||
let generatedProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, profile);
|
||||
let expectedNonDefaultOption = ' (testOption1=test value; testOption2=50)';
|
||||
let profileServerInfo = generatedProfile.serverInfo.substring(0, generatedProfile.serverInfo.indexOf(expectedNonDefaultOption));
|
||||
|
||||
assert.strictEqual(result, `${profileServerInfo}`, `getEditorConnectionProfileTitle included a connection name when it shouldn't`);
|
||||
|
||||
connectionStoreMock.setup(x => x.getAllConnectionsFromConfig()).returns(() => {
|
||||
return [generatedProfile];
|
||||
});
|
||||
|
||||
// We should expect that the string only contains the server info (basic) if there is only default options, and another connection with similar title but non default options.
|
||||
profile.options['testOption1'] = undefined;
|
||||
profile.options['testOption2'] = undefined;
|
||||
profile.options['testOption3'] = undefined;
|
||||
|
||||
result = connectionManagementService.getEditorConnectionProfileTitle(profile);
|
||||
|
||||
assert.strictEqual(result, `${profileServerInfo}`, `getEditorConnectionProfileTitle included differing connection options when it shouldn't`);
|
||||
|
||||
//Reset profiles for next test and add secondary profile .
|
||||
profile.options['testOption1'] = 'test value';
|
||||
profile.options['testOption2'] = '50';
|
||||
profile.options['testOption3'] = 'default';
|
||||
generatedProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, profile);
|
||||
profile.options['testOption1'] = undefined;
|
||||
profile.options['testOption2'] = undefined;
|
||||
profile.options['testOption3'] = undefined;
|
||||
let emptyGeneratedProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, profile);
|
||||
|
||||
connectionStoreMock.setup(x => x.getAllConnectionsFromConfig()).returns(() => {
|
||||
return [generatedProfile, emptyGeneratedProfile];
|
||||
});
|
||||
|
||||
// We should expect that the string contains the server info appended with differing options, if there's another connection with similar title that has only default options.
|
||||
result = connectionManagementService.getEditorConnectionProfileTitle(generatedProfile);
|
||||
|
||||
assert.equal(result, `${generatedProfile.serverInfo}`, `getEditorConnectionProfileTitle did not include differing connection options when it should`);
|
||||
|
||||
connectionStoreMock.setup(x => x.getAllConnectionsFromConfig()).returns(() => {
|
||||
return [generatedProfile, emptyGeneratedProfile];
|
||||
});
|
||||
|
||||
// We should expect that the string contains only the differing options when we ask for options only
|
||||
result = connectionManagementService.getEditorConnectionProfileTitle(generatedProfile, true);
|
||||
|
||||
assert.equal(result, expectedNonDefaultOption, `getEditorConnectionProfileTitle did not return differing options only`);
|
||||
|
||||
//Reset profiles for next test and add secondary profile .
|
||||
profile.options['testOption1'] = 'test value';
|
||||
profile.options['testOption2'] = '50';
|
||||
profile.options['testOption3'] = 'default';
|
||||
profile.connectionName = 'New Connection Name';
|
||||
profile.options['connectionName'] = profile.connectionName;
|
||||
generatedProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, profile);
|
||||
profile.options['testOption1'] = undefined;
|
||||
profile.options['testOption2'] = undefined;
|
||||
profile.options['testOption3'] = undefined;
|
||||
profile.connectionName = 'new name';
|
||||
profile.options['connectionName'] = profile.connectionName;
|
||||
emptyGeneratedProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, profile);
|
||||
expectedNonDefaultOption = ' (connectionName=New Connection Name; testOption1=test value; testOption2=50)';
|
||||
|
||||
connectionStoreMock.setup(x => x.getAllConnectionsFromConfig()).returns(() => {
|
||||
return [generatedProfile, emptyGeneratedProfile];
|
||||
});
|
||||
|
||||
// We should expect that the string now contains connectionName, when it is different.
|
||||
result = connectionManagementService.getEditorConnectionProfileTitle(generatedProfile, false);
|
||||
|
||||
assert.equal(result, `${profileServerInfo}${expectedNonDefaultOption}`, `getEditorConnectionProfileTitle did not include connectionName in options when it should`);
|
||||
|
||||
connectionStoreMock.setup(x => x.getAllConnectionsFromConfig()).returns(() => {
|
||||
return [generatedProfile, emptyGeneratedProfile];
|
||||
});
|
||||
|
||||
// We should expect that the string contains only the differing options (including Connection Name) against server info when we ask for options only.
|
||||
result = connectionManagementService.getEditorConnectionProfileTitle(generatedProfile, true);
|
||||
|
||||
assert.equal(result, expectedNonDefaultOption, `getEditorConnectionProfileTitle did not include only options with connectionName`);
|
||||
|
||||
});
|
||||
|
||||
export function createConnectionProfile(id: string, password?: string): ConnectionProfile {
|
||||
const capabilitiesService = new TestCapabilitiesService();
|
||||
return new ConnectionProfile(capabilitiesService, {
|
||||
|
|
|
@ -47,6 +47,21 @@ export class TreeUpdateUtils {
|
|||
|
||||
public static isInDragAndDrop: boolean = false;
|
||||
|
||||
/**
|
||||
* Functions to restore/remove the groupId for title generation as they are removed when added to treeInput
|
||||
*/
|
||||
private static restoreGroupId(treeInput: ConnectionProfileGroup, originalProfiles: ConnectionProfile[]) {
|
||||
for (let i = 0; i < treeInput.connections.length; i++) {
|
||||
treeInput.connections[i].groupId = originalProfiles[i].groupId
|
||||
}
|
||||
}
|
||||
|
||||
private static removeGroupId(treeInput: ConnectionProfileGroup) {
|
||||
for (let i = 0; i < treeInput.connections.length; i++) {
|
||||
treeInput.connections[i].groupId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set input for the tree.
|
||||
*/
|
||||
|
@ -68,11 +83,21 @@ export class TreeUpdateUtils {
|
|||
if (viewKey === 'recent') {
|
||||
groups = connectionManagementService.getRecentConnections(providers);
|
||||
treeInput.addConnections(groups);
|
||||
this.restoreGroupId(treeInput, connectionManagementService.getRecentConnections(providers));
|
||||
let treeArray = TreeUpdateUtils.alterTreeChildrenTitles([treeInput], connectionManagementService);
|
||||
this.removeGroupId(treeInput);
|
||||
treeInput = treeArray[0];
|
||||
} else if (viewKey === 'active') {
|
||||
groups = connectionManagementService.getActiveConnections(providers);
|
||||
treeInput.addConnections(groups);
|
||||
this.restoreGroupId(treeInput, connectionManagementService.getActiveConnections(providers));
|
||||
let treeArray = TreeUpdateUtils.alterTreeChildrenTitles([treeInput], connectionManagementService);
|
||||
this.removeGroupId(treeInput);
|
||||
treeInput = treeArray[0];
|
||||
} else if (viewKey === 'saved') {
|
||||
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService, providers);
|
||||
let treeArray = TreeUpdateUtils.alterTreeChildrenTitles([treeInput], connectionManagementService);
|
||||
treeInput = treeArray[0];
|
||||
}
|
||||
const previousTreeInput = tree.getInput();
|
||||
if (treeInput) {
|
||||
|
@ -93,13 +118,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[], connectionManagementService: IConnectionManagementService, includeGroupName?: boolean): ConnectionProfileGroup[] {
|
||||
inputGroups.forEach(group => {
|
||||
group.children = TreeUpdateUtils.alterTreeChildrenTitles(group.children, connectionManagementService, includeGroupName);
|
||||
let connections = group.connections;
|
||||
TreeUpdateUtils.alterConnectionTitles(connections, connectionManagementService, includeGroupName);
|
||||
group.connections = connections;
|
||||
});
|
||||
return inputGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set input for the registered servers tree.
|
||||
*/
|
||||
public static async registeredServerUpdate(tree: ITree | AsyncServerTree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): Promise<void> {
|
||||
if (tree instanceof AsyncServerTree) {
|
||||
const treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||
if (treeInput) {
|
||||
let treeArray = this.alterTreeChildrenTitles([treeInput], connectionManagementService, false);
|
||||
treeInput = treeArray[0];
|
||||
await tree.setInput(treeInput);
|
||||
}
|
||||
tree.rerender();
|
||||
|
@ -128,6 +169,8 @@ export class TreeUpdateUtils {
|
|||
|
||||
let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
|
||||
if (treeInput) {
|
||||
let treeArray = TreeUpdateUtils.alterTreeChildrenTitles([treeInput], connectionManagementService, false);
|
||||
treeInput = treeArray[0];
|
||||
const originalInput = tree.getInput();
|
||||
if (treeInput !== originalInput) {
|
||||
return tree.setInput(treeInput).then(async () => {
|
||||
|
@ -370,4 +413,14 @@ export class TreeUpdateUtils {
|
|||
}
|
||||
return connectionProfile;
|
||||
}
|
||||
|
||||
private static alterConnectionTitles(inputList: ConnectionProfile[], connectionManagementService: IConnectionManagementService, includeGroupName?: boolean): void {
|
||||
for (let i = 0; i < inputList.length; i++) {
|
||||
let currentConnection = inputList[i];
|
||||
let listOfDuplicates = inputList.filter(connection => connection.getOriginalTitle() === currentConnection.getOriginalTitle());
|
||||
if (listOfDuplicates.length > 1) {
|
||||
inputList[i].title = connectionManagementService.getEditorConnectionProfileTitle(inputList[i], false, includeGroupName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,645 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
// TODO - Need to fix these tests to match the refactoring of the Connection Title Generation.
|
||||
suite.skip('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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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], undefined);
|
||||
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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], undefined);
|
||||
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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], undefined);
|
||||
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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], undefined);
|
||||
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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], undefined);
|
||||
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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], undefined);
|
||||
|
||||
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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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',
|
||||
serverCapabilities: undefined,
|
||||
getOptionsKey: undefined!,
|
||||
getOptionKeyIdNames: 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], undefined);
|
||||
|
||||
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]);
|
||||
});
|
||||
});
|
Загрузка…
Ссылка в новой задаче