extend JobDetailService to process job_event host results, resolves #1410

This commit is contained in:
Leigh Johnson
2016-04-06 15:00:54 -04:00
parent 6150addc2e
commit a8eaf8cd9c
8 changed files with 359 additions and 316 deletions

View File

@@ -822,7 +822,6 @@ export default
url += (scope.search_task_name) ? '&task__icontains=' + scope.search_task_name : ''; url += (scope.search_task_name) ? '&task__icontains=' + scope.search_task_name : '';
url += (scope.search_task_status === 'failed') ? '&failed=true' : ''; url += (scope.search_task_status === 'failed') ? '&failed=true' : '';
url += '&page_size=' + scope.tasksMaxRows + '&order=id'; url += '&page_size=' + scope.tasksMaxRows + '&order=id';
scope.plays.every(function(p, idx) { scope.plays.every(function(p, idx) {
if (p.id === scope.selectedPlay) { if (p.id === scope.selectedPlay) {
play = scope.plays[idx]; play = scope.plays[idx];
@@ -931,7 +930,7 @@ export default
}]) }])
// Call when the selected task needs to change // Call when the selected task needs to change
.factory('SelectTask', ['LoadHosts', function(LoadHosts) { .factory('SelectTask', ['LoadHosts', 'JobDetailService', function(LoadHosts, JobDetailService) {
return function(params) { return function(params) {
var scope = params.scope, var scope = params.scope,
id = params.id, id = params.id,
@@ -946,11 +945,16 @@ export default
scope.tasks[idx].taskActiveClass = ''; scope.tasks[idx].taskActiveClass = '';
} }
}); });
// /api/v1/jobs/16/job_events/?parent=832&event__startswith=runner&page_size=200&order=host_name,counte
LoadHosts({ var params = {
scope: scope, parent: scope.selectedTask,
callback: callback, event__startswith: 'runner',
clear: true page_size: scope.hostResultsMaxRows,
order: 'host_name,counter',
};
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostResults(res.results)
scope.hostResultsLoading = false;
}); });
}; };
}]) }])
@@ -961,7 +965,6 @@ export default
var scope = params.scope, var scope = params.scope,
callback = params.callback, callback = params.callback,
url; url;
scope.hostResults = []; scope.hostResults = [];
if (scope.selectedTask) { if (scope.selectedTask) {
@@ -970,6 +973,7 @@ export default
url += (scope.search_host_name) ? 'host__name__icontains=' + scope.search_host_name + '&' : ''; url += (scope.search_host_name) ? 'host__name__icontains=' + scope.search_host_name + '&' : '';
url += (scope.search_host_status === 'failed') ? 'failed=true&' : ''; url += (scope.search_host_status === 'failed') ? 'failed=true&' : '';
url += 'event__startswith=runner&page_size=' + scope.hostResultsMaxRows + '&order=host_name,counter'; url += 'event__startswith=runner&page_size=' + scope.hostResultsMaxRows + '&order=host_name,counter';
console.log(url)
scope.hostResultsLoading = true; scope.hostResultsLoading = true;
Rest.setUrl(url); Rest.setUrl(url);
Rest.get() Rest.get()
@@ -1020,7 +1024,6 @@ export default
msg = event.event_data.res; msg = event.event_data.res;
} }
} }
if (event.event !== "runner_on_no_hosts") { if (event.event !== "runner_on_no_hosts") {
scope.hostResults.push({ scope.hostResults.push({
id: event.id, id: event.id,
@@ -1037,7 +1040,6 @@ export default
}); });
scope.hostResultsLoading = false; scope.hostResultsLoading = false;
if (callback) { if (callback) {
scope.$emit(callback); scope.$emit(callback);
} }

View File

@@ -12,9 +12,9 @@
<span class="HostEvent-field--label">STATUS</span> <span class="HostEvent-field--label">STATUS</span>
<span class="HostEvent-field--content"> <span class="HostEvent-field--content">
<a class="HostEvents-status"> <a class="HostEvents-status">
<i class="fa fa-circle" ng-class="processEventStatus"></i> <i class="fa fa-circle" ng-class="processEventStatus(event).class"></i>
</a> </a>
{{event.status || "No result found"}} {{processEventStatus(event).status || "No result found"}}
</span> </span>
</div> </div>
<div class="HostEvent-field"> <div class="HostEvent-field">

View File

@@ -14,8 +14,7 @@
<!-- view navigation buttons --> <!-- view navigation buttons -->
<button ui-sref="jobDetail.host-event.details" type="button" class="btn btn-sm btn-default" >Details</button> <button ui-sref="jobDetail.host-event.details" type="button" class="btn btn-sm btn-default" >Details</button>
<button ui-sref="jobDetail.host-event.json" type="button" class="btn btn-sm btn-default ">JSON</button> <button ui-sref="jobDetail.host-event.json" type="button" class="btn btn-sm btn-default ">JSON</button>
<button ng-show="event.stdout" ui-sref="jobDetail.host-event.stdout" type="button" class="btn btn-sm btn-default ">Standard Out</button> <button ng-show="stdout" ui-sref="jobDetail.host-event.stdout" type="button" class="btn btn-sm btn-default ">Standard Out</button>
<button ng-show="event.timing" ui-sref="jobDetail.host-event.timing" type="button" class="btn btn-sm btn-default ">Timing</button>
</div> </div>
<div class="HostEvent-body"> <div class="HostEvent-body">

View File

@@ -1,13 +1,2 @@
<div class="EventHost-stdoutPanel Panel"> <textarea id="HostEvent-stdout" class="HostEvent-stdout">
<div class="StandardOut-panelHeader"> </textarea>
<div class="StandardOut-panelHeaderText">STANDARD OUT</div>
<div class="StandardOut-panelHeaderActions">
<a href="/api/v1/jobs/{{ job.id }}/stdout?format=txt_download&token={{ token }}">
<button class="StandardOut-actionButton" aw-tool-tip="Download Output" data-placement="top">
<i class="fa fa-download"></i>
</button>
</a>
</div>
</div>
<standard-out-log stdout-endpoint="event._stdout"></standard-out-log>
</div>

View File

@@ -4,67 +4,77 @@
* All Rights Reserved * All Rights Reserved
*************************************************/ *************************************************/
export default export default
['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'moment', 'event', ['$stateParams', '$scope', '$state', 'Wait', 'JobDetailService', 'moment', 'event',
function($stateParams, $scope, $state, Wait, JobDetailService, moment, event){ function($stateParams, $scope, $state, Wait, JobDetailService, moment, event){
$scope.processEventStatus = JobDetailService.processEventStatus;
// Avoid rendering objects in the details fieldset // Avoid rendering objects in the details fieldset
// ng-if="processResults(value)" via host-event-details.partial.html // ng-if="processResults(value)" via host-event-details.partial.html
$scope.processResults = function(value){ $scope.processResults = function(value){
if (typeof value == 'object'){return false} if (typeof value == 'object'){return false;}
else {return true} else {return true;}
}; };
var codeMirror = function(){ var codeMirror = function(el, json){
var el = $('#HostEvent-json')[0]; var el = $(el)[0];
var editor = CodeMirror.fromTextArea(el, { var editor = CodeMirror.fromTextArea(el, {
lineNumbers: true, lineNumbers: true,
mode: {name: "javascript", json: true} mode: {name: "javascript", json: true}
}); });
editor.getDoc().setValue(JSON.stringify($scope.json, null, 4)); editor.getDoc().setValue(JSON.stringify(json, null, 4));
}; };
$scope.getActiveHostIndex = function(){ $scope.getActiveHostIndex = function(){
var result = $scope.hostResults.filter(function( obj ) { var result = $scope.hostResults.filter(function( obj ) {
return obj.id == $scope.event.id; return obj.id == $scope.event.id;
}); });
return $scope.hostResults.indexOf(result[0]) return $scope.hostResults.indexOf(result[0]);
}; };
$scope.showPrev = function(){ $scope.showPrev = function(){
return $scope.getActiveHostIndex() != 0 return $scope.getActiveHostIndex() != 0;
}; };
$scope.showNext = function(){ $scope.showNext = function(){
return $scope.getActiveHostIndex() < $scope.hostResults.indexOf($scope.hostResults[$scope.hostResults.length - 1]) return $scope.getActiveHostIndex() < $scope.hostResults.indexOf($scope.hostResults[$scope.hostResults.length - 1]);
}; };
$scope.goNext = function(){ $scope.goNext = function(){
var index = $scope.getActiveHostIndex() + 1; var index = $scope.getActiveHostIndex() + 1;
var id = $scope.hostResults[index].id; var id = $scope.hostResults[index].id;
$state.go('jobDetail.host-event.details', {eventId: id}) $state.go('jobDetail.host-event.details', {eventId: id});
}; };
$scope.goPrev = function(){ $scope.goPrev = function(){
var index = $scope.getActiveHostIndex() - 1; var index = $scope.getActiveHostIndex() - 1;
var id = $scope.hostResults[index].id; var id = $scope.hostResults[index].id;
$state.go('jobDetail.host-event.details', {eventId: id}) $state.go('jobDetail.host-event.details', {eventId: id});
}; };
var init = function(){ var init = function(){
console.log(event)
$scope.event = event.data.results[0]; $scope.event = event.data.results[0];
$scope.event.created = moment($scope.event.created).format(); $scope.event.created = moment($scope.event.created).format();
$scope.processEventStatus = JobDetailService.processEventStatus($scope.event);
$scope.hostResults = $stateParams.hostResults; $scope.hostResults = $stateParams.hostResults;
$scope.json = JobDetailService.processJson($scope.event); $scope.json = JobDetailService.processJson($scope.event);
if ($state.current.name == 'jobDetail.host-event.json'){ if ($state.current.name == 'jobDetail.host-event.json'){
codeMirror(); codeMirror('#HostEvent-json', $scope.json);
} }
try { try {
$scope.stdout = $scope.event.event_data.res.stdout $scope.stdout = JobDetailService.processJson($scope.event.event_data.res)
console.log($scope.stdout)
if ($state.current.name == 'jobDetail.host-event.stdout'){
codeMirror('#HostEvent-stdout', $scope.stdout);
}
} }
catch(err){ catch(err){
$scope.sdout = null; $scope.sdout = null;
} }
console.log($scope)
$('#HostEvent').modal('show'); $('#HostEvent').modal('show');
}; };
init(); init();

View File

@@ -36,9 +36,9 @@
<td class=HostEvents-table--cell> <td class=HostEvents-table--cell>
<!-- status circles --> <!-- status circles -->
<a class="HostEvents-status"> <a class="HostEvents-status">
<i class="fa fa-circle" ng-class="processEventStatus(event)"></i> <i class="fa fa-circle" ng-class="processEventStatus(event).class"></i>
</a> </a>
{{event.status}} {{processEventStatus(event).status}}
</td> </td>
<td class=HostEvents-table--cell>{{event.play}}</td> <td class=HostEvents-table--cell>{{event.play}}</td>
<td class=HostEvents-table--cell>{{event.task}}</td> <td class=HostEvents-table--cell>{{event.task}}</td>

View File

@@ -16,7 +16,7 @@ export default
'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed',
'DrawGraph', 'LoadHostSummary', 'ReloadHostSummaryList', 'DrawGraph', 'LoadHostSummary', 'ReloadHostSummaryList',
'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun', 'JobIsFinished', 'SetTaskStyles', 'DigestEvent', 'UpdateDOM', 'DeleteJob', 'PlaybookRun',
'LoadPlays', 'LoadTasks', 'LoadHosts', 'HostsEdit', 'LoadPlays', 'LoadTasks', 'HostsEdit',
'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels', 'ParseVariableString', 'GetChoices', 'fieldChoices', 'fieldLabels',
'EditSchedule', 'ParseTypeChange', 'JobDetailService', 'EditSchedule', 'ParseTypeChange', 'JobDetailService',
function( function(
@@ -25,7 +25,7 @@ export default
SelectPlay, SelectTask, Socket, GetElapsed, DrawGraph, SelectPlay, SelectTask, Socket, GetElapsed, DrawGraph,
LoadHostSummary, ReloadHostSummaryList, JobIsFinished, LoadHostSummary, ReloadHostSummaryList, JobIsFinished,
SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob, SetTaskStyles, DigestEvent, UpdateDOM, DeleteJob,
PlaybookRun, LoadPlays, LoadTasks, LoadHosts, PlaybookRun, LoadPlays, LoadTasks,
HostsEdit, ParseVariableString, GetChoices, fieldChoices, HostsEdit, ParseVariableString, GetChoices, fieldChoices,
fieldLabels, EditSchedule, ParseTypeChange, JobDetailService fieldLabels, EditSchedule, ParseTypeChange, JobDetailService
) { ) {
@@ -376,64 +376,26 @@ export default
var params = { var params = {
parent: task.id, parent: task.id,
event__startswith: 'runner', event__startswith: 'runner',
page_size: scope.hostResultsMaxRows
}; };
JobDetailService.getRelatedJobEvents(scope.job.id, params) JobDetailService.getRelatedJobEvents(scope.job.id, params)
.success(function(data) { .success(function(data) {
console.log(data)
var idx, event, status, status_text, item, msg; var idx, event, status, status_text, item, msg;
if (data.results.length > 0) { if (data.results.length > 0) {$
lastEventId = data.results[0].id; lastEventId = data.results[0].id;
} }
scope.next_host_results = data.next; scope.next_host_results = data.next;
for (idx=data.results.length - 1; idx >= 0; idx--) { for (idx=data.results.length - 1; idx >= 0; idx--) {
event = data.results[idx]; event = data.results[idx];
if (event.event === "runner_on_skipped") { event.status = JobDetailService.processEventStatus(event).status;
status = 'skipped'; msg = JobDetailService.processEventMsg(event);
} item = JobDetailService.processEventItem(event);
else if (event.event === "runner_on_unreachable") {
status = 'unreachable';
}
else {
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
}
switch(status) {
case "successful":
status_text = 'OK';
break;
case "changed":
status_text = "Changed";
break;
case "failed":
status_text = "Failed";
break;
case "unreachable":
status_text = "Unreachable";
break;
case "skipped":
status_text = "Skipped";
}
if (event.event_data && event.event_data.res) { if (event.event !== "runner_on$_no_hosts") {
item = event.event_data.res.item;
if (typeof item === "object") {
item = JSON.stringify(item);
}
}
msg = '';
if (event.event_data && event.event_data.res) {
if (typeof event.event_data.res === 'object') {
msg = event.event_data.res.msg;
} else {
msg = event.event_data.res;
}
}
if (event.event !== "runner_on_no_hosts") {
task.hostResults[event.id] = { task.hostResults[event.id] = {
id: event.id, id: event.id,
status: status, status: event.status,
status_text: status_text, status_text: event.status.toUpperCase(),
host_id: event.host, host_id: event.host,
task_id: event.parent, task_id: event.parent,
name: event.event_data.host, name: event.event_data.host,
@@ -1037,6 +999,16 @@ export default
scope.searchTasksEnabled = true; scope.searchTasksEnabled = true;
} }
if (!scope.liveEventProcessing || scope.pauseLiveEvents) { if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
// /api/v1/jobs/15/job_tasks/?event_id=762&task__icontains=create&page_size=200&order=id
var params = {
event_id: scope.selectedPlay,
task__icontains: scope.search_task_name,
page_size: scope.tasksMaxRows,
};
if (scope.search_task_status === 'failed'){
params.failed = true;
}
LoadTasks({ LoadTasks({
scope: scope scope: scope
}); });
@@ -1058,8 +1030,20 @@ export default
scope.searchHostsEnabled = true; scope.searchHostsEnabled = true;
} }
if (!scope.liveEventProcessing || scope.pauseLiveEvents) { if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadHosts({ scope.hostResultsLoading = true;
scope: scope var params = {
parent: scope.selectedTask,
event__startswith: 'runner',
page_size: scope.hostResultsMaxRows,
order: 'host_name,counter',
host_name__icontains: scope.search_host_name
}
if (scope.search_host_status === 'failed'){
params.failed = true;
}
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostResults(res.results)
scope.hostResultsLoading = false;
}); });
} }
}; };
@@ -1104,8 +1088,20 @@ export default
scope.filterHostStatus = function() { scope.filterHostStatus = function() {
scope.search_host_status = (scope.search_host_status === 'all') ? 'failed' : 'all'; scope.search_host_status = (scope.search_host_status === 'all') ? 'failed' : 'all';
if (!scope.liveEventProcessing || scope.pauseLiveEvents) { if (!scope.liveEventProcessing || scope.pauseLiveEvents) {
LoadHosts({ console.log('filterHostStattus', scope)
scope: scope var params = {
parent: scope.selectedTask,
event__startswith: 'runner',
page_size: scope.hostResultsMaxRows,
order: 'host_name,counter'
}
if (scope.search_host_status === 'failed'){
params.failed = true;
}
scope.hostResultsLoading = true;
JobDetailService.getRelatedJobEvents(scope.job.id, params).success(function(res){
scope.hostResults = JobDetailService.processHostResults(res.results)
scope.hostResultsLoading = false;
}); });
} }
}; };

View File

@@ -3,16 +3,15 @@ export default
return { return {
/* /*
For ES6 * For ES6
it might be useful to set some default params here, e.g. * it might be useful to set some default params here, e.g.
getJobHostSummaries: function(id, page_size=200, order='host_name'){} * getJobHostSummaries: function(id, page_size=200, order='host_name'){}
without ES6, we'd have to supply defaults like this: * without ES6, we'd have to supply defaults like this:
this.page_size = params.page_size ? params.page_size : 200; * this.page_size = params.page_size ? params.page_size : 200;
*/ */
// the the API passes through Ansible's event_data response // the the API passes through Ansible's event_data response
// we need to massage away the verbose and redundant properties // we need to massage away the verbose and redundant properties
processJson: function(data){ processJson: function(data){
// a deep copy // a deep copy
var result = $.extend(true, {}, data); var result = $.extend(true, {}, data);
@@ -44,44 +43,92 @@ export default
} }
}); });
} }
catch(err){result.event_data = null;} catch(err){result.event_data = undefined;}
return result return result
}, },
// Return Ansible's passed-through response msg on a job_event
processEventStatus: function(event){ processEventMsg: function(event){
return typeof event.event_data.res === 'object' ? event.event_data.res.msg : event.event_data.res;
},
// Return only Ansible's passed-through response item on a job_event
processEventItem: function(event){
try{
var item = event.event_data.res.item;
return typeof item === 'object' ? JSON.stringify(item) : item
}
catch(err){return;}
},
// Generate a helper class for job_event statuses // Generate a helper class for job_event statuses
// the stack for which status to display is // the stack for which status to display is
// unreachable > failed > changed > ok // unreachable > failed > changed > ok
// uses the API's runner events and convenience properties .failed .changed to determine status. // uses the API's runner events and convenience properties .failed .changed to determine status.
// see: job_event_callback.py // see: job_event_callback.py
processEventStatus: function(event){
if (event.event == 'runner_on_unreachable'){ if (event.event == 'runner_on_unreachable'){
event.status = 'Unreachable'; return {
return 'HostEvents-status--unreachable' class: 'HostEvents-status--unreachable',
status: 'unreachable'
}
} }
// equiv to 'runner_on_error' && 'runner on failed' // equiv to 'runner_on_error' && 'runner on failed'
if (event.failed){ if (event.failed){
event.status = 'Failed'; return {
return 'HostEvents-status--failed' class: 'HostEvents-status--failed',
status: 'failed'
}
} }
// catch the changed case before ok, because both can be true // catch the changed case before ok, because both can be true
if (event.changed){ if (event.changed){
event.status = 'Changed'; return {
return 'HostEvents-status--changed' class: 'HostEvents-status--changed',
status: 'changed'
}
} }
if (event.event == 'runner_on_ok'){ if (event.event == 'runner_on_ok'){
event.status = 'OK'; return {
return 'HostEvents-status--ok' class: 'HostEvents-status--ok',
status: 'ok'
}
} }
if (event.event == 'runner_on_skipped'){ if (event.event == 'runner_on_skipped'){
event.status = 'Skipped'; return {
return 'HostEvents-status--skipped' class: 'HostEvents-status--skipped',
status: 'skipped'
}
} }
else{ else{
// study a case where none of these apply // study a case where none of these apply
} }
}, },
// Consumes a response from this.getRelatedJobEvents
// returns an array for view logic to iterate over to build host result rows
processHostResults: function(data){
var self = this;
var results = [];
data.forEach(function(element, index, array){
var event = element;
if (event.event !== 'runner_on_no_hosts'){
var status = self.processEventStatus(event);
var msg = self.processEventMsg(event);
var item = self.processEventItem(event);
results.push({
id: event.id,
status: status.status,
status_text: status.status.charAt(0).toUpperCase() + status.status.slice(1),
host_id: event.host,
task_id: event.parent,
name: event.event_data.host,
created: event.created,
msg: typeof msg === 'undefined' ? undefined : msg,
item: typeof item === 'undefined' ? undefined : item
});
}
});
return results;
},
// GET events related to a job run // GET events related to a job run
// e.g. // e.g.
// ?event=playbook_on_stats // ?event=playbook_on_stats