diff --git a/awx/ui/client/src/job-results/event-queue.service.js b/awx/ui/client/src/job-results/event-queue.service.js
index 6a363d3e6c..cd3d037557 100644
--- a/awx/ui/client/src/job-results/event-queue.service.js
+++ b/awx/ui/client/src/job-results/event-queue.service.js
@@ -4,90 +4,21 @@
* All Rights Reserved
*************************************************/
-export default [function(){
+export default ['jobResultsService', function(jobResultsService){
var val = {};
- // the playbook_on_stats event returns the count data in a weird format.
- // format to what we need!
- var getCountsFromStatsEvent = function(event_data) {
- var hosts = {},
- hostsArr;
-
- // iterate over the event_data and populate an object with hosts
- // and their status data
- Object.keys(event_data).forEach(key => {
- // failed passes boolean not integer
- if (key === "failed") {
- // array of hosts from failed type
- hostsArr = Object.keys(event_data[key]);
- hostsArr.forEach(host => {
- if (!hosts[host]) {
- // host has not been added to hosts object
- // add now
- hosts[host] = {};
- }
-
- hosts[host][key] = event_data[key][host];
- });
- } else {
- // array of hosts from each type ("changed", "dark", etc.)
- hostsArr = Object.keys(event_data[key]);
- hostsArr.forEach(host => {
- if (!hosts[host]) {
- // host has not been added to hosts object
- // add now
- hosts[host] = {};
- }
-
- if (!hosts[host][key]) {
- // host doesn't have key
- hosts[host][key] = 0;
- }
- hosts[host][key] += event_data[key][host];
- });
- }
- });
-
- // use the hosts data populate above to get the count
- var count = {
- ok : _.filter(hosts, function(o){
- return !o.failures && !o.changed && o.ok > 0;
- }),
- skipped : _.filter(hosts, function(o){
- return o.skipped > 0;
- }),
- unreachable : _.filter(hosts, function(o){
- return o.dark > 0;
- }),
- failures : _.filter(hosts, function(o){
- return o.failed === true;
- }),
- changed : _.filter(hosts, function(o){
- return o.changed > 0;
- })
- };
-
- // turn the count into an actual count, rather than a list of host
- // names
- Object.keys(count).forEach(key => {
- count[key] = count[key].length;
- });
-
- return count;
- };
-
// Get the count of the last event
- var getPreviousCount = function(id) {
+ var getPreviousCount = function(counter) {
// get the ids of all the queue
- var ids = Object.keys(val.queue).map(id => parseInt(id));
+ var counters = Object.keys(val.queue).map(counter => parseInt(counter));
// iterate backwards to find the last count
- while(ids.indexOf(id - 1) > -1) {
- id = id - 1;
- if (val.queue[id].count) {
+ while(counters.indexOf(counter - 1) > -1) {
+ counter = counter - 1;
+ if (val.queue[counter].count) {
// need to create a new copy of count when returning
// so that it is accurate for the particular event
- return _.clone(val.queue[id].count);
+ return _.clone(val.queue[counter].count);
}
}
@@ -104,33 +35,39 @@ export default [function(){
// munge the raw event from the backend into the event_queue's format
var mungeEvent = function(event) {
var mungedEvent = {
+ counter: event.counter,
id: event.id,
processed: false,
- name: event.event_name,
- count: getPreviousCount(event.id)
+ name: event.event_name
};
if (event.event_name === 'playbook_on_start') {
// sets count initially so this is a change
+ mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.changes = ['count'];
} else if (event.event_name === 'runner_on_ok' ||
event.event_name === 'runner_on_async_ok') {
+ mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.count.ok++;
mungedEvent.changes = ['count'];
} else if (event.event_name === 'runner_on_skipped') {
+ mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.count.skipped++;
mungedEvent.changes = ['count'];
} else if (event.event_name === 'runner_on_unreachable') {
+ mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.count.unreachable++;
mungedEvent.changes = ['count'];
} else if (event.event_name === 'runner_on_error' ||
event.event_name === 'runner_on_async_failed') {
+ mungedEvent.count = getPreviousCount(mungedEvent.counter);
mungedEvent.count.failed++;
mungedEvent.changes = ['count'];
} else if (event.event_name === 'playbook_on_stats') {
// get the data for populating the host status bar
- mungedEvent.count = getCountsFromStatsEvent(event.event_data);
- mungedEvent.changes = ['count'];
+ mungedEvent.count = jobResultsService
+ .getCountsFromStatsEvent(event.event_data);
+ mungedEvent.changes = ['count', 'countFinished'];
}
return mungedEvent;
};
@@ -143,15 +80,19 @@ export default [function(){
},
// populates the event queue
populate: function(event) {
- var mungedEvent = mungeEvent(event);
- val.queue[event.id] = mungedEvent;
+ // don't populate the event if it's already been added either
+ // by rest or by websocket event
+ if (!val.queue[event.counter]) {
+ var mungedEvent = mungeEvent(event);
+ val.queue[mungedEvent.counter] = mungedEvent;
- return mungedEvent;
+ return mungedEvent;
+ }
},
// the event has been processed in the view and should be marked as
// completed in the queue
markProcessed: function(event) {
- val.queue[event.id].processed = true;
+ val.queue[event.counter].processed = true;
}
};
diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less
index 36895a6b5b..893700ce8a 100644
--- a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less
+++ b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.block.less
@@ -7,41 +7,46 @@
margin-top: 10px;
}
+.HostStatusBar-ok,
+.HostStatusBar-changed,
+.HostStatusBar-unreachable,
+.HostStatusBar-failures,
+.HostStatusBar-skipped,
+.HostStatusBar-noData {
+ height: 15px;
+ border-top: 5px solid @default-bg;
+ border-bottom: 5px solid @default-bg;
+}
+
.HostStatusBar-ok {
background-color: @default-succ;
display: flex;
flex: 0 0 auto;
- height: 5px;
}
.HostStatusBar-changed {
background-color: @default-warning;
flex: 0 0 auto;
- height: 5px;
}
.HostStatusBar-unreachable {
background-color: @default-unreachable;
flex: 0 0 auto;
- height: 5px;
}
.HostStatusBar-failures {
background-color: @default-err;
flex: 0 0 auto;
- height: 5px;
}
.HostStatusBar-skipped {
background-color: @default-link;
flex: 0 0 auto;
- height: 5px;
}
.HostStatusBar-noData {
background-color: @default-icon-hov;
flex: 1 0 auto;
- height: 5px;
}
.HostStatusBar-tooltipLabel {
diff --git a/awx/ui/client/src/job-results/job-results.controller.js b/awx/ui/client/src/job-results/job-results.controller.js
index 2a51f6691b..af4f250062 100644
--- a/awx/ui/client/src/job-results/job-results.controller.js
+++ b/awx/ui/client/src/job-results/job-results.controller.js
@@ -1,4 +1,4 @@
-export default ['jobData', 'jobDataOptions', 'jobLabels', '$scope', 'ParseTypeChange', 'ParseVariableString', 'jobResultsService', '$rootScope', 'eventQueue', function(jobData, jobDataOptions, jobLabels, $scope, ParseTypeChange, ParseVariableString, jobResultsService, $rootScope, eventQueue) {
+export default ['jobData', 'jobDataOptions', 'jobLabels', 'count', '$scope', 'ParseTypeChange', 'ParseVariableString', 'jobResultsService', '$rootScope', 'eventQueue', function(jobData, jobDataOptions, jobLabels, count, $scope, ParseTypeChange, ParseVariableString, jobResultsService, $rootScope, eventQueue) {
var getTowerLinks = function() {
var getTowerLink = function(key) {
if ($scope.job.related[key]) {
@@ -34,6 +34,11 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', '$scope', 'ParseTypeCh
$scope.verbosity_label = getTowerLabel('verbosity');
};
+ var getTotalHostCount = function(count) {
+ return Object
+ .keys(count).reduce((acc, i) => acc += count[i], 0);
+ };
+
// put initially resolved request data on scope
$scope.job = jobData;
$scope.jobOptions = jobDataOptions.actions.GET;
@@ -66,6 +71,11 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', '$scope', 'ParseTypeCh
jobResultsService.cancelJob($scope.job);
};
+ // get initial count from resolve
+ $scope.count = count.val;
+ $scope.hostCount = getTotalHostCount(count.val);
+ $scope.countFinished = count.countFinished;
+
// EVENT STUFF BELOW
// just putting the event queue on scope so it can be inspected in the
@@ -79,8 +89,14 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', '$scope', 'ParseTypeCh
// make changes to ui based on the event returned from the queue
if (mungedEvent.changes) {
mungedEvent.changes.forEach(change => {
- if (change === 'count') {
+ if (change === 'count' && !$scope.countFinished) {
$scope.count = mungedEvent.count;
+ $scope.hostCount = getTotalHostCount(mungedEvent
+ .count);
+ }
+
+ if (change === 'countFnished') {
+ $scope.countFinished = true;
}
});
}
@@ -90,15 +106,21 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', '$scope', 'ParseTypeCh
};
// grab completed event data and process each event
- jobResultsService.getEvents($scope.job)
- .then(events => {
- events.forEach(event => {
- // get the name in the same format as the data
- // coming over the websocket
- event.event_name = event.event;
- processEvent(event);
+ var getEvents = function(url) {
+ jobResultsService.getEvents(url)
+ .then(events => {
+ events.results.forEach(event => {
+ // get the name in the same format as the data
+ // coming over the websocket
+ event.event_name = event.event;
+ processEvent(event);
+ });
+ if (events.next) {
+ getEvents(events.next);
+ }
});
- });
+ };
+ getEvents($scope.job.related.job_events);
// process incoming job events
$rootScope.event_socket.on("job_events-" + $scope.job.id, function(data) {
diff --git a/awx/ui/client/src/job-results/job-results.partial.html b/awx/ui/client/src/job-results/job-results.partial.html
index 3300afa695..2b98b73592 100644
--- a/awx/ui/client/src/job-results/job-results.partial.html
+++ b/awx/ui/client/src/job-results/job-results.partial.html
@@ -459,7 +459,7 @@
Plays
- {{jobData.playCount || 0}}
+ {{ playCount || 0}}
@@ -467,7 +467,7 @@
Tasks
- {{jobData.taskCount || 0}}
+ {{ taskCount || 0}}
@@ -475,7 +475,7 @@
Hosts
- {{jobData.hostCount || 0}}
+ {{ hostCount || 0}}
diff --git a/awx/ui/client/src/job-results/job-results.route.js b/awx/ui/client/src/job-results/job-results.route.js
index 023fa237d5..1807898387 100644
--- a/awx/ui/client/src/job-results/job-results.route.js
+++ b/awx/ui/client/src/job-results/job-results.route.js
@@ -35,6 +35,43 @@ export default {
});
return val.promise;
}],
+ count: ['jobData', 'jobResultsService', 'Rest', '$q', function(jobData, jobResultsService, Rest, $q) {
+ var defer = $q.defer();
+ if (jobData.finished) {
+ // if the job is finished, grab the playbook_on_stats
+ // role to get the final count
+ Rest.setUrl(jobData.related.job_events +
+ "?event=playbook_on_stats");
+ Rest.get()
+ .success(function(data) {
+ defer.resolve({
+ val: jobResultsService
+ .getCountsFromStatsEvent(data
+ .results[0].event_data),
+ countFinished: true});
+ })
+ .error(function() {
+ defer.resolve({val: {
+ ok: 0,
+ skipped: 0,
+ unreachable: 0,
+ failures: 0,
+ changed: 0
+ }, countFinished: false});
+ });
+ } else {
+ // job isn't finished so just send an empty count and read
+ // from events
+ defer.resolve({val: {
+ ok: 0,
+ skipped: 0,
+ unreachable: 0,
+ failures: 0,
+ changed: 0
+ }, countFinished: false});
+ }
+ return defer.promise;
+ }],
jobLabels: ['Rest', 'GetBasePath', '$stateParams', '$q', function(Rest, GetBasePath, $stateParams, $q) {
var getNext = function(data, arr, resolve) {
Rest.setUrl(data.next);
diff --git a/awx/ui/client/src/job-results/job-results.service.js b/awx/ui/client/src/job-results/job-results.service.js
index c271f7dd3c..81f4a99fb0 100644
--- a/awx/ui/client/src/job-results/job-results.service.js
+++ b/awx/ui/client/src/job-results/job-results.service.js
@@ -7,13 +7,81 @@
export default ['$q', 'Prompt', '$filter', 'Wait', 'Rest', '$state', 'ProcessErrors', function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors) {
var val = {
- getEvents: function(job) {
+ // the playbook_on_stats event returns the count data in a weird format.
+ // format to what we need!
+ getCountsFromStatsEvent: function(event_data) {
+ var hosts = {},
+ hostsArr;
+
+ // iterate over the event_data and populate an object with hosts
+ // and their status data
+ Object.keys(event_data).forEach(key => {
+ // failed passes boolean not integer
+ if (key === "failed") {
+ // array of hosts from failed type
+ hostsArr = Object.keys(event_data[key]);
+ hostsArr.forEach(host => {
+ if (!hosts[host]) {
+ // host has not been added to hosts object
+ // add now
+ hosts[host] = {};
+ }
+
+ hosts[host][key] = event_data[key][host];
+ });
+ } else {
+ // array of hosts from each type ("changed", "dark", etc.)
+ hostsArr = Object.keys(event_data[key]);
+ hostsArr.forEach(host => {
+ if (!hosts[host]) {
+ // host has not been added to hosts object
+ // add now
+ hosts[host] = {};
+ }
+
+ if (!hosts[host][key]) {
+ // host doesn't have key
+ hosts[host][key] = 0;
+ }
+ hosts[host][key] += event_data[key][host];
+ });
+ }
+ });
+
+ // use the hosts data populate above to get the count
+ var count = {
+ ok : _.filter(hosts, function(o){
+ return !o.failures && !o.changed && o.ok > 0;
+ }),
+ skipped : _.filter(hosts, function(o){
+ return o.skipped > 0;
+ }),
+ unreachable : _.filter(hosts, function(o){
+ return o.dark > 0;
+ }),
+ failures : _.filter(hosts, function(o){
+ return o.failed === true;
+ }),
+ changed : _.filter(hosts, function(o){
+ return o.changed > 0;
+ })
+ };
+
+ // turn the count into an actual count, rather than a list of host
+ // names
+ Object.keys(count).forEach(key => {
+ count[key] = count[key].length;
+ });
+
+ return count;
+ },
+ getEvents: function(url) {
var val = $q.defer();
- Rest.setUrl(job.related.job_events);
+ Rest.setUrl(url);
Rest.get()
.success(function(data) {
- val.resolve(data.results);
+ val.resolve({results: data.results, next: data.next});
})
.error(function(obj, status) {
ProcessErrors(null, obj, status, null, {