- Add an AngularPollChannel class for easier handling of HTTP polling.
- Convert the build view page over to use the new class - Add code so that if the builds logs returned by the API start in the set we already have, we only add the new ones
This commit is contained in:
parent
8b3a3178b0
commit
8a94e38028
3 changed files with 110 additions and 57 deletions
|
@ -215,6 +215,78 @@ quayApp = angular.module('quay', quayDependencies, function($provide, cfpLoading
|
||||||
return service;
|
return service;
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized class for conducting an HTTP poll, while properly preventing multiple calls.
|
||||||
|
*/
|
||||||
|
$provide.factory('AngularPollChannel', ['ApiService', '$timeout', function(ApiService, $timeout) {
|
||||||
|
var _PollChannel = function(scope, requester, opt_sleeptime) {
|
||||||
|
this.scope_ = scope;
|
||||||
|
this.requester_ = requester;
|
||||||
|
this.sleeptime_ = opt_sleeptime || (60 * 1000 /* 60s */);
|
||||||
|
this.timer_ = null;
|
||||||
|
|
||||||
|
this.working = false;
|
||||||
|
this.polling = false;
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
scope.$on('$destroy', function() {
|
||||||
|
that.stop();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_PollChannel.prototype.stop = function() {
|
||||||
|
if (this.timer_) {
|
||||||
|
$timeout.cancel(this.timer_);
|
||||||
|
this.timer_ = null;
|
||||||
|
this.polling_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.working = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
_PollChannel.prototype.start = function() {
|
||||||
|
// Make sure we invoke call outside the normal digest cycle, since
|
||||||
|
// we'll call $scope.$apply ourselves.
|
||||||
|
var that = this;
|
||||||
|
setTimeout(function() { that.call_(); }, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
_PollChannel.prototype.call_ = function() {
|
||||||
|
if (this.working) { return; }
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
this.working = true;
|
||||||
|
this.scope_.$apply(function() {
|
||||||
|
that.requester_(function(status) {
|
||||||
|
if (status) {
|
||||||
|
that.working = false;
|
||||||
|
that.setupTimer_();
|
||||||
|
} else {
|
||||||
|
that.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
_PollChannel.prototype.setupTimer_ = function() {
|
||||||
|
if (this.timer_) { return; }
|
||||||
|
|
||||||
|
var that = this;
|
||||||
|
this.polling = true;
|
||||||
|
this.timer_ = $timeout(function() {
|
||||||
|
that.timer_ = null;
|
||||||
|
that.call_();
|
||||||
|
}, this.sleeptime_)
|
||||||
|
};
|
||||||
|
|
||||||
|
var service = {
|
||||||
|
'create': function(scope, requester, opt_sleeptime) {
|
||||||
|
return new _PollChannel(scope, requester, opt_sleeptime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return service;
|
||||||
|
}]);
|
||||||
|
|
||||||
$provide.factory('DataFileService', [function() {
|
$provide.factory('DataFileService', [function() {
|
||||||
var dataFileService = {};
|
var dataFileService = {};
|
||||||
|
|
|
@ -978,14 +978,9 @@ function BuildPackageCtrl($scope, Restangular, ApiService, DataFileService, $rou
|
||||||
}
|
}
|
||||||
|
|
||||||
function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize,
|
function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize,
|
||||||
ansi2html, AngularViewArray) {
|
ansi2html, AngularViewArray, AngularPollChannel) {
|
||||||
var namespace = $routeParams.namespace;
|
var namespace = $routeParams.namespace;
|
||||||
var name = $routeParams.name;
|
var name = $routeParams.name;
|
||||||
var pollTimerHandle = null;
|
|
||||||
|
|
||||||
$scope.$on('$destroy', function() {
|
|
||||||
stopPollTimer();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Watch for changes to the current parameter.
|
// Watch for changes to the current parameter.
|
||||||
$scope.$on('$routeUpdate', function(){
|
$scope.$on('$routeUpdate', function(){
|
||||||
|
@ -995,8 +990,7 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.builds = null;
|
$scope.builds = null;
|
||||||
$scope.polling = false;
|
$scope.pollChannel = null;
|
||||||
|
|
||||||
$scope.buildDialogShowCounter = 0;
|
$scope.buildDialogShowCounter = 0;
|
||||||
|
|
||||||
$scope.showNewBuildDialog = function() {
|
$scope.showNewBuildDialog = function() {
|
||||||
|
@ -1081,8 +1075,6 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
|
||||||
$scope.setCurrentBuildInternal = function(index, build, opt_updateURL) {
|
$scope.setCurrentBuildInternal = function(index, build, opt_updateURL) {
|
||||||
if (build == $scope.currentBuild) { return; }
|
if (build == $scope.currentBuild) { return; }
|
||||||
|
|
||||||
stopPollTimer();
|
|
||||||
|
|
||||||
$scope.logEntries = null;
|
$scope.logEntries = null;
|
||||||
$scope.logStartIndex = null;
|
$scope.logStartIndex = null;
|
||||||
$scope.currentParentEntry = null;
|
$scope.currentParentEntry = null;
|
||||||
|
@ -1103,47 +1095,35 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
|
||||||
$scope.adjustLogHeight();
|
$scope.adjustLogHeight();
|
||||||
}, 1);
|
}, 1);
|
||||||
|
|
||||||
// Load the first set of logs.
|
// Stop any existing polling.
|
||||||
getBuildStatusAndLogs();
|
if ($scope.pollChannel) {
|
||||||
|
$scope.pollChannel.stop();
|
||||||
// If the build is currently processing, start the build timer.
|
|
||||||
checkPollTimer();
|
|
||||||
};
|
|
||||||
|
|
||||||
var checkPollTimer = function() {
|
|
||||||
var build = $scope.currentBuild;
|
|
||||||
if (!build) {
|
|
||||||
stopPollTimer();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a new channel for polling the build status and logs.
|
||||||
|
var conductStatusAndLogRequest = function(callback) {
|
||||||
|
getBuildStatusAndLogs(build, callback);
|
||||||
|
};
|
||||||
|
|
||||||
if (build['phase'] != 'complete' && build['phase'] != 'error') {
|
$scope.pollChannel = AngularPollChannel.create($scope, conductStatusAndLogRequest, 5 * 1000 /* 5s */);
|
||||||
startPollTimer();
|
$scope.pollChannel.start();
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
stopPollTimer();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var stopPollTimer = function() {
|
var processLogs = function(logs, startIndex, endIndex) {
|
||||||
$interval.cancel(pollTimerHandle);
|
|
||||||
};
|
|
||||||
|
|
||||||
var startPollTimer = function() {
|
|
||||||
stopPollTimer();
|
|
||||||
pollTimerHandle = $interval(getBuildStatusAndLogs, 2000);
|
|
||||||
};
|
|
||||||
|
|
||||||
var processLogs = function(logs, startIndex) {
|
|
||||||
if (!$scope.logEntries) { $scope.logEntries = []; }
|
if (!$scope.logEntries) { $scope.logEntries = []; }
|
||||||
|
|
||||||
|
// If the start index given is less than that requested, then we've received a larger
|
||||||
|
// pool of logs, and we need to only consider the new ones.
|
||||||
|
if (startIndex < $scope.logStartIndex) {
|
||||||
|
logs = logs.slice($scope.logStartIndex - startIndex);
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < logs.length; ++i) {
|
for (var i = 0; i < logs.length; ++i) {
|
||||||
var entry = logs[i];
|
var entry = logs[i];
|
||||||
var type = entry['type'] || 'entry';
|
var type = entry['type'] || 'entry';
|
||||||
if (type == 'command' || type == 'phase' || type == 'error') {
|
if (type == 'command' || type == 'phase' || type == 'error') {
|
||||||
entry['logs'] = AngularViewArray.create();
|
entry['logs'] = AngularViewArray.create();
|
||||||
entry['index'] = startIndex + i;
|
entry['index'] = $scope.logStartIndex + i;
|
||||||
|
|
||||||
$scope.logEntries.push(entry);
|
$scope.logEntries.push(entry);
|
||||||
$scope.currentParentEntry = entry;
|
$scope.currentParentEntry = entry;
|
||||||
|
@ -1151,18 +1131,19 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
|
||||||
$scope.currentParentEntry['logs'].push(entry);
|
$scope.currentParentEntry['logs'].push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return endIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
var getBuildStatusAndLogs = function() {
|
var getBuildStatusAndLogs = function(build, callback) {
|
||||||
if (!$scope.currentBuild || $scope.polling) { return; }
|
|
||||||
$scope.polling = true;
|
|
||||||
|
|
||||||
var params = {
|
var params = {
|
||||||
'repository': namespace + '/' + name,
|
'repository': namespace + '/' + name,
|
||||||
'build_uuid': $scope.currentBuild.id
|
'build_uuid': build.id
|
||||||
};
|
};
|
||||||
|
|
||||||
ApiService.getRepoBuildStatus(null, params, true).then(function(resp) {
|
ApiService.getRepoBuildStatus(null, params, true).then(function(resp) {
|
||||||
|
if (build != $scope.currentBuild) { callback(false); return; }
|
||||||
|
|
||||||
// Note: We use extend here rather than replacing as Angular is depending on the
|
// Note: We use extend here rather than replacing as Angular is depending on the
|
||||||
// root build object to remain the same object.
|
// root build object to remain the same object.
|
||||||
var matchingBuilds = $.grep($scope.builds, function(elem) {
|
var matchingBuilds = $.grep($scope.builds, function(elem) {
|
||||||
|
@ -1177,22 +1158,16 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
|
||||||
$scope.builds.push(currentBuild);
|
$scope.builds.push(currentBuild);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPollTimer();
|
|
||||||
|
|
||||||
// Load the updated logs for the build.
|
// Load the updated logs for the build.
|
||||||
var options = {
|
var options = {
|
||||||
'start': $scope.logStartIndex
|
'start': $scope.logStartIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) {
|
ApiService.getRepoBuildLogsAsResource(params, true).withOptions(options).get(function(resp) {
|
||||||
if ($scope.logStartIndex != null && resp['start'] != $scope.logStartIndex) {
|
if (build != $scope.currentBuild) { callback(false); return; }
|
||||||
$scope.polling = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
processLogs(resp['logs'], resp['start']);
|
// Process the logs we've received.
|
||||||
$scope.logStartIndex = resp['total'];
|
$scope.logStartIndex = processLogs(resp['logs'], resp['start'], resp['total']);
|
||||||
$scope.polling = false;
|
|
||||||
|
|
||||||
// If the build status is an error, open the last two log entries.
|
// If the build status is an error, open the last two log entries.
|
||||||
if (currentBuild['phase'] == 'error' && $scope.logEntries.length > 1) {
|
if (currentBuild['phase'] == 'error' && $scope.logEntries.length > 1) {
|
||||||
|
@ -1205,9 +1180,15 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
|
||||||
openLogEntries($scope.logEntries[$scope.logEntries.length - 2]);
|
openLogEntries($scope.logEntries[$scope.logEntries.length - 2]);
|
||||||
openLogEntries($scope.logEntries[$scope.logEntries.length - 1]);
|
openLogEntries($scope.logEntries[$scope.logEntries.length - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the build phase is an error or a complete, then we mark the channel
|
||||||
|
// as closed.
|
||||||
|
callback(currentBuild['phase'] != 'error' && currentBuild['phase'] != 'complete');
|
||||||
}, function() {
|
}, function() {
|
||||||
$scope.polling = false;
|
callback(false);
|
||||||
});
|
});
|
||||||
|
}, function() {
|
||||||
|
callback(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-top: 10px">
|
<div style="margin-top: 10px">
|
||||||
<span class="quay-spinner" ng-show="polling"></span>
|
<span class="quay-spinner" ng-show="pollChannel.working"></span>
|
||||||
<button class="btn" ng-show="(build.phase == 'error' || build.phase == 'complete') && build.resource_key"
|
<button class="btn" ng-show="(build.phase == 'error' || build.phase == 'complete') && build.resource_key"
|
||||||
ng-class="build.phase == 'error' ? 'btn-success' : 'btn-default'"
|
ng-class="build.phase == 'error' ? 'btn-success' : 'btn-default'"
|
||||||
ng-click="askRestartBuild(build)">
|
ng-click="askRestartBuild(build)">
|
||||||
|
|
Reference in a new issue