Renaming query plan to execution plan (#18551)

This commit is contained in:
Aasim Khan
2022-02-25 00:49:34 -08:00
committed by GitHub
parent 8032f59d41
commit 02341088eb
165 changed files with 373 additions and 350 deletions

View File

@@ -0,0 +1,265 @@
/*---------------------------------------------------------------------------------------------
* 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 executionPlanNodeIconPaths =
{
// 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'
};
const parentContainer = 'qps-container';
export const savePlanIconClassNames = [parentContainer, 'save-plan-icon'].join(' ');
export const openPropertiesIconClassNames = [parentContainer, 'open-properties-icon'].join(' ');
export const openQueryIconClassNames = [parentContainer, 'open-query-icon'].join(' ');
export const openPlanFileIconClassNames = [parentContainer, 'open-plan-file-icon'].join(' ');
export const saveIconClassNames = [parentContainer, 'save-icon'].join(' ');
export const searchIconClassNames = [parentContainer, 'search-icon'].join(' ');
export const sortAlphabeticallyIconClassNames = [parentContainer, 'sort-alphabetically-icon'].join(' ');
export const sortByDisplayOrderIconClassNames = [parentContainer, 'sort-display-order-icon'].join(' ');
export const zoomInIconClassNames = [parentContainer, 'zoom-in-icon'].join(' ');
export const zoomOutIconClassNames = [parentContainer, 'zoom-out-icon'].join(' ');
export const customZoomIconClassNames = [parentContainer, 'custom-zoom-icon'].join(' ');
export const zoomToFitIconClassNames = [parentContainer, 'zoom-to-fit-icon'].join(' ');
export const zoomIconClassNames = [parentContainer, 'zoom-icon'].join(' ');

View File

@@ -0,0 +1,657 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/executionPlan';
import type * as azdata from 'azdata';
import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
import { localize } from 'vs/nls';
import { dispose } from 'vs/base/common/lifecycle';
import { ActionBar } from 'sql/base/browser/ui/taskbar/actionbar';
import * as DOM from 'vs/base/browser/dom';
import * as azdataGraphModule from 'azdataGraph';
import { customZoomIconClassNames, openPlanFileIconClassNames, openPropertiesIconClassNames, openQueryIconClassNames, executionPlanNodeIconPaths, savePlanIconClassNames, searchIconClassNames, zoomInIconClassNames, zoomOutIconClassNames, zoomToFitIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
import { isString } from 'vs/base/common/types';
import { PlanHeader } from 'sql/workbench/contrib/executionPlan/browser/planHeader';
import { ExecutionPlanPropertiesView } from 'sql/workbench/contrib/executionPlan/browser/executionPlanPropertiesView';
import { Action } from 'vs/base/common/actions';
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 { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { contrastBorder, editorBackground, editorWidgetBackground, foreground, listHoverBackground, textLinkForeground, widgetShadow } 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';
import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { ExecutionPlanWidgetController } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetController';
import { CustomZoomWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/customZoomWidget';
import { NodeSearchWidget } from 'sql/workbench/contrib/executionPlan/browser/widgets/nodeSearchWidget';
import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IFileService } from 'vs/platform/files/common/files';
import { VSBuffer } from 'vs/base/common/buffer';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { URI } from 'vs/base/common/uri';
import { ITextResourcePropertiesService } from 'vs/editor/common/services/textResourceConfigurationService';
let azdataGraph = azdataGraphModule();
export interface InternalExecutionPlanNode extends azdata.ExecutionPlanNode {
/**
* Unique internal id given to graph node by ADS.
*/
id?: string;
}
export interface InternalExecutionPlanEdge extends azdata.ExecutionPlanEdge {
/**
* Unique internal id given to graph edge by ADS.
*/
id?: string;
}
export class ExecutionPlanTab implements IPanelTab {
public readonly title = localize('executionPlanTitle', "Query Plan (Preview)");
public readonly identifier = 'ExecutionPlan2Tab';
public readonly view: ExecutionPlanView;
constructor(
@IInstantiationService instantiationService: IInstantiationService,
) {
this.view = instantiationService.createInstance(ExecutionPlanView);
}
public dispose() {
dispose(this.view);
}
public clear() {
this.view.clear();
}
}
export class ExecutionPlanView implements IPanelView {
private _eps?: ExecutionPlan[] = [];
private _graphs?: azdata.ExecutionPlanGraph[] = [];
private _container = DOM.$('.eps-container');
constructor(
@IInstantiationService private instantiationService: IInstantiationService,
) {
}
public render(container: HTMLElement): void {
container.appendChild(this._container);
}
dispose() {
this._container.remove();
delete this._eps;
delete this._graphs;
}
public layout(dimension: DOM.Dimension): void {
}
public clear() {
this._eps = [];
this._graphs = [];
DOM.clearNode(this._container);
}
public addGraphs(newGraphs: azdata.ExecutionPlanGraph[] | undefined) {
if (newGraphs) {
newGraphs.forEach(g => {
const ep = this.instantiationService.createInstance(ExecutionPlan, this._container, this._eps.length + 1);
ep.graphModel = g;
this._eps.push(ep);
this._graphs.push(g);
this.updateRelativeCosts();
});
}
}
private updateRelativeCosts() {
const sum = this._graphs.reduce((prevCost: number, cg) => {
return prevCost += cg.root.subTreeCost + cg.root.cost;
}, 0);
if (sum > 0) {
this._eps.forEach(ep => {
ep.planHeader.relativeCost = ((ep.graphModel.root.subTreeCost + ep.graphModel.root.cost) / sum) * 100;
});
}
}
}
export class ExecutionPlan implements ISashLayoutProvider {
private _graphModel?: azdata.ExecutionPlanGraph;
private _container: HTMLElement;
private _actionBarContainer: HTMLElement;
private _actionBar: ActionBar;
public planHeader: PlanHeader;
private _planContainer: HTMLElement;
private _planHeaderContainer: HTMLElement;
public propertiesView: ExecutionPlanPropertiesView;
private _propContainer: HTMLElement;
private _planActionContainer: HTMLElement;
public planActionView: ExecutionPlanWidgetController;
public azdataGraphDiagram: any;
public graphElementPropertiesSet: Set<string> = new Set();
private uniqueElementId: number = -1;
constructor(
private _parent: HTMLElement,
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,
@IContextMenuService private _contextMenuService: IContextMenuService,
@IFileDialogService public fileDialogService: IFileDialogService,
@IFileService public fileService: IFileService,
@IWorkspaceContextService public workspaceContextService: IWorkspaceContextService,
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService,
) {
// parent container for query plan.
this._container = DOM.$('.execution-plan');
this._parent.appendChild(this._container);
const sashContainer = DOM.$('.execution-plan-sash');
this._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;
});
/**
* 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;
}
/**
* Since the parent container is flex, we will have
* to change the flex-basis property to change the height.
*/
this._container.style.minHeight = '200px';
this._container.style.flex = `0 0 ${newHeight}px`;
});
/**
* Resizing properties window table only once at the end as it is a heavy operation and worsens the smooth resizing experience
*/
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');
// Styling header text like the query editor
this._planHeaderContainer.style.fontFamily = EDITOR_FONT_DEFAULTS.fontFamily;
this._planHeaderContainer.style.fontSize = EDITOR_FONT_DEFAULTS.fontSize.toString();
this._planHeaderContainer.style.fontWeight = EDITOR_FONT_DEFAULTS.fontWeight;
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 ExecutionPlanPropertiesView(this._propContainer, this._themeService);
this._planActionContainer = DOM.$('.plan-action-container');
this._planContainer.appendChild(this._planActionContainer);
this.planActionView = new ExecutionPlanWidgetController(this._planActionContainer);
// container that holds actionbar icons
this._actionBarContainer = DOM.$('.action-bar-container');
this._container.appendChild(this._actionBarContainer);
this._actionBar = new ActionBar(this._actionBarContainer, {
orientation: ActionsOrientation.VERTICAL, context: this
});
const actions = [
new SavePlanFile(),
new OpenPlanFile(),
new OpenQueryAction(),
new SearchNodeAction(),
new ZoomInAction(),
new ZoomOutAction(),
new ZoomToFitAction(),
new CustomZoomAction(),
new PropertiesAction(),
];
this._actionBar.pushAction(actions, { icon: true, label: false });
// Setting up context menu
const self = this;
this._container.oncontextmenu = (e: MouseEvent) => {
if (actions) {
this._contextMenuService.showContextMenu({
getAnchor: () => {
return {
x: e.x,
y: e.y
};
},
getActions: () => actions,
getActionsContext: () => (self)
});
}
};
}
getHorizontalSashTop(sash: Sash): number {
return 0;
}
getHorizontalSashLeft?(sash: Sash): number {
return 0;
}
getHorizontalSashWidth?(sash: Sash): number {
return this._container.clientWidth;
}
private populate(node: InternalExecutionPlanNode, diagramNode: any): any {
diagramNode.label = node.subtext.join(this.textResourcePropertiesService.getEOL(undefined));
diagramNode.tooltipTitle = node.name;
const nodeId = this.createGraphElementId();
diagramNode.id = nodeId;
node.id = nodeId;
if (node.properties && node.properties.length > 0) {
diagramNode.metrics = this.populateProperties(node.properties);
}
if (node.type) {
diagramNode.icon = node.type;
}
if (node.edges) {
diagramNode.edges = [];
for (let i = 0; i < node.edges.length; i++) {
diagramNode.edges.push(this.populateEdges(node.edges[i], new Object()));
}
}
if (node.children) {
diagramNode.children = [];
for (let i = 0; i < node.children.length; ++i) {
diagramNode.children.push(this.populate(node.children[i], new Object()));
}
}
if (node.description) {
diagramNode.description = node.description;
}
return diagramNode;
}
private populateEdges(edge: InternalExecutionPlanEdge, diagramEdge: any) {
diagramEdge.label = '';
const edgeId = this.createGraphElementId();
diagramEdge.id = edgeId;
edge.id = edgeId;
diagramEdge.metrics = this.populateProperties(edge.properties);
diagramEdge.weight = Math.max(0.5, Math.min(0.5 + 0.75 * Math.log10(edge.rowCount), 6));
return diagramEdge;
}
private populateProperties(props: azdata.ExecutionPlanGraphElementProperty[]) {
return props.filter(e => isString(e.displayValue) && e.showInTooltip)
.sort((a, b) => a.displayOrder - b.displayOrder)
.map(e => {
this.graphElementPropertiesSet.add(e.name);
return {
name: e.name,
value: e.displayValue,
isLongString: e.positionAtBottom
};
});
}
private createGraphElementId(): string {
this.uniqueElementId += 1;
return `element-${this.uniqueElementId}`;
}
private createPlanDiagram(container: HTMLElement) {
let diagramRoot: any = new Object();
let graphRoot: azdata.ExecutionPlanNode = this._graphModel.root;
this.populate(graphRoot, diagramRoot);
this.azdataGraphDiagram = new azdataGraph.azdataQueryPlan(container, diagramRoot, executionPlanNodeIconPaths);
this.azdataGraphDiagram.graph.setCellsMovable(false); // preventing drag and drop of graph nodes.
this.azdataGraphDiagram.graph.setCellsDisconnectable(false); // preventing graph edges to be disconnected from source and target nodes.
this.azdataGraphDiagram.graph.addListener('click', (sender, evt) => {
// Updating properties view table on node clicks
const cell = evt.properties['cell'];
if (cell) {
this.propertiesView.graphElement = this.searchNodes(cell.id);
} else if (!this.azdataGraphDiagram.graph.getSelectionCell()) {
const root = this.azdataGraphDiagram.graph.model.getCell(diagramRoot.id);
this.azdataGraphDiagram.graph.getSelectionModel().setCell(root);
this.propertiesView.graphElement = this.searchNodes(diagramRoot.id);
evt.consume();
} else {
evt.consume();
}
});
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);
this.azdataGraphDiagram.setEdgeColor(iconLabelColor);
}
});
}
public set graphModel(graph: azdata.ExecutionPlanGraph | undefined) {
this._graphModel = graph;
if (this._graphModel) {
this.planHeader.graphIndex = this._graphIndex;
this.planHeader.query = graph.query;
if (graph.recommendations) {
this.planHeader.recommendations = graph.recommendations;
}
let diagramContainer = DOM.$('.diagram');
this.createPlanDiagram(diagramContainer);
/**
* We do not want to scroll the diagram through mouse wheel.
* Instead, we pass this event to parent control. So, when user
* uses the scroll wheel, they scroll through graphs present in
* the graph control. To scroll the individual graphs, users should
* use the scroll bars.
*/
diagramContainer.addEventListener('wheel', e => {
this._parent.scrollTop += e.deltaY;
//Hiding all tooltips when we scroll.
const element = document.getElementsByClassName('mxTooltip');
for (let i = 0; i < element.length; i++) {
(<HTMLElement>element[i]).style.visibility = 'hidden';
}
e.preventDefault();
e.stopPropagation();
});
this._planContainer.appendChild(diagramContainer);
this.propertiesView.graphElement = this._graphModel.root;
}
}
public get graphModel(): azdata.ExecutionPlanGraph | undefined {
return this._graphModel;
}
public openQuery() {
return this._instantiationService.invokeFunction(openNewQuery, undefined, this.graphModel.query, RunQueryOnConnectionMode.none).then();
}
public async openGraphFile() {
const input = this._untitledEditorService.create({ mode: this.graphModel.graphFile.graphFileType, initialValue: this.graphModel.graphFile.graphFileContent });
await input.resolve();
await this._instantiationService.invokeFunction(formatDocumentWithSelectedProvider, input.textEditorModel, FormattingMode.Explicit, Progress.None, CancellationToken.None);
input.setDirty(false);
this.editorService.openEditor(input);
}
public searchNodes(searchId: string): InternalExecutionPlanNode | InternalExecutionPlanEdge | undefined {
let stack: InternalExecutionPlanNode[] = [];
stack.push(this._graphModel.root);
while (stack.length !== 0) {
const currentNode = stack.pop();
if (currentNode.id === searchId) {
return currentNode;
}
stack.push(...currentNode.children);
const resultEdge = currentNode.edges.find(e => (<InternalExecutionPlanEdge>e).id === searchId);
if (resultEdge) {
return resultEdge;
}
}
return undefined;
}
}
class OpenQueryAction extends Action {
public static ID = 'ep.OpenQueryAction';
public static LABEL = localize('openQueryAction', "Open Query");
constructor() {
super(OpenQueryAction.ID, OpenQueryAction.LABEL, openQueryIconClassNames);
}
public override async run(context: ExecutionPlan): Promise<void> {
context.openQuery();
}
}
class PropertiesAction extends Action {
public static ID = 'ep.propertiesAction';
public static LABEL = localize('executionPlanPropertiesActionLabel', "Properties");
constructor() {
super(PropertiesAction.ID, PropertiesAction.LABEL, openPropertiesIconClassNames);
}
public override async run(context: ExecutionPlan): Promise<void> {
context.propertiesView.toggleVisibility();
}
}
class ZoomInAction extends Action {
public static ID = 'ep.ZoomInAction';
public static LABEL = localize('executionPlanZoomInActionLabel', "Zoom In");
constructor() {
super(ZoomInAction.ID, ZoomInAction.LABEL, zoomInIconClassNames);
}
public override async run(context: ExecutionPlan): Promise<void> {
context.azdataGraphDiagram.graph.zoomIn();
}
}
class ZoomOutAction extends Action {
public static ID = 'ep.ZoomOutAction';
public static LABEL = localize('executionPlanZoomOutActionLabel', "Zoom Out");
constructor() {
super(ZoomOutAction.ID, ZoomOutAction.LABEL, zoomOutIconClassNames);
}
public override async run(context: ExecutionPlan): Promise<void> {
context.azdataGraphDiagram.graph.zoomOut();
}
}
class ZoomToFitAction extends Action {
public static ID = 'ep.FitGraph';
public static LABEL = localize('executionPlanFitGraphLabel', "Zoom to fit");
constructor() {
super(ZoomToFitAction.ID, ZoomToFitAction.LABEL, zoomToFitIconClassNames);
}
public override async run(context: ExecutionPlan): Promise<void> {
context.azdataGraphDiagram.graph.fit();
context.azdataGraphDiagram.graph.view.rendering = true;
context.azdataGraphDiagram.graph.refresh();
}
}
class SavePlanFile extends Action {
public static ID = 'ep.saveXML';
public static LABEL = localize('executionPlanSavePlanXML', "Save Plan File");
constructor() {
super(SavePlanFile.ID, SavePlanFile.LABEL, savePlanIconClassNames);
}
public override async run(context: ExecutionPlan): Promise<void> {
const workspaceFolders = await context.workspaceContextService.getWorkspace().folders;
const defaultFileName = 'plan';
let currentWorkSpaceFolder: URI;
if (workspaceFolders.length !== 0) {
currentWorkSpaceFolder = workspaceFolders[0].uri;
currentWorkSpaceFolder = URI.joinPath(currentWorkSpaceFolder, defaultFileName); //appending default file name to workspace uri
} else {
currentWorkSpaceFolder = URI.parse(defaultFileName); // giving default name
}
const saveFileUri = await context.fileDialogService.showSaveDialog({
filters: [
{
extensions: ['sqlplan'], //TODO: Get this extension from provider
name: localize('executionPlan.SaveFileDescription', 'Execution Plan Files') //TODO: Get the names from providers.
}
],
defaultUri: currentWorkSpaceFolder // If no workspaces are opened this will be undefined
});
if (saveFileUri) {
await context.fileService.writeFile(saveFileUri, VSBuffer.fromString(context.graphModel.graphFile.graphFileContent));
}
}
}
class CustomZoomAction extends Action {
public static ID = 'ep.customZoom';
public static LABEL = localize('executionPlanCustomZoom', "Custom Zoom");
constructor() {
super(CustomZoomAction.ID, CustomZoomAction.LABEL, customZoomIconClassNames);
}
public override async run(context: ExecutionPlan): Promise<void> {
context.planActionView.toggleWidget(context._instantiationService.createInstance(CustomZoomWidget, context));
}
}
class SearchNodeAction extends Action {
public static ID = 'ep.searchNode';
public static LABEL = localize('executionPlanSearchNodeAction', "Find Node");
constructor() {
super(SearchNodeAction.ID, SearchNodeAction.LABEL, searchIconClassNames);
}
public override async run(context: ExecutionPlan): Promise<void> {
context.planActionView.toggleWidget(context._instantiationService.createInstance(NodeSearchWidget, context));
}
}
class OpenPlanFile extends Action {
public static ID = 'ep.openGraphFile';
public static Label = localize('executionPlanOpenGraphFile', "Show Query Plan XML"); //TODO: add a contribution point for providers to set this text
constructor() {
super(OpenPlanFile.ID, OpenPlanFile.Label, openPlanFileIconClassNames);
}
public override async run(context: ExecutionPlan): Promise<void> {
await context.openGraphFile();
}
}
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
const recommendationsColor = theme.getColor(textLinkForeground);
if (recommendationsColor) {
collector.addRule(`
.eps-container .execution-plan .plan .header .recommendations {
color: ${recommendationsColor};
}
`);
}
const shadow = theme.getColor(widgetShadow);
if (shadow) {
collector.addRule(`
.eps-container .execution-plan .plan .plan-action-container .child {
box-shadow: 0 0 8px 2px ${shadow};
}
`);
}
const menuBackgroundColor = theme.getColor(listHoverBackground);
if (menuBackgroundColor) {
collector.addRule(`
.eps-container .execution-plan .plan .header,
.eps-container .execution-plan .properties .title,
.eps-container .execution-plan .properties .table-action-bar {
background-color: ${menuBackgroundColor};
}
`);
}
const widgetBackgroundColor = theme.getColor(editorWidgetBackground);
if (widgetBackgroundColor) {
collector.addRule(`
.eps-container .execution-plan .plan .plan-action-container .child,
.mxTooltip {
background-color: ${widgetBackgroundColor};
}
`);
}
const widgetBorderColor = theme.getColor(contrastBorder);
if (widgetBorderColor) {
collector.addRule(`
.eps-container .execution-plan .plan .plan-action-container .child,
.eps-container .execution-plan .plan .header,
.eps-container .execution-plan .properties .title,
.eps-container .execution-plan .properties .table-action-bar,
.mxTooltip {
border: 1px solid ${widgetBorderColor};
}
`);
}
const textColor = theme.getColor(foreground);
if (textColor) {
collector.addRule(`
.mxTooltip {
color: ${textColor};
}
`);
}
});

