Merge pull request #1589 from leigh-johnson/JobDetailBugs

Fixes Job Details 404s, event summary failed filter bug
This commit is contained in:
Leigh 2016-04-20 10:07:07 -04:00
commit caab667684
11 changed files with 111 additions and 94 deletions

View File

@ -42,7 +42,7 @@ export default
.factory('DigestEvent', ['$rootScope', '$log', 'UpdatePlayStatus', 'UpdateHostStatus', 'AddHostResult',
'GetElapsed', 'UpdateTaskStatus', 'JobIsFinished', 'AddNewTask', 'AddNewPlay',
function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, GetElapsed,
UpdateTaskStatus, JobIsFinished, AddNewTask, AddNewPlay, longDateFilter) {
UpdateTaskStatus, JobIsFinished, AddNewTask, AddNewPlay) {
return function(params) {
var scope = params.scope,
@ -485,8 +485,7 @@ export default
created = params.created,
msg = params.message,
item = params.item,
counter = params.counter,
h, host;
counter = params.counter;
if (scope.jobData.hostSummaries[host_id] !== undefined) {
scope.jobData.hostSummaries[host_id].ok += (status === 'successful') ? 1 : 0;
@ -920,7 +919,7 @@ export default
order: 'host_name,counter',
};
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostEvents(res.results)
scope.hostResults = JobDetailService.processHostEvents(res.results);
scope.hostResultsLoading = false;
});
};

View File

