Initial Code Layering (#3788)

* working on formatting

* fixed basic lint errors; starting moving things to their appropriate location

* formatting

* update tslint to match the version of vscode we have

* remove unused code

* work in progress fixing layering

* formatting

* moved connection management service to platform

* formatting

* add missing file

* moving more servies

* formatting

* moving more services

* formatting

* wip

* moving more services

* formatting

* revert back tslint rules

* move css file

* add missing svgs
This commit is contained in:
Anthony Dresser
2019-01-25 14:52:35 -08:00
committed by GitHub
parent c8986464ec
commit ea67859de7
338 changed files with 2036 additions and 7386 deletions

View File

@@ -0,0 +1,481 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as Constants from './constants';
import * as Utils from './utils';
import { IConnectionProfile, IConnectionProfileStore } from './interfaces';
import { IConnectionConfig } from './iconnectionConfig';
import { ConnectionProfileGroup, IConnectionProfileGroup } from './connectionProfileGroup';
import { ConfigurationEditingService, IConfigurationValue } from 'vs/workbench/services/configuration/node/configurationEditingService';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ConnectionProfile } from './connectionProfile';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import { generateUuid } from 'vs/base/common/uuid';
export interface ISaveGroupResult {
groups: IConnectionProfileGroup[];
newGroupId: string;
}
/**
* Implements connection profile file storage.
*/
export class ConnectionConfig implements IConnectionConfig {
/**
* Constructor.
*/
public constructor(
private _configurationEditService: ConfigurationEditingService,
private _workspaceConfigurationService: IWorkspaceConfigurationService,
private _capabilitiesService: ICapabilitiesService
) { }
/**
* Returns connection groups from user and workspace settings.
*/
public getAllGroups(): IConnectionProfileGroup[] {
let allGroups: IConnectionProfileGroup[] = [];
let userGroups = this.getConfiguration(Constants.connectionGroupsArrayName).user as IConnectionProfileGroup[];
let workspaceGroups = this.getConfiguration(Constants.connectionGroupsArrayName).workspace as IConnectionProfileGroup[];
if (userGroups) {
if (workspaceGroups) {
userGroups = userGroups.filter(x => workspaceGroups.find(f => this.isSameGroupName(f, x)) === undefined);
allGroups = allGroups.concat(workspaceGroups);
}
allGroups = allGroups.concat(userGroups);
}
allGroups = allGroups.map(g => {
if (g.parentId === '' || !g.parentId) {
g.parentId = undefined;
}
return g;
});
return allGroups;
}
/**
* Add a new connection to the connection config.
*/
public addConnection(profile: IConnectionProfile): Promise<IConnectionProfile> {
return new Promise<IConnectionProfile>((resolve, reject) => {
if (profile.saveProfile) {
this.addGroupFromProfile(profile).then(groupId => {
let profiles = this._workspaceConfigurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).user;
if (!profiles) {
profiles = [];
}
let connectionProfile = this.getConnectionProfileInstance(profile, groupId);
let newProfile = ConnectionProfile.convertToProfileStore(this._capabilitiesService, connectionProfile);
// Remove the profile if already set
var sameProfileInList = profiles.find(value => {
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService);
return providerConnectionProfile.matches(connectionProfile);
});
if (sameProfileInList) {
let profileIndex = profiles.findIndex(value => value === sameProfileInList);
newProfile.id = sameProfileInList.id;
connectionProfile.id = sameProfileInList.id;
profiles[profileIndex] = newProfile;
} else {
profiles.push(newProfile);
}
this.writeConfiguration(Constants.connectionsArrayName, profiles).then(() => {
resolve(connectionProfile);
}).catch(err => {
reject(err);
});
});
}
});
}
private getConnectionProfileInstance(profile: IConnectionProfile, groupId: string): ConnectionProfile {
let connectionProfile = profile as ConnectionProfile;
if (connectionProfile === undefined) {
connectionProfile = new ConnectionProfile(this._capabilitiesService, profile);
}
connectionProfile.groupId = groupId;
return connectionProfile;
}
/**
*Returns group id
* @param groupName
*/
public addGroupFromProfile(profile: IConnectionProfile): Promise<string> {
return new Promise<string>((resolve, reject) => {
if (profile.groupId && profile.groupId !== Utils.defaultGroupId) {
resolve(profile.groupId);
} else {
let groups = this._workspaceConfigurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
let result = this.saveGroup(groups, profile.groupFullName, undefined, undefined);
groups = result.groups;
this.writeConfiguration(Constants.connectionGroupsArrayName, groups).then(() => {
resolve(result.newGroupId);
}).catch(err => {
reject(err);
});
}
});
}
/**
*Returns group id
* @param groupName
*/
public addGroup(profileGroup: IConnectionProfileGroup): Promise<string> {
return new Promise<string>((resolve, reject) => {
if (profileGroup.id) {
resolve(profileGroup.id);
} else {
let groups = this._workspaceConfigurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
let sameNameGroup = groups ? groups.find(group => group.name === profileGroup.name) : undefined;
if (sameNameGroup) {
let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists.");
reject(errMessage);
} else {
let result = this.saveGroup(groups, profileGroup.name, profileGroup.color, profileGroup.description);
groups = result.groups;
this.writeConfiguration(Constants.connectionGroupsArrayName, groups).then(() => {
resolve(result.newGroupId);
}).catch(err => {
reject(err);
});
}
}
});
}
private getConnectionProfilesForTarget(configTarget: ConfigurationTarget): IConnectionProfileStore[] {
let configs = this.getConfiguration(Constants.connectionsArrayName);
let profiles: IConnectionProfileStore[];
if (configs) {
if (configTarget === ConfigurationTarget.USER) {
profiles = <IConnectionProfileStore[]>configs.user;
} else if (configTarget === ConfigurationTarget.WORKSPACE) {
profiles = <IConnectionProfileStore[]>configs.workspace;
}
if (profiles) {
if (this.fixConnectionIds(profiles)) {
this.writeConfiguration(Constants.connectionsArrayName, profiles, configTarget);
}
} else {
profiles = [];
}
}
return profiles;
}
/**
* Replace duplicate ids with new ones. Sets id for the profiles without id
* @param profiles
*/
public fixConnectionIds(profiles: IConnectionProfileStore[]): boolean {
let idsCache: { [label: string]: boolean } = {};
let changed: boolean = false;
for (var index = 0; index < profiles.length; index++) {
var profile = profiles[index];
if (!profile.id) {
profile.id = generateUuid();
changed = true;
}
if (profile.id in idsCache) {
profile.id = generateUuid();
changed = true;
}
idsCache[profile.id] = true;
}
return changed;
}
/**
* Get a list of all connections in the connection config. Connections returned
* are sorted first by whether they were found in the user/workspace settings,
* and next alphabetically by profile/server name.
*/
public getConnections(getWorkspaceConnections: boolean): ConnectionProfile[] {
let profiles: IConnectionProfileStore[] = [];
//TODO: have to figure out how to sort connections for all provider
// Read from user settings
let userProfiles: IConnectionProfileStore[] = this.getConnectionProfilesForTarget(ConfigurationTarget.USER);
if (userProfiles !== undefined) {
profiles = profiles.concat(userProfiles);
}
if (getWorkspaceConnections) {
// Read from workspace settings
let workspaceProfiles: IConnectionProfileStore[] = this.getConnectionProfilesForTarget(ConfigurationTarget.WORKSPACE);
if (workspaceProfiles !== undefined) {
profiles = profiles.concat(workspaceProfiles);
}
}
let connectionProfiles = profiles.map(p => {
return ConnectionProfile.createFromStoredProfile(p, this._capabilitiesService);
});
return connectionProfiles;
}
/**
* Delete a connection profile from settings.
*/
public deleteConnection(profile: ConnectionProfile): Promise<void> {
// Get all connections in the settings
let profiles = this._workspaceConfigurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).user;
// Remove the profile from the connections
profiles = profiles.filter(value => {
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService);
return providerConnectionProfile.getOptionsKey() !== profile.getOptionsKey();
});
// Write connections back to settings
return this.writeConfiguration(Constants.connectionsArrayName, profiles);
}
/**
* Delete a group and all its child connections and groups from settings.
* Fails if writing to settings fails.
*/
public deleteGroup(group: ConnectionProfileGroup): Promise<void> {
let connections = ConnectionProfileGroup.getConnectionsInGroup(group);
let subgroups = ConnectionProfileGroup.getSubgroups(group);
// Add selected group to subgroups list
subgroups.push(group);
// Get all connections in the settings
let profiles = this._workspaceConfigurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).user;
// Remove the profiles from the connections
profiles = profiles.filter(value => {
let providerConnectionProfile = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService);
return !connections.some((val) => val.getOptionsKey() === providerConnectionProfile.getOptionsKey());
});
// Get all groups in the settings
let groups = this._workspaceConfigurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
// Remove subgroups in the settings
groups = groups.filter((grp) => {
return !subgroups.some((item) => item.id === grp.id);
});
return new Promise<void>((resolve, reject) => {
this.writeConfiguration(Constants.connectionsArrayName, profiles).then(() => {
this.writeConfiguration(Constants.connectionGroupsArrayName, groups).then(() => {
resolve();
}).catch(() => reject());
}).catch(() => reject());
});
}
/**
* Moves the source group under the target group.
*/
public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void> {
let groups = this._workspaceConfigurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
groups = groups.map(g => {
if (g.id === source.id) {
g.parentId = target.id;
}
return g;
});
return this.writeConfiguration(Constants.connectionGroupsArrayName, groups);
}
/**
* Returns true if connection can be moved to another group
*/
public canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean {
let profiles = this.getConnections(true);
let existingProfile = profiles.find(p => p.getConnectionInfoId() === profile.getConnectionInfoId()
&& p.groupId === newGroupID);
return existingProfile === undefined;
}
/**
* Moves the connection under the target group with the new ID.
*/
private changeGroupIdForConnectionInSettings(profile: ConnectionProfile, newGroupID: string, target: ConfigurationTarget = ConfigurationTarget.USER): Promise<void> {
return new Promise<void>((resolve, reject) => {
let profiles = target === ConfigurationTarget.USER ? this._workspaceConfigurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).user :
this._workspaceConfigurationService.inspect<IConnectionProfileStore[]>(Constants.connectionsArrayName).workspace;
if (profiles) {
if (profile.parent && profile.parent.id === Constants.unsavedGroupId) {
profile.groupId = newGroupID;
profiles.push(ConnectionProfile.convertToProfileStore(this._capabilitiesService, profile));
} else {
profiles.forEach((value) => {
let configProf = ConnectionProfile.createFromStoredProfile(value, this._capabilitiesService);
if (configProf.getOptionsKey() === profile.getOptionsKey()) {
value.groupId = newGroupID;
}
});
}
this.writeConfiguration(Constants.connectionsArrayName, profiles, target).then(result => {
resolve();
}).catch(error => {
reject(error);
});
} else {
resolve();
}
});
}
/**
* Moves the connection under the target group with the new ID.
*/
public changeGroupIdForConnection(profile: ConnectionProfile, newGroupID: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
if (!this.canChangeConnectionConfig(profile, newGroupID)) {
// Same connection already exists in this group
reject('Same connection already exists in the group');
} else {
this.changeGroupIdForConnectionInSettings(profile, newGroupID, ConfigurationTarget.USER).then(result1 => {
this.changeGroupIdForConnectionInSettings(profile, newGroupID, ConfigurationTarget.WORKSPACE).then(result2 => {
resolve();
}).catch(error2 => {
reject(error2);
});
}).catch(error1 => {
reject(error1);
});
}
});
}
public saveGroup(groups: IConnectionProfileGroup[], groupFullName: string, color: string, description: string): ISaveGroupResult {
let result: ISaveGroupResult;
let groupNames = ConnectionProfileGroup.getGroupFullNameParts(groupFullName);
result = this.saveGroupInTree(groups, undefined, groupNames, color, description, 0);
return result;
}
public editGroup(source: ConnectionProfileGroup): Promise<void> {
let groups = this._workspaceConfigurationService.inspect<IConnectionProfileGroup[]>(Constants.connectionGroupsArrayName).user;
let sameNameGroup = groups ? groups.find(group => group.name === source.name && group.id !== source.id) : undefined;
if (sameNameGroup) {
let errMessage: string = nls.localize('invalidServerName', "A server group with the same name already exists.");
return Promise.reject(errMessage);
}
groups = groups.map(g => {
if (g.id === source.id) {
g.name = source.name;
g.description = source.description;
g.color = source.color;
source.isRenamed = false;
}
return g;
});
return this.writeConfiguration(Constants.connectionGroupsArrayName, groups);
}
private isSameGroupName(group1: IConnectionProfileGroup, group2: IConnectionProfileGroup): boolean {
let sameGroupName: boolean = false;
if (group1 && group2) {
sameGroupName = ((!group1.name && !group2.name) || group1.name.toUpperCase() === group2.name.toUpperCase()) &&
(group1.parentId === group2.parentId || (!group1.parentId && !group2.parentId));
}
return sameGroupName;
}
private saveGroupInTree(groupTree: IConnectionProfileGroup[], parentId: string, groupNames: string[], color: string, description: string, index: number): ISaveGroupResult {
if (!groupTree) {
groupTree = [];
}
let newGroupId: string;
if (index < groupNames.length) {
let groupName: string = groupNames[index];
let newGroup: IConnectionProfileGroup = {
name: groupName,
id: undefined,
parentId: parentId,
color: color,
description: description
};
let found = groupTree.find(group => this.isSameGroupName(group, newGroup));
if (found) {
if (index === groupNames.length - 1) {
newGroupId = found.id;
//Found the group full name
} else {
let result = this.saveGroupInTree(groupTree, found.id, groupNames, color, description, index + 1);
groupTree = result.groups;
newGroupId = result.newGroupId;
}
} else {
if (ConnectionProfileGroup.isRoot(newGroup.name)) {
newGroup.id = Utils.defaultGroupId;
} else {
newGroup.id = generateUuid();
}
let result = this.saveGroupInTree(groupTree, newGroup.id, groupNames, color, description, index + 1);
newGroupId = result.newGroupId;
groupTree = result.groups;
groupTree.push(newGroup);
if (index === groupNames.length - 1) {
newGroupId = newGroup.id;
}
}
}
let groupResult: ISaveGroupResult = {
groups: groupTree,
newGroupId: newGroupId
};
return groupResult;
}
/**
* Get all profiles from the parsed settings file.
* This is public for testing only.
* @param parsedSettingsFile an object representing the parsed contents of the settings file.
* @returns the set of connection profiles found in the parsed settings file.
*/
private getConfiguration(key: string): any {
let configs: any;
configs = this._workspaceConfigurationService.inspect<IConnectionProfileStore[] | IConnectionProfileGroup[] | sqlops.DataProtocolServerCapabilities[]>(key);
return configs;
}
/**
* Replace existing profiles in the settings file with a new set of profiles.
* @param parsedSettingsFile an object representing the parsed contents of the settings file.
* @param profiles the set of profiles to insert into the settings file.
*/
private writeConfiguration(
key: string,
profiles: IConnectionProfileStore[] | IConnectionProfileGroup[] | sqlops.DataProtocolServerCapabilities[],
target: ConfigurationTarget = ConfigurationTarget.USER): Promise<void> {
return new Promise<void>((resolve, reject) => {
let configValue: IConfigurationValue = {
key: key,
value: profiles
};
this._configurationEditService.writeConfiguration(target, configValue).then(result => {
this._workspaceConfigurationService.reloadConfiguration().then(() => {
resolve();
});
}, (error => {
reject(error);
}));
});
}
}

