From 3bfd29a6317d19698ed4d6f80f5c2d4730f36cc5 Mon Sep 17 00:00:00 2001 From: Chris Houseknecht Date: Thu, 26 Jun 2014 17:12:38 -0400 Subject: [PATCH] Job stdout page Fixed endless scroll, both up and down. It now models the Jenkins behavior. If live events are happening, it captures and displays them, always auto-scrolling down. If user srolls up, live events continue to be captured but auto-scrolling is suspended. If user scrolls all the way back down to bottom, auto-scrolling resumes. --- awx/ui/static/js/controllers/JobStdout.js | 161 +++++++++++++++------- awx/ui/static/lib/ansible/directives.js | 5 +- awx/ui/static/partials/job_stdout.html | 3 +- 3 files changed, 120 insertions(+), 49 deletions(-) diff --git a/awx/ui/static/js/controllers/JobStdout.js b/awx/ui/static/js/controllers/JobStdout.js index 38284b5c4a..bbacd2a6ef 100644 --- a/awx/ui/static/js/controllers/JobStdout.js +++ b/awx/ui/static/js/controllers/JobStdout.js @@ -16,12 +16,14 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, stdout_url, current_range, event_socket, - first_time=0, + first_time_up = 0, + first_time_down = 0, loaded_sections = [], event_queue = 0, - auto_scroll_down=true, + auto_scroll_down=true, // programmatic scroll to bottom live_event_processing = true, should_apply_live_events = true, + prior_mcs, page_size = 500; event_socket = Socket({ @@ -45,14 +47,14 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, $rootScope.removeJobStatusChange = $rootScope.$on('JobStatusChange', function(e, data) { // if we receive a status change event for the current job indicating the job // is finished, stop event queue processing and reload - if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10)) { + if (parseInt(data.unified_job_id, 10) === parseInt(job_id,10) && $scope.job) { $scope.job.status = data.status; if (data.status === 'failed' || data.status === 'canceled' || data.status === 'error' || data.status === 'successful') { if ($rootScope.jobStdOutInterval) { window.clearInterval($rootScope.jobStdOutInterval); } - if (live_event_processing && should_apply_live_events) { + if (live_event_processing) { getNextSection(); } live_event_processing = false; @@ -61,20 +63,20 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, }); $rootScope.jobStdOutInterval = setInterval( function() { - // limit the scrolling to every 5 seconds - if (event_queue > 5) { + if (event_queue > 0) { + // events happened since the last check $log.debug('checking for stdout...'); if (loaded_sections.length === 0) { $log.debug('calling LoadStdout'); $scope.$emit('LoadStdout'); } - else { + else if (live_event_processing) { $log.debug('calling getNextSection'); getNextSection(); } event_queue = 0; } - }, 1500); + }, 2000); if ($scope.removeLoadStdout) { $scope.removeLoadStdout(); @@ -88,7 +90,10 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, api_complete = true; $('#pre-container-content').html(data.content); current_range = data.range; - loaded_sections.push(data.range.start); + loaded_sections.push({ + start: (data.range.start < 0) ? 0 : data.range.start, + end: data.range.end + }); setTimeout(function() { $('#pre-container').mCustomScrollbar("scrollTo", "bottom"); }, 300); @@ -136,22 +141,42 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, $scope.onTotalScroll = function() { // scroll forward or into the future toward the end of the file - var start = current_range.end + 1, url; - if ((!live_event_processing) && (!auto_scroll_down) && loaded_sections.indexOf(start) < 0) { - url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (current_range.start + page_size); - first_time++; + var start, url; + if ((live_event_processing === false || (live_event_processing && should_apply_live_events === false)) && + auto_scroll_down === false) { + + if (loaded_sections.length > 0) { + start = loaded_sections[loaded_sections.length - 1].end + 1; + } + else { + start = 0; + } + url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (start + page_size); + first_time_down++; Wait('start'); Rest.setUrl(url); Rest.get() .success( function(data) { Wait('stop'); - if (data.content) { - $('#pre-container-content').append(data.content); - loaded_sections.push(data.range.start); - current_range = data.range; + if (loaded_sections.indexOf(start) < 0) { + if (data.content) { + $('#pre-container-content').append(data.content); + loaded_sections.push({ + start: (data.range.start < 0) ? 0 : data.range.start, + end: data.range.end + }); + current_range = data.range; + } } - //$("#pre-container").mCustomScrollbar("scrollTo", "bottom", {scrollInertia:0}); - //$('#pre-container').mCustomScrollbar("update"); + if (data.range.end === data.range.absolute_end) { + should_apply_live_events = true; //we're at the bottom + $log.debug('at the end. turned on live events'); + } + auto_scroll_down = true; + if (first_time_down === 1) { + $('#pre-container').mCustomScrollbar("update"); + } + $("#pre-container").mCustomScrollbar("scrollTo", "bottom", {scrollInertia:0}); }) .error(function(data, status) { ProcessErrors($scope, data, status, null, { hdr: 'Error!', @@ -165,11 +190,18 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, $scope.onTotalScrollBack = function() { // scroll up or back in time toward the beginning of the file - if ((!live_event_processing) && current_range.start > 0) { - //we haven't hit the top yet - var start = (current_range.start < page_size) ? 0 : current_range.start - page_size, - url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (current_range.start - 1); - first_time++; + var start, end, url; + if (loaded_sections.length > 0 && loaded_sections[0].start > 0) { + start = (loaded_sections[0].start - page_size > 0) ? loaded_sections[0].start - page_size : 0; + end = loaded_sections[0].start - 1; + } + else if (loaded_sections.length === 0) { + start = 0; + end = page_size; + } + if (start !== undefined && end !== undefined) { + url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + end; + first_time_up++; Wait('start'); Rest.setUrl(url); Rest.get() @@ -178,11 +210,13 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, var oldContentHeight, heightDiff; oldContentHeight=$("#pre-container .mCSB_container").innerHeight(); $('#pre-container-content').prepend(data.content); - loaded_sections.unshift(data.range.start); + loaded_sections.unshift({ + start: (data.range.start < 0) ? 0 : data.range.start, + end: data.range.end + }); current_range = data.range; heightDiff=$("#pre-container .mCSB_container").innerHeight() - oldContentHeight; - if (first_time === 1) { - //setTimeout(function() { $("#pre-container").mCustomScrollbar("scrollTo", heightDiff, {scrollInertia:0}); }, 300); + if (first_time_up === 1) { $('#pre-container').mCustomScrollbar("update"); } $("#pre-container").mCustomScrollbar("scrollTo", heightDiff, {scrollInertia:0}); @@ -194,32 +228,65 @@ function JobStdoutController ($log, $rootScope, $scope, $compile, $routeParams, } }; + $scope.whileScrolling = function(mcs) { + var direction; + if (prior_mcs !== undefined) { + if (mcs.topPct < prior_mcs.topPct && prior_mcs.topPct !== 100) { + direction = "up"; + } + else if (mcs.topPct > prior_mcs.topPct) { + direction = "down"; + } + else { + direction = "none"; + } + } + prior_mcs = mcs; + if (direction === "up") { + // user is scrollin up or back in time + $log.debug('user scrolled up. turned off live events.'); + should_apply_live_events = false; + } + }; + $scope.scrollStarted = function() { // user touched the scroll bar. stop applying live events and forcing - should_apply_live_events = false; - $log.debug('turned off live events'); + //if (auto_scroll_down === false) { + // should_apply_live_events = false; + // $log.debug('turned off live events'); + //} + //else { + // auto_scroll_down = false; + //} }; function getNextSection() { - var start = current_range.end + 1, url; - should_apply_live_events = true; //user scrolled all the way to bottom. if there are live events, start applying them - if (loaded_sections.indexOf(start) < 0) { - url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (current_range.start + page_size); - Wait('start'); - Rest.setUrl(url); - Rest.get() - .success( function(data) { - Wait('stop'); - $('#pre-container-content').append(data.content); - loaded_sections.push(data.range.start); - current_range = data.range; - $("#pre-container").mCustomScrollbar("scrollTo", "bottom", {scrollInertia:0}); - }) - .error(function(data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', - msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status }); + // get the next range of data from the API + var start = loaded_sections[loaded_sections.length - 1].end + 1, url; + url = stdout_url + '?format=json&start_line=' + start + '&end_line=' + (start + page_size); + Rest.setUrl(url); + Rest.get() + .success( function(data) { + $('#pre-container-content').append(data.content); + loaded_sections.push({ + start: (data.range.start < 0) ? 0 : data.range.start, + end: data.range.end }); - } + if (should_apply_live_events) { + // if user has not disabled live event view by scrolling upward, then scroll down to the new content + current_range = data.range; + auto_scroll_down = true; // prevent auto load from happening + first_time_down++; + if (first_time_down === 1) { + $('#pre-container').mCustomScrollbar("update"); + } + $("#pre-container").mCustomScrollbar("scrollTo", "bottom", {scrollInertia:0}); + } + }) + .error(function(data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', + msg: 'Failed to retrieve stdout for job: ' + job_id + '. GET returned: ' + status }); + }); } } diff --git a/awx/ui/static/lib/ansible/directives.js b/awx/ui/static/lib/ansible/directives.js index 87917f8b3e..83550786f6 100644 --- a/awx/ui/static/lib/ansible/directives.js +++ b/awx/ui/static/lib/ansible/directives.js @@ -723,7 +723,8 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job .directive('awCustomScroll', [ function() { return function(scope, element, attrs) { var theme = (attrs.scrollTheme) ? attrs.scrollTheme : 'dark-thin', - inertia = (attrs.scrollInertia) ? parseInt(attrs.scrollInertia,10) : 500; + inertia = (attrs.scrollInertia) ? parseInt(attrs.scrollInertia,10) : 500, + axis = ($(element).attr('data-scroll-axis')) ? $(element).attr('data-scroll-axis') : 'y'; $(element).mCustomScrollbar({ advanced:{ updateOnContentResize: true @@ -731,6 +732,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job scrollButtons: { enable: false }, + axis: axis, theme: theme, mouseWheel: true, scrollInertia: inertia, @@ -740,6 +742,7 @@ angular.module('AWDirectives', ['RestServices', 'Utilities', 'AuthService', 'Job onTotalScrollBack: scope[attrs.onTotalScrollBack], onTotalScrollBackOffset: attrs.onTotalScrollBackOffset, onScrollStart: scope[$(element).attr('data-on-scroll-start')], + whileScrolling: scope[$(element).attr('data-while-scrolling')], alwaysTriggerOffsets: false }, }); diff --git a/awx/ui/static/partials/job_stdout.html b/awx/ui/static/partials/job_stdout.html index 149adcb90b..7334be1282 100644 --- a/awx/ui/static/partials/job_stdout.html +++ b/awx/ui/static/partials/job_stdout.html @@ -16,7 +16,8 @@
{{ job.status }}
+ data-scroll-inertia="500" data-on-total-scroll="onTotalScroll" data-on-total-scroll-back="onTotalScrollBack" data-on-scroll-start="scrollStarted" + data-while-scrolling="whileScrolling" data-on-total-scroll-back-offset="100" data-scroll-axis="yx">