Add tooltips with advanced options on hover for editor tabs (#24486)

* Added WIP table designer input change

* added test details to tableDesigner

* added connection name to details

* wip restoration of nonDefaultOptions

* added Verbosity todo for getTitle

* added updated info

* added fix for mainController

* fixed assignment

* added update to description

* restore title parts to old names

* added clarifying message

* added title to dashboard and profilerinput

* added advanced titles for edit data and query editor input

* added changes based on feedback

* added additional description

* Added some changes to tableDesigner input

* fixed comments

* removed erroneous import

* added updated titles and tooltips

* added small corrections

* added profiler XEL title feature

* added session name to profiler input tooltip

* added small tooltip rework

* remove unavailable session name

* added update to config.json
This commit is contained in:
Alex Ma
2023-10-11 14:27:25 -07:00
committed by GitHub
parent ab0ea5db62
commit 52bbb60def
15 changed files with 214 additions and 20 deletions

View File

@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "4.10.0.15",
"version": "4.10.0.16",
"downloadFileNames": {
"Windows_86": "win-x86-net7.0.zip",
"Windows_64": "win-x64-net7.0.zip",

View File

@@ -28,16 +28,18 @@ export function registerTableDesignerCommands(appContext: AppContext) {
}
const tableIcon = context.nodeInfo!.nodeSubType as azdata.designers.TableIcon;
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
let nonDefaultOptions = await azdata.connection.getNonDefaultOptions(context.connectionProfile);
await azdata.designers.openTableDesigner(sqlProviderName, {
title: NewTableText,
tooltip: `${context.connectionProfile!.serverName} - ${context.connectionProfile!.databaseName} - ${NewTableText}`,
tooltip: context.connectionProfile!.connectionName ? `${context.connectionProfile!.connectionName} - ${NewTableText}` : `${context.connectionProfile!.serverName} - ${context.connectionProfile!.databaseName} - ${NewTableText}`,
server: context.connectionProfile!.serverName,
database: context.connectionProfile!.databaseName,
isNewTable: true,
id: generateUuid(),
connectionString: connectionString,
accessToken: context.connectionProfile!.options.azureAccountToken as string,
tableIcon: tableIcon
tableIcon: tableIcon,
additionalInfo: `${context.connectionProfile!.serverName + ' - ' + context.connectionProfile!.databaseName}${nonDefaultOptions}`
}, telemetryInfo, context);
} catch (error) {
console.error(error);
@@ -48,6 +50,7 @@ export function registerTableDesignerCommands(appContext: AppContext) {
appContext.extensionContext.subscriptions.push(vscode.commands.registerCommand('mssql.designTable', async (context: azdata.ObjectExplorerContext) => {
try {
void showPreloadDbModelSettingPrompt(appContext);
const connName = context.connectionProfile!.connectionName;
const server = context.connectionProfile!.serverName;
const database = context.connectionProfile!.databaseName;
const schema = context.nodeInfo!.metadata!.schema;
@@ -58,9 +61,10 @@ export function registerTableDesignerCommands(appContext: AppContext) {
}
const tableIcon = context.nodeInfo!.nodeSubType as azdata.designers.TableIcon;
const telemetryInfo = await getTelemetryInfo(context, tableIcon);
let nonDefaultOptions = await azdata.connection.getNonDefaultOptions(context.connectionProfile);
await azdata.designers.openTableDesigner(sqlProviderName, {
title: `${schema}.${name}`,
tooltip: `${server} - ${database} - ${schema}.${name}`,
tooltip: connName ? `${connName} - ${schema}.${name}` : `${server} - ${database} - ${schema}.${name}`,
server: server,
database: database,
isNewTable: false,
@@ -69,7 +73,8 @@ export function registerTableDesignerCommands(appContext: AppContext) {
id: `${sqlProviderName}|${server}|${database}|${schema}|${name}`,
connectionString: connectionString,
accessToken: context.connectionProfile!.options.azureAccountToken as string,
tableIcon: tableIcon
tableIcon: tableIcon,
additionalInfo: `${server + ' - ' + database}${nonDefaultOptions}`
}, telemetryInfo, context);
} catch (error) {
console.error(error);

View File

@@ -508,6 +508,13 @@ declare module 'azdata' {
* @returns The new password that is returned from the operation or undefined if unsuccessful.
*/
export function openChangePasswordDialog(profile: IConnectionProfile): Thenable<string | undefined>;
/**
* Gets the non default options of the connection profile.
* @param profile The connection profile to get the options for.
* @returns The string key containing the non default options (if any) for the profile.
*/
export function getNonDefaultOptions(profile: IConnectionProfile): Thenable<string>;
}
/*
@@ -968,11 +975,11 @@ declare module 'azdata' {
*/
export interface TableInfo {
/**
* Used as the table designer editor's tab header text.
* Used as the table designer editor's tab header text (as well as the base value of the tooltip).
*/
title: string;
/**
* Used as the table designer editor's tab header hover text.
* Used as the table designer editor's tab header name text.
*/
tooltip: string;
/**
@@ -992,6 +999,10 @@ declare module 'azdata' {
* table icon.
*/
tableIcon?: TableIcon;
/**
* Additional information for tooltip on hover displaying the full information of the connection.
*/
additionalInfo?: string;
}
/**

View File

@@ -381,6 +381,13 @@ export interface IConnectionManagementService {
* @returns the new valid password that is entered, or undefined if cancelled or errored.
*/
openChangePasswordDialog(profile: IConnectionProfile): Promise<string | undefined>;
/**
* Launches the password change dialog.
* @param profile The connection profile to retrieve the non default connection options from
* @returns a string key containing the options that aren't default values.
*/
getNonDefaultOptions(profile: IConnectionProfile): string;
}
export enum RunQueryOnConnectionMode {

View File

@@ -355,6 +355,10 @@ export class TestConnectionManagementService implements IConnectionManagementSer
return undefined;
}
getNonDefaultOptions(profile: IConnectionProfile): string {
return undefined!;
}
openCustomErrorDialog(options: azdata.window.IErrorDialogOptions): Promise<string | undefined> {
return undefined;
}

View File

@@ -185,6 +185,11 @@ export class MainThreadConnectionManagement extends Disposable implements MainTh
return this._connectionManagementService.openChangePasswordDialog(convertedProfile);
}
public $getNonDefaultOptions(profile: azdata.IConnectionProfile): Thenable<string> {
let convertedProfile = new ConnectionProfile(this._capabilitiesService, profile);
return Promise.resolve(this._connectionManagementService.getNonDefaultOptions(convertedProfile));
}
public async $listDatabases(connectionId: string): Promise<string[]> {
let connectionUri = await this.$getUriForConnection(connectionId);
let result = await this._connectionManagementService.listDatabases(connectionUri);

View File

@@ -78,6 +78,10 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap
return this._proxy.$openChangePasswordDialog(profile);
}
$getNonDefaultOptions(profile: azdata.IConnectionProfile): Thenable<string> {
return this._proxy.$getNonDefaultOptions(profile);
}
public $listDatabases(connectionId: string): Thenable<string[]> {
return this._proxy.$listDatabases(connectionId);
}

View File

@@ -143,6 +143,9 @@ export function createAdsApiFactory(accessor: ServicesAccessor): IAdsExtensionAp
openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined> {
return extHostConnectionManagement.$openChangePasswordDialog(profile);
},
getNonDefaultOptions(profile: azdata.IConnectionProfile): Thenable<string> {
return extHostConnectionManagement.$getNonDefaultOptions(profile);
},
listDatabases(connectionId: string): Thenable<string[]> {
return extHostConnectionManagement.$listDatabases(connectionId);
},

View File

@@ -734,6 +734,7 @@ export interface MainThreadConnectionManagementShape extends IDisposable {
$getServerInfo(connectedId: string): Thenable<azdata.ServerInfo>;
$openConnectionDialog(providers: string[], initialConnectionProfile?: azdata.IConnectionProfile, connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable<azdata.connection.Connection>;
$openChangePasswordDialog(profile: azdata.IConnectionProfile): Thenable<string | undefined>;
$getNonDefaultOptions(profile: azdata.IConnectionProfile): Thenable<string>;
$listDatabases(connectionId: string): Thenable<string[]>;
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
$getUriForConnection(connectionId: string): Thenable<string>;

View File

@@ -18,6 +18,7 @@ import { UntitledTextEditorModel } from 'vs/workbench/services/untitled/common/u
import { EncodingMode } from 'vs/workbench/services/textfile/common/textfiles';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IEditorModel, IEditorOptions } from 'vs/platform/editor/common/editor';
import { Verbosity } from 'vs/workbench/common/editor';
/**
* Input for the EditDataEditor.
@@ -223,7 +224,46 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
}
public getEncoding(): string | undefined { return this._sql.getEncoding(); }
public override getName(): string { return this._sql.getName(); }
public override getName(): string {
let profile = this._connectionManagementService.getConnectionProfile(this.uri);
let title = this._sql.getName();
if (profile) {
if (profile.connectionName) {
title += ` - ${profile.connectionName}`;
}
else {
title += ` - ${profile.serverName}`;
if (profile.databaseName) {
title += `.${profile.databaseName}`;
}
title += ` (${profile.userName || profile.authenticationType})`;
}
}
return title;
}
public override getTitle(verbosity?: Verbosity): string {
let fullTitle = this._sql.getName();
let profile = this._connectionManagementService.getConnectionProfile(this.uri);
if (profile) {
fullTitle += ` - ${profile.serverName}`;
if (profile.databaseName) {
fullTitle += `.${profile.databaseName}`;
}
fullTitle += ` (${profile.userName || profile.authenticationType})`;
let nonDefaultOptions = this._connectionManagementService.getNonDefaultOptions(profile);
fullTitle += nonDefaultOptions;
}
switch (verbosity) {
case Verbosity.LONG:
// Used by tabsTitleControl as the tooltip hover.
return fullTitle;
default:
case Verbosity.SHORT:
case Verbosity.MEDIUM:
// Used for header title by tabsTitleControl.
return this.getName();
}
}
public get hasAssociatedFilePath(): boolean { return this._sql.model.hasAssociatedFilePath; }
public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {

View File

@@ -12,6 +12,7 @@ import { IConnectionManagementService } from 'sql/platform/connection/common/con
import { mssqlProviderName } from 'sql/platform/connection/common/constants';
import { IModelService } from 'vs/editor/common/services/model';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { Verbosity } from 'vs/workbench/common/editor';
export class DashboardInput extends EditorInput {
@@ -93,6 +94,27 @@ export class DashboardInput extends EditorInput {
return name;
}
public override getTitle(verbosity?: Verbosity): string {
let baseName = this.connectionProfile.serverName;
if (this.connectionProfile.databaseName && !this.isMasterMssql()) {
// Only add DB name if this is a non-default, non-master connection and if there is no user set profile name.
baseName = baseName + ':' + this.connectionProfile.databaseName;
}
let advancedOptions = this._connectionService.getNonDefaultOptions(this.connectionProfile);
let fullTitle = baseName + advancedOptions;
switch (verbosity) {
case Verbosity.LONG:
// Used by tabsTitleControl as the tooltip hover.
return fullTitle;
default:
case Verbosity.SHORT:
case Verbosity.MEDIUM:
// Used for header title by tabsTitleControl.
return this.getName();
}
}
private isMasterMssql(): boolean {
return this.connectionProfile.providerName === mssqlProviderName
&& this.connectionProfile.databaseName?.toLowerCase() === 'master';

View File

@@ -7,6 +7,7 @@ import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { IProfilerSession, IProfilerService, ProfilerSessionID, IProfilerViewTemplate, ProfilerFilter } from 'sql/workbench/services/profiler/browser/interfaces';
import { ProfilerState } from 'sql/workbench/common/editor/profiler/profilerState';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import * as azdata from 'azdata';
import * as nls from 'vs/nls';
@@ -17,8 +18,10 @@ import { Event, Emitter } from 'vs/base/common/event';
import { generateUuid } from 'vs/base/common/uuid';
import * as types from 'vs/base/common/types';
import { URI } from 'vs/base/common/uri';
import * as path from 'vs/base/common/path';
import { FilterData } from 'sql/workbench/services/profiler/browser/profilerFilter';
import { uriPrefixes } from 'sql/platform/connection/common/utils';
import { Verbosity } from 'vs/workbench/common/editor';
export interface ColumnDefinition extends Slick.Column<Slick.SlickData> {
name: string;
@@ -26,6 +29,7 @@ export interface ColumnDefinition extends Slick.Column<Slick.SlickData> {
export class ProfilerInput extends EditorInput implements IProfilerSession {
private static PROFILERNAME: string = nls.localize('profilerInput.profiler', "Profiler");
public static ID: string = 'workbench.editorinputs.profilerinputs';
public static SCHEMA: string = 'profiler';
private _data: TableDataView<Slick.SlickData>;
@@ -48,6 +52,7 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
constructor(
public connection: IConnectionProfile | undefined,
public fileURI: URI | undefined,
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@IProfilerService private _profilerService: IProfilerService,
@INotificationService private _notificationService: INotificationService
) {
@@ -114,6 +119,7 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
public setSessionName(name: string) {
if (!this.state.isRunning || !this.state.isPaused) {
this._sessionName = name;
this._onDidChangeLabel.fire();
}
}
@@ -126,14 +132,48 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
}
public override getName(): string {
let name: string = nls.localize('profilerInput.profiler', "Profiler");
let name: string = ProfilerInput.PROFILERNAME;
if (!this.connection) {
if (this.isFileSession) {
name += ': ' + path.basename(this.fileURI.fsPath);
}
return name;
}
name += ': ' + this.connection.serverName.substring(0, 20);
if (this.connection.connectionName) {
name += ': ' + this.connection.connectionName.substring(0, 20);
}
else {
name += ': ' + this.connection.serverName.substring(0, 20);
}
return name;
}
public override getTitle(verbosity?: Verbosity): string {
let fullTitle = ProfilerInput.PROFILERNAME;
if (this.connection) {
if (this.sessionName) {
fullTitle += nls.localize('profilerInput.sessionName', ': Session Name: {0}\n', this.sessionName);
}
let baseName = this.connection.serverName + ':' + this.connection.databaseName;
let advancedOptions = this._connectionService.getNonDefaultOptions(this.connection);
fullTitle = fullTitle + nls.localize('profilerInput.connDetails', 'Connection Details: ') + baseName + advancedOptions;
}
else if (this.isFileSession) {
fullTitle += ': ' + path.basename(this.fileURI.fsPath);
}
switch (verbosity) {
case Verbosity.LONG:
// Used by tabsTitleControl as the tooltip hover.
return fullTitle;
default:
case Verbosity.SHORT:
case Verbosity.MEDIUM:
// Used for header title by tabsTitleControl.
return this.getName();
}
}
public getResource(): URI {
return URI.from({
scheme: ProfilerInput.SCHEMA,

View File

@@ -9,7 +9,7 @@ import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { TableDesignerComponentInput } from 'sql/workbench/services/tableDesigner/browser/tableDesignerComponentInput';
import { TableDesignerProvider } from 'sql/workbench/services/tableDesigner/common/interface';
import * as azdata from 'azdata';
import { GroupIdentifier, IRevertOptions, ISaveOptions } from 'vs/workbench/common/editor';
import { GroupIdentifier, IRevertOptions, ISaveOptions, Verbosity } from 'vs/workbench/common/editor';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Schemas } from 'sql/base/common/schemas';
@@ -25,6 +25,7 @@ export class TableDesignerInput extends EditorInput {
public static ID: string = 'workbench.editorinputs.tableDesignerInput';
private _designerComponentInput: TableDesignerComponentInput;
private _title: string;
private _additionalDetails: string = '';
private _name: string;
private _tableIcon: azdata.designers.TableIcon;
private _tableIconMap: Map<TableIcon, string> = new Map<TableIcon, string>([
@@ -80,8 +81,17 @@ export class TableDesignerInput extends EditorInput {
return this._name;
}
override getTitle(): string {
return this._title;
override getTitle(verbosity?: Verbosity): string {
switch (verbosity) {
case Verbosity.LONG:
// Used by tabsTitleControl as the tooltip hover.
return this._additionalDetails + this._title.substring(this._title.lastIndexOf(' - '));
default:
case Verbosity.SHORT:
case Verbosity.MEDIUM:
// Used for header title by tabsTitleControl.
return this._title;
}
}
override isDirty(): boolean {
@@ -119,6 +129,10 @@ export class TableDesignerInput extends EditorInput {
private setEditorLabel(): void {
this._name = this._designerComponentInput.tableInfo.title;
this._title = this._designerComponentInput.tableInfo.tooltip;
let addlDetails = this._designerComponentInput.tableInfo.additionalInfo
if (addlDetails) {
this._additionalDetails = addlDetails;
}
this._onDidChangeLabel.fire();
}
}

View File

@@ -7,7 +7,7 @@ import { localize } from 'vs/nls';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { URI } from 'vs/base/common/uri';
import { GroupIdentifier, IRevertOptions, ISaveOptions, EditorInputCapabilities, IUntypedEditorInput } from 'vs/workbench/common/editor';
import { GroupIdentifier, IRevertOptions, ISaveOptions, EditorInputCapabilities, IUntypedEditorInput, Verbosity } from 'vs/workbench/common/editor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConnectionManagementService, IConnectableInput, INewConnectionParams, RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
@@ -245,11 +245,16 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
title = this._description + ' ';
}
if (profile) {
title += `${profile.serverName}`;
if (profile.databaseName) {
title += `.${profile.databaseName}`;
if (profile.connectionName) {
title += `${profile.connectionName}`;
}
else {
title += `${profile.serverName}`;
if (profile.databaseName) {
title += `.${profile.databaseName}`;
}
title += ` (${profile.userName || profile.authenticationType})`;
}
title += ` (${profile.userName || profile.authenticationType})`;
} else {
title += localize('disconnected', "disconnected");
}
@@ -264,8 +269,35 @@ export abstract class QueryEditorInput extends EditorInput implements IConnectab
}
// Called to get the tooltip of the tab
public override getTitle(): string {
return this.getName(true);
public override getTitle(verbosity?: Verbosity): string {
let profile = this.connectionManagementService.getConnectionProfile(this.uri);
let fullTitle = '';
if (profile) {
let additionalOptions = this.connectionManagementService.getNonDefaultOptions(profile);
if (this._description && this._description !== '') {
fullTitle = this._description + ' ';
}
fullTitle += `${profile.serverName}`;
if (profile.databaseName) {
fullTitle += `.${profile.databaseName}`;
}
fullTitle += ` (${profile.userName || profile.authenticationType})`;
fullTitle += additionalOptions;
}
else {
fullTitle = this.getName(true);
}
switch (verbosity) {
case Verbosity.LONG:
// Used by tabsTitleControl as the tooltip hover.
return fullTitle;
default:
case Verbosity.SHORT:
case Verbosity.MEDIUM:
// Used for header title by tabsTitleControl.
return this.getName(true);
}
}
// State update funtions

View File

@@ -728,6 +728,12 @@ export class ConnectionManagementService extends Disposable implements IConnecti
return result;
}
public getNonDefaultOptions(profile: interfaces.IConnectionProfile): string {
let convProfile = new ConnectionProfile(this._capabilitiesService, profile);
let nonDefOptions = convProfile.getNonDefaultOptionsString();
return nonDefOptions.replace('(', '[').replace(')', ']');
}
private doActionsAfterConnectionComplete(uri: string, options: IConnectionCompletionOptions): void {
let connectionManagementInfo = this._connectionStatusManager.findConnection(uri);
if (!connectionManagementInfo) {