View File

@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import Interfaces = require('./interfaces');
/**
* Sets sensible defaults for key connection properties, especially
* if connection to Azure
*
* @export connectionInfo/fixupConnectionCredentials
* @param {Interfaces.IConnectionCredentials} connCreds connection to be fixed up
* @returns {Interfaces.IConnectionCredentials} the updated connection
*/
export function fixupConnectionCredentials(connCreds: Interfaces.IConnectionProfile): Interfaces.IConnectionProfile {
if (!connCreds.serverName) {
connCreds.serverName = '';
}
if (!connCreds.databaseName) {
connCreds.databaseName = '';
}
if (!connCreds.userName) {
connCreds.userName = '';
}
if (!connCreds.password) {
connCreds.password = '';
}
return connCreds;
}

View File

@@ -0,0 +1,344 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
import * as sqlops from 'sqlops';
import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ConnectionManagementInfo } from './connectionManagementInfo';
import { IServerGroupDialogCallbacks } from 'sql/platform/serverGroup/common/serverGroupController';
export const VIEWLET_ID = 'workbench.view.connections';
/**
* Options for the actions that could happen after connecting is complete
*/
export interface IConnectionCompletionOptions {
/**
* save the connection to MRU and settings (only save to setting if profile.saveProfile is set to true)
*/
saveTheConnection: boolean;
/**
* open the dashboard after connection is complete
*/
showDashboard: boolean;
/**
* Parameters to be used if connecting from an editor
*/
params: INewConnectionParams;
/**
* Open the connection dialog if connection fails
*/
showConnectionDialogOnError: boolean;
/**
* Open the connection firewall rule dialog if connection fails
*/
showFirewallRuleOnError: boolean;
}
export interface IConnectionResult {
connected: boolean;
errorMessage: string;
errorCode: number;
callStack: string;
errorHandled?: boolean;
connectionProfile?: IConnectionProfile;
}
export interface IConnectionCallbacks {
onConnectStart(): void;
onConnectReject(error?: string): void;
onConnectSuccess(params?: INewConnectionParams): void;
onDisconnect(): void;
onConnectCanceled(): void;
}
export const SERVICE_ID = 'connectionManagementService';
export const IConnectionManagementService = createDecorator<IConnectionManagementService>(SERVICE_ID);
export interface IConnectionManagementService {
_serviceBrand: any;
// Event Emitters
onAddConnectionProfile: Event<IConnectionProfile>;
onDeleteConnectionProfile: Event<void>;
onConnect: Event<IConnectionParams>;
onDisconnect: Event<IConnectionParams>;
onConnectionChanged: Event<IConnectionParams>;
onLanguageFlavorChanged: Event<sqlops.DidChangeLanguageFlavorParams>;
/**
* Opens the connection dialog to create new connection
*/
showConnectionDialog(params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): Promise<void>;
/**
* Opens the add server group dialog
*/
showCreateServerGroupDialog(callbacks?: IServerGroupDialogCallbacks): Promise<void>;
/**
* Opens the edit server group dialog
*/
showEditServerGroupDialog(group: ConnectionProfileGroup): Promise<void>;
/**
* Load the password and opens a new connection
*/
connect(connection: IConnectionProfile, uri: string, options?: IConnectionCompletionOptions, callbacks?: IConnectionCallbacks): Promise<IConnectionResult>;
/**
* Opens a new connection and save the profile in settings
*/
connectAndSaveProfile(connection: IConnectionProfile, uri: string, options?: IConnectionCompletionOptions, callbacks?: IConnectionCallbacks): Promise<IConnectionResult>;
/**
* Finds existing connection for given profile and purpose is any exists.
* The purpose is connection by default
*/
findExistingConnection(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): ConnectionProfile;
/**
* If there's already a connection for given profile and purpose, returns the ownerUri for the connection
* otherwise tries to make a connection and returns the owner uri when connection is complete
* The purpose is connection by default
*/
connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection', saveConnection?: boolean): Promise<string>;
/**
* Adds the successful connection to MRU and send the connection error back to the connection handler for failed connections
*/
onConnectionComplete(handle: number, connectionInfoSummary: sqlops.ConnectionInfoSummary): void;
onIntelliSenseCacheComplete(handle: number, connectionUri: string): void;
onConnectionChangedNotification(handle: number, changedConnInfo: sqlops.ChangedConnectionInfo);
getConnectionGroups(providers?: string[]): ConnectionProfileGroup[];
getRecentConnections(providers?: string[]): ConnectionProfile[];
clearRecentConnectionsList(): void;
clearRecentConnection(connectionProfile: IConnectionProfile): void;
getActiveConnections(providers?: string[]): ConnectionProfile[];
saveProfileGroup(profile: IConnectionProfileGroup): Promise<string>;
changeGroupIdForConnectionGroup(source: IConnectionProfileGroup, target: IConnectionProfileGroup): Promise<void>;
changeGroupIdForConnection(source: ConnectionProfile, targetGroupName: string): Promise<void>;
deleteConnection(connection: ConnectionProfile): Promise<boolean>;
deleteConnectionGroup(group: ConnectionProfileGroup): Promise<boolean>;
getAdvancedProperties(): sqlops.ConnectionOption[];
getConnectionUri(connectionProfile: IConnectionProfile): string;
getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string;
getConnectionUriFromId(connectionId: string): string;
isConnected(fileUri: string): boolean;
/**
* Returns true if the connection profile is connected
*/
isProfileConnected(connectionProfile: IConnectionProfile): boolean;
/**
* Returns true if the connection profile is connecting
*/
isProfileConnecting(connectionProfile: IConnectionProfile): boolean;
isRecent(connectionProfile: ConnectionProfile): boolean;
isConnected(fileUri: string, connectionProfile?: ConnectionProfile): boolean;
disconnectEditor(owner: IConnectableInput, force?: boolean): Promise<boolean>;
disconnect(connection: IConnectionProfile): Promise<void>;
disconnect(ownerUri: string): Promise<void>;
addSavedPassword(connectionProfile: IConnectionProfile): Promise<IConnectionProfile>;
listDatabases(connectionUri: string): Thenable<sqlops.ListDatabasesResult>;
/**
* Register a connection provider
*/
registerProvider(providerId: string, provider: sqlops.ConnectionProvider): void;
editGroup(group: ConnectionProfileGroup): Promise<void>;
getConnectionProfile(fileUri: string): IConnectionProfile;
getConnectionInfo(fileUri: string): ConnectionManagementInfo;
/**
* Cancels the connection
*/
cancelConnection(connection: IConnectionProfile): Thenable<boolean>;
/**
* Changes the database for an active connection
*/
changeDatabase(connectionUri: string, databaseName: string): Thenable<boolean>;
/**
* Cancels the connection for the editor
*/
cancelEditorConnection(owner: IConnectableInput): Thenable<boolean>;
showDashboard(connection: IConnectionProfile): Thenable<boolean>;
closeDashboard(uri: string): void;
getProviderIdFromUri(ownerUri: string): string;
hasRegisteredServers(): boolean;
canChangeConnectionConfig(profile: IConnectionProfile, newGroupID: string): boolean;
getTabColorForUri(uri: string): string;
/**
* Sends a notification that the language flavor for a given URI has changed.
* For SQL, this would be the specific SQL implementation being used.
*
* @param {string} uri the URI of the resource whose language has changed
* @param {string} language the base language
* @param {string} flavor the specific language flavor that's been set
*
* @memberof IConnectionManagementService
*/
doChangeLanguageFlavor(uri: string, language: string, flavor: string): void;
/**
* Ensures that a default language flavor is set for a URI, if none has already been defined.
* @param {string} uri document identifier
* @memberof ConnectionManagementService
*/
ensureDefaultLanguageFlavor(uri: string): void;
/**
* Refresh the IntelliSense cache for the connection with the given URI
*/
rebuildIntelliSenseCache(uri: string): Thenable<void>;
/**
* Get a copy of the connection profile with its passwords removed
* @param {IConnectionProfile} profile The connection profile to remove passwords from
* @returns {IConnectionProfile} A copy of the connection profile with passwords removed
*/
removeConnectionProfileCredentials(profile: IConnectionProfile): IConnectionProfile;
/**
* Get the credentials for a connected connection profile, as they would appear in the options dictionary
* @param {string} profileId The id of the connection profile to get the password for
* @returns {{ [name: string]: string }} A dictionary containing the credentials as they would be included
* in the connection profile's options dictionary, or undefined if the profile is not connected
*/
getActiveConnectionCredentials(profileId: string): { [name: string]: string };
/**
* Get the connection string for the provided connection ID
*/
getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
/**
* Serialize connection string with optional provider
*/
buildConnectionInfo(connectionString: string, provider?: string): Thenable<sqlops.ConnectionInfo>;
}
export const IConnectionDialogService = createDecorator<IConnectionDialogService>('connectionDialogService');
export interface IConnectionDialogService {
_serviceBrand: any;
/**
* Opens the connection dialog and returns the promise for successfully opening the dialog
* @param connectionManagementService
* @param params
* @param model
* @param connectionResult
*/
showDialog(connectionManagementService: IConnectionManagementService, params: INewConnectionParams, model: IConnectionProfile, connectionResult?: IConnectionResult): Thenable<void>;
/**
* Opens the connection dialog and returns the promise when connection is made
* or dialog is closed
* @param connectionManagementService
* @param params
* @param model
* @param connectionResult
*/
openDialogAndWait(connectionManagementService: IConnectionManagementService, params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): Thenable<IConnectionProfile>;
}
export enum RunQueryOnConnectionMode {
none = 0,
executeQuery = 1,
executeCurrentQuery = 2,
estimatedQueryPlan = 3,
actualQueryPlan = 4
}
export interface INewConnectionParams {
connectionType: ConnectionType;
input?: IConnectableInput;
runQueryOnCompletion?: RunQueryOnConnectionMode;
querySelection?: sqlops.ISelectionData;
showDashboard?: boolean;
providers?: string[];
}
export interface IConnectableInput {
uri: string;
onConnectStart(): void;
onConnectReject(error?: string): void;
onConnectSuccess(params?: INewConnectionParams): void;
onDisconnect(): void;
onConnectCanceled(): void;
}
export enum ConnectionType {
default = 0,
editor = 1
}
export enum MetadataType {
Table = 0,
View = 1,
SProc = 2,
Function = 3
}
export enum TaskStatus {
NotStarted = 0,
InProgress = 1,
Succeeded = 2,
SucceededWithWarning = 3,
Failed = 4,
Canceled = 5,
Canceling = 6
}
export interface IConnectionParams {
connectionUri: string;
connectionProfile: IConnectionProfile;
}

