Adding properties grid enhancements in execution plan (#20208)

* init push

* Fixing properties in plan comparison

* Add long Text Cell viewer

* Disabling auto edit by default

* Removing text editor
This commit is contained in:
Aasim Khan
2022-08-11 11:22:12 -07:00
committed by GitHub
parent 9ec68087ac
commit a7a337f063
7 changed files with 110 additions and 19 deletions

View File

@@ -352,7 +352,7 @@ export class ExecutionPlanComparisonEditorView {
if (this.activeBottomPlanDiagram) {
const element = this.activeBottomPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
if (similarNode.matchingNodesId.find(m => this.activeBottomPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
if (this.activeBottomPlanDiagram.getSelectedElement() && similarNode.matchingNodesId.find(m => this.activeBottomPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
return;
}
@@ -392,7 +392,7 @@ export class ExecutionPlanComparisonEditorView {
if (this.activeTopPlanDiagram) {
const element = this.activeTopPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
if (similarNode.matchingNodesId.find(m => this.activeTopPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
if (this.activeTopPlanDiagram.getSelectedElement() && similarNode.matchingNodesId.find(m => this.activeTopPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
return;
}

View File

@@ -15,6 +15,8 @@ import { InternalExecutionPlanElement } from 'sql/workbench/contrib/executionPla
import { executionPlanComparisonPropertiesDifferent, executionPlanComparisonPropertiesUpArrow, executionPlanComparisonPropertiesDownArrow } from 'sql/workbench/contrib/executionPlan/browser/constants';
import * as sqlExtHostType from 'sql/workbench/api/common/sqlExtHostTypes';
import { TextWithIconColumn } from 'sql/base/browser/ui/table/plugins/textWithIconColumn';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export enum ExecutionPlanCompareOrientation {
Horizontal = 'horizontal',
@@ -37,8 +39,10 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
public constructor(
parentContainer: HTMLElement,
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextMenuService contextMenuService: IContextMenuService
) {
super(parentContainer, themeService);
super(parentContainer, themeService, instantiationService, contextMenuService);
this._model = <ExecutionPlanComparisonPropertiesViewModel>{};
this._parentContainer.style.display = 'none';
const header = DOM.$('.compare-operation-name');
@@ -128,30 +132,27 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
name: localize('nodePropertyViewNameNameColumnHeader', "Name"),
field: 'name',
width: 200,
editor: Slick.Editors.Text,
headerCssClass: 'prop-table-header',
formatter: textFormatter
});
columns.push({
id: 'value',
id: 'value1',
name: getPropertyViewNameValueColumnTopHeaderForOrientation(this._orientation),
field: 'primary',
width: 150,
editor: Slick.Editors.Text,
headerCssClass: 'prop-table-header',
formatter: textFormatter
});
}
if (this._model.bottomElement) {
columns.push(new TextWithIconColumn({
id: 'value',
id: 'value2',
name: getPropertyViewNameValueColumnBottomHeaderForOrientation(this._orientation),
field: 'secondary',
width: 150,
headerCssClass: 'prop-table-header',
}).definition);
}
return columns;
}
@@ -313,6 +314,7 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
}
});
return rows;
}

View File

@@ -48,6 +48,16 @@ export class ExecutionPlanEditor extends EditorPane {
public layout(dimension: DOM.Dimension): void {
}
override clearInput(): void {
const currentInput = this.input as ExecutionPlanInput;
// clearing old input view if present in the editor
if (currentInput?._executionPlanFileViewUUID) {
const oldView = this._viewCache.executionPlanFileViewMap.get(currentInput._executionPlanFileViewUUID);
oldView.onHide(this._parentContainer);
}
super.clearInput();
}
public override async setInput(newInput: ExecutionPlanInput, options: IEditorOptions, context: IEditorOpenContext, token: CancellationToken): Promise<void> {
const oldInput = this.input as ExecutionPlanInput;

View File

@@ -11,6 +11,8 @@ import { removeLineBreaks } from 'sql/base/common/strings';
import { isString } from 'vs/base/common/types';
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
import { ExecutionPlanPropertiesViewBase, PropertiesSortType } from 'sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesViewBase';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase {
// Div that holds the name of the element selected
@@ -19,9 +21,11 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
public constructor(
parentContainer: HTMLElement,
themeService: IThemeService
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IContextMenuService contextMenuService: IContextMenuService,
) {
super(parentContainer, themeService);
super(parentContainer, themeService, instantiationService, contextMenuService);
this._model = <ExecutionPlanPropertiesView>{};
this._operationName = DOM.$('h3');
this._operationName.classList.add('operation-name');
@@ -91,7 +95,6 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
name: localize('nodePropertyViewNameNameColumnHeader', "Name"),
field: 'name',
width: 250,
editor: Slick.Editors.Text,
headerCssClass: 'prop-table-header',
formatter: textFormatter
},
@@ -100,7 +103,6 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
name: localize('nodePropertyViewNameValueColumnHeader', "Value"),
field: 'value',
width: 250,
editor: Slick.Editors.Text,
headerCssClass: 'prop-table-header',
formatter: textFormatter
}

View File

@@ -16,6 +16,12 @@ import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/common/constants';
import { contrastBorder, listHoverBackground } from 'vs/platform/theme/common/colorRegistry';
import { TreeGrid } from 'sql/base/browser/ui/table/treeGrid';
import { ISashEvent, IVerticalSashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { isString } from 'vs/base/common/types';
import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugin';
export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLayoutProvider {
// Title bar with close button action
@@ -42,9 +48,13 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
public resizeSash: Sash;
private _selectionModel: CellSelectionModel<Slick.SlickData>;
constructor(
public _parentContainer: HTMLElement,
private _themeService: IThemeService
private _themeService: IThemeService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService private _contextMenuService: IContextMenuService
) {
const sashContainer = DOM.$('.properties-sash');
@@ -103,14 +113,38 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
const table = DOM.$('.table');
this._tableContainer.appendChild(table);
this._selectionModel = new CellSelectionModel<Slick.SlickData>();
this._tableComponent = new TreeGrid(table, {
columns: []
}, {
rowHeight: RESULTS_GRID_DEFAULTS.rowHeight,
forceFitColumns: true,
defaultColumnWidth: 120
defaultColumnWidth: 120,
editable: true,
autoEdit: false
});
attachTableStyler(this._tableComponent, this._themeService);
this._tableComponent.setSelectionModel(this._selectionModel);
const contextMenuAction = [
this._instantiationService.createInstance(CopyTableData),
];
this._tableComponent.onContextMenu(e => {
this._contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
getActions: () => contextMenuAction,
getActionsContext: () => this.getCopyString()
});
});
let copyHandler = new CopyKeybind<any>();
this._tableComponent.registerPlugin(copyHandler);
copyHandler.onCopy(e => {
this._instantiationService.createInstance(CopyTableData).run(this.getCopyString());
});
new ResizeObserver((e) => {
this.resizeSash.layout();
@@ -118,6 +152,33 @@ export abstract class ExecutionPlanPropertiesViewBase implements IVerticalSashLa
}).observe(_parentContainer);
}
public getCopyString(): string {
const selectedDataRange = this._selectionModel.getSelectedRanges()[0];
let csvString = '';
if (selectedDataRange) {
const data = [];
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
const dataRow = this._tableComponent.getData().getItem(rowIndex);
const row = [];
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
const dataItem = dataRow[this._tableComponent.grid.getColumns()[colIndex].field];
if (dataItem) {
row.push(isString(dataItem) ? dataItem : dataItem.displayText ?? dataItem.text ?? dataItem.title);
} else {
row.push('');
}
}
data.push(row);
}
csvString = data.map(row =>
row.map(x => `${x}`).join('\t')
).join('\n');
}
return csvString;
}
getVerticalSashLeft(sash: Sash): number {
return 0;
}
@@ -273,3 +334,19 @@ registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) =
}
});
export class CopyTableData extends Action {
public static ID = 'ep.CopyTableData';
public static LABEL = localize('ep.topOperationsCopyTableData', "Copy");
constructor(
@IClipboardService private _clipboardService: IClipboardService
) {
super(CopyTableData.ID, CopyTableData.LABEL, '');
}
public override async run(context: string): Promise<void> {
this._clipboardService.writeText(context);
}
}

View File

@@ -14,7 +14,6 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFileService } from 'vs/platform/files/common/files';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
@@ -75,7 +74,6 @@ export class ExecutionPlanView implements ISashLayoutProvider {
private _executionPlanFileView: ExecutionPlanFileView,
private _queryResultsView: QueryResultsView,
@IInstantiationService public readonly _instantiationService: IInstantiationService,
@IThemeService private readonly _themeService: IThemeService,
@IContextViewService public readonly contextViewService: IContextViewService,
@IUntitledTextEditorService private readonly _untitledEditorService: IUntitledTextEditorService,
@IEditorService private readonly editorService: IEditorService,
@@ -144,7 +142,7 @@ export class ExecutionPlanView implements ISashLayoutProvider {
// container properties
this._propContainer = DOM.$('.properties');
this.container.appendChild(this._propContainer);
this.propertiesView = new ExecutionPlanPropertiesView(this._propContainer, this._themeService);
this.propertiesView = this._instantiationService.createInstance(ExecutionPlanPropertiesView, this._propContainer);
this._widgetContainer = DOM.$('.plan-action-container');
this._planContainer.appendChild(this._widgetContainer);
@@ -197,7 +195,7 @@ export class ExecutionPlanView implements ISashLayoutProvider {
this._actionBar.pushAction(actionBarActions, { icon: true, label: false });
const self = this;
this.container.oncontextmenu = (e: MouseEvent) => {
this._planContainer.oncontextmenu = (e: MouseEvent) => {
if (contextMenuAction) {
this._contextMenuService.showContextMenu({
getAnchor: () => {