Fix the log view performance issues in the build history view by creating a specialized collection class that asynchronously adds the items to be displayed in a batch-like manner.

This commit is contained in:
Joseph Schorr 2014-03-18 15:08:46 -04:00
parent a727717add
commit 877427378d
3 changed files with 90 additions and 23 deletions

View file

@ -105,6 +105,86 @@ function getMarkedDown(string) {
quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angular-tour', 'restangular', 'angularMoment', 'angulartics', /*'angulartics.google.analytics',*/ 'angulartics.mixpanel', 'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml', 'ngAnimate'], function($provide, cfpLoadingBarProvider) {
cfpLoadingBarProvider.includeSpinner = false;
/**
* Specialized wrapper around array which provides a toggle() method for viewing the contents of the
* array in a manner that is asynchronously filled in over a short time period. This prevents long
* pauses in the UI for ngRepeat's when the array is significant in size.
*/
$provide.factory('AngularViewArray', ['$interval', function($interval) {
var ADDTIONAL_COUNT = 50;
function _ViewArray() {
this.isVisible = false;
this.visibleEntries = null;
this.hasEntries = false;
this.entries = [];
this.timerRef_ = null;
this.currentIndex_ = 0;
}
_ViewArray.prototype.push = function(elem) {
this.entries.push(elem);
this.hasEntries = true;
if (this.isVisible) {
this.setVisible(true);
}
};
_ViewArray.prototype.toggle = function() {
this.setVisible(!this.isVisible);
};
_ViewArray.prototype.setVisible = function(newState) {
this.isVisible = newState;
this.visibleEntries = [];
this.currentIndex_ = 0;
if (newState) {
this.showAdditionalEntries_();
this.startTimer_();
} else {
this.stopTimer_();
}
};
_ViewArray.prototype.showAdditionalEntries_ = function() {
var i = 0;
for (i = this.currentIndex_; i < (this.currentIndex_ + ADDTIONAL_COUNT) && i < this.entries.length; ++i) {
this.visibleEntries.push(this.entries[i]);
}
this.currentIndex_ = i;
if (this.currentIndex_ >= this.entries.length) {
this.stopTimer_();
}
};
_ViewArray.prototype.startTimer_ = function() {
var that = this;
this.timerRef_ = $interval(function() {
that.showAdditionalEntries_();
}, 10);
};
_ViewArray.prototype.stopTimer_ = function() {
if (this.timerRef_) {
$interval.cancel(this.timerRef_);
this.timerRef_ = null;
}
};
var service = {
'create': function() {
return new _ViewArray();
}
};
return service;
}]);
$provide.factory('UtilService', ['$sanitize', function($sanitize) {
var utilService = {};

View file

@ -924,7 +924,8 @@ function BuildPackageCtrl($scope, Restangular, ApiService, $routeParams, $rootSc
getBuildInfo();
}
function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize, ansi2html) {
function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope, $location, $interval, $sanitize,
ansi2html, AngularViewArray) {
var namespace = $routeParams.namespace;
var name = $routeParams.name;
var pollTimerHandle = null;
@ -990,19 +991,9 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
};
$scope.hasLogs = function(container) {
return ((container.logs && container.logs.length) || (container._logs && container._logs.length));
return container.logs.hasEntries;
};
$scope.toggleLogs = function(container) {
if (container._logs) {
container.logs = container._logs;
container._logs = null;
} else {
container._logs = container.logs;
container.logs = null;
}
};
$scope.setCurrentBuild = function(buildId, opt_updateURL) {
if (!$scope.builds) { return; }
@ -1090,17 +1081,13 @@ function RepoBuildCtrl($scope, Restangular, ApiService, $routeParams, $rootScope
var entry = logs[i];
var type = entry['type'] || 'entry';
if (type == 'command' || type == 'phase' || type == 'error') {
entry['_logs'] = [];
entry['logs'] = AngularViewArray.create();
entry['index'] = startIndex + i;
$scope.logEntries.push(entry);
$scope.currentParentEntry = entry;
$scope.currentParentEntry = entry;
} else if ($scope.currentParentEntry) {
if ($scope.currentParentEntry['logs']) {
$scope.currentParentEntry['logs'].push(entry);
} else {
$scope.currentParentEntry['_logs'].push(entry);
}
$scope.currentParentEntry['logs'].push(entry);
}
}
};

View file

@ -70,9 +70,9 @@
<div class="log-container" ng-class="container.type" ng-repeat="container in logEntries">
<div class="container-header" ng-class="container.type == 'phase' ? container.message : ''"
ng-switch on="container.type" ng-click="toggleLogs(container)">
ng-switch on="container.type" ng-click="container.logs.toggle()">
<i class="fa chevron"
ng-class="container.logs ? 'fa-chevron-down' : 'fa-chevron-right'" ng-show="hasLogs(container)"></i>
ng-class="container.logs.isVisible ? 'fa-chevron-down' : 'fa-chevron-right'" ng-show="hasLogs(container)"></i>
<div ng-switch-when="phase">
<span class="container-content build-log-phase" phase="container"></span>
</div>
@ -85,8 +85,8 @@
</div>
<!-- Display the entries for the container -->
<div class="container-logs" ng-show="container.logs">
<div class="log-entry" bindonce ng-repeat="entry in container.logs">
<div class="container-logs" ng-show="container.logs.isVisible">
<div class="log-entry" bindonce ng-repeat="entry in container.logs.visibleEntries">
<span class="id" bo-text="$index + container.index + 1"></span>
<span class="message" bo-html="processANSI(entry.message, container)"></span>
</div>