View File

@@ -0,0 +1,73 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import * as sqlops from 'sqlops';
import { StopWatch } from 'vs/base/common/stopwatch';
/**
* Information for a document's connection. Exported for testing purposes.
*/
export class ConnectionManagementInfo {
/**
* Connection GUID returned from the service host
*/
public connectionId: string;
public providerId: string;
/**
* Credentials used to connect
*/
public connectionProfile: ConnectionProfile;
/**
* Callback for when a connection notification is received.
*/
public connectHandler: (result: boolean, errorMessage?: string, errorCode?: number, callStack?: string) => void;
/**
* Information about the SQL Server instance.
*/
//public serverInfo: ConnectionContracts.ServerInfo;
/**
* Timer for tracking extension connection time.
*/
public extensionTimer: StopWatch;
/**
* Timer for tracking service connection time.
*/
public serviceTimer: StopWatch;
/**
* Timer for tracking intelliSense activation time.
*/
public intelliSenseTimer: StopWatch;
/**
* Whether the connection is in the process of connecting.
*/
public connecting: boolean;
/**
* Whether the connection should be deleted after connection is complete.
*/
public deleted: boolean;
/**
* Information about the connected server.
*/
serverInfo: sqlops.ServerInfo;
/**
* Owner uri assigned to the connection
*/
public ownerUri: string;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,232 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ConnectionProfileGroup } from './connectionProfileGroup';
import * as sqlops from 'sqlops';
import { ProviderConnectionInfo } from 'sql/platform/connection/common/providerConnectionInfo';
import * as interfaces from 'sql/platform/connection/common/interfaces';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { generateUuid } from 'vs/base/common/uuid';
import * as objects from 'sql/base/common/objects';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { isString } from 'vs/base/common/types';
// Concrete implementation of the IConnectionProfile interface
/**
* A concrete implementation of an IConnectionProfile with support for profile creation and validation
*/
export class ConnectionProfile extends ProviderConnectionInfo implements interfaces.IConnectionProfile {
public parent: ConnectionProfileGroup = null;
private _id: string;
public savePassword: boolean;
private _groupName: string;
public groupId: string;
public saveProfile: boolean;
public isDisconnecting: boolean = false;
public constructor(
capabilitiesService: ICapabilitiesService,
model: string | sqlops.IConnectionProfile
) {
super(capabilitiesService, model);
if (model && !isString(model)) {
this.groupId = model.groupId;
this.groupFullName = model.groupFullName;
this.savePassword = model.savePassword;
this.saveProfile = model.saveProfile;
this._id = model.id;
this.azureTenantId = model.azureTenantId;
} else {
//Default for a new connection
this.savePassword = false;
this.saveProfile = true;
this._groupName = ConnectionProfile.RootGroupName;
this._id = generateUuid();
}
this.options['groupId'] = this.groupId;
this.options['databaseDisplayName'] = this.databaseName;
}
public matches(other: interfaces.IConnectionProfile): boolean {
return other
&& this.providerName === other.providerName
&& equalsIgnoreCase(this.serverName, other.serverName)
&& equalsIgnoreCase(this.databaseName, other.databaseName)
&& equalsIgnoreCase(this.userName, other.userName)
&& equalsIgnoreCase(this.options['databaseDisplayName'], other.options['databaseDisplayName'])
&& this.authenticationType === other.authenticationType
&& this.groupId === other.groupId;
}
public generateNewId() {
this._id = generateUuid();
}
public getParent(): ConnectionProfileGroup {
return this.parent;
}
public get id(): string {
if (!this._id) {
this._id = generateUuid();
}
return this._id;
}
public set id(value: string) {
this._id = value;
}
public get azureTenantId(): string {
return this.options['azureTenantId'];
}
public set azureTenantId(value: string) {
this.options['azureTenantId'] = value;
}
public get groupFullName(): string {
return this._groupName;
}
public set groupFullName(value: string) {
this._groupName = value;
}
public get isAddedToRootGroup(): boolean {
return (this._groupName === ConnectionProfile.RootGroupName);
}
public clone(): ConnectionProfile {
let instance = new ConnectionProfile(this.capabilitiesService, this);
return instance;
}
public cloneWithNewId(): ConnectionProfile {
let instance = this.clone();
instance.generateNewId();
return instance;
}
public cloneWithDatabase(databaseName: string): ConnectionProfile {
let instance = this.cloneWithNewId();
instance.databaseName = databaseName;
return instance;
}
public static readonly RootGroupName: string = '/';
public withoutPassword(): ConnectionProfile {
let clone = this.clone();
clone.password = '';
return clone;
}
/**
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
* This key uniquely identifies a connection in a group
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
*/
public getOptionsKey(): string {
let id = super.getOptionsKey();
let databaseDisplayName: string = this.options['databaseDisplayName'];
if (databaseDisplayName) {
id += ProviderConnectionInfo.idSeparator + 'databaseDisplayName' + ProviderConnectionInfo.nameValueSeparator + databaseDisplayName;
}
return id + ProviderConnectionInfo.idSeparator + 'group' + ProviderConnectionInfo.nameValueSeparator + this.groupId;
}
/**
* Returns the unique id for the connection that doesn't include the group name
*/
public getConnectionInfoId(): string {
return super.getOptionsKey();
}
public toIConnectionProfile(): interfaces.IConnectionProfile {
let result: interfaces.IConnectionProfile = {
connectionName: this.connectionName,
serverName: this.serverName,
databaseName: this.databaseName,
authenticationType: this.authenticationType,
getOptionsKey: undefined,
matches: undefined,
groupId: this.groupId,
groupFullName: this.groupFullName,
password: this.password,
providerName: this.providerName,
savePassword: this.savePassword,
userName: this.userName,
options: this.options,
saveProfile: this.saveProfile,
id: this.id,
azureTenantId: this.azureTenantId
};
return result;
}
public toConnectionInfo(): sqlops.ConnectionInfo {
return {
options: this.options
};
}
public static fromIConnectionProfile(capabilitiesService: ICapabilitiesService, profile: sqlops.IConnectionProfile) {
if (profile) {
if (profile instanceof ConnectionProfile) {
return profile;
} else {
return new ConnectionProfile(capabilitiesService, profile);
}
}
return undefined;
}
public static createFromStoredProfile(profile: interfaces.IConnectionProfileStore, capabilitiesService: ICapabilitiesService): ConnectionProfile {
let connectionInfo = new ConnectionProfile(capabilitiesService, profile.providerName);
connectionInfo.options = profile.options;
// append group ID and original display name to build unique OE session ID
connectionInfo.options = objects.clone(profile.options);
connectionInfo.options['groupId'] = connectionInfo.groupId;
connectionInfo.options['databaseDisplayName'] = connectionInfo.databaseName;
connectionInfo.groupId = profile.groupId;
connectionInfo.providerName = profile.providerName;
connectionInfo.saveProfile = true;
connectionInfo.savePassword = profile.savePassword;
connectionInfo.id = profile.id || generateUuid();
return connectionInfo;
}
public static convertToProfileStore(
capabilitiesService: ICapabilitiesService,
connectionProfile: interfaces.IConnectionProfile): interfaces.IConnectionProfileStore {
if (connectionProfile) {
let connectionInfo = ConnectionProfile.fromIConnectionProfile(capabilitiesService, connectionProfile);
let profile: interfaces.IConnectionProfileStore = {
options: {},
groupId: connectionProfile.groupId,
providerName: connectionInfo.providerName,
savePassword: connectionInfo.savePassword,
id: connectionInfo.id
};
profile.options = connectionInfo.options;
return profile;
} else {
return undefined;
}
}
}

