diff --git a/app/js/directives/timeline-overview.js b/app/js/directives/timeline-overview.js index a4c8669..36635fd 100644 --- a/app/js/directives/timeline-overview.js +++ b/app/js/directives/timeline-overview.js @@ -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(); @@ -49,6 +50,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(); @@ -144,6 +156,8 @@ function timelineOverview() { .attr('height', height - 1); timelineController.setViewExtents(brush.extent()); + + loaded = true; }); scope.$on('update', function() { @@ -160,6 +174,13 @@ function timelineOverview() { shiftViewport(selection.item); } }); + + scope.$on('filter', function() { + if (loaded) { + console.log('filtering'); + updateItems(timelineController.data); + } + }); }; return { diff --git a/app/js/directives/timeline-search.js b/app/js/directives/timeline-search.js new file mode 100644 index 0000000..e60d748 --- /dev/null +++ b/app/js/directives/timeline-search.js @@ -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); diff --git a/app/js/directives/timeline-viewport.js b/app/js/directives/timeline-viewport.js index 81cfae4..ea90738 100644 --- a/app/js/directives/timeline-viewport.js +++ b/app/js/directives/timeline-viewport.js @@ -202,6 +202,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); @@ -311,6 +322,12 @@ function timelineViewport($document) { select(null); } }); + + scope.$on('filter', function() { + if (loaded) { + update(); + } + }); }; return { diff --git a/app/js/directives/timeline.js b/app/js/directives/timeline.js index cf51b1e..125ad46 100644 --- a/app/js/directives/timeline.js +++ b/app/js/directives/timeline.js @@ -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: '', + templateUrl: 'directives/timeline.html', scope: { 'dataset': '=', 'hoveredItem': '=', diff --git a/app/js/filters/bootstrap-filters.js b/app/js/filters/bootstrap-filters.js new file mode 100644 index 0000000..b72b3fe --- /dev/null +++ b/app/js/filters/bootstrap-filters.js @@ -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; }); diff --git a/app/styles/directives/_timeline-overview.scss b/app/styles/directives/_timeline-overview.scss new file mode 100644 index 0000000..a574704 --- /dev/null +++ b/app/styles/directives/_timeline-overview.scss @@ -0,0 +1,9 @@ +timeline-overview svg { + .filter-hit { + + } + + .filter-miss { + opacity: 0.15; + } +} diff --git a/app/styles/directives/_timeline-search.scss b/app/styles/directives/_timeline-search.scss new file mode 100644 index 0000000..3dad1e4 --- /dev/null +++ b/app/styles/directives/_timeline-search.scss @@ -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; + } + } + } +} diff --git a/app/styles/directives/_timeline-viewport.scss b/app/styles/directives/_timeline-viewport.scss new file mode 100644 index 0000000..3456ae8 --- /dev/null +++ b/app/styles/directives/_timeline-viewport.scss @@ -0,0 +1,9 @@ +timeline-viewport svg { + .filter-hit { + + } + + .filter-miss { + opacity: 0.15; + } +} diff --git a/app/styles/main.scss b/app/styles/main.scss index 03765b3..a8e6407 100644 --- a/app/styles/main.scss +++ b/app/styles/main.scss @@ -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'; diff --git a/app/views/directives/timeline-details.html b/app/views/directives/timeline-details.html index 0033f2d..40c4d37 100644 --- a/app/views/directives/timeline-details.html +++ b/app/views/directives/timeline-details.html @@ -9,16 +9,13 @@
+ ng-class="item | contextClass:'panel'">

Details: {{item.name | split:'.' | pickRight:1}} - success - skip - fail + + {{item.status}} +

diff --git a/app/views/directives/timeline-search-popover.html b/app/views/directives/timeline-search-popover.html new file mode 100644 index 0000000..1ad38fb --- /dev/null +++ b/app/views/directives/timeline-search-popover.html @@ -0,0 +1,38 @@ +
+ +
+ + +
+
+ + +
+ + + +
+
+ +
+ +
    +
  • + {{item.name | split:'.' | pickRight:1}} +
  • +
+
+
diff --git a/app/views/directives/timeline-search.html b/app/views/directives/timeline-search.html new file mode 100644 index 0000000..a3048a7 --- /dev/null +++ b/app/views/directives/timeline-search.html @@ -0,0 +1,7 @@ + + + + diff --git a/app/views/directives/timeline.html b/app/views/directives/timeline.html new file mode 100644 index 0000000..53e26fa --- /dev/null +++ b/app/views/directives/timeline.html @@ -0,0 +1,13 @@ +
+
+

+ Timeline + +

+
+
+ + + +
+
diff --git a/app/views/timeline.html b/app/views/timeline.html index 9d03a5e..139a91d 100644 --- a/app/views/timeline.html +++ b/app/views/timeline.html @@ -17,20 +17,10 @@
-
-
-

Timeline

-
- - - - - -
+