mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Registers all disposable items for query execution plans (#20851)
This commit is contained in:
@@ -14,13 +14,14 @@ import { Event, Emitter } from 'vs/base/common/event';
|
|||||||
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||||
import { foreground } from 'vs/platform/theme/common/colorRegistry';
|
import { foreground } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
const azdataGraph = azdataGraphModule();
|
const azdataGraph = azdataGraphModule();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This view holds the azdataGraph diagram and provides different
|
* This view holds the azdataGraph diagram and provides different
|
||||||
* methods to manipulate the azdataGraph
|
* methods to manipulate the azdataGraph
|
||||||
*/
|
*/
|
||||||
export class AzdataGraphView {
|
export class AzdataGraphView extends Disposable {
|
||||||
|
|
||||||
private _diagram: any;
|
private _diagram: any;
|
||||||
private _diagramModel: AzDataGraphCell;
|
private _diagramModel: AzDataGraphCell;
|
||||||
@@ -37,6 +38,8 @@ export class AzdataGraphView {
|
|||||||
private _executionPlan: azdata.executionPlan.ExecutionPlanGraph,
|
private _executionPlan: azdata.executionPlan.ExecutionPlanGraph,
|
||||||
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService,
|
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService,
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
this._diagramModel = this.populate(this._executionPlan.root);
|
this._diagramModel = this.populate(this._executionPlan.root);
|
||||||
|
|
||||||
let queryPlanConfiguration = {
|
let queryPlanConfiguration = {
|
||||||
@@ -58,13 +61,13 @@ export class AzdataGraphView {
|
|||||||
this._diagram.graph.setCellsDisconnectable(false); // preventing graph edges to be disconnected from source and target nodes.
|
this._diagram.graph.setCellsDisconnectable(false); // preventing graph edges to be disconnected from source and target nodes.
|
||||||
this._diagram.graph.tooltipHandler.delay = 700; // increasing delay for tooltips
|
this._diagram.graph.tooltipHandler.delay = 700; // increasing delay for tooltips
|
||||||
|
|
||||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
this._register(registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||||
const iconLabelColor = theme.getColor(foreground);
|
const iconLabelColor = theme.getColor(foreground);
|
||||||
if (iconLabelColor) {
|
if (iconLabelColor) {
|
||||||
this._diagram.setTextFontColor(iconLabelColor);
|
this._diagram.setTextFontColor(iconLabelColor);
|
||||||
this._diagram.setEdgeColor(iconLabelColor);
|
this._diagram.setEdgeColor(iconLabelColor);
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeGraphEvents(): void {
|
private initializeGraphEvents(): void {
|
||||||
@@ -74,10 +77,12 @@ export class AzdataGraphView {
|
|||||||
if (this._cellInFocus?.id === evt.properties.removed[0]?.id) {
|
if (this._cellInFocus?.id === evt.properties.removed[0]?.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newSelection = evt.properties.removed[0];
|
const newSelection = evt.properties.removed[0];
|
||||||
if (!newSelection) {
|
if (!newSelection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._onElementSelectedEmitter.fire(this.getElementById(newSelection.id));
|
this._onElementSelectedEmitter.fire(this.getElementById(newSelection.id));
|
||||||
this.centerElement(this.getElementById(newSelection.id));
|
this.centerElement(this.getElementById(newSelection.id));
|
||||||
this._cellInFocus = evt.properties.removed[0];
|
this._cellInFocus = evt.properties.removed[0];
|
||||||
@@ -102,7 +107,9 @@ export class AzdataGraphView {
|
|||||||
} else {
|
} else {
|
||||||
cell = this._diagram.graph.model.getCell((<azdata.executionPlan.ExecutionPlanNode>this._executionPlan.root).id);
|
cell = this._diagram.graph.model.getCell((<azdata.executionPlan.ExecutionPlanNode>this._executionPlan.root).id);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._diagram.graph.getSelectionModel().setCell(cell);
|
this._diagram.graph.getSelectionModel().setCell(cell);
|
||||||
|
|
||||||
if (bringToCenter) {
|
if (bringToCenter) {
|
||||||
this.centerElement(element);
|
this.centerElement(element);
|
||||||
}
|
}
|
||||||
@@ -116,6 +123,7 @@ export class AzdataGraphView {
|
|||||||
if (cell?.id) {
|
if (cell?.id) {
|
||||||
return this.getElementById(cell.id);
|
return this.getElementById(cell.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +166,7 @@ export class AzdataGraphView {
|
|||||||
if (level < 1) {
|
if (level < 1) {
|
||||||
throw new Error(localize('invalidExecutionPlanZoomError', "Zoom level cannot be 0 or negative"));
|
throw new Error(localize('invalidExecutionPlanZoomError', "Zoom level cannot be 0 or negative"));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._diagram.zoomTo(level);
|
this._diagram.zoomTo(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,11 +177,13 @@ export class AzdataGraphView {
|
|||||||
public getElementById(id: string): InternalExecutionPlanElement | undefined {
|
public getElementById(id: string): InternalExecutionPlanElement | undefined {
|
||||||
const nodeStack: azdata.executionPlan.ExecutionPlanNode[] = [];
|
const nodeStack: azdata.executionPlan.ExecutionPlanNode[] = [];
|
||||||
nodeStack.push(this._executionPlan.root);
|
nodeStack.push(this._executionPlan.root);
|
||||||
|
|
||||||
while (nodeStack.length !== 0) {
|
while (nodeStack.length !== 0) {
|
||||||
const currentNode = nodeStack.pop();
|
const currentNode = nodeStack.pop();
|
||||||
if (currentNode.id === id) {
|
if (currentNode.id === id) {
|
||||||
return currentNode;
|
return currentNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentNode.edges) {
|
if (currentNode.edges) {
|
||||||
for (let i = 0; i < currentNode.edges.length; i++) {
|
for (let i = 0; i < currentNode.edges.length; i++) {
|
||||||
if ((<InternalExecutionPlanEdge>currentNode.edges[i]).id === id) {
|
if ((<InternalExecutionPlanEdge>currentNode.edges[i]).id === id) {
|
||||||
@@ -180,8 +191,10 @@ export class AzdataGraphView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeStack.push(...currentNode.children);
|
nodeStack.push(...currentNode.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,12 +238,15 @@ export class AzdataGraphView {
|
|||||||
matchFound = matchingProp.value < searchQuery.value || matchingProp.value > searchQuery.value;
|
matchFound = matchingProp.value < searchQuery.value || matchingProp.value > searchQuery.value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchFound) {
|
if (matchFound) {
|
||||||
resultNodes.push(currentNode);
|
resultNodes.push(currentNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nodeStack.push(...currentNode.children);
|
nodeStack.push(...currentNode.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
return resultNodes;
|
return resultNodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,13 +319,14 @@ export class AzdataGraphView {
|
|||||||
diagramNode.tooltipTitle = node.name;
|
diagramNode.tooltipTitle = node.name;
|
||||||
diagramNode.rowCountDisplayString = node.rowCountDisplayString;
|
diagramNode.rowCountDisplayString = node.rowCountDisplayString;
|
||||||
diagramNode.costDisplayString = node.costDisplayString;
|
diagramNode.costDisplayString = node.costDisplayString;
|
||||||
if (!node.id.toString().startsWith(`element-`)) {
|
|
||||||
node.id = `element-${node.id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.expensiveMetricTypes.add(ExpensiveMetricType.Off);
|
this.expensiveMetricTypes.add(ExpensiveMetricType.Off);
|
||||||
|
|
||||||
|
if (!node.id.toString().startsWith(`element-`)) {
|
||||||
|
node.id = `element-${node.id}`;
|
||||||
|
}
|
||||||
diagramNode.id = node.id;
|
diagramNode.id = node.id;
|
||||||
|
|
||||||
diagramNode.icon = node.type;
|
diagramNode.icon = node.type;
|
||||||
diagramNode.metrics = this.populateProperties(node.properties);
|
diagramNode.metrics = this.populateProperties(node.properties);
|
||||||
|
|
||||||
@@ -401,6 +418,7 @@ export class AzdataGraphView {
|
|||||||
props.forEach(p => {
|
props.forEach(p => {
|
||||||
this._graphElementPropertiesSet.add(p.name);
|
this._graphElementPropertiesSet.add(p.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
return props.filter(e => isString(e.displayValue) && e.showInTooltip)
|
return props.filter(e => isString(e.displayValue) && e.showInTooltip)
|
||||||
.sort((a, b) => a.displayOrder - b.displayOrder)
|
.sort((a, b) => a.displayOrder - b.displayOrder)
|
||||||
.map(e => {
|
.map(e => {
|
||||||
@@ -449,6 +467,7 @@ export class AzdataGraphView {
|
|||||||
} else {
|
} else {
|
||||||
this._diagram.graph.tooltipHandler.setEnabled(true);
|
this._diagram.graph.tooltipHandler.setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._diagram.graph.tooltipHandler.enabled;
|
return this._diagram.graph.tooltipHandler.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export class ExecutionPlanComparisonInput extends EditorInput {
|
|||||||
const existingNames = this._editorService.editors.map(editor => editor.getName());
|
const existingNames = this._editorService.editors.map(editor => editor.getName());
|
||||||
let i = 1;
|
let i = 1;
|
||||||
this._editorName = `${this.editorNamePrefix}_${i}`;
|
this._editorName = `${this.editorNamePrefix}_${i}`;
|
||||||
|
|
||||||
while (existingNames.includes(this._editorName)) {
|
while (existingNames.includes(this._editorName)) {
|
||||||
i++;
|
i++;
|
||||||
this._editorName = `${this.editorNamePrefix}_${i}`;
|
this._editorName = `${this.editorNamePrefix}_${i}`;
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ export class ExecutionPlanComparisonEditor extends EditorPane {
|
|||||||
|
|
||||||
// creating a new comparison view if the new input does not already have a cached one.
|
// creating a new comparison view if the new input does not already have a cached one.
|
||||||
if (!input._executionPlanComparisonView) {
|
if (!input._executionPlanComparisonView) {
|
||||||
input._executionPlanComparisonView = this._instantiationService.createInstance(ExecutionPlanComparisonEditorView, this._editorContainer);
|
input._executionPlanComparisonView = this._register(this._instantiationService.createInstance(ExecutionPlanComparisonEditorView, this._editorContainer));
|
||||||
|
|
||||||
if (this.input.preloadModel) {
|
if (this.input.preloadModel) {
|
||||||
if (this.input.preloadModel.topExecutionPlan) {
|
if (this.input.preloadModel.topExecutionPlan) {
|
||||||
input._executionPlanComparisonView.addExecutionPlanGraph(this.input.preloadModel.topExecutionPlan, this.input.preloadModel.topPlanIndex);
|
input._executionPlanComparisonView.addExecutionPlanGraph(this.input.preloadModel.topExecutionPlan, this.input.preloadModel.topPlanIndex);
|
||||||
|
|||||||
@@ -34,10 +34,11 @@ import { ExecutionPlanWidgetController } from 'sql/workbench/contrib/executionPl
|
|||||||
import { NodeSearchWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/nodeSearchWidget';
|
import { NodeSearchWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/nodeSearchWidget';
|
||||||
import { Button } from 'sql/base/browser/ui/button/button';
|
import { Button } from 'sql/base/browser/ui/button/button';
|
||||||
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
import { attachButtonStyler } from 'vs/platform/theme/common/styler';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
const ADD_EXECUTION_PLAN_STRING = localize('epCompare.addExecutionPlanLabel', 'Add execution plan');
|
const ADD_EXECUTION_PLAN_STRING = localize('epCompare.addExecutionPlanLabel', 'Add execution plan');
|
||||||
|
|
||||||
export class ExecutionPlanComparisonEditorView {
|
export class ExecutionPlanComparisonEditorView extends Disposable {
|
||||||
|
|
||||||
public container: HTMLElement;
|
public container: HTMLElement;
|
||||||
|
|
||||||
@@ -134,6 +135,7 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
@IProgressService private _progressService: IProgressService,
|
@IProgressService private _progressService: IProgressService,
|
||||||
@IContextMenuService private _contextMenuService: IContextMenuService
|
@IContextMenuService private _contextMenuService: IContextMenuService
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
this.container = DOM.$('.comparison-editor');
|
this.container = DOM.$('.comparison-editor');
|
||||||
parentContainer.appendChild(this.container);
|
parentContainer.appendChild(this.container);
|
||||||
@@ -144,22 +146,16 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
|
|
||||||
// creating and adding editor toolbar actions
|
// creating and adding editor toolbar actions
|
||||||
private initializeToolbar(): void {
|
private initializeToolbar(): void {
|
||||||
this._taskbarContainer = DOM.$('.editor-toolbar');
|
this._addExecutionPlanAction = this._register(this._instantiationService.createInstance(AddExecutionPlanAction));
|
||||||
this._taskbar = new Taskbar(this._taskbarContainer, {
|
this._zoomOutAction = this._register(new ZoomOutAction());
|
||||||
orientation: ActionsOrientation.HORIZONTAL,
|
this._zoomInAction = this._register(new ZoomInAction());
|
||||||
|
this._zoomToFitAction = this._register(new ZoomToFitAction());
|
||||||
});
|
this._propertiesAction = this._register(this._instantiationService.createInstance(PropertiesAction));
|
||||||
this._taskbar.context = this;
|
this._toggleOrientationAction = this._register(new ToggleOrientation());
|
||||||
this._addExecutionPlanAction = this._instantiationService.createInstance(AddExecutionPlanAction);
|
this._searchNodeAction = this._register(this._instantiationService.createInstance(SearchNodeAction, PlanIdentifier.Primary));
|
||||||
this._zoomOutAction = new ZoomOutAction();
|
this._searchNodeActionForAddedPlan = this._register(this._instantiationService.createInstance(SearchNodeAction, PlanIdentifier.Added));
|
||||||
this._zoomInAction = new ZoomInAction();
|
this._resetZoomAction = this._register(new ZoomReset());
|
||||||
this._zoomToFitAction = new ZoomToFitAction();
|
this._toggleTooltipAction = this._register(new ActionBarToggleTooltip());
|
||||||
this._propertiesAction = this._instantiationService.createInstance(PropertiesAction);
|
|
||||||
this._toggleOrientationAction = new ToggleOrientation();
|
|
||||||
this._searchNodeAction = this._instantiationService.createInstance(SearchNodeAction, PlanIdentifier.Primary);
|
|
||||||
this._searchNodeActionForAddedPlan = this._instantiationService.createInstance(SearchNodeAction, PlanIdentifier.Added);
|
|
||||||
this._resetZoomAction = new ZoomReset();
|
|
||||||
this._toggleTooltipAction = new ActionBarToggleTooltip();
|
|
||||||
const content: ITaskbarContent[] = [
|
const content: ITaskbarContent[] = [
|
||||||
{ action: this._addExecutionPlanAction },
|
{ action: this._addExecutionPlanAction },
|
||||||
{ action: this._zoomInAction },
|
{ action: this._zoomInAction },
|
||||||
@@ -172,6 +168,12 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
{ action: this._searchNodeActionForAddedPlan },
|
{ action: this._searchNodeActionForAddedPlan },
|
||||||
{ action: this._toggleTooltipAction }
|
{ action: this._toggleTooltipAction }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
this._taskbarContainer = DOM.$('.editor-toolbar');
|
||||||
|
this._taskbar = this._register(new Taskbar(this._taskbarContainer, {
|
||||||
|
orientation: ActionsOrientation.HORIZONTAL,
|
||||||
|
}));
|
||||||
|
this._taskbar.context = this;
|
||||||
this._taskbar.setContent(content);
|
this._taskbar.setContent(content);
|
||||||
this.container.appendChild(this._taskbarContainer);
|
this.container.appendChild(this._taskbarContainer);
|
||||||
}
|
}
|
||||||
@@ -192,9 +194,10 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
this._placeholderContainer = DOM.$('.placeholder');
|
this._placeholderContainer = DOM.$('.placeholder');
|
||||||
|
|
||||||
const contextMenuAction = [
|
const contextMenuAction = [
|
||||||
this._instantiationService.createInstance(AddExecutionPlanAction)
|
this._register(this._instantiationService.createInstance(AddExecutionPlanAction))
|
||||||
];
|
];
|
||||||
this._placeholderContainer.oncontextmenu = (e: MouseEvent) => {
|
|
||||||
|
this._register(DOM.addDisposableListener(this._placeholderContainer, DOM.EventType.CONTEXT_MENU, (e: MouseEvent) => {
|
||||||
if (contextMenuAction) {
|
if (contextMenuAction) {
|
||||||
this._contextMenuService.showContextMenu({
|
this._contextMenuService.showContextMenu({
|
||||||
getAnchor: () => {
|
getAnchor: () => {
|
||||||
@@ -207,24 +210,25 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
getActionsContext: () => (self)
|
getActionsContext: () => (self)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}));
|
||||||
|
|
||||||
this._placeholderInfoboxContainer = DOM.$('.placeholder-infobox');
|
this._placeholderInfoboxContainer = DOM.$('.placeholder-infobox');
|
||||||
|
|
||||||
this._placeholderButton = new Button(this._placeholderInfoboxContainer, { secondary: true });
|
this._placeholderButton = this._register(new Button(this._placeholderInfoboxContainer, { secondary: true }));
|
||||||
attachButtonStyler(this._placeholderButton, this.themeService);
|
this._register(attachButtonStyler(this._placeholderButton, this.themeService));
|
||||||
this._placeholderButton.label = ADD_EXECUTION_PLAN_STRING;
|
this._placeholderButton.label = ADD_EXECUTION_PLAN_STRING;
|
||||||
this._placeholderButton.ariaLabel = ADD_EXECUTION_PLAN_STRING;
|
this._placeholderButton.ariaLabel = ADD_EXECUTION_PLAN_STRING;
|
||||||
this._placeholderButton.enabled = true;
|
this._placeholderButton.enabled = true;
|
||||||
this._placeholderButton.onDidClick(e => {
|
|
||||||
const addExecutionPlanAction = this._instantiationService.createInstance(AddExecutionPlanAction);
|
|
||||||
addExecutionPlanAction.run(self);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._placeholderLoading = new LoadingSpinner(this._placeholderContainer, {
|
this._register(this._placeholderButton.onDidClick(e => {
|
||||||
|
const addExecutionPlanAction = this._register(this._instantiationService.createInstance(AddExecutionPlanAction));
|
||||||
|
addExecutionPlanAction.run(self);
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._placeholderLoading = this._register(new LoadingSpinner(this._placeholderContainer, {
|
||||||
fullSize: true,
|
fullSize: true,
|
||||||
showText: true
|
showText: true
|
||||||
});
|
}));
|
||||||
this._placeholderContainer.appendChild(this._placeholderInfoboxContainer);
|
this._placeholderContainer.appendChild(this._placeholderInfoboxContainer);
|
||||||
this._placeholderLoading.loadingMessage = localize('epComapre.LoadingPlanMessage', "Loading execution plan");
|
this._placeholderLoading.loadingMessage = localize('epComapre.LoadingPlanMessage', "Loading execution plan");
|
||||||
this._placeholderLoading.loadingCompletedMessage = localize('epComapre.LoadingPlanCompleteMessage', "Execution plan successfully loaded");
|
this._placeholderLoading.loadingCompletedMessage = localize('epComapre.LoadingPlanCompleteMessage', "Execution plan successfully loaded");
|
||||||
@@ -232,9 +236,10 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
this._topPlanContainer = DOM.$('.plan-container');
|
this._topPlanContainer = DOM.$('.plan-container');
|
||||||
this.planSplitViewContainer.appendChild(this._topPlanContainer);
|
this.planSplitViewContainer.appendChild(this._topPlanContainer);
|
||||||
this._topPlanDropdownContainer = DOM.$('.dropdown-container');
|
this._topPlanDropdownContainer = DOM.$('.dropdown-container');
|
||||||
this._topPlanDropdown = new SelectBox(['option 1', 'option2'], 'option1', this.contextViewService, this._topPlanDropdownContainer);
|
this._topPlanDropdown = this._register(new SelectBox(['option 1', 'option2'], 'option1', this.contextViewService, this._topPlanDropdownContainer));
|
||||||
this._topPlanDropdown.render(this._topPlanDropdownContainer);
|
this._topPlanDropdown.render(this._topPlanDropdownContainer);
|
||||||
this._topPlanDropdown.onDidSelect(async (e) => {
|
|
||||||
|
this._register(this._topPlanDropdown.onDidSelect(async (e) => {
|
||||||
this.activeBottomPlanDiagram?.clearSubtreePolygon();
|
this.activeBottomPlanDiagram?.clearSubtreePolygon();
|
||||||
this.activeTopPlanDiagram?.clearSubtreePolygon();
|
this.activeTopPlanDiagram?.clearSubtreePolygon();
|
||||||
|
|
||||||
@@ -247,19 +252,21 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
this._activeTopPlanIndex = e.index;
|
this._activeTopPlanIndex = e.index;
|
||||||
|
|
||||||
await this.getSkeletonNodes();
|
await this.getSkeletonNodes();
|
||||||
});
|
}));
|
||||||
attachSelectBoxStyler(this._topPlanDropdown, this.themeService);
|
|
||||||
|
this._register(attachSelectBoxStyler(this._topPlanDropdown, this.themeService));
|
||||||
this._topPlanContainer.appendChild(this._topPlanDropdownContainer);
|
this._topPlanContainer.appendChild(this._topPlanDropdownContainer);
|
||||||
this._topPlanRecommendations = this._instantiationService.createInstance(ExecutionPlanViewHeader, this._topPlanContainer, undefined);
|
this._topPlanRecommendations = this._register(this._instantiationService.createInstance(ExecutionPlanViewHeader, this._topPlanContainer, undefined));
|
||||||
|
|
||||||
this.initializeSash();
|
this.initializeSash();
|
||||||
|
|
||||||
this._bottomPlanContainer = DOM.$('.plan-container');
|
this._bottomPlanContainer = DOM.$('.plan-container');
|
||||||
this.planSplitViewContainer.appendChild(this._bottomPlanContainer);
|
this.planSplitViewContainer.appendChild(this._bottomPlanContainer);
|
||||||
this._bottomPlanDropdownContainer = DOM.$('.dropdown-container');
|
this._bottomPlanDropdownContainer = DOM.$('.dropdown-container');
|
||||||
this._bottomPlanDropdown = new SelectBox(['option 1', 'option2'], 'option1', this.contextViewService, this._bottomPlanDropdownContainer);
|
this._bottomPlanDropdown = this._register(new SelectBox(['option 1', 'option2'], 'option1', this.contextViewService, this._bottomPlanDropdownContainer));
|
||||||
this._bottomPlanDropdown.render(this._bottomPlanDropdownContainer);
|
this._bottomPlanDropdown.render(this._bottomPlanDropdownContainer);
|
||||||
this._bottomPlanDropdown.onDidSelect(async (e) => {
|
|
||||||
|
this._register(this._bottomPlanDropdown.onDidSelect(async (e) => {
|
||||||
this.activeBottomPlanDiagram?.clearSubtreePolygon();
|
this.activeBottomPlanDiagram?.clearSubtreePolygon();
|
||||||
this.activeTopPlanDiagram?.clearSubtreePolygon();
|
this.activeTopPlanDiagram?.clearSubtreePolygon();
|
||||||
|
|
||||||
@@ -272,24 +279,27 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
this._activeBottomPlanIndex = e.index;
|
this._activeBottomPlanIndex = e.index;
|
||||||
|
|
||||||
await this.getSkeletonNodes();
|
await this.getSkeletonNodes();
|
||||||
});
|
}));
|
||||||
attachSelectBoxStyler(this._bottomPlanDropdown, this.themeService);
|
|
||||||
|
this._register(attachSelectBoxStyler(this._bottomPlanDropdown, this.themeService));
|
||||||
|
|
||||||
this._bottomPlanContainer.appendChild(this._bottomPlanDropdownContainer);
|
this._bottomPlanContainer.appendChild(this._bottomPlanDropdownContainer);
|
||||||
this._bottomPlanRecommendations = this._instantiationService.createInstance(ExecutionPlanViewHeader, this._bottomPlanContainer, undefined);
|
this._bottomPlanRecommendations = this._register(this._instantiationService.createInstance(ExecutionPlanViewHeader, this._bottomPlanContainer, undefined));
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeSash(): void {
|
private initializeSash(): void {
|
||||||
this._sashContainer = DOM.$('.sash-container');
|
this._sashContainer = DOM.$('.sash-container');
|
||||||
this.planSplitViewContainer.appendChild(this._sashContainer);
|
this.planSplitViewContainer.appendChild(this._sashContainer);
|
||||||
this._verticalSash = new Sash(this._sashContainer, new VerticalSash(this), { orientation: Orientation.VERTICAL, size: 3 });
|
this._verticalSash = this._register(new Sash(this._sashContainer, new VerticalSash(this), { orientation: Orientation.VERTICAL, size: 3 }));
|
||||||
|
|
||||||
let originalWidth;
|
let originalWidth;
|
||||||
let change = 0;
|
let change = 0;
|
||||||
this._verticalSash.onDidStart((e: ISashEvent) => {
|
|
||||||
|
this._register(this._verticalSash.onDidStart((e: ISashEvent) => {
|
||||||
originalWidth = this._topPlanContainer.offsetWidth;
|
originalWidth = this._topPlanContainer.offsetWidth;
|
||||||
});
|
}));
|
||||||
this._verticalSash.onDidChange((evt: ISashEvent) => {
|
|
||||||
|
this._register(this._verticalSash.onDidChange((evt: ISashEvent) => {
|
||||||
change = evt.startX - evt.currentX;
|
change = evt.startX - evt.currentX;
|
||||||
const newWidth = originalWidth - change;
|
const newWidth = originalWidth - change;
|
||||||
if (newWidth < 200) {
|
if (newWidth < 200) {
|
||||||
@@ -297,14 +307,16 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
}
|
}
|
||||||
this._topPlanContainer.style.minWidth = '200px';
|
this._topPlanContainer.style.minWidth = '200px';
|
||||||
this._topPlanContainer.style.flex = `0 0 ${newWidth}px`;
|
this._topPlanContainer.style.flex = `0 0 ${newWidth}px`;
|
||||||
});
|
}));
|
||||||
|
|
||||||
this._horizontalSash = new Sash(this._sashContainer, new HorizontalSash(this), { orientation: Orientation.HORIZONTAL, size: 3 });
|
this._horizontalSash = this._register(new Sash(this._sashContainer, new HorizontalSash(this), { orientation: Orientation.HORIZONTAL, size: 3 }));
|
||||||
let startHeight;
|
let startHeight;
|
||||||
this._horizontalSash.onDidStart((e: ISashEvent) => {
|
|
||||||
|
this._register(this._horizontalSash.onDidStart((e: ISashEvent) => {
|
||||||
startHeight = this._topPlanContainer.offsetHeight;
|
startHeight = this._topPlanContainer.offsetHeight;
|
||||||
});
|
}));
|
||||||
this._horizontalSash.onDidChange((evt: ISashEvent) => {
|
|
||||||
|
this._register(this._horizontalSash.onDidChange((evt: ISashEvent) => {
|
||||||
change = evt.startY - evt.currentY;
|
change = evt.startY - evt.currentY;
|
||||||
const newHeight = startHeight - change;
|
const newHeight = startHeight - change;
|
||||||
if (newHeight < 200) {
|
if (newHeight < 200) {
|
||||||
@@ -312,12 +324,12 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
}
|
}
|
||||||
this._topPlanContainer.style.minHeight = '200px';
|
this._topPlanContainer.style.minHeight = '200px';
|
||||||
this._topPlanContainer.style.flex = `0 0 ${newHeight}px`;
|
this._topPlanContainer.style.flex = `0 0 ${newHeight}px`;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeProperties(): void {
|
private initializeProperties(): void {
|
||||||
this._propertiesContainer = DOM.$('.properties');
|
this._propertiesContainer = DOM.$('.properties');
|
||||||
this._propertiesView = this._instantiationService.createInstance(ExecutionPlanComparisonPropertiesView, this._propertiesContainer);
|
this._propertiesView = this._register(this._instantiationService.createInstance(ExecutionPlanComparisonPropertiesView, this._propertiesContainer));
|
||||||
this._planComparisonContainer.appendChild(this._propertiesContainer);
|
this._planComparisonContainer.appendChild(this._propertiesContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,6 +355,7 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
canSelectMany: false,
|
canSelectMany: false,
|
||||||
canSelectFiles: true
|
canSelectFiles: true
|
||||||
});
|
});
|
||||||
|
|
||||||
if (openedFileUris?.length === 1) {
|
if (openedFileUris?.length === 1) {
|
||||||
this._placeholderInfoboxContainer.style.display = 'none';
|
this._placeholderInfoboxContainer.style.display = 'none';
|
||||||
this._placeholderLoading.loading = true;
|
this._placeholderLoading.loading = true;
|
||||||
@@ -354,6 +367,7 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
});
|
});
|
||||||
await this.addExecutionPlanGraph(executionPlanGraphs.graphs, 0);
|
await this.addExecutionPlanGraph(executionPlanGraphs.graphs, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._placeholderInfoboxContainer.style.display = '';
|
this._placeholderInfoboxContainer.style.display = '';
|
||||||
this._placeholderLoading.loading = false;
|
this._placeholderLoading.loading = false;
|
||||||
this._placeholderInfoboxContainer.style.display = '';
|
this._placeholderInfoboxContainer.style.display = '';
|
||||||
@@ -367,6 +381,7 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
public async addExecutionPlanGraph(executionPlanGraphs: azdata.executionPlan.ExecutionPlanGraph[], preSelectIndex: number): Promise<void> {
|
public async addExecutionPlanGraph(executionPlanGraphs: azdata.executionPlan.ExecutionPlanGraph[], preSelectIndex: number): Promise<void> {
|
||||||
if (!this._topPlanDiagramModels) {
|
if (!this._topPlanDiagramModels) {
|
||||||
this._topPlanDiagramModels = executionPlanGraphs;
|
this._topPlanDiagramModels = executionPlanGraphs;
|
||||||
|
|
||||||
this._topPlanDropdown.setOptions(executionPlanGraphs.map((e, index) => {
|
this._topPlanDropdown.setOptions(executionPlanGraphs.map((e, index) => {
|
||||||
return {
|
return {
|
||||||
text: this.createQueryDropdownPrefixString(e.query, index + 1, executionPlanGraphs.length)
|
text: this.createQueryDropdownPrefixString(e.query, index + 1, executionPlanGraphs.length)
|
||||||
@@ -377,26 +392,30 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
const graphContainer = DOM.$('.plan-diagram');
|
const graphContainer = DOM.$('.plan-diagram');
|
||||||
this._topPlanDiagramContainers.push(graphContainer);
|
this._topPlanDiagramContainers.push(graphContainer);
|
||||||
this._topPlanContainer.appendChild(graphContainer);
|
this._topPlanContainer.appendChild(graphContainer);
|
||||||
const diagram = this._instantiationService.createInstance(AzdataGraphView, graphContainer, e);
|
|
||||||
diagram.onElementSelected(e => {
|
const diagram = this._register(this._instantiationService.createInstance(AzdataGraphView, graphContainer, e));
|
||||||
|
this._register(diagram.onElementSelected(e => {
|
||||||
this._propertiesView.setPrimaryElement(e);
|
this._propertiesView.setPrimaryElement(e);
|
||||||
|
|
||||||
const id = e.id.replace(`element-`, '');
|
const id = e.id.replace(`element-`, '');
|
||||||
if (this._topSimilarNode.has(id)) {
|
if (this._topSimilarNode.has(id)) {
|
||||||
const similarNode = this._topSimilarNode.get(id);
|
const similarNode = this._topSimilarNode.get(id);
|
||||||
|
|
||||||
if (this.activeBottomPlanDiagram) {
|
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 (similarNode.matchingNodesId.find(m => this.activeBottomPlanDiagram.getSelectedElement().id === `element-` + m) !== undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const element = this.activeBottomPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
|
||||||
this.activeBottomPlanDiagram.selectElement(element);
|
this.activeBottomPlanDiagram.selectElement(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.topPlanDiagrams.push(diagram);
|
this.topPlanDiagrams.push(diagram);
|
||||||
graphContainer.style.display = 'none';
|
graphContainer.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
this._topPlanDropdown.select(preSelectIndex);
|
this._topPlanDropdown.select(preSelectIndex);
|
||||||
this._propertiesView.setPrimaryElement(executionPlanGraphs[0].root);
|
this._propertiesView.setPrimaryElement(executionPlanGraphs[0].root);
|
||||||
this._propertiesAction.enabled = true;
|
this._propertiesAction.enabled = true;
|
||||||
@@ -408,43 +427,51 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
this._searchNodeAction.enabled = true;
|
this._searchNodeAction.enabled = true;
|
||||||
} else {
|
} else {
|
||||||
this._bottomPlanDiagramModels = executionPlanGraphs;
|
this._bottomPlanDiagramModels = executionPlanGraphs;
|
||||||
|
|
||||||
this._bottomPlanDropdown.setOptions(executionPlanGraphs.map((e, index) => {
|
this._bottomPlanDropdown.setOptions(executionPlanGraphs.map((e, index) => {
|
||||||
return {
|
return {
|
||||||
text: this.createQueryDropdownPrefixString(e.query, index + 1, executionPlanGraphs.length)
|
text: this.createQueryDropdownPrefixString(e.query, index + 1, executionPlanGraphs.length)
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
executionPlanGraphs.forEach((e, i) => {
|
executionPlanGraphs.forEach((e, i) => {
|
||||||
const graphContainer = DOM.$('.plan-diagram');
|
const graphContainer = DOM.$('.plan-diagram');
|
||||||
this._bottomPlanDiagramContainers.push(graphContainer);
|
this._bottomPlanDiagramContainers.push(graphContainer);
|
||||||
this._bottomPlanContainer.appendChild(graphContainer);
|
this._bottomPlanContainer.appendChild(graphContainer);
|
||||||
const diagram = this._instantiationService.createInstance(AzdataGraphView, graphContainer, e);
|
const diagram = this._register(this._instantiationService.createInstance(AzdataGraphView, graphContainer, e));
|
||||||
diagram.onElementSelected(e => {
|
|
||||||
|
this._register(diagram.onElementSelected(e => {
|
||||||
this._propertiesView.setSecondaryElement(e);
|
this._propertiesView.setSecondaryElement(e);
|
||||||
|
|
||||||
const id = e.id.replace(`element-`, '');
|
const id = e.id.replace(`element-`, '');
|
||||||
if (this._bottomSimilarNode.has(id)) {
|
if (this._bottomSimilarNode.has(id)) {
|
||||||
const similarNode = this._bottomSimilarNode.get(id);
|
const similarNode = this._bottomSimilarNode.get(id);
|
||||||
|
|
||||||
if (this.activeTopPlanDiagram) {
|
if (this.activeTopPlanDiagram) {
|
||||||
const element = this.activeTopPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
|
|
||||||
if (this.activeTopPlanDiagram.getSelectedElement() && 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const element = this.activeTopPlanDiagram.getElementById(`element-` + similarNode.matchingNodesId[0]);
|
||||||
this.activeTopPlanDiagram.selectElement(element);
|
this.activeTopPlanDiagram.selectElement(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.bottomPlanDiagrams.push(diagram);
|
this.bottomPlanDiagrams.push(diagram);
|
||||||
graphContainer.style.display = 'none';
|
graphContainer.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
this._bottomPlanDropdown.select(preSelectIndex);
|
this._bottomPlanDropdown.select(preSelectIndex);
|
||||||
this._propertiesView.setSecondaryElement(executionPlanGraphs[0].root);
|
this._propertiesView.setSecondaryElement(executionPlanGraphs[0].root);
|
||||||
this._addExecutionPlanAction.enabled = false;
|
this._addExecutionPlanAction.enabled = false;
|
||||||
this._searchNodeActionForAddedPlan.enabled = true;
|
this._searchNodeActionForAddedPlan.enabled = true;
|
||||||
|
|
||||||
if (!this._areTooltipsEnabled) {
|
if (!this._areTooltipsEnabled) {
|
||||||
this.activeBottomPlanDiagram.toggleTooltip();
|
this.activeBottomPlanDiagram.toggleTooltip();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.refreshSplitView();
|
this.refreshSplitView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -463,19 +490,24 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
this._polygonRootsMap = new Map();
|
this._polygonRootsMap = new Map();
|
||||||
this._topSimilarNode = new Map();
|
this._topSimilarNode = new Map();
|
||||||
this._bottomSimilarNode = new Map();
|
this._bottomSimilarNode = new Map();
|
||||||
|
|
||||||
if (this._topPlanDiagramModels && this._bottomPlanDiagramModels) {
|
if (this._topPlanDiagramModels && this._bottomPlanDiagramModels) {
|
||||||
this._topPlanDiagramModels[this._activeTopPlanIndex].graphFile.graphFileType = 'sqlplan';
|
this._topPlanDiagramModels[this._activeTopPlanIndex].graphFile.graphFileType = 'sqlplan';
|
||||||
this._bottomPlanDiagramModels[this._activeBottomPlanIndex].graphFile.graphFileType = 'sqlplan';
|
this._bottomPlanDiagramModels[this._activeBottomPlanIndex].graphFile.graphFileType = 'sqlplan';
|
||||||
|
|
||||||
const currentRequestId = generateUuid();
|
const currentRequestId = generateUuid();
|
||||||
this._latestRequestUuid = currentRequestId;
|
this._latestRequestUuid = currentRequestId;
|
||||||
|
|
||||||
const result = await this._executionPlanService.compareExecutionPlanGraph(this._topPlanDiagramModels[this._activeTopPlanIndex].graphFile,
|
const result = await this._executionPlanService.compareExecutionPlanGraph(this._topPlanDiagramModels[this._activeTopPlanIndex].graphFile,
|
||||||
this._bottomPlanDiagramModels[this._activeBottomPlanIndex].graphFile);
|
this._bottomPlanDiagramModels[this._activeBottomPlanIndex].graphFile);
|
||||||
|
|
||||||
if (currentRequestId !== this._latestRequestUuid) {
|
if (currentRequestId !== this._latestRequestUuid) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.getSimilarSubtrees(result.firstComparisonResult);
|
this.getSimilarSubtrees(result.firstComparisonResult);
|
||||||
this.getSimilarSubtrees(result.secondComparisonResult, true);
|
this.getSimilarSubtrees(result.secondComparisonResult, true);
|
||||||
|
|
||||||
let colorIndex = 0;
|
let colorIndex = 0;
|
||||||
this._polygonRootsMap.forEach((v, k) => {
|
this._polygonRootsMap.forEach((v, k) => {
|
||||||
if (this.activeTopPlanDiagram && this.activeBottomPlanDiagram) {
|
if (this.activeTopPlanDiagram && this.activeBottomPlanDiagram) {
|
||||||
@@ -494,6 +526,7 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
if (comparedNode.hasMatch) {
|
if (comparedNode.hasMatch) {
|
||||||
if (!isBottomPlan) {
|
if (!isBottomPlan) {
|
||||||
this._topSimilarNode.set(`${comparedNode.baseNode.id}`, comparedNode);
|
this._topSimilarNode.set(`${comparedNode.baseNode.id}`, comparedNode);
|
||||||
|
|
||||||
if (!this._polygonRootsMap.has(comparedNode.groupIndex)) {
|
if (!this._polygonRootsMap.has(comparedNode.groupIndex)) {
|
||||||
this._polygonRootsMap.set(comparedNode.groupIndex, {
|
this._polygonRootsMap.set(comparedNode.groupIndex, {
|
||||||
topPolygon: comparedNode,
|
topPolygon: comparedNode,
|
||||||
@@ -502,6 +535,7 @@ export class ExecutionPlanComparisonEditorView {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this._bottomSimilarNode.set(`${comparedNode.baseNode.id}`, comparedNode);
|
this._bottomSimilarNode.set(`${comparedNode.baseNode.id}`, comparedNode);
|
||||||
|
|
||||||
if (this._polygonRootsMap.get(comparedNode.groupIndex).bottomPolygon === undefined) {
|
if (this._polygonRootsMap.get(comparedNode.groupIndex).bottomPolygon === undefined) {
|
||||||
const polygonMapEntry = this._polygonRootsMap.get(comparedNode.groupIndex);
|
const polygonMapEntry = this._polygonRootsMap.get(comparedNode.groupIndex);
|
||||||
polygonMapEntry.bottomPolygon = comparedNode;
|
polygonMapEntry.bottomPolygon = comparedNode;
|
||||||
@@ -779,7 +813,7 @@ class SearchNodeAction extends Action {
|
|||||||
.withAdditionalProperties({ source: 'ComparisonView' })
|
.withAdditionalProperties({ source: 'ComparisonView' })
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
let nodeSearchWidget = this._instantiationService.createInstance(NodeSearchWidget, widgetController, executionPlan);
|
let nodeSearchWidget = this._register(this._instantiationService.createInstance(NodeSearchWidget, widgetController, executionPlan));
|
||||||
widgetController.toggleWidget(nodeSearchWidget);
|
widgetController.toggleWidget(nodeSearchWidget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,10 +135,11 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
const columns: Slick.Column<Slick.SlickData>[] = this.getPropertyTableColumns();
|
const columns: Slick.Column<Slick.SlickData>[] = this.getPropertyTableColumns();
|
||||||
|
|
||||||
let primaryProps = [];
|
let primaryProps = [];
|
||||||
let secondaryProps = [];
|
|
||||||
if (this._model.primaryElement?.properties) {
|
if (this._model.primaryElement?.properties) {
|
||||||
primaryProps = this._model.primaryElement.properties;
|
primaryProps = this._model.primaryElement.properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let secondaryProps = [];
|
||||||
if (this._model.secondaryElement?.properties) {
|
if (this._model.secondaryElement?.properties) {
|
||||||
secondaryProps = this._model.secondaryElement.properties;
|
secondaryProps = this._model.secondaryElement.properties;
|
||||||
}
|
}
|
||||||
@@ -440,6 +441,7 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
};
|
};
|
||||||
|
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
|
|
||||||
if (!isString(primaryProp.value)) {
|
if (!isString(primaryProp.value)) {
|
||||||
row.name.iconCssClass += ` parent-row-styling`;
|
row.name.iconCssClass += ` parent-row-styling`;
|
||||||
row.primary.iconCssClass += ` parent-row-styling`;
|
row.primary.iconCssClass += ` parent-row-styling`;
|
||||||
@@ -459,6 +461,7 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
};
|
};
|
||||||
|
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
|
|
||||||
if (!isString(secondaryProp.value)) {
|
if (!isString(secondaryProp.value)) {
|
||||||
row.name.iconCssClass += ` parent-row-styling`;
|
row.name.iconCssClass += ` parent-row-styling`;
|
||||||
row.secondary.iconCssClass += ` parent-row-styling`;
|
row.secondary.iconCssClass += ` parent-row-styling`;
|
||||||
@@ -475,6 +478,7 @@ export class ExecutionPlanComparisonPropertiesView extends ExecutionPlanProperti
|
|||||||
if (this._orientation === value) {
|
if (this._orientation === value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._orientation = value;
|
this._orientation = value;
|
||||||
this.updatePropertyContainerTitles();
|
this.updatePropertyContainerTitles();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export class ExecutionPlanEditorOverrideContribution extends Disposable implemen
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._editorResolverService.registerEditor(
|
this._register(this._editorResolverService.registerEditor(
|
||||||
this.getGlobForFileExtensions(supportedFileFormats),
|
this.getGlobForFileExtensions(supportedFileFormats),
|
||||||
{
|
{
|
||||||
id: ExecutionPlanEditor.ID,
|
id: ExecutionPlanEditor.ID,
|
||||||
@@ -74,11 +74,11 @@ export class ExecutionPlanEditorOverrideContribution extends Disposable implemen
|
|||||||
graphFileContent: undefined,
|
graphFileContent: undefined,
|
||||||
graphFileType: undefined
|
graphFileType: undefined
|
||||||
};
|
};
|
||||||
|
const executionPlanInput = this._register(this._instantiationService.createInstance(ExecutionPlanInput, editorInput.resource, executionPlanGraphInfo));
|
||||||
|
|
||||||
const executionPlanInput = this._instantiationService.createInstance(ExecutionPlanInput, editorInput.resource, executionPlanGraphInfo);
|
|
||||||
return { editor: executionPlanInput, options: editorInput.options, group: group };
|
return { editor: executionPlanInput, options: editorInput.options, group: group };
|
||||||
}
|
}
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getGlobForFileExtensions(extensions: string[]): string {
|
private getGlobForFileExtensions(extensions: string[]): string {
|
||||||
|
|||||||
@@ -50,11 +50,13 @@ export class ExecutionPlanEditor extends EditorPane {
|
|||||||
|
|
||||||
override clearInput(): void {
|
override clearInput(): void {
|
||||||
const currentInput = this.input as ExecutionPlanInput;
|
const currentInput = this.input as ExecutionPlanInput;
|
||||||
|
|
||||||
// clearing old input view if present in the editor
|
// clearing old input view if present in the editor
|
||||||
if (currentInput?._executionPlanFileViewUUID) {
|
if (currentInput?._executionPlanFileViewUUID) {
|
||||||
const oldView = this._viewCache.executionPlanFileViewMap.get(currentInput._executionPlanFileViewUUID);
|
const oldView = this._viewCache.executionPlanFileViewMap.get(currentInput._executionPlanFileViewUUID);
|
||||||
oldView.onHide(this._parentContainer);
|
oldView.onHide(this._parentContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
super.clearInput();
|
super.clearInput();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||||||
import { contrastBorder, editorWidgetBackground, foreground, listHoverBackground, textLinkForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
import { contrastBorder, editorWidgetBackground, foreground, listHoverBackground, textLinkForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
import { IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||||
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
|
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
export class ExecutionPlanFileView {
|
export class ExecutionPlanFileView extends Disposable {
|
||||||
private _parent: HTMLElement;
|
private _parent: HTMLElement;
|
||||||
private _loadingSpinner: LoadingSpinner;
|
private _loadingSpinner: LoadingSpinner;
|
||||||
private _loadingErrorInfoBox: InfoBox;
|
private _loadingErrorInfoBox: InfoBox;
|
||||||
@@ -30,6 +31,7 @@ export class ExecutionPlanFileView {
|
|||||||
@IInstantiationService private instantiationService: IInstantiationService,
|
@IInstantiationService private instantiationService: IInstantiationService,
|
||||||
@IExecutionPlanService private executionPlanService: IExecutionPlanService
|
@IExecutionPlanService private executionPlanService: IExecutionPlanService
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(parent: HTMLElement): void {
|
public render(parent: HTMLElement): void {
|
||||||
@@ -48,9 +50,6 @@ export class ExecutionPlanFileView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds executionPlanGraph to the graph controller.
|
* Adds executionPlanGraph to the graph controller.
|
||||||
* @param newGraphs ExecutionPlanGraphs to be added.
|
* @param newGraphs ExecutionPlanGraphs to be added.
|
||||||
@@ -58,7 +57,7 @@ export class ExecutionPlanFileView {
|
|||||||
public addGraphs(newGraphs: azdata.executionPlan.ExecutionPlanGraph[] | undefined) {
|
public addGraphs(newGraphs: azdata.executionPlan.ExecutionPlanGraph[] | undefined) {
|
||||||
if (newGraphs) {
|
if (newGraphs) {
|
||||||
newGraphs.forEach(g => {
|
newGraphs.forEach(g => {
|
||||||
const ep = this.instantiationService.createInstance(ExecutionPlanView, this._container, this._executionPlanViews.length + 1, this, this._queryResultsView);
|
const ep = this._register(this.instantiationService.createInstance(ExecutionPlanView, this._container, this._executionPlanViews.length + 1, this, this._queryResultsView));
|
||||||
ep.model = g;
|
ep.model = g;
|
||||||
this._executionPlanViews.push(ep);
|
this._executionPlanViews.push(ep);
|
||||||
this.graphs.push(g);
|
this.graphs.push(g);
|
||||||
@@ -76,10 +75,11 @@ export class ExecutionPlanFileView {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
public async loadGraphFile(graphFile: azdata.executionPlan.ExecutionPlanGraphInfo) {
|
public async loadGraphFile(graphFile: azdata.executionPlan.ExecutionPlanGraphInfo) {
|
||||||
this._loadingSpinner = new LoadingSpinner(this._container, { showText: true, fullSize: true });
|
this._loadingSpinner = this._register(new LoadingSpinner(this._container, { showText: true, fullSize: true }));
|
||||||
this._loadingSpinner.loadingMessage = localize('loadingExecutionPlanFile', "Generating execution plans");
|
this._loadingSpinner.loadingMessage = localize('loadingExecutionPlanFile', "Generating execution plans");
|
||||||
try {
|
try {
|
||||||
this._loadingSpinner.loading = true;
|
this._loadingSpinner.loading = true;
|
||||||
|
|
||||||
if (this._planCache.has(graphFile.graphFileContent)) {
|
if (this._planCache.has(graphFile.graphFileContent)) {
|
||||||
this.addGraphs(this._planCache.get(graphFile.graphFileContent));
|
this.addGraphs(this._planCache.get(graphFile.graphFileContent));
|
||||||
return;
|
return;
|
||||||
@@ -91,13 +91,15 @@ export class ExecutionPlanFileView {
|
|||||||
this.addGraphs(graphs);
|
this.addGraphs(graphs);
|
||||||
this._planCache.set(graphFile.graphFileContent, graphs);
|
this._planCache.set(graphFile.graphFileContent, graphs);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._loadingSpinner.loadingCompletedMessage = localize('executionPlanFileLoadingComplete', "Execution plans are generated");
|
this._loadingSpinner.loadingCompletedMessage = localize('executionPlanFileLoadingComplete', "Execution plans are generated");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._loadingErrorInfoBox = this.instantiationService.createInstance(InfoBox, this._container, {
|
this._loadingErrorInfoBox = this._register(this.instantiationService.createInstance(InfoBox, this._container, {
|
||||||
text: e.toString(),
|
text: e.toString(),
|
||||||
style: 'error',
|
style: 'error',
|
||||||
isClickable: false
|
isClickable: false
|
||||||
});
|
}));
|
||||||
|
|
||||||
this._loadingErrorInfoBox.isClickable = false;
|
this._loadingErrorInfoBox.isClickable = false;
|
||||||
this._loadingSpinner.loadingCompletedMessage = localize('executionPlanFileLoadingFailed', "Failed to load execution plan");
|
this._loadingSpinner.loadingCompletedMessage = localize('executionPlanFileLoadingFailed', "Failed to load execution plan");
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -116,11 +116,14 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
|
|||||||
if (!properties) {
|
if (!properties) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedProperties = this.sortProperties(properties);
|
const sortedProperties = this.sortProperties(properties);
|
||||||
const rows: Slick.SlickData[] = [];
|
const rows: Slick.SlickData[] = [];
|
||||||
|
|
||||||
sortedProperties.forEach((property, index) => {
|
sortedProperties.forEach((property, index) => {
|
||||||
let row = {};
|
let row = {};
|
||||||
row['name'] = property.name;
|
row['name'] = property.name;
|
||||||
|
|
||||||
if (!isString(property.value)) {
|
if (!isString(property.value)) {
|
||||||
// Styling values in the parent row differently to make them more apparent and standout compared to the rest of the cells.
|
// Styling values in the parent row differently to make them more apparent and standout compared to the rest of the cells.
|
||||||
row['name'] = {
|
row['name'] = {
|
||||||
@@ -131,12 +134,15 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
|
|||||||
};
|
};
|
||||||
row['tootltip'] = property.displayValue;
|
row['tootltip'] = property.displayValue;
|
||||||
row['treeGridChildren'] = this.convertPropertiesToTableRows(property.value);
|
row['treeGridChildren'] = this.convertPropertiesToTableRows(property.value);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
row['value'] = removeLineBreaks(property.displayValue, ' ');
|
row['value'] = removeLineBreaks(property.displayValue, ' ');
|
||||||
row['tooltip'] = property.displayValue;
|
row['tooltip'] = property.displayValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
});
|
});
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +171,7 @@ export class ExecutionPlanPropertiesView extends ExecutionPlanPropertiesViewBase
|
|||||||
rows.push(row);
|
rows.push(row);
|
||||||
row['name'] = p.name;
|
row['name'] = p.name;
|
||||||
row['parent'] = parentIndex;
|
row['parent'] = parentIndex;
|
||||||
|
|
||||||
if (!isString(p.value)) {
|
if (!isString(p.value)) {
|
||||||
// Styling values in the parent row differently to make them more apparent and standout compared to the rest of the cells.
|
// Styling values in the parent row differently to make them more apparent and standout compared to the rest of the cells.
|
||||||
row['name'] = {
|
row['name'] = {
|
||||||
|
|||||||
@@ -75,15 +75,19 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
|
|
||||||
this.resizeSash = this._register(new Sash(sashContainer, this, { orientation: Orientation.VERTICAL, size: 3 }));
|
this.resizeSash = this._register(new Sash(sashContainer, this, { orientation: Orientation.VERTICAL, size: 3 }));
|
||||||
let originalWidth = 0;
|
let originalWidth = 0;
|
||||||
|
|
||||||
this._register(this.resizeSash.onDidStart((e: ISashEvent) => {
|
this._register(this.resizeSash.onDidStart((e: ISashEvent) => {
|
||||||
originalWidth = this._parentContainer.clientWidth;
|
originalWidth = this._parentContainer.clientWidth;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._register(this.resizeSash.onDidChange((evt: ISashEvent) => {
|
this._register(this.resizeSash.onDidChange((evt: ISashEvent) => {
|
||||||
const change = evt.startX - evt.currentX;
|
const change = evt.startX - evt.currentX;
|
||||||
const newWidth = originalWidth + change;
|
const newWidth = originalWidth + change;
|
||||||
|
|
||||||
if (newWidth < 200) {
|
if (newWidth < 200) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._parentContainer.style.flex = `0 0 ${newWidth}px`;
|
this._parentContainer.style.flex = `0 0 ${newWidth}px`;
|
||||||
propertiesContent.style.width = `${newWidth}px`;
|
propertiesContent.style.width = `${newWidth}px`;
|
||||||
}));
|
}));
|
||||||
@@ -105,7 +109,7 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
this._titleActions = this._register(new ActionBar(this._titleBarActionsContainer, {
|
this._titleActions = this._register(new ActionBar(this._titleBarActionsContainer, {
|
||||||
orientation: ActionsOrientation.HORIZONTAL, context: this
|
orientation: ActionsOrientation.HORIZONTAL, context: this
|
||||||
}));
|
}));
|
||||||
this._titleActions.pushAction([new ClosePropertyViewAction()], { icon: true, label: false });
|
this._titleActions.pushAction([this._register(new ClosePropertyViewAction())], { icon: true, label: false });
|
||||||
|
|
||||||
this._headerContainer = DOM.$('.header');
|
this._headerContainer = DOM.$('.header');
|
||||||
propertiesContent.appendChild(this._headerContainer);
|
propertiesContent.appendChild(this._headerContainer);
|
||||||
@@ -115,24 +119,28 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
|
|
||||||
this._headerActionsContainer = DOM.$('.table-action-bar');
|
this._headerActionsContainer = DOM.$('.table-action-bar');
|
||||||
this._searchAndActionBarContainer.appendChild(this._headerActionsContainer);
|
this._searchAndActionBarContainer.appendChild(this._headerActionsContainer);
|
||||||
|
|
||||||
this._headerActions = this._register(new ActionBar(this._headerActionsContainer, {
|
this._headerActions = this._register(new ActionBar(this._headerActionsContainer, {
|
||||||
orientation: ActionsOrientation.HORIZONTAL, context: this
|
orientation: ActionsOrientation.HORIZONTAL, context: this
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._headerActions.pushAction([
|
this._headerActions.pushAction([
|
||||||
new SortPropertiesByDisplayOrderAction(),
|
this._register(new SortPropertiesByDisplayOrderAction()),
|
||||||
new SortPropertiesAlphabeticallyAction(),
|
this._register(new SortPropertiesAlphabeticallyAction()),
|
||||||
new SortPropertiesReverseAlphabeticallyAction(),
|
this._register(new SortPropertiesReverseAlphabeticallyAction()),
|
||||||
new ExpandAllPropertiesAction(),
|
this._register(new ExpandAllPropertiesAction()),
|
||||||
new CollapseAllPropertiesAction()
|
this._register(new CollapseAllPropertiesAction())
|
||||||
], { icon: true, label: false });
|
], { icon: true, label: false });
|
||||||
|
|
||||||
this._propertiesSearchInputContainer = DOM.$('.table-search');
|
this._propertiesSearchInputContainer = DOM.$('.table-search');
|
||||||
this._propertiesSearchInputContainer.classList.add('codicon', filterIconClassNames);
|
this._propertiesSearchInputContainer.classList.add('codicon', filterIconClassNames);
|
||||||
|
|
||||||
this._propertiesSearchInput = this._register(new InputBox(this._propertiesSearchInputContainer, this._contextViewService, {
|
this._propertiesSearchInput = this._register(new InputBox(this._propertiesSearchInputContainer, this._contextViewService, {
|
||||||
ariaDescription: propertiesSearchDescription,
|
ariaDescription: propertiesSearchDescription,
|
||||||
placeholder: searchPlaceholder
|
placeholder: searchPlaceholder
|
||||||
}));
|
}));
|
||||||
attachInputBoxStyler(this._propertiesSearchInput, this._themeService);
|
|
||||||
|
this._register(attachInputBoxStyler(this._propertiesSearchInput, this._themeService));
|
||||||
this._propertiesSearchInput.element.classList.add('codicon', filterIconClassNames);
|
this._propertiesSearchInput.element.classList.add('codicon', filterIconClassNames);
|
||||||
this._searchAndActionBarContainer.appendChild(this._propertiesSearchInputContainer);
|
this._searchAndActionBarContainer.appendChild(this._propertiesSearchInputContainer);
|
||||||
this._register(this._propertiesSearchInput.onDidChange(e => {
|
this._register(this._propertiesSearchInput.onDidChange(e => {
|
||||||
@@ -156,11 +164,12 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
editable: true,
|
editable: true,
|
||||||
autoEdit: false
|
autoEdit: false
|
||||||
}));
|
}));
|
||||||
attachTableStyler(this._tableComponent, this._themeService);
|
|
||||||
|
this._register(attachTableStyler(this._tableComponent, this._themeService));
|
||||||
this._tableComponent.setSelectionModel(this._selectionModel);
|
this._tableComponent.setSelectionModel(this._selectionModel);
|
||||||
|
|
||||||
const contextMenuAction = [
|
const contextMenuAction = [
|
||||||
this._instantiationService.createInstance(CopyTableData),
|
this._register(this._instantiationService.createInstance(CopyTableData)),
|
||||||
];
|
];
|
||||||
|
|
||||||
this._register(this._tableComponent.onContextMenu(e => {
|
this._register(this._tableComponent.onContextMenu(e => {
|
||||||
@@ -184,10 +193,10 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
}).observe(_parentContainer);
|
}).observe(_parentContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public getCopyString(): string {
|
public getCopyString(): string {
|
||||||
const selectedDataRange = this._selectionModel.getSelectedRanges()[0];
|
|
||||||
let csvString = '';
|
let csvString = '';
|
||||||
|
|
||||||
|
const selectedDataRange = this._selectionModel.getSelectedRanges()[0];
|
||||||
if (selectedDataRange) {
|
if (selectedDataRange) {
|
||||||
const data = [];
|
const data = [];
|
||||||
|
|
||||||
@@ -204,10 +213,12 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
}
|
}
|
||||||
data.push(row);
|
data.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
csvString = data.map(row =>
|
csvString = data.map(row =>
|
||||||
row.map(x => `${x}`).join('\t')
|
row.map(x => `${x}`).join('\t')
|
||||||
).join('\n');
|
).join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
return csvString;
|
return csvString;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,6 +326,7 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
-1)
|
-1)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._tableComponent.rerenderGrid();
|
this._tableComponent.rerenderGrid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,15 +334,18 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
let resultData: Slick.SlickData[] = [];
|
let resultData: Slick.SlickData[] = [];
|
||||||
data.forEach(dataRow => {
|
data.forEach(dataRow => {
|
||||||
let includeRow = false;
|
let includeRow = false;
|
||||||
|
|
||||||
const columns = this._tableComponent.grid.getColumns();
|
const columns = this._tableComponent.grid.getColumns();
|
||||||
for (let i = 0; i < columns.length; i++) {
|
for (let i = 0; i < columns.length; i++) {
|
||||||
let dataValue = '';
|
let dataValue = '';
|
||||||
|
|
||||||
let rawDataValue = dataRow[columns[i].field];
|
let rawDataValue = dataRow[columns[i].field];
|
||||||
if (isString(rawDataValue)) {
|
if (isString(rawDataValue)) {
|
||||||
dataValue = rawDataValue;
|
dataValue = rawDataValue;
|
||||||
} else if (rawDataValue !== undefined) {
|
} else if (rawDataValue !== undefined) {
|
||||||
dataValue = rawDataValue.text ?? rawDataValue.title;
|
dataValue = rawDataValue.text ?? rawDataValue.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataValue?.toLowerCase().includes(search.toLowerCase())) {
|
if (dataValue?.toLowerCase().includes(search.toLowerCase())) {
|
||||||
includeRow = true;
|
includeRow = true;
|
||||||
break;
|
break;
|
||||||
@@ -348,9 +363,11 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
if (rowClone['treeGridChildren'] !== undefined) {
|
if (rowClone['treeGridChildren'] !== undefined) {
|
||||||
rowClone['expanded'] = true;
|
rowClone['expanded'] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
resultData.push(rowClone);
|
resultData.push(rowClone);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return { include: resultData.length > 0, data: resultData };
|
return { include: resultData.length > 0, data: resultData };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -358,13 +375,16 @@ export abstract class ExecutionPlanPropertiesViewBase extends Disposable impleme
|
|||||||
if (nestedData === undefined || nestedData.length === 0) {
|
if (nestedData === undefined || nestedData.length === 0) {
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
nestedData.forEach((dataRow) => {
|
nestedData.forEach((dataRow) => {
|
||||||
rows.push(dataRow);
|
rows.push(dataRow);
|
||||||
dataRow['parent'] = parentIndex;
|
dataRow['parent'] = parentIndex;
|
||||||
|
|
||||||
if (dataRow['treeGridChildren']) {
|
if (dataRow['treeGridChildren']) {
|
||||||
this.flattenTableData(dataRow['treeGridChildren'], rows.length - 1, rows);
|
this.flattenTableData(dataRow['treeGridChildren'], rows.length - 1, rows);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { ExecutionPlanFileView } from 'sql/workbench/contrib/executionPlan/brows
|
|||||||
import { ExecutionPlanFileViewCache } from 'sql/workbench/contrib/executionPlan/browser/executionPlanFileViewCache';
|
import { ExecutionPlanFileViewCache } from 'sql/workbench/contrib/executionPlan/browser/executionPlanFileViewCache';
|
||||||
import { generateUuid } from 'vs/base/common/uuid';
|
import { generateUuid } from 'vs/base/common/uuid';
|
||||||
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
|
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
|
||||||
|
import { Disposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
export class ExecutionPlanTab implements IPanelTab {
|
export class ExecutionPlanTab implements IPanelTab {
|
||||||
public readonly title = localize('executionPlanTitle', "Query Plan (Preview)");
|
public readonly title = localize('executionPlanTitle', "Query Plan (Preview)");
|
||||||
@@ -27,6 +28,7 @@ export class ExecutionPlanTab implements IPanelTab {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
|
dispose(this.view);
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear() {
|
public clear() {
|
||||||
@@ -35,7 +37,7 @@ export class ExecutionPlanTab implements IPanelTab {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExecutionPlanTabView implements IPanelView {
|
export class ExecutionPlanTabView extends Disposable implements IPanelView {
|
||||||
private _container: HTMLElement = DOM.$('.execution-plan-tab');
|
private _container: HTMLElement = DOM.$('.execution-plan-tab');
|
||||||
private _input: ExecutionPlanState;
|
private _input: ExecutionPlanState;
|
||||||
private _viewCache: ExecutionPlanFileViewCache = ExecutionPlanFileViewCache.getInstance();
|
private _viewCache: ExecutionPlanFileViewCache = ExecutionPlanFileViewCache.getInstance();
|
||||||
@@ -45,6 +47,7 @@ export class ExecutionPlanTabView implements IPanelView {
|
|||||||
private _queryResultsView: QueryResultsView,
|
private _queryResultsView: QueryResultsView,
|
||||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public set state(newInput: ExecutionPlanState) {
|
public set state(newInput: ExecutionPlanState) {
|
||||||
@@ -63,7 +66,7 @@ export class ExecutionPlanTabView implements IPanelView {
|
|||||||
} else {
|
} else {
|
||||||
// creating a new view for the new input
|
// creating a new view for the new input
|
||||||
newInput.executionPlanFileViewUUID = generateUuid();
|
newInput.executionPlanFileViewUUID = generateUuid();
|
||||||
newView = this._instantiationService.createInstance(ExecutionPlanFileView, this._queryResultsView);
|
newView = this._register(this._instantiationService.createInstance(ExecutionPlanFileView, this._queryResultsView));
|
||||||
newView.onShow(this._container);
|
newView.onShow(this._container);
|
||||||
newView.addGraphs(
|
newView.addGraphs(
|
||||||
newInput.graphs
|
newInput.graphs
|
||||||
@@ -88,7 +91,7 @@ export class ExecutionPlanTabView implements IPanelView {
|
|||||||
if (currentView) {
|
if (currentView) {
|
||||||
currentView.onHide(this._container);
|
currentView.onHide(this._container);
|
||||||
this._input.graphs = [];
|
this._input.graphs = [];
|
||||||
currentView = this._instantiationService.createInstance(ExecutionPlanFileView, this._queryResultsView);
|
currentView = this._register(this._instantiationService.createInstance(ExecutionPlanFileView, this._queryResultsView));
|
||||||
this._viewCache.executionPlanFileViewMap.set(this._input.executionPlanFileViewUUID, currentView);
|
this._viewCache.executionPlanFileViewMap.set(this._input.executionPlanFileViewUUID, currentView);
|
||||||
currentView.render(this._container);
|
currentView.render(this._container);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
while (this._container.firstChild) {
|
while (this._container.firstChild) {
|
||||||
this._container.removeChild(this._container.firstChild);
|
this._container.removeChild(this._container.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._input.graphs.forEach((g, i) => {
|
this._input.graphs.forEach((g, i) => {
|
||||||
this.convertExecutionPlanGraphToTreeGrid(g, i);
|
this.convertExecutionPlanGraphToTreeGrid(g, i);
|
||||||
});
|
});
|
||||||
@@ -84,13 +85,16 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
let dataMap: { [key: string]: any }[] = [];
|
let dataMap: { [key: string]: any }[] = [];
|
||||||
const columnValues: string[] = [];
|
const columnValues: string[] = [];
|
||||||
const stack: { node: azdata.executionPlan.ExecutionPlanNode, parentIndex: number }[] = [];
|
const stack: { node: azdata.executionPlan.ExecutionPlanNode, parentIndex: number }[] = [];
|
||||||
|
|
||||||
stack.push({
|
stack.push({
|
||||||
node: graph.root,
|
node: graph.root,
|
||||||
parentIndex: -1,
|
parentIndex: -1,
|
||||||
});
|
});
|
||||||
|
|
||||||
while (stack.length !== 0) {
|
while (stack.length !== 0) {
|
||||||
const treeGridNode = stack.pop();
|
const treeGridNode = stack.pop();
|
||||||
const row: { [key: string]: any } = {};
|
const row: { [key: string]: any } = {};
|
||||||
|
|
||||||
treeGridNode.node.topOperationsData.forEach((d, i) => {
|
treeGridNode.node.topOperationsData.forEach((d, i) => {
|
||||||
let displayText = d.displayValue.toString();
|
let displayText = d.displayValue.toString();
|
||||||
|
|
||||||
@@ -104,10 +108,12 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
columnValues.splice(i, 0, d.columnName);
|
columnValues.splice(i, 0, d.columnName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
row['nodeId'] = treeGridNode.node.id;
|
row['nodeId'] = treeGridNode.node.id;
|
||||||
row['parent'] = treeGridNode.parentIndex;
|
row['parent'] = treeGridNode.parentIndex;
|
||||||
row['parentNodeId'] = dataMap[treeGridNode.parentIndex] ? dataMap[treeGridNode.parentIndex]['nodeId'] : undefined;
|
row['parentNodeId'] = dataMap[treeGridNode.parentIndex] ? dataMap[treeGridNode.parentIndex]['nodeId'] : undefined;
|
||||||
row['expanded'] = true;
|
row['expanded'] = true;
|
||||||
|
|
||||||
if (treeGridNode.node.children) {
|
if (treeGridNode.node.children) {
|
||||||
treeGridNode.node.children.forEach(c => stack.push({
|
treeGridNode.node.children.forEach(c => stack.push({
|
||||||
node: c,
|
node: c,
|
||||||
@@ -132,26 +138,29 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
|
|
||||||
const topOperationContainer = DOM.$('.top-operations-container');
|
const topOperationContainer = DOM.$('.top-operations-container');
|
||||||
this._container.appendChild(topOperationContainer);
|
this._container.appendChild(topOperationContainer);
|
||||||
const header = this._instantiationService.createInstance(ExecutionPlanViewHeader, topOperationContainer, {
|
|
||||||
|
const header = this._register(this._instantiationService.createInstance(ExecutionPlanViewHeader, topOperationContainer, {
|
||||||
planIndex: index,
|
planIndex: index,
|
||||||
});
|
}));
|
||||||
header.query = graph.query;
|
header.query = graph.query;
|
||||||
header.relativeCost = graph.root.relativeCost;
|
header.relativeCost = graph.root.relativeCost;
|
||||||
|
|
||||||
const tableContainer = DOM.$('.table-container');
|
const tableContainer = DOM.$('.table-container');
|
||||||
topOperationContainer.appendChild(tableContainer);
|
topOperationContainer.appendChild(tableContainer);
|
||||||
this._planTreeContainers.push(topOperationContainer);
|
this._planTreeContainers.push(topOperationContainer);
|
||||||
|
|
||||||
let copyHandler = new CopyKeybind<any>();
|
let copyHandler = new CopyKeybind<any>();
|
||||||
this._register(copyHandler.onCopy(e => {
|
this._register(copyHandler.onCopy(e => {
|
||||||
|
let csvString = '';
|
||||||
|
|
||||||
const selectedDataRange = selectionModel.getSelectedRanges()[0];
|
const selectedDataRange = selectionModel.getSelectedRanges()[0];
|
||||||
let csvString = '';
|
|
||||||
if (selectedDataRange) {
|
if (selectedDataRange) {
|
||||||
const data = [];
|
const data = [];
|
||||||
|
|
||||||
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
|
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
|
||||||
const dataRow = treeGrid.getData().getItem(rowIndex);
|
const dataRow = treeGrid.getData().getItem(rowIndex);
|
||||||
const row = [];
|
const row = [];
|
||||||
|
|
||||||
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
||||||
const dataItem = dataRow[treeGrid.grid.getColumns()[colIndex].field];
|
const dataItem = dataRow[treeGrid.grid.getColumns()[colIndex].field];
|
||||||
if (dataItem) {
|
if (dataItem) {
|
||||||
@@ -160,8 +169,10 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
row.push(' ');
|
row.push(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.push(row);
|
data.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
csvString = data.map(row =>
|
csvString = data.map(row =>
|
||||||
row.map(x => `${x}`).join('\t')
|
row.map(x => `${x}`).join('\t')
|
||||||
).join('\n');
|
).join('\n');
|
||||||
@@ -174,21 +185,23 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._instantiationService.createInstance(CopyTableData).run({
|
this._register(this._instantiationService.createInstance(CopyTableData)).run({
|
||||||
selectedText: csvString
|
selectedText: csvString
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const selectionModel = new CellSelectionModel<Slick.SlickData>();
|
const selectionModel = new CellSelectionModel<Slick.SlickData>();
|
||||||
|
|
||||||
const treeGrid = new TreeGrid<Slick.SlickData>(tableContainer, {
|
const treeGrid = this._register(new TreeGrid<Slick.SlickData>(tableContainer, {
|
||||||
columns: columns,
|
columns: columns,
|
||||||
sorter: (args) => {
|
sorter: (args) => {
|
||||||
const sortColumn = args.sortCol.field;
|
const sortColumn = args.sortCol.field;
|
||||||
|
|
||||||
let data = deepClone(dataMap);
|
let data = deepClone(dataMap);
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
data = treeGrid.getData().getItems();
|
data = this._register(treeGrid.getData()).getItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedData = [];
|
const sortedData = [];
|
||||||
const rootRow = data[0];
|
const rootRow = data[0];
|
||||||
const stack: { row: Slick.SlickData, originalIndex: number }[] = [];
|
const stack: { row: Slick.SlickData, originalIndex: number }[] = [];
|
||||||
@@ -196,8 +209,9 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
|
|
||||||
while (stack.length !== 0) {
|
while (stack.length !== 0) {
|
||||||
const currentTreeGridRow = stack.pop();
|
const currentTreeGridRow = stack.pop();
|
||||||
let currentTreeGridRowChildren: { row: Slick.SlickData, originalIndex: number }[] = [];
|
|
||||||
sortedData.push(currentTreeGridRow.row);
|
sortedData.push(currentTreeGridRow.row);
|
||||||
|
|
||||||
|
let currentTreeGridRowChildren: { row: Slick.SlickData, originalIndex: number }[] = [];
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
if (data[i].parentNodeId === currentTreeGridRow.row.nodeId) {
|
if (data[i].parentNodeId === currentTreeGridRow.row.nodeId) {
|
||||||
currentTreeGridRowChildren.push({ row: data[i], originalIndex: i });
|
currentTreeGridRowChildren.push({ row: data[i], originalIndex: i });
|
||||||
@@ -207,6 +221,7 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
currentTreeGridRowChildren = currentTreeGridRowChildren.sort((a, b) => {
|
currentTreeGridRowChildren = currentTreeGridRowChildren.sort((a, b) => {
|
||||||
const aRow = a.row;
|
const aRow = a.row;
|
||||||
const bRow = b.row;
|
const bRow = b.row;
|
||||||
|
|
||||||
let result = -1;
|
let result = -1;
|
||||||
if (!aRow[sortColumn]) {
|
if (!aRow[sortColumn]) {
|
||||||
result = 1;
|
result = 1;
|
||||||
@@ -217,6 +232,7 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
const dataType = aRow[sortColumn].dataType;
|
const dataType = aRow[sortColumn].dataType;
|
||||||
const aText = aRow[sortColumn].text;
|
const aText = aRow[sortColumn].text;
|
||||||
const bText = bRow[sortColumn].text;
|
const bText = bRow[sortColumn].text;
|
||||||
|
|
||||||
if (aText === bText) {
|
if (aText === bText) {
|
||||||
result = 0;
|
result = 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -232,6 +248,7 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return args.sortAsc ? result : -result;
|
return args.sortAsc ? result : -result;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -241,6 +258,7 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
|
|
||||||
stack.push(...currentTreeGridRowChildren);
|
stack.push(...currentTreeGridRowChildren);
|
||||||
}
|
}
|
||||||
|
|
||||||
dataMap = sortedData;
|
dataMap = sortedData;
|
||||||
treeGrid.setData(sortedData);
|
treeGrid.setData(sortedData);
|
||||||
}
|
}
|
||||||
@@ -249,24 +267,23 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
forceFitColumns: false,
|
forceFitColumns: false,
|
||||||
defaultColumnWidth: 120,
|
defaultColumnWidth: 120,
|
||||||
showRowNumber: true
|
showRowNumber: true
|
||||||
});
|
}));
|
||||||
|
|
||||||
treeGrid.setSelectionModel(selectionModel);
|
treeGrid.setSelectionModel(selectionModel);
|
||||||
treeGrid.setData(dataMap);
|
treeGrid.setData(dataMap);
|
||||||
|
|
||||||
treeGrid.registerPlugin(copyHandler);
|
treeGrid.registerPlugin(copyHandler);
|
||||||
|
|
||||||
treeGrid.setTableTitle(localize('topOperationsTableTitle', "Execution Plan Tree"));
|
treeGrid.setTableTitle(localize('topOperationsTableTitle', "Execution Plan Tree"));
|
||||||
|
|
||||||
this._treeGrids.push(treeGrid);
|
this._treeGrids.push(treeGrid);
|
||||||
|
|
||||||
const contextMenuAction = [
|
const contextMenuAction = [
|
||||||
this._instantiationService.createInstance(CopyTableData),
|
this._register(this._instantiationService.createInstance(CopyTableData)),
|
||||||
this._instantiationService.createInstance(CopyTableDataWithHeader),
|
this._register(this._instantiationService.createInstance(CopyTableDataWithHeader)),
|
||||||
this._instantiationService.createInstance(SelectAll)
|
this._register(this._instantiationService.createInstance(SelectAll))
|
||||||
];
|
];
|
||||||
|
|
||||||
this._register(treeGrid.onKeyDown((evt: ITableKeyboardEvent) => {
|
this._register(treeGrid.onKeyDown((evt: ITableKeyboardEvent) => {
|
||||||
if (evt.event.ctrlKey && (evt.event.key === 'a' || evt.event.key === 'A')) {
|
if (evt.event.ctrlKey && (evt.event.key === 'a' || evt.event.key === 'A')) {
|
||||||
selectionModel.setSelectedRanges([new Slick.Range(0, 0, treeGrid.getData().getLength() - 1, treeGrid.grid.getColumns().length - 1)]);
|
selectionModel.setSelectedRanges([new Slick.Range(0, 0, this._register(treeGrid.getData()).getLength() - 1, treeGrid.grid.getColumns().length - 1)]);
|
||||||
treeGrid.focus();
|
treeGrid.focus();
|
||||||
evt.event.preventDefault();
|
evt.event.preventDefault();
|
||||||
evt.event.stopPropagation();
|
evt.event.stopPropagation();
|
||||||
@@ -274,14 +291,16 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
this._register(treeGrid.onContextMenu(e => {
|
this._register(treeGrid.onContextMenu(e => {
|
||||||
const selectedDataRange = selectionModel.getSelectedRanges()[0];
|
|
||||||
let csvString = '';
|
let csvString = '';
|
||||||
let csvStringWithHeader = '';
|
let csvStringWithHeader = '';
|
||||||
|
|
||||||
|
const selectedDataRange = selectionModel.getSelectedRanges()[0];
|
||||||
if (selectedDataRange) {
|
if (selectedDataRange) {
|
||||||
const data = [];
|
const data = [];
|
||||||
|
|
||||||
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
|
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
|
||||||
const dataRow = treeGrid.getData().getItem(rowIndex);
|
const dataRow = this._register(treeGrid.getData()).getItem(rowIndex); // TODO lewissanchez: ask if it's okay to register disposable data providers like this.
|
||||||
|
|
||||||
const row = [];
|
const row = [];
|
||||||
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
||||||
const dataItem = dataRow[treeGrid.grid.getColumns()[colIndex].field];
|
const dataItem = dataRow[treeGrid.grid.getColumns()[colIndex].field];
|
||||||
@@ -291,14 +310,15 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
row.push('');
|
row.push('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.push(row);
|
data.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
csvString = data.map(row =>
|
csvString = data.map(row =>
|
||||||
row.map(x => `${x}`).join('\t')
|
row.map(x => `${x}`).join('\t')
|
||||||
).join('\n');
|
).join('\n');
|
||||||
|
|
||||||
const columns = [];
|
const columns = [];
|
||||||
|
|
||||||
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
||||||
columns.push(treeGrid.grid.getColumns()[colIndex].name);
|
columns.push(treeGrid.grid.getColumns()[colIndex].name);
|
||||||
}
|
}
|
||||||
@@ -318,11 +338,13 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
});
|
});
|
||||||
|
|
||||||
}));
|
}));
|
||||||
attachTableStyler(treeGrid, this._themeService);
|
|
||||||
|
this._register(attachTableStyler(treeGrid, this._themeService));
|
||||||
|
|
||||||
new ResizeObserver((e) => {
|
new ResizeObserver((e) => {
|
||||||
treeGrid.layout(new DOM.Dimension(tableContainer.clientWidth, tableContainer.clientHeight));
|
treeGrid.layout(new DOM.Dimension(tableContainer.clientWidth, tableContainer.clientHeight));
|
||||||
}).observe(tableContainer);
|
}).observe(tableContainer);
|
||||||
|
|
||||||
return treeGrid;
|
return treeGrid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,10 +352,13 @@ export class ExecutionPlanTreeTabView extends Disposable implements IPanelView {
|
|||||||
this._container.style.width = dimension.width + 'px';
|
this._container.style.width = dimension.width + 'px';
|
||||||
this._container.style.height = dimension.height + 'px';
|
this._container.style.height = dimension.height + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
remove?(): void {
|
remove?(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
onShow?(): void {
|
onShow?(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
onHide?(): void {
|
onHide?(): void {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,14 +367,11 @@ export class CopyTableData extends Action {
|
|||||||
public static ID = 'ept.CopyTableData';
|
public static ID = 'ept.CopyTableData';
|
||||||
public static LABEL = localize('ept.topOperationsCopyTableData', "Copy");
|
public static LABEL = localize('ept.topOperationsCopyTableData', "Copy");
|
||||||
|
|
||||||
constructor(
|
constructor(@IClipboardService private _clipboardService: IClipboardService) {
|
||||||
@IClipboardService private _clipboardService: IClipboardService
|
|
||||||
) {
|
|
||||||
super(CopyTableData.ID, CopyTableData.LABEL, '');
|
super(CopyTableData.ID, CopyTableData.LABEL, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ContextMenuModel): Promise<void> {
|
public override async run(context: ContextMenuModel): Promise<void> {
|
||||||
|
|
||||||
this._clipboardService.writeText(context.selectedText);
|
this._clipboardService.writeText(context.selectedText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -358,14 +380,11 @@ export class CopyTableDataWithHeader extends Action {
|
|||||||
public static ID = 'ept.CopyTableDataWithHeader';
|
public static ID = 'ept.CopyTableDataWithHeader';
|
||||||
public static LABEL = localize('ept.topOperationsCopyWithHeader', "Copy with Header");
|
public static LABEL = localize('ept.topOperationsCopyWithHeader', "Copy with Header");
|
||||||
|
|
||||||
constructor(
|
constructor(@IClipboardService private _clipboardService: IClipboardService) {
|
||||||
@IClipboardService private _clipboardService: IClipboardService
|
|
||||||
) {
|
|
||||||
super(CopyTableDataWithHeader.ID, CopyTableDataWithHeader.LABEL, '');
|
super(CopyTableDataWithHeader.ID, CopyTableDataWithHeader.LABEL, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async run(context: ContextMenuModel): Promise<void> {
|
public override async run(context: ContextMenuModel): Promise<void> {
|
||||||
|
|
||||||
this._clipboardService.writeText(context.selectionTextWithHeader);
|
this._clipboardService.writeText(context.selectionTextWithHeader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,8 +393,7 @@ export class SelectAll extends Action {
|
|||||||
public static ID = 'ept.SelectAllTableData';
|
public static ID = 'ept.SelectAllTableData';
|
||||||
public static LABEL = localize('ept.topOperationsSelectAll', "Select All");
|
public static LABEL = localize('ept.topOperationsSelectAll', "Select All");
|
||||||
|
|
||||||
constructor(
|
constructor() {
|
||||||
) {
|
|
||||||
super(SelectAll.ID, SelectAll.LABEL, '');
|
super(SelectAll.ID, SelectAll.LABEL, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ import { ExecutionPlanComparisonInput } from 'sql/workbench/contrib/executionPla
|
|||||||
import { ExecutionPlanFileView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanFileView';
|
import { ExecutionPlanFileView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanFileView';
|
||||||
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
|
import { QueryResultsView } from 'sql/workbench/contrib/query/browser/queryResultsView';
|
||||||
import { HighlightExpensiveOperationWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/highlightExpensiveNodeWidget';
|
import { HighlightExpensiveOperationWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/highlightExpensiveNodeWidget';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
export class ExecutionPlanView implements ISashLayoutProvider {
|
export class ExecutionPlanView extends Disposable implements ISashLayoutProvider {
|
||||||
|
|
||||||
// Underlying execution plan displayed in the view
|
// Underlying execution plan displayed in the view
|
||||||
private _model?: azdata.executionPlan.ExecutionPlanGraph;
|
private _model?: azdata.executionPlan.ExecutionPlanGraph;
|
||||||
@@ -87,6 +88,8 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
@IWorkspaceContextService public workspaceContextService: IWorkspaceContextService,
|
@IWorkspaceContextService public workspaceContextService: IWorkspaceContextService,
|
||||||
@IEditorService private _editorService: IEditorService
|
@IEditorService private _editorService: IEditorService
|
||||||
) {
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
// parent container for query plan.
|
// parent container for query plan.
|
||||||
this.container = DOM.$('.execution-plan');
|
this.container = DOM.$('.execution-plan');
|
||||||
this._parent.appendChild(this.container);
|
this._parent.appendChild(this.container);
|
||||||
@@ -94,19 +97,20 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
this._parent.appendChild(sashContainer);
|
this._parent.appendChild(sashContainer);
|
||||||
|
|
||||||
// resizing sash for the query plan.
|
// resizing sash for the query plan.
|
||||||
const sash = new Sash(sashContainer, this, { orientation: Orientation.HORIZONTAL, size: 3 });
|
const sash = this._register(new Sash(sashContainer, this, { orientation: Orientation.HORIZONTAL, size: 3 }));
|
||||||
let originalHeight = this.container.offsetHeight;
|
let originalHeight = this.container.offsetHeight;
|
||||||
let originalTableHeight = 0;
|
let originalTableHeight = 0;
|
||||||
let change = 0;
|
let change = 0;
|
||||||
sash.onDidStart((e: ISashEvent) => {
|
|
||||||
|
this._register(sash.onDidStart((e: ISashEvent) => {
|
||||||
originalHeight = this.container.offsetHeight;
|
originalHeight = this.container.offsetHeight;
|
||||||
originalTableHeight = this.propertiesView.tableHeight;
|
originalTableHeight = this.propertiesView.tableHeight;
|
||||||
});
|
}));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using onDidChange for the smooth resizing of the graph diagram
|
* Using onDidChange for the smooth resizing of the graph diagram
|
||||||
*/
|
*/
|
||||||
sash.onDidChange((evt: ISashEvent) => {
|
this._register(sash.onDidChange((evt: ISashEvent) => {
|
||||||
change = evt.startY - evt.currentY;
|
change = evt.startY - evt.currentY;
|
||||||
const newHeight = originalHeight - change;
|
const newHeight = originalHeight - change;
|
||||||
if (newHeight < 200) {
|
if (newHeight < 200) {
|
||||||
@@ -118,14 +122,14 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
*/
|
*/
|
||||||
this.container.style.minHeight = '200px';
|
this.container.style.minHeight = '200px';
|
||||||
this.container.style.flex = `0 0 ${newHeight}px`;
|
this.container.style.flex = `0 0 ${newHeight}px`;
|
||||||
});
|
}));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizing properties window table only once at the end as it is a heavy operation and worsens the smooth resizing experience
|
* Resizing properties window table only once at the end as it is a heavy operation and worsens the smooth resizing experience
|
||||||
*/
|
*/
|
||||||
sash.onDidEnd(() => {
|
this._register(sash.onDidEnd(() => {
|
||||||
this.propertiesView.tableHeight = originalTableHeight - change;
|
this.propertiesView.tableHeight = originalTableHeight - change;
|
||||||
});
|
}));
|
||||||
|
|
||||||
this._planContainer = DOM.$('.plan');
|
this._planContainer = DOM.$('.plan');
|
||||||
this.container.appendChild(this._planContainer);
|
this.container.appendChild(this._planContainer);
|
||||||
@@ -139,14 +143,14 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
this._planHeaderContainer.style.fontWeight = EDITOR_FONT_DEFAULTS.fontWeight;
|
this._planHeaderContainer.style.fontWeight = EDITOR_FONT_DEFAULTS.fontWeight;
|
||||||
|
|
||||||
this._planContainer.appendChild(this._planHeaderContainer);
|
this._planContainer.appendChild(this._planHeaderContainer);
|
||||||
this.planHeader = this._instantiationService.createInstance(ExecutionPlanViewHeader, this._planHeaderContainer, {
|
this.planHeader = this._register(this._instantiationService.createInstance(ExecutionPlanViewHeader, this._planHeaderContainer, {
|
||||||
planIndex: this._graphIndex,
|
planIndex: this._graphIndex,
|
||||||
});
|
}));
|
||||||
|
|
||||||
// container properties
|
// container properties
|
||||||
this._propContainer = DOM.$('.properties');
|
this._propContainer = DOM.$('.properties');
|
||||||
this.container.appendChild(this._propContainer);
|
this.container.appendChild(this._propContainer);
|
||||||
this.propertiesView = this._instantiationService.createInstance(ExecutionPlanPropertiesView, this._propContainer);
|
this.propertiesView = this._register(this._instantiationService.createInstance(ExecutionPlanPropertiesView, this._propContainer));
|
||||||
|
|
||||||
this._widgetContainer = DOM.$('.plan-action-container');
|
this._widgetContainer = DOM.$('.plan-action-container');
|
||||||
this._planContainer.appendChild(this._widgetContainer);
|
this._planContainer.appendChild(this._widgetContainer);
|
||||||
@@ -155,56 +159,56 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
// container that holds action bar icons
|
// container that holds action bar icons
|
||||||
this._actionBarContainer = DOM.$('.action-bar-container');
|
this._actionBarContainer = DOM.$('.action-bar-container');
|
||||||
this.container.appendChild(this._actionBarContainer);
|
this.container.appendChild(this._actionBarContainer);
|
||||||
this._actionBar = new ActionBar(this._actionBarContainer, {
|
this._actionBar = this._register(new ActionBar(this._actionBarContainer, {
|
||||||
orientation: ActionsOrientation.VERTICAL, context: this
|
orientation: ActionsOrientation.VERTICAL, context: this
|
||||||
});
|
}));
|
||||||
|
|
||||||
this.actionBarToggleTopTip = new ActionBarToggleTooltip();
|
this.actionBarToggleTopTip = this._register(new ActionBarToggleTooltip());
|
||||||
const actionBarActions = [
|
const actionBarActions = [
|
||||||
new SavePlanFile(),
|
this._register(new SavePlanFile()),
|
||||||
new OpenPlanFile(),
|
this._register(new OpenPlanFile()),
|
||||||
this._instantiationService.createInstance(OpenQueryAction, 'ActionBar'),
|
this._register(this._instantiationService.createInstance(OpenQueryAction, 'ActionBar')),
|
||||||
new Separator(),
|
this._register(new Separator()),
|
||||||
this._instantiationService.createInstance(ZoomInAction, 'ActionBar'),
|
this._register(this._instantiationService.createInstance(ZoomInAction, 'ActionBar')),
|
||||||
this._instantiationService.createInstance(ZoomOutAction, 'ActionBar'),
|
this._register(this._instantiationService.createInstance(ZoomOutAction, 'ActionBar')),
|
||||||
this._instantiationService.createInstance(ZoomToFitAction, 'ActionBar'),
|
this._register(this._instantiationService.createInstance(ZoomToFitAction, 'ActionBar')),
|
||||||
this._instantiationService.createInstance(CustomZoomAction, 'ActionBar'),
|
this._register(this._instantiationService.createInstance(CustomZoomAction, 'ActionBar')),
|
||||||
new Separator(),
|
this._register(new Separator()),
|
||||||
this._instantiationService.createInstance(SearchNodeAction, 'ActionBar'),
|
this._register(this._instantiationService.createInstance(SearchNodeAction, 'ActionBar')),
|
||||||
this._instantiationService.createInstance(PropertiesAction, 'ActionBar'),
|
this._register(this._instantiationService.createInstance(PropertiesAction, 'ActionBar')),
|
||||||
this._instantiationService.createInstance(CompareExecutionPlanAction, 'ActionBar'),
|
this._register(this._instantiationService.createInstance(CompareExecutionPlanAction, 'ActionBar')),
|
||||||
this._instantiationService.createInstance(HighlightExpensiveOperationAction, 'ActionBar'),
|
this._register(this._instantiationService.createInstance(HighlightExpensiveOperationAction, 'ActionBar')),
|
||||||
this.actionBarToggleTopTip
|
this.actionBarToggleTopTip
|
||||||
];
|
];
|
||||||
// Setting up context menu
|
// Setting up context menu
|
||||||
this.contextMenuToggleTooltipAction = new ContextMenuTooltipToggle();
|
this.contextMenuToggleTooltipAction = this._register(new ContextMenuTooltipToggle());
|
||||||
const contextMenuAction = [
|
const contextMenuAction = [
|
||||||
new SavePlanFile(),
|
this._register(new SavePlanFile()),
|
||||||
new OpenPlanFile(),
|
this._register(new OpenPlanFile()),
|
||||||
this._instantiationService.createInstance(OpenQueryAction, 'ContextMenu'),
|
this._register(this._instantiationService.createInstance(OpenQueryAction, 'ContextMenu')),
|
||||||
new Separator(),
|
this._register(new Separator()),
|
||||||
this._instantiationService.createInstance(ZoomInAction, 'ContextMenu'),
|
this._register(this._instantiationService.createInstance(ZoomInAction, 'ContextMenu')),
|
||||||
this._instantiationService.createInstance(ZoomOutAction, 'ContextMenu'),
|
this._register(this._instantiationService.createInstance(ZoomOutAction, 'ContextMenu')),
|
||||||
this._instantiationService.createInstance(ZoomToFitAction, 'ContextMenu'),
|
this._register(this._instantiationService.createInstance(ZoomToFitAction, 'ContextMenu')),
|
||||||
this._instantiationService.createInstance(CustomZoomAction, 'ContextMenu'),
|
this._register(this._instantiationService.createInstance(CustomZoomAction, 'ContextMenu')),
|
||||||
new Separator(),
|
this._register(new Separator()),
|
||||||
this._instantiationService.createInstance(SearchNodeAction, 'ContextMenu'),
|
this._register(this._instantiationService.createInstance(SearchNodeAction, 'ContextMenu')),
|
||||||
this._instantiationService.createInstance(PropertiesAction, 'ContextMenu'),
|
this._register(this._instantiationService.createInstance(PropertiesAction, 'ContextMenu')),
|
||||||
this._instantiationService.createInstance(CompareExecutionPlanAction, 'ContextMenu'),
|
this._register(this._instantiationService.createInstance(CompareExecutionPlanAction, 'ContextMenu')),
|
||||||
this._instantiationService.createInstance(HighlightExpensiveOperationAction, 'ContextMenu'),
|
this._register(this._instantiationService.createInstance(HighlightExpensiveOperationAction, 'ContextMenu')),
|
||||||
this.contextMenuToggleTooltipAction,
|
this.contextMenuToggleTooltipAction,
|
||||||
new Separator(),
|
this._register(new Separator()),
|
||||||
];
|
];
|
||||||
|
|
||||||
if (this._queryResultsView) {
|
if (this._queryResultsView) {
|
||||||
actionBarActions.push(this._instantiationService.createInstance(TopOperationsAction));
|
actionBarActions.push(this._register(this._instantiationService.createInstance(TopOperationsAction)));
|
||||||
contextMenuAction.push(this._instantiationService.createInstance(TopOperationsAction));
|
contextMenuAction.push(this._register(this._instantiationService.createInstance(TopOperationsAction)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this._actionBar.pushAction(actionBarActions, { icon: true, label: false });
|
this._actionBar.pushAction(actionBarActions, { icon: true, label: false });
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this._planContainer.oncontextmenu = (e: MouseEvent) => {
|
this._register(DOM.addDisposableListener(this._planContainer, DOM.EventType.CONTEXT_MENU, (e: MouseEvent) => {
|
||||||
if (contextMenuAction) {
|
if (contextMenuAction) {
|
||||||
this._contextMenuService.showContextMenu({
|
this._contextMenuService.showContextMenu({
|
||||||
getAnchor: () => {
|
getAnchor: () => {
|
||||||
@@ -217,34 +221,37 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
getActionsContext: () => (self)
|
getActionsContext: () => (self)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
}));
|
||||||
|
|
||||||
this.container.onkeydown = (e: KeyboardEvent) => {
|
this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||||
if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
|
if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
|
||||||
let searchNodeAction = self._instantiationService.createInstance(SearchNodeAction, 'HotKey');
|
let searchNodeAction = self._register(self._instantiationService.createInstance(SearchNodeAction, 'HotKey'));
|
||||||
searchNodeAction.run(self);
|
searchNodeAction.run(self);
|
||||||
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
};
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
getHorizontalSashTop(sash: Sash): number {
|
getHorizontalSashTop(sash: Sash): number {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHorizontalSashLeft?(sash: Sash): number {
|
getHorizontalSashLeft?(sash: Sash): number {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
getHorizontalSashWidth?(sash: Sash): number {
|
getHorizontalSashWidth?(sash: Sash): number {
|
||||||
return this.container.clientWidth;
|
return this.container.clientWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPlanDiagram(container: HTMLElement) {
|
private createPlanDiagram(container: HTMLElement) {
|
||||||
this.executionPlanDiagram = this._instantiationService.createInstance(AzdataGraphView, container, this._model);
|
this.executionPlanDiagram = this._register(this._instantiationService.createInstance(AzdataGraphView, container, this._model));
|
||||||
this.executionPlanDiagram.onElementSelected(e => {
|
|
||||||
|
this._register(this.executionPlanDiagram.onElementSelected(e => {
|
||||||
container.focus();
|
container.focus();
|
||||||
this.propertiesView.graphElement = e;
|
this.propertiesView.graphElement = e;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -253,9 +260,11 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
if (this._model) {
|
if (this._model) {
|
||||||
this.planHeader.graphIndex = this._graphIndex;
|
this.planHeader.graphIndex = this._graphIndex;
|
||||||
this.planHeader.query = graph.query;
|
this.planHeader.query = graph.query;
|
||||||
|
|
||||||
if (graph.recommendations) {
|
if (graph.recommendations) {
|
||||||
this.planHeader.recommendations = graph.recommendations;
|
this.planHeader.recommendations = graph.recommendations;
|
||||||
}
|
}
|
||||||
|
|
||||||
let diagramContainer = DOM.$('.diagram');
|
let diagramContainer = DOM.$('.diagram');
|
||||||
this.createPlanDiagram(diagramContainer);
|
this.createPlanDiagram(diagramContainer);
|
||||||
|
|
||||||
@@ -266,13 +275,13 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
* the graph control. To scroll the individual graphs, users should
|
* the graph control. To scroll the individual graphs, users should
|
||||||
* use the scroll bars.
|
* use the scroll bars.
|
||||||
*/
|
*/
|
||||||
diagramContainer.addEventListener('wheel', e => {
|
this._register(DOM.addDisposableListener(diagramContainer, DOM.EventType.WHEEL, (e: WheelEvent) => {
|
||||||
//Hiding all tooltips when we scroll.
|
//Hiding all tooltips when we scroll.
|
||||||
const element = document.getElementsByClassName('mxTooltip');
|
const element = document.getElementsByClassName('mxTooltip');
|
||||||
for (let i = 0; i < element.length; i++) {
|
for (let i = 0; i < element.length; i++) {
|
||||||
(<HTMLElement>element[i]).style.visibility = 'hidden';
|
(<HTMLElement>element[i]).style.visibility = 'hidden';
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
|
|
||||||
this._planContainer.appendChild(diagramContainer);
|
this._planContainer.appendChild(diagramContainer);
|
||||||
|
|
||||||
@@ -301,10 +310,10 @@ export class ExecutionPlanView implements ISashLayoutProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public compareCurrentExecutionPlan() {
|
public compareCurrentExecutionPlan() {
|
||||||
this._editorService.openEditor(this._instantiationService.createInstance(ExecutionPlanComparisonInput, {
|
this._editorService.openEditor(this._register(this._instantiationService.createInstance(ExecutionPlanComparisonInput, {
|
||||||
topExecutionPlan: this._executionPlanFileView.graphs,
|
topExecutionPlan: this._executionPlanFileView.graphs,
|
||||||
topPlanIndex: this._graphIndex - 1
|
topPlanIndex: this._graphIndex - 1
|
||||||
}), {
|
})), {
|
||||||
pinned: true
|
pinned: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -466,7 +475,7 @@ export class CustomZoomAction extends Action {
|
|||||||
.withAdditionalProperties({ source: this.source })
|
.withAdditionalProperties({ source: this.source })
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
context.widgetController.toggleWidget(context._instantiationService.createInstance(CustomZoomWidget, context.widgetController, context.executionPlanDiagram));
|
context.widgetController.toggleWidget(this._register(context._instantiationService.createInstance(CustomZoomWidget, context.widgetController, context.executionPlanDiagram)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,7 +495,7 @@ export class SearchNodeAction extends Action {
|
|||||||
.withAdditionalProperties({ source: this.source })
|
.withAdditionalProperties({ source: this.source })
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
context.widgetController.toggleWidget(context._instantiationService.createInstance(NodeSearchWidget, context.widgetController, context.executionPlanDiagram));
|
context.widgetController.toggleWidget(this._register(context._instantiationService.createInstance(NodeSearchWidget, context.widgetController, context.executionPlanDiagram)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,6 +609,6 @@ export class HighlightExpensiveOperationAction extends Action {
|
|||||||
.withAdditionalProperties({ source: this.source })
|
.withAdditionalProperties({ source: this.source })
|
||||||
.send();
|
.send();
|
||||||
|
|
||||||
context.widgetController.toggleWidget(context._instantiationService.createInstance(HighlightExpensiveOperationWidget, context.widgetController, context.executionPlanDiagram));
|
context.widgetController.toggleWidget(this._register(context._instantiationService.createInstance(HighlightExpensiveOperationWidget, context.widgetController, context.executionPlanDiagram)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||||||
import { RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
|
import { RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
|
||||||
import { Button } from 'sql/base/browser/ui/button/button';
|
import { Button } from 'sql/base/browser/ui/button/button';
|
||||||
import { removeLineBreaks } from 'sql/base/common/strings';
|
import { removeLineBreaks } from 'sql/base/common/strings';
|
||||||
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
|
||||||
export class ExecutionPlanViewHeader {
|
export class ExecutionPlanViewHeader extends Disposable {
|
||||||
|
|
||||||
private _graphIndex: number; // Index of the graph in the view
|
private _graphIndex: number; // Index of the graph in the view
|
||||||
private _relativeCost: number; // Relative cost of the graph to the script
|
private _relativeCost: number; // Relative cost of the graph to the script
|
||||||
@@ -28,7 +29,9 @@ export class ExecutionPlanViewHeader {
|
|||||||
public constructor(
|
public constructor(
|
||||||
private _parentContainer: HTMLElement,
|
private _parentContainer: HTMLElement,
|
||||||
headerData: PlanHeaderData | undefined,
|
headerData: PlanHeaderData | undefined,
|
||||||
@IInstantiationService public readonly _instantiationService: IInstantiationService) {
|
@IInstantiationService public readonly _instantiationService: IInstantiationService
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
if (headerData) {
|
if (headerData) {
|
||||||
this._graphIndex = headerData.planIndex;
|
this._graphIndex = headerData.planIndex;
|
||||||
@@ -67,6 +70,7 @@ export class ExecutionPlanViewHeader {
|
|||||||
recommendations.forEach(r => {
|
recommendations.forEach(r => {
|
||||||
r.displayString = removeLineBreaks(r.displayString);
|
r.displayString = removeLineBreaks(r.displayString);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._recommendations = recommendations;
|
this._recommendations = recommendations;
|
||||||
this.renderRecommendations();
|
this.renderRecommendations();
|
||||||
}
|
}
|
||||||
@@ -97,19 +101,19 @@ export class ExecutionPlanViewHeader {
|
|||||||
while (this._recommendationsContainer.firstChild) {
|
while (this._recommendationsContainer.firstChild) {
|
||||||
this._recommendationsContainer.removeChild(this._recommendationsContainer.firstChild);
|
this._recommendationsContainer.removeChild(this._recommendationsContainer.firstChild);
|
||||||
}
|
}
|
||||||
this._recommendations.forEach(r => {
|
|
||||||
|
|
||||||
const link = new Button(this._recommendationsContainer, {
|
this._recommendations.forEach(r => {
|
||||||
|
const link = this._register(new Button(this._recommendationsContainer, {
|
||||||
title: r.displayString,
|
title: r.displayString,
|
||||||
secondary: true,
|
secondary: true,
|
||||||
});
|
}));
|
||||||
|
|
||||||
link.label = r.displayString;
|
link.label = r.displayString;
|
||||||
|
|
||||||
//Enabling on click action for recommendations. It will open the recommendation File
|
//Enabling on click action for recommendations. It will open the recommendation File
|
||||||
link.onDidClick(e => {
|
this._register(link.onDidClick(e => {
|
||||||
this._instantiationService.invokeFunction(openNewQuery, undefined, r.queryWithDescription, RunQueryOnConnectionMode.none);
|
this._instantiationService.invokeFunction(openNewQuery, undefined, r.queryWithDescription, RunQueryOnConnectionMode.none);
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export class ExecutionPlanWidgetController {
|
|||||||
private addWidget(widget: ExecutionPlanWidgetBase) {
|
private addWidget(widget: ExecutionPlanWidgetBase) {
|
||||||
if (widget.identifier && !this._executionPlanWidgetMap.has(widget.identifier)) {
|
if (widget.identifier && !this._executionPlanWidgetMap.has(widget.identifier)) {
|
||||||
this._executionPlanWidgetMap.set(widget.identifier, widget);
|
this._executionPlanWidgetMap.set(widget.identifier, widget);
|
||||||
|
|
||||||
if (widget.container) {
|
if (widget.container) {
|
||||||
widget.container.classList.add('child');
|
widget.container.classList.add('child');
|
||||||
this._parentContainer.appendChild(widget.container);
|
this._parentContainer.appendChild(widget.container);
|
||||||
|
|||||||
@@ -69,9 +69,11 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
public scrollToIndex(index: number) {
|
public scrollToIndex(index: number) {
|
||||||
index = index - 1;
|
index = index - 1;
|
||||||
this._topOperationsContainers[index].scrollIntoView(true);
|
this._topOperationsContainers[index].scrollIntoView(true);
|
||||||
|
|
||||||
this._tables.forEach(t => {
|
this._tables.forEach(t => {
|
||||||
t.getSelectionModel().setSelectedRanges([]);
|
t.getSelectionModel().setSelectedRanges([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
this._tables[index].getSelectionModel().setSelectedRanges([new Slick.Range(0, 1, 0, 1)]);
|
this._tables[index].getSelectionModel().setSelectedRanges([new Slick.Range(0, 1, 0, 1)]);
|
||||||
this._tables[index].focus();
|
this._tables[index].focus();
|
||||||
}
|
}
|
||||||
@@ -95,6 +97,7 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
while (this._container.firstChild) {
|
while (this._container.firstChild) {
|
||||||
this._container.removeChild(this._container.firstChild);
|
this._container.removeChild(this._container.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._input.graphs.forEach((g, i) => {
|
this._input.graphs.forEach((g, i) => {
|
||||||
this.convertExecutionPlanGraphToTable(g, i);
|
this.convertExecutionPlanGraphToTable(g, i);
|
||||||
});
|
});
|
||||||
@@ -102,17 +105,19 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
|
|
||||||
|
|
||||||
public convertExecutionPlanGraphToTable(graph: azdata.executionPlan.ExecutionPlanGraph, index: number): Table<Slick.SlickData> {
|
public convertExecutionPlanGraphToTable(graph: azdata.executionPlan.ExecutionPlanGraph, index: number): Table<Slick.SlickData> {
|
||||||
|
|
||||||
const dataMap: { [key: string]: any }[] = [];
|
const dataMap: { [key: string]: any }[] = [];
|
||||||
const columnValues: string[] = [];
|
const columnValues: string[] = [];
|
||||||
|
|
||||||
const stack: azdata.executionPlan.ExecutionPlanNode[] = [];
|
const stack: azdata.executionPlan.ExecutionPlanNode[] = [];
|
||||||
stack.push(...graph.root.children);
|
stack.push(...graph.root.children);
|
||||||
|
|
||||||
while (stack.length !== 0) {
|
while (stack.length !== 0) {
|
||||||
const node = stack.pop();
|
const node = stack.pop();
|
||||||
const row: { [key: string]: any } = {};
|
const row: { [key: string]: any } = {};
|
||||||
|
|
||||||
node.topOperationsData.forEach((d, i) => {
|
node.topOperationsData.forEach((d, i) => {
|
||||||
let displayText = d.displayValue.toString();
|
let displayText = d.displayValue.toString();
|
||||||
|
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
row[d.columnName] = {
|
row[d.columnName] = {
|
||||||
displayText: displayText,
|
displayText: displayText,
|
||||||
@@ -126,14 +131,18 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
dataType: d.dataType
|
dataType: d.dataType
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (columnValues.indexOf(d.columnName) === -1) {
|
if (columnValues.indexOf(d.columnName) === -1) {
|
||||||
columnValues.splice(i, 0, d.columnName);
|
columnValues.splice(i, 0, d.columnName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
row['nodeId'] = node.id;
|
row['nodeId'] = node.id;
|
||||||
|
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
node.children.forEach(c => stack.push(c));
|
node.children.forEach(c => stack.push(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
row[TABLE_SORT_COLUMN_KEY] = node.cost;
|
row[TABLE_SORT_COLUMN_KEY] = node.cost;
|
||||||
dataMap.push(row);
|
dataMap.push(row);
|
||||||
}
|
}
|
||||||
@@ -165,18 +174,19 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
headerContainer.appendChild(headerSearchBarContainer);
|
headerContainer.appendChild(headerSearchBarContainer);
|
||||||
headerContainer.classList.add('codicon', filterIconClassNames);
|
headerContainer.classList.add('codicon', filterIconClassNames);
|
||||||
|
|
||||||
const topOperationsSearchInput = new InputBox(headerSearchBarContainer, this._contextViewService, {
|
const topOperationsSearchInput = this._register(new InputBox(headerSearchBarContainer, this._contextViewService, {
|
||||||
ariaDescription: topOperationsSearchDescription,
|
ariaDescription: topOperationsSearchDescription,
|
||||||
placeholder: searchPlaceholder
|
placeholder: searchPlaceholder
|
||||||
});
|
}));
|
||||||
attachInputBoxStyler(topOperationsSearchInput, this._themeService);
|
this._register(attachInputBoxStyler(topOperationsSearchInput, this._themeService));
|
||||||
topOperationsSearchInput.element.classList.add('codicon', filterIconClassNames);
|
topOperationsSearchInput.element.classList.add('codicon', filterIconClassNames);
|
||||||
|
|
||||||
const header = this._instantiationService.createInstance(ExecutionPlanViewHeader, headerInfoContainer, {
|
const header = this._register(this._instantiationService.createInstance(ExecutionPlanViewHeader, headerInfoContainer, {
|
||||||
planIndex: index,
|
planIndex: index,
|
||||||
});
|
}));
|
||||||
header.query = graph.query;
|
header.query = graph.query;
|
||||||
header.relativeCost = graph.root.relativeCost;
|
header.relativeCost = graph.root.relativeCost;
|
||||||
|
|
||||||
const tableContainer = DOM.$('.table-container');
|
const tableContainer = DOM.$('.table-container');
|
||||||
topOperationContainer.appendChild(tableContainer);
|
topOperationContainer.appendChild(tableContainer);
|
||||||
this._topOperationsContainers.push(topOperationContainer);
|
this._topOperationsContainers.push(topOperationContainer);
|
||||||
@@ -186,14 +196,15 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
|
|
||||||
let copyHandler = new CopyKeybind<any>();
|
let copyHandler = new CopyKeybind<any>();
|
||||||
this._register(copyHandler.onCopy(e => {
|
this._register(copyHandler.onCopy(e => {
|
||||||
|
let csvString = '';
|
||||||
|
|
||||||
const selectedDataRange = selectionModel.getSelectedRanges()[0];
|
const selectedDataRange = selectionModel.getSelectedRanges()[0];
|
||||||
let csvString = '';
|
|
||||||
if (selectedDataRange) {
|
if (selectedDataRange) {
|
||||||
const data = [];
|
const data = [];
|
||||||
|
|
||||||
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
|
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
|
||||||
const dataRow = table.getData().getItem(rowIndex);
|
const dataRow = this._register(table.getData()).getItem(rowIndex);
|
||||||
|
|
||||||
const row = [];
|
const row = [];
|
||||||
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
||||||
const dataItem = dataRow[table.columns[colIndex].field];
|
const dataItem = dataRow[table.columns[colIndex].field];
|
||||||
@@ -203,8 +214,10 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
row.push(' ');
|
row.push(' ');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.push(row);
|
data.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
csvString = data.map(row =>
|
csvString = data.map(row =>
|
||||||
row.map(x => `${x}`).join('\t')
|
row.map(x => `${x}`).join('\t')
|
||||||
).join('\n');
|
).join('\n');
|
||||||
@@ -214,7 +227,6 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
||||||
columns.push(table.columns[colIndex].name);
|
columns.push(table.columns[colIndex].name);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._instantiationService.createInstance(CopyTableData).run({
|
this._instantiationService.createInstance(CopyTableData).run({
|
||||||
@@ -224,11 +236,12 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
|
|
||||||
const selectionModel = new CellSelectionModel<Slick.SlickData>();
|
const selectionModel = new CellSelectionModel<Slick.SlickData>();
|
||||||
|
|
||||||
const table = new Table<Slick.SlickData>(tableContainer, {
|
const table = this._register(new Table<Slick.SlickData>(tableContainer, {
|
||||||
columns: columns,
|
columns: columns,
|
||||||
sorter: (args) => {
|
sorter: (args) => {
|
||||||
const column = args.sortCol.field;
|
const column = args.sortCol.field;
|
||||||
const sortedData = table.getData().getItems().sort((a, b) => {
|
|
||||||
|
const sortedData = this._register(table.getData()).getItems().sort((a, b) => {
|
||||||
let result = -1;
|
let result = -1;
|
||||||
|
|
||||||
if (!a[column]) {
|
if (!a[column]) {
|
||||||
@@ -256,8 +269,10 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return args.sortAsc ? result : -result;
|
return args.sortAsc ? result : -result;
|
||||||
});
|
});
|
||||||
|
|
||||||
table.setData(sortedData);
|
table.setData(sortedData);
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@@ -265,13 +280,13 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
forceFitColumns: false,
|
forceFitColumns: false,
|
||||||
defaultColumnWidth: 120,
|
defaultColumnWidth: 120,
|
||||||
showRowNumber: true
|
showRowNumber: true
|
||||||
});
|
}));
|
||||||
|
|
||||||
table.setSelectionModel(selectionModel);
|
table.setSelectionModel(selectionModel);
|
||||||
table.setData(dataMap);
|
table.setData(dataMap);
|
||||||
|
|
||||||
table.registerPlugin(copyHandler);
|
table.registerPlugin(copyHandler);
|
||||||
|
|
||||||
table.setTableTitle(localize('topOperationsTableTitle', "Top Operations"));
|
table.setTableTitle(localize('topOperationsTableTitle', "Top Operations"));
|
||||||
|
|
||||||
this._register(table.onClick(e => {
|
this._register(table.onClick(e => {
|
||||||
if (e.cell.cell === 1) {
|
if (e.cell.cell === 1) {
|
||||||
const row = table.getData().getItem(e.cell.row);
|
const row = table.getData().getItem(e.cell.row);
|
||||||
@@ -283,17 +298,13 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
this._tables.push(table);
|
this._tables.push(table);
|
||||||
const contextMenuAction = [
|
|
||||||
this._instantiationService.createInstance(CopyTableData),
|
|
||||||
this._instantiationService.createInstance(CopyTableDataWithHeader),
|
|
||||||
this._instantiationService.createInstance(SelectAll)
|
|
||||||
];
|
|
||||||
|
|
||||||
this._register(topOperationsSearchInput.onDidChange(e => {
|
this._register(topOperationsSearchInput.onDidChange(e => {
|
||||||
const filter = e.toLowerCase();
|
const filter = e.toLowerCase();
|
||||||
if (filter) {
|
if (filter) {
|
||||||
const filteredData = dataMap.filter(row => {
|
const filteredData = dataMap.filter(row => {
|
||||||
let includeRow = false;
|
let includeRow = false;
|
||||||
|
|
||||||
for (let i = 0; i < columns.length; i++) {
|
for (let i = 0; i < columns.length; i++) {
|
||||||
const columnField = columns[i].field;
|
const columnField = columns[i].field;
|
||||||
if (row[columnField]) {
|
if (row[columnField]) {
|
||||||
@@ -303,18 +314,21 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return includeRow;
|
return includeRow;
|
||||||
});
|
});
|
||||||
|
|
||||||
table.setData(filteredData);
|
table.setData(filteredData);
|
||||||
} else {
|
} else {
|
||||||
table.setData(dataMap);
|
table.setData(dataMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
table.rerenderGrid();
|
table.rerenderGrid();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this._register(table.onKeyDown((evt: ITableKeyboardEvent) => {
|
this._register(table.onKeyDown((evt: ITableKeyboardEvent) => {
|
||||||
if (evt.event.ctrlKey && (evt.event.key === 'a' || evt.event.key === 'A')) {
|
if (evt.event.ctrlKey && (evt.event.key === 'a' || evt.event.key === 'A')) {
|
||||||
selectionModel.setSelectedRanges([new Slick.Range(0, 1, table.getData().getLength() - 1, table.columns.length - 1)]);
|
selectionModel.setSelectedRanges([new Slick.Range(0, 1, this._register(table.getData()).getLength() - 1, table.columns.length - 1)]);
|
||||||
table.focus();
|
table.focus();
|
||||||
evt.event.preventDefault();
|
evt.event.preventDefault();
|
||||||
evt.event.stopPropagation();
|
evt.event.stopPropagation();
|
||||||
@@ -322,15 +336,17 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
this._register(table.onContextMenu(e => {
|
this._register(table.onContextMenu(e => {
|
||||||
const selectedDataRange = selectionModel.getSelectedRanges()[0];
|
|
||||||
let csvString = '';
|
let csvString = '';
|
||||||
let csvStringWithHeader = '';
|
let csvStringWithHeader = '';
|
||||||
|
|
||||||
|
const selectedDataRange = selectionModel.getSelectedRanges()[0];
|
||||||
if (selectedDataRange) {
|
if (selectedDataRange) {
|
||||||
const data = [];
|
const data = [];
|
||||||
|
|
||||||
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
|
for (let rowIndex = selectedDataRange.fromRow; rowIndex <= selectedDataRange.toRow; rowIndex++) {
|
||||||
const dataRow = table.getData().getItem(rowIndex);
|
const dataRow = this._register(table.getData()).getItem(rowIndex);
|
||||||
const row = [];
|
const row = [];
|
||||||
|
|
||||||
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
||||||
const dataItem = dataRow[table.columns[colIndex].field];
|
const dataItem = dataRow[table.columns[colIndex].field];
|
||||||
if (dataItem) {
|
if (dataItem) {
|
||||||
@@ -339,14 +355,15 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
row.push('');
|
row.push('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data.push(row);
|
data.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
csvString = data.map(row =>
|
csvString = data.map(row =>
|
||||||
row.map(x => `${x}`).join('\t')
|
row.map(x => `${x}`).join('\t')
|
||||||
).join('\n');
|
).join('\n');
|
||||||
|
|
||||||
const columns = [];
|
const columns = [];
|
||||||
|
|
||||||
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
for (let colIndex = selectedDataRange.fromCell; colIndex <= selectedDataRange.toCell; colIndex++) {
|
||||||
columns.push(table.columns[colIndex].name);
|
columns.push(table.columns[colIndex].name);
|
||||||
}
|
}
|
||||||
@@ -354,6 +371,12 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
csvStringWithHeader = columns.join('\t') + '\n' + csvString;
|
csvStringWithHeader = columns.join('\t') + '\n' + csvString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const contextMenuAction = [
|
||||||
|
this._register(this._instantiationService.createInstance(CopyTableData)),
|
||||||
|
this._register(this._instantiationService.createInstance(CopyTableDataWithHeader)),
|
||||||
|
this._register(this._instantiationService.createInstance(SelectAll))
|
||||||
|
];
|
||||||
|
|
||||||
this._contextMenuService.showContextMenu({
|
this._contextMenuService.showContextMenu({
|
||||||
getAnchor: () => e.anchor,
|
getAnchor: () => e.anchor,
|
||||||
getActions: () => contextMenuAction,
|
getActions: () => contextMenuAction,
|
||||||
@@ -364,13 +387,14 @@ export class TopOperationsTabView extends Disposable implements IPanelView {
|
|||||||
selectionTextWithHeader: csvStringWithHeader
|
selectionTextWithHeader: csvStringWithHeader
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
}));
|
}));
|
||||||
attachTableStyler(table, this._themeService);
|
|
||||||
|
this._register(attachTableStyler(table, this._themeService));
|
||||||
|
|
||||||
new ResizeObserver((e) => {
|
new ResizeObserver((e) => {
|
||||||
table.layout(new DOM.Dimension(tableContainer.clientWidth, tableContainer.clientHeight));
|
table.layout(new DOM.Dimension(tableContainer.clientWidth, tableContainer.clientHeight));
|
||||||
}).observe(tableContainer);
|
}).observe(tableContainer);
|
||||||
|
|
||||||
return table;
|
return table;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,11 +34,12 @@ export class CustomZoomWidget extends ExecutionPlanWidgetBase {
|
|||||||
|
|
||||||
// Custom zoom input box
|
// Custom zoom input box
|
||||||
const zoomValueLabel = localize("qpZoomValueLabel", 'Zoom (percent)');
|
const zoomValueLabel = localize("qpZoomValueLabel", 'Zoom (percent)');
|
||||||
this.customZoomInputBox = new InputBox(this.container, this.contextViewService, {
|
|
||||||
|
this.customZoomInputBox = this._register(new InputBox(this.container, this.contextViewService, {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
ariaLabel: zoomValueLabel,
|
ariaLabel: zoomValueLabel,
|
||||||
flexibleWidth: false
|
flexibleWidth: false
|
||||||
});
|
}));
|
||||||
this._register(attachInputBoxStyler(this.customZoomInputBox, this.themeService));
|
this._register(attachInputBoxStyler(this.customZoomInputBox, this.themeService));
|
||||||
|
|
||||||
const currentZoom = this.executionPlanDiagram.getZoomLevel();
|
const currentZoom = this.executionPlanDiagram.getZoomLevel();
|
||||||
@@ -48,28 +49,28 @@ export class CustomZoomWidget extends ExecutionPlanWidgetBase {
|
|||||||
|
|
||||||
// Setting up keyboard shortcuts
|
// Setting up keyboard shortcuts
|
||||||
const self = this;
|
const self = this;
|
||||||
this.customZoomInputBox.element.onkeydown = async (ev) => {
|
this._register(DOM.addDisposableListener(this.customZoomInputBox.element, DOM.EventType.KEY_DOWN, async (ev: KeyboardEvent) => {
|
||||||
if (ev.key === 'Enter') {
|
if (ev.key === 'Enter') {
|
||||||
await new CustomZoomAction().run(self);
|
await this._register(new CustomZoomAction()).run(self);
|
||||||
} else if (ev.key === 'Escape') {
|
} else if (ev.key === 'Escape') {
|
||||||
this.widgetController.removeWidget(self);
|
this.widgetController.removeWidget(self);
|
||||||
}
|
}
|
||||||
};
|
}));
|
||||||
|
|
||||||
const applyButton = new Button(this.container, {
|
const applyButton = this._register(new Button(this.container, {
|
||||||
title: localize('customZoomApplyButtonTitle', "Apply Zoom")
|
title: localize('customZoomApplyButtonTitle', "Apply Zoom")
|
||||||
});
|
}));
|
||||||
applyButton.setWidth('60px');
|
applyButton.setWidth('60px');
|
||||||
applyButton.label = localize('customZoomApplyButton', "Apply");
|
applyButton.label = localize('customZoomApplyButton', "Apply");
|
||||||
|
|
||||||
this._register(applyButton.onDidClick(async e => {
|
this._register(applyButton.onDidClick(async e => {
|
||||||
await new CustomZoomAction().run(self);
|
await this._register(new CustomZoomAction()).run(self);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Adding action bar
|
// Adding action bar
|
||||||
this._actionBar = new ActionBar(this.container);
|
this._actionBar = this._register(new ActionBar(this.container));
|
||||||
this._actionBar.context = this;
|
this._actionBar.context = this;
|
||||||
this._actionBar.pushAction(new CancelZoom(), { label: false, icon: true });
|
this._actionBar.pushAction(this._register(new CancelZoom()), { label: false, icon: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setting initial focus to input box
|
// Setting initial focus to input box
|
||||||
@@ -93,7 +94,7 @@ export class CustomZoomAction extends Action {
|
|||||||
context.widgetController.removeWidget(context);
|
context.widgetController.removeWidget(context);
|
||||||
} else {
|
} else {
|
||||||
context.notificationService.error(
|
context.notificationService.error(
|
||||||
localize('invalidCustomZoomError', "Select a zoom value between 1 to 200")
|
localize('invalidCustomZoomError', "Select a zoom value between 1 to 200") // TODO lewissanchez: Ask Aasim about this error message after removing zoom limit.
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ export class HighlightExpensiveOperationWidget extends ExecutionPlanWidgetBase {
|
|||||||
this.container.appendChild(this._expenseMetricSelectBoxContainer);
|
this.container.appendChild(this._expenseMetricSelectBoxContainer);
|
||||||
|
|
||||||
const selectBoxOptions = this.getSelectBoxOptionsFromExecutionPlanDiagram();
|
const selectBoxOptions = this.getSelectBoxOptionsFromExecutionPlanDiagram();
|
||||||
this.expenseMetricSelectBox = new SelectBox(selectBoxOptions, COST_STRING, this.contextViewService, this._expenseMetricSelectBoxContainer);
|
this.expenseMetricSelectBox = this._register(new SelectBox(selectBoxOptions, COST_STRING, this.contextViewService, this._expenseMetricSelectBoxContainer));
|
||||||
this.expenseMetricSelectBox.setAriaLabel(SELECT_EXPENSE_METRIC_TITLE);
|
this.expenseMetricSelectBox.setAriaLabel(SELECT_EXPENSE_METRIC_TITLE);
|
||||||
|
|
||||||
this.expenseMetricSelectBox.render(this._expenseMetricSelectBoxContainer);
|
this.expenseMetricSelectBox.render(this._expenseMetricSelectBoxContainer);
|
||||||
@@ -119,19 +119,15 @@ export class HighlightExpensiveOperationWidget extends ExecutionPlanWidgetBase {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Apply Button
|
// Apply Button
|
||||||
const highlightExpensiveOperationAction = new HighlightExpensiveOperationAction();
|
const highlightExpensiveOperationAction = this._register(new HighlightExpensiveOperationAction());
|
||||||
this._register(highlightExpensiveOperationAction);
|
const clearHighlightExpensiveOperationAction = this._register(new TurnOffExpensiveHighlightingOperationAction());
|
||||||
|
const cancelHighlightExpensiveOperationAction = this._register(new CancelHIghlightExpensiveOperationAction());
|
||||||
const clearHighlightExpensiveOperationAction = new TurnOffExpensiveHighlightingOperationAction();
|
|
||||||
this._register(clearHighlightExpensiveOperationAction);
|
|
||||||
|
|
||||||
const cancelHighlightExpensiveOperationAction = new CancelHIghlightExpensiveOperationAction();
|
|
||||||
this._register(cancelHighlightExpensiveOperationAction);
|
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
const applyButton = new Button(this.container, {
|
const applyButton = this._register(new Button(this.container, {
|
||||||
title: localize('highlightExpensiveOperationButtonTitle', 'Highlight Expensive Operation')
|
title: localize('highlightExpensiveOperationButtonTitle', 'Highlight Expensive Operation')
|
||||||
});
|
}));
|
||||||
|
|
||||||
applyButton.label = localize('highlightExpensiveOperationApplyButton', 'Apply');
|
applyButton.label = localize('highlightExpensiveOperationApplyButton', 'Apply');
|
||||||
|
|
||||||
this._register(applyButton.onDidClick(async e => {
|
this._register(applyButton.onDidClick(async e => {
|
||||||
@@ -146,7 +142,7 @@ export class HighlightExpensiveOperationWidget extends ExecutionPlanWidgetBase {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Adds Action bar
|
// Adds Action bar
|
||||||
this._actionBar = new ActionBar(this.container);
|
this._actionBar = this._register(new ActionBar(this.container));
|
||||||
this._actionBar.context = this;
|
this._actionBar.context = this;
|
||||||
this._actionBar.pushAction(cancelHighlightExpensiveOperationAction, { label: false, icon: true });
|
this._actionBar.pushAction(cancelHighlightExpensiveOperationAction, { label: false, icon: true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,20 +57,24 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
// property name dropdown
|
// property name dropdown
|
||||||
this._propertyNameSelectBoxContainer = DOM.$('.search-widget-property-name-select-box .dropdown-container');
|
this._propertyNameSelectBoxContainer = DOM.$('.search-widget-property-name-select-box .dropdown-container');
|
||||||
this.container.appendChild(this._propertyNameSelectBoxContainer);
|
this.container.appendChild(this._propertyNameSelectBoxContainer);
|
||||||
|
this._propertyNameSelectBoxContainer.style.width = '150px';
|
||||||
|
|
||||||
const propDropdownOptions = this._executionPlanDiagram.getUniqueElementProperties();
|
const propDropdownOptions = this._executionPlanDiagram.getUniqueElementProperties();
|
||||||
this._propertyNameSelectBox = new SelectBox(propDropdownOptions, propDropdownOptions[0], this.contextViewService, this._propertyNameSelectBoxContainer);
|
this._propertyNameSelectBox = this._register(new SelectBox(propDropdownOptions, propDropdownOptions[0], this.contextViewService, this._propertyNameSelectBoxContainer));
|
||||||
this._propertyNameSelectBox.setAriaLabel(SELECT_PROPERTY_TITLE);
|
this._propertyNameSelectBox.setAriaLabel(SELECT_PROPERTY_TITLE);
|
||||||
this._register(attachSelectBoxStyler(this._propertyNameSelectBox, this.themeService));
|
this._register(attachSelectBoxStyler(this._propertyNameSelectBox, this.themeService));
|
||||||
this._propertyNameSelectBoxContainer.style.width = '150px';
|
|
||||||
this._propertyNameSelectBox.render(this._propertyNameSelectBoxContainer);
|
this._propertyNameSelectBox.render(this._propertyNameSelectBoxContainer);
|
||||||
|
|
||||||
this._register(this._propertyNameSelectBox.onDidSelect(e => {
|
this._register(this._propertyNameSelectBox.onDidSelect(e => {
|
||||||
this._usePreviousSearchResult = false;
|
this._usePreviousSearchResult = false;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// search type dropdown
|
// search type dropdown
|
||||||
this._searchTypeSelectBoxContainer = DOM.$('.search-widget-search-type-select-box .dropdown-container');
|
this._searchTypeSelectBoxContainer = DOM.$('.search-widget-search-type-select-box .dropdown-container');
|
||||||
|
this._searchTypeSelectBoxContainer.style.width = '100px';
|
||||||
this.container.appendChild(this._searchTypeSelectBoxContainer);
|
this.container.appendChild(this._searchTypeSelectBoxContainer);
|
||||||
this._searchTypeSelectBox = new SelectBox([
|
|
||||||
|
this._searchTypeSelectBox = this._register(new SelectBox([
|
||||||
EQUALS_DISPLAY_STRING,
|
EQUALS_DISPLAY_STRING,
|
||||||
CONTAINS_DISPLAY_STRING,
|
CONTAINS_DISPLAY_STRING,
|
||||||
GREATER_DISPLAY_STRING,
|
GREATER_DISPLAY_STRING,
|
||||||
@@ -78,11 +82,11 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
GREATER_EQUAL_DISPLAY_STRING,
|
GREATER_EQUAL_DISPLAY_STRING,
|
||||||
LESSER_EQUAL_DISPLAY_STRING,
|
LESSER_EQUAL_DISPLAY_STRING,
|
||||||
LESSER_AND_GREATER_DISPLAY_STRING
|
LESSER_AND_GREATER_DISPLAY_STRING
|
||||||
], EQUALS_DISPLAY_STRING, this.contextViewService, this._searchTypeSelectBoxContainer);
|
], EQUALS_DISPLAY_STRING, this.contextViewService, this._searchTypeSelectBoxContainer));
|
||||||
this._searchTypeSelectBox.setAriaLabel(SELECT_SEARCH_TYPE_TITLE);
|
this._searchTypeSelectBox.setAriaLabel(SELECT_SEARCH_TYPE_TITLE);
|
||||||
this._searchTypeSelectBox.render(this._searchTypeSelectBoxContainer);
|
this._searchTypeSelectBox.render(this._searchTypeSelectBoxContainer);
|
||||||
this._register(attachSelectBoxStyler(this._searchTypeSelectBox, this.themeService));
|
this._register(attachSelectBoxStyler(this._searchTypeSelectBox, this.themeService));
|
||||||
this._searchTypeSelectBoxContainer.style.width = '100px';
|
|
||||||
this._register(this._searchTypeSelectBox.onDidSelect(e => {
|
this._register(this._searchTypeSelectBox.onDidSelect(e => {
|
||||||
this._usePreviousSearchResult = false;
|
this._usePreviousSearchResult = false;
|
||||||
switch (e.selected) {
|
switch (e.selected) {
|
||||||
@@ -110,27 +114,21 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// search text input box
|
// search text input box
|
||||||
this._searchTextInputBox = new InputBox(this.container, this.contextViewService, {});
|
this._searchTextInputBox = this._register(new InputBox(this.container, this.contextViewService, {}));
|
||||||
this._searchTextInputBox.setAriaLabel(ENTER_SEARCH_VALUE_TITLE);
|
this._searchTextInputBox.setAriaLabel(ENTER_SEARCH_VALUE_TITLE);
|
||||||
this._register(attachInputBoxStyler(this._searchTextInputBox, this.themeService));
|
|
||||||
this._searchTextInputBox.element.style.marginLeft = '5px';
|
this._searchTextInputBox.element.style.marginLeft = '5px';
|
||||||
|
this._register(attachInputBoxStyler(this._searchTextInputBox, this.themeService));
|
||||||
this._register(this._searchTextInputBox.onDidChange(e => {
|
this._register(this._searchTextInputBox.onDidChange(e => {
|
||||||
this._usePreviousSearchResult = false;
|
this._usePreviousSearchResult = false;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
// setting up key board shortcuts
|
// setting up key board shortcuts
|
||||||
const goToPreviousMatchAction = new GoToPreviousMatchAction();
|
const goToPreviousMatchAction = this._register(new GoToPreviousMatchAction());
|
||||||
this._register(goToPreviousMatchAction);
|
const goToNextMatchAction = this._register(new GoToNextMatchAction());
|
||||||
|
const cancelSearchAction = this._register(new CancelSearch());
|
||||||
const goToNextMatchAction = new GoToNextMatchAction();
|
|
||||||
this._register(goToNextMatchAction);
|
|
||||||
|
|
||||||
const cancelSearchAction = new CancelSearch();
|
|
||||||
this._register(cancelSearchAction);
|
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this._searchTextInputBox.element.onkeydown = async e => {
|
this._register(DOM.addDisposableListener(this._searchTextInputBox.element, DOM.EventType.KEY_DOWN, async (e: KeyboardEvent) => {
|
||||||
if (e.key === 'Enter' && e.shiftKey) {
|
if (e.key === 'Enter' && e.shiftKey) {
|
||||||
await goToPreviousMatchAction.run(self);
|
await goToPreviousMatchAction.run(self);
|
||||||
} else if (e.key === 'Enter') {
|
} else if (e.key === 'Enter') {
|
||||||
@@ -138,10 +136,10 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
await cancelSearchAction.run(self);
|
await cancelSearchAction.run(self);
|
||||||
}
|
}
|
||||||
};
|
}));
|
||||||
|
|
||||||
// Adding action bar
|
// Adding action bar
|
||||||
this._actionBar = new ActionBar(this.container);
|
this._actionBar = this._register(new ActionBar(this.container));
|
||||||
this._actionBar.context = this;
|
this._actionBar.context = this;
|
||||||
this._actionBar.pushAction(goToPreviousMatchAction, { label: false, icon: true });
|
this._actionBar.pushAction(goToPreviousMatchAction, { label: false, icon: true });
|
||||||
this._actionBar.pushAction(goToNextMatchAction, { label: false, icon: true });
|
this._actionBar.pushAction(goToNextMatchAction, { label: false, icon: true });
|
||||||
@@ -155,6 +153,7 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
|
|
||||||
public searchNodes(): void {
|
public searchNodes(): void {
|
||||||
this._currentSearchResultIndex = 0;
|
this._currentSearchResultIndex = 0;
|
||||||
|
|
||||||
this._searchResults = this._executionPlanDiagram.searchNodes({
|
this._searchResults = this._executionPlanDiagram.searchNodes({
|
||||||
propertyName: this._propertyNameSelectBox.value,
|
propertyName: this._propertyNameSelectBox.value,
|
||||||
value: this._searchTextInputBox.value,
|
value: this._searchTextInputBox.value,
|
||||||
@@ -171,6 +170,7 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
|
|
||||||
this._executionPlanDiagram.centerElement(this._searchResults[this._currentSearchResultIndex]);
|
this._executionPlanDiagram.centerElement(this._searchResults[this._currentSearchResultIndex]);
|
||||||
this._executionPlanDiagram.selectElement(this._searchResults[this._currentSearchResultIndex]);
|
this._executionPlanDiagram.selectElement(this._searchResults[this._currentSearchResultIndex]);
|
||||||
|
|
||||||
this._currentSearchResultIndex = this._currentSearchResultIndex === this._searchResults.length - 1 ?
|
this._currentSearchResultIndex = this._currentSearchResultIndex === this._searchResults.length - 1 ?
|
||||||
this._currentSearchResultIndex = 0 :
|
this._currentSearchResultIndex = 0 :
|
||||||
this._currentSearchResultIndex = ++this._currentSearchResultIndex;
|
this._currentSearchResultIndex = ++this._currentSearchResultIndex;
|
||||||
@@ -183,6 +183,7 @@ export class NodeSearchWidget extends ExecutionPlanWidgetBase {
|
|||||||
|
|
||||||
this._executionPlanDiagram.centerElement(this._searchResults[this._currentSearchResultIndex]);
|
this._executionPlanDiagram.centerElement(this._searchResults[this._currentSearchResultIndex]);
|
||||||
this._executionPlanDiagram.selectElement(this._searchResults[this._currentSearchResultIndex]);
|
this._executionPlanDiagram.selectElement(this._searchResults[this._currentSearchResultIndex]);
|
||||||
|
|
||||||
this._currentSearchResultIndex = this._currentSearchResultIndex === 0 ?
|
this._currentSearchResultIndex = this._currentSearchResultIndex === 0 ?
|
||||||
this._currentSearchResultIndex = this._searchResults.length - 1 :
|
this._currentSearchResultIndex = this._searchResults.length - 1 :
|
||||||
this._currentSearchResultIndex = --this._currentSearchResultIndex;
|
this._currentSearchResultIndex = --this._currentSearchResultIndex;
|
||||||
|
|||||||
@@ -726,7 +726,7 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
|
|||||||
graphFileType: result.queryExecutionPlanFileExtension
|
graphFileType: result.queryExecutionPlanFileExtension
|
||||||
};
|
};
|
||||||
|
|
||||||
const executionPlanInput = this.instantiationService.createInstance(ExecutionPlanInput, undefined, executionPlanGraphInfo);
|
const executionPlanInput = this._register(this.instantiationService.createInstance(ExecutionPlanInput, undefined, executionPlanGraphInfo));
|
||||||
await this.editorService.openEditor(executionPlanInput);
|
await this.editorService.openEditor(executionPlanInput);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -800,7 +800,7 @@ export abstract class GridTableBase<T> extends Disposable implements IView {
|
|||||||
this.currentHeight = size;
|
this.currentHeight = size;
|
||||||
}
|
}
|
||||||
// Table is always called with Orientation as VERTICAL
|
// Table is always called with Orientation as VERTICAL
|
||||||
this.table.layout(size, Orientation.VERTICAL);
|
this.table?.layout(size, Orientation.VERTICAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get minimumSize(): number {
|
public get minimumSize(): number {
|
||||||
|
|||||||
Reference in New Issue
Block a user