View File

@@ -0,0 +1,219 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ConnectionProfile } from './connectionProfile';
export interface IConnectionProfileGroup {
id: string;
parentId: string;
name: string;
color: string;
description: string;
}
export class ConnectionProfileGroup implements IConnectionProfileGroup {
public children: ConnectionProfileGroup[];
public connections: ConnectionProfile[];
public parentId: string;
private _isRenamed: boolean;
public constructor(
public name: string,
public parent: ConnectionProfileGroup,
public id: string,
public color: string,
public description: string
) {
this.parentId = parent ? parent.id : undefined;
if (this.name === ConnectionProfileGroup.RootGroupName) {
this.name = '';
}
}
public static GroupNameSeparator: string = '/';
public static RootGroupName: string = 'ROOT';
public toObject(): IConnectionProfileGroup {
let subgroups = undefined;
if (this.children) {
subgroups = [];
this.children.forEach((group) => {
subgroups.push(group.toObject());
});
}
return Object.assign({}, { name: this.name, id: this.id, parentId: this.parentId, children: subgroups, color: this.color, description: this.description });
}
public get groupName(): string {
return this.name;
}
public get fullName(): string {
let fullName: string = (this.id === 'root') ? undefined : this.name;
if (this.parent) {
let parentFullName = this.parent.fullName;
if (parentFullName) {
fullName = parentFullName + ConnectionProfileGroup.GroupNameSeparator + this.name;
}
}
return fullName;
}
public get isRenamed(): boolean {
return this._isRenamed;
}
public set isRenamed(val: boolean) {
this._isRenamed = val;
}
public hasChildren(): boolean {
if ((this.children && this.children.length > 0) || (this.connections && this.connections.length > 0)) {
return true;
}
return false;
}
/**
* Returns true if all connections in the tree have valid options using the correct capabilities
*/
public get hasValidConnections(): boolean {
if (this.connections) {
let invalidConnections = this.connections.find(c => !c.isConnectionOptionsValid);
if (invalidConnections !== undefined) {
return false;
} else {
let childrenAreValid: boolean = true;
this.children.forEach(element => {
let isChildValid = element.hasValidConnections;
if (!isChildValid) {
childrenAreValid = false;
}
});
return childrenAreValid;
}
} else {
return true;
}
}
public getChildren(): any {
let allChildren = [];
if (this.connections) {
this.connections.forEach((conn) => {
allChildren.push(conn);
});
}
if (this.children) {
this.children.forEach((group) => {
allChildren.push(group);
});
}
return allChildren;
}
public equals(other: any): boolean {
if (!(other instanceof ConnectionProfileGroup)) {
return false;
}
return other.id === this.id;
}
public addConnections(connections: ConnectionProfile[]): void {
if (!this.connections) {
this.connections = [];
}
connections.forEach((conn) => {
this.connections = this.connections.filter((curConn) => { return curConn.id !== conn.id; });
conn.parent = this;
this.connections.push(conn);
});
}
public addGroups(groups: ConnectionProfileGroup[]): void {
if (!this.children) {
this.children = [];
}
groups.forEach((group) => {
this.children = this.children.filter((grp) => { return group.id !== grp.id; });
group.parent = this;
this.children.push(group);
});
}
public getParent(): ConnectionProfileGroup {
return this.parent;
}
public isAncestorOf(node: ConnectionProfileGroup | ConnectionProfile): boolean {
let isAncestor = false;
let currentNode = node;
while (currentNode) {
if (currentNode.parent && currentNode.parent.id === this.id) {
isAncestor = true;
break;
}
currentNode = currentNode.parent;
}
return isAncestor;
}
public static getGroupFullNameParts(groupFullName: string): string[] {
groupFullName = groupFullName ? groupFullName : '';
let groupNames: string[] = groupFullName.split(ConnectionProfileGroup.GroupNameSeparator);
groupNames = groupNames.filter(g => !!g);
if (groupNames.length === 0) {
groupNames.push('ROOT');
} else if (groupNames[0].toUpperCase() !== 'ROOT') {
groupNames.unshift('ROOT');
}
groupNames[0] = 'ROOT';
return groupNames;
}
public static isRoot(name: string): boolean {
return (!name || name.toUpperCase() === ConnectionProfileGroup.RootGroupName ||
name === ConnectionProfileGroup.GroupNameSeparator);
}
public static sameGroupName(name1: string, name2: string): boolean {
let sameGroupName: boolean =
(!name1 && !name2) ||
name1.toUpperCase() === name2.toUpperCase() ||
(ConnectionProfileGroup.isRoot(name1) && ConnectionProfileGroup.isRoot(name2));
return sameGroupName;
}
public static getConnectionsInGroup(group: ConnectionProfileGroup): ConnectionProfile[] {
let connections = [];
if (group.connections) {
group.connections.forEach((con) => connections.push(con));
}
if (group.children) {
group.children.forEach((subgroup) => {
connections = connections.concat(this.getConnectionsInGroup(subgroup));
});
}
return connections;
}
public static getSubgroups(group: ConnectionProfileGroup): ConnectionProfileGroup[] {
let subgroups = [];
if (group && group.children) {
group.children.forEach((grp) => subgroups.push(grp));
group.children.forEach((subgroup) => {
subgroups = subgroups.concat(this.getSubgroups(subgroup));
});
}
return subgroups;
}
}

View File

