Merge "Split tests page into list and detail page"

This commit is contained in:
Jenkins 2016-02-24 18:57:05 +00:00 committed by Gerrit Code Review
commit b2267c824e
8 changed files with 288 additions and 109 deletions

View File

@ -0,0 +1,82 @@
'use strict';
var controllersModule = require('./_index');
var _ = require('underscore');
/**
* @ngInject
*/
function TestsDetailController($scope, healthService, testService, key, $location) {
// ViewModel
var vm = this;
vm.searchTest = '';
vm.key = decodeURIComponent(key);
vm.processData = function(data) {
vm.chartData = {};
var testsByHierarchy = _.groupBy(data.tests, function(test) {
var testId = testService.removeIdNoise(test.test_id);
var keyMatcher = /^(\w*)\./g;
var matches = keyMatcher.exec(testId);
if (matches) {
return matches[1];
}
return 'Others';
});
var getTestFailureAvg = function(test) {
return test.failure / test.run_count;
};
_.each(testsByHierarchy, function(tests, hierarchy, list) {
if (!vm.chartData[hierarchy]) {
vm.chartData[hierarchy] = [{
key: hierarchy,
values: [],
tests: []
}];
}
var orderedTests = _.sortBy(tests, function(test) {
return getTestFailureAvg(test) * -1;
});
var topFailures = _.first(orderedTests, 10);
topFailures.forEach(function(test) {
var failureAverage = getTestFailureAvg(test);
if (!isNaN(failureAverage) && parseFloat(failureAverage) > 0.01) {
var chartData = {
label: test.test_id,
value: failureAverage
};
vm.chartData[hierarchy][0].values.push(chartData);
}
});
orderedTests.forEach(function(test) {
test.failureAverage = getTestFailureAvg(test);
vm.chartData[hierarchy][0].tests.push(test);
});
});
};
vm.loadData = function() {
healthService.getTests().then(function(response) {
vm.processData(response.data);
});
};
vm.searchTest = $location.search().searchTest || '';
vm.loadData();
vm.onSearchChange = function() {
$location.search('searchTest', $scope.testsDetail.searchTest);
};
}
controllersModule.controller('TestsDetailController', TestsDetailController);

View File

