Add global resolution and grouping key options.

This commit enhances the breadcrumb menu to add global selectors
for resolution and grouping key into a single place on every page.
Existing pages make use of and share these values as users browse
between them. All existing UI elements for changing resolution or
grouping parameters have also been removed in favor of this
universal solution.

Change-Id: Ifdef72219392c3afb979251d13863ca0146dde03
Co-Authored-By: Matthew Treinish <mtreinish@kortar.org>
This commit is contained in:
Tim Buckley 2015-12-22 15:43:18 -07:00
parent a8c3cd1f8b
commit 74400a889b
18 changed files with 300 additions and 65 deletions

View File

@ -5,7 +5,10 @@ var controllersModule = require('./_index');
/**
* @ngInject
*/
function GroupedRunsController(pageTitleService, healthService, runMetadataKey, name, currentDate) {
function GroupedRunsController(
$scope, pageTitleService, healthService, viewService,
runMetadataKey, name, currentDate) {
// ViewModel
var vm = this;
@ -17,6 +20,10 @@ function GroupedRunsController(pageTitleService, healthService, runMetadataKey,
vm.runMetadataKey = decodeURIComponent(runMetadataKey);
vm.name = decodeURIComponent(name);
// update the global grouping key - if we arrived here directly, it will not
// be set already
viewService.groupKey(runMetadataKey);
// Updates the page title based on the selected runMetadataKey
pageTitleService.update(vm.runMetadataKey);
@ -105,13 +112,17 @@ function GroupedRunsController(pageTitleService, healthService, runMetadataKey,
healthService.getRunsForRunMetadataKey(vm.runMetadataKey, vm.name, {
start_date: startDate,
stop_date: stopDate,
datetime_resolution: 'hour'
datetime_resolution: viewService.resolution().key
}).then(function(response) {
vm.processData(response.data);
});
};
vm.loadData();
$scope.$on('view:resolution', function(event, resolution) {
vm.loadData();
});
}
controllersModule.controller('GroupedRunsController', GroupedRunsController);

View File

@ -5,7 +5,7 @@ var controllersModule = require('./_index');
/**
* @ngInject
*/
function HomeController(healthService, startDate, projectService) {
function HomeController($scope, healthService, startDate, projectService, viewService) {
var byFailRateDesc = function(project1, project2) {
// To get descending order, project2 should come first
@ -53,36 +53,33 @@ function HomeController(healthService, startDate, projectService) {
};
};
function loadRunMetadataKeys() {
healthService.getRunMetadataKeys().then(function(response) {
vm.runMetadataKeys = response.data;
});
}
var loadData = function(runMetadataKey) {
var groupBy = runMetadataKey || vm.selectedRunMetadataKey;
vm.selectedRunMetadataKey = groupBy;
var start = new Date(startDate);
start.setDate(start.getDate() - 20);
healthService.getRunsGroupedByMetadataPerDatetime(groupBy, {
healthService.getRunsGroupedByMetadataPerDatetime(vm.groupKey, {
start_date: start,
datetime_resolution: 'hour'
datetime_resolution: viewService.resolution().key
}).then(function(response) {
vm.processData(response.data);
processData(response.data);
});
};
// ViewModel
var vm = this;
vm.searchProject = '';
vm.selectedRunMetadataKey = 'project';
vm.loadRunMetadataKeys = loadRunMetadataKeys;
vm.processData = processData;
vm.loadData = loadData;
vm.groupKey = viewService.groupKey();
vm.searchProject = '';
vm.loadData();
vm.loadRunMetadataKeys();
loadData();
$scope.$on('view:groupKey', function(event, groupKey) {
vm.groupKey = groupKey;
loadData(groupKey);
});
$scope.$on('view:resolution', function(event, resolution) {
loadData();
});
}
controllersModule.controller('HomeController', HomeController);

View File

@ -5,7 +5,7 @@ var controllersModule = require('./_index');
/**
* @ngInject
*/
function JobController(healthService, jobName, startDate) {
function JobController($scope, healthService, viewService, jobName, startDate) {
// ViewModel
var vm = this;
@ -141,13 +141,17 @@ function JobController(healthService, jobName, startDate) {
healthService.getTestsFromBuildName(vm.name, {
start_date: start,
datetime_resolution: 'hour'
datetime_resolution: viewService.resolution().key
}).then(function(response) {
vm.processData(response.data);
});
};
vm.loadData();
$scope.$on('view:resolution', function(event, resolution) {
vm.loadData();
});
}
controllersModule.controller('JobController', JobController);

View File

@ -5,7 +5,7 @@ var controllersModule = require('./_index');
/**
* @ngInject
*/
function TestController(healthService, testService, startDate, testId) {
function TestController($scope, healthService, testService, viewService, startDate, testId) {
// ViewModel
var vm = this;
@ -119,12 +119,17 @@ function TestController(healthService, testService, startDate, testId) {
beginDate.setDate(startDate.getDate() - 15);
healthService.getTestRunList(vm.testName, {
start_date: beginDate,
stop_date: stopDate
stop_date: stopDate,
datetime_resolution: viewService.resolution().key
}).then(function(response) {
vm.processData(response.data);
});
};
vm.loadData();
$scope.$on('view:resolution', function(event, resolution) {
vm.loadData();
});
}
controllersModule.controller('TestController', TestController);

View File

@ -8,7 +8,37 @@ var directivesModule = require('./_index.js');
function crumbMenu() {
var link = function(scope, element, attrs, ctrl, transclude) {
transclude(function(clone) {
angular.element(element[0].querySelector('ol')).append(clone);
angular.element(element[0].querySelector('nav ul')).append(clone);
});
};
/**
* @ngInject
*/
var controller = function($scope, healthService, viewService) {
$scope.resolutionOptions = viewService.resolutionOptions();
$scope.selectedResolution = viewService.resolution();
$scope.selectedGroupKey = viewService.groupKey();
$scope.groupKeys = [];
$scope.setResolution = function(resolution) {
viewService.resolution(resolution);
};
$scope.setGroupKey = function(groupKey) {
viewService.groupKey(groupKey);
};
$scope.$on('view:resolution', function(event, resolution) {
$scope.selectedResolution = resolution;
});
$scope.$on('view:groupKey', function(event, groupKey) {
$scope.selectedGroupKey = groupKey;
});
healthService.getRunMetadataKeys().then(function(response) {
$scope.groupKeys = response.data;
});
};
@ -16,7 +46,12 @@ function crumbMenu() {
restrict: 'E',
transclude: true,
templateUrl: 'crumb-menu.html',
link: link
link: link,
controller: controller,
scope: {
'showGroupKey': '@',
'showResolution': '@'
}
};
}

42
app/js/services/view.js Normal file
View File

@ -0,0 +1,42 @@
'use strict';
var servicesModule = require('./_index.js');
/**
* @ngInject
*/
var viewService = function($rootScope) {
var resolutionOptions = [
{ name: 'Second', key: 'sec'},
{ name: 'Minute', key: 'min' },
{ name: 'Hour', key: 'hour' },
{ name: 'Day', key: 'day' }
];
var resolution = resolutionOptions[2];
var groupKey = 'project';
return {
resolution: function(res) {
if (arguments.length === 1) {
resolution = res;
$rootScope.$broadcast('view:resolution', res);
}
return resolution;
},
resolutionOptions: function() {
return resolutionOptions;
},
groupKey: function(key) {
if (arguments.length === 1) {
groupKey = key;
$rootScope.$broadcast('view:groupKey', groupKey);
}
return groupKey;
}
};
};
servicesModule.factory('viewService', viewService);

View File

@ -0,0 +1,94 @@
.nav-breadcrumb > li:not(:last-child):after {
content: " ";
display: block;
width: 0;
height: 0;
border-top: 17px solid transparent;
border-bottom: 17px solid transparent;
border-left: 10px solid #f8f8f8;
position: absolute;
top: 50%;
margin-top: -17px;
left: 100%;
z-index: 3;
}
.nav-breadcrumb > li:not(:last-child):before {
content: " ";
display: block;
width: 0;
height: 0;
border-top: 17px solid transparent;
border-bottom: 17px solid transparent;
border-left: 10px solid rgb(173, 173, 173);
position: absolute;
top: 50%;
margin-top: -17px;
margin-left: 1px;
left: 100%;
z-index: 3;
}
.navbar-breadcrumb {
min-height: 10px;
}
.navbar-breadcrumb ul:not(.nav-breadcrumb) > li > a {
padding: 6px 10px;
}
.nav-breadcrumb > li {
padding:6px 12px 6px 24px;
}
.nav-breadcrumb > li > a {
padding: 0px;
}
.nav-breadcrumb li:not(:last-child):hover {
background-color: #ebebeb;
}
.nav-breadcrumb > li:first-child {
padding:6px 6px 6px 10px;
}
.btn-breadcrumb > li:last-child {
padding:6px 18px 6px 24px;
}
.nav-breadcrumb > li:not(:last-child):after {
border-left: 10px solid #f8f8f8;
}
.nav-breadcrumb > li:not(:last-child):before {
border-left: 10px solid #ccc;
}
.nav-breadcrumb > li:hover:not(:last-child):after {
border-left: 10px solid #ebebeb;
}
.nav-breadcrumb > li:hover:not(:last-child):before {
border-left: 10px solid #adadad;
}
.navbar-form-xs {
padding: 0px 20px;
margin: 5px 8px;
}
.input-xs {
height: 22px;
padding: 0px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
.navbar-breadcrumb .container-fluid li[dropdown]:not(:last-child):before {
background: #ebebeb;
width: 1px;
content: "";
display:block;
position: absolute;
top:0;
bottom: 0;
right: 0;
}
.navbar-breadcrumb ul.dropdown-menu a {
cursor: pointer;
}

View File

@ -332,7 +332,7 @@ $grid-columns: 12 !default;
$grid-gutter-width: 30px !default;
// Navbar collapse
//** Point at which the navbar becomes uncollapsed.
$grid-float-breakpoint: $screen-sm-min !default;
$grid-float-breakpoint: 0px !default;
//** Point at which the navbar begins collapsing.
$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default;

View File

@ -6,3 +6,4 @@
@import 'nv.d3';
@import 'header';
@import 'footer';
@import 'breadcrumbs'

View File

@ -1,17 +1,62 @@
<ol class="breadcrumb">
<li dropdown>
<span ng-switch on="'tests' | isState">
<a ng-switch-when="true" dropdown-toggle href>Tests <fa name="caret-down"></fa></a>
<a ng-switch-default dropdown-toggle href>Overview <fa name="caret-down"></fa></a>
</span>
<nav class="navbar-breadcrumb navbar navbar-default">
<ul class="nav navbar-nav navbar-left nav-breadcrumb">
<li><a ui-sref="home"><fa name="home"></fa></a></li>
<li dropdown>
<span ng-switch on="'tests' | isState">
<a ng-switch-when="true" dropdown-toggle href>
Tests <fa name="caret-down"></fa>
</a>
<a ng-switch-default dropdown-toggle href>
Overview <fa name="caret-down"></fa>
</a>
</span>
<ul role="menu" class="dropdown-menu">
<li>
<a ui-sref="home"><fa name="line-chart" fw></fa> Overview</a>
<ul role="menu" class="dropdown-menu">
<li>
<a ui-sref="home"><fa name="line-chart" fw></fa> Overview</a>
</li>
<li>
<a ui-sref="tests"><fa name="bar-chart" fw></fa> Tests</a>
</li>
</ul>
</li>
</ul>
<div class="container-fluid">
<ul class="nav navbar-nav navbar-right">
<li dropdown ng-if="showGroupKey == 'true'">
<a dropdown-toggle href title="Group By Key">
<fa name="key"></fa>
&nbsp;Grouping: {{selectedGroupKey}}&nbsp;
<fa name="caret-down"></fa>
</a>
<ul role="menu" class="dropdown-menu">
<li ng-repeat="key in groupKeys">
<a ng-click="setGroupKey(key)">
{{key}}
<fa name="check"
class="pull-right"
ng-if="key == selectedGroupKey"></fa>
</a>
</li>
</ul>
</li>
<li>
<a ui-sref="tests"><fa name="bar-chart" fw></fa> Tests</a>
<li dropdown ng-if="showResolution == 'true'">
<a dropdown-toggle href title="Resolution">
<fa name="expand"></fa>
&nbsp;Resolution: {{selectedResolution.key}}&nbsp;
<fa name="caret-down"></fa>
</a>
<ul role="menu" class="dropdown-menu">
<li ng-repeat="res in resolutionOptions">
<a ng-click="setResolution(res)">
{{res.name}}
<fa name="check"
class="pull-right"
ng-if="res == selectedResolution"></fa>
</a>
</li>
</ul>
</li>
</ul>
</li>
</ol>
</div>
</nav>

View File

@ -1,7 +1,7 @@
<header class="bs-header">
<div class="container">
<h1 class="page-header">{{ groupedRuns.name }} jobs</h1>
<crumb-menu>
<crumb-menu show-resolution="true">
<li>Project: {{ groupedRuns.name }}</li>
</crumb-menu>
</div>

View File

@ -3,7 +3,7 @@
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">OpenStack Health</h1>
<crumb-menu></crumb-menu>
<crumb-menu show-group-key="true" show-resolution="true"></crumb-menu>
<loading-indicator></loading-indicator>
</div>
</div>
@ -36,15 +36,6 @@
<div class="row">
<div class="col-lg-12">
<form>
<div class="form-group">
<label for="sel1">Group information by:</label>
<select ng-init="selectedRunMetadataKey = 'project'"
ng-model="selectedRunMetadataKey"
ng-options="runMetadataKey for runMetadataKey in home.runMetadataKeys"
ng-change="home.loadData(selectedRunMetadataKey)"
class="form-control">
</select>
</div>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-search"></i></div>
@ -56,7 +47,7 @@
<div class="col-lg-4" ng-repeat="project in home.projects | filter:home.searchProject">
<div class="panel panel-default">
<div class="panel-heading">
<a ui-sref="groupedRuns({ runMetadataKey: home.selectedRunMetadataKey, name: project.name })">
<a ui-sref="groupedRuns({ runMetadataKey: home.groupKey, name: project.name })">
<h3 class="panel-title">{{project.name}}</h3>
</a>
</div>

View File

@ -1,7 +1,7 @@
<header class="bs-header">
<div class="container">
<h1 class="page-header">{{ job.name }} tests</h1>
<crumb-menu>
<crumb-menu show-resolution="true">
<li>Job: {{ job.name }}</li>
</crumb-menu>
</div>

View File

@ -1,7 +1,7 @@
<header class="bs-header">
<div class="container">
<h1 class="page-header">{{ testCtrl.testShortName }}</h1>
<crumb-menu>
<crumb-menu show-resolution="true">
<li>Test: {{testCtrl.testShortName }}</li>
</crumb-menu>
</div>

View File

@ -4,11 +4,12 @@ describe('GroupedRunsController', function() {
module('app.controllers');
});
var $httpBackend, $controller, healthService;
var $scope, $httpBackend, $controller, healthService;
var API_ROOT = 'http://8.8.4.4:8080';
var DEFAULT_CURRENT_DATE = new Date();
beforeEach(inject(function(_$httpBackend_, _$controller_, _healthService_) {
beforeEach(inject(function($rootScope, _$httpBackend_, _$controller_, _healthService_) {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
mockConfigService();
mockHealthService();
@ -78,6 +79,7 @@ describe('GroupedRunsController', function() {
it('should process chart data correctly', function() {
var groupedRunsController = $controller('GroupedRunsController', {
$scope: $scope,
healthService: healthService,
runMetadataKey: 'project',
name: 'openstack/cinder',
@ -104,6 +106,7 @@ describe('GroupedRunsController', function() {
it('should process chart data rate correctly', function() {
var groupedRunsController = $controller('GroupedRunsController', {
$scope: $scope,
healthService: healthService,
runMetadataKey: 'project',
name: 'openstack/cinder',

View File

@ -3,7 +3,7 @@ describe('HomeController', function() {
module('app');
});
var $controller, homeController, projectService;
var $scope, $controller, homeController, projectService;
var mockResponse = { data: {} };
var mockMetadataKeysResponse = {
data: {
@ -15,7 +15,8 @@ describe('HomeController', function() {
}
};
beforeEach(inject(function(_$controller_) {
beforeEach(inject(function($rootScope, _$controller_) {
$scope = $rootScope.$new();
$controller = _$controller_;
var defaultStartDate = new Date();
@ -35,6 +36,7 @@ describe('HomeController', function() {
};
homeController = $controller('HomeController', {
$scope: $scope,
healthService: healthService,
startDate: defaultStartDate,
projectService: projectService

View File

@ -4,17 +4,17 @@ describe('JobController', function() {
module('app.controllers');
});
var $httpBackend, $controller, healthService;
var $scope, $httpBackend, $controller, healthService;
var API_ROOT = 'http://8.8.4.4:8080';
var DEFAULT_START_DATE = new Date();
beforeEach(inject(function(_$httpBackend_, _$controller_, _healthService_) {
beforeEach(inject(function($rootScope, _$httpBackend_, _$controller_, _healthService_) {
$httpBackend = _$httpBackend_;
mockConfigService();
mockHealthService();
$scope = $rootScope.$new();
$controller = _$controller_;
healthService = _healthService_;
}));
@ -64,6 +64,7 @@ describe('JobController', function() {
it('should process chart data correctly', function() {
var jobController = $controller('JobController', {
$scope: $scope,
healthService: healthService,
jobName: 'gate-tempest-dsvm-neutron-full',
startDate: DEFAULT_START_DATE
@ -97,6 +98,7 @@ describe('JobController', function() {
it('should process chart data rate correctly', function() {
var jobController = $controller('JobController', {
$scope: $scope,
healthService: healthService,
jobName: 'gate-tempest-dsvm-neutron-full',
startDate: DEFAULT_START_DATE
@ -115,6 +117,7 @@ describe('JobController', function() {
it('should process tests correctly', function() {
var jobController = $controller('JobController', {
$scope: $scope,
healthService: healthService,
jobName: 'gate-tempest-dsvm-neutron-full',
startDate: DEFAULT_START_DATE

View File

@ -4,16 +4,17 @@ describe('TestsController', function() {
module('app.controllers');
});
var $httpBackend, $controller, healthService;
var $scope, $httpBackend, $controller, healthService;
var API_ROOT = 'http://8.8.4.4:8080';
var DEFAULT_START_DATE = new Date();
beforeEach(inject(function(_$httpBackend_, _$controller_, _healthService_) {
beforeEach(inject(function($rootScope, _$httpBackend_, _$controller_, _healthService_) {
$httpBackend = _$httpBackend_;
mockConfigService();
mockHealthService();
$scope = $rootScope.$new();
$controller = _$controller_;
healthService = _healthService_;
}));
@ -61,7 +62,8 @@ describe('TestsController', function() {
it('should process chart data correctly', function() {
var testsController = $controller('TestsController', {
healthService: healthService
healthService: healthService,
$scope: $scope
});
$httpBackend.flush();