@@ -0,0 +1,210 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ConnectionManagementInfo } from './connectionManagementInfo';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { IConnectionProfile } from './interfaces';
import * as Utils from './utils';
import * as sqlops from 'sqlops';
import { StopWatch } from 'vs/base/common/stopwatch';
export class ConnectionStatusManager {
private _connections: { [id: string]: ConnectionManagementInfo };
private _providerCapabilitiesMap: { [providerName: string]: sqlops.DataProtocolServerCapabilities };
constructor( @ICapabilitiesService private _capabilitiesService: ICapabilitiesService) {
this._connections = {};
this._providerCapabilitiesMap = {};
}
public findConnection(uri: string): ConnectionManagementInfo {
if (uri in this._connections) {
return this._connections[uri];
} else {
return undefined;
}
}
public findConnectionByProfileId(profileId: string): ConnectionManagementInfo {
return Object.values(this._connections).find((connection: ConnectionManagementInfo) => connection.connectionProfile.id === profileId);
}
public findConnectionProfile(connectionProfile: IConnectionProfile): ConnectionManagementInfo {
let id = Utils.generateUri(connectionProfile);
return this.findConnection(id);
}
public hasConnection(id: string): Boolean {
return !!this.findConnection(id);
}
public deleteConnection(id: string): void {
let info = this.findConnection(id);
if (info) {
for (let key in this._connections) {
if (this._connections[key].connectionId === info.connectionId) {
if (this._connections[key].connecting) {
this._connections[key].deleted = true;
} else {
delete this._connections[key];
}
}
}
}
}
public getConnectionProfile(id: string): ConnectionProfile {
let connectionInfoForId = this.findConnection(id);
return connectionInfoForId ? connectionInfoForId.connectionProfile : undefined;
}
public addConnection(connection: IConnectionProfile, id: string): ConnectionManagementInfo {
// Always create a copy and save that in the list
let connectionProfile = new ConnectionProfile(this._capabilitiesService, connection);
let connectionInfo: ConnectionManagementInfo = new ConnectionManagementInfo();
connectionInfo.providerId = connection.providerName;
connectionInfo.extensionTimer = StopWatch.create();
connectionInfo.intelliSenseTimer = StopWatch.create();
connectionInfo.connectionProfile = connectionProfile;
connectionInfo.connecting = true;
this._connections[id] = connectionInfo;
connectionInfo.serviceTimer = StopWatch.create();
connectionInfo.ownerUri = id;
return connectionInfo;
}
/**
*
* @param uri Remove connection from list of active connections
*/
public removeConnection(uri: string) {
delete this._connections[uri];
}
/**
* Call after a connection is saved to settings. It's only for default url connections
* which their id is generated from connection options. The group id is used in the generated id.
* when the connection is stored, the group id get assigned to the profile and it can change the id
* So for those kind of connections, we need to add the new id and the connection
*/
public updateConnectionProfile(connection: IConnectionProfile, id: string): string {
let newId: string = id;
let connectionInfo: ConnectionManagementInfo = this._connections[id];
if (connectionInfo && connection) {
if (this.isDefaultTypeUri(id)) {
connectionInfo.connectionProfile.groupId = connection.groupId;
newId = Utils.generateUri(connection);
if (newId !== id) {
this.deleteConnection(id);
this._connections[newId] = connectionInfo;
}
}
connectionInfo.connectionProfile.id = connection.id;
}
return newId;
}
public onConnectionComplete(summary: sqlops.ConnectionInfoSummary): ConnectionManagementInfo {
let connection = this._connections[summary.ownerUri];
connection.serviceTimer.stop();
connection.connecting = false;
connection.connectionId = summary.connectionId;
connection.serverInfo = summary.serverInfo;
return connection;
}
/**
* Updates database name after connection is complete
* @param summary connection summary
*/
public updateDatabaseName(summary: sqlops.ConnectionInfoSummary): void {
let connection = this._connections[summary.ownerUri];
//Check if the existing connection database name is different the one in the summary
if (connection.connectionProfile.databaseName !== summary.connectionSummary.databaseName) {
//Add the ownerUri with database name to the map if not already exists
connection.connectionProfile.databaseName = summary.connectionSummary.databaseName;
let prefix = Utils.getUriPrefix(summary.ownerUri);
let ownerUriWithDbName = Utils.generateUriWithPrefix(connection.connectionProfile, prefix);
if (!(ownerUriWithDbName in this._connections)) {
this._connections[ownerUriWithDbName] = connection;
}
}
}
/**
* Tries to find an existing connection that's mapped with the given ownerUri
* The purpose for this method is to find the connection given the ownerUri and find the original uri assigned to it. most of the times should be the same.
* Only if the db name in the original uri is different when connection is complete, we need to use the original uri
* Returns the generated ownerUri for the connection profile if not existing connection found
* @param ownerUri connection owner uri to find an existing connection
* @param purpose purpose for the connection
*/
public getOriginalOwnerUri(ownerUri: string): string {
let ownerUriToReturn: string = ownerUri;
let connectionStatusInfo = this.findConnection(ownerUriToReturn);
if (connectionStatusInfo && connectionStatusInfo.ownerUri) {
//The ownerUri in the connection status is the one service knows about so use that
//To call the service for any operation
ownerUriToReturn = connectionStatusInfo.ownerUri;
}
return ownerUriToReturn;
}
public onConnectionChanged(changedConnInfo: sqlops.ChangedConnectionInfo): IConnectionProfile {
let connection = this._connections[changedConnInfo.connectionUri];
if (connection && connection.connectionProfile) {
connection.connectionProfile.serverName = changedConnInfo.connection.serverName;
connection.connectionProfile.databaseName = changedConnInfo.connection.databaseName;
connection.connectionProfile.userName = changedConnInfo.connection.userName;
return connection.connectionProfile;
}
return undefined;
}
public isConnected(id: string): boolean {
return (id in this._connections && this._connections[id].connectionId && !!this._connections[id].connectionId);
}
public isConnecting(id: string): boolean {
return (id in this._connections && this._connections[id].connecting);
}
public isDefaultTypeUri(uri: string): boolean {
return uri && uri.startsWith(Utils.uriPrefixes.default);
}
public getProviderIdFromUri(ownerUri: string): string {
let providerId: string = '';
let connection = this.findConnection(ownerUri);
if (connection) {
providerId = connection.connectionProfile.providerName;
}
if (!providerId && this.isDefaultTypeUri(ownerUri)) {
let optionsKey = ownerUri.replace(Utils.uriPrefixes.default, '');
providerId = ConnectionProfile.getProviderFromOptionsKey(optionsKey);
}
return providerId;
}
/**
* Get a list of the active connection profiles managed by the status manager
*/
public getActiveConnectionProfiles(providers?: string[]): ConnectionProfile[] {
let profiles = Object.values(this._connections).map((connectionInfo: ConnectionManagementInfo) => connectionInfo.connectionProfile);
// Remove duplicate profiles that may be listed multiple times under different URIs by filtering for profiles that don't have the same ID as an earlier profile in the list
profiles = profiles.filter((profile, index) => profiles.findIndex(otherProfile => otherProfile.id === profile.id) === index);
if (providers) {
profiles = profiles.filter(f => providers.includes(f.providerName));
}
return profiles;
}
}

View File

