diff options
author | Alex Tatiyants <atatiyan@gmail.com> | 2016-01-03 17:17:48 -0800 |
---|---|---|
committer | Alex Tatiyants <atatiyan@gmail.com> | 2016-01-03 17:17:48 -0800 |
commit | 5310ac7d8eb1838a6297117bc7f9fca70291f46a (patch) | |
tree | 28f54b184cb85f04e6d6720dd03258f3728fedde /app/components/plan-node |
initial commit
Diffstat (limited to 'app/components/plan-node')
-rw-r--r-- | app/components/plan-node/plan-node.html | 50 | ||||
-rw-r--r-- | app/components/plan-node/plan-node.ts | 176 |
2 files changed, 226 insertions, 0 deletions
diff --git a/app/components/plan-node/plan-node.html b/app/components/plan-node/plan-node.html new file mode 100644 index 0000000..49380a2 --- /dev/null +++ b/app/components/plan-node/plan-node.html @@ -0,0 +1,50 @@ +<div class="plan-node"> + <header (click)="showDetails = !showDetails"> + <h4>{{node['Node Type'] | uppercase}}</h4> + <span class="node-duration">{{duration}}<span class="text-muted">{{durationUnit}} | </span> + <strong>{{executionTimePercent}}</strong><span class="text-muted">%</span> + </span> + </header> + + <div class="relation-name" *ngIf="node['Relation Name']"><span class="text-muted">on </span> + <span *ngIf="node['Schema']">{{node['Schema']}}.</span>{{node['Relation Name']}} + <span *ngIf="node['Alias']"> ({{node['Alias']}})</span> + </div> + + <div class="relation-name" *ngIf="node['Group Key']"><span class="text-muted">by</span> {{node['Group Key']}}</div> + <div class="relation-name" *ngIf="node['Sort Key']"><span class="text-muted">by</span> {{node['Sort Key']}}</div> + <div class="relation-name" *ngIf="node['Join Type']">{{node['Join Type']}} <span class="text-muted">join</span></div> + <div class="relation-name" *ngIf="node['Index Name']"><span class="text-muted">using</span> {{node['Index Name']}}</div> + + <div class="tags" *ngIf="viewOptions.showTags && tags.length > 0"> + <span *ngFor="#tag of tags">{{tag}}</span> + </div> + + <div *ngIf="currentHighlightType !== highlightTypes.NONE"> + <div class="node-bar-container" [style.width]="(MAX_WIDTH+3)+'px'"> + <span class="node-bar" [style.width]="width+'px'" [style.backgroundColor]="backgroundColor"></span> + </div> + <span class="node-bar-label"> + <span class="text-muted">{{viewOptions.highlightType}}:</span> {{highlightValue | number:'.0-2'}} + </span> + </div> + + <div class="planner-estimate" *ngIf="viewOptions.showPlannerEstimate"> + <span *ngIf="plannerRowEstimateDirection === estimateDirections.over"><strong>over</strong> estimated rows</span> + <span *ngIf="plannerRowEstimateDirection === estimateDirections.under"><strong>under</strong> estimated rows</span> + <span> by <strong>{{plannerRowEstimateValue}}</strong>x</span> + </div> + + <table *ngIf="showDetails" class="table prop-list"> + <tr *ngFor="#prop of props"> + <td width="40%">{{prop.key}}</td> + <td>{{prop.value}}</td> + <tr> + </table> +</div> + +<ul *ngIf="node.Plans"> + <li *ngFor="#subNode of node.Plans"> + <plan-node [node]="subNode" [viewOptions]="viewOptions" [planStats]="planStats"></plan-node> + </li> +</ul> diff --git a/app/components/plan-node/plan-node.ts b/app/components/plan-node/plan-node.ts new file mode 100644 index 0000000..3562ea6 --- /dev/null +++ b/app/components/plan-node/plan-node.ts @@ -0,0 +1,176 @@ +import {Component, OnInit} from 'angular2/core'; +import {HighlightType, EstimateDirection} from '../../enums'; +import {PlanService} from '../../services/plan-service'; +/// <reference path="lodash.d.ts" /> + +@Component({ + selector: 'plan-node', + inputs: ['node', 'planStats', 'viewOptions'], + templateUrl: './components/plan-node/plan-node.html', + directives: [PlanNode], + providers: [PlanService] +}) + +export class PlanNode { + // consts + MAX_WIDTH: number = 220; + MIN_ESTIMATE_MISS: number = 100; + COSTLY_TAG: string = 'costliest'; + SLOW_TAG: string = 'slowest'; + LARGE_TAG: string = 'largest'; + ESTIMATE_TAG: string = 'bad estimate'; + + // inputs + node: any; + planStats: any; + viewOptions: any; + + // calculated properties + duration: string; + durationUnit: string; + executionTimePercent: number; + backgroundColor: string; + highlightValue: number; + width: number; + props: Array<any>; + tags: Array<string>; + plannerRowEstimateValue: number; + plannerRowEstimateDirection: EstimateDirection; + currentHighlightType: string; + + // expose enum to view + estimateDirections = EstimateDirection; + highlightTypes = HighlightType; + + constructor(private _planService: PlanService) { } + + ngOnInit() { + this.currentHighlightType = this.viewOptions.highlightType; + this.calculateBar(); + this.calculateProps(); + this.calculateDuration(); + this.calculateTags(); + + this.plannerRowEstimateDirection = this.node[this._planService.PLANNER_ESIMATE_DIRECTION]; + this.plannerRowEstimateValue = _.round(this.node[this._planService.PLANNER_ESTIMATE_FACTOR]); + } + + ngDoCheck() { + // console.log("check", this.currentHighlightType, this.viewOptions.highlightType); + if (this.currentHighlightType !== this.viewOptions.highlightType) { + this.currentHighlightType = this.viewOptions.highlightType; + this.calculateBar(); + } + } + + calculateBar() { + switch (this.currentHighlightType) { + case HighlightType.DURATION: + this.highlightValue = (this.node[this._planService.ACTUAL_DURATION_PROP]); + this.width = Math.round((this.highlightValue / this.planStats.maxDuration) * this.MAX_WIDTH); + break; + case HighlightType.ROWS: + this.highlightValue = (this.node[this._planService.ACTUAL_ROWS_PROP]); + this.width = Math.round((this.highlightValue / this.planStats.maxRows) * this.MAX_WIDTH); + break; + case HighlightType.COST: + this.highlightValue = (this.node[this._planService.ACTUAL_COST_PROP]); + this.width = Math.round((this.highlightValue / this.planStats.maxCost) * this.MAX_WIDTH); + break; + } + + if (this.width < 1) { this.width = 1 } + this.backgroundColor = this.numberToColorHsl(1 - this.width / this.MAX_WIDTH); + } + + calculateDuration() { + var dur: number = _.round(this.node[this._planService.ACTUAL_DURATION_PROP]); + // convert duration into approriate units + if (dur < 1) { + this.duration = "<1"; + this.durationUnit = 'ms'; + } else if (dur > 1 && dur < 1000) { + this.duration = dur.toString(); + this.durationUnit = 'ms'; + } else { + this.duration = _.round(dur / 1000, 2).toString(); + this.durationUnit = 'mins'; + } + this.executionTimePercent = (_.round((dur / this.planStats.executionTime) * 100)); + } + + // create an array of node propeties so that they can be displayed in the view + calculateProps() { + this.props = _.chain(this.node) + .omit('Plans') + .map((value, key) => { + return { key: key, value: value }; + }) + .value(); + } + + calculateTags() { + this.tags = []; + if (this.node[this._planService.SLOWEST_NODE_PROP]) { + this.tags.push(this.SLOW_TAG); + } + if (this.node[this._planService.COSTLIEST_NODE_PROP]) { + this.tags.push(this.COSTLY_TAG); + } + if (this.node[this._planService.LARGEST_NODE_PROP]) { + this.tags.push(this.LARGE_TAG); + } + if (this.node[this._planService.PLANNER_ESTIMATE_FACTOR] >= this.MIN_ESTIMATE_MISS) { + this.tags.push(this.ESTIMATE_TAG); + } + } + + /** + * http://stackoverflow.com/questions/2353211/hsl-to-rgb-color-conversion + * + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h, s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + * + * @param Number h The hue + * @param Number s The saturation + * @param Number l The lightness + * @return Array The RGB representation + */ + hslToRgb(h, s, l) { + var r, g, b; + + if (s == 0) { + r = g = b = l; // achromatic + } else { + function hue2rgb(p, q, t) { + if (t < 0) t += 1; + if (t > 1) t -= 1; + if (t < 1 / 6) return p + (q - p) * 6 * t; + if (t < 1 / 2) return q; + if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return [Math.floor(r * 255), Math.floor(g * 255), Math.floor(b * 255)]; + } + + // convert a number to a color using hsl + numberToColorHsl(i) { + // as the function expects a value between 0 and 1, and red = 0° and green = 120° + // we convert the input to the appropriate hue value + var hue = i * 100 * 1.2 / 360; + // we convert hsl to rgb (saturation 100%, lightness 50%) + var rgb = this.hslToRgb(hue, .9, .4); + // we format to css value and return + return 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'; + } +} |