Job detail page refactor

First pass at aggregating all incoming events without any DOM updates. Lets see if we can just perform simple aggregration in memory without a CPU meltdown.
This commit is contained in:
Chris Houseknecht 2014-06-19 16:08:55 -04:00
parent 4eddd692fb
commit 958e806ddc
2 changed files with 254 additions and 219 deletions

View File

@ -8,8 +8,8 @@
'use strict';
function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log, ClearScope, Breadcrumbs, LoadBreadCrumbs, GetBasePath, Wait, Rest,
ProcessErrors, ProcessEventQueue, SelectPlay, SelectTask, Socket, GetElapsed, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList,
JobIsFinished, SetTaskStyles) {
ProcessErrors, SelectPlay, SelectTask, Socket, GetElapsed, FilterAllByHostName, DrawGraph, LoadHostSummary, ReloadHostSummaryList,
JobIsFinished, SetTaskStyles, DigestEvent) {
ClearScope();
@ -18,8 +18,7 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
scope = $scope,
api_complete = false,
refresh_count = 0,
lastEventId = 0,
queue = [];
lastEventId = 0;
scope.plays = [];
scope.playsMap = {};
@ -31,8 +30,8 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
scope.hostResultsMap = {};
api_complete = false;
scope.hostTableRows = 75;
scope.hostSummaryTableRows = 75;
scope.hostResultsMaxRows = 75;
scope.hostSummariesMaxRows = 75;
scope.tasksMaxRows = 75;
scope.playsMaxRows = 75;
@ -54,6 +53,8 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
scope.host_summary.failed = 0;
scope.host_summary.total = 0;
scope.jobData = {};
scope.eventsHelpText = "<p><i class=\"fa fa-circle successful-hosts-color\"></i> Successful</p>\n" +
"<p><i class=\"fa fa-circle changed-hosts-color\"></i> Changed</p>\n" +
"<p><i class=\"fa fa-circle unreachable-hosts-color\"></i> Unreachable</p>\n" +
@ -68,23 +69,9 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
event_socket.init();
event_socket.on("job_events-" + job_id, function(data) {
data.event = data.event_name;
if (api_complete && data.id > lastEventId) {
if (queue.length < 25) {
$log.debug('received event: ' + data.id);
queue.unshift(data);
}
else {
api_complete = false; // stop more events from hitting the queue
window.clearInterval($rootScope.jobDetailInterval);
$log.debug('halting queue. reloading...');
setTimeout(function() {
$log.debug('reload');
scope.haltEventQueue = true;
queue = [];
scope.$emit('LoadJob');
}, 300);
}
data.event = data.event_name;
DigestEvent({ scope: scope, event: data });
}
});
@ -99,37 +86,17 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
if (data.status === 'failed' || data.status === 'canceled' ||
data.status === 'error' || data.status === 'successful') {
$log.debug('Job completed!');
api_complete = false;
scope.haltEventQueue = true;
window.clearInterval($rootScope.jobDetailInterval);
queue = [];
scope.$emit('LoadJob');
$log.debug(scope.jobData);
}
}
});
if (scope.removeAPIComplete) {
scope.removeAPIComplete();
if (scope.removeInitialLoadComplete) {
scope.removeInitialLoadComplete();
}
scope.removeAPIComplete = scope.$on('APIComplete', function() {
// process any events sitting in the queue
var keys, url, hostId = 0, taskId = 0, playId = 0;
// Find the max event.id value in memory
hostId = (scope.hostResults.length > 0) ? scope.hostResults[scope.hostResults.length - 1].id : 0;
if (scope.hostResults.length > 0) {
keys = Object.keys(scope.hostResults);
keys.sort();
hostId = keys[keys.length - 1];
}
taskId = (scope.tasks.length > 0) ? scope.tasks[scope.tasks.length - 1].id : 0;
playId = (scope.plays.length > 0) ? scope.plays[scope.plays.length - 1].id : 0;
lastEventId = Math.max(hostId, taskId, playId);
Wait('stop');
// Draw the graph
scope.removeInitialLoadComplete = scope.$on('InitialLoadComplete', function() {
var url;
if (JobIsFinished(scope)) {
url = scope.job.related.job_events + '?event=playbook_on_stats';
Rest.setUrl(url);
@ -141,58 +108,182 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
data: data.results[0].event_data
});
DrawGraph({ scope: scope, resize: true });
Wait('stop');
}
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
$log.debug(scope.jobData);
}
else {
if (scope.host_summary.total > 0) {
// Draw the graph based on summary values in memory
DrawGraph({ scope: scope, resize: true });
}
api_complete = true; //trigger events to start processing
}
});
if (scope.removeLoadHostSummaries) {
scope.removeLoadHostSummaries();
}
scope.removeHostSummaries = scope.$on('LoadHostSummaries', function() {
var url = scope.job.related.job_host_summaries + '?';
url += '&page_size=' + scope.hostSummariesMaxRows + '&order_by=host__name';
scope.jobData.hostSummaries = {};
Rest.setUrl(url);
Rest.get()
.success(function(data) {
data.results.forEach(function(event) {
scope.jobData.hostSummaries[event.id] = {
id: event.host,
name: event.summary_fields.host.name,
ok: event.ok,
changed: event.changed,
unreachable: event.dark,
failed: event.failures
};
});
scope.$emit('InitialLoadComplete');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
});
if (scope.removeLoadHosts) {
scope.removeLoadHosts();
}
scope.removeLoadHosts = scope.$on('LoadHosts', function() {
if (scope.activeTask) {
var play = scope.jobData.plays[scope.activePlay],
task = play.tasks[scope.activeTask],
url;
url = scope.job.related.job_events + '?parent=' + task.id + '&';
url += 'host__isnull=false&page_size=' + scope.hostResultsMaxRows + '&order_by=-host__name';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
var idx, event;
if (data.results.length > 0) {
lastEventId = data.results[0].id;
}
for (idx=data.results.length - 1; idx >= 0; idx--) {
event = data.results[idx];
task.hostResults[event.id] = {
id: event.id,
status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ),
host_id: event.host,
task_id: event.parent,
name: event.event_data.host,
created: event.created,
msg: ( (event.event_data && event.event_data.res) ? event.event_data.res.msg : '' )
};
}
scope.$emit('LoadHostSummaries');
})
.error(function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
else {
if (scope.host_summary.total > 0) {
// Draw the graph based on summary values in memory
DrawGraph({ scope: scope, resize: true });
}
api_complete = true;
scope.haltEventQueue = false;
ProcessEventQueue({
scope: scope,
eventQueue: queue
});
scope.jobData.hostSummaries = {};
scope.$emit('InitialLoadComplete');
}
});
if (scope.removeInitialDataLoaded) {
scope.removeInitialDataLoaded();
if (scope.removeLoadTasks) {
scope.removeLoadTasks();
}
scope.removeInitialDataLoaded = scope.$on('InitialDataLoaded', function() {
// Load data for the host summary table
if (!api_complete) {
ReloadHostSummaryList({
scope: scope,
callback: 'APIComplete'
});
scope.removeLoadTasks = scope.$on('LoadTasks', function() {
if (scope.activePlay) {
var play = scope.jobData.plays[scope.activePlay], url;
url = scope.job.url + 'job_tasks/?event_id=' + play.id;
url += '&page_size=' + scope.tasksMaxRows + '&order_by=-id';
Rest.setUrl(url);
Rest.get()
.success(function(data) {
var idx, end, elapsed, event;
if (data.results.length > 0) {
lastEventId = data.results[0].id;
scope.activeTask = data.results[0].id;
}
for (idx=data.results.length - 1; idx >= 0; idx--) {
event = data.results[idx];
if (play.firstTask === null) {
play.firstTask = event.id;
play.hostCount = (event.host_count) ? event.host_count : 0;
}
if (idx < data.length - 1) {
// end date = starting date of the next event
end = data[idx + 1].created;
}
else {
// no next event (task), get the end time of the play
end = scope.jobData.plays[scope.activePlay].finished;
}
if (end) {
elapsed = GetElapsed({
start: event.created,
end: end
});
}
else {
elapsed = '00:00:00';
}
play.tasks[event.id] = {
id: event.id,
play_id: scope.activePlay,
name: event.name,
status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ),
created: event.created,
modified: event.modified,
finished: end,
elapsed: elapsed,
hostCount: (event.host_count) ? event.host_count : 0,
reportedHosts: (event.reported_hosts) ? event.reported_hosts : 0,
successfulCount: (event.successful_count) ? event.successful_count : 0,
failedCount: (event.failed_count) ? event.failed_count : 0,
changedCount: (event.changed_count) ? event.changed_count : 0,
skippedCount: (event.skipped_count) ? event.skipped_count : 0,
taskActiveClass: '',
hostResults: {}
};
SetTaskStyles({
scope: scope,
task_id: event.id
});
}
scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].taskActiveClass = 'active';
scope.$emit('LoadHosts');
})
.error(function(data) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
msg: 'Call to ' + url + '. GET returned: ' + status });
});
}
else {
scope.jobData.hostSummaries = {};
scope.$emit('InitialLoadComplete');
}
});
if (scope.removePlaysReady) {
scope.removePlaysReady();
if (scope.removeLoadPlays) {
scope.removeLoadPlays();
}
scope.removePlaysReady = scope.$on('PlaysReady', function() {
// Select the most recent play, which will trigger tasks and hosts to load
SelectPlay({
scope: scope,
id: ((scope.plays.length > 0) ? scope.plays[scope.plays.length - 1].id : null ),
callback: 'InitialDataLoaded'
});
});
if (scope.removeLoadJobDetails) {
scope.removeLoadJobDetails();
}
scope.removeRefreshJobDetails = scope.$on('LoadJobDetails', function(e, events_url) {
// Call to load all the job bits including, plays, tasks, hosts results and host summary
scope.removeLoadPlays = scope.$on('LoadPlays', function(e, events_url) {
scope.host_summary.ok = 0;
scope.host_summary.changed = 0;
@ -200,14 +291,22 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
scope.host_summary.failed = 0;
scope.host_summary.total = 0;
scope.jobData.plays = {};
var url = scope.job.url + 'job_plays/?order_by=id';
url += '&page_size=' + scope.playsMaxRows + '&order_by=-id';
Rest.setUrl(url);
Rest.get()
.success( function(data) {
data.forEach(function(event, idx) {
var status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful',
start = event.started,
end, elapsed, play;
var idx, event, status, start, end, elapsed;
if (data.length > 0) {
scope.activePlay = data[0].id;
}
for (idx=data.length - 1; idx >= 0; idx--) {
event = data[idx];
status = (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful';
start = event.started;
if (idx < data.length - 1) {
// end date = starting date of the next event
@ -227,29 +326,17 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
elapsed = '00:00:00';
}
if (scope.playsMap[event.id] !== undefined) {
play = scope.plays[scope.playsMap[event.id]];
play.finished = end;
play.status = status;
play.elapsed = elapsed;
play.playActiveClass = '';
}
else {
scope.plays.push({
id: event.id,
name: event.play,
created: start,
finished: end,
status: status,
elapsed: elapsed,
playActiveClass: '',
hostCount: 0,
fistTask: null
});
if (scope.plays.length > scope.playsMaxRows) {
scope.plays.shift();
}
}
scope.jobData.plays[event.id] = {
id: event.id,
name: event.play,
created: start,
finished: end,
status: status,
elapsed: elapsed,
hostCount: 0,
fistTask: null,
tasks: {}
};
scope.host_summary.ok += (data.ok_count) ? data.ok_count : 0;
scope.host_summary.changed += (data.changed_count) ? data.changed_count : 0;
@ -257,16 +344,9 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
scope.host_summary.failed += (data.failed_count) ? data.failed_count : 0;
scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed +
scope.host_summary.unreachable + scope.host_summary.failed;
});
//rebuild the index
scope.playsMap = {};
scope.plays.forEach(function(play, idx) {
scope.playsMap[play.id] = idx;
});
scope.$emit('PlaysReady', events_url);
scope.$emit('FixPlaysScroll');
}
scope.$emit('LoadTasks', events_url);
//scope.$emit('FixPlaysScroll');
})
.error( function(data, status) {
ProcessErrors(scope, data, status, null, { hdr: 'Error!',
@ -352,7 +432,7 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
scope.job_status.elapsed = '00:00:00';
}
scope.setSearchAll('host');
scope.$emit('LoadJobDetails', data.related.job_events);
scope.$emit('LoadPlays', data.related.job_events);
if (!scope.credential_name) {
scope.$emit('GetCredentialNames', data);
}
@ -972,6 +1052,6 @@ function JobDetailController ($rootScope, $scope, $compile, $routeParams, $log,
}
JobDetailController.$inject = [ '$rootScope', '$scope', '$compile', '$routeParams', '$log', 'ClearScope', 'Breadcrumbs', 'LoadBreadCrumbs', 'GetBasePath',
'Wait', 'Rest', 'ProcessErrors', 'ProcessEventQueue', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'FilterAllByHostName', 'DrawGraph',
'LoadHostSummary', 'ReloadHostSummaryList', 'JobIsFinished', 'SetTaskStyles'
'Wait', 'Rest', 'ProcessErrors', 'SelectPlay', 'SelectTask', 'Socket', 'GetElapsed', 'FilterAllByHostName', 'DrawGraph',
'LoadHostSummary', 'ReloadHostSummaryList', 'JobIsFinished', 'SetTaskStyles', 'DigestEvent'
];

View File

@ -72,6 +72,8 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
var scope = params.scope,
event = params.event;
$log.debug('processing event: ' + event.id);
switch (event.event) {
case 'playbook_on_start':
if (!JobIsFinished(scope)) {
@ -81,28 +83,20 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
break;
case 'playbook_on_play_start':
scope.plays.push({
scope.jobData.plays[event.id] = {
id: event.id,
name: event.play,
created: event.created,
status: (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful',
elapsed: '00:00:00',
hostCount: 0,
fistTask: null
});
scope.playsMap[event.id] = scope.plays.length -1;
if (scope.playsMap[scope.activePlay] !== undefined) {
scope.plays[scope.playsMap[scope.activePlay]].playActiveClass = '';
fistTask: null,
tasks: {}
};
if (scope.activePlay) {
scope.jobData.plays[scope.activePlay].tasks = {};
}
scope.activePlay = event.id;
scope.plays[scope.playsMap[event.id]].playActiveClass = 'active';
scope.tasks = [];
scope.tasksMap = {};
scope.hostResults = [];
scope.hostResultsMap = {};
$('#hosts-table-detail').mCustomScrollbar("update");
$('#tasks-table-detail').mCustomScrollbar("update");
scope.$emit('FixPlaysScroll');
break;
case 'playbook_on_setup':
@ -201,8 +195,8 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
});
scope.job_status.status = (event.failed) ? 'failed' : 'successful';
scope.job_status.status_class = "";
LoadHostSummary({ scope: scope, data: event.event_data });
DrawGraph({ scope: scope, resize: true });
//LoadHostSummary({ scope: scope, data: event.event_data });
//DrawGraph({ scope: scope, resize: true });
break;
}
};
@ -246,14 +240,14 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
var scope = params.scope,
event = params.event;
scope.tasks.push({
scope.jobData.plays[scope.activePlay].tasks[event.id] = {
id: event.id,
play_id: event.parent,
name: event.event_display,
status: ( (event.failed) ? 'failed' : (event.changed) ? 'changed' : 'successful' ),
created: event.created,
modified: event.modified,
hostCount: scope.plays[scope.playsMap[scope.activePlay]].hostCount,
hostCount: scope.jobData.plays[scope.activePlay].hostCount,
reportedHosts: 0,
successfulCount: 0,
failedCount: 0,
@ -262,31 +256,21 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
successfulStyle: { display: 'none'},
failedStyle: { display: 'none' },
changedStyle: { display: 'none' },
skippedStyle: { display: 'none' }
});
scope.tasksMap[event.id] = scope.tasks.length - 1;
if (scope.tasks.length > scope.tasksMaxRows) {
scope.tasks.shift();
}
if (!scope.plays[scope.playsMap[scope.activePlay]].firstTask) {
scope.plays[scope.playsMap[scope.activePlay]].firstTask = event.id;
skippedStyle: { display: 'none' },
hostResults: {}
};
if (!scope.jobData.plays[scope.activePlay].firstTask) {
scope.jobData.plays[scope.activePlay].firstTask = event.id;
}
if (scope.activeTask && scope.tasksMap[scope.activeTask] !== undefined) {
scope.tasks[scope.tasksMap[scope.activeTask]].taskActiveClass = '';
if (scope.activeTask && scope.jobData.plays[scope.activePlay].tasks[scope.activeTask] !== undefined) {
scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].taskActiveClass = '';
scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults = {};
}
scope.activeTask = event.id;
scope.tasks[scope.tasksMap[event.id]].taskActiveClass = 'active';
scope.hostResults = [];
scope.hostResultsMap = {};
scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].taskActiveClass = 'active';
// Not sure if this still works
scope.hasRoles = (event.role) ? true : false;
$('#hosts-table-detail').mCustomScrollbar("update");
scope.$emit('FixTasksScroll');
// Record the first task id
UpdatePlayStatus({
scope: scope,
play_id: event.parent,
@ -295,9 +279,9 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
modified: event.modified
});
if (scope.host_summary.total > 0) {
/*if (scope.host_summary.total > 0) {
DrawGraph({ scope: scope, resize: true });
}
}*/
};
}])
@ -339,8 +323,8 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
status_text = params.status_text,
play;
if (scope.playsMap[id] !== undefined) {
play = scope.plays[scope.playsMap[id]];
if (scope.jobData.plays[scope.activePlay] !== undefined) {
play = scope.jobData.plays[scope.activePlay];
if (failed) {
play.status = 'failed';
}
@ -379,8 +363,8 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
no_hosts = params.no_hosts,
task;
if (scope.tasksMap[id] !== undefined) {
task = scope.tasks[scope.tasksMap[id]];
if (scope.jobData.plays[scope.activePlay].tasks[scope.activeTask] !== undefined) {
task = scope.jobData.plays[scope.activePlay].tasks[scope.activeTask];
if (no_hosts){
task.status = 'no-matching-hosts';
}
@ -430,35 +414,21 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
scope.host_summary.total = scope.host_summary.ok + scope.host_summary.changed + scope.host_summary.unreachable +
scope.host_summary.failed;
if (scope.hostsMap[host_id] !== undefined) {
scope.hosts[scope.hostsMap[host_id]].ok += (status === 'successful') ? 1 : 0;
scope.hosts[scope.hostsMap[host_id]].changed += (status === 'changed') ? 1 : 0;
scope.hosts[scope.hostsMap[host_id]].unreachable += (status === 'unreachable') ? 1 : 0;
scope.hosts[scope.hostsMap[host_id]].failed += (status === 'failed') ? 1 : 0;
if (scope.jobData.hostSummaries[host_id] !== undefined) {
scope.jobData.hostSummaries[host_id].ok += (status === 'successful') ? 1 : 0;
scope.jobData.hostSummaries[host_id].changed += (status === 'changed') ? 1 : 0;
scope.jobData.hostSummaries[host_id].unreachable += (status === 'unreachable') ? 1 : 0;
scope.jobData.hostSummaries[host_id].failed += (status === 'failed') ? 1 : 0;
}
else if (scope.hosts.length < scope.hostSummaryTableRows) {
scope.hosts.push({
else {
scope.jobData.hostSummaries[host_id] = {
id: host_id,
name: name,
ok: (status === 'successful') ? 1 : 0,
changed: (status === 'changed') ? 1 : 0,
unreachable: (status === 'unreachable') ? 1 : 0,
failed: (status === 'failed') ? 1 : 0
});
scope.hosts.sort(function (a, b) {
if (a.name > b.name)
return 1;
if (a.name < b.name)
return -1;
// a must be equal to b
return 0;
});
scope.hostsMap = {};
scope.hosts.forEach(function(host, idx){
scope.hostsMap[host.id] = idx;
});
$('#hosts-table-detail').mCustomScrollbar("update");
};
}
UpdateTaskStatus({
@ -495,40 +465,25 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
msg = params.message,
task;
if (scope.hostResultsMap[host_id] === undefined && scope.hostResults.length < scope.hostTableRows) {
scope.hostResults.push({
id: event_id,
status: status,
host_id: host_id,
task_id: task_id,
name: name,
created: created,
msg: msg
});
scope.hostResults.sort(function(a,b) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
});
// Refresh the map
scope.hostResultsMap = {};
scope.hostResults.forEach(function(result, idx) {
scope.hostResultsMap[result.id] = idx;
});
scope.$emit('FixHostResultsScroll');
scope.jobData.plays[scope.activePlay].tasks[scope.activeTask].hostResults[event_id] = {
id: event_id,
status: status,
host_id: host_id,
task_id: task_id,
name: name,
created: created,
msg: msg
}
// update the task status bar
if (scope.tasksMap[task_id] !== undefined) {
task = scope.tasks[scope.tasksMap[task_id]];
if (task_id === scope.plays[scope.playsMap[scope.activePlay]].firstTask) {
scope.plays[scope.playsMap[scope.activePlay]].hostCount++;
if (scope.jobData.plays[scope.activePlay].tasks[task_id] !== undefined) {
task = scope.jobData.plays[scope.activePlay].tasks[task_id];
if (task_id === scope.jobData.plays[scope.activePlay].firstTask) {
scope.jobData.plays[scope.activePlay].hostCount++;
task.hostCount++;
}
task.reportedHosts += 1;
task.failedCount += (status === 'failed' || status === 'unreachable') ? 1 : 0;
task.changedCount += (status === 'changed') ? 1 : 0;
@ -548,7 +503,7 @@ function($rootScope, $log, UpdatePlayStatus, UpdateHostStatus, AddHostResult, Ge
scope = params.scope,
diff, task;
task = scope.tasks[scope.tasksMap[task_id]];
task = scope.jobData.plays[scope.activePlay].tasks[task_id];
task.failedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.failedCount / task.hostCount))) : 0;
task.changedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.changedCount / task.hostCount))) : 0;
task.skippedPct = (task.hostCount > 0) ? Math.ceil((100 * (task.skippedCount / task.hostCount))) : 0;