aboutsummaryrefslogtreecommitdiff
path: root/app/components
diff options
context:
space:
mode:
Diffstat (limited to 'app/components')
-rw-r--r--app/components/plan-list/plan-list.html36
-rw-r--r--app/components/plan-list/plan-list.ts7
-rw-r--r--app/components/plan-new/plan-new.html12
-rw-r--r--app/components/plan-new/plan-new.ts21
-rw-r--r--app/components/plan-node/plan-node.html25
-rw-r--r--app/components/plan-node/plan-node.ts55
-rw-r--r--app/components/plan-view/plan-view.html15
-rw-r--r--app/components/plan-view/plan-view.ts6
8 files changed, 127 insertions, 50 deletions
diff --git a/app/components/plan-list/plan-list.html b/app/components/plan-list/plan-list.html
index 28c95af..a16ffbe 100644
--- a/app/components/plan-list/plan-list.html
+++ b/app/components/plan-list/plan-list.html
@@ -3,28 +3,28 @@
Welcome to PEV! Please <a [routerLink]="['/PlanNew']">submit</a> a plan for visualization
</div>
- <table class="table">
+ <table class="table pad-bottom">
<tr *ngFor="#plan of plans">
+ <!-- this is a hack that should be converted to a proper dialog once that is available in angular 2-->
+ <div *ngIf="openDialog">
+ <div class="modal-backdrop"></div>
+ <div class="modal">
+ <div class="modal-dialog">
+ <div class="modal-content">
+ <div class="modal-body">You're about to delete plan "{{planToDelete.name}}". Are you sure?</div>
+ <div class="modal-footer">
+ <button class="btn btn-primary" (click)="deletePlan()">Yes</button>
+ <button class="btn btn-default" (click)="cancelDelete()">No</button>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
<td width="30%"><a [routerLink]="['/PlanView', {id: plan.id}]">{{plan.name}}</a></td>
<td>created on {{plan.createdOn | momentDate }}</td>
- <td class="align-right"><button class="btn btn-danger" (click)="requestDelete()">
+ <td class="align-right"><button class="btn btn-danger" (click)="requestDelete(plan)">
<i class="fa fa-trash"></i>delete</button>
- <!-- this is a hack that should be converted to a proper dialog once that is available in angular 2-->
- <div *ngIf="openDialog">
- <div class="modal-backdrop"></div>
-
- <div class="modal">
- <div class="modal-dialog">
- <div class="modal-content">
- <div class="modal-body">You're about to delete this plan. Are you sure?</div>
- <div class="modal-footer">
- <button class="btn btn-primary" (click)="deletePlan(plan)">Yes</button>
- <button class="btn btn-default" (click)="cancelDelete()">No</button>
- </div>
- </div>
- </div>
- </div>
- </div>
</td>
</tr>
</table>
diff --git a/app/components/plan-list/plan-list.ts b/app/components/plan-list/plan-list.ts
index 888e2e2..32583a3 100644
--- a/app/components/plan-list/plan-list.ts
+++ b/app/components/plan-list/plan-list.ts
@@ -20,6 +20,7 @@ export class PlanList {
newPlanContent: any;
newPlanId: string;
openDialog: boolean = false;
+ planToDelete: IPlan;
constructor(private _planService: PlanService) { }
@@ -27,13 +28,15 @@ export class PlanList {
this.plans = this._planService.getPlans();
}
- requestDelete() {
+ requestDelete(plan) {
this.openDialog = true;
+ this.planToDelete = plan;
}
deletePlan(plan) {
this.openDialog = false;
- this._planService.deletePlan(plan);
+ console.log(this.planToDelete);
+ this._planService.deletePlan(this.planToDelete);
this.plans = this._planService.getPlans();
}
diff --git a/app/components/plan-new/plan-new.html b/app/components/plan-new/plan-new.html
index f74529d..42bb8f6 100644
--- a/app/components/plan-new/plan-new.html
+++ b/app/components/plan-new/plan-new.html
@@ -1,11 +1,15 @@
<div class="page">
- <span class="text-muted">For best results, use EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON)</span>
- <button class="btn btn-link" (click)="prefill()">create a sample plan</button>
+ <button class="pull-right btn btn-link" (click)="prefill()">create a sample plan</button>
+
+ <span class="text-muted">For best results, use <code>EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON)</code><br>
+ Psql users can export the plan to a file using <code>psql -qAt -f explain.sql > analyze.json</code></span>
+ <p class="pad-top">DISCLAIMER: Pev stores your plans locally (localStorage) and will not send them anywhere.</p>
<div>
<input placeholder="name (optional)" class="input-box input-box-main" type="text" [(ngModel)]="newPlanName">
<button class="btn btn-default btn-lg pad-top pull-right" (click)="submitPlan()">submit</button>
</div>
- <textarea placeholder="paste execution plan" class="input-box input-box-lg" [(ngModel)]="newPlanContent"></textarea>
- <textarea placeholder="paste corresponding SQL query" class="input-box input-box-lg" [(ngModel)]="newPlanQuery"></textarea>
+ <p *ngIf="validationMessage" class="error-message">{{validationMessage}}</p>
+ <textarea placeholder="paste execution plan" class="input-box input-box-lg code" [(ngModel)]="newPlanContent"></textarea>
+ <textarea placeholder="paste corresponding SQL query" class="input-box input-box-lg code" [(ngModel)]="newPlanQuery"></textarea>
</div>
diff --git a/app/components/plan-new/plan-new.ts b/app/components/plan-new/plan-new.ts
index 93149e2..115fa82 100644
--- a/app/components/plan-new/plan-new.ts
+++ b/app/components/plan-new/plan-new.ts
@@ -16,19 +16,28 @@ export class PlanNew {
newPlanContent: string;
newPlanQuery: string;
newPlan: IPlan;
+ validationMessage: string;
- constructor( private _router: Router, private _planService: PlanService) { }
+ constructor(private _router: Router, private _planService: PlanService) { }
submitPlan() {
+ // remove psql generated header
+ this.newPlanContent = this.newPlanContent.replace('QUERY PLAN', '');
+
+ if (!this._planService.isJsonString(this.newPlanContent)) {
+ this.validationMessage = 'The string you submitted is not valid JSON'
+ return;
+ }
+
this.newPlan = this._planService.createPlan(this.newPlanName, this.newPlanContent, this.newPlanQuery);
- this._router.navigate( ['PlanView', { id: this.newPlan.id }] );
+ this._router.navigate(['PlanView', { id: this.newPlan.id }]);
}
prefill() {
- this.newPlanName = 'Sample plan';
- this.newPlanContent = SAMPLE_JSON;
- this.newPlanQuery = SAMPLE_QUERY;
- }
+ this.newPlanName = 'Sample plan';
+ this.newPlanContent = SAMPLE_JSON;
+ this.newPlanQuery = SAMPLE_QUERY;
+ }
}
export var SAMPLE_JSON = `[
{
diff --git a/app/components/plan-node/plan-node.html b/app/components/plan-node/plan-node.html
index 5ddf55c..86db018 100644
--- a/app/components/plan-node/plan-node.html
+++ b/app/components/plan-node/plan-node.html
@@ -1,8 +1,11 @@
-<div class="plan-node" [class.expanded]="showDetails" [class.compact]="viewOptions.showCompactView">
+<div class="plan-node"
+ [class.expanded]="showDetails"
+ [class.compact]="viewOptions.viewMode === viewModes.COMPACT"
+ [class.dot]="viewOptions.viewMode === viewModes.DOT">
+
<header (click)="showDetails = !showDetails" tooltip="view node details">
- <h4>{{node[_planService.NODE_TYPE_PROP] | uppercase}}
- </h4>
- <span *ngIf="!viewOptions.showCompactView">
+ <h4>{{getNodeName()}}</h4>
+ <span *ngIf="viewOptions.viewMode === viewModes.FULL || showDetails">
<span class="node-duration">{{node[_planService.ACTUAL_DURATION_PROP] | duration}}<span class="text-muted">{{node[_planService.ACTUAL_DURATION_PROP] | durationUnit}}
| </span><strong>{{executionTimePercent}}</strong>
<span class="text-muted">%</span>
@@ -10,12 +13,12 @@
</span>
</header>
- <button *ngIf="plan.query && !viewOptions.showCompactView" tooltip="view corresponding query"
+ <button *ngIf="plan.query && viewOptions.viewMode === viewModes.FULL" tooltip="view corresponding query"
class="btn btn-sm btn-default btn-slim pull-right" (click)="showQuery = !showQuery">
<i class="fa fa-database"></i>
</button>
- <div *ngIf="!viewOptions.showCompactView">
+ <div *ngIf="viewOptions.viewMode === viewModes.FULL">
<div class="relation-name" *ngIf="node[_planService.RELATION_NAME_PROP]">
<span class="text-muted">on </span>
<span *ngIf="node[_planService.SCHEMA_PROP]">{{node[_planService.SCHEMA_PROP]}}.</span>{{node[_planService.RELATION_NAME_PROP]}}
@@ -32,21 +35,25 @@
using</span> {{node[_planService.INDEX_NAME_PROP]}}</div>
<div class="relation-name" *ngIf="node[_planService.HASH_CONDITION_PROP]"><span class="text-muted">
on</span> {{node[_planService.HASH_CONDITION_PROP]}}</div>
+ <div class="relation-name" *ngIf="node[_planService.CTE_NAME_PROP]">
+ <span class="text-muted">CTE</span> {{node[_planService.CTE_NAME_PROP]}}
+ </div>
</div>
+
<div class="tags" *ngIf="viewOptions.showTags && tags.length > 0">
- <span *ngFor="#tag of tags">{{tag}}</span>
+ <span *ngFor="#tag of tags">{{getTagName(tag)}}</span>
</div>
<div *ngIf="currentHighlightType !== highlightTypes.NONE">
<div class="node-bar-container">
<span class="node-bar" [style.width]="barWidth+'px'" [style.backgroundColor]="backgroundColor"></span>
</div>
- <span class="node-bar-label">
+ <span class="node-bar-label" *ngIf="shouldShowNodeBarLabel()">
<span class="text-muted">{{viewOptions.highlightType}}:</span> {{highlightValue | number:'.0-2'}}
</span>
</div>
- <div class="planner-estimate" *ngIf="viewOptions.showPlannerEstimate">
+ <div class="planner-estimate" *ngIf="shouldShowPlannerEstimate()">
<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 | number}}</strong>x</span>
diff --git a/app/components/plan-node/plan-node.ts b/app/components/plan-node/plan-node.ts
index 0d3c420..52590e8 100644
--- a/app/components/plan-node/plan-node.ts
+++ b/app/components/plan-node/plan-node.ts
@@ -1,6 +1,6 @@
import {IPlan} from '../../interfaces/iplan';
import {Component, OnInit} from 'angular2/core';
-import {HighlightType, EstimateDirection} from '../../enums';
+import {HighlightType, EstimateDirection, ViewMode} from '../../enums';
import {DurationPipe, DurationUnitPipe} from '../../pipes';
import {PlanService} from '../../services/plan-service';
@@ -23,6 +23,7 @@ export class PlanNode {
// consts
NORMAL_WIDTH: number = 220;
COMPACT_WIDTH: number = 140;
+ DOT_WIDTH: number = 30;
EXPANDED_WIDTH: number = 400;
MIN_ESTIMATE_MISS: number = 100;
@@ -58,6 +59,7 @@ export class PlanNode {
// expose enum to view
estimateDirections = EstimateDirection;
highlightTypes = HighlightType;
+ viewModes = ViewMode;
constructor(private _planService: PlanService,
private _syntaxHighlightService: SyntaxHighlightService,
@@ -122,7 +124,17 @@ export class PlanNode {
}
calculateBar() {
- this.barContainerWidth = this.viewOptions.showCompactView ? this.COMPACT_WIDTH : this.NORMAL_WIDTH;
+ switch (this.viewOptions.viewMode) {
+ case ViewMode.DOT:
+ this.barContainerWidth = this.DOT_WIDTH;
+ break;
+ case ViewMode.COMPACT:
+ this.barContainerWidth = this.COMPACT_WIDTH;
+ break;
+ default:
+ this.barContainerWidth = this.NORMAL_WIDTH;
+ break;
+ }
// expanded view width trumps others
if (this.currentExpandedView) {
@@ -184,4 +196,43 @@ export class PlanNode {
getNodeTypeDescription() {
return this._helpService.getNodeTypeDescription(this.node[this._planService.NODE_TYPE_PROP]);
}
+
+ getNodeName() {
+ if (this.viewOptions.viewMode === ViewMode.DOT && !this.showDetails) {
+ return this.node[this._planService.NODE_TYPE_PROP].replace(/[^A-Z]/g, '').toUpperCase();
+ }
+
+ return (this.node[this._planService.NODE_TYPE_PROP]).toUpperCase();
+ }
+
+ getTagName(tagName: String) {
+ if (this.viewOptions.viewMode === ViewMode.DOT && !this.showDetails) {
+ return tagName.charAt(0);
+ }
+ return tagName;
+ }
+
+ shouldShowPlannerEstimate() {
+ if (this.viewOptions.showPlannerEstimate && this.showDetails) {
+ return true;
+ }
+
+ if (this.viewOptions.viewMode === ViewMode.DOT) {
+ return false;
+ }
+
+ return this.viewOptions.showPlannerEstimate;
+ }
+
+ shouldShowNodeBarLabel() {
+ if (this.showDetails) {
+ return true;
+ }
+
+ if (this.viewOptions.viewMode === ViewMode.DOT) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/app/components/plan-view/plan-view.html b/app/components/plan-view/plan-view.html
index 4ebb70c..70278f4 100644
--- a/app/components/plan-view/plan-view.html
+++ b/app/components/plan-view/plan-view.html
@@ -9,21 +9,22 @@
<input id="showPlanStats" type="checkbox" [(ngModel)]="viewOptions.showPlanStats">
<label class="clickable" for="showPlanStats"> show plan stats</label>
</li>
-
- <li>
- <input id="showCompactView" type="checkbox" [(ngModel)]="viewOptions.showCompactView">
- <label class="clickable" for="showCompactView"> show compact view</label>
- </li>
-
<li>
<input id="showPlannerEstimate" type="checkbox" [(ngModel)]="viewOptions.showPlannerEstimate">
<label class="clickable" for="showPlannerEstimate"> show planner estimate</label>
</li>
-
<li>
<input id="showTags" type="checkbox" [(ngModel)]="viewOptions.showTags">
<label class="clickable" for="showTags"> show analysis tags</label>
</li>
+ <li>
+ <label>view mode: </label>
+ <div class="button-group">
+ <button [class.selected]="viewOptions.viewMode == viewModes.FULL" (click)="viewOptions.viewMode = viewModes.FULL">full</button>
+ <button [class.selected]="viewOptions.viewMode == viewModes.COMPACT" (click)="viewOptions.viewMode = viewModes.COMPACT">compact</button>
+ <button [class.selected]="viewOptions.viewMode == viewModes.DOT" (click)="viewOptions.viewMode = viewModes.DOT">dot</button>
+ </div>
+ </li>
<li>
<label>graph metric: </label>
diff --git a/app/components/plan-view/plan-view.ts b/app/components/plan-view/plan-view.ts
index 895cccf..9ba8782 100644
--- a/app/components/plan-view/plan-view.ts
+++ b/app/components/plan-view/plan-view.ts
@@ -2,7 +2,7 @@ import {Component, OnInit} from 'angular2/core';
import {ROUTER_DIRECTIVES, RouteParams} from 'angular2/router';
import {IPlan} from '../../interfaces/iplan';
-import {HighlightType} from '../../enums';
+import {HighlightType, ViewMode} from '../../enums';
import {PlanNode} from '../plan-node/plan-node';
import {PlanService} from '../../services/plan-service';
import {SyntaxHighlightService} from '../../services/syntax-highlight-service';
@@ -26,13 +26,15 @@ export class PlanView {
showHighlightBar: true,
showPlannerEstimate: false,
showTags: true,
- highlightType: HighlightType.NONE
+ highlightType: HighlightType.NONE,
+ viewMode: ViewMode.FULL
};
showPlannerEstimate: boolean = true;
showMenu: boolean = false;
highlightTypes = HighlightType; // exposing the enum to the view
+ viewModes = ViewMode;
constructor(private _planService: PlanService, routeParams: RouteParams) {
this.id = routeParams.get('id');