From d46de8f20d7715bc2a215b630efd0eda815779e7 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 9 Jan 2017 12:25:45 -0500 Subject: [PATCH 01/12] fix job details memory leaks --- .../host-status-bar.directive.js | 6 ++- .../job-results-stdout.directive.js | 44 ++++++++++++++----- .../src/job-results/job-results.controller.js | 36 +++++++++++---- .../src/job-results/parse-stdout.service.js | 1 - 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js index f7fbd2e8f6..778d238e47 100644 --- a/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js +++ b/awx/ui/client/src/job-results/host-status-bar/host-status-bar.directive.js @@ -15,7 +15,7 @@ export default [ 'templateUrl', link: function(scope) { // as count is changed by event data coming in, // update the host status bar - scope.$watch('count', function(val) { + var toDestroy = scope.$watch('count', function(val) { if (val) { Object.keys(val).forEach(key => { // reposition the hosts status bar by setting @@ -38,6 +38,10 @@ export default [ 'templateUrl', .filter(key => (val[key] > 0)).length > 0); } }); + + scope.$on('$destroy', function(){ + toDestroy(); + }); } }; }]; diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js index 77c87c74da..d6f0ee8e04 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js @@ -12,6 +12,18 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', templateUrl: templateUrl('job-results/job-results-stdout/job-results-stdout'), restrict: 'E', link: function(scope, element) { + var toDestroy = [], + resizer, + scrollWatcher; + + scope.$on('$destroy', function(){ + $(window).off("resize", resizer); + $(window).off("scroll", scrollWatcher); + $(".JobResultsStdOut-stdoutContainer").off('scroll', + scrollWatcher); + toDestroy.forEach(v => v()); + }); + scope.stdoutContainerAvailable.resolve("container available"); // utility function used to find the top visible line and // parent header in the pane @@ -115,9 +127,15 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', // stop iterating over the standard out // lines once the first one has been // found + + $this = null; return false; - } - }); + } + + $this = null; + }); + + $container = null; return { visLine: visItem, @@ -131,22 +149,24 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', } else { scope.isMobile = false; } - // watch changes to the window size - $(window).resize(function() { + + resizer = function() { // and update the isMobile var accordingly if (window.innerWidth <= 1200 && !scope.isMobile) { scope.isMobile = true; } else if (window.innerWidth > 1200 & scope.isMobile) { scope.isMobile = false; } - }); + }; + // watch changes to the window size + $(window).resize(resizer); var lastScrollTop; var initScrollTop = function() { lastScrollTop = 0; }; - var scrollWatcher = function() { + scrollWatcher = function() { var st = $(this).scrollTop(); var netScroll = st + $(this).innerHeight(); var fullHeight; @@ -178,11 +198,15 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', } lastScrollTop = st; + + st = null; + netScroll = null; + fullHeight = null; }; // update scroll watchers when isMobile changes based on // window resize - scope.$watch('isMobile', function(val) { + toDestroy.push(scope.$watch('isMobile', function(val) { if (val === true) { // make sure ^ TOP always shown for mobile scope.stdoutOverflowed = true; @@ -204,7 +228,7 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', $(".JobResultsStdOut-stdoutContainer").on('scroll', scrollWatcher); } - }); + })); // called to scroll to follow anchor scope.followScroll = function() { @@ -237,7 +261,7 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', // if following becomes active, go ahead and get to the bottom // of the standard out pane - scope.$watch('followEngaged', function(val) { + toDestroy.push(scope.$watch('followEngaged', function(val) { // scroll to follow point if followEngaged is true if (val) { scope.followScroll(); @@ -251,7 +275,7 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', scope.followTooltip = "Click to follow standard out as it comes in."; } } - }); + })); // follow button ng-click function scope.followToggleClicked = function() { 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 56aed6ce5b..c2c1f99df3 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -1,5 +1,7 @@ export default ['jobData', 'jobDataOptions', 'jobLabels', 'jobFinished', 'count', '$scope', 'ParseTypeChange', 'ParseVariableString', 'jobResultsService', 'eventQueue', '$compile', '$log', 'Dataset', '$q', 'Rest', '$state', 'QuerySet', '$rootScope', 'moment', function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTypeChange, ParseVariableString, jobResultsService, eventQueue, $compile, $log, Dataset, $q, Rest, $state, QuerySet, $rootScope, moment) { + var toDestroy = []; + var cancelRequests = false; // used for tag search $scope.job_event_dataset = Dataset.data; @@ -66,14 +68,14 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // update label in left pane and tooltip in right pane when the job_status // changes - $scope.$watch('job_status', function(status) { + toDestroy.push($scope.$watch('job_status', function(status) { if (status) { $scope.status_label = $scope.jobOptions.status.choices .filter(val => val[0] === status) .map(val => val[1])[0]; $scope.status_tooltip = "Job " + $scope.status_label; } - }); + })); // update the job_status value. Use the cached rootScope value if there // is one. This is a workaround when the rest call for the jobData is @@ -278,6 +280,9 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy .stdout)($scope.events[mungedEvent .counter])); } + + classList = null; + putIn = null; } else { // this is a header or recap line, so just // append to the bottom @@ -368,7 +373,7 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy delete event.event; processEvent(event); }); - if (events.next) { + if (events.next && !cancelRequests) { getEvents(events.next); } else { // put those paused events into the pane @@ -378,7 +383,7 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy }; // grab non-header recap lines - $scope.$watch('job_event_dataset', function(val) { + toDestroy.push($scope.$watch('job_event_dataset', function(val) { // pause websocket events from coming in to the pane $scope.gotPreviouslyRanEvents = $q.defer(); @@ -391,19 +396,19 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy delete event.event; processEvent(event); }); - if (val.next) { + if (val.next && !cancelRequests) { getEvents(val.next); } else { // put those paused events into the pane $scope.gotPreviouslyRanEvents.resolve(""); } }); - }); + })); // Processing of job_events messages from the websocket - $scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { + toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { $q.all([$scope.gotPreviouslyRanEvents.promise, $scope.hasSkeleton.promise]).then(() => { var url = Dataset @@ -446,10 +451,10 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy } }); - }); + })); // Processing of job-status messages from the websocket - $scope.$on(`ws-jobs`, function(e, data) { + toDestroy.push($scope.$on(`ws-jobs`, function(e, data) { if (parseInt(data.unified_job_id, 10) === parseInt($scope.job.id,10)) { // controller is defined, so set the job_status @@ -477,5 +482,18 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // for this job. cache the socket status on root scope $rootScope['lastSocketStatus' + data.unified_job_id] = data.status; } + })); + + $scope.$on('$destroy', function(){ + cancelRequests = true; + eventQueue.initialize(); + Object.keys($scope.events) + .forEach(v => { + $scope.events[v].$destroy(); + $scope.events[v] = null; + }); + $scope.events = {}; + clearInterval(elapsedInterval); + toDestroy.forEach(v => v()); }); }]; 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 8d6564f3f0..858d86efe2 100644 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ b/awx/ui/client/src/job-results/parse-stdout.service.js @@ -185,7 +185,6 @@ export default ['$log', 'moment', function($log, moment){ data-uuid="${clickClass}"> `; - // console.log(expandDom); return expandDom; } else { // non-header lines don't get an expander From a1c0bd5dcdfd6144cf13edf21f2f307456dd3e34 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 9 Jan 2017 12:26:13 -0500 Subject: [PATCH 02/12] increase page size to improve ux of loading large amounts of standard out --- awx/ui/client/src/job-results/job-results.route.js | 1 + 1 file changed, 1 insertion(+) 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 88154d3114..ec47cc7757 100644 --- a/awx/ui/client/src/job-results/job-results.route.js +++ b/awx/ui/client/src/job-results/job-results.route.js @@ -25,6 +25,7 @@ export default { params: { job_event_search: { value: { + page_size: 100, order_by: 'id', not__event__in: 'playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats' }, From dda8f14673d9b37548f2c3a2e76c775df1c9f00d Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 9 Jan 2017 16:53:00 -0500 Subject: [PATCH 03/12] establish filter context checking on job details page in other words, stop making requests and doing stuff for a stale filter state also currently removing the behavior that kept live events controlled by the filter --- .../src/job-results/job-results.controller.js | 135 +++++++++--------- 1 file changed, 69 insertions(+), 66 deletions(-) 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 c2c1f99df3..ac9ff7993a 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -3,6 +3,10 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy var toDestroy = []; var cancelRequests = false; + // this allows you to manage the timing of rest-call based events as + // filters are updated. see processPage for more info + var currentContext = 1; + // used for tag search $scope.job_event_dataset = Dataset.data; @@ -187,7 +191,12 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // This is where the async updates to the UI actually happen. // Flow is event queue munging in the service -> $scope setting in here - var processEvent = function(event) { + var processEvent = function(event, context) { + // only care about filter context checking when the event comes + // as a rest call + if (context && context !== currentContext) { + return; + } // put the event in the queue var mungedEvent = eventQueue.populate(event); @@ -362,46 +371,69 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy getSkeleton(jobData.related.job_events + "?order_by=id&or__event__in=playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats"); }); + var getEvents; + + var processPage = function(events, context) { + // currentContext is the context of the filter when this request + // to processPage was made + // + // currentContext is the context of the filter currently + // + // if they are not the same, make sure to stop process events/ + // making rest calls for next pages/etc. (you can see context is + // also passed into getEvents and processEvent and similar checks + // exist in these functions) + if (context !== currentContext) { + return; + } + + events.results.forEach(event => { + // get the name in the same format as the data + // coming over the websocket + event.event_name = event.event; + delete event.event; + + processEvent(event, context); + }); + if (events.next && !cancelRequests) { + getEvents(events.next, context); + } else { + // put those paused events into the pane + $scope.gotPreviouslyRanEvents.resolve(""); + } + }; + // grab non-header recap lines - var getEvents = function(url) { + getEvents = function(url, context) { + if (context !== currentContext) { + return; + } + 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; - delete event.event; - processEvent(event); - }); - if (events.next && !cancelRequests) { - getEvents(events.next); - } else { - // put those paused events into the pane - $scope.gotPreviouslyRanEvents.resolve(""); - } + processPage(events, context); }); }; // grab non-header recap lines toDestroy.push($scope.$watch('job_event_dataset', function(val) { + eventQueue.initialize(); + Object.keys($scope.events) + .forEach(v => { + $scope.events[v].$destroy(); + $scope.events[v] = null; + }); + $scope.events = {}; + // pause websocket events from coming in to the pane $scope.gotPreviouslyRanEvents = $q.defer(); + currentContext += 1; + + let context = currentContext; $( ".JobResultsStdOut-aLineOfStdOut.not_skeleton" ).remove(); $scope.hasSkeleton.promise.then(() => { - val.results.forEach(event => { - // get the name in the same format as the data - // coming over the websocket - event.event_name = event.event; - delete event.event; - processEvent(event); - }); - if (val.next && !cancelRequests) { - getEvents(val.next); - } else { - // put those paused events into the pane - $scope.gotPreviouslyRanEvents.resolve(""); - } + processPage(val, context); }); })); @@ -411,45 +443,16 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { $q.all([$scope.gotPreviouslyRanEvents.promise, $scope.hasSkeleton.promise]).then(() => { - var url = Dataset - .config.url.split("?")[0] + - QuerySet.encodeQueryset($state.params.job_event_search); - var noFilter = (url.split("&") - .filter(v => v.indexOf("page=") !== 0 && - v.indexOf("/api/v1") !== 0 && - v.indexOf("order_by=id") !== 0 && - v.indexOf("not__event__in=playbook_on_start,playbook_on_play_start,playbook_on_task_start,playbook_on_stats") !== 0).length === 0); - - if(data.event_name === "playbook_on_start" || - data.event_name === "playbook_on_play_start" || - data.event_name === "playbook_on_task_start" || - data.event_name === "playbook_on_stats" || - noFilter) { - // for header and recap lines, as well as if no filters - // were added by the user, just put the line in the - // standard out pane (and increment play and task - // count) - if (data.event_name === "playbook_on_play_start") { - $scope.playCount++; - } else if (data.event_name === "playbook_on_task_start") { - $scope.taskCount++; - } - processEvent(data); - } else { - // to make sure host event/verbose lines go through a - // user defined filter, appent the id to the url, and - // make a request to the job_events endpoint with the - // id of the incoming event appended. If the event, - // is returned, put the line in the standard out pane - Rest.setUrl(`${url}&id=${data.id}`); - Rest.get() - .success(function(isHere) { - if (isHere.count) { - processEvent(data); - } - }); + // for header and recap lines, as well as if no filters + // were added by the user, just put the line in the + // standard out pane (and increment play and task + // count) + if (data.event_name === "playbook_on_play_start") { + $scope.playCount++; + } else if (data.event_name === "playbook_on_task_start") { + $scope.taskCount++; } - + processEvent(data); }); })); From d65e1a5d8230034e7d49833a524a413c92c25a37 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Mon, 9 Jan 2017 16:54:05 -0500 Subject: [PATCH 04/12] fix parse standard out issue --- awx/ui/client/src/job-results/parse-stdout.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 858d86efe2..178fd88f4b 100644 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ b/awx/ui/client/src/job-results/parse-stdout.service.js @@ -195,7 +195,7 @@ export default ['$log', 'moment', function($log, moment){ return _ .zip(_.range(event.start_line + 1, event.end_line + 1), - event.stdout.replace("\t", " ").split("\r\n").slice(0, -1)); + event.stdout.replace("\t", " ").split("\r\n")).slice(0, -1); }, // public function that provides the parsed stdout line, given a // job_event From d98e4a6cc51d1ec8d7e2cc0688a9381a0217725e Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 10 Jan 2017 11:26:36 -0500 Subject: [PATCH 05/12] dont delete skeleton line scopes as it causes toggle to not work --- .../src/job-results/job-results.controller.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) 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 ac9ff7993a..aef8b2ae86 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -418,12 +418,20 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // grab non-header recap lines toDestroy.push($scope.$watch('job_event_dataset', function(val) { eventQueue.initialize(); + Object.keys($scope.events) .forEach(v => { - $scope.events[v].$destroy(); - $scope.events[v] = null; + // dont destroy scope events for skeleton lines + let name = $scope.events[v].event.name; + + if (!(name === "playbook_on_play_start" || + name === "playbook_on_task_start" || + name === "playbook_on_stats")) { + $scope.events[v].$destroy(); + $scope.events[v] = null; + delete $scope.events[v]; + } }); - $scope.events = {}; // pause websocket events from coming in to the pane $scope.gotPreviouslyRanEvents = $q.defer(); @@ -443,10 +451,9 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy toDestroy.push($scope.$on(`ws-job_events-${$scope.job.id}`, function(e, data) { $q.all([$scope.gotPreviouslyRanEvents.promise, $scope.hasSkeleton.promise]).then(() => { - // for header and recap lines, as well as if no filters - // were added by the user, just put the line in the + // put the line in the // standard out pane (and increment play and task - // count) + // count if applicable) if (data.event_name === "playbook_on_play_start") { $scope.playCount++; } else if (data.event_name === "playbook_on_task_start") { From b8ce36022700f38141a062515d34dda5a4fef8f6 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 10 Jan 2017 11:30:56 -0500 Subject: [PATCH 06/12] update api max events header based on performance findings --- awx/settings/defaults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index 8e67e9a025..2ec150bf35 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -154,7 +154,7 @@ STDOUT_MAX_BYTES_DISPLAY = 1048576 # Returned in the header on event api lists as a recommendation to the UI # on how many events to display before truncating/hiding -RECOMMENDED_MAX_EVENTS_DISPLAY_HEADER = 10000 +RECOMMENDED_MAX_EVENTS_DISPLAY_HEADER = 4000 # The maximum size of the ansible callback event's res data structure # beyond this limit and the value will be removed From c0bbc4a9dd775cb5c81b65ad1f0928e50fe7a305 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 10 Jan 2017 14:48:48 -0500 Subject: [PATCH 07/12] show message when too many events come back for a job detail view --- .../job-results-stdout.block.less | 6 +++++ .../job-results-stdout.partial.html | 7 +++++ .../src/job-results/job-results.controller.js | 17 ++++++++++-- .../shared/smart-search/queryset.service.js | 27 ++++++++++++++----- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less index 408e4cbb32..0f01da5b2e 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less @@ -162,6 +162,7 @@ .JobResultsStdOut-stdoutColumn { padding-left: 20px; + padding-right: 20px; padding-top: 2px; padding-bottom: 2px; color: @default-interface-txt; @@ -171,6 +172,11 @@ width:100%; } +.JobResultsStdOut-stdoutColumn--tooMany { + font-weight: bold; + text-transform: uppercase; +} + .JobResultsStdOut-stdoutColumn { cursor: pointer; } diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html index 0ba992b146..87e65f54b4 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html @@ -31,6 +31,13 @@
+
+
+ +
+
The standard out based on the current filter is too large to display. Please use additional filters to view results.
+
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 aef8b2ae86..81ce5894bf 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -383,7 +383,11 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy // making rest calls for next pages/etc. (you can see context is // also passed into getEvents and processEvent and similar checks // exist in these functions) - if (context !== currentContext) { + // + // also, if the page doesn't contain results (i.e.: the response + // returns an error), don't process the page + if (context !== currentContext || events === undefined || + events.results === undefined) { return; } @@ -441,7 +445,16 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy $( ".JobResultsStdOut-aLineOfStdOut.not_skeleton" ).remove(); $scope.hasSkeleton.promise.then(() => { - processPage(val, context); + if (val.count > parseInt(val.maxEvents)) { + $(".header_task").hide(); + $(".header_play").hide(); + $scope.tooManyEvents = true; + } else { + $(".header_task").show(); + $(".header_play").show(); + $scope.tooManyEvents = false; + processPage(val, context); + } }); })); diff --git a/awx/ui/client/src/shared/smart-search/queryset.service.js b/awx/ui/client/src/shared/smart-search/queryset.service.js index 359245bed4..0ffeb521ce 100644 --- a/awx/ui/client/src/shared/smart-search/queryset.service.js +++ b/awx/ui/client/src/shared/smart-search/queryset.service.js @@ -147,20 +147,33 @@ export default ['$q', 'Rest', 'ProcessErrors', '$rootScope', 'Wait', 'DjangoSear Wait('start'); this.url = `${endpoint}${this.encodeQueryset(params)}`; Rest.setUrl(this.url); + return Rest.get() - .success(this.success.bind(this)) - .error(this.error.bind(this)) - .finally(Wait('stop')); + .then(function(response) { + Wait('stop'); + + if (response + .headers('X-UI-Max-Events') !== null) { + response.data.maxEvents = response. + headers('X-UI-Max-Events'); + } + + return response; + }) + .catch(function(response) { + Wait('stop'); + + this.error(response.data, response.status); + + return response; + }.bind(this)); }, error(data, status) { ProcessErrors($rootScope, data, status, null, { hdr: 'Error!', msg: 'Call to ' + this.url + '. GET returned: ' + status }); - }, - success(data) { - return data; - }, + } }; } ]; From 0befcced8754a602d2387bbdd2f1969eea1f7b3d Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Tue, 10 Jan 2017 16:11:13 -0500 Subject: [PATCH 08/12] update too large message for standard out --- .../job-results-stdout/job-results-stdout.block.less | 1 + .../job-results-stdout/job-results-stdout.partial.html | 8 ++++---- awx/ui/client/src/job-results/job-results.block.less | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less index 0f01da5b2e..2df922e2c4 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less @@ -175,6 +175,7 @@ .JobResultsStdOut-stdoutColumn--tooMany { font-weight: bold; text-transform: uppercase; + color: @default-err; } .JobResultsStdOut-stdoutColumn { diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html index 87e65f54b4..63e41b8fa8 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.partial.html @@ -31,16 +31,16 @@
+
+
+
-
The standard out based on the current filter is too large to display. Please use additional filters to view results.
+
The standard output is too large to display. Please specify additional filters to narrow the standard out.
-
-
-
Date: Wed, 11 Jan 2017 13:07:19 -0500 Subject: [PATCH 09/12] updates to job results based on feedback from pr --- .../job-results-stdout/job-results-stdout.directive.js | 2 +- awx/ui/client/src/job-results/job-results.controller.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js index d6f0ee8e04..14a34a607a 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.directive.js @@ -21,7 +21,7 @@ export default [ 'templateUrl', '$timeout', '$location', '$anchorScroll', $(window).off("scroll", scrollWatcher); $(".JobResultsStdOut-stdoutContainer").off('scroll', scrollWatcher); - toDestroy.forEach(v => v()); + toDestroy.forEach(closureFunc => closureFunc()); }); scope.stdoutContainerAvailable.resolve("container available"); 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 81ce5894bf..30da9d4ea9 100644 --- a/awx/ui/client/src/job-results/job-results.controller.js +++ b/awx/ui/client/src/job-results/job-results.controller.js @@ -508,6 +508,7 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy })); $scope.$on('$destroy', function(){ + $( ".JobResultsStdOut-aLineOfStdOut" ).remove(); cancelRequests = true; eventQueue.initialize(); Object.keys($scope.events) @@ -517,6 +518,6 @@ function(jobData, jobDataOptions, jobLabels, jobFinished, count, $scope, ParseTy }); $scope.events = {}; clearInterval(elapsedInterval); - toDestroy.forEach(v => v()); + toDestroy.forEach(closureFunc => closureFunc()); }); }]; From dc47bacc61630ed309e584e86a255c256a5954d5 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 11 Jan 2017 18:51:37 -0500 Subject: [PATCH 10/12] deal with capped lines --- .../src/job-results/parse-stdout.service.js | 22 +++++++++++++++---- .../job-results/parse-stdout.service-test.js | 13 +++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) 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 178fd88f4b..bf4bac3799 100644 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ b/awx/ui/client/src/job-results/parse-stdout.service.js @@ -27,6 +27,7 @@ export default ['$log', 'moment', function($log, moment){ line = line.replace(/u001b/g, ''); // ansi classes + line = line.replace(/\[1;im/g, ''); line = line.replace(/\[1;31m/g, ''); line = line.replace(/\[0;31m/g, ''); line = line.replace(/\[0;32m/g, ''); @@ -39,6 +40,7 @@ export default ['$log', 'moment', function($log, moment){ line = line.replace(/()\s/g, '$1'); //end span + line = line.replace(/\[0im/g, ''); line = line.replace(/\[0m/g, ''); } else { // For the host event modal in the standard out tab, @@ -192,10 +194,22 @@ export default ['$log', 'moment', function($log, moment){ } }, getLineArr: function(event) { - return _ - .zip(_.range(event.start_line + 1, - event.end_line + 1), - event.stdout.replace("\t", " ").split("\r\n")).slice(0, -1); + let lineNums = _.range(event.start_line + 1, + event.end_line + 1); + + let lines = event.stdout + .replace("\t", " ") + .split("\r\n"); + + if (lineNums.length > lines.length) { + let padBy = lineNums.length - lines.length; + + for (let i = 0; i <= padBy; i++) { + lines.push("[1;imline capped.[0im"); + } + } + + return _.zip(lineNums, lines).slice(0, -1); }, // public function that provides the parsed stdout line, given a // job_event diff --git a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js index 5dd6788b02..e34ad4d6c2 100644 --- a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js +++ b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js @@ -115,6 +115,19 @@ describe('parseStdoutService', () => { expect(returnedEvent).toEqual(expectedReturn); }); + + it('deals correctly with capped lines', () => { + let mockEvent = { + start_line: 7, + end_line: 11, + stdout: "a\r\nb\r\nc..." + }; + let expectedReturn = [[8, "a"],[9, "b"], [10,"c..."], [11, "[1;imline capped.[0im"]]; + + let returnedEvent = parseStdoutService.getLineArr(mockEvent); + + expect(returnedEvent).toEqual(expectedReturn); + }); }); describe('parseStdout()', () => { From f44670576703c5c0812d42fea15c2e2f0e2bcc8f Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Wed, 11 Jan 2017 18:52:06 -0500 Subject: [PATCH 11/12] update escaped parse standard out unit test --- .../spec/job-results/parse-stdout.service-test.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js index e34ad4d6c2..9dada797b0 100644 --- a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js +++ b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js @@ -31,10 +31,14 @@ describe('parseStdoutService', () => { unstyledLine = 'ok: [host-00]'; expect(parseStdoutService.prettify(line, unstyled)).toBe(unstyledLine); }); + + it('can return empty strings', () => { + expect(parseStdoutService.prettify("")).toBe(""); + }); }); describe('getLineClasses()', () => { - xit('creates a string that is used as a class', () => { + it('creates a string that is used as a class', () => { let headerEvent = { event_name: 'playbook_on_task_start', event_data: { @@ -44,12 +48,15 @@ describe('parseStdoutService', () => { }; let lineNum = 3; let line = "TASK [setup] *******************************************************************"; - let styledLine = " header_task header_task_80dd087c-268b-45e8-9aab-1083bcfd9364 play_0f667a23-d9ab-4128-a735-80566bcdbca0 line_num_3"; + let styledLine = " header_task header_task_80dd087c-268b-45e8-9aab-1083bcfd9364 actual_header play_0f667a23-d9ab-4128-a735-80566bcdbca0 line_num_3"; expect(parseStdoutService.getLineClasses(headerEvent, line, lineNum)).toBe(styledLine); }); }); describe('getStartTime()', () => { + // TODO: the problem is that the date here calls moment, and thus + // the date will be timezone'd in the string (this could be + // different based on where you are) xit('creates returns a badge with the start time of the event', () => { let headerEvent = { event_name: 'playbook_on_play_start', From 5ba0e1383b2ef0e303fb3d07df0f9f9bd5152137 Mon Sep 17 00:00:00 2001 From: John Mitchell Date: Thu, 12 Jan 2017 16:06:36 -0500 Subject: [PATCH 12/12] update line cap display --- .../job-results-stdout/job-results-stdout.block.less | 5 +++++ awx/ui/client/src/job-results/parse-stdout.service.js | 5 ++--- awx/ui/tests/spec/job-results/parse-stdout.service-test.js | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less index 2df922e2c4..7ff93d17ee 100644 --- a/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less +++ b/awx/ui/client/src/job-results/job-results-stdout/job-results-stdout.block.less @@ -223,6 +223,11 @@ color: @default-interface-txt; } +.JobResultsStdOut-cappedLine { + color: @b7grey; + font-style: italic; +} + @media (max-width: @breakpoint-md) { .JobResultsStdOut-numberColumnPreload { display: none; 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 bf4bac3799..f04b5d1fb6 100644 --- a/awx/ui/client/src/job-results/parse-stdout.service.js +++ b/awx/ui/client/src/job-results/parse-stdout.service.js @@ -27,7 +27,7 @@ export default ['$log', 'moment', function($log, moment){ line = line.replace(/u001b/g, ''); // ansi classes - line = line.replace(/\[1;im/g, ''); + line = line.replace(/\[1;im/g, ''); line = line.replace(/\[1;31m/g, ''); line = line.replace(/\[0;31m/g, ''); line = line.replace(/\[0;32m/g, ''); @@ -40,7 +40,6 @@ export default ['$log', 'moment', function($log, moment){ line = line.replace(/()\s/g, '$1'); //end span - line = line.replace(/\[0im/g, ''); line = line.replace(/\[0m/g, ''); } else { // For the host event modal in the standard out tab, @@ -205,7 +204,7 @@ export default ['$log', 'moment', function($log, moment){ let padBy = lineNums.length - lines.length; for (let i = 0; i <= padBy; i++) { - lines.push("[1;imline capped.[0im"); + lines.push("[1;imLine capped.[0m"); } } diff --git a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js index 9dada797b0..c4aeace3e7 100644 --- a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js +++ b/awx/ui/tests/spec/job-results/parse-stdout.service-test.js @@ -129,7 +129,7 @@ describe('parseStdoutService', () => { end_line: 11, stdout: "a\r\nb\r\nc..." }; - let expectedReturn = [[8, "a"],[9, "b"], [10,"c..."], [11, "[1;imline capped.[0im"]]; + let expectedReturn = [[8, "a"],[9, "b"], [10,"c..."], [11, "[1;imLine capped.[0m"]]; let returnedEvent = parseStdoutService.getLineArr(mockEvent);