@ -110,7 +110,6 @@
var init = function(){
$scope.hostName = $stateParams.hostName;
// create filter dropdown
console.log($stateParams)
CreateSelect2({
element: '.HostEvents-select',
multiple: false

View File

@ -8,14 +8,13 @@
['$scope', '$rootScope', '$stateParams', 'Wait', 'JobDetailService', 'jobSocket', 'DrawGraph', function($scope, $rootScope, $stateParams, Wait, JobDetailService, jobSocket, DrawGraph){
var page_size = 200;
$scope.loading = $scope.hosts.length > 0 ? false : true;
$scope.filter = 'all';
$scope.search = null;
var buildTooltips = function(hosts){
var buildGraph = function(hosts){
// status waterfall: unreachable > failed > changed > ok > skipped
var count, grammar, text = {};
var count;
count = {
ok : _.filter(hosts, function(o){
return o.failures === 0 && o.changed === 0 && o.ok > 0;
@ -33,6 +32,25 @@
return o.changed > 0;
})
};
return count;
};
var socketListener = function(){
// emitted by the API in the same function used to persist host summary data
// JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py
jobSocket.on('summary_complete', function(data) {
// discard socket msgs we don't care about in this context
if ($stateParams.id == data['unified_job_id']){
init();
}
});
// UnifiedJob.def socketio_emit_status() from /awx/main.models.unified_jobs.py
jobSocket.on('status_changed', function(data) {
if ($stateParams.id == data['unified_job_id']){
$scope.status = data['status'];
}
});
};
$scope.buildTooltip = function(n, status){
var grammar = function(n, status){
var dict = {
0: 'No host events were ',
@ -46,28 +64,13 @@
return n !== 0 ? n + dict[n] + status : dict[n] + status;
}
};
/*
_.forIn(count, function(value, key){
text[key] = grammar(value.length, key);
});
return {count, text}
*/
return grammar(n, status)
};
var socketListener = function(){
// emitted by the API in the same function used to persist host summary data
// JobEvent.update_host_summary_from_stats() from /awx/main.models.jobs.py
jobSocket.on('summary_complete', function(data) {
// discard socket msgs we don't care about in this context
if ($stateParams.id == data['unified_job_id']){
init()
}
});
// UnifiedJob.def socketio_emit_status() from /awx/main.models.unified_jobs.py
jobSocket.on('status_changed', function(data) {
if ($stateParams.id == data['unified_job_id']){
$scope.status = data['status'];
}
});
};
$scope.getNextPage = function(){
if ($scope.next){
JobDetailService.getNextPage($scope.next).success(function(res){
@ -80,14 +83,14 @@
}
};
$scope.search = function(){
Wait('start')
Wait('start');
JobDetailService.getJobHostSummaries($stateParams.id, {
page_size: page_size,
host_name__icontains: $scope.searchTerm,
}).success(function(res){
$scope.hosts = res.results;
$scope.next = res.next;
Wait('stop')
Wait('stop');
})
};
$scope.setFilter = function(filter){
@ -113,12 +116,12 @@
$scope.next = res.next;
});
}
var get = filter == 'all' ? getAll() : getFailed()
var get = filter == 'all' ? getAll() : getFailed();
};
$scope.$watchCollection('hosts', function(curr, prev){
$scope.tooltips = buildTooltips(curr);
DrawGraph({count: $scope.tooltips.count, resize:true});
$scope.count = buildGraph(curr);
DrawGraph({count: $scope.count, resize:true});
});
var init = function(){
@ -129,9 +132,9 @@
$scope.next = res.next;
Wait('stop');
});
JobDetailService.getJob($stateParams.id)
JobDetailService.getJob({id: $stateParams.id})
.success(function(res){
$scope.status = status;
$scope.status = res.results[0].status;
});
};
socketListener();

View File

@ -14,9 +14,9 @@
<div class="btn-group" >
<button
ng-click="setFilter('all')"
class="JobDetail-tableToggle btn btn-xs" ng-class="{'btn-default': filter == 'failed', 'btn-primary': filter == 'all'}">All</button>
class="JobDetail-tableToggle btn btn-xs" ng-class="{'btn-default': filter === 'failed', 'btn-primary': filter === 'all'}">All</button>
<button ng-click="setFilter('failed')"
ng-class="{'btn-default': filter == 'all', 'btn-primary': filter == 'failed'}" class="JobDetail-tableToggle btn btn-xs">Failed</button>
ng-class="{'btn-default': filter === 'all', 'btn-primary': filter === 'failed'}" ng-disabled='count.failures == 0' class="JobDetail-tableToggle btn btn-xs">Failed</button>
</div>
</div>
</div>
@ -40,11 +40,11 @@
<a ui-sref="jobDetail.host-events({hostName: host.host_name})" aw-tool-tip="View events" data-placement="top">{{ host.host_name }}</a>
</td>
<td class="List-tableCell col-lg-6 col-md-5 col-sm-5 col-xs-5 badge-column">
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'ok'})" aw-tool-tip="{{ tooltips.text.ok }}" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok - host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'changed'})" aw-tool-tip="{{ tooltips.text.changed }}" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'skipped'})" aw-tool-tip="{{ tooltips.text.skipped }}" data-placement="top" ng-hide="host.skipped == 0"><span class="badge skipped-hosts">{{ host.skipped }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'unreachable'})" aw-tool-tip="{{ tooltips.text.unreachable}}" data-placement="top" ng-hide="host.dark == 0"><span class="badge unreachable-hosts">{{ host.dark }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.name, filter: 'failed'})" aw-tool-tip="{{ tooltips.text.failures}}" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failures }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'ok'})" aw-tool-tip="{{ buildTooltip(host.ok - host.changed, 'ok') }}" data-placement="top" ng-hide="host.ok == 0"><span class="badge successful-hosts">{{ host.ok - host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'changed'})" aw-tool-tip="{{buildTooltip(host.changed, 'changed')}}" data-placement="top" ng-hide="host.changed == 0"><span class="badge changed-hosts">{{ host.changed }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'skipped'})" aw-tool-tip="{{buildTooltip(host.skipped, 'skipped')}}" data-placement="top" ng-hide="host.skipped == 0"><span class="badge skipped-hosts">{{ host.skipped }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.host_name, filter: 'unreachable'})" aw-tool-tip="{{buildTooltip(host.dark, 'unreachable')}" data-placement="top" ng-hide="host.dark == 0"><span class="badge unreachable-hosts">{{ host.dark }}</span></a>
<a ui-sref="jobDetail.host-events({hostName: host.name, filter: 'failed'})" aw-tool-tip="{{ buildTooltip(host.failures, 'failed')}}" data-placement="top" ng-hide="host.failed == 0"><span class="badge failed-hosts">{{ host.failures }}</span></a>
</td>
</tr>
<tr ng-show="hosts.length === 0 && status === 'pending'">

View File

@ -0,0 +1,27 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import {templateUrl} from '../../shared/template-url/template-url.factory';
export default {
name: 'jobDetail.host-summary',
resolve: {
jobSocket: ['Socket', '$rootScope', function(Socket, $rootScope) {
var job_socket = Socket({
scope: $rootScope,
endpoint: "jobs"
});
job_socket.init();
return job_socket;
}]
},
views:{
'host-summary': {
controller: 'HostSummaryController',
templateUrl: templateUrl('job-detail/host-summary/host-summary'),
}
}
};

View File

@ -0,0 +1,15 @@
/*************************************************
* Copyright (c) 2016 Ansible, Inc.
*
* All Rights Reserved
*************************************************/
import route from './host-summary.route';
import controller from './host-summary.controller';
export default
angular.module('jobDetail.hostSummary', [])
.controller('HostSummaryController', controller)
.run(['$stateExtender', function($stateExtender){
$stateExtender.addState(route);
}]);

View File

@ -413,7 +413,8 @@ export default
var params = {
order_by: 'id'
};
JobDetailService.getJobPlays(scope.job.id, params)
if (scope.job.summary_fields.unified_job_template.unified_job_type == 'job'){
JobDetailService.getJobPlays(scope.job.id, params)
.success( function(data) {
scope.next_plays = data.next;
if (data.results.length > 0) {
@ -498,6 +499,7 @@ export default
}
scope.$emit('LoadTasks', events_url);
});
}
});
@ -513,9 +515,10 @@ export default
scope.hostResultsLoading = true;
// Load the job record
JobDetailService.getJob(job_id)
.success(function(data) {
var i;
JobDetailService.getJob({id: job_id})
.success(function(res) {
var i,
data = res.results[0];
scope.job = data;
scope.job_template_name = data.name;
scope.project_name = (data.summary_fields.project) ? data.summary_fields.project.name : '';

View File

@ -157,7 +157,7 @@
<!--- end of results-->
<!--beginning of details-->
<div id="job-detail-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen">
<div id="job-detail-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen && job.summary_fields.unified_job_template.unified_job_type == 'job'">
<div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessDetail" href="" ng-click="toggleLessDetail()">
@ -376,21 +376,21 @@
<div class="JobDetail-rightSide">
<!--beginning of events summary-->
<div id="events-summary-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen">
<div id="events-summary-panel" class="JobDetail-resultsContainer Panel" ng-show="!stdoutFullScreen && job.summary_fields.unified_job_template.unified_job_type == 'job'">
<div class="JobDetail-panelHeader">
<div class="JobDetail-expandContainer">
<a class="JobDetail-panelHeaderText" ng-show="lessEvents" href="" ng-click="toggleLessEvents()">
<a class="JobDetail-panelHeaderText" ng-show="lessEvents" ui-sref="jobDetail.host-summary" ng-click="toggleLessEvents()">
EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-left"></i>
</a>
<a class="JobDetail-panelHeaderText" ng-show="!lessEvents" href="" ng-click="toggleLessEvents()">
<a class="JobDetail-panelHeaderText" ng-show="!lessEvents" ui-sref="jobDetail" ng-click="toggleLessEvents()">
EVENT SUMMARY<i class="JobDetail-expandArrow fa fa-caret-down"></i>
</a>
</div>
</div>
<!-- Host Summary view -->
<div id="events-summary" ng-hide="lessEvents">
<div ui-view="host-summary@jobDetail"></div>
<div id="events-summary" ng-hide="lessEvents">
<div ui-view="host-summary"></div>
</div>
</div>
<!-- end of events summary-->

View File

@ -38,19 +38,9 @@ export default {
endpoint: "jobs"
});
job_socket.init();
// returns should really be providing $rootScope.job_socket
// otherwise, we have to inject the entire $rootScope into the controller
return job_socket;
}]
},
views: {
'': {
templateUrl: templateUrl('job-detail/job-detail'),
controller: 'JobDetailController',
},
'host-summary@jobDetail': {
templateUrl: templateUrl('job-detail/host-summary/host-summary'),
controller: HostSummaryController
}
}
templateUrl: templateUrl('job-detail/job-detail'),
controller: 'JobDetailController'
};

