Merge "Add search and filtering support to the timeline."

This commit is contained in:
Jenkins
2016-01-13 21:20:26 +00:00
committed by Gerrit Code Review
14 changed files with 290 additions and 24 deletions

View File

@@ -9,6 +9,7 @@ function timelineOverview() {
var margin = timelineController.margin;
var height = 80;
var laneHeight = 10;
var loaded = false;
var x = timelineController.axes.x;
var y = d3.scale.linear();
@@ -50,6 +51,17 @@ function timelineOverview() {
.attr('stroke', 'rgba(100, 100, 100, 0.25)')
.attr('fill', function(d) {
return timelineController.statusColorMap[d.status];
})
.attr('class', function(d) {
if (timelineController.filterFunction) {
if (timelineController.filterFunction(d)) {
return 'filter-hit';
} else {
return 'filter-miss';
}
} else {
return null;
}
});
rects.exit().remove();
@@ -145,6 +157,8 @@ function timelineOverview() {
.attr('height', height - 1);
timelineController.setViewExtents(brush.extent());
loaded = true;
});
scope.$on('update', function() {
@@ -161,6 +175,13 @@ function timelineOverview() {
shiftViewport(selection.item);
}
});
scope.$on('filter', function() {
if (loaded) {
console.log('filtering');
updateItems(timelineController.data);
}
});
};
return {

View File

@@ -0,0 +1,98 @@
'use strict';
var directivesModule = require('./_index.js');
/**
* @ngInject
*/
function timelineSearch() {
/**
* @ngInject
*/
var controller = function($scope, $element) {
var self = this;
this.open = false;
this.query = '';
this.showSuccess = true;
this.showSkip = true;
this.showFail = true;
this.results = [];
var doFilter = function(item) {
if ((item.status === 'success' && !self.showSuccess) ||
(item.status === 'skip' && !self.showSkip) ||
(item.status === 'fail' && !self.showFail)) {
return false;
}
if (item.name.toLowerCase().indexOf(self.query.toLowerCase()) < 0) {
return false;
}
return true;
};
this.updateResults = function() {
var timeline = $element.controller('timeline');
timeline.setFilterFunction(function(item) {
return doFilter(item);
});
var ret = [];
for (var i = 0; i < timeline.dataRaw.length; i++) {
var item = timeline.dataRaw[i];
if (!doFilter(item)) {
continue;
}
ret.push(timeline.dataRaw[i]);
if (ret.length > 25) {
break;
}
}
this.results = ret;
};
this.select = function(item) {
var timeline = $element.controller('timeline');
timeline.selectItem(item);
timeline.setFilterFunction(null);
self.query = '';
self.open = false;
};
var update = function(a, b) {
if (a === b) {
return;
}
self.updateResults();
};
$scope.$watch(function() { return self.query; }, update);
$scope.$watch(function() { return self.showSuccess; }, update);
$scope.$watch(function() { return self.showSkip; }, update);
$scope.$watch(function() { return self.showFail; }, update);
$scope.$on('dataLoaded', function() {
self.updateResults();
});
};
return {
restrict: 'EA',
require: ['^timelineSearch', '^timeline'],
scope: true,
controller: controller,
controllerAs: 'search',
templateUrl: 'directives/timeline-search.html'
};
}
directivesModule.directive('timelineSearch', timelineSearch);

View File

@@ -201,6 +201,17 @@ function timelineViewport($document) {
return null;
}
})
.attr('class', function(d) {
if (timelineController.filterFunction) {
if (timelineController.filterFunction(d)) {
return 'filter-hit';
} else {
return 'filter-miss';
}
} else {
return null;
}
})
.on("mouseover", rectMouseOver)
.on('mouseout', rectMouseOut)
.on('click', rectClick);
@@ -308,6 +319,12 @@ function timelineViewport($document) {
select(null);
}
});
scope.$on('filter', function() {
if (loaded) {
update();
}
});
};
return {

View File

@@ -56,6 +56,7 @@ function timeline($log, datasetService) {
self.selectionName = null;
self.selection = null;
self.hover = null;
self.filterFunction = null;
self.setViewExtents = function(extents) {
if (angular.isNumber(extents[0])) {
@@ -104,6 +105,12 @@ function timeline($log, datasetService) {
$scope.$broadcast('postSelect', self.selection);
};
self.setFilterFunction = function(fn) {
self.filterFunction = fn;
$scope.$broadcast('filter', fn);
};
self.selectItem = function(item) {
var workerItems = self.data[item.worker].values;
var index = -1;
@@ -249,9 +256,14 @@ function timeline($log, datasetService) {
var link = function(scope, el, attrs, ctrl) {
var updateWidth = function() {
ctrl.width = el.parent()[0].clientWidth -
var body = el[0].querySelector('div.panel div.panel-body');
var style = getComputedStyle(body);
ctrl.width = body.clientWidth -
ctrl.margin.left -
ctrl.margin.right;
ctrl.margin.right -
parseFloat(style.paddingLeft) -
parseFloat(style.paddingRight);
};
scope.$on('windowResize', updateWidth);
@@ -276,7 +288,7 @@ function timeline($log, datasetService) {
controllerAs: 'timeline',
restrict: 'EA',
transclude: true,
template: '<ng-transclude></ng-transclude>',
templateUrl: 'directives/timeline.html',
scope: {
'dataset': '=',
'hoveredItem': '=',

24
app/js/filters/bootstrap-filters.js vendored Normal file
View File

@@ -0,0 +1,24 @@
'use strict';
var filtersModule = require('./_index.js');
var contextClass = function(test, type) {
var clazz;
if (test.status === 'success') {
clazz = 'success';
} else if (test.status === 'skip') {
clazz = 'info';
} else if (test.status === 'fail') {
clazz = 'danger';
} else {
clazz = 'default';
}
if (type) {
return type + '-' + clazz;
} else {
return clazz;
}
};
filtersModule.filter('contextClass', function() { return contextClass; });

View File

@@ -0,0 +1,9 @@
timeline-overview svg {
.filter-hit {
}
.filter-miss {
opacity: 0.15;
}
}

View File

@@ -0,0 +1,28 @@
timeline-search {
display: inline-block;
.popover {
max-width: 500px;
.timeline-search-popover {
width: 300px;
.input-group {
width: 100%;
}
.status-group label {
font-family: monospace;
}
.jump-group li {
cursor: pointer;
}
.jump-group ul {
max-height: 20em;
overflow-x: hidden;
overflow-y: auto;
}
}
}
}

View File

@@ -0,0 +1,9 @@
timeline-viewport svg {
.filter-hit {
}
.filter-miss {
opacity: 0.15;
}
}

View File

@@ -5,3 +5,6 @@
@import 'sb-admin-2';
@import 'directives/_timeline-details.scss';
@import 'directives/_timeline-search.scss';
@import 'directives/_timeline-viewport.scss';
@import 'directives/_timeline-overview.scss';

View File

@@ -9,16 +9,13 @@
<div ng-if="!!item"
class="panel"
ng-class="{'panel-success': item.status == 'success', 'panel-info': item.status == 'skip', 'panel-danger': item.status == 'fail'}">
ng-class="item | contextClass:'panel'">
<div class="panel-heading">
<h3 class="panel-title">
Details: {{item.name | split:'.' | pickRight:1}}
<span class="label label-success"
ng-if="item.status == 'success'">success</span>
<span class="label label-info"
ng-if="item.status == 'skip'">skip</span>
<span class="label label-danger"
ng-if="item.status == 'fail'">fail</span>
<span class="label label-{{item | contextClass}}">
{{item.status}}
</span>
</h3>
</div>
<table class="table table-bordered table-hover table-striped">

View File

@@ -0,0 +1,38 @@
<div class="timeline-search-popover">
<label>Filter by name:</label>
<div class="input-group">
<span class="input-group-addon"><fa name="search"></fa></span>
<input type="text"
placeholder="query..."
class="form-control"
ng-model="search.query"
ng-model-options="{debounce: 250}">
</div>
<br>
<label>Filter by status:</label>
<div class="status-group btn-group btn-group-justified">
<label uib-btn-checkbox
ng-model="search.showSuccess"
class="btn btn-default">success</label>
<label uib-btn-checkbox
ng-model="search.showSkip"
class="btn btn-default">skip</label>
<label uib-btn-checkbox
ng-model="search.showFail"
class="btn btn-default">fail</label>
</div>
<br>
<div class="jump-group">
<label>Jump to:</label>
<ul class="list-group">
<li ng-repeat="item in search.results"
class="list-group-item"
ng-class="item | contextClass:'list-group-item'"
ng-click="search.select(item)">
{{item.name | split:'.' | pickRight:1}}
</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,7 @@
<a uib-popover-template="'directives/timeline-search-popover.html'"
popover-placement="bottom-right"
popover-title="Filter Options"
popover-is-open="search.open">
<fa name="search"></fa>
<fa name="caret-down"></fa>
</a>

View File

@@ -0,0 +1,13 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
Timeline
<timeline-search class="pull-right"></timeline-search>
</h3>
</div>
<div class="panel-body">
<timeline-viewport></timeline-viewport>
<timeline-dstat></timeline-dstat>
<timeline-overview></timeline-overview>
</div>
</div>

View File

@@ -17,20 +17,10 @@
</div>
<div class="row" ng-if="!timeline.error">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Timeline</h3>
</div>
<timeline class="panel-body"
dataset="timeline.dataset"
hovered-item="timeline.hoveredItem"
selected-item="timeline.selectedItem"
preselect="timeline.preselect">
<timeline-viewport></timeline-viewport>
<timeline-dstat></timeline-dstat>
<timeline-overview></timeline-overview>
</timeline>
</div>
<timeline dataset="timeline.dataset"
hovered-item="timeline.hoveredItem"
selected-item="timeline.selectedItem"
preselect="timeline.preselect"></timeline>
</div>
</div>
<div class="row">