mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-22 01:25:38 -05:00
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 commit e30aee5151319863aebbb4738fb30354c179c2c5.
* 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 commit 898bb73a34.
* 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 commit 2c7b94f98cabddb34116dcdd83177614a484c026.
* 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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user