Connection URI with complete options (finalized) (#22735)

* Connection URI made to include every option available instead of basic details (#22045)

* Revert "Merge remote-tracking branch 'origin' into feat/connectionUri"

This reverts commit 11b2d31bf99e216daee823f732254f69a017fee1, reversing
changes made to 36e4db8c0744f81565efdfd2f56a3ae3c0026896.

* Revert "Revert "Merge remote-tracking branch 'origin' into feat/connectionUri""

This reverts commit f439673c2693e1144c52e04c14e82cd8566c13a6.

* Added changes and fixes for feat connectionuri (#22706)

* add title generation at start

* added await to refreshConnectionTreeTitles
This commit is contained in:
Alex Ma
2023-04-18 11:08:48 -07:00
committed by GitHub
parent a9bc34acf0
commit b69e87df15
27 changed files with 1641 additions and 86 deletions

View File

@@ -540,6 +540,22 @@ export class ConnectionManagementService extends Disposable implements IConnecti
let isEdit = options?.params?.isEditConnection ?? false;
let matcher: interfaces.ProfileMatcher;
if (isEdit) {
matcher = (a: interfaces.IConnectionProfile, b: interfaces.IConnectionProfile) => a.id === options.params.oldProfileId;
//Check to make sure the edits are not identical to another connection.
await this._connectionStore.isDuplicateEdit(connection, matcher).then(result => {
if (result) {
// Must get connection group name here as it may not always be initialized and causes problems when deleting when included with options.
this._logService.error(`Profile edit for '${connection.id}' exactly matches an existing profile with data: '${ConnectionProfile.getDisplayOptionsKey(connection.getOptionsKey())}'`);
throw new Error(`Cannot save profile, the selected connection options are identical to an existing profile with details: \n
${ConnectionProfile.getDisplayOptionsKey(connection.getOptionsKey())}${(connection.groupFullName !== undefined && connection.groupFullName !== '' && connection.groupFullName !== '/') ?
ConnectionProfile.displayIdSeparator + 'groupName' + ConnectionProfile.displayNameValueSeparator + connection.groupFullName : ''}`);
}
});
}
if (!uri) {
uri = Utils.generateUri(connection);
}
@@ -588,11 +604,6 @@ export class ConnectionManagementService extends Disposable implements IConnecti
callbacks.onConnectSuccess(options.params, connectionResult.connectionProfile);
}
if (options.saveTheConnection || isEdit) {
let matcher: interfaces.ProfileMatcher;
if (isEdit) {
matcher = (a: interfaces.IConnectionProfile, b: interfaces.IConnectionProfile) => a.id === options.params.oldProfileId;
}
await this.saveToSettings(uri, connection, matcher).then(value => {
this._onAddConnectionProfile.fire(connection);
if (isEdit) {
@@ -700,6 +711,20 @@ export class ConnectionManagementService extends Disposable implements IConnecti
return result;
}
public getEditorConnectionProfileTitle(profile: interfaces.IConnectionProfile, getNonDefaultsOnly?: boolean): string {
let result = '';
if (profile) {
let tempProfile = new ConnectionProfile(this._capabilitiesService, profile);
if (!getNonDefaultsOnly) {
result = tempProfile.getEditorFullTitleWithOptions();
}
else {
result = tempProfile.getNonDefaultOptionsString();
}
}
return result;
}
private doActionsAfterConnectionComplete(uri: string, options: IConnectionCompletionOptions): void {
let connectionManagementInfo = this._connectionStatusManager.findConnection(uri);
if (!connectionManagementInfo) {
@@ -1265,7 +1290,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')) {

View File

@@ -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';
@@ -514,7 +514,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 = {
@@ -1013,7 +1013,15 @@ suite('SQL ConnectionManagementService tests', () => {
showFirewallRuleOnError: true
};
let originalProfileKey = '';
connectionStore.setup(x => x.isDuplicateEdit(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((inputProfile, matcher) => {
let newProfile = ConnectionProfile.fromIConnectionProfile(new TestCapabilitiesService(), inputProfile);
let result = newProfile.getOptionsKey() === originalProfileKey;
return Promise.resolve(result);
});
await connect(uri1, options, true, profile);
let originalProfile = ConnectionProfile.fromIConnectionProfile(new TestCapabilitiesService(), connectionProfile);
originalProfileKey = originalProfile.getOptionsKey();
let newProfile = Object.assign({}, connectionProfile);
newProfile.connectionName = newname;
options.params.isEditConnection = true;
@@ -1047,6 +1055,8 @@ suite('SQL ConnectionManagementService tests', () => {
showFirewallRuleOnError: true
};
// In an actual edit situation, the profile options would be different for different URIs, as a placeholder, we check the test uris instead here.
connectionStore.setup(x => x.isDuplicateEdit(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve(uri1 === uri2));
await connect(uri1, options, true, profile);
options.params.isEditConnection = true;
await connect(uri2, options, true, profile);
@@ -1055,6 +1065,51 @@ suite('SQL ConnectionManagementService tests', () => {
assert.strictEqual(uri1info.connectionProfile.id, uri2info.connectionProfile.id);
});
test('Edit Connection - Connecting with an already connected profile via edit should throw an error', async () => {
let uri1 = 'test_uri1';
let profile = Object.assign({}, connectionProfile);
profile.id = '0451';
let options: IConnectionCompletionOptions = {
params: {
connectionType: ConnectionType.editor,
input: {
onConnectSuccess: undefined,
onConnectReject: undefined,
onConnectStart: undefined,
onDisconnect: undefined,
onConnectCanceled: undefined,
uri: uri1
},
queryRange: undefined,
runQueryOnCompletion: RunQueryOnConnectionMode.none,
isEditConnection: true
},
saveTheConnection: true,
showDashboard: false,
showConnectionDialogOnError: true,
showFirewallRuleOnError: true
};
let originalProfileKey = '';
connectionStore.setup(x => x.isDuplicateEdit(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns((inputProfile, matcher) => {
let newProfile = ConnectionProfile.fromIConnectionProfile(new TestCapabilitiesService(), inputProfile);
let result = newProfile.getOptionsKey() === originalProfileKey;
return Promise.resolve(result)
});
await connect(uri1, options, true, profile);
let originalProfile = ConnectionProfile.fromIConnectionProfile(new TestCapabilitiesService(), connectionProfile);
originalProfileKey = originalProfile.getOptionsKey();
let newProfile = Object.assign({}, connectionProfile);
options.params.isEditConnection = true;
try {
await connect(uri1, options, true, newProfile);
assert.fail;
}
catch {
}
});
test('failed firewall rule should open the firewall rule dialog', async () => {
handleFirewallRuleResult.canHandleFirewallRule = true;
@@ -1992,6 +2047,117 @@ test('clearRecentConnection and ConnectionsList should call connectionStore func
assert(called);
});
test('getEditorConnectionProfileTitle should return a correctly formatted title for a connection profile', () => {
let profile: IConnectionProfile = {
connectionName: 'new name',
serverName: 'new server',
databaseName: 'database',
userName: 'user',
password: 'password',
authenticationType: Constants.AuthenticationType.Integrated,
savePassword: true,
groupFullName: 'g2/g2-2',
groupId: 'group id',
getOptionsKey: () => { return ''; },
matches: undefined,
providerName: 'MSSQL',
options: {},
saveProfile: true,
id: undefined
};
let capabilitiesService = new TestCapabilitiesService();
const testOption1 = {
name: 'testOption1',
displayName: 'testOption1',
description: 'test description',
groupName: 'test group name',
valueType: ServiceOptionType.string,
specialValueType: undefined,
defaultValue: '',
categoryValues: undefined,
isIdentity: false,
isRequired: false
};
const testOption2 = {
name: 'testOption2',
displayName: 'testOption2',
description: 'test description',
groupName: 'test group name',
valueType: ServiceOptionType.number,
specialValueType: undefined,
defaultValue: '10',
categoryValues: undefined,
isIdentity: false,
isRequired: false
};
const testOption3 = {
name: 'testOption3',
displayName: 'testOption3',
description: 'test description',
groupName: 'test group name',
valueType: ServiceOptionType.string,
specialValueType: undefined,
defaultValue: 'default',
categoryValues: undefined,
isIdentity: false,
isRequired: false
};
profile.options['testOption1'] = 'test value';
profile.options['testOption2'] = '50';
profile.options['testOption3'] = 'default';
let mainProvider = capabilitiesService.capabilities['MSSQL'];
let mainProperties = mainProvider.connection;
let mainOptions = mainProperties.connectionOptions;
mainOptions.push(testOption1);
mainOptions.push(testOption2);
mainOptions.push(testOption3);
mainProperties.connectionOptions = mainOptions;
mainProvider.connection = mainProperties;
capabilitiesService.capabilities['MSSQL'] = mainProvider;
const connectionStoreMock = TypeMoq.Mock.ofType(ConnectionStore, TypeMoq.MockBehavior.Loose, new TestStorageService());
const testInstantiationService = new TestInstantiationService();
testInstantiationService.stub(IStorageService, new TestStorageService());
sinon.stub(testInstantiationService, 'createInstance').withArgs(ConnectionStore).returns(connectionStoreMock.object);
const connectionManagementService = new ConnectionManagementService(undefined, testInstantiationService, undefined, undefined, undefined, capabilitiesService, undefined, undefined, undefined, new TestErrorDiagnosticsService(), undefined, undefined, undefined, undefined, getBasicExtensionService(), undefined, undefined, undefined);
// We should expect that non default options are returned when we try to get the nonDefaultOptions string.
let result = connectionManagementService.getEditorConnectionProfileTitle(profile, true);
let expectedNonDefaultOption = ' (testOption1=test value; testOption2=50)';
assert.strictEqual(result, expectedNonDefaultOption, `Profile non default options contained incorrect options`);
// We should expect that the string contains the connection name and the server info (with all non default options appended).
let generatedProfile = ConnectionProfile.fromIConnectionProfile(capabilitiesService, profile);
let profileServerInfo = generatedProfile.serverInfo;
result = connectionManagementService.getEditorConnectionProfileTitle(profile);
assert.strictEqual(result, `${profile.connectionName}: ${profileServerInfo}`, `getEditorConnectionProfileTitle does not return the correct string for ${profile.connectionName}`);
// We should expect that the string contains only the server info (with non default options) if there is no connection name.
profile.connectionName = undefined;
result = connectionManagementService.getEditorConnectionProfileTitle(profile);
assert.strictEqual(result, `${profileServerInfo}`, `getEditorConnectionProfileTitle included a connection name when it shouldn't`);
// We should expect that the string only contains the server info without any non default options if no such options exist.
profile.options['testOption1'] = undefined;
profile.options['testOption2'] = undefined;
profile.options['testOption3'] = undefined;
result = connectionManagementService.getEditorConnectionProfileTitle(profile);
assert.notEqual(result, `${profileServerInfo}`, `getEditorConnectionProfileTitle included non default connection options when it shouldn't`);
});
export function createConnectionProfile(id: string, password?: string): ConnectionProfile {
const capabilitiesService = new TestCapabilitiesService();
return new ConnectionProfile(capabilitiesService, {