View File

@ -2,13 +2,10 @@ export default
['$rootScope', 'Rest', 'GetBasePath', 'ProcessErrors', function($rootScope, Rest, GetBasePath, ProcessErrors){
return {
/*
* For ES6
* it might be useful to set some default params here, e.g.
* getJobHostSummaries: function(id, page_size=200, order='host_name'){}
* without ES6, we'd have to supply defaults like this:
* this.page_size = params.page_size ? params.page_size : 200;
*/
stringifyParams: function(params){
return _.reduce(params, (result, value, key) => {
return result + key + '=' + value + '&'}, '');
},
// the the API passes through Ansible's event_data response
// we need to massage away the verbose and redundant properties
@ -129,12 +126,7 @@ export default
// ?parent=206&event__startswith=runner&page_size=200&order=host_name,counter
getRelatedJobEvents: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_events/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
// ?&event=playbook_on_start == ?event=playbook_on_stats
url = url + '&' + key + '=' + params[key];
});
url = url + id + '/job_events/?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
@ -162,11 +154,7 @@ export default
// e.g. ?page_size=200&order=host_name
getJobHostSummaries: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_host_summaries/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
url = url + '&' + key + '=' + params[key];
});
url = url + id + '/job_host_summaries/?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
@ -181,11 +169,7 @@ export default
// e.g. ?page_size=200
getJobPlays: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_plays/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
url = url + '&' + key + '=' + params[key];
});
url = url + id + '/job_plays/?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
@ -198,11 +182,7 @@ export default
},
getJobTasks: function(id, params){
var url = GetBasePath('jobs');
url = url + id + '/job_tasks/?';
Object.keys(params).forEach(function(key, index) {
// the API is tolerant of extra ampersands
url = url + '&' + key + '=' + params[key];
});
url = url + id + '/job_tasks/?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){
@ -213,9 +193,8 @@ export default
msg: 'Call to ' + url + '. GET returned: ' + status });
});
},
getJob: function(id){
var url = GetBasePath('jobs');
url = url + id;
getJob: function(params){
var url = GetBasePath('unified_jobs') + '?' + this.stringifyParams(params);
Rest.setUrl(url);
return Rest.get()
.success(function(data){

View File

@ -9,11 +9,13 @@ import controller from './job-detail.controller';
import service from './job-detail.service';
import hostEvents from './host-events/main';
import hostEvent from './host-event/main';
import hostSummary from './host-summary/main';
export default
angular.module('jobDetail', [
hostEvents.name,
hostEvent.name
hostEvent.name,
hostSummary.name
])
.controller('JobDetailController', controller)
.service('JobDetailService', service)