@@ -0,0 +1,564 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as Constants from './constants';
import * as ConnInfo from './connectionInfo';
import { ConnectionProfile } from '../common/connectionProfile';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { ICredentialsService } from 'sql/platform/credentials/common/credentialsService';
import { IConnectionConfig } from './iconnectionConfig';
import { ConnectionConfig } from './connectionConfig';
import { Memento, Scope as MementoScope } from 'vs/workbench/common/memento';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ConnectionProfileGroup, IConnectionProfileGroup } from './connectionProfileGroup';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as sqlops from 'sqlops';
const MAX_CONNECTIONS_DEFAULT = 25;
/**
* Manages the connections list including saved profiles and the most recently used connections
*
* @export
* @class ConnectionStore
*/
export class ConnectionStore {
private _memento: any;
private _groupIdToFullNameMap: { [groupId: string]: string };
private _groupFullNameToIdMap: { [groupId: string]: string };
constructor(
private _storageService: IStorageService,
private _context: Memento,
private _configurationEditService: ConfigurationEditingService,
private _workspaceConfigurationService: IWorkspaceConfigurationService,
private _credentialService: ICredentialsService,
private _capabilitiesService: ICapabilitiesService,
private _connectionConfig?: IConnectionConfig
) {
if (_context) {
this._memento = this._context.getMemento(this._storageService, MementoScope.GLOBAL);
}
this._groupIdToFullNameMap = {};
this._groupFullNameToIdMap = {};
if (!this._connectionConfig) {
this._connectionConfig = new ConnectionConfig(this._configurationEditService,
this._workspaceConfigurationService, this._capabilitiesService);
}
}
public static get CRED_PREFIX(): string { return 'Microsoft.SqlTools'; }
public static get CRED_SEPARATOR(): string { return '|'; }
public static get CRED_ID_PREFIX(): string { return 'id:'; }
public static get CRED_ITEMTYPE_PREFIX(): string { return 'itemtype:'; }
public static get CRED_PROFILE_USER(): string { return 'Profile'; }
public formatCredentialIdForCred(connectionProfile: IConnectionProfile): string {
if (!connectionProfile) {
throw new Error('Missing Connection which is required');
}
let itemTypeString: string = ConnectionStore.CRED_PROFILE_USER;
return this.formatCredentialId(connectionProfile, itemTypeString);
}
/**
* Creates a formatted credential usable for uniquely identifying a SQL Connection.
* This string can be decoded but is not optimized for this.
* @static
* @param {IConnectionProfile} connectionProfile connection profile - require
* @param {string} itemType type of the item (MRU or Profile) - optional
* @returns {string} formatted string with server, DB and username
*/
public formatCredentialId(connectionProfile: IConnectionProfile, itemType?: string): string {
let connectionProfileInstance: ConnectionProfile = ConnectionProfile.fromIConnectionProfile(
this._capabilitiesService, connectionProfile);
if (!connectionProfileInstance.getConnectionInfoId()) {
throw new Error('Missing Id, which is required');
}
let cred: string[] = [ConnectionStore.CRED_PREFIX];
if (!itemType) {
itemType = ConnectionStore.CRED_PROFILE_USER;
}
ConnectionStore.pushIfNonEmpty(itemType, ConnectionStore.CRED_ITEMTYPE_PREFIX, cred);
ConnectionStore.pushIfNonEmpty(connectionProfileInstance.getConnectionInfoId(), ConnectionStore.CRED_ID_PREFIX, cred);
return cred.join(ConnectionStore.CRED_SEPARATOR);
}
private static pushIfNonEmpty(value: string, prefix: string, arr: string[]): void {
if (value) {
arr.push(prefix.concat(value));
}
}
/**
* Returns true if the password is required
* @param connection profile
*/
public isPasswordRequired(connection: IConnectionProfile): boolean {
if (connection) {
let connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, connection);
return connectionProfile.isPasswordRequired();
} else {
return false;
}
}
public addSavedPassword(credentialsItem: IConnectionProfile): Promise<{ profile: IConnectionProfile, savedCred: boolean }> {
let self = this;
return new Promise<{ profile: IConnectionProfile, savedCred: boolean }>((resolve, reject) => {
if (credentialsItem.savePassword && this.isPasswordRequired(credentialsItem)
&& !credentialsItem.password) {
let credentialId = this.formatCredentialIdForCred(credentialsItem);
self._credentialService.readCredential(credentialId)
.then(savedCred => {
if (savedCred) {
credentialsItem.password = savedCred.password;
credentialsItem.options['password'] = savedCred.password;
}
resolve({ profile: credentialsItem, savedCred: !!savedCred });
},
reason => {
reject(reason);
});
} else {
// No need to look up the password
resolve({ profile: credentialsItem, savedCred: credentialsItem.savePassword });
}
});
}
/**
* Saves a connection profile to the user settings.
* Password values are stored to a separate credential store if the "savePassword" option is true
*
* @param {IConnectionProfile} profile the profile to save
* @param {forceWritePlaintextPassword} whether the plaintext password should be written to the settings file
* @returns {Promise<IConnectionProfile>} a Promise that returns the original profile, for help in chaining calls
*/
public saveProfile(profile: IConnectionProfile, forceWritePlaintextPassword?: boolean): Promise<IConnectionProfile> {
const self = this;
return new Promise<IConnectionProfile>((resolve, reject) => {
// Add the profile to the saved list, taking care to clear out the password field if necessary
let savedProfile: IConnectionProfile;
if (forceWritePlaintextPassword) {
savedProfile = profile;
} else {
savedProfile = this.getProfileWithoutPassword(profile);
}
self.saveProfileToConfig(savedProfile)
.then(savedConnectionProfile => {
profile.groupId = savedConnectionProfile.groupId;
profile.id = savedConnectionProfile.id;
// Only save if we successfully added the profile
return self.saveProfilePasswordIfNeeded(profile);
// And resolve / reject at the end of the process
}, err => {
reject(err);
}).then(resolved => {
// Add necessary default properties before returning
// this is needed to support immediate connections
ConnInfo.fixupConnectionCredentials(profile);
resolve(profile);
}, err => {
reject(err);
});
});
}
/**
* Saves a connection profile group to the user settings.
*
* @param {IConnectionProfileGroup} profile the profile group to save
* @returns {Promise<string>} a Promise that returns the id of connection group
*/
public saveProfileGroup(profile: IConnectionProfileGroup): Promise<string> {
const self = this;
return new Promise<string>((resolve, reject) => {
self._connectionConfig.addGroup(profile).then(groupId => {
resolve(groupId);
}).catch(error => {
reject(error);
});
});
}
private saveProfileToConfig(profile: IConnectionProfile): Promise<IConnectionProfile> {
const self = this;
return new Promise<IConnectionProfile>((resolve, reject) => {
if (profile.saveProfile) {
self._connectionConfig.addConnection(profile).then(savedProfile => {
resolve(savedProfile);
}).catch(error => {
reject(error);
});
} else {
resolve(profile);
}
});
}
/**
* Gets the list of recently used connections. These will not include the password - a separate call to
* {addSavedPassword} is needed to fill that before connecting
*
* @returns {sqlops.ConnectionInfo} the array of connections, empty if none are found
*/
public getRecentlyUsedConnections(providers?: string[]): ConnectionProfile[] {
let configValues: IConnectionProfile[] = this._memento[Constants.recentConnections];
if (!configValues) {
configValues = [];
}
configValues = configValues.filter(c => !!(c));
if (providers && providers.length > 0) {
configValues = configValues.filter(c => providers.includes(c.providerName));
}
return this.convertConfigValuesToConnectionProfiles(configValues);
}
private convertConfigValuesToConnectionProfiles(configValues: IConnectionProfile[]): ConnectionProfile[] {
return configValues.map(c => {
if (c) {
let connectionProfile = new ConnectionProfile(this._capabilitiesService, c);
if (connectionProfile.saveProfile) {
if (!connectionProfile.groupFullName && connectionProfile.groupId) {
connectionProfile.groupFullName = this.getGroupFullName(connectionProfile.groupId);
}
if (!connectionProfile.groupId && connectionProfile.groupFullName) {
connectionProfile.groupId = this.getGroupId(connectionProfile.groupFullName);
} else if (!connectionProfile.groupId && !connectionProfile.groupFullName) {
connectionProfile.groupId = this.getGroupId('');
}
}
return connectionProfile;
} else {
return undefined;
}
});
}
/**
* Gets the list of active connections. These will not include the password - a separate call to
* {addSavedPassword} is needed to fill that before connecting
*
* @returns {sqlops.ConnectionInfo} the array of connections, empty if none are found
*/
public getActiveConnections(): ConnectionProfile[] {
let configValues: IConnectionProfile[] = this._memento[Constants.activeConnections];
if (!configValues) {
configValues = [];
}
return this.convertConfigValuesToConnectionProfiles(configValues);
}
public getProfileWithoutPassword(conn: IConnectionProfile): ConnectionProfile {
if (conn) {
let savedConn: ConnectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, conn);
savedConn = savedConn.withoutPassword();
return savedConn;
} else {
return undefined;
}
}
/**
* Adds a connection to the active connections list.
* Connection is only added if there are no other connections with the same connection ID in the list.
* Password values are stored to a separate credential store if the "savePassword" option is true
*
* @param {IConnectionCredentials} conn the connection to add
* @returns {Promise<void>} a Promise that returns when the connection was saved
*/
public addActiveConnection(conn: IConnectionProfile, isConnectionToDefaultDb: boolean = false): Promise<void> {
if (this.getActiveConnections().some(existingConn => existingConn.id === conn.id)) {
return Promise.resolve(undefined);
} else {
return this.addConnectionToMemento(conn, Constants.activeConnections, undefined, conn.savePassword).then(() => {
let maxConnections = this.getMaxRecentConnectionsCount();
if (isConnectionToDefaultDb) {
conn.databaseName = '';
}
return this.addConnectionToMemento(conn, Constants.recentConnections, maxConnections);
});
}
}
public addConnectionToMemento(conn: IConnectionProfile, mementoKey: string, maxConnections?: number, savePassword?: boolean): Promise<void> {
const self = this;
return new Promise<void>((resolve, reject) => {
// Get all profiles
let configValues = self.getConnectionsFromMemento(mementoKey);
let configToSave = this.addToConnectionList(conn, configValues);
if (maxConnections) {
// Remove last element if needed
if (configToSave.length > maxConnections) {
configToSave = configToSave.slice(0, maxConnections);
}
}
self._memento[mementoKey] = configToSave;
if (savePassword) {
self.doSavePassword(conn).then(result => {
resolve(undefined);
});
} else {
resolve(undefined);
}
});
}
public removeConnectionToMemento(conn: IConnectionProfile, mementoKey: string): Promise<void> {
const self = this;
return new Promise<void>((resolve, reject) => {
// Get all profiles
let configValues = self.getConnectionsFromMemento(mementoKey);
let configToSave = this.removeFromConnectionList(conn, configValues);
self._memento[mementoKey] = configToSave;
resolve(undefined);
});
}
public getConnectionsFromMemento(mementoKey: string): ConnectionProfile[] {
let configValues: IConnectionProfile[] = this._memento[mementoKey];
if (!configValues) {
configValues = [];
}
return this.convertConfigValuesToConnectionProfiles(configValues);
}
private addToConnectionList(conn: IConnectionProfile, list: ConnectionProfile[]): IConnectionProfile[] {
let savedProfile: ConnectionProfile = this.getProfileWithoutPassword(conn);
// Remove the connection from the list if it already exists
list = list.filter(value => {
let equal = value && value.getConnectionInfoId() === savedProfile.getConnectionInfoId();
if (equal && savedProfile.saveProfile) {
equal = value.groupId === savedProfile.groupId ||
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
}
return !equal;
});
list.unshift(savedProfile);
let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile;
});
return newList.filter(n => n !== undefined);
}
private removeFromConnectionList(conn: IConnectionProfile, list: ConnectionProfile[]): IConnectionProfile[] {
let savedProfile: ConnectionProfile = this.getProfileWithoutPassword(conn);
// Remove the connection from the list if it already exists
list = list.filter(value => {
let equal = value && value.getConnectionInfoId() === savedProfile.getConnectionInfoId();
if (equal && savedProfile.saveProfile) {
equal = value.groupId === savedProfile.groupId ||
ConnectionProfileGroup.sameGroupName(value.groupFullName, savedProfile.groupFullName);
}
return !equal;
});
let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile;
});
return newList.filter(n => n !== undefined);
}
/**
* Clear all recently used connections from the MRU list.
*/
public clearRecentlyUsed(): void {
this._memento[Constants.recentConnections] = [];
}
public clearFromMemento(name: string): void {
this._memento[name] = [];
}
/**
* Clear all active connections from the MRU list.
*/
public clearActiveConnections(): void {
this._memento[Constants.activeConnections] = [];
}
/**
* Remove a connection profile from the active connections list.
*/
public removeActiveConnection(conn: IConnectionProfile): Promise<void> {
return this.removeConnectionToMemento(conn, Constants.activeConnections);
}
private saveProfilePasswordIfNeeded(profile: IConnectionProfile): Promise<boolean> {
if (!profile.savePassword) {
return Promise.resolve(true);
}
return this.doSavePassword(profile);
}
private doSavePassword(conn: IConnectionProfile): Promise<boolean> {
let self = this;
return new Promise<boolean>((resolve, reject) => {
if (conn.password) {
let credentialId = this.formatCredentialId(conn);
self._credentialService.saveCredential(credentialId, conn.password)
.then((result) => {
resolve(result);
}, reason => {
// Bubble up error if there was a problem executing the set command
reject(reason);
});
} else {
resolve(true);
}
});
}
public getConnectionProfileGroups(withoutConnections?: boolean, providers?: string[]): ConnectionProfileGroup[] {
let profilesInConfiguration: ConnectionProfile[];
if (!withoutConnections) {
profilesInConfiguration = this._connectionConfig.getConnections(true);
if (providers && providers.length > 0) {
profilesInConfiguration = profilesInConfiguration.filter(x => providers.includes(x.providerName));
}
}
let groups = this._connectionConfig.getAllGroups();
let connectionProfileGroups = this.convertToConnectionGroup(groups, profilesInConfiguration, undefined);
return connectionProfileGroups;
}
private convertToConnectionGroup(groups: IConnectionProfileGroup[], connections: ConnectionProfile[], parent: ConnectionProfileGroup = undefined): ConnectionProfileGroup[] {
let result: ConnectionProfileGroup[] = [];
let children = groups.filter(g => g.parentId === (parent ? parent.id : undefined));
if (children) {
children.map(group => {
let connectionGroup = new ConnectionProfileGroup(group.name, parent, group.id, group.color, group.description);
this.addGroupFullNameToMap(group.id, connectionGroup.fullName);
if (connections) {
let connectionsForGroup = connections.filter(conn => conn.groupId === connectionGroup.id);
var conns = [];
connectionsForGroup.forEach((conn) => {
conn.groupFullName = connectionGroup.fullName;
conns.push(conn);
});
connectionGroup.addConnections(conns);
}
let childrenGroups = this.convertToConnectionGroup(groups, connections, connectionGroup);
connectionGroup.addGroups(childrenGroups);
result.push(connectionGroup);
});
if (parent) {
parent.addGroups(result);
}
}
return result;
}
public getGroupFromId(groupId: string): IConnectionProfileGroup {
let groups = this._connectionConfig.getAllGroups();
return groups.find(group => group.id === groupId);
}
private getMaxRecentConnectionsCount(): number {
let config = this._workspaceConfigurationService.getValue(Constants.sqlConfigSectionName);
let maxConnections: number = config[Constants.configMaxRecentConnections];
if (typeof (maxConnections) !== 'number' || maxConnections <= 0) {
maxConnections = MAX_CONNECTIONS_DEFAULT;
}
return maxConnections;
}
public editGroup(group: ConnectionProfileGroup): Promise<any> {
const self = this;
return new Promise<string>((resolve, reject) => {
self._connectionConfig.editGroup(group).then(() => {
resolve(null);
}).catch(error => {
reject(error);
});
});
}
public deleteConnectionFromConfiguration(connection: ConnectionProfile): Promise<void> {
return this._connectionConfig.deleteConnection(connection);
}
public deleteGroupFromConfiguration(group: ConnectionProfileGroup): Promise<void> {
return this._connectionConfig.deleteGroup(group);
}
public changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void> {
return this._connectionConfig.changeGroupIdForConnectionGroup(source, target);
}
public canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean {
return this._connectionConfig.canChangeConnectionConfig(profile, newGroupID);
}
public changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
this._connectionConfig.changeGroupIdForConnection(source, targetGroupId).then(() => {
resolve();
}, (error => {
reject(error);
}));
});
}
private addGroupFullNameToMap(groupId: string, groupFullName: string): void {
if (groupId) {
this._groupIdToFullNameMap[groupId] = groupFullName;
}
if (groupFullName !== undefined) {
this._groupFullNameToIdMap[groupFullName.toUpperCase()] = groupId;
}
}
private getGroupFullName(groupId: string): string {
if (groupId in this._groupIdToFullNameMap) {
return this._groupIdToFullNameMap[groupId];
} else {
// Load the cache
this.getConnectionProfileGroups(true);
}
return this._groupIdToFullNameMap[groupId];
}
private getGroupId(groupFullName: string): string {
if (groupFullName === ConnectionProfileGroup.GroupNameSeparator) {
groupFullName = '';
}
let key = groupFullName.toUpperCase();
let result: string = '';
if (key in this._groupFullNameToIdMap) {
result = this._groupFullNameToIdMap[key];
} else {
// Load the cache
this.getConnectionProfileGroups(true);
result = this._groupFullNameToIdMap[key];
}
return result;
}
}

