Allow sharing of selected item by page URL.

This patch adds the ability for users to share a selected timeline
item via the page URL. When an item is selected, the page address
is updated to include the current test name as a parameter. If
this link is shared, the test named in the URL will automatically
be highlighed when the page is loaded.

Change-Id: I228d58e68ee986f621a3763bba1a565300c79023
This commit is contained in:
Tim Buckley 2015-11-20 16:31:21 -07:00
parent 39e9779596
commit 35a5b6b9f7
4 changed files with 79 additions and 31 deletions

View File

@ -5,7 +5,7 @@ var controllersModule = require('./_index');
/** /**
* @ngInject * @ngInject
*/ */
function TimelineCtrl($stateParams, datasetService) { function TimelineCtrl($scope, $location, $stateParams, datasetService) {
// ViewModel // ViewModel
var vm = this; var vm = this;
@ -19,6 +19,19 @@ function TimelineCtrl($stateParams, datasetService) {
vm.hoveredItem = null; vm.hoveredItem = null;
vm.selectedItem = null; vm.selectedItem = null;
vm.preselect = $location.search().test;
$scope.$watch(function() {
return vm.selectedItem;
}, function(value) {
if (value) {
$location.search({ test: value.name });
vm.preselect = null;
} else if (vm.preselect === null) {
$location.search({ test: null });
}
});
} }
controllersModule.controller('TimelineCtrl', TimelineCtrl); controllersModule.controller('TimelineCtrl', TimelineCtrl);

View File

@ -134,11 +134,11 @@ function timeline($log, datasetService) {
.append('clipPath') .append('clipPath')
.attr('id', 'clip') .attr('id', 'clip')
.append('rect') .append('rect')
.attr('width', width); // TODO: set height later .attr('width', width);
var main = chart.append('g') var main = chart.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
.attr('width', width); // TODO: set height later .attr('width', width);
var laneLines = main.append('g'); var laneLines = main.append('g');
var laneLabels = main.append('g'); var laneLabels = main.append('g');
@ -280,31 +280,8 @@ function timeline($log, datasetService) {
}) })
.on('click', function(d) { .on('click', function(d) {
var self = d3.select(this); var self = d3.select(this);
if (selectedRect) { selectItem(self, d);
if (selectedRect.attr('data-old-fill')) {
selectedRect.attr('fill', selectedRect.attr('data-old-fill'));
selectedRect.attr('data-old-fill', null);
}
if (scope.selectedItem.name === d.name) {
scope.selectedItem = null;
scope.$apply();
selectedRect = null;
return;
}
}
scope.selectedItem = d;
scope.$apply(); scope.$apply();
selectedRect = self;
if (!self.attr('data-old-fill')) {
self.attr('data-old-fill', self.attr('fill'));
}
self.attr('fill', 'goldenrod');
}); });
rects.exit().remove(); rects.exit().remove();
@ -371,6 +348,30 @@ function timeline($log, datasetService) {
groups.exit().remove(); groups.exit().remove();
}; };
var selectItem = function(element, datum) {
if (selectedRect) {
if (selectedRect.attr('data-old-fill')) {
selectedRect.attr('fill', selectedRect.attr('data-old-fill'));
selectedRect.attr('data-old-fill', null);
}
if (scope.selectedItem.name === datum.name) {
scope.selectedItem = null;
selectedRect = null;
return;
}
}
scope.selectedItem = datum;
selectedRect = element;
if (!element.attr('data-old-fill')) {
element.attr('data-old-fill', element.attr('fill'));
}
element.attr('fill', 'goldenrod');
};
var initChart = function() { var initChart = function() {
// determine lanes available based on current data // determine lanes available based on current data
dstatLanes = getDstatLanes(dstat.entries, dstat.minimums, dstat.maximums); dstatLanes = getDstatLanes(dstat.entries, dstat.minimums, dstat.maximums);
@ -413,6 +414,7 @@ function timeline($log, datasetService) {
.on('brush', update); .on('brush', update);
var brushElement = mini.append('g') var brushElement = mini.append('g')
.attr('class', 'brush')
.call(brush) .call(brush)
.selectAll('rect') .selectAll('rect')
.attr('y', 1) .attr('y', 1)
@ -481,6 +483,7 @@ function timeline($log, datasetService) {
// find data extents // find data extents
var minStart = null; var minStart = null;
var maxEnd = null; var maxEnd = null;
var preselect = null;
raw.forEach(function(d) { raw.forEach(function(d) {
d.startDate = new Date(d.timestamps[0]); d.startDate = new Date(d.timestamps[0]);
@ -492,6 +495,10 @@ function timeline($log, datasetService) {
if (maxEnd === null || d.endDate > maxEnd) { if (maxEnd === null || d.endDate > maxEnd) {
maxEnd = d.endDate; maxEnd = d.endDate;
} }
if (scope.preselect && d.name === scope.preselect) {
preselect = d;
}
}); });
// define a nested data structure with groups by worker, and fill using // define a nested data structure with groups by worker, and fill using
@ -513,6 +520,32 @@ function timeline($log, datasetService) {
timeExtents = [ minStart, maxEnd ]; timeExtents = [ minStart, maxEnd ];
initChart(); initChart();
if (preselect) {
// determine desired viewport and item sizes to center view on
var extentSize = (maxEnd - minStart) / 8;
var itemLength = (preselect.endDate - preselect.startDate);
var itemMid = preselect.startDate.getTime() + (itemLength / 2);
// find real begin, end values, but don't exceed min/max time extents
var begin = Math.max(minStart.getTime(), itemMid - (extentSize / 2));
begin = Math.min(begin, maxEnd.getTime() - extentSize);
var end = begin + extentSize;
// update the brush extent and redraw
brush.extent([ begin, end ]);
mini.select('.brush').call(brush);
// update items to reflect the new viewport bounds
update();
// find + select the actual dom element
itemGroups.selectAll('rect').each(function(d) {
if (d.name === preselect.name) {
selectItem(d3.select(this), d);
}
});
}
}; };
chart.on('mouseout', function() { chart.on('mouseout', function() {
@ -548,7 +581,6 @@ function timeline($log, datasetService) {
defs.attr('width', width); defs.attr('width', width);
main.attr('width', width); main.attr('width', width);
mini.attr('width', width); mini.attr('width', width);
// TODO: dstat?
laneLines.selectAll('.laneLine').attr('x2', width); laneLines.selectAll('.laneLine').attr('x2', width);
@ -588,7 +620,8 @@ function timeline($log, datasetService) {
scope: { scope: {
'dataset': '=', 'dataset': '=',
'hoveredItem': '=', 'hoveredItem': '=',
'selectedItem': '=' 'selectedItem': '=',
'preselect': '='
}, },
link: link link: link
}; };

View File

@ -15,9 +15,10 @@ function OnConfig($stateProvider, $locationProvider, $urlRouterProvider) {
}); });
$stateProvider.state('timeline', { $stateProvider.state('timeline', {
url: '/timeline/{datasetId:int}', url: '/timeline/{datasetId:int}?test',
controller: 'TimelineCtrl as timeline', controller: 'TimelineCtrl as timeline',
templateUrl: 'timeline.html', templateUrl: 'timeline.html',
reloadOnSearch: false,
title: 'Timeline' title: 'Timeline'
}); });

View File

@ -25,7 +25,8 @@
<timeline class="panel-body" <timeline class="panel-body"
dataset="timeline.dataset" dataset="timeline.dataset"
hovered-item="timeline.hoveredItem" hovered-item="timeline.hoveredItem"
selected-item="timeline.selectedItem"></timeline> selected-item="timeline.selectedItem"
preselect="timeline.preselect"></timeline>
</div> </div>
</div> </div>
<div class="col-lg-4"> <div class="col-lg-4">