@ -27,40 +27,15 @@ function TestsController($scope, healthService, testService, $location) {
return 'Others';
});
var getTestFailureAvg = function(test) {
return test.failure / test.run_count;
};
_.each(testsByHierarchy, function(tests, hierarchy, list) {
if (!vm.chartData[hierarchy]) {
vm.chartData[hierarchy] = [{
key: hierarchy,
var sortedKeys = _.sortBy(_.keys(testsByHierarchy));
_.each(sortedKeys, function(key) {
if (!vm.chartData[key]) {
vm.chartData[key] = [{
key: key,
values: [],
tests: []
}];
}
var orderedTests = _.sortBy(tests, function(test) {
return getTestFailureAvg(test) * -1;
});
var topFailures = _.first(orderedTests, 10);
topFailures.forEach(function(test) {
var failureAverage = getTestFailureAvg(test);
if (!isNaN(failureAverage) && parseFloat(failureAverage) > 0.01) {
var chartData = {
label: test.test_id,
value: failureAverage
};
vm.chartData[hierarchy][0].values.push(chartData);
}
});
orderedTests.forEach(function(test) {
test.failureAverage = getTestFailureAvg(test);
vm.chartData[hierarchy][0].tests.push(test);
});
});
};

View File

@ -31,6 +31,17 @@ function OnConfig($stateProvider, $locationProvider, $urlRouterProvider) {
templateUrl: 'tests.html',
title: 'Tests'
})
.state('testsDetail', {
url: '/tests/:key',
controller: 'TestsDetailController as testsDetail',
templateUrl: 'tests-detail.html',
title: 'Tests Detail',
resolve: /*@ngInject*/ {
'key': function($stateParams) {
return $stateParams.key;
}
}
})
.state('job', {
url: '/job/:jobName',
controller: 'JobController as job',

View File

@ -100,6 +100,7 @@ function HealthService($http, config) {
service.getTests = function() {
return config.get().then(function(config) {
return $http.jsonp(config.apiRoot + '/tests', {
cache: true,
params: { callback: 'JSON_CALLBACK' }
});
});

View File

@ -0,0 +1,70 @@
<header class="bs-header">
<div class="container">
<h1 class="page-header">Tests Detail</h1>
<crumb-menu></crumb-menu>
</div>
</header>
<div class="container">
<div class="row">
<div class="col-lg-12">
<loading-indicator></loading-indicator>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<accordion close-others="false">
<div class="panel panel-body panel-default">
<accordion-group heading="Details for {{ testsDetail.key }}" is-open="true">
<div>
<form>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-search"></i></div>
<input type="text" class="form-control"
placeholder="Search for test"
ng-model="testsDetail.searchTest"
ng-model-options="{debounce: 250}"
ng-change="testsDetail.onSearchChange()">
</div>
</div>
</form>
<table table-sort data="testsDetail.chartData[testsDetail.key][0]['tests']" class="table">
<thead>
<tr>
<th sort-field="test_id" class="text-left">
Test ID
</th>
<th sort-field="success" class="text-right" style="min-width:80px">
Passed
</th>
<th sort-field="failure" class="text-right" style="min-width:80px">
Failed
</th>
<th sort-default='reversed' sort-field="failureAverage" class="text-right" style="min-width:95px">
Failure %
</th>
<th sort-field="run_time" class="text-right" style="min-width:80px">
Avg. Runtime (secs.)
</th>
</tr>
</thead>
<tbody>
<tr table-ref="table" ng-repeat="test in table.dataSorted | filter:testsDetail.searchTest">
<td class="text-left">
<a ui-sref="test({ testId: test.test_id })"> {{test.test_id | limitTo: 110}}</a>
</td>
<td class="text-right">{{ test.success | number }}</td>
<td class="text-right">{{ test.failure | number }}</td>
<td class="text-right">{{ test.failureAverage * 100 | number: 2 }}%</td>
<td class="text-right">{{ test.run_time | number: 2 }}</td>
</tr>
</tbody>
</table>
</div>
</accordion-group>
</div>
</accordion>
</div>
</div>
</div>

View File

@ -13,58 +13,18 @@
</div>
<div class="row">
<div class="col-lg-12">
<accordion close-others="false">
<div class="panel panel-body panel-default" ng-repeat="(key, value) in tests.chartData">
<accordion-group heading="Details for {{ key }}" is-open="true">
<div>
<form>
<div class="form-group">
<div class="input-group">
<div class="input-group-addon"><i class="fa fa-search"></i></div>
<input type="text" class="form-control"
placeholder="Search for test"
ng-model="tests.searchTest"
ng-model-options="{debounce: 250}"
ng-change="tests.onSearchChange()">
</div>
</div>
</form>
<table table-sort data="tests.chartData[key][0]['tests']" class="table">
<thead>
<tr>
<th sort-field="test_id" class="text-left">
Test ID
</th>
<th sort-field="success" class="text-right" style="min-width:80px">
Passed
</th>
<th sort-field="failure" class="text-right" style="min-width:80px">
Failed
</th>
<th sort-default='reversed' sort-field="failureAverage" class="text-right" style="min-width:95px">
Failure %
</th>
<th sort-field="run_time" class="text-right" style="min-width:80px">
Avg. Runtime (secs.)
</th>
</tr>
</thead>
<tbody>
<tr table-ref="table" ng-repeat="test in table.dataSorted | filter:tests.searchTest">
<td class="text-left">
<a ui-sref="test({ testId: test.test_id })"> {{test.test_id | limitTo: 110}}</a>
</td>
<td class="text-right">{{ test.success | number }}</td>
<td class="text-right">{{ test.failure | number }}</td>
<td class="text-right">{{ test.failureAverage * 100 | number: 2 }}%</td>
<td class="text-right">{{ test.run_time | number: 2 }}</td>
</tr>
</tbody>
</table>
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Details list</h3>
</div>
<div class="panel-body">
<div class="list-group">
<a ng-repeat="(key, value) in tests.chartData"
ui-sref="testsDetail({ key: key })"
class="list-group-item">{{ key }}</a>
</div>
</accordion-group>
</div>
</div>
</accordion>
</div>
</div>
</div>

View File

@ -0,0 +1,107 @@
describe('TestsDetailController', function() {
beforeEach(function() {
module('app');
module('app.controllers');
});
var $scope, $httpBackend, $controller, healthService;
var API_ROOT = 'http://8.8.4.4:8080';
var DEFAULT_START_DATE = new Date();
beforeEach(inject(function($rootScope, _$httpBackend_, _$controller_, _healthService_) {
$httpBackend = _$httpBackend_;
mockConfigService();
mockHealthService();
$scope = $rootScope.$new();
$controller = _$controller_;
healthService = _healthService_;
}));
function mockHealthService() {
var expectedResponse = {
tests: [
{
failure: 5592,
id: '00187173-ab23-4181-9a15-e291a0d8e2d1',
run_count: 55920,
run_time: 0.608151,
success: 55920,
test_id: 'tempest.api.identity.admin.v2.test_users.one'
},
{
failure: 0,
id: '001c6860-c966-4c0b-9928-ecccd162bed0',
run_count: 4939,
run_time: 5.97596,
success: 4939,
test_id: 'tempest.api.volume.admin.test_snapshots_actions.two'
},
{
failure: 1,
id: '002a15e0-f6d1-472a-bd66-bb13ac4d77aa',
run_count: 32292,
run_time: 1.18864,
success: 32291,
test_id: 'tempest.api.network.test_routers.three'
}
]
};
var endpoint = API_ROOT + '/tests?callback=JSON_CALLBACK';
$httpBackend.expectJSONP(endpoint)
.respond(200, expectedResponse);
}
function mockConfigService() {
var expectedResponse = { apiRoot: API_ROOT };
var endpoint = 'config.json';
$httpBackend.expectGET(endpoint).respond(200, expectedResponse);
}
it('should process chart data correctly', function() {
var testsDetailController = $controller('TestsDetailController', {
healthService: healthService,
$scope: $scope,
key: 'tempest'
});
$httpBackend.flush();
var expectedChartData = {
'tempest': [{
key: 'tempest',
values: [{
label: 'tempest.api.identity.admin.v2.test_users.one',
value: 0.1
}],
tests: [{
failure: 5592,
id: '00187173-ab23-4181-9a15-e291a0d8e2d1',
run_count: 55920,
run_time: 0.608151,
success: 55920,
test_id: 'tempest.api.identity.admin.v2.test_users.one',
failureAverage: 0.1
}, {
failure: 1,
id: '002a15e0-f6d1-472a-bd66-bb13ac4d77aa',
run_count: 32292,
run_time: 1.18864,
success: 32291,
test_id: 'tempest.api.network.test_routers.three',
failureAverage: 0.0000309674222717701
}, {
failure: 0,
id: '001c6860-c966-4c0b-9928-ecccd162bed0',
run_count: 4939,
run_time: 5.97596,
success: 4939,
test_id: 'tempest.api.volume.admin.test_snapshots_actions.two',
failureAverage: 0
}]
}]
};
expect(testsDetailController.chartData).toEqual(expectedChartData);
});
});

View File

@ -70,35 +70,8 @@ describe('TestsController', function() {
var expectedChartData = {
'tempest': [{
key: 'tempest',
values: [{
label: 'tempest.api.identity.admin.v2.test_users.one',
value: 0.1
}],
tests: [{
failure: 5592,
id: '00187173-ab23-4181-9a15-e291a0d8e2d1',
run_count: 55920,
run_time: 0.608151,
success: 55920,
test_id: 'tempest.api.identity.admin.v2.test_users.one',
failureAverage: 0.1
}, {
failure: 1,
id: '002a15e0-f6d1-472a-bd66-bb13ac4d77aa',
run_count: 32292,
run_time: 1.18864,
success: 32291,
test_id: 'tempest.api.network.test_routers.three',
failureAverage: 0.0000309674222717701
}, {
failure: 0,
id: '001c6860-c966-4c0b-9928-ecccd162bed0',
run_count: 4939,
run_time: 5.97596,
success: 4939,
test_id: 'tempest.api.volume.admin.test_snapshots_actions.two',
failureAverage: 0
}]
values: [],
tests: []
}]
};
expect(testsController.chartData).toEqual(expectedChartData);