aboutsummaryrefslogtreecommitdiff
path: root/app/components/plan-node
diff options
context:
space:
mode:
Diffstat (limited to 'app/components/plan-node')
-rw-r--r--app/components/plan-node/plan-node.html50
-rw-r--r--app/components/plan-node/plan-node.ts176
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] + ')';
+ }
+}