From 11592047d6512aca3e38e363265f461271cb83f1 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Fri, 30 Sep 2016 14:47:08 -0400 Subject: [PATCH] updates to getting host counts --- .../src/job-results/event-queue.service.js | 109 ++++-------------- .../host-status-bar.block.less | 17 ++- .../src/job-results/job-results.controller.js | 42 +++++-- .../src/job-results/job-results.partial.html | 6 +- .../src/job-results/job-results.route.js | 37 ++++++ .../src/job-results/job-results.service.js | 74 +++++++++++- 6 files changed, 179 insertions(+), 106 deletions(-) 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, {