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 3e052def56..2af29d5b05 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -297,6 +297,26 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy } if(change === 'stdout'){ + var appendToBottom = function(mungedEvent){ + // if we get here then the event type was either a + // header line, recap line, or one of the additional + // event types, so we append it to the bottom. + // These are the event types for captured + // stdout not directly related to playbook or runner + // events: + // (0, 'debug', _('Debug'), False), + // (0, 'verbose', _('Verbose'), False), + // (0, 'deprecated', _('Deprecated'), False), + // (0, 'warning', _('Warning'), False), + // (0, 'system_warning', _('System Warning'), False), + // (0, 'error', _('Error'), True), + angular + .element(".JobResultsStdOut-stdoutContainer") + .append($compile(mungedEvent + .stdout)($scope.events[mungedEvent + .counter])); + }; + if (!$scope.events[mungedEvent.counter]) { // line hasn't been put in the pane yet @@ -314,13 +334,56 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy .after($compile(mungedEvent .stdout)($scope.events[mungedEvent .counter])); + } else if (mungedEvent.stdout.indexOf("not_skeleton") > -1) { + var putIn; + var classList = $("div", + "
"+mungedEvent.stdout+"
") + .attr("class").split(" "); + if (classList + .filter(v => v.indexOf("task_") > -1) + .length) { + putIn = classList + .filter(v => v.indexOf("task_") > -1)[0]; + } else if(classList + .filter(v => v.indexOf("play_") > -1) + .length) { + putIn = classList + .filter(v => v.indexOf("play_") > -1)[0]; + } + + var putAfter; + var isDup = false; + $(".header_" + putIn + ",." + putIn) + .each((i, v) => { + if (angular.element(v).scope() + .event.start_line < mungedEvent + .start_line) { + putAfter = v; + } else if (angular.element(v).scope() + .event.start_line === mungedEvent + .start_line) { + isDup = true; + return false; + } else if (angular.element(v).scope() + .event.start_line > mungedEvent + .start_line) { + return false; + } else { + appendToBottom(mungedEvent); + } + }); + + if (!isDup) { + $(putAfter).after($compile(mungedEvent + .stdout)($scope.events[mungedEvent + .counter])); + } + + + classList = null; + putIn = null; } else { - // if not, put it at the bottom - angular - .element(".JobResultsStdOut-stdoutContainer") - .append($compile(mungedEvent - .stdout)($scope.events[mungedEvent - .counter])); + appendToBottom(mungedEvent); } // delete ref to the elem because it might leak scope @@ -508,10 +571,24 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy } })); + var buffer = []; + var processBuffer = function() { + buffer.forEach((event, i) => { + processEvent(event); + buffer.splice(i, 1); + }); + }; + + var bufferInterval; // Processing of job_events messages from the websocket toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { + if (!bufferInterval) { + bufferInterval = setInterval(function(){ + processBuffer(); + }, 500); + } // use the lowest counter coming over the socket to retrigger pull data // to only be for stuff lower than that id @@ -538,6 +615,7 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy } else if (data.event_name === "playbook_on_task_start") { $scope.taskCount++; } + buffer.push(data); processEvent(data); }); })); @@ -553,6 +631,9 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy data.status === "error" || data.status === "canceled") { clearInterval(elapsedInterval); + if (bufferInterval) { + clearInterval(bufferInterval); + } // When the fob is finished retrieve the job data to // correct anything that was out of sync from the job run jobResultsService.getJobData($scope.job.id).then(function(data){ @@ -584,6 +665,9 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy }); $scope.events = {}; clearInterval(elapsedInterval); + if (bufferInterval) { + clearInterval(bufferInterval); + } toDestroy.forEach(closureFunc => closureFunc()); }); }]; 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 6fbeefde13..7c3d36aab9 100644 --- a/awx/ui/client/src/job-results/job-results.service.js +++ b/awx/ui/client/src/job-results/job-results.service.js @@ -18,19 +18,11 @@ function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybo // 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 { + if (key === "changed" || + key === "dark" || + key === "failures" || + key === "ok" || + key === "skipped") { // array of hosts from each type ("changed", "dark", etc.) hostsArr = Object.keys(event_data[key]); hostsArr.forEach(host => { @@ -61,7 +53,7 @@ function ($q, Prompt, $filter, Wait, Rest, $state, ProcessErrors, InitiatePlaybo return o.dark > 0; }), failures : _.filter(hosts, function(o){ - return o.failed === true; + return o.failures > 0; }), changed : _.filter(hosts, function(o){ return o.changed > 0; diff --git a/awx/ui/client/src/job-results/parse-stdout.service.js b/awx/ui/client/src/job-results/parse-stdout.service.js index 8630c0fa07..8c7e91f755 100644 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ b/awx/ui/client/src/job-results/parse-stdout.service.js @@ -202,6 +202,11 @@ export default ['$log', 'moment', function($log, moment){ let lineNums = _.range(event.start_line + 1, event.end_line + 1); + // hack around no-carriage return issues + if (!lineNums.length) { + lineNums = [event.start_line + 1]; + } + let lines = event.stdout .replace("\t", " ") .split("\r\n"); @@ -214,6 +219,11 @@ export default ['$log', 'moment', function($log, moment){ } } + // hack around no-carriage return issues + if (lineNums.length === lines.length) { + return _.zip(lineNums, lines); + } + return _.zip(lineNums, lines).slice(0, -1); }, // public function that provides the parsed stdout line, given a