mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Readd Top Operations (#3628)
* workin on top operations * added top operations, changed default sorter to handle number string better
This commit is contained in:
@@ -8,6 +8,7 @@ import { Observer } from 'rxjs/Observer';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { compare as stringCompare } from 'vs/base/common/strings';
|
||||
|
||||
import { IDisposableDataProvider } from 'sql/base/browser/ui/table/interfaces';
|
||||
|
||||
@@ -19,7 +20,23 @@ export interface IFindPosition {
|
||||
function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
|
||||
let field = args.sortCol.field;
|
||||
let sign = args.sortAsc ? 1 : -1;
|
||||
return data.sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign);
|
||||
let comparer: (a, b) => number;
|
||||
if (types.isString(data[0][field])) {
|
||||
if (Number(data[0][field]) !== NaN) {
|
||||
comparer = (a: number, b: number) => {
|
||||
let anum = Number(a[field]);
|
||||
let bnum = Number(b[field]);
|
||||
return anum === bnum ? 0 : anum > bnum ? 1 : -1;
|
||||
};
|
||||
} else {
|
||||
comparer = stringCompare;
|
||||
}
|
||||
} else {
|
||||
comparer = (a: number, b: number) => {
|
||||
return a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1);
|
||||
};
|
||||
}
|
||||
return data.sort((a, b) => comparer(a, b) * sign);
|
||||
}
|
||||
|
||||
export class TableDataView<T extends Slick.SlickData> implements IDisposableDataProvider<T> {
|
||||
|
||||
@@ -9,18 +9,20 @@ import { localize } from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
||||
import { GridPanelState } from 'sql/parts/query/editor/gridPanel';
|
||||
import { MessagePanelState } from 'sql/parts/query/editor/messagePanel';
|
||||
import { QueryPlanState } from 'sql/parts/queryPlan/queryPlan';
|
||||
import { ChartState } from 'sql/parts/query/editor/charting/chartView';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { TopOperationsState } from 'sql/parts/queryPlan/topOperations';
|
||||
|
||||
export class ResultsViewState {
|
||||
public gridPanelState: GridPanelState = new GridPanelState();
|
||||
public messagePanelState: MessagePanelState = new MessagePanelState(this.configurationService);
|
||||
public chartState: ChartState = new ChartState();
|
||||
public queryPlanState: QueryPlanState = new QueryPlanState();
|
||||
public topOperationsState = new TopOperationsState();
|
||||
public gridPanelSize: number;
|
||||
public messagePanelSize: number;
|
||||
public activeTab: string;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { MessagePanel } from './messagePanel';
|
||||
import { GridPanel } from './gridPanel';
|
||||
import { ChartTab } from './charting/chartTab';
|
||||
import { QueryPlanTab } from 'sql/parts/queryPlan/queryPlan';
|
||||
import { TopOperationsTab } from 'sql/parts/queryPlan/topOperations';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
@@ -172,6 +173,7 @@ export class QueryResultsView extends Disposable {
|
||||
private resultsTab: ResultsTab;
|
||||
private chartTab: ChartTab;
|
||||
private qpTab: QueryPlanTab;
|
||||
private topOperationsTab: TopOperationsTab;
|
||||
|
||||
private runnerDisposables: IDisposable[];
|
||||
|
||||
@@ -185,6 +187,8 @@ export class QueryResultsView extends Disposable {
|
||||
this.chartTab = this._register(new ChartTab(instantiationService));
|
||||
this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: false }));
|
||||
this.qpTab = this._register(new QueryPlanTab());
|
||||
this.topOperationsTab = this._register(new TopOperationsTab(instantiationService));
|
||||
|
||||
this._panelView.pushTab(this.resultsTab);
|
||||
this._register(this._panelView.onTabChange(e => {
|
||||
if (this.input) {
|
||||
@@ -202,6 +206,7 @@ export class QueryResultsView extends Disposable {
|
||||
this.runnerDisposables = [];
|
||||
this.resultsTab.view.state = this.input.state;
|
||||
this.qpTab.view.state = this.input.state.queryPlanState;
|
||||
this.topOperationsTab.view.state = this.input.state.topOperationsState;
|
||||
this.chartTab.view.state = this.input.state.chartState;
|
||||
let queryRunner = this.queryModelService._getQueryInfo(input.uri).queryRunner;
|
||||
this.resultsTab.queryRunner = queryRunner;
|
||||
@@ -222,6 +227,11 @@ export class QueryResultsView extends Disposable {
|
||||
this._panelView.pushTab(this.qpTab);
|
||||
}
|
||||
}
|
||||
if (this.input.state.visibleTabs.has(this.topOperationsTab.identifier)) {
|
||||
if (!this._panelView.contains(this.topOperationsTab)) {
|
||||
this._panelView.pushTab(this.topOperationsTab);
|
||||
}
|
||||
}
|
||||
this.runnerDisposables.push(queryRunner.onQueryEnd(() => {
|
||||
if (queryRunner.isQueryPlan) {
|
||||
queryRunner.planXml.then(e => {
|
||||
@@ -238,6 +248,7 @@ export class QueryResultsView extends Disposable {
|
||||
this._input = undefined;
|
||||
this.resultsTab.clear();
|
||||
this.qpTab.clear();
|
||||
this.topOperationsTab.clear();
|
||||
this.chartTab.clear();
|
||||
}
|
||||
|
||||
@@ -270,9 +281,14 @@ export class QueryResultsView extends Disposable {
|
||||
if (!this._panelView.contains(this.qpTab)) {
|
||||
this._panelView.pushTab(this.qpTab);
|
||||
}
|
||||
this.input.state.visibleTabs.add(this.topOperationsTab.identifier);
|
||||
if (!this._panelView.contains(this.topOperationsTab)) {
|
||||
this._panelView.pushTab(this.topOperationsTab);
|
||||
}
|
||||
|
||||
this._panelView.showTab(this.qpTab.identifier);
|
||||
this.qpTab.view.showPlan(xml);
|
||||
this.topOperationsTab.view.showPlan(xml);
|
||||
}
|
||||
|
||||
public hidePlan() {
|
||||
|
||||
@@ -292,4 +292,4 @@ export class PlanXmlParser {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
136
src/sql/parts/queryPlan/topOperations.ts
Normal file
136
src/sql/parts/queryPlan/topOperations.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Dimension } from 'vs/base/browser/dom';
|
||||
import { localize } from 'vs/nls';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
import { Table } from 'sql/base/browser/ui/table/table';
|
||||
import { PlanXmlParser } from 'sql/parts/queryPlan/planXmlParser';
|
||||
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachTableStyler } from 'sql/common/theme/styler';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||
|
||||
const topOperationColumns: Array<Slick.Column<any>> = [
|
||||
{ name: localize('topOperations.operation', 'Operation'), field: 'operation', sortable: true },
|
||||
{ name: localize('topOperations.object', 'Object'), field: 'object', sortable: true },
|
||||
{ name: localize('topOperations.estCost', 'Est Cost'), field: 'estCost', sortable: true },
|
||||
{ name: localize('topOperations.estSubtreeCost', 'Est Subtree Cost'), field: 'estSubtreeCost', sortable: true },
|
||||
{ name: localize('topOperations.actualRows', 'Actual Rows'), field: 'actualRows', sortable: true },
|
||||
{ name: localize('topOperations.estRows', 'Est Rows'), field: 'estRows', sortable: true },
|
||||
{ name: localize('topOperations.actualExecutions', 'Actual Executions'), field: 'actualExecutions', sortable: true },
|
||||
{ name: localize('topOperations.estCPUCost', 'Est CPU Cost'), field: 'estCPUCost', sortable: true },
|
||||
{ name: localize('topOperations.estIOCost', 'Est IO Cost'), field: 'estIOCost', sortable: true },
|
||||
{ name: localize('topOperations.parallel', 'Parallel'), field: 'parallel', sortable: true },
|
||||
{ name: localize('topOperations.actualRebinds', 'Actual Rebinds'), field: 'actualRebinds', sortable: true },
|
||||
{ name: localize('topOperations.estRebinds', 'Est Rebinds'), field: 'estRebinds', sortable: true },
|
||||
{ name: localize('topOperations.actualRewinds', 'Actual Rewinds'), field: 'actualRewinds', sortable: true },
|
||||
{ name: localize('topOperations.estRewinds', 'Est Rewinds'), field: 'estRewinds', sortable: true },
|
||||
{ name: localize('topOperations.partitioned', 'Partitioned'), field: 'partitioned', sortable: true }
|
||||
];
|
||||
|
||||
export class TopOperationsState {
|
||||
xml: string;
|
||||
dispose() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export class TopOperationsTab implements IPanelTab {
|
||||
public readonly title = localize('topOperationsTitle', 'Top Operation');
|
||||
public readonly identifier = 'TopOperationsTab';
|
||||
public readonly view: TopOperationsView;
|
||||
|
||||
constructor(@IInstantiationService instantiationService: IInstantiationService) {
|
||||
this.view = instantiationService.createInstance(TopOperationsView);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
dispose(this.view);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.view.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export class TopOperationsView implements IPanelView {
|
||||
private _state: TopOperationsState;
|
||||
private table: Table<any>;
|
||||
private disposables: IDisposable[] = [];
|
||||
private container = document.createElement('div');
|
||||
private dataView = new TableDataView();
|
||||
|
||||
constructor(@IThemeService private themeService: IThemeService) {
|
||||
this.table = new Table(this.container, {
|
||||
columns: topOperationColumns,
|
||||
dataProvider: this.dataView,
|
||||
sorter: {
|
||||
sort: (args) => {
|
||||
this.dataView.sort(args);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.disposables.push(this.table);
|
||||
this.disposables.push(attachTableStyler(this.table, this.themeService));
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
container.appendChild(this.container);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
dispose(this.disposables);
|
||||
}
|
||||
|
||||
public layout(dimension: Dimension): void {
|
||||
this.table.layout(dimension);
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this.dataView.clear();
|
||||
}
|
||||
|
||||
public showPlan(xml: string) {
|
||||
this.state.xml = xml;
|
||||
this.dataView.clear();
|
||||
let parser = new PlanXmlParser(xml);
|
||||
let operations = parser.topOperations;
|
||||
let data = operations.map(i => {
|
||||
return {
|
||||
operation: i.title,
|
||||
object: i.indexObject.title,
|
||||
estCost: i.estimatedOperatorCost,
|
||||
estSubtreeCost: i.subtreeCost,
|
||||
actualRows: i.runtimeInfo.actualRows,
|
||||
estRows: i.estimateRows,
|
||||
actualExecutions: i.runtimeInfo.actualExecutions,
|
||||
estCPUCost: i.estimateCpu,
|
||||
estIOCost: i.estimateIo,
|
||||
parallel: i.parallel,
|
||||
actualRebinds: '',
|
||||
estRebinds: i.estimateRebinds,
|
||||
actualRewinds: '',
|
||||
estRewinds: i.estimateRewinds,
|
||||
partitioned: i.partitioned
|
||||
};
|
||||
});
|
||||
this.dataView.push(data);
|
||||
}
|
||||
|
||||
public set state(val: TopOperationsState) {
|
||||
this._state = val;
|
||||
if (this.state.xml) {
|
||||
this.showPlan(this.state.xml);
|
||||
}
|
||||
}
|
||||
|
||||
public get state(): TopOperationsState {
|
||||
return this._state;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user