Merge "Add search and filtering support to the timeline."
This commit is contained in:
@@ -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 {
|
||||
|
98
app/js/directives/timeline-search.js
Normal file
98
app/js/directives/timeline-search.js
Normal 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);
|
@@ -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 {
|
||||
|
@@ -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
24
app/js/filters/bootstrap-filters.js
vendored
Normal 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; });
|
9
app/styles/directives/_timeline-overview.scss
Normal file
9
app/styles/directives/_timeline-overview.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
timeline-overview svg {
|
||||
.filter-hit {
|
||||
|
||||
}
|
||||
|
||||
.filter-miss {
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
28
app/styles/directives/_timeline-search.scss
Normal file
28
app/styles/directives/_timeline-search.scss
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
9
app/styles/directives/_timeline-viewport.scss
Normal file
9
app/styles/directives/_timeline-viewport.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
timeline-viewport svg {
|
||||
.filter-hit {
|
||||
|
||||
}
|
||||
|
||||
.filter-miss {
|
||||
opacity: 0.15;
|
||||
}
|
||||
}
|
@@ -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';
|
||||
|
@@ -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">
|
||||
|
38
app/views/directives/timeline-search-popover.html
Normal file
38
app/views/directives/timeline-search-popover.html
Normal 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>
|
7
app/views/directives/timeline-search.html
Normal file
7
app/views/directives/timeline-search.html
Normal 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>
|
13
app/views/directives/timeline.html
Normal file
13
app/views/directives/timeline.html
Normal 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>
|
@@ -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">
|
||||
|
Reference in New Issue
Block a user