View File

@@ -0,0 +1,271 @@
/*---------------------------------------------------------------------------------------------
* 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 { ActionBar } from 'sql/base/browser/ui/taskbar/actionbar';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { removeLineBreaks } from 'sql/base/common/strings';
import { isString } from 'vs/base/common/types';
import { sortAlphabeticallyIconClassNames, sortByDisplayOrderIconClassNames } from 'sql/workbench/contrib/executionPlan/browser/constants';
import { textFormatter } from 'sql/base/browser/ui/table/formatters';
export class ExecutionPlanPropertiesView {
// 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;
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',
formatter: textFormatter
},
{
id: 'value',
name: localize('nodePropertyViewNameValueColumnHeader', "Value"),
field: 'value',
width: 250,
editor: Slick.Editors.Text,
headerCssClass: 'prop-table-header',
formatter: textFormatter
}
];
this._table = new Table(this._actualTable, {
dataProvider: this._dataView, columns: columns
}, {
rowHeight: RESULTS_GRID_DEFAULTS.rowHeight,
forceFitColumns: true,
defaultColumnWidth: 120
});
new ResizeObserver((e) => {
this.tableHeight = (this._parentContainer.getBoundingClientRect().height - 80);
}).observe(this._parentContainer);
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 ? removeLineBreaks(nodeName) : localize('executionPlanPropertiesEdgeOperationName', "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.tableHeight = (this._parentContainer.getBoundingClientRect().height - 80); //80px is the space taken by the title and toolbar
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 = {};
rows.push(row);
row['name'] = ' '.repeat(indent) + p.name;
row['parent'] = parentIndex;
if (!isString(p.value)) {
row['value'] = removeLineBreaks(p.displayValue, ' ');
this.convertPropertiesToTableRows(p.value, rows.length - 1, indent + 2, rows);
} else {
row['value'] = removeLineBreaks(p.value, ' ');
row['tooltip'] = 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 = 'ep.propertiesView.close';
public static LABEL = localize('executionPlanPropertyViewClose', "Close");
constructor() {
super(ClosePropertyViewAction.ID, ClosePropertyViewAction.LABEL, Codicon.close.classNames);
}
public override async run(context: ExecutionPlanPropertiesView): Promise<void> {
context.toggleVisibility();
}
}
export class SortPropertiesAlphabeticallyAction extends Action {
public static ID = 'ep.propertiesView.sortByAlphabet';
public static LABEL = localize('executionPlanPropertyViewSortAlphabetically', "Alphabetical");
constructor() {
super(SortPropertiesAlphabeticallyAction.ID, SortPropertiesAlphabeticallyAction.LABEL, sortAlphabeticallyIconClassNames);
}
public override async run(context: ExecutionPlanPropertiesView): Promise<void> {
context.sortPropertiesAlphabetically();
}
}
export class SortPropertiesByDisplayOrderAction extends Action {
public static ID = 'ep.propertiesView.sortByDisplayOrder';
public static LABEL = localize('executionPlanPropertyViewSortByDisplayOrder', "Categorized");
constructor() {
super(SortPropertiesByDisplayOrderAction.ID, SortPropertiesByDisplayOrderAction.LABEL, sortByDisplayOrderIconClassNames);
}
public override async run(context: ExecutionPlanPropertiesView): Promise<void> {
context.sortPropertiesByImportance();
}
}

View File

@@ -0,0 +1,22 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export abstract class ExecutionPlanWidgetBase {
/**
*
* @param container HTML Element that contains the UI for the plan action view.
* @param identifier Uniquely identify the view to be added or removed. Note: Only 1 view with the same id can be added to the controller
*/
constructor(public container: HTMLElement, public identifier: string) {
this.container = container;
this.identifier = identifier;
}
/**
* This method is called when the view is added to PlanActionView.
* Generally, the view should focus the first input element in the view
*/
public abstract focus();
}

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExecutionPlanWidgetBase as ExecutionPlanWidgetBase } from 'sql/workbench/contrib/executionPlan/browser/executionPlanWidgetBase';
export class ExecutionPlanWidgetController {
private _executionPlanWidgetMap: Map<string, ExecutionPlanWidgetBase> = new Map();
constructor(private _parentContainer: HTMLElement) {
}
private addWidget(widget: ExecutionPlanWidgetBase) {
if (widget.identifier && !this._executionPlanWidgetMap.has(widget.identifier)) {
this._executionPlanWidgetMap.set(widget.identifier, widget);
if (widget.container) {
widget.container.classList.add('child');
this._parentContainer.appendChild(widget.container);
widget.focus();
}
}
}
public removeWidget(widget: ExecutionPlanWidgetBase) {
if (widget.identifier) {
if (this._executionPlanWidgetMap.has(widget.identifier)) {
this._parentContainer.removeChild(this._executionPlanWidgetMap.get(widget.identifier).container);
this._executionPlanWidgetMap.delete(widget.identifier);
} else {
throw new Error('The view is not present in the container');
}
}
}
/**
* Adds or removes view from the controller.
* @param widget PlanActionView to be added.
*/
public toggleWidget(widget: ExecutionPlanWidgetBase) {
if (!this._executionPlanWidgetMap.has(widget.identifier)) {
this.addWidget(widget);
} else {
this.removeWidget(widget);
}
}
}

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F6F6F6;fill-opacity:0;}
.st1{fill:#F6F6F6;}
.st2{fill:#424242;}
.st3{fill:#F0EFF1;}
</style>
<g id="canvas">
<path class="st0" d="M16,16H0V0h16V16z"/>
</g>
<path id="outline" class="st1" d="M16,5.8c0-3.2-2.6-5.7-5.8-5.8C7,0,4.4,2.6,4.3,5.8c0,1,0.3,2,0.8,2.9l-4.7,4.7
c-0.6,0.6-0.6,1.5,0,2.1c0.6,0.6,1.5,0.6,2.1,0l4.7-4.7c0.4,0.3,0.9,0.4,1.3,0.6l-0.8,0.8L7,16h1.4l2.4-0.8l5.2-5.1l-1-1
C15.6,8.1,16,7,16,5.8z"/>
<path id="iconBg" class="st2" d="M10.4,9.6c-0.1,0-0.2,0-0.2,0C8,9.7,6.3,8,6.3,5.8C6.3,3.7,8,2,10.2,2C12.3,2,14,3.7,14,5.8v0
c0,0.6-0.2,1.2-0.5,1.8l0.7,0.7c0.4-0.7,0.7-1.6,0.7-2.5c0-2.6-2.1-4.7-4.8-4.8v0C7.5,1,5.3,3.2,5.3,5.8c0,1.1,0.4,2.2,1.1,3
l-5.3,5.3c-0.2,0.2-0.2,0.5,0,0.7c0.2,0.2,0.5,0.2,0.7,0l5.3-5.3c0.7,0.6,1.5,0.9,2.3,1L10.4,9.6z M8.2,15l2.1-0.7l-1.6-1.6L8.2,15z
M12.3,9.2l1.6,1.6l0.7-0.7L13,8.5L12.3,9.2z M9.5,12l1.6,1.6l2.1-2.1l-1.6-1.6L9.5,12z"/>
<path id="iconFg" class="st3" d="M10.2,9.7c0.1,0,0.2,0,0.2,0L13,7.1l0.6,0.6C13.8,7.1,14,6.5,14,5.8C14,3.7,12.3,2,10.2,2
c0,0,0,0,0,0C8,2,6.3,3.7,6.3,5.8C6.3,8,8,9.7,10.2,9.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F6F6F6;fill-opacity:0;}
.st1{fill:#F6F6F6;}
.st2{fill:#424242;}
.st3{fill:#F0EFF1;}
</style>
<g id="canvas">
<path class="st0" d="M16,16H0V0h16V16z"/>
</g>
<path id="outline" class="st1" d="M16,5.8c0-3.2-2.6-5.7-5.8-5.8C7,0,4.4,2.6,4.3,5.8c0,1,0.3,2,0.8,2.9l-4.7,4.7
c-0.6,0.6-0.6,1.5,0,2.1c0.6,0.6,1.5,0.6,2.1,0l4.7-4.7c0.4,0.3,0.9,0.4,1.3,0.6l-0.8,0.8L7,16h1.4l2.4-0.8l5.2-5.1l-1-1
C15.6,8.1,16,7,16,5.8z"/>
<path id="iconBg" class="st2" d="M10.4,9.6c-0.1,0-0.2,0-0.2,0C8,9.7,6.3,8,6.3,5.8C6.3,3.7,8,2,10.2,2C12.3,2,14,3.7,14,5.8v0
c0,0.6-0.2,1.2-0.5,1.8l0.7,0.7c0.4-0.7,0.7-1.6,0.7-2.5c0-2.6-2.1-4.7-4.8-4.8v0C7.5,1,5.3,3.2,5.3,5.8c0,1.1,0.4,2.2,1.1,3
l-5.3,5.3c-0.2,0.2-0.2,0.5,0,0.7c0.2,0.2,0.5,0.2,0.7,0l5.3-5.3c0.7,0.6,1.5,0.9,2.3,1L10.4,9.6z M8.2,15l2.1-0.7l-1.6-1.6L8.2,15z
M12.3,9.2l1.6,1.6l0.7-0.7L13,8.5L12.3,9.2z M9.5,12l1.6,1.6l2.1-2.1l-1.6-1.6L9.5,12z"/>
<path id="iconFg" class="st3" d="M10.2,9.7c0.1,0,0.2,0,0.2,0L13,7.1l0.6,0.6C13.8,7.1,14,6.5,14,5.8C14,3.7,12.3,2,10.2,2
c0,0,0,0,0,0C8,2,6.3,3.7,6.3,5.8C6.3,8,8,9.7,10.2,9.7z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F6F6F6;}
.st1{fill:#424242;}
.st2{fill:#00539C;}
</style>
<path id="outline" class="st0" d="M12.2,8L12.2,8h-0.5h-1.3H9H6.5L10,4.5L6.7,1.2L5.9,0.5L3.8,2.6L4.2,3H3.5C1.6,3,0,4.6,0,6.5
S1.6,10,3.5,10h0.3l-2,2l4,4h0.1h0.4h1.3H9h2.7h0.5h0l3.8-3.8v-0.4L12.2,8z"/>
<path id="iconBg" class="st1" d="M7.3,14.8l0.9,0.4l2.4-6L9.7,8.8L7.3,14.8z M6.1,9.1L5.2,10h0l-2,2l2.9,2.9l0.7-0.7L4.6,12l2-2h0
l0.1-0.1L6.1,9.1z M12.8,10L12.8,10l-0.9-0.9l-0.7,0.7l0.1,0.1h0l2,2l-2.1,2.1l0.7,0.7l2.9-2.9L12.8,10z"/>
<path id="colorAction" class="st2" d="M1,6.5C1,5.1,2.1,4,3.5,4h3.1L5.2,2.6l0.7-0.7l0,0l0,0l2.6,2.6L5.9,7.2l0,0L5.2,6.5L6.6,5H3.5
C2.7,5,2,5.7,2,6.5S2.7,8,3.5,8H4v1H3.5C2.1,9,1,7.9,1,6.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;} .icon-vs-fg{fill:#F0EFF1;}</style><rect class="icon-canvas-transparent" viewBox="0 0 16 16" id="canvas"/><rect class="icon-vs-out" width="16" height="15" id="outline"/><path class="icon-vs-bg" d="M1 1v13h14v-13h-14zm13 12h-12v-11h12v11zm-4-8h-6v-1h6v1zm-6 1h8v1h-8v-1zm6 3h-6v-1h6v1zm-6 1h8v1h-8v-1z" id="iconBg"/><path class="icon-vs-fg" d="M2 2v11h12v-11h-12zm2 2h6v1h-6v-1zm0 4h6v1h-6v-1zm8 3h-8v-1h8v1zm0-4h-8v-1h8v1z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 630 B

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F6F6F6;fill-opacity:0;}
.st1{fill:#F6F6F6;}
.st2{fill:#F0EFF1;}
.st3{fill:#424242;}
.st4{fill:#00539C;}
</style>
<path id="canvas" class="st0" d="M16,16H0V0h16V16z"/>
<path id="outline" class="st1" d="M7.9,0H6.5h-1h-1H2v2.4C1.7,2.5,1.3,2.7,1.1,3H0v13h14v-3h2V0H7.9z"/>
<path id="iconFg" class="st2" d="M9.2,2.8L10,3.5l-4.1,4L5,6.7V9H3.5C2.9,9,2.3,8.8,1.8,8.6v5.7h10.5v-3h2V2.8H9.2z"/>
<path id="iconBg" class="st3" d="M11,8H5v1h6V8z M7.4,1l2,2H14v8h-1V4H9.5l-2,2H12v5H4V9H3.5C3.3,9,3.2,9,3,8.9V12h9v2H2V8.6
C1.6,8.5,1.3,8.2,1,7.9V15h12v-3h2V1H7.4z"/>
<path id="colorAction" class="st4" d="M1,5.5C1,4.1,2.1,3,3.5,3h3.1L5.2,1.6l0.7-0.7l0,0l0,0l2.6,2.6L5.9,6.2l0,0L5.2,5.5L6.6,4H3.5
C2.7,4,2,4.7,2,5.5S2.7,7,3.5,7H4v1H3.5C2.1,8,1,6.9,1,5.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>Save_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M16,2V16H2.586L0,13.414V2A2,2,0,0,1,2,0H14A2,2,0,0,1,16,2Z"/></g><g id="iconBg"><path class="icon-vs-action-blue" d="M6,12H8v3H6ZM15,2V15H12V10H4v5H3L1,13V2A1,1,0,0,1,2,1H14A1,1,0,0,1,15,2ZM13,3H3V7H13Z"/></g></svg>

After

Width:  |  Height:  |  Size: 570 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-fg{fill:#f0eff1;}.icon-vs-bg{fill:#424242;}</style></defs><title>Search_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline"><path class="icon-vs-out" d="M16,5.833a5.84,5.84,0,0,1-5.833,5.834,5.688,5.688,0,0,1-2.913-.8L2.561,15.561A1.5,1.5,0,0,1,.439,13.439L5.133,8.745a5.694,5.694,0,0,1-.8-2.912A5.834,5.834,0,0,1,16,5.833Z"/></g><g id="iconFg"><path class="icon-vs-fg" d="M14,5.833A3.834,3.834,0,1,1,10.167,2,3.838,3.838,0,0,1,14,5.833Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M10.167,1A4.839,4.839,0,0,0,5.333,5.833a4.786,4.786,0,0,0,1.1,3.029L1.147,14.146a.5.5,0,0,0,.707.708L7.138,9.569a4.783,4.783,0,0,0,3.029,1.1,4.834,4.834,0,0,0,0-9.667Zm0,8.667A3.834,3.834,0,1,1,14,5.833,3.838,3.838,0,0,1,10.167,9.667Z"/></g></svg>

After

Width:  |  Height:  |  Size: 956 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1,.icon-canvas-transparent{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-white{fill:#fff;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>SortAscending_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline"><path class="cls-1" d="M16,9.983l-4.467,4.484-3.3-3.3L6.449,13H8v3H1V13.586L2.586,12H1V9H0V0H9V7.692l1.018,1.014V3.027h3v5.7l1.148-1.145L16,9.412Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M2,11H5L2,14v1H7V14H4.013L7,11.051,6.952,11H7V10H2ZM4.5,3.25,5.2,5H3.8ZM8,1V8H1V1ZM7,7,5,2H4L2,7H3l.4-1H5.6L6,7Z"/></g><g id="iconFg"><path class="icon-white" d="M6,7H7L5,2H4L2,7H3l.4-1H5.6ZM3.8,5l.7-1.75L5.2,5Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M14.154,8.968l.707.707-3.34,3.352L8.169,9.675l.707-.707,2.13,2.122V4h1v7.1Z"/></g></svg>

After

Width:  |  Height:  |  Size: 938 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-blue{fill:#1ba1e2}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M5 4v1.6c.2.1.3.3.4.4H15v3H6v2h9v3H5.6c0 .3-.1.5-.2.6-.1.3-.3.6-.5.8-.2.2-.5.4-.8.5-.2 0-.4.1-.6.1H2.2c-.1 0-.3-.1-.4-.1-.2-.1-.4-.2-.5-.3l-.3-.2V8.9S1.6 8 2.1 8H1V6.3c0-.5.3-.9 1-1.1V2.7l-1 .2V1l2.8-1H5v1h10v3H5z" id="outline"/><path class="icon-vs-bg" d="M14 3H6V2h8v1zm0 4H6v1h8V7zm0 5H6v1h8v-1z" id="iconBg"/><path class="icon-vs-blue" d="M4.5 5V4H4V1l-1.6.4v.7l.6-.2V4h-.4v1h1.9zM3.4 9.1s1-1.2 1.1-1.3c.1-.1.1-.2.1-.3v-.3c0-.2 0-.3-.1-.4 0-.3 0-.4-.2-.5-.1-.1-.2-.2-.3-.2-.2-.1-.4-.1-.6-.1H3c-.1 0-1 .3-1 .3v1c0-.1.6-.3.7-.4.1 0 .2-.1.3-.1h.6c.1 0 .2.2.2.4 0 .1-.1.4-.3.6C3.3 7.9 2 9.3 2 9.3v.7h2.6v-.9H3.4zm1.2 4.2c-.2-.4-.6-.5-.6-.5.7-.1.6-.9.6-1 0-.1 0-.2-.1-.4 0-.1-.1-.2-.2-.3-.1-.1-.2-.2-.4-.2-.2-.1-.4-.1-.6-.1H3c-.1 0-.2 0-.3.1-.1 0-.2 0-.2.1-.3-.1-.5 0-.5 0v.7c.5-.3 1.1-.2 1.2-.2.5.1.2.8.1.8s-.1.1-.3.1H2.4v.7h.8c.1 0 .2.1.3.1.1 0 .1.1.2.2 0 .1.1.2.1.3 0 .2-.1.3-.2.4-.1.1-.3.2-.6.2h-.2c-.1 0-.2 0-.3-.1-.1 0-.2-.1-.2-.1-.2 0-.2-.1-.3-.1v.8c.1 0 .1.1.2.1s.2 0 .3.1h.6c.3 0 .5 0 .7-.1.2-.1.4-.2.5-.3.1-.1.2-.2.3-.4 0 0 .1-.5 0-.9z" id="colorImportance"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 16 16" style="enable-background:new 0 0 16 16;" xml:space="preserve">
<style type="text/css">
.st0{fill:#F6F6F6;fill-opacity:0;}
.st1{fill:#F6F6F6;}
.st2{fill:#424242;}
</style>
<g id="canvas">
<path class="st0" d="M16,0v16H0V0H16z"/>
</g>
<g id="outline">
<path class="st1" d="M14.5,16c-0.4,0-0.8-0.2-1.1-0.4L9.9,12C6.8,13.9,2.8,13,1,9.9S0,2.8,3.1,1s7.1-0.9,9,2.1
c1.3,2.1,1.3,4.7,0,6.8l3.5,3.5c0.6,0.6,0.6,1.5,0,2.1C15.3,15.8,14.9,16,14.5,16z"/>
</g>
<g id="iconBg">
<path class="st2" d="M14.9,14.1L10.7,10c1.9-2.3,1.6-5.8-0.7-7.8S4.2,0.6,2.3,3S0.6,8.8,3,10.7c2,1.7,5,1.7,7.1,0l4.1,4.1
c0.2,0.2,0.5,0.2,0.7,0S15,14.3,14.9,14.1z M6.5,11C4,11,2,9,2,6.5S4,2,6.5,2S11,4,11,6.5C11,9,9,11,6.5,11z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 993 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>ZoomIn_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M14.5,16a1.494,1.494,0,0,1-1.061-.439L9.909,12.03A6.51,6.51,0,1,1,12.03,9.909l3.531,3.53A1.5,1.5,0,0,1,14.5,16Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M14.854,14.146l-4.13-4.129a5.509,5.509,0,1,0-.707.707l4.129,4.13a.5.5,0,0,0,.708-.708ZM6.5,11A4.5,4.5,0,1,1,11,6.5,4.505,4.505,0,0,1,6.5,11Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M9,6V7H7V9H6V7H4V6H6V4H7V6Z"/></g></svg>

After

Width:  |  Height:  |  Size: 788 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>ZoomOut_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M14.5,16a1.494,1.494,0,0,1-1.061-.439L9.909,12.03A6.51,6.51,0,1,1,12.03,9.909l3.531,3.53A1.5,1.5,0,0,1,14.5,16Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M14.854,14.146l-4.13-4.129a5.509,5.509,0,1,0-.707.707l4.129,4.13a.5.5,0,0,0,.708-.708ZM6.5,11A4.5,4.5,0,1,1,11,6.5,4.505,4.505,0,0,1,6.5,11Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M9,6V7H4V6Z"/></g></svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>ZoomToFit_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline"><path class="icon-vs-out" d="M12.977,15s1.03.98,1.053,1h1.944c.008-.007.018-.009.026-.016V13.755l-1-.972V3H13V0H2V3H0V13H2v2Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M15.349,14.517l-4.257-4.139A4.581,4.581,0,0,0,12.125,7.5,4.625,4.625,0,1,0,7.5,12.126a4.579,4.579,0,0,0,2.885-1.04l4.266,4.147a.5.5,0,0,0,.7-.716ZM3.875,7.5A3.625,3.625,0,1,1,7.5,11.126,3.629,3.629,0,0,1,3.875,7.5ZM8,7h2V8H8v2H7V8H5V7H7V5H8ZM1,4H2v8H1ZM12,2H3V1h9ZM3,13h9v1H3Zm11-1H13V4h1Z"/></g></svg>

After

Width:  |  Height:  |  Size: 800 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 980 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 843 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Some files were not shown because too many files have changed in this diff Show More