View File

@@ -0,0 +1,37 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// constants
export const sqlConfigSectionName = 'sql';
export const outputChannelName = 'MSSQL';
export const connectionsArrayName = 'datasource.connections';
export const connectionGroupsArrayName = 'datasource.connectionGroups';
/**Unsaved connections Id */
export const unsavedGroupId = 'unsaved';
/* Memento constants */
export const activeConnections = 'ACTIVE_CONNECTIONS';
export const recentConnections = 'RECENT_CONNECTIONS';
export const capabilitiesOptions = 'OPTIONS_METADATA';
export const configMaxRecentConnections = 'maxRecentConnections';
export const mssqlProviderName = 'MSSQL';
export const anyProviderName = '*';
export const connectionProviderContextKey = 'connectionProvider';
export const applicationName = 'sqlops';
export const defaultEngine = 'defaultEngine';
export const passwordChars = '***************';
/* authentication types */
export const sqlLogin = 'SqlLogin';
export const integrated = 'Integrated';
export const azureMFA = 'AzureMFA';

View File

@@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IConnectionProfile } from './interfaces';
import { IConnectionProfileGroup, ConnectionProfileGroup } from './connectionProfileGroup';
import { ConnectionProfile } from './connectionProfile';
import * as sqlops from 'sqlops';
/**
* Interface for a configuration file that stores connection profiles.
*
* @export
* @interface IConnectionConfig
*/
export interface IConnectionConfig {
addConnection(profile: IConnectionProfile): Promise<IConnectionProfile>;
addGroup(profileGroup: IConnectionProfileGroup): Promise<string>;
getConnections(getWorkspaceConnections: boolean): ConnectionProfile[];
getAllGroups(): IConnectionProfileGroup[];
changeGroupIdForConnectionGroup(source: ConnectionProfileGroup, target: ConnectionProfileGroup): Promise<void>;
changeGroupIdForConnection(source: ConnectionProfile, targetGroupId: string): Promise<void>;
editGroup(group: ConnectionProfileGroup): Promise<void>;
deleteConnection(profile: ConnectionProfile): Promise<void>;
deleteGroup(group: ConnectionProfileGroup): Promise<void>;
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean;
}

View File

@@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
export interface IConnectionProfile extends sqlops.IConnectionProfile {
getOptionsKey(): string;
matches(profile: sqlops.IConnectionProfile): boolean;
}
export interface IConnectionProfileStore {
options: {};
groupId: string;
providerName: string;
savePassword: boolean;
id: string;
}

View File

