Merge "Split tests page into list and detail page"
This commit is contained in:
commit
b2267c824e
82
app/js/controllers/tests-detail.js
Normal file
82
app/js/controllers/tests-detail.js
Normal 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);
|
@ -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);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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' }
|
||||
});
|
||||
});
|
||||
|
70
app/views/tests-detail.html
Normal file
70
app/views/tests-detail.html
Normal 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>
|
@ -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>
|
||||
|
107
test/unit/controllers/tests_detail_spec.js
Normal file
107
test/unit/controllers/tests_detail_spec.js
Normal 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);
|
||||
});
|
||||
});
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user