import {IPlan} from '../../interfaces/iplan';
import {Component, OnInit} from 'angular2/core';
import {HighlightType, EstimateDirection} from '../../enums';
import {DurationPipe, DurationUnitPipe} from '../../pipes';
import {PlanService} from '../../services/plan-service';
import {SyntaxHighlightService} from '../../services/syntax-highlight-service';
import {HelpService} from '../../services/help-service';
import {ColorService} from '../../services/color-service';
///
@Component({
selector: 'plan-node',
inputs: ['plan', 'node', 'viewOptions'],
templateUrl: './components/plan-node/plan-node.html',
directives: [PlanNode],
providers: [PlanService, SyntaxHighlightService, HelpService, ColorService],
pipes: [DurationPipe, DurationUnitPipe]
})
export class PlanNode {
// consts
NORMAL_WIDTH: number = 220;
COMPACT_WIDTH: number = 140;
EXPANDED_WIDTH: number = 400;
MIN_ESTIMATE_MISS: number = 100;
COSTLY_TAG: string = 'costliest';
SLOW_TAG: string = 'slowest';
LARGE_TAG: string = 'largest';
ESTIMATE_TAG: string = 'bad estimate';
// inputs
plan: IPlan;
node: any;
viewOptions: any;
// UI flags
showDetails: boolean;
// calculated properties
executionTimePercent: number;
backgroundColor: string;
highlightValue: number;
barContainerWidth: number;
barWidth: number;
props: Array;
tags: Array;
plannerRowEstimateValue: number;
plannerRowEstimateDirection: EstimateDirection;
// required for custom change detection
currentHighlightType: string;
currentCompactView: boolean;
currentExpandedView: boolean;
// expose enum to view
estimateDirections = EstimateDirection;
highlightTypes = HighlightType;
constructor(private _planService: PlanService,
private _syntaxHighlightService: SyntaxHighlightService,
private _helpService: HelpService,
private _colorService: ColorService) { }
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() {
if (this.currentHighlightType !== this.viewOptions.highlightType) {
this.currentHighlightType = this.viewOptions.highlightType;
this.calculateBar();
}
if (this.currentCompactView !== this.viewOptions.showCompactView) {
this.currentCompactView = this.viewOptions.showCompactView;
this.calculateBar();
}
if (this.currentExpandedView !== this.showDetails) {
this.currentExpandedView = this.showDetails;
this.calculateBar();
}
}
getFormattedQuery() {
var keyItems: Array = [];
// relation name will be highlighted for SCAN nodes
var relationName: string = this.node[this._planService.RELATION_NAME_PROP];
if (relationName) {
keyItems.push(this.node[this._planService.SCHEMA_PROP] + '.' + relationName);
keyItems.push(' ' + relationName);
keyItems.push(' ' + this.node[this._planService.ALIAS_PROP] + ' ');
}
// group key will be highlighted for AGGREGATE nodes
var groupKey: Array = this.node[this._planService.GROUP_KEY_PROP];
if (groupKey) {
keyItems.push('GROUP BY ' + groupKey.join(','));
}
// hash condition will be highlighted for HASH JOIN nodes
var hashCondition: string = this.node[this._planService.HASH_CONDITION_PROP];
if (hashCondition) {
keyItems.push(hashCondition.replace('(', '').replace(')', ''));
}
if (this.node[this._planService.NODE_TYPE_PROP].toUpperCase() === 'LIMIT') {
keyItems.push('LIMIT');
}
return this._syntaxHighlightService.highlight(this.plan.query, keyItems);
}
calculateBar() {
this.barContainerWidth = this.viewOptions.showCompactView ? this.COMPACT_WIDTH : this.NORMAL_WIDTH;
// expanded view width trumps others
if (this.currentExpandedView) {
this.barContainerWidth = this.EXPANDED_WIDTH;
}
switch (this.currentHighlightType) {
case HighlightType.DURATION:
this.highlightValue = (this.node[this._planService.ACTUAL_DURATION_PROP]);
this.barWidth = Math.round((this.highlightValue / this.plan.planStats.maxDuration) * this.barContainerWidth);
break;
case HighlightType.ROWS:
this.highlightValue = (this.node[this._planService.ACTUAL_ROWS_PROP]);
this.barWidth = Math.round((this.highlightValue / this.plan.planStats.maxRows) * this.barContainerWidth);
break;
case HighlightType.COST:
this.highlightValue = (this.node[this._planService.ACTUAL_COST_PROP]);
this.barWidth = Math.round((this.highlightValue / this.plan.planStats.maxCost) * this.barContainerWidth);
break;
}
if (this.barWidth < 1) {
this.barWidth = 1;
}
this.backgroundColor = this._colorService.numberToColorHsl(1 - this.barWidth / this.barContainerWidth);
}
calculateDuration() {
this.executionTimePercent = (_.round((this.node[this._planService.ACTUAL_DURATION_PROP] / this.plan.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(this._planService.PLANS_PROP)
.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);
}
}
getNodeTypeDescription() {
return this._helpService.getNodeTypeDescription(this.node[this._planService.NODE_TYPE_PROP]);
}
}