@@ -0,0 +1,305 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Disposable } from 'vs/base/common/lifecycle';
import { isString } from 'vs/base/common/types';
import * as sqlops from 'sqlops';
import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as Constants from 'sql/platform/connection/common/constants';
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/common/connectionProviderExtension';
export class ProviderConnectionInfo extends Disposable implements sqlops.ConnectionInfo {
options: { [name: string]: any } = {};
private _providerName: string;
protected _serverCapabilities: ConnectionProviderProperties;
private static readonly SqlAuthentication = 'SqlLogin';
public static readonly ProviderPropertyName = 'providerName';
public constructor(
protected capabilitiesService: ICapabilitiesService,
model: string | sqlops.IConnectionProfile
) {
super();
// we can't really do a whole lot if we don't have a provider
if (isString(model) || (model && model.providerName)) {
this.providerName = isString(model) ? model : model.providerName;
if (!isString(model)) {
if (model.options && this._serverCapabilities) {
this._serverCapabilities.connectionOptions.forEach(option => {
let value = model.options[option.name];
this.options[option.name] = value;
});
}
this.serverName = model.serverName;
this.authenticationType = model.authenticationType;
this.databaseName = model.databaseName;
this.password = model.password;
this.userName = model.userName;
this.connectionName = model.connectionName;
}
}
}
public get providerName(): string {
return this._providerName;
}
public set providerName(name: string) {
this._providerName = name;
if (!this._serverCapabilities) {
let capabilities = this.capabilitiesService.getCapabilities(this.providerName);
if (capabilities) {
this._serverCapabilities = capabilities.connection;
}
this._register(this.capabilitiesService.onCapabilitiesRegistered(e => {
if (e.connection.providerId === this.providerName) {
this._serverCapabilities = e.connection;
}
}));
}
}
public clone(): ProviderConnectionInfo {
let instance = new ProviderConnectionInfo(this.capabilitiesService, this.providerName);
instance.options = Object.assign({}, this.options);
return instance;
}
public get serverCapabilities(): ConnectionProviderProperties {
return this._serverCapabilities;
}
public get connectionName(): string {
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.connectionName);
}
public get serverName(): string {
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.serverName);
}
public get databaseName(): string {
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.databaseName);
}
public get userName(): string {
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.userName);
}
public get password(): string {
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.password);
}
public get authenticationType(): string {
return this.getSpecialTypeOptionValue(ConnectionOptionSpecialType.authType);
}
public set connectionName(value: string) {
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.connectionName, value);
}
public set serverName(value: string) {
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.serverName, value);
}
public set databaseName(value: string) {
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.databaseName, value);
}
public set userName(value: string) {
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.userName, value);
}
public set password(value: string) {
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.password, value);
}
public set authenticationType(value: string) {
this.setSpecialTypeOptionName(ConnectionOptionSpecialType.authType, value);
}
public getOptionValue(name: string): any {
return this.options[name];
}
public setOptionValue(name: string, value: any): void {
//TODO: validate
this.options[name] = value;
}
private getServerInfo() {
let databaseName = this.databaseName ? this.databaseName : '<default>';
let userName = this.userName ? this.userName : 'Windows Authentication';
return this.serverName + ', ' + databaseName + ' (' + userName + ')';
}
/**
* Returns the title of the connection
*/
public get title(): string {
let label = '';
if (this.connectionName) {
label = this.connectionName;
} else {
label = this.getServerInfo();
}
return label;
}
public get serverInfo(): string {
return this.getServerInfo();
}
/**
* Returns true if the capabilities and options are loaded correctly
*/
public get isConnectionOptionsValid(): boolean {
return this.serverCapabilities && this.title.indexOf('undefined') < 0;
}
public isPasswordRequired(): boolean {
let optionMetadata = this._serverCapabilities.connectionOptions.find(
option => option.specialValueType === ConnectionOptionSpecialType.password);
let isPasswordRequired: boolean = optionMetadata.isRequired;
if (this.providerName === Constants.mssqlProviderName) {
isPasswordRequired = this.authenticationType === ProviderConnectionInfo.SqlAuthentication && optionMetadata.isRequired;
}
return isPasswordRequired;
}
private getSpecialTypeOptionValue(type: string): string {
let name = this.getSpecialTypeOptionName(type);
if (name) {
return this.options[name];
}
return undefined;
}
/**
* Returns a key derived the connections options (providerName, authenticationType, serverName, databaseName, userName, groupid)
* This key uniquely identifies a connection in a group
* Example: "providerName:MSSQL|authenticationType:|databaseName:database|serverName:server3|userName:user|group:testid"
*/
public getOptionsKey(): string {
let idNames = [];
if (this._serverCapabilities) {
idNames = this._serverCapabilities.connectionOptions.map(o => {
if ((o.specialValueType || o.isIdentity)
&& o.specialValueType !== ConnectionOptionSpecialType.password
&& o.specialValueType !== ConnectionOptionSpecialType.connectionName) {
return o.name;
} else {
return undefined;
}
});
} else {
// This should never happen but just incase the serverCapabilities was not ready at this time
idNames = ['authenticationType', 'database', 'server', 'user'];
}
idNames = idNames.filter(x => x !== undefined);
//Sort to make sure using names in the same order every time otherwise the ids would be different
idNames.sort();
let idValues: string[] = [];
for (var index = 0; index < idNames.length; index++) {
let value = this.options[idNames[index]];
value = value ? value : '';
idValues.push(`${idNames[index]}${ProviderConnectionInfo.nameValueSeparator}${value}`);
}
return ProviderConnectionInfo.ProviderPropertyName + ProviderConnectionInfo.nameValueSeparator +
this.providerName + ProviderConnectionInfo.idSeparator + idValues.join(ProviderConnectionInfo.idSeparator);
}
public static getProviderFromOptionsKey(optionsKey: string) {
let providerId: string = '';
if (optionsKey) {
let ids: string[] = optionsKey.split(ProviderConnectionInfo.idSeparator);
ids.forEach(id => {
let idParts = id.split(ProviderConnectionInfo.nameValueSeparator);
if (idParts.length >= 2 && idParts[0] === ProviderConnectionInfo.ProviderPropertyName) {
providerId = idParts[1];
}
});
}
return providerId;
}
public getSpecialTypeOptionName(type: string): string {
if (this._serverCapabilities) {
let optionMetadata = this._serverCapabilities.connectionOptions.find(o => o.specialValueType === type);
return !!optionMetadata ? optionMetadata.name : undefined;
} else {
return type.toString();
}
}
public setSpecialTypeOptionName(type: string, value: string): void {
let name = this.getSpecialTypeOptionName(type);
if (!!name) {
this.options[name] = value;
}
}
public get authenticationTypeDisplayName(): string {
let optionMetadata = this._serverCapabilities.connectionOptions.find(o => o.specialValueType === ConnectionOptionSpecialType.authType);
let authType = this.authenticationType;
let displayName: string = authType;
if (optionMetadata && optionMetadata.categoryValues) {
optionMetadata.categoryValues.forEach(element => {
if (element.name === authType) {
displayName = element.displayName;
}
});
}
return displayName;
}
public getProviderOptions(): sqlops.ConnectionOption[] {
return this._serverCapabilities.connectionOptions;
}
public static get idSeparator(): string {
return '|';
}
public static get nameValueSeparator(): string {
return ':';
}
public get titleParts(): string[] {
let parts: string[] = [];
// Always put these three on top. TODO: maybe only for MSSQL?
parts.push(this.serverName);
parts.push(this.databaseName);
parts.push(this.authenticationTypeDisplayName);
this._serverCapabilities.connectionOptions.forEach(element => {
if (element.specialValueType !== ConnectionOptionSpecialType.serverName &&
element.specialValueType !== ConnectionOptionSpecialType.databaseName &&
element.specialValueType !== ConnectionOptionSpecialType.authType &&
element.specialValueType !== ConnectionOptionSpecialType.password &&
element.specialValueType !== ConnectionOptionSpecialType.connectionName &&
element.isIdentity && element.valueType === ServiceOptionType.string) {
let value = this.getOptionValue(element.name);
if (value) {
parts.push(value);
}
}
});
return parts;
}
}

View File

@@ -0,0 +1,139 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { IConnectionProfile } from './interfaces';
import { ConnectionProfile } from './connectionProfile';
import { ConnectionProfileGroup } from './connectionProfileGroup';
// CONSTANTS //////////////////////////////////////////////////////////////////////////////////////
const msInH = 3.6e6;
const msInM = 60000;
const msInS = 1000;
export const uriPrefixes = {
default: 'connection://',
connection: 'connection://',
dashboard: 'dashboard://',
insights: 'insights://'
};
// FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////
export const defaultGroupId = 'C777F06B-202E-4480-B475-FA416154D458';
export const ConnectionUriBackupIdAttributeName = 'backupId';
export const ConnectionUriRestoreIdAttributeName = 'restoreId';
/**
* Takes a string in the format of HH:MM:SS.MS and returns a number representing the time in
* miliseconds
* @param value The string to convert to milliseconds
* @return False is returned if the string is an invalid format,
* the number of milliseconds in the time string is returned otherwise.
*/
export function parseTimeString(value: string): number | boolean {
if (!value) {
return false;
}
let tempVal = value.split('.');
if (tempVal.length === 1) {
// Ideally would handle more cleanly than this but for now handle case where ms not set
tempVal = [tempVal[0], '0'];
} else if (tempVal.length !== 2) {
return false;
}
let msString = tempVal[1];
let msStringEnd = msString.length < 3 ? msString.length : 3;
let ms = parseInt(tempVal[1].substring(0, msStringEnd), 10);
tempVal = tempVal[0].split(':');
if (tempVal.length !== 3) {
return false;
}
let h = parseInt(tempVal[0], 10);
let m = parseInt(tempVal[1], 10);
let s = parseInt(tempVal[2], 10);
return ms + (h * msInH) + (m * msInM) + (s * msInS);
}
/**
* Takes a number of milliseconds and converts it to a string like HH:MM:SS.fff
* @param value The number of milliseconds to convert to a timespan string
* @returns A properly formatted timespan string.
*/
export function parseNumAsTimeString(value: number, includeFraction: boolean = true): string {
let tempVal = value;
let h = Math.floor(tempVal / msInH);
tempVal %= msInH;
let m = Math.floor(tempVal / msInM);
tempVal %= msInM;
let s = Math.floor(tempVal / msInS);
tempVal %= msInS;
let hs = h < 10 ? '0' + h : '' + h;
let ms = m < 10 ? '0' + m : '' + m;
let ss = s < 10 ? '0' + s : '' + s;
let mss = tempVal < 10 ? '00' + tempVal : tempVal < 100 ? '0' + tempVal : '' + tempVal;
let rs = hs + ':' + ms + ':' + ss;
return tempVal > 0 && includeFraction ? rs + '.' + mss : rs;
}
export function generateUri(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection' | 'notebook'): string {
let prefix = purpose ? uriPrefixes[purpose] : uriPrefixes.default;
let uri = generateUriWithPrefix(connection, prefix);
return uri;
}
export function getUriPrefix(ownerUri: string): string {
let prefix: string = '';
if (ownerUri) {
let index = ownerUri.indexOf('://');
if (index > 0) {
prefix = ownerUri.substring(0, index + 3);
} else {
return uriPrefixes.default;
}
}
return prefix;
}
export function generateUriWithPrefix(connection: IConnectionProfile, prefix: string): string {
let id = connection.getOptionsKey();
let uri = prefix + (id ? id : connection.serverName + ':' + connection.databaseName);
return uri;
}
export function findProfileInGroup(og: IConnectionProfile, groups: ConnectionProfileGroup[]): ConnectionProfile {
for (let group of groups) {
for (let conn of group.connections) {
if (conn.id === og.id) {
return conn;
}
}
if (group.hasChildren()) {
let potentialReturn = findProfileInGroup(og, group.children);
if (potentialReturn) {
return potentialReturn;
}
}
}
return undefined;
}
export function isMaster(profile: IConnectionProfile): boolean {
return profile.providerName.toLowerCase() === 'mssql' && profile.databaseName.toLowerCase() === 'master';
}