mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Adding properties view and action bar to graph and fixing styling. (#18185)
* Adding properties view and action bar to graph. * Open Graph File * replacing innerhtml with innertext * Fixing floating promises * Fixed typo * renaming * Fixing hardcoded colors and comments * Removing todo and hardcoded colors * renaming method * removed unused contract * Fixed path in comment * converting div to button * adding checks to table width and height setter * Make method name more meaningful * adding method return types * concising repeated logic * removing unused styling * better sorting logic * Fixing graph parsing Renaming some stuff Implementing IDisposable * Fixing bad props logic * Fixed image loading issue * Removing hardcoded colors * Adding comments to localize and handling undefined cases in sort * Changed ch to px * moving util function to strings
This commit is contained in:
@@ -75,7 +75,7 @@
|
|||||||
"angular2-grid": "2.0.6",
|
"angular2-grid": "2.0.6",
|
||||||
"ansi_up": "^3.0.0",
|
"ansi_up": "^3.0.0",
|
||||||
"applicationinsights": "1.0.8",
|
"applicationinsights": "1.0.8",
|
||||||
"azdataGraph": "github:Microsoft/azdataGraph#0.0.7",
|
"azdataGraph": "github:Microsoft/azdataGraph#0.0.9",
|
||||||
"chart.js": "^2.9.4",
|
"chart.js": "^2.9.4",
|
||||||
"chokidar": "3.5.2",
|
"chokidar": "3.5.2",
|
||||||
"graceful-fs": "4.2.6",
|
"graceful-fs": "4.2.6",
|
||||||
|
|||||||
68
src/sql/azdata.proposed.d.ts
vendored
68
src/sql/azdata.proposed.d.ts
vendored
@@ -915,9 +915,9 @@ declare module 'azdata' {
|
|||||||
|
|
||||||
export interface QueryExecuteResultSetNotificationParams {
|
export interface QueryExecuteResultSetNotificationParams {
|
||||||
/**
|
/**
|
||||||
* Contains query plans returned by the database in ResultSets.
|
* Contains execution plans returned by the database in ResultSets.
|
||||||
*/
|
*/
|
||||||
executionPlans: QueryPlanGraph[];
|
executionPlans: ExecutionPlanGraph[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResultSetSummary {
|
export interface ResultSetSummary {
|
||||||
@@ -925,10 +925,6 @@ declare module 'azdata' {
|
|||||||
* The visualization options for the result set.
|
* The visualization options for the result set.
|
||||||
*/
|
*/
|
||||||
visualization?: VisualizationOptions;
|
visualization?: VisualizationOptions;
|
||||||
/**
|
|
||||||
* Generic query plan graph to be displayed in the results view.
|
|
||||||
*/
|
|
||||||
showplangraph?: QueryPlanGraph;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1435,18 +1431,26 @@ declare module 'azdata' {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryPlanGraph {
|
export interface ExecutionPlanGraph {
|
||||||
/**
|
/**
|
||||||
* Root of the query plan tree
|
* Root of the execution plan tree
|
||||||
*/
|
*/
|
||||||
root: QueryPlanGraphNode;
|
root: ExecutionPlanNode;
|
||||||
/**
|
/**
|
||||||
* Underlying query for the query plan graph.
|
* Underlying query for the execution plan graph.
|
||||||
*/
|
*/
|
||||||
query: string;
|
query: string;
|
||||||
|
/**
|
||||||
|
* String representation of graph
|
||||||
|
*/
|
||||||
|
graphFile: ExecutionPlanGraphFile;
|
||||||
|
/**
|
||||||
|
* Query recommendations for optimizing performance
|
||||||
|
*/
|
||||||
|
recommendations: ExecutionPlanRecommendations[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryPlanGraphNode {
|
export interface ExecutionPlanNode {
|
||||||
/**
|
/**
|
||||||
* Type of the node. This property determines the icon that is displayed for it
|
* Type of the node. This property determines the icon that is displayed for it
|
||||||
*/
|
*/
|
||||||
@@ -1470,7 +1474,7 @@ declare module 'azdata' {
|
|||||||
/**
|
/**
|
||||||
* Node properties to be shown in the tooltip
|
* Node properties to be shown in the tooltip
|
||||||
*/
|
*/
|
||||||
properties: QueryPlanGraphElementProperty[];
|
properties: ExecutionPlanGraphElementProperty[];
|
||||||
/**
|
/**
|
||||||
* Display name for the node
|
* Display name for the node
|
||||||
*/
|
*/
|
||||||
@@ -1486,14 +1490,14 @@ declare module 'azdata' {
|
|||||||
/**
|
/**
|
||||||
* Direct children of the nodes.
|
* Direct children of the nodes.
|
||||||
*/
|
*/
|
||||||
children: QueryPlanGraphNode[];
|
children: ExecutionPlanNode[];
|
||||||
/**
|
/**
|
||||||
* Edges corresponding to the children.
|
* Edges corresponding to the children.
|
||||||
*/
|
*/
|
||||||
edges: QueryGraphEdge[];
|
edges: ExecutionPlanEdge[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryGraphEdge {
|
export interface ExecutionPlanEdge {
|
||||||
/**
|
/**
|
||||||
* Count of the rows returned by the subtree of the edge.
|
* Count of the rows returned by the subtree of the edge.
|
||||||
*/
|
*/
|
||||||
@@ -1505,18 +1509,18 @@ declare module 'azdata' {
|
|||||||
/**
|
/**
|
||||||
* Edge properties to be shown in the tooltip.
|
* Edge properties to be shown in the tooltip.
|
||||||
*/
|
*/
|
||||||
properties: QueryPlanGraphElementProperty[]
|
properties: ExecutionPlanGraphElementProperty[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryPlanGraphElementProperty {
|
export interface ExecutionPlanGraphElementProperty {
|
||||||
/**
|
/**
|
||||||
* Name of the property
|
* Name of the property
|
||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
/**
|
/**
|
||||||
* Formatted value for the property
|
* value for the property
|
||||||
*/
|
*/
|
||||||
formattedValue: string;
|
value: string | ExecutionPlanGraphElementProperty[];
|
||||||
/**
|
/**
|
||||||
* Flag to show/hide props in tooltip
|
* Flag to show/hide props in tooltip
|
||||||
*/
|
*/
|
||||||
@@ -1530,4 +1534,30 @@ declare module 'azdata' {
|
|||||||
*/
|
*/
|
||||||
isLongString: boolean;
|
isLongString: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ExecutionPlanRecommendations {
|
||||||
|
/**
|
||||||
|
* Text displayed in the show plan graph control description
|
||||||
|
*/
|
||||||
|
displayString: string;
|
||||||
|
/**
|
||||||
|
* Query that is recommended to the user
|
||||||
|
*/
|
||||||
|
queryText: string;
|
||||||
|
/**
|
||||||
|
* Query that will be opened in a new file once the user click on the recommendation
|
||||||
|
*/
|
||||||
|
queryWithDescription: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExecutionPlanGraphFile {
|
||||||
|
/**
|
||||||
|
* File contents
|
||||||
|
*/
|
||||||
|
graphFileContent: string;
|
||||||
|
/**
|
||||||
|
* File type for execution plan. This will be the file type of the editor when the user opens the graph file
|
||||||
|
*/
|
||||||
|
graphFileType: string;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,3 +71,10 @@ export function endsWith(haystack: string, needle: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove line breaks/eols from a string across different operating systems.
|
||||||
|
*/
|
||||||
|
export function removeLineBreaks(str: string): string {
|
||||||
|
return str.replace(/(\r\n|\n|\r)/gm, '');
|
||||||
|
}
|
||||||
|
|||||||
13
src/sql/workbench/common/editor/query/queryPlan2State.ts
Normal file
13
src/sql/workbench/common/editor/query/queryPlan2State.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import type * as azdata from 'azdata';
|
||||||
|
|
||||||
|
export class QueryPlan2State {
|
||||||
|
graphs: azdata.ExecutionPlanGraph[] = [];
|
||||||
|
clearQueryPlan2State() {
|
||||||
|
this.graphs = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import { QueryPlanState } from 'sql/workbench/common/editor/query/queryPlanState
|
|||||||
import { GridPanelState } from 'sql/workbench/common/editor/query/gridTableState';
|
import { GridPanelState } from 'sql/workbench/common/editor/query/gridTableState';
|
||||||
import { QueryModelViewState } from 'sql/workbench/common/editor/query/modelViewState';
|
import { QueryModelViewState } from 'sql/workbench/common/editor/query/modelViewState';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { QueryPlan2State } from 'sql/workbench/common/editor/query/queryPlan2State';
|
||||||
|
|
||||||
export class ResultsViewState {
|
export class ResultsViewState {
|
||||||
public readonly gridPanelState: GridPanelState = new GridPanelState();
|
public readonly gridPanelState: GridPanelState = new GridPanelState();
|
||||||
@@ -19,6 +20,7 @@ export class ResultsViewState {
|
|||||||
public readonly queryPlanState: QueryPlanState = new QueryPlanState();
|
public readonly queryPlanState: QueryPlanState = new QueryPlanState();
|
||||||
public readonly topOperationsState = new TopOperationsState();
|
public readonly topOperationsState = new TopOperationsState();
|
||||||
public readonly dynamicModelViewTabsState: Map<string, QueryModelViewState> = new Map<string, QueryModelViewState>();
|
public readonly dynamicModelViewTabsState: Map<string, QueryModelViewState> = new Map<string, QueryModelViewState>();
|
||||||
|
public readonly queryPlan2State: QueryPlan2State = new QueryPlan2State();
|
||||||
|
|
||||||
public activeTab?: string;
|
public activeTab?: string;
|
||||||
public readonly visibleTabs: Set<string> = new Set<string>();
|
public readonly visibleTabs: Set<string> = new Set<string>();
|
||||||
@@ -27,6 +29,7 @@ export class ResultsViewState {
|
|||||||
this.gridPanelState.dispose();
|
this.gridPanelState.dispose();
|
||||||
this.chartState.dispose();
|
this.chartState.dispose();
|
||||||
this.queryPlanState.dispose();
|
this.queryPlanState.dispose();
|
||||||
|
this.queryPlan2State.clearQueryPlan2State();
|
||||||
this.dynamicModelViewTabsState.forEach((state: QueryModelViewState, identifier: string) => {
|
this.dynamicModelViewTabsState.forEach((state: QueryModelViewState, identifier: string) => {
|
||||||
state.dispose();
|
state.dispose();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ export class QueryResultsView extends Disposable {
|
|||||||
this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: true }));
|
this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: true }));
|
||||||
this._register(attachTabbedPanelStyler(this._panelView, themeService));
|
this._register(attachTabbedPanelStyler(this._panelView, themeService));
|
||||||
this.qpTab = this._register(new QueryPlanTab());
|
this.qpTab = this._register(new QueryPlanTab());
|
||||||
this.qp2Tab = this._register(new QueryPlan2Tab());
|
this.qp2Tab = this._register(this.instantiationService.createInstance(QueryPlan2Tab));
|
||||||
this.topOperationsTab = this._register(new TopOperationsTab(instantiationService));
|
this.topOperationsTab = this._register(new TopOperationsTab(instantiationService));
|
||||||
|
|
||||||
this._panelView.pushTab(this.resultsTab);
|
this._panelView.pushTab(this.resultsTab);
|
||||||
@@ -254,6 +254,8 @@ export class QueryResultsView extends Disposable {
|
|||||||
if (!this.input.state.visibleTabs.has(this.qp2Tab.identifier)) {
|
if (!this.input.state.visibleTabs.has(this.qp2Tab.identifier)) {
|
||||||
this.showPlan2();
|
this.showPlan2();
|
||||||
}
|
}
|
||||||
|
// Adding graph to state and tab as they become available
|
||||||
|
this.input.state.queryPlan2State.graphs.push(...e.planGraphs);
|
||||||
this.qp2Tab.view.addGraphs(e.planGraphs);
|
this.qp2Tab.view.addGraphs(e.planGraphs);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@@ -334,6 +336,7 @@ export class QueryResultsView extends Disposable {
|
|||||||
if (input) {
|
if (input) {
|
||||||
this.resultsTab.view.state = input.state.gridPanelState;
|
this.resultsTab.view.state = input.state.gridPanelState;
|
||||||
this.qpTab.view.setState(input.state.queryPlanState);
|
this.qpTab.view.setState(input.state.queryPlanState);
|
||||||
|
this.qp2Tab.view.addGraphs(input.state.queryPlan2State.graphs);
|
||||||
this.topOperationsTab.view.setState(input.state.topOperationsState);
|
this.topOperationsTab.view.setState(input.state.topOperationsState);
|
||||||
this.chartTab.view.state = input.state.chartState;
|
this.chartTab.view.state = input.state.chartState;
|
||||||
this.dynamicModelViewTabs.forEach((dynamicTab: QueryModelViewTab) => {
|
this.dynamicModelViewTabs.forEach((dynamicTab: QueryModelViewTab) => {
|
||||||
@@ -454,6 +457,7 @@ export class QueryResultsView extends Disposable {
|
|||||||
public hidePlan2() {
|
public hidePlan2() {
|
||||||
if (this._panelView.contains(this.qp2Tab)) {
|
if (this._panelView.contains(this.qp2Tab)) {
|
||||||
this.qp2Tab.clear();
|
this.qp2Tab.clear();
|
||||||
|
this.input.state.queryPlan2State.clearQueryPlan2State();
|
||||||
this._panelView.removeTab(this.qp2Tab.identifier);
|
this._panelView.removeTab(this.qp2Tab.identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { QueryPlan2 } from 'sql/workbench/contrib/queryplan2/browser/queryPlan';
|
|
||||||
import { Action } from 'vs/base/common/actions';
|
|
||||||
import { Codicon } from 'vs/base/common/codicons';
|
|
||||||
import { localize } from 'vs/nls';
|
|
||||||
|
|
||||||
|
|
||||||
export class PropertiesAction extends Action {
|
|
||||||
public static ID = 'qp.propertiesAction';
|
|
||||||
public static LABEL = localize('queryPlanPropertiesActionLabel', "Properties");
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super(PropertiesAction.ID, PropertiesAction.LABEL, Codicon.listUnordered.classNames);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async run(context: QueryPlan2): Promise<void> {
|
|
||||||
context.propContainer.style.visibility = context.propContainer.style.visibility === 'visible' ? 'hidden' : 'visible';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
250
src/sql/workbench/contrib/queryplan2/browser/constants.ts
Normal file
250
src/sql/workbench/contrib/queryplan2/browser/constants.ts
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
let imageBasePath = require.toUrl('./images/icons/');
|
||||||
|
export let queryPlanNodeIconPaths =
|
||||||
|
{
|
||||||
|
// generic icons
|
||||||
|
iteratorCatchAll: imageBasePath + 'iterator_catch_all.png',
|
||||||
|
|
||||||
|
cursorCatchAll: imageBasePath + 'cursor_catch_all.png',
|
||||||
|
|
||||||
|
languageConstructCatchAll: imageBasePath + 'language_construct_catch_all.png',
|
||||||
|
|
||||||
|
// operator icons
|
||||||
|
adaptiveJoin: imageBasePath + 'adaptive_join.png',
|
||||||
|
|
||||||
|
assert: imageBasePath + 'assert.png',
|
||||||
|
|
||||||
|
bitmap: imageBasePath + 'bitmap.png',
|
||||||
|
|
||||||
|
clusteredIndexDelete: imageBasePath + 'clustered_index_delete.png',
|
||||||
|
|
||||||
|
clusteredIndexInsert: imageBasePath + 'clustered_index_insert.png',
|
||||||
|
|
||||||
|
clusteredIndexScan: imageBasePath + 'clustered_index_scan.png',
|
||||||
|
|
||||||
|
clusteredIndexSeek: imageBasePath + 'clustered_index_seek.png',
|
||||||
|
|
||||||
|
clusteredIndexUpdate: imageBasePath + 'clustered_index_update.png',
|
||||||
|
|
||||||
|
clusteredIndexMerge: imageBasePath + 'clustered_index_merge.png',
|
||||||
|
|
||||||
|
clusteredUpdate: imageBasePath + 'clustered_update.png',
|
||||||
|
|
||||||
|
collapse: imageBasePath + 'collapse.png',
|
||||||
|
|
||||||
|
computeScalar: imageBasePath + 'compute_scalar.png',
|
||||||
|
|
||||||
|
concatenation: imageBasePath + 'concatenation.png',
|
||||||
|
|
||||||
|
constantScan: imageBasePath + 'constant_scan.png',
|
||||||
|
|
||||||
|
deletedScan: imageBasePath + 'deleted_scan.png',
|
||||||
|
|
||||||
|
filter: imageBasePath + 'filter.png',
|
||||||
|
|
||||||
|
hashMatch: imageBasePath + 'hash_match.png',
|
||||||
|
|
||||||
|
indexDelete: imageBasePath + 'index_delete.png',
|
||||||
|
|
||||||
|
indexInsert: imageBasePath + 'index_insert.png',
|
||||||
|
|
||||||
|
indexScan: imageBasePath + 'index_scan.png',
|
||||||
|
|
||||||
|
columnstoreIndexDelete: imageBasePath + 'columnstore_index_delete.png',
|
||||||
|
|
||||||
|
columnstoreIndexInsert: imageBasePath + 'columnstore_index_insert.png',
|
||||||
|
|
||||||
|
columnstoreIndexMerge: imageBasePath + 'columnstore_index_merge.png',
|
||||||
|
|
||||||
|
columnstoreIndexScan: imageBasePath + 'columnstore_index_scan.png',
|
||||||
|
|
||||||
|
columnstoreIndexUpdate: imageBasePath + 'columnstore_index_update.png',
|
||||||
|
|
||||||
|
indexSeek: imageBasePath + 'index_seek.png',
|
||||||
|
|
||||||
|
indexSpool: imageBasePath + 'index_spool.png',
|
||||||
|
|
||||||
|
indexUpdate: imageBasePath + 'index_update.png',
|
||||||
|
|
||||||
|
insertedScan: imageBasePath + 'inserted_scan.png',
|
||||||
|
|
||||||
|
logRowScan: imageBasePath + 'log_row_scan.png',
|
||||||
|
|
||||||
|
mergeInterval: imageBasePath + 'merge_interval.png',
|
||||||
|
|
||||||
|
mergeJoin: imageBasePath + 'merge_join.png',
|
||||||
|
|
||||||
|
nestedLoops: imageBasePath + 'nested_loops.png',
|
||||||
|
|
||||||
|
parallelism: imageBasePath + 'parallelism.png',
|
||||||
|
|
||||||
|
parameterTableScan: imageBasePath + 'parameter_table_scan.png',
|
||||||
|
|
||||||
|
print: imageBasePath + 'print.png',
|
||||||
|
|
||||||
|
rank: imageBasePath + 'rank.png',
|
||||||
|
|
||||||
|
foreignKeyReferencesCheck: imageBasePath + 'foreign_key_references_check.png',
|
||||||
|
|
||||||
|
remoteDelete: imageBasePath + 'remote_delete.png',
|
||||||
|
|
||||||
|
remoteIndexScan: imageBasePath + 'remote_index_scan.png',
|
||||||
|
|
||||||
|
remoteIndexSeek: imageBasePath + 'remote_index_seek.png',
|
||||||
|
|
||||||
|
remoteInsert: imageBasePath + 'remote_insert.png',
|
||||||
|
|
||||||
|
remoteQuery: imageBasePath + 'remote_query.png',
|
||||||
|
|
||||||
|
remoteScan: imageBasePath + 'remote_scan.png',
|
||||||
|
|
||||||
|
remoteUpdate: imageBasePath + 'remote_update.png',
|
||||||
|
|
||||||
|
ridLookup: imageBasePath + 'rid_lookup.png',
|
||||||
|
|
||||||
|
rowCountSpool: imageBasePath + 'row_count_spool.png',
|
||||||
|
|
||||||
|
segment: imageBasePath + 'segment.png',
|
||||||
|
|
||||||
|
sequence: imageBasePath + 'sequence.png',
|
||||||
|
|
||||||
|
sequenceProject: imageBasePath + 'sequence_project.png',
|
||||||
|
|
||||||
|
sort: imageBasePath + 'sort.png',
|
||||||
|
|
||||||
|
split: imageBasePath + 'split.png',
|
||||||
|
|
||||||
|
streamAggregate: imageBasePath + 'stream_aggregate.png',
|
||||||
|
|
||||||
|
switchStatement: imageBasePath + 'switch.png',
|
||||||
|
|
||||||
|
tableValuedFunction: imageBasePath + 'table_valued_function.png',
|
||||||
|
|
||||||
|
tableDelete: imageBasePath + 'table_delete.png',
|
||||||
|
|
||||||
|
tableInsert: imageBasePath + 'table_insert.png',
|
||||||
|
|
||||||
|
tableScan: imageBasePath + 'table_scan.png',
|
||||||
|
|
||||||
|
tableSpool: imageBasePath + 'table_spool.png',
|
||||||
|
|
||||||
|
tableUpdate: imageBasePath + 'table_update.png',
|
||||||
|
|
||||||
|
tableMerge: imageBasePath + 'table_merge.png',
|
||||||
|
|
||||||
|
tfp: imageBasePath + 'predict.png',
|
||||||
|
|
||||||
|
top: imageBasePath + 'top.png',
|
||||||
|
|
||||||
|
udx: imageBasePath + 'udx.png',
|
||||||
|
|
||||||
|
batchHashTableBuild: imageBasePath + 'batch_hash_table_build.png',
|
||||||
|
|
||||||
|
windowSpool: imageBasePath + 'table_spool.png',
|
||||||
|
|
||||||
|
windowAggregate: imageBasePath + 'window_aggregate.png',
|
||||||
|
|
||||||
|
// cursor operators
|
||||||
|
fetchQuery: imageBasePath + 'fetch_query.png',
|
||||||
|
|
||||||
|
populateQuery: imageBasePath + 'population_query.png',
|
||||||
|
|
||||||
|
refreshQuery: imageBasePath + 'refresh_query.png',
|
||||||
|
|
||||||
|
// shiloh operators
|
||||||
|
result: imageBasePath + 'result.png',
|
||||||
|
|
||||||
|
aggregate: imageBasePath + 'aggregate.png',
|
||||||
|
|
||||||
|
assign: imageBasePath + 'assign.png',
|
||||||
|
|
||||||
|
arithmeticExpression: imageBasePath + 'arithmetic_expression.png',
|
||||||
|
|
||||||
|
bookmarkLookup: imageBasePath + 'bookmark_lookup.png',
|
||||||
|
|
||||||
|
convert: imageBasePath + 'convert.png',
|
||||||
|
|
||||||
|
declare: imageBasePath + 'declare.png',
|
||||||
|
|
||||||
|
deleteOperator: imageBasePath + 'delete.png',
|
||||||
|
|
||||||
|
dynamic: imageBasePath + 'dynamic.png',
|
||||||
|
|
||||||
|
hashMatchRoot: imageBasePath + 'hash_match_root.png',
|
||||||
|
|
||||||
|
hashMatchTeam: imageBasePath + 'hash_match_team.png',
|
||||||
|
|
||||||
|
ifOperator: imageBasePath + 'if.png',
|
||||||
|
|
||||||
|
insert: imageBasePath + 'insert.png',
|
||||||
|
|
||||||
|
intrinsic: imageBasePath + 'intrinsic.png',
|
||||||
|
|
||||||
|
keyset: imageBasePath + 'keyset.png',
|
||||||
|
|
||||||
|
locate: imageBasePath + 'locate.png',
|
||||||
|
|
||||||
|
populationQuery: imageBasePath + 'population_query.png',
|
||||||
|
|
||||||
|
setFunction: imageBasePath + 'set_function.png',
|
||||||
|
|
||||||
|
snapshot: imageBasePath + 'snapshot.png',
|
||||||
|
|
||||||
|
spool: imageBasePath + 'spool.png',
|
||||||
|
|
||||||
|
tsql: imageBasePath + 'sql.png',
|
||||||
|
|
||||||
|
update: imageBasePath + 'update.png',
|
||||||
|
|
||||||
|
// fake operators
|
||||||
|
keyLookup: imageBasePath + 'bookmark_lookup.png',
|
||||||
|
|
||||||
|
// PDW operators
|
||||||
|
apply: imageBasePath + 'apply.png',
|
||||||
|
|
||||||
|
broadcast: imageBasePath + 'broadcast.png',
|
||||||
|
|
||||||
|
computeToControlNode: imageBasePath + 'compute_to_control_node.png',
|
||||||
|
|
||||||
|
constTableGet: imageBasePath + 'const_table_get.png',
|
||||||
|
|
||||||
|
controlToComputeNodes: imageBasePath + 'control_to_compute_nodes.png',
|
||||||
|
|
||||||
|
externalBroadcast: imageBasePath + 'external_broadcast.png',
|
||||||
|
|
||||||
|
externalExport: imageBasePath + 'external_export.png',
|
||||||
|
|
||||||
|
externalLocalStreaming: imageBasePath + 'external_local_streaming.png',
|
||||||
|
|
||||||
|
externalRoundRobin: imageBasePath + 'external_round_robin.png',
|
||||||
|
|
||||||
|
externalShuffle: imageBasePath + 'external_shuffle.png',
|
||||||
|
|
||||||
|
get: imageBasePath + 'get.png',
|
||||||
|
|
||||||
|
groupByApply: imageBasePath + 'apply.png',
|
||||||
|
|
||||||
|
groupByAggregate: imageBasePath + 'group_by_aggregate.png',
|
||||||
|
|
||||||
|
join: imageBasePath + 'join.png',
|
||||||
|
|
||||||
|
localCube: imageBasePath + 'intrinsic.png',
|
||||||
|
|
||||||
|
project: imageBasePath + 'project.png',
|
||||||
|
|
||||||
|
shuffle: imageBasePath + 'shuffle.png',
|
||||||
|
|
||||||
|
singleSourceRoundRobin: imageBasePath + 'single_source_round_robin.png',
|
||||||
|
|
||||||
|
singleSourceShuffle: imageBasePath + 'single_source_shuffle.png',
|
||||||
|
|
||||||
|
trim: imageBasePath + 'trim.png',
|
||||||
|
|
||||||
|
union: imageBasePath + 'union.png',
|
||||||
|
|
||||||
|
unionAll: imageBasePath + 'union_all.png'
|
||||||
|
};
|
||||||
@@ -0,0 +1,260 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import type * as azdata from 'azdata';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { Action } from 'vs/base/common/actions';
|
||||||
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { attachTableStyler } from 'sql/platform/theme/common/styler';
|
||||||
|
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
|
||||||
|
import { Table } from 'sql/base/browser/ui/table/table';
|
||||||
|
import { RESULTS_GRID_DEFAULTS } from 'sql/workbench/common/constants';
|
||||||
|
import { isString } from 'vs/base/common/types';
|
||||||
|
import { ActionBar } from 'sql/base/browser/ui/taskbar/actionbar';
|
||||||
|
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||||
|
|
||||||
|
|
||||||
|
export class GraphElementPropertiesView {
|
||||||
|
|
||||||
|
// Title bar with close button action
|
||||||
|
private _propertiesTitle!: HTMLElement;
|
||||||
|
private _titleText!: HTMLElement;
|
||||||
|
private _titleActionBarContainer!: HTMLElement;
|
||||||
|
private _titleActionBar: ActionBar;
|
||||||
|
|
||||||
|
// Div that holds the name of the element selected
|
||||||
|
private _operationName!: HTMLElement;
|
||||||
|
|
||||||
|
// Action bar that contains sorting option for the table
|
||||||
|
private _tableActionBarContainer!: HTMLElement;
|
||||||
|
private _tableActionBar!: ActionBar;
|
||||||
|
|
||||||
|
// Properties table
|
||||||
|
private _table: Table<Slick.SlickData>;
|
||||||
|
private _dataView: TableDataView<Slick.SlickData>;
|
||||||
|
private _data: { [key: string]: string }[];
|
||||||
|
private _tableContainer!: HTMLElement;
|
||||||
|
private _actualTable!: HTMLElement;
|
||||||
|
|
||||||
|
// Table dimensions.
|
||||||
|
private _tableWidth = 485;
|
||||||
|
private _tableHeight = 420;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private _parentContainer: HTMLElement,
|
||||||
|
private _themeService: IThemeService,
|
||||||
|
private _model: GraphElementPropertyViewData = <GraphElementPropertyViewData>{}
|
||||||
|
) {
|
||||||
|
this._parentContainer.style.display = 'none';
|
||||||
|
|
||||||
|
this._propertiesTitle = DOM.$('.title');
|
||||||
|
this._parentContainer.appendChild(this._propertiesTitle);
|
||||||
|
|
||||||
|
this._titleText = DOM.$('h3');
|
||||||
|
this._titleText.classList.add('text');
|
||||||
|
this._titleText.innerText = localize('nodePropertyViewTitle', "Properties");
|
||||||
|
this._propertiesTitle.appendChild(this._titleText);
|
||||||
|
|
||||||
|
this._titleActionBarContainer = DOM.$('.action-bar');
|
||||||
|
this._propertiesTitle.appendChild(this._titleActionBarContainer);
|
||||||
|
this._titleActionBar = new ActionBar(this._titleActionBarContainer, {
|
||||||
|
orientation: ActionsOrientation.HORIZONTAL, context: this
|
||||||
|
});
|
||||||
|
this._titleActionBar.pushAction([new ClosePropertyViewAction()], { icon: true, label: false });
|
||||||
|
|
||||||
|
this._operationName = DOM.$('h3');
|
||||||
|
this._operationName.classList.add('operation-name');
|
||||||
|
this._parentContainer.appendChild(this._operationName);
|
||||||
|
|
||||||
|
this._tableActionBarContainer = DOM.$('.table-action-bar');
|
||||||
|
this._parentContainer.appendChild(this._tableActionBarContainer);
|
||||||
|
this._tableActionBar = new ActionBar(this._tableActionBarContainer, {
|
||||||
|
orientation: ActionsOrientation.HORIZONTAL, context: this
|
||||||
|
});
|
||||||
|
this._tableActionBar.pushAction([new SortPropertiesByDisplayOrderAction(), new SortPropertiesAlphabeticallyAction()], { icon: true, label: false });
|
||||||
|
|
||||||
|
|
||||||
|
this._tableContainer = DOM.$('.table-container');
|
||||||
|
this._parentContainer.appendChild(this._tableContainer);
|
||||||
|
|
||||||
|
this._actualTable = DOM.$('.table');
|
||||||
|
this._tableContainer.appendChild(this._actualTable);
|
||||||
|
|
||||||
|
this._dataView = new TableDataView();
|
||||||
|
this._data = [];
|
||||||
|
|
||||||
|
const columns: Slick.Column<Slick.SlickData>[] = [
|
||||||
|
{
|
||||||
|
id: 'name',
|
||||||
|
name: localize('nodePropertyViewNameNameColumnHeader', "Name"),
|
||||||
|
field: 'name',
|
||||||
|
width: 250,
|
||||||
|
editor: Slick.Editors.Text,
|
||||||
|
headerCssClass: 'prop-table-header'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'value',
|
||||||
|
name: localize('nodePropertyViewNameValueColumnHeader', "Value"),
|
||||||
|
field: 'value',
|
||||||
|
width: 250,
|
||||||
|
editor: Slick.Editors.Text,
|
||||||
|
headerCssClass: 'prop-table-header'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
this._table = new Table(this._actualTable, {
|
||||||
|
dataProvider: this._dataView, columns: columns
|
||||||
|
}, {
|
||||||
|
rowHeight: RESULTS_GRID_DEFAULTS.rowHeight,
|
||||||
|
forceFitColumns: true,
|
||||||
|
defaultColumnWidth: 120
|
||||||
|
});
|
||||||
|
|
||||||
|
attachTableStyler(this._table, this._themeService);
|
||||||
|
}
|
||||||
|
|
||||||
|
public set graphElement(element: azdata.ExecutionPlanNode | azdata.ExecutionPlanEdge) {
|
||||||
|
this._model.graphElement = element;
|
||||||
|
this.sortPropertiesByImportance();
|
||||||
|
this.renderView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sortPropertiesAlphabetically(): void {
|
||||||
|
this._model.graphElement.properties = this._model.graphElement.properties.sort((a, b) => {
|
||||||
|
if (!a?.name && !b?.name) {
|
||||||
|
return 0;
|
||||||
|
} else if (!a?.name) {
|
||||||
|
return -1;
|
||||||
|
} else if (!b?.name) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return a.name.localeCompare(b.name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.renderView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sortPropertiesByImportance(): void {
|
||||||
|
this._model.graphElement.properties = this._model.graphElement.properties.sort((a, b) => {
|
||||||
|
if (!a?.displayOrder && !b?.displayOrder) {
|
||||||
|
return 0;
|
||||||
|
} else if (!a?.displayOrder) {
|
||||||
|
return -1;
|
||||||
|
} else if (!b?.displayOrder) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return a.displayOrder - b.displayOrder;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.renderView();
|
||||||
|
}
|
||||||
|
|
||||||
|
public set tableHeight(value: number) {
|
||||||
|
if (this.tableHeight !== value) {
|
||||||
|
this._tableHeight = value;
|
||||||
|
this.renderView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tableHeight(): number {
|
||||||
|
return this._tableHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set tableWidth(value: number) {
|
||||||
|
if (this._tableWidth !== value) {
|
||||||
|
this._tableWidth = value;
|
||||||
|
this.renderView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tableWidth(): number {
|
||||||
|
return this._tableWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderView(): void {
|
||||||
|
if (this._model.graphElement) {
|
||||||
|
const nodeName = (<azdata.ExecutionPlanNode>this._model.graphElement).name;
|
||||||
|
this._operationName.innerText = nodeName ?? localize('queryPlanPropertiesEdgeOperationName', "Edge"); //since edges do not have names like node, we set the operation name to 'Edge'
|
||||||
|
}
|
||||||
|
this._tableContainer.scrollTo(0, 0);
|
||||||
|
this._dataView.clear();
|
||||||
|
this._data = this.convertPropertiesToTableRows(this._model.graphElement.properties, -1, 0);
|
||||||
|
this._dataView.push(this._data);
|
||||||
|
this._table.setData(this._dataView);
|
||||||
|
this._table.autosizeColumns();
|
||||||
|
this._table.updateRowCount();
|
||||||
|
this._table.layout(new DOM.Dimension(this._tableWidth, this._tableHeight));
|
||||||
|
this._table.resizeCanvas();
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertPropertiesToTableRows(props: azdata.ExecutionPlanGraphElementProperty[], parentIndex: number, indent: number, rows: { [key: string]: string }[] = []): { [key: string]: string }[] {
|
||||||
|
if (!props) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
props.forEach((p, i) => {
|
||||||
|
let row = {};
|
||||||
|
row['name'] = '\t'.repeat(indent) + p.name;
|
||||||
|
row['parent'] = parentIndex;
|
||||||
|
rows.push(row);
|
||||||
|
if (!isString(p.value)) {
|
||||||
|
row['value'] = '';
|
||||||
|
this.convertPropertiesToTableRows(p.value, rows.length - 1, indent + 2, rows);
|
||||||
|
} else {
|
||||||
|
row['value'] = p.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleVisibility(): void {
|
||||||
|
this._parentContainer.style.display = this._parentContainer.style.display === 'none' ? 'block' : 'none';
|
||||||
|
this.renderView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GraphElementPropertyViewData {
|
||||||
|
graphElement: azdata.ExecutionPlanNode | azdata.ExecutionPlanEdge;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClosePropertyViewAction extends Action {
|
||||||
|
public static ID = 'qp.propertiesView.close';
|
||||||
|
public static LABEL = localize('queryPlanPropertyViewClose', "Close");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(ClosePropertyViewAction.ID, ClosePropertyViewAction.LABEL, Codicon.close.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: GraphElementPropertiesView): Promise<void> {
|
||||||
|
context.toggleVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SortPropertiesAlphabeticallyAction extends Action {
|
||||||
|
public static ID = 'qp.propertiesView.sortByAlphabet';
|
||||||
|
public static LABEL = localize('queryPlanPropertyViewSortAlphabetically', "Alphabetical");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(SortPropertiesAlphabeticallyAction.ID, SortPropertiesAlphabeticallyAction.LABEL, Codicon.sortPrecedence.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: GraphElementPropertiesView): Promise<void> {
|
||||||
|
context.sortPropertiesAlphabetically();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SortPropertiesByDisplayOrderAction extends Action {
|
||||||
|
public static ID = 'qp.propertiesView.sortByDisplayOrder';
|
||||||
|
public static LABEL = localize('queryPlanPropertyViewSortByDisplayOrde', "Categorized");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(SortPropertiesByDisplayOrderAction.ID, SortPropertiesByDisplayOrderAction.LABEL, Codicon.listOrdered.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: GraphElementPropertiesView): Promise<void> {
|
||||||
|
context.sortPropertiesByImportance();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,67 +3,213 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
.qp-container .query-plan {
|
/* Styling for the a queryplan container in the tab */
|
||||||
|
.qps-container .query-plan {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 500px;
|
height: 500px;
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* horizontal height resizing sash container that is below a queryplan */
|
||||||
|
.qps-container .query-plan-sash {
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The actual sash element constructed by code. Important is used here because the width of the sash is fixed.
|
||||||
|
However we always want it to be the width of the container it is resizing.
|
||||||
|
*/
|
||||||
|
.qps-container .query-plan-sash > div {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container that contains showplan header, description and graph diagram */
|
||||||
|
.qps-container .query-plan .plan {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Container that contains views made by the action-bar actions */
|
||||||
|
.qps-container .query-plan .plan .plan-action-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* views created by the action-bar actions */
|
||||||
|
.qps-container .query-plan .plan .plan-action-container .child {
|
||||||
|
flex: 0 0 25px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search node action view */
|
||||||
|
.qps-container .query-plan .plan .plan-action-container .search-node-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
border: 1px solid;
|
||||||
|
padding: 5px;
|
||||||
|
height: auto;
|
||||||
|
width: 470px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* input bar styling in search node action view */
|
||||||
|
.qps-container .query-plan .plan .plan-action-container .search-node-view .search-bar-container{
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* styling for select element in search node action view */
|
||||||
|
.qps-container .query-plan .plan .plan-action-container .search-node-view .search-bar-container > select{
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom zoom action view */
|
||||||
|
.qps-container .query-plan .plan .plan-action-container .custom-zoom-view {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
border: 1px solid;
|
||||||
|
padding: 5px;
|
||||||
|
height: auto;
|
||||||
|
width: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* query plan header that contains the relative query cost, query statement and recommendations */
|
||||||
|
.qps-container .query-plan .plan .header {
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
border-bottom: solid 1px;
|
||||||
|
font-weight: bolder;
|
||||||
|
padding-left: 5px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* each link in query plan recommendations */
|
||||||
|
.qps-container .query-plan .plan .header .recommendations > a {
|
||||||
|
width: fit-content;
|
||||||
|
align-items: left;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* graph diagram in query plan */
|
||||||
|
.qps-container .query-plan .plan .diagram {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Properties view in query plan */
|
||||||
|
.qps-container .query-plan .properties {
|
||||||
|
flex: 0 0 500px;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 500px;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border: 1px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Title container of the properties view */
|
||||||
|
.qps-container .query-plan .properties .title {
|
||||||
|
line-height: 22px;
|
||||||
|
height: 22px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-top: 3px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
border-bottom: solid 1px;
|
||||||
|
flex: 0 0 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* text in title container of properties view */
|
||||||
|
.qps-container .query-plan .properties .title .text {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 11px;
|
||||||
|
-webkit-margin-before: 0;
|
||||||
|
-webkit-margin-after: 0;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* action bar in the title container for the properties view. This contains the close icon */
|
||||||
|
.qps-container .query-plan .properties .title .action-bar {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 11px;
|
||||||
|
-webkit-margin-before: 0;
|
||||||
|
-webkit-margin-after: 0;
|
||||||
|
flex: 0 0 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Operation name styling in the properties view. */
|
||||||
|
.qps-container .query-plan .properties .operation-name {
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
-webkit-margin-before: 0;
|
||||||
|
-webkit-margin-after: 0;
|
||||||
|
flex: 0 0 25px;
|
||||||
|
margin-top: 3px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Properties table container in properties view */
|
||||||
|
.qps-container .query-plan .properties .table-container {
|
||||||
|
overflow-y: scroll;
|
||||||
|
flex: 1;
|
||||||
|
flex-grow: 1;
|
||||||
border-top: 1px solid;
|
border-top: 1px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qp-container .query-plan .actionbar-container {
|
/* Action bar for the query plan */
|
||||||
height: 100%;
|
.qps-container .query-plan .action-bar-container {
|
||||||
width: 30px;
|
flex: 0 0 25px;
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
right: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.qp-container .query-plan .properties-container {
|
/* styling for the column headers in the properties table */
|
||||||
position: absolute;
|
.qps-container .properties .table-container .prop-table-header {
|
||||||
top: 0px;
|
white-space: nowrap;
|
||||||
right: 32px;
|
text-overflow: ellipsis;
|
||||||
height: 100%;
|
overflow: hidden;
|
||||||
overflow-y: scroll;
|
font-size: 11px;
|
||||||
background-color: #eeeeee;
|
min-width: 10px;
|
||||||
width: 510px;
|
-webkit-margin-before: 0;
|
||||||
}
|
-webkit-margin-after: 0;
|
||||||
|
|
||||||
.qp-container .prop-table-header {
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qp-container .properties-header {
|
.qps-container .properties-header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qp-container .properties-toggle {
|
.qps-container .properties-toggle {
|
||||||
height: 9px;
|
height: 9px;
|
||||||
width: 9px;
|
width: 9px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qp-container .properties-toggle .expand {
|
.qps-container .properties-toggle .expand {
|
||||||
background: url(../images/expand.gif) no-repeat center center;
|
background: url(../images/expand.gif) no-repeat center center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qp-container .properties-toggle .collapse {
|
.qps-container .properties-toggle .collapse {
|
||||||
background: url(../images/collapse.gif) no-repeat center center;
|
background: url(../images/collapse.gif) no-repeat center center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.qp-container .mxTooltip {
|
|
||||||
-webkit-box-shadow: 3px 3px 12px #C0C0C0;
|
|
||||||
-moz-box-shadow: 3px 3px 12px #C0C0C0;
|
|
||||||
box-shadow: 3px 3px 12px #C0C0C0;
|
|
||||||
background: #FFFFCC;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: black;
|
|
||||||
font-family: Arial;
|
|
||||||
font-size: 8pt;
|
|
||||||
position: absolute;
|
|
||||||
cursor: default;
|
|
||||||
padding: 4px;
|
|
||||||
color: black;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
|
|||||||
117
src/sql/workbench/contrib/queryplan2/browser/planHeader.ts
Normal file
117
src/sql/workbench/contrib/queryplan2/browser/planHeader.ts
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import type * as azdata from 'azdata';
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { openNewQuery } from 'sql/workbench/contrib/query/browser/queryActions';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
|
||||||
|
import { Button } from 'sql/base/browser/ui/button/button';
|
||||||
|
import { removeLineBreaks } from 'sql/base/common/strings';
|
||||||
|
|
||||||
|
export class PlanHeader {
|
||||||
|
|
||||||
|
private _graphIndex: number; // Index of the graph in the view
|
||||||
|
private _relativeCost: number; // Relative cost of the graph to the script
|
||||||
|
private _graphIndexAndCostContainer: HTMLElement; //Container that holds the graph index and relative cost
|
||||||
|
|
||||||
|
|
||||||
|
private _query: string;
|
||||||
|
private _queryContainer: HTMLElement; // container that holds query text
|
||||||
|
|
||||||
|
private _recommendations: azdata.ExecutionPlanRecommendations[];
|
||||||
|
private _recommendationsContainer: HTMLElement; // container that holds graph recommendations
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private _parentContainer: HTMLElement,
|
||||||
|
headerData: PlanHeaderData,
|
||||||
|
@IInstantiationService public readonly _instantiationService: IInstantiationService) {
|
||||||
|
|
||||||
|
this._graphIndex = headerData.planIndex;
|
||||||
|
this._relativeCost = headerData.relativeCost;
|
||||||
|
this._query = headerData.query;
|
||||||
|
this._recommendations = headerData.recommendations ?? [];
|
||||||
|
|
||||||
|
this._graphIndexAndCostContainer = DOM.$('.index-row');
|
||||||
|
this._queryContainer = DOM.$('.query-row');
|
||||||
|
this._recommendationsContainer = DOM.$('.recommendations');
|
||||||
|
|
||||||
|
this._parentContainer.appendChild(this._graphIndexAndCostContainer);
|
||||||
|
this._parentContainer.appendChild(this._queryContainer);
|
||||||
|
this._parentContainer.appendChild(this._recommendationsContainer);
|
||||||
|
|
||||||
|
this.renderGraphIndexAndCost();
|
||||||
|
this.renderQueryText();
|
||||||
|
this.renderRecommendations();
|
||||||
|
}
|
||||||
|
|
||||||
|
public set graphIndex(index: number) {
|
||||||
|
this._graphIndex = index;
|
||||||
|
this.renderGraphIndexAndCost();
|
||||||
|
}
|
||||||
|
public set relativeCost(cost: number) {
|
||||||
|
this._relativeCost = cost;
|
||||||
|
this.renderGraphIndexAndCost();
|
||||||
|
}
|
||||||
|
public set query(query: string) {
|
||||||
|
this._query = removeLineBreaks(query);
|
||||||
|
this.renderQueryText();
|
||||||
|
}
|
||||||
|
|
||||||
|
public set recommendations(recommendations: azdata.ExecutionPlanRecommendations[]) {
|
||||||
|
recommendations.forEach(r => {
|
||||||
|
r.displayString = removeLineBreaks(r.displayString);
|
||||||
|
});
|
||||||
|
this._recommendations = recommendations;
|
||||||
|
this.renderRecommendations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderGraphIndexAndCost(): void {
|
||||||
|
if (this._graphIndex && this._relativeCost) {
|
||||||
|
this._graphIndexAndCostContainer.innerText = localize(
|
||||||
|
{
|
||||||
|
key: 'planHeaderIndexAndCost',
|
||||||
|
comment: [
|
||||||
|
'{0} is the index of the graph in the execution plan tab',
|
||||||
|
'{1} is the relative cost in percentage of the graph to the rest of the graphs in execution plan tab '
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Query {0}: Query cost (relative to the script): {1}%", this._graphIndex, this._relativeCost.toFixed(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderQueryText(): void {
|
||||||
|
this._queryContainer.innerText = this._query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderRecommendations(): void {
|
||||||
|
while (this._recommendationsContainer.firstChild) {
|
||||||
|
this._recommendationsContainer.removeChild(this._recommendationsContainer.firstChild);
|
||||||
|
}
|
||||||
|
this._recommendations.forEach(r => {
|
||||||
|
|
||||||
|
const link = new Button(this._recommendationsContainer, {
|
||||||
|
title: r.displayString,
|
||||||
|
secondary: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
link.label = r.displayString;
|
||||||
|
|
||||||
|
//Enabling on click action for recommendations. It will open the recommendation File
|
||||||
|
link.onDidClick(e => {
|
||||||
|
this._instantiationService.invokeFunction(openNewQuery, undefined, r.queryWithDescription, RunQueryOnConnectionMode.none);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlanHeaderData {
|
||||||
|
planIndex?: number;
|
||||||
|
relativeCost?: number;
|
||||||
|
query?: string;
|
||||||
|
recommendations?: azdata.ExecutionPlanRecommendations[];
|
||||||
|
}
|
||||||
@@ -6,16 +6,33 @@
|
|||||||
import 'vs/css!./media/queryPlan2';
|
import 'vs/css!./media/queryPlan2';
|
||||||
import type * as azdata from 'azdata';
|
import type * as azdata from 'azdata';
|
||||||
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
|
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
|
||||||
import { URI } from 'vs/base/common/uri';
|
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { dispose } from 'vs/base/common/lifecycle';
|
import { dispose } from 'vs/base/common/lifecycle';
|
||||||
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
import { ActionBar } from 'sql/base/browser/ui/taskbar/actionbar';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { PropertiesAction } from 'sql/workbench/contrib/queryplan2/browser/actions/propertiesAction';
|
|
||||||
import * as azdataGraphModule from 'azdataGraph';
|
import * as azdataGraphModule from 'azdataGraph';
|
||||||
import { escape } from 'sql/base/common/strings';
|
import { queryPlanNodeIconPaths } from 'sql/workbench/contrib/queryplan2/browser/constants';
|
||||||
|
import { isString } from 'vs/base/common/types';
|
||||||
|
import { PlanHeader } from 'sql/workbench/contrib/queryplan2/browser/planHeader';
|
||||||
|
import { GraphElementPropertiesView } from 'sql/workbench/contrib/queryplan2/browser/graphElementPropertiesView';
|
||||||
|
import { Action } from 'vs/base/common/actions';
|
||||||
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { openNewQuery } from 'sql/workbench/contrib/query/browser/queryActions';
|
||||||
|
import { RunQueryOnConnectionMode } from 'sql/platform/connection/common/connectionManagement';
|
||||||
|
import { IColorTheme, ICssStyleCollector, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||||
|
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||||
|
import { editorBackground, foreground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||||
|
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||||
|
import { ISashEvent, ISashLayoutProvider, Orientation, Sash } from 'vs/base/browser/ui/sash/sash';
|
||||||
|
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||||
|
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||||
|
import { formatDocumentWithSelectedProvider, FormattingMode } from 'vs/editor/contrib/format/format';
|
||||||
|
import { Progress } from 'vs/platform/progress/common/progress';
|
||||||
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
|
|
||||||
let azdataGraph = azdataGraphModule();
|
let azdataGraph = azdataGraphModule();
|
||||||
|
|
||||||
export class QueryPlan2Tab implements IPanelTab {
|
export class QueryPlan2Tab implements IPanelTab {
|
||||||
@@ -23,8 +40,10 @@ export class QueryPlan2Tab implements IPanelTab {
|
|||||||
public readonly identifier = 'QueryPlan2Tab';
|
public readonly identifier = 'QueryPlan2Tab';
|
||||||
public readonly view: QueryPlan2View;
|
public readonly view: QueryPlan2View;
|
||||||
|
|
||||||
constructor() {
|
constructor(
|
||||||
this.view = new QueryPlan2View();
|
@IInstantiationService instantiationService: IInstantiationService,
|
||||||
|
) {
|
||||||
|
this.view = instantiationService.createInstance(QueryPlan2View);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose() {
|
public dispose() {
|
||||||
@@ -34,12 +53,18 @@ export class QueryPlan2Tab implements IPanelTab {
|
|||||||
public clear() {
|
public clear() {
|
||||||
this.view.clear();
|
this.view.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryPlan2View implements IPanelView {
|
export class QueryPlan2View implements IPanelView {
|
||||||
private _qps?: QueryPlan2[] = [];
|
private _qps?: QueryPlan2[] = [];
|
||||||
private _graphs?: azdata.QueryPlanGraph[] = [];
|
private _graphs?: azdata.ExecutionPlanGraph[] = [];
|
||||||
private _container = DOM.$('.qp-container');
|
private _container = DOM.$('.qps-container');
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IInstantiationService private instantiationService: IInstantiationService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
public render(container: HTMLElement): void {
|
public render(container: HTMLElement): void {
|
||||||
container.appendChild(this._container);
|
container.appendChild(this._container);
|
||||||
@@ -63,15 +88,17 @@ export class QueryPlan2View implements IPanelView {
|
|||||||
DOM.clearNode(this._container);
|
DOM.clearNode(this._container);
|
||||||
}
|
}
|
||||||
|
|
||||||
public addGraphs(newGraphs: azdata.QueryPlanGraph[]) {
|
public addGraphs(newGraphs: azdata.ExecutionPlanGraph[] | undefined) {
|
||||||
|
if (newGraphs) {
|
||||||
newGraphs.forEach(g => {
|
newGraphs.forEach(g => {
|
||||||
const qp2 = new QueryPlan2(this._container, this._qps.length + 1);
|
const qp2 = this.instantiationService.createInstance(QueryPlan2, this._container, this._qps.length + 1);
|
||||||
qp2.graph = g;
|
qp2.graph = g;
|
||||||
this._qps.push(qp2);
|
this._qps.push(qp2);
|
||||||
this._graphs.push(g);
|
this._graphs.push(g);
|
||||||
this.updateRelativeCosts();
|
this.updateRelativeCosts();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private updateRelativeCosts() {
|
private updateRelativeCosts() {
|
||||||
const sum = this._graphs.reduce((prevCost: number, cg) => {
|
const sum = this._graphs.reduce((prevCost: number, cg) => {
|
||||||
@@ -80,420 +107,130 @@ export class QueryPlan2View implements IPanelView {
|
|||||||
|
|
||||||
if (sum > 0) {
|
if (sum > 0) {
|
||||||
this._qps.forEach(qp => {
|
this._qps.forEach(qp => {
|
||||||
qp.relativeCost = ((qp.graph.root.subTreeCost + qp.graph.root.cost) / sum) * 100;
|
qp.planHeader.relativeCost = ((qp.graph.root.subTreeCost + qp.graph.root.cost) / sum) * 100;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryPlan2 {
|
export class QueryPlan2 implements ISashLayoutProvider {
|
||||||
private _graph?: azdata.QueryPlanGraph;
|
private _graph?: azdata.ExecutionPlanGraph;
|
||||||
private _relativeCost?: globalThis.Text;
|
|
||||||
private _actionBar: ActionBar;
|
|
||||||
private _table: Slick.Grid<any>;
|
|
||||||
private _dataView: Slick.Data.DataView<any>;
|
|
||||||
private _container: HTMLElement;
|
|
||||||
private _actionBarContainer: HTMLElement;
|
|
||||||
private _data: any[];
|
|
||||||
private _iconMap: any = new Object();
|
|
||||||
private _iconPaths: any = new Object();
|
|
||||||
|
|
||||||
public propContainer: HTMLElement;
|
private _container: HTMLElement;
|
||||||
|
|
||||||
|
private _actionBarContainer: HTMLElement;
|
||||||
|
private _actionBar: ActionBar;
|
||||||
|
|
||||||
|
public planHeader: PlanHeader;
|
||||||
|
private _planContainer: HTMLElement;
|
||||||
|
private _planHeaderContainer: HTMLElement;
|
||||||
|
|
||||||
|
public propertiesView: GraphElementPropertiesView;
|
||||||
|
private _propContainer: HTMLElement;
|
||||||
|
|
||||||
|
private _azdataGraphDiagram: any;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
parent: HTMLElement,
|
parent: HTMLElement,
|
||||||
private _graphIndex: number,
|
private _graphIndex: number,
|
||||||
|
@IInstantiationService public readonly _instantiationService: IInstantiationService,
|
||||||
|
@IThemeService private readonly _themeService: IThemeService,
|
||||||
|
@IContextViewService public readonly contextViewService: IContextViewService,
|
||||||
|
@IUntitledTextEditorService private readonly _untitledEditorService: IUntitledTextEditorService,
|
||||||
|
@IEditorService private readonly editorService: IEditorService
|
||||||
) {
|
) {
|
||||||
|
// parent container for query plan.
|
||||||
this._container = DOM.$('.query-plan');
|
this._container = DOM.$('.query-plan');
|
||||||
parent.appendChild(this._container);
|
parent.appendChild(this._container);
|
||||||
|
const sashContainer = DOM.$('.query-plan-sash');
|
||||||
|
parent.appendChild(sashContainer);
|
||||||
|
|
||||||
|
const sash = new Sash(sashContainer, this, { orientation: Orientation.HORIZONTAL });
|
||||||
|
let originalHeight = this._container.offsetHeight;
|
||||||
|
let originalTableHeight = 0;
|
||||||
|
let change = 0;
|
||||||
|
sash.onDidStart((e: ISashEvent) => {
|
||||||
|
originalHeight = this._container.offsetHeight;
|
||||||
|
originalTableHeight = this.propertiesView.tableHeight;
|
||||||
|
});
|
||||||
|
|
||||||
this._actionBarContainer = DOM.$('.actionbar-container');
|
/**
|
||||||
|
* Using onDidChange for the smooth resizing of the graph diagram
|
||||||
|
*/
|
||||||
|
sash.onDidChange((evt: ISashEvent) => {
|
||||||
|
change = evt.startY - evt.currentY;
|
||||||
|
const newHeight = originalHeight - change;
|
||||||
|
if (newHeight < 200) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._container.style.height = `${newHeight}px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resizing properties window table only once at the end as it is a heavy operation and worsens the smooth resizing experience
|
||||||
|
*/
|
||||||
|
sash.onDidEnd(() => {
|
||||||
|
this.propertiesView.tableHeight = originalTableHeight - change;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._planContainer = DOM.$('.plan');
|
||||||
|
this._container.appendChild(this._planContainer);
|
||||||
|
|
||||||
|
// container that holds plan header info
|
||||||
|
this._planHeaderContainer = DOM.$('.header');
|
||||||
|
this._planContainer.appendChild(this._planHeaderContainer);
|
||||||
|
this.planHeader = this._instantiationService.createInstance(PlanHeader, this._planHeaderContainer, {
|
||||||
|
planIndex: this._graphIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
// container properties
|
||||||
|
this._propContainer = DOM.$('.properties');
|
||||||
|
this._container.appendChild(this._propContainer);
|
||||||
|
this.propertiesView = new GraphElementPropertiesView(this._propContainer, this._themeService);
|
||||||
|
|
||||||
|
// container that holds actionbar icons
|
||||||
|
this._actionBarContainer = DOM.$('.action-bar-container');
|
||||||
|
this._container.appendChild(this._actionBarContainer);
|
||||||
this._actionBar = new ActionBar(this._actionBarContainer, {
|
this._actionBar = new ActionBar(this._actionBarContainer, {
|
||||||
orientation: ActionsOrientation.VERTICAL, context: this
|
orientation: ActionsOrientation.VERTICAL, context: this
|
||||||
});
|
});
|
||||||
|
|
||||||
this.propContainer = DOM.$('.properties-container');
|
|
||||||
const propHeader = document.createElement('div');
|
|
||||||
propHeader.className = 'properties-header';
|
|
||||||
propHeader.innerText = 'Properties';
|
|
||||||
this.propContainer.appendChild(propHeader);
|
|
||||||
|
|
||||||
this.propContainer.style.visibility = 'hidden';
|
|
||||||
|
|
||||||
this._dataView = new Slick.Data.DataView({ inlineFilters: false });
|
|
||||||
let self = this;
|
|
||||||
this._data = [];
|
|
||||||
const TaskNameFormatter = function (row, cell, value, columnDef, dataContext) {
|
|
||||||
value = escape(value);
|
|
||||||
const spacer = '<span style="display:inline-block;height:1px;width' + (15 * dataContext['indent']) + 'px"></span>';
|
|
||||||
const idx = self._dataView.getIdxById(dataContext.id);
|
|
||||||
if (self._data[idx + 1] && self._data[idx + 1].indent > self._data[idx].indent) {
|
|
||||||
if (dataContext._collapsed) {
|
|
||||||
return spacer + '<span class="properties-toggle expand"></span> ' + value;
|
|
||||||
} else {
|
|
||||||
return spacer + '<span class="properties-toggle collapse"></span> ' + value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return spacer + '<span class="properties-toggle"></span> ' + value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns: Slick.Column<any>[] = [
|
|
||||||
{
|
|
||||||
id: 'name',
|
|
||||||
name: 'Name',
|
|
||||||
field: 'name',
|
|
||||||
width: 250,
|
|
||||||
editor: Slick.Editors.Text,
|
|
||||||
formatter: TaskNameFormatter,
|
|
||||||
headerCssClass: 'prop-table-header'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'value',
|
|
||||||
name: 'Value',
|
|
||||||
field: 'propValue',
|
|
||||||
width: 250,
|
|
||||||
editor: Slick.Editors.Text,
|
|
||||||
headerCssClass: 'prop-table-header'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const options: Slick.GridOptions<any> = {
|
|
||||||
editable: false,
|
|
||||||
enableAddRow: false,
|
|
||||||
enableCellNavigation: true,
|
|
||||||
autoHeight: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const tableContainer = DOM.$('.table-container');
|
|
||||||
tableContainer.style.height = '500px';
|
|
||||||
tableContainer.style.width = '490px';
|
|
||||||
this.propContainer.appendChild(tableContainer);
|
|
||||||
this._table = new Slick.Grid(tableContainer, this._dataView, columns, options);
|
|
||||||
|
|
||||||
this._table.onClick.subscribe((e: any, args) => {
|
|
||||||
|
|
||||||
const item = this._dataView.getItem(args.row);
|
|
||||||
if (item) {
|
|
||||||
item._collapsed = !item._collapsed;
|
|
||||||
this._dataView.updateItem(item.id, item);
|
|
||||||
}
|
|
||||||
e.stopImmediatePropagation();
|
|
||||||
});
|
|
||||||
|
|
||||||
this._dataView.setFilter((item) => {
|
|
||||||
if (item.parent !== null) {
|
|
||||||
let parent = this._data[item.parent];
|
|
||||||
while (parent) {
|
|
||||||
if (parent._collapsed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
parent = this._data[parent.parent];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
// wire up model events to drive the grid
|
|
||||||
this._dataView.onRowCountChanged.subscribe((e, args) => {
|
|
||||||
this._table.updateRowCount();
|
|
||||||
this._table.render();
|
|
||||||
});
|
|
||||||
|
|
||||||
this._dataView.onRowsChanged.subscribe((e, args) => {
|
|
||||||
this._table.invalidateRows(args.rows);
|
|
||||||
this._table.render();
|
|
||||||
});
|
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
new PropertiesAction()
|
new SaveXml(),
|
||||||
|
new OpenGraphFile(),
|
||||||
|
new OpenQueryAction(),
|
||||||
|
new SearchNodeAction(),
|
||||||
|
new ZoomInAction(),
|
||||||
|
new ZoomOutAction(),
|
||||||
|
new ZoomToFitAction(),
|
||||||
|
new CustomZoomAction(),
|
||||||
|
new PropertiesAction(),
|
||||||
];
|
];
|
||||||
this._actionBar.push(actions, { icon: true, label: false });
|
this._actionBar.pushAction(actions, { icon: true, label: false });
|
||||||
|
|
||||||
this._iconMap['Adaptive_Join_32x.ico'] = 'adaptiveJoin';
|
|
||||||
this._iconMap['Assert_32x.ico'] = 'assert';
|
|
||||||
this._iconMap['Bitmap_32x.ico'] = 'bitmap';
|
|
||||||
this._iconMap['Clustered_index_delete_32x.ico'] = 'clusteredIndexDelete';
|
|
||||||
this._iconMap['Clustered_index_insert_32x.ico'] = 'ClusteredIndexInsert';
|
|
||||||
this._iconMap['Clustered_index_scan_32x.ico'] = 'ClusteredIndexScan';
|
|
||||||
this._iconMap['Clustered_index_seek_32x.ico'] = 'ClusteredIndexSeek';
|
|
||||||
this._iconMap['Clustered_index_update_32x.ico'] = 'ClusteredIndexUpdate';
|
|
||||||
this._iconMap['Clustered_index_merge_32x.icoo'] = 'ClusteredIndexMerge';
|
|
||||||
|
|
||||||
|
|
||||||
this._iconMap['Filter_32x.ico'] = 'filter';
|
|
||||||
this._iconMap['Clustered_index_scan_32x.ico'] = 'clusteredIndexScan';
|
|
||||||
this._iconMap['Clustered_index_seek_32x.ico'] = 'clusteredIndexSeek';
|
|
||||||
this._iconMap['Compute_scalar_32x.ico'] = 'computeScalar';
|
|
||||||
this._iconMap['Concatenation_32x.ico'] = 'concatenation';
|
|
||||||
|
|
||||||
this._iconMap['Concatenation_32x.ico'] = 'concatenation';
|
|
||||||
|
|
||||||
this._iconMap['Nested_loops_32x.ico'] = 'nestedLoops';
|
|
||||||
this._iconMap['Result_32x.ico'] = 'result';
|
|
||||||
this._iconMap['Table_spool_32x.ico'] = 'tableSpool';
|
|
||||||
this._iconMap['Top_32x.ico'] = 'top';
|
|
||||||
let imageBasePath = URI.parse(decodeURI(require.toUrl('./images/icons/'))).fsPath;
|
|
||||||
this._iconPaths =
|
|
||||||
{
|
|
||||||
// generic icons
|
|
||||||
iteratorCatchAll: imageBasePath + 'iterator_catch_all.png',
|
|
||||||
|
|
||||||
cursorCatchAll: imageBasePath + 'cursor_catch_all.png',
|
|
||||||
|
|
||||||
languageConstructCatchAll: imageBasePath + 'language_construct_catch_all.png',
|
|
||||||
|
|
||||||
// operator icons
|
|
||||||
adaptiveJoin: imageBasePath + 'adaptive_join.png',
|
|
||||||
|
|
||||||
assert: imageBasePath + 'assert.png',
|
|
||||||
|
|
||||||
bitmap: imageBasePath + 'bitmap.png',
|
|
||||||
|
|
||||||
clusteredIndexDelete: imageBasePath + 'clustered_index_delete.png',
|
|
||||||
|
|
||||||
clusteredIndexInsert: imageBasePath + 'clustered_index_insert.png',
|
|
||||||
|
|
||||||
clusteredIndexScan: imageBasePath + 'clustered_index_scan.png',
|
|
||||||
|
|
||||||
clusteredIndexSeek: imageBasePath + 'clustered_index_seek.png',
|
|
||||||
|
|
||||||
clusteredIndexUpdate: imageBasePath + 'clustered_index_update.png',
|
|
||||||
|
|
||||||
clusteredIndexMerge: imageBasePath + 'clustered_index_merge.png',
|
|
||||||
|
|
||||||
clusteredUpdate: imageBasePath + 'clustered_update.png',
|
|
||||||
|
|
||||||
collapse: imageBasePath + 'collapse.png',
|
|
||||||
|
|
||||||
computeScalar: imageBasePath + 'compute_scalar.png',
|
|
||||||
|
|
||||||
concatenation: imageBasePath + 'concatenation.png',
|
|
||||||
|
|
||||||
constantScan: imageBasePath + 'constant_scan.png',
|
|
||||||
|
|
||||||
deletedScan: imageBasePath + 'deleted_scan.png',
|
|
||||||
|
|
||||||
filter: imageBasePath + 'filter.png',
|
|
||||||
|
|
||||||
hashMatch: imageBasePath + 'hash_match.png',
|
|
||||||
|
|
||||||
indexDelete: imageBasePath + 'index_delete.png',
|
|
||||||
|
|
||||||
indexInsert: imageBasePath + 'index_insert.png',
|
|
||||||
|
|
||||||
indexScan: imageBasePath + 'index_scan.png',
|
|
||||||
|
|
||||||
columnstoreIndexDelete: imageBasePath + 'columnstore_index_delete.png',
|
|
||||||
|
|
||||||
columnstoreIndexInsert: imageBasePath + 'columnstore_index_insert.png',
|
|
||||||
|
|
||||||
columnstoreIndexMerge: imageBasePath + 'columnstore_index_merge.png',
|
|
||||||
|
|
||||||
columnstoreIndexScan: imageBasePath + 'columnstore_index_scan.png',
|
|
||||||
|
|
||||||
columnstoreIndexUpdate: imageBasePath + 'columnstore_index_update.png',
|
|
||||||
|
|
||||||
indexSeek: imageBasePath + 'index_seek.png',
|
|
||||||
|
|
||||||
indexSpool: imageBasePath + 'index_spool.png',
|
|
||||||
|
|
||||||
indexUpdate: imageBasePath + 'index_update.png',
|
|
||||||
|
|
||||||
insertedScan: imageBasePath + 'inserted_scan.png',
|
|
||||||
|
|
||||||
logRowScan: imageBasePath + 'log_row_scan.png',
|
|
||||||
|
|
||||||
mergeInterval: imageBasePath + 'merge_interval.png',
|
|
||||||
|
|
||||||
mergeJoin: imageBasePath + 'merge_join.png',
|
|
||||||
|
|
||||||
nestedLoops: imageBasePath + 'nested_loops.png',
|
|
||||||
|
|
||||||
parallelism: imageBasePath + 'parallelism.png',
|
|
||||||
|
|
||||||
parameterTableScan: imageBasePath + 'parameter_table_scan.png',
|
|
||||||
|
|
||||||
print: imageBasePath + 'print.png',
|
|
||||||
|
|
||||||
rank: imageBasePath + 'rank.png',
|
|
||||||
|
|
||||||
foreignKeyReferencesCheck: imageBasePath + 'foreign_key_references_check.png',
|
|
||||||
|
|
||||||
remoteDelete: imageBasePath + 'remote_delete.png',
|
|
||||||
|
|
||||||
remoteIndexScan: imageBasePath + 'remote_index_scan.png',
|
|
||||||
|
|
||||||
remoteIndexSeek: imageBasePath + 'remote_index_seek.png',
|
|
||||||
|
|
||||||
remoteInsert: imageBasePath + 'remote_insert.png',
|
|
||||||
|
|
||||||
remoteQuery: imageBasePath + 'remote_query.png',
|
|
||||||
|
|
||||||
remoteScan: imageBasePath + 'remote_scan.png',
|
|
||||||
|
|
||||||
remoteUpdate: imageBasePath + 'remote_update.png',
|
|
||||||
|
|
||||||
ridLookup: imageBasePath + 'rid_lookup.png',
|
|
||||||
|
|
||||||
rowCountSpool: imageBasePath + 'row_count_spool.png',
|
|
||||||
|
|
||||||
segment: imageBasePath + 'segment.png',
|
|
||||||
|
|
||||||
sequence: imageBasePath + 'sequence.png',
|
|
||||||
|
|
||||||
sequenceProject: imageBasePath + 'sequence_project.png',
|
|
||||||
|
|
||||||
sort: imageBasePath + 'sort.png',
|
|
||||||
|
|
||||||
split: imageBasePath + 'split.png',
|
|
||||||
|
|
||||||
streamAggregate: imageBasePath + 'stream_aggregate.png',
|
|
||||||
|
|
||||||
switchStatement: imageBasePath + 'switch.png',
|
|
||||||
|
|
||||||
tableValuedFunction: imageBasePath + 'table_valued_function.png',
|
|
||||||
|
|
||||||
tableDelete: imageBasePath + 'table_delete.png',
|
|
||||||
|
|
||||||
tableInsert: imageBasePath + 'table_insert.png',
|
|
||||||
|
|
||||||
tableScan: imageBasePath + 'table_scan.png',
|
|
||||||
|
|
||||||
tableSpool: imageBasePath + 'table_spool.png',
|
|
||||||
|
|
||||||
tableUpdate: imageBasePath + 'table_update.png',
|
|
||||||
|
|
||||||
tableMerge: imageBasePath + 'table_merge.png',
|
|
||||||
|
|
||||||
tfp: imageBasePath + 'predict.png',
|
|
||||||
|
|
||||||
top: imageBasePath + 'top.png',
|
|
||||||
|
|
||||||
udx: imageBasePath + 'udx.png',
|
|
||||||
|
|
||||||
batchHashTableBuild: imageBasePath + 'batch_hash_table_build.png',
|
|
||||||
|
|
||||||
windowSpool: imageBasePath + 'table_spool.png',
|
|
||||||
|
|
||||||
windowAggregate: imageBasePath + 'window_aggregate.png',
|
|
||||||
|
|
||||||
// cursor operators
|
|
||||||
fetchQuery: imageBasePath + 'fetch_query.png',
|
|
||||||
|
|
||||||
populateQuery: imageBasePath + 'population_query.png',
|
|
||||||
|
|
||||||
refreshQuery: imageBasePath + 'refresh_query.png',
|
|
||||||
|
|
||||||
// shiloh operators
|
|
||||||
result: imageBasePath + 'result.png',
|
|
||||||
|
|
||||||
aggregate: imageBasePath + 'aggregate.png',
|
|
||||||
|
|
||||||
assign: imageBasePath + 'assign.png',
|
|
||||||
|
|
||||||
arithmeticExpression: imageBasePath + 'arithmetic_expression.png',
|
|
||||||
|
|
||||||
bookmarkLookup: imageBasePath + 'bookmark_lookup.png',
|
|
||||||
|
|
||||||
convert: imageBasePath + 'convert.png',
|
|
||||||
|
|
||||||
declare: imageBasePath + 'declare.png',
|
|
||||||
|
|
||||||
deleteOperator: imageBasePath + 'delete.png',
|
|
||||||
|
|
||||||
dynamic: imageBasePath + 'dynamic.png',
|
|
||||||
|
|
||||||
hashMatchRoot: imageBasePath + 'hash_match_root.png',
|
|
||||||
|
|
||||||
hashMatchTeam: imageBasePath + 'hash_match_team.png',
|
|
||||||
|
|
||||||
ifOperator: imageBasePath + 'if.png',
|
|
||||||
|
|
||||||
insert: imageBasePath + 'insert.png',
|
|
||||||
|
|
||||||
intrinsic: imageBasePath + 'intrinsic.png',
|
|
||||||
|
|
||||||
keyset: imageBasePath + 'keyset.png',
|
|
||||||
|
|
||||||
locate: imageBasePath + 'locate.png',
|
|
||||||
|
|
||||||
populationQuery: imageBasePath + 'population_query.png',
|
|
||||||
|
|
||||||
setFunction: imageBasePath + 'set_function.png',
|
|
||||||
|
|
||||||
snapshot: imageBasePath + 'snapshot.png',
|
|
||||||
|
|
||||||
spool: imageBasePath + 'spool.png',
|
|
||||||
|
|
||||||
tsql: imageBasePath + 'sql.png',
|
|
||||||
|
|
||||||
update: imageBasePath + 'update.png',
|
|
||||||
|
|
||||||
// fake operators
|
|
||||||
keyLookup: imageBasePath + 'bookmark_lookup.png',
|
|
||||||
|
|
||||||
// PDW operators
|
|
||||||
apply: imageBasePath + 'apply.png',
|
|
||||||
|
|
||||||
broadcast: imageBasePath + 'broadcast.png',
|
|
||||||
|
|
||||||
computeToControlNode: imageBasePath + 'compute_to_control_node.png',
|
|
||||||
|
|
||||||
constTableGet: imageBasePath + 'const_table_get.png',
|
|
||||||
|
|
||||||
controlToComputeNodes: imageBasePath + 'control_to_compute_nodes.png',
|
|
||||||
|
|
||||||
externalBroadcast: imageBasePath + 'external_broadcast.png',
|
|
||||||
|
|
||||||
externalExport: imageBasePath + 'external_export.png',
|
|
||||||
|
|
||||||
externalLocalStreaming: imageBasePath + 'external_local_streaming.png',
|
|
||||||
|
|
||||||
externalRoundRobin: imageBasePath + 'external_round_robin.png',
|
|
||||||
|
|
||||||
externalShuffle: imageBasePath + 'external_shuffle.png',
|
|
||||||
|
|
||||||
get: imageBasePath + 'get.png',
|
|
||||||
|
|
||||||
groupByApply: imageBasePath + 'apply.png',
|
|
||||||
|
|
||||||
groupByAggregate: imageBasePath + 'group_by_aggregate.png',
|
|
||||||
|
|
||||||
join: imageBasePath + 'join.png',
|
|
||||||
|
|
||||||
localCube: imageBasePath + 'intrinsic.png',
|
|
||||||
|
|
||||||
project: imageBasePath + 'project.png',
|
|
||||||
|
|
||||||
shuffle: imageBasePath + 'shuffle.png',
|
|
||||||
|
|
||||||
singleSourceRoundRobin: imageBasePath + 'single_source_round_robin.png',
|
|
||||||
|
|
||||||
singleSourceShuffle: imageBasePath + 'single_source_shuffle.png',
|
|
||||||
|
|
||||||
trim: imageBasePath + 'trim.png',
|
|
||||||
|
|
||||||
union: imageBasePath + 'union.png',
|
|
||||||
|
|
||||||
unionAll: imageBasePath + 'union_all.png'
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private populate(node: azdata.QueryPlanGraphNode, diagramNode: any): any {
|
getHorizontalSashTop(sash: Sash): number {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
getHorizontalSashLeft?(sash: Sash): number {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
getHorizontalSashWidth?(sash: Sash): number {
|
||||||
|
return this._container.clientWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
private populate(node: azdata.ExecutionPlanNode, diagramNode: any): any {
|
||||||
diagramNode.label = node.name;
|
diagramNode.label = node.name;
|
||||||
|
|
||||||
if (node.properties && node.properties.length > 0) {
|
if (node.properties && node.properties.length > 0) {
|
||||||
diagramNode.metrics = node.properties.map(e => { return { name: e.name, value: e.formattedValue.substring(0, 75) }; });
|
diagramNode.metrics = this.populateProperties(node.properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
let icon = this._iconMap[node.type];
|
if (node.type) {
|
||||||
if (icon) {
|
diagramNode.icon = node.type;
|
||||||
diagramNode.icon = icon;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.children) {
|
if (node.children) {
|
||||||
@@ -502,65 +239,218 @@ export class QueryPlan2 {
|
|||||||
diagramNode.children.push(this.populate(node.children[i], new Object()));
|
diagramNode.children.push(this.populate(node.children[i], new Object()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node.edges) {
|
||||||
|
diagramNode.edges = [];
|
||||||
|
for (let i = 0; i < node.edges.length; i++) {
|
||||||
|
diagramNode.edges.push(this.populateEdges(node.edges[i], new Object()));
|
||||||
|
}
|
||||||
|
}
|
||||||
return diagramNode;
|
return diagramNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPlanDiagram(container: HTMLDivElement): void {
|
private populateEdges(edge: azdata.ExecutionPlanEdge, diagramEdge: any) {
|
||||||
let diagramRoot: any = new Object();
|
diagramEdge.label = '';
|
||||||
let graphRoot: azdata.QueryPlanGraphNode = this._graph.root;
|
diagramEdge.metrics = this.populateProperties(edge.properties);
|
||||||
this.populate(graphRoot, diagramRoot);
|
diagramEdge.weight = Math.max(0.5, Math.min(0.5 + 0.75 * Math.log10(edge.rowCount), 6));
|
||||||
|
return diagramEdge;
|
||||||
|
}
|
||||||
|
|
||||||
new azdataGraph.azdataQueryPlan(container, diagramRoot, this._iconPaths);
|
private populateProperties(props: azdata.ExecutionPlanGraphElementProperty[]) {
|
||||||
|
return props.filter(e => isString(e.value))
|
||||||
|
.map(e => {
|
||||||
|
return {
|
||||||
|
name: e.name,
|
||||||
|
value: e.value.toString().substring(0, 75)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createPlanDiagram(container: HTMLElement): void {
|
||||||
|
let diagramRoot: any = new Object();
|
||||||
|
let graphRoot: azdata.ExecutionPlanNode = this._graph.root;
|
||||||
|
this.populate(graphRoot, diagramRoot);
|
||||||
|
this._azdataGraphDiagram = new azdataGraph.azdataQueryPlan(container, diagramRoot, queryPlanNodeIconPaths);
|
||||||
|
|
||||||
|
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||||
|
const iconBackground = theme.getColor(editorBackground);
|
||||||
|
if (iconBackground) {
|
||||||
|
this._azdataGraphDiagram.setIconBackgroundColor(iconBackground);
|
||||||
|
}
|
||||||
|
|
||||||
|
const iconLabelColor = theme.getColor(foreground);
|
||||||
|
if (iconLabelColor) {
|
||||||
|
this._azdataGraphDiagram.setTextFontColor(iconLabelColor);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public set graph(graph: azdata.QueryPlanGraph | undefined) {
|
public set graph(graph: azdata.ExecutionPlanGraph | undefined) {
|
||||||
this._graph = graph;
|
this._graph = graph;
|
||||||
if (this._graph) {
|
if (this._graph) {
|
||||||
this._container.appendChild(document.createTextNode(localize('queryIndex', "Query {0}: ", this._graphIndex)));
|
this.planHeader.graphIndex = this._graphIndex;
|
||||||
this._relativeCost = document.createTextNode(localize('relativeToTheScript', "(relative to the script):"));
|
this.planHeader.query = graph.query;
|
||||||
this._container.appendChild(this._relativeCost);
|
if (graph.recommendations) {
|
||||||
this._container.appendChild(document.createElement('br'));
|
this.planHeader.recommendations = graph.recommendations;
|
||||||
this._container.appendChild(document.createTextNode(`${graph.query}`));
|
}
|
||||||
let diagramContainer = document.createElement('div');
|
let diagramContainer = DOM.$('.diagram');
|
||||||
this.createPlanDiagram(diagramContainer);
|
this.createPlanDiagram(diagramContainer);
|
||||||
this._container.appendChild(diagramContainer);
|
this._planContainer.appendChild(diagramContainer);
|
||||||
|
|
||||||
this._container.appendChild(this.propContainer);
|
this.propertiesView.graphElement = this._graph.root;
|
||||||
this.setData(this._graph.root.properties);
|
|
||||||
this._container.appendChild(this._actionBarContainer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get graph(): azdata.QueryPlanGraph | undefined {
|
public get graph(): azdata.ExecutionPlanGraph | undefined {
|
||||||
return this._graph;
|
return this._graph;
|
||||||
}
|
}
|
||||||
|
|
||||||
public set relativeCost(newCost: number) {
|
public openQuery() {
|
||||||
this._relativeCost.nodeValue = localize('relativeToTheScriptWithCost', "(relative to the script): {0}%", newCost.toFixed(2));
|
return this._instantiationService.invokeFunction(openNewQuery, undefined, this.graph.query, RunQueryOnConnectionMode.none).then();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setData(props: azdata.QueryPlanGraphElementProperty[]): void {
|
public async openGraphFile() {
|
||||||
this._data = [];
|
const input = this._untitledEditorService.create({ mode: this.graph.graphFile.graphFileType, initialValue: this.graph.graphFile.graphFileContent });
|
||||||
props.forEach((p, i) => {
|
await input.resolve();
|
||||||
this._data.push({
|
await this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, input.textEditorModel, FormattingMode.Explicit, Progress.None, CancellationToken.None);
|
||||||
id: p.name,
|
input.setDirty(false);
|
||||||
name: p.name,
|
this.editorService.openEditor(input);
|
||||||
propValue: p.formattedValue,
|
|
||||||
_collapsed: true
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this._dataView.beginUpdate();
|
|
||||||
this._dataView.setItems(this._data);
|
|
||||||
this._dataView.endUpdate();
|
|
||||||
this._dataView.refresh();
|
|
||||||
this._table.autosizeColumns();
|
|
||||||
this._table.updateRowCount();
|
|
||||||
this._table.resizeCanvas();
|
|
||||||
this._table.render();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class OpenQueryAction extends Action {
|
||||||
|
public static ID = 'qp.OpenQueryAction';
|
||||||
|
public static LABEL = localize('openQueryAction', "Open Query");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(OpenQueryAction.ID, OpenQueryAction.LABEL, Codicon.dash.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: QueryPlan2): Promise<void> {
|
||||||
|
context.openQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PropertiesAction extends Action {
|
||||||
|
public static ID = 'qp.propertiesAction';
|
||||||
|
public static LABEL = localize('queryPlanPropertiesActionLabel', "Properties");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(PropertiesAction.ID, PropertiesAction.LABEL, Codicon.book.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: QueryPlan2): Promise<void> {
|
||||||
|
context.propertiesView.toggleVisibility();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZoomInAction extends Action {
|
||||||
|
public static ID = 'qp.ZoomInAction';
|
||||||
|
public static LABEL = localize('queryPlanZoomInActionLabel', "Zoom In");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(ZoomInAction.ID, ZoomInAction.LABEL, Codicon.zoomIn.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: QueryPlan2): Promise<void> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZoomOutAction extends Action {
|
||||||
|
public static ID = 'qp.ZoomOutAction';
|
||||||
|
public static LABEL = localize('queryPlanZoomOutActionLabel', "Zoom Out");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(ZoomOutAction.ID, ZoomOutAction.LABEL, Codicon.zoomOut.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: QueryPlan2): Promise<void> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ZoomToFitAction extends Action {
|
||||||
|
public static ID = 'qp.FitGraph';
|
||||||
|
public static LABEL = localize('queryPlanFitGraphLabel', "Zoom to fit");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(ZoomToFitAction.ID, ZoomToFitAction.LABEL, Codicon.debugStop.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: QueryPlan2): Promise<void> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SaveXml extends Action {
|
||||||
|
public static ID = 'qp.saveXML';
|
||||||
|
public static LABEL = localize('queryPlanSavePlanXML', "Save XML");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(SaveXml.ID, SaveXml.LABEL, Codicon.save.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: QueryPlan2): Promise<void> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CustomZoomAction extends Action {
|
||||||
|
public static ID = 'qp.customZoom';
|
||||||
|
public static LABEL = localize('queryPlanCustomZoom', "Custom Zoom");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(CustomZoomAction.ID, CustomZoomAction.LABEL, Codicon.searchStop.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: QueryPlan2): Promise<void> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchNodeAction extends Action {
|
||||||
|
public static ID = 'qp.searchNode';
|
||||||
|
public static LABEL = localize('queryPlanSearchNodeAction', "SearchNode");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(SearchNodeAction.ID, SearchNodeAction.LABEL, Codicon.search.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: QueryPlan2): Promise<void> {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OpenGraphFile extends Action {
|
||||||
|
public static ID = 'qp.openGraphFile';
|
||||||
|
public static Label = localize('queryPlanOpenGraphFile', "Open Graph File");
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(OpenGraphFile.ID, OpenGraphFile.Label, Codicon.output.classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async run(context: QueryPlan2): Promise<void> {
|
||||||
|
await context.openGraphFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||||
|
const menuBackgroundColor = theme.getColor(editorBackground);
|
||||||
|
if (menuBackgroundColor) {
|
||||||
|
collector.addRule(`
|
||||||
|
.qps-container .query-plan .plan .plan-action-container .child {
|
||||||
|
background-color: ${menuBackgroundColor};
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
const recommendationsColor = theme.getColor(textLinkForeground);
|
||||||
|
if (recommendationsColor) {
|
||||||
|
collector.addRule(`
|
||||||
|
.qps-container .query-plan .plan .header .recommendations {
|
||||||
|
color: ${recommendationsColor};
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registering a feature flag for query plan.
|
* Registering a feature flag for query plan.
|
||||||
* TODO: This should be removed before taking the feature to public preview.
|
* TODO: This should be removed before taking the feature to public preview.
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
EditRevertCellResult,
|
EditRevertCellResult,
|
||||||
ExecutionPlanOptions,
|
ExecutionPlanOptions,
|
||||||
queryeditor,
|
queryeditor,
|
||||||
QueryPlanGraph
|
ExecutionPlanGraph
|
||||||
} from 'azdata';
|
} from 'azdata';
|
||||||
import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService';
|
import { QueryInfo } from 'sql/workbench/services/query/common/queryModelService';
|
||||||
import { IRange } from 'vs/editor/common/core/range';
|
import { IRange } from 'vs/editor/common/core/range';
|
||||||
@@ -34,7 +34,7 @@ export interface IQueryPlanInfo {
|
|||||||
export interface IQueryPlan2Info {
|
export interface IQueryPlan2Info {
|
||||||
providerId: string;
|
providerId: string;
|
||||||
fileUri: string;
|
fileUri: string;
|
||||||
planGraphs: QueryPlanGraph[];
|
planGraphs: ExecutionPlanGraph[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IQueryInfo {
|
export interface IQueryInfo {
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ export default class QueryRunner extends Disposable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleQueryPlan2Available(queryPlans: azdata.QueryPlanGraph[] | undefined) {
|
public handleQueryPlan2Available(queryPlans: azdata.ExecutionPlanGraph[] | undefined) {
|
||||||
if (queryPlans) {
|
if (queryPlans) {
|
||||||
this._onQueryPlan2Available.fire({
|
this._onQueryPlan2Available.fire({
|
||||||
providerId: mssqlProviderName,
|
providerId: mssqlProviderName,
|
||||||
|
|||||||
@@ -1831,9 +1831,9 @@ aws4@^1.8.0:
|
|||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||||
|
|
||||||
"azdataGraph@github:Microsoft/azdataGraph#0.0.7":
|
"azdataGraph@github:Microsoft/azdataGraph#0.0.9":
|
||||||
version "0.0.7"
|
version "0.0.9"
|
||||||
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/e6c21240186cad9829a729b60e260e6d5b57c316"
|
resolved "https://codeload.github.com/Microsoft/azdataGraph/tar.gz/4d80cfee29c4761e31b6e4e3612e450ce42b55ae"
|
||||||
|
|
||||||
azure-storage@^2.10.2:
|
azure-storage@^2.10.2:
|
||||||
version "2.10.2"
|
version "2.10.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user