Switch to using an aggregated logs query and infinite scrolling
This should allow users to work with large logs set. Fixes #294
This commit is contained in:
parent
572d6ba53c
commit
3d6c92901c
15 changed files with 270 additions and 99 deletions
|
@ -37,7 +37,7 @@ quayPages.constant('pages', {
|
|||
|
||||
quayDependencies = ['ngRoute', 'chieffancypants.loadingBar', 'cfp.hotkeys', 'angular-tour', 'restangular', 'angularMoment',
|
||||
'mgcrea.ngStrap', 'ngCookies', 'ngSanitize', 'angular-md5', 'pasvaz.bindonce', 'ansiToHtml', 'debounce',
|
||||
'core-ui', 'core-config-setup', 'quayPages'];
|
||||
'core-ui', 'core-config-setup', 'quayPages', 'infinite-scroll'];
|
||||
|
||||
if (window.__config && window.__config.MIXPANEL_KEY) {
|
||||
quayDependencies.push('angulartics');
|
||||
|
|
|
@ -17,7 +17,7 @@ angular.module('quay').directive('feedbackBar', function () {
|
|||
|
||||
$scope.$watch('feedback', function(feedback) {
|
||||
if (feedback) {
|
||||
$scope.formattedMessage = StringBuilderService.buildString(feedback.message, feedback.data || {}, 'span');
|
||||
$scope.formattedMessage = StringBuilderService.buildTrustedString(feedback.message, feedback.data || {}, 'span');
|
||||
$scope.viewCounter++;
|
||||
} else {
|
||||
$scope.viewCounter = 0;
|
||||
|
|
|
@ -22,7 +22,8 @@ angular.module('quay').directive('logsView', function () {
|
|||
$scope.logs = null;
|
||||
$scope.kindsAllowed = null;
|
||||
$scope.chartVisible = true;
|
||||
$scope.logsPath = '';
|
||||
$scope.chartLoading = true;
|
||||
$scope.currentPage = 1;
|
||||
|
||||
$scope.options = {};
|
||||
|
||||
|
@ -253,6 +254,29 @@ angular.module('quay').directive('logsView', function () {
|
|||
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + days);
|
||||
};
|
||||
|
||||
var getUrl = function(suffix) {
|
||||
var url = UtilService.getRestUrl('user/' + suffix);
|
||||
if ($scope.organization) {
|
||||
url = UtilService.getRestUrl('organization', $scope.organization.name, suffix);
|
||||
}
|
||||
if ($scope.repository) {
|
||||
url = UtilService.getRestUrl('repository', $scope.repository.namespace, $scope.repository.name, suffix);
|
||||
}
|
||||
|
||||
if ($scope.allLogs) {
|
||||
url = UtilService.getRestUrl('superuser', suffix)
|
||||
}
|
||||
|
||||
url += '?starttime=' + encodeURIComponent(getDateString($scope.options.logStartDate));
|
||||
url += '&endtime=' + encodeURIComponent(getDateString($scope.options.logEndDate));
|
||||
|
||||
if ($scope.performer) {
|
||||
url += '&performer=' + encodeURIComponent($scope.performer.name);
|
||||
}
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
var update = function() {
|
||||
var hasValidUser = !!$scope.user;
|
||||
var hasValidOrg = !!$scope.organization;
|
||||
|
@ -269,44 +293,44 @@ angular.module('quay').directive('logsView', function () {
|
|||
$scope.options.logStartDate = twoWeeksAgo;
|
||||
}
|
||||
|
||||
$scope.chartLoading = true;
|
||||
|
||||
var aggregateUrl = getUrl('aggregatelogs')
|
||||
|
||||
var loadAggregate = Restangular.one(aggregateUrl);
|
||||
loadAggregate.customGET().then(function(resp) {
|
||||
$scope.chart = new LogUsageChart(logKinds);
|
||||
$($scope.chart).bind('filteringChanged', function(e) {
|
||||
$scope.$apply(function() { $scope.kindsAllowed = e.allowed; });
|
||||
});
|
||||
|
||||
$scope.chart.draw('bar-chart', resp.aggregated, $scope.options.logStartDate,
|
||||
$scope.options.logEndDate);
|
||||
$scope.chartLoading = false;
|
||||
});
|
||||
|
||||
$scope.currentPage = 0;
|
||||
$scope.hasAdditional = true;
|
||||
$scope.loading = false;
|
||||
$scope.logs = [];
|
||||
$scope.nextPage();
|
||||
};
|
||||
|
||||
$scope.nextPage = function() {
|
||||
if ($scope.loading) { return; }
|
||||
|
||||
$scope.loading = true;
|
||||
|
||||
// Note: We construct the URLs here manually because we also use it for the download
|
||||
// path.
|
||||
var url = UtilService.getRestUrl('user/logs');
|
||||
if ($scope.organization) {
|
||||
url = UtilService.getRestUrl('organization', $scope.organization.name, 'logs');
|
||||
}
|
||||
if ($scope.repository) {
|
||||
url = UtilService.getRestUrl('repository', $scope.repository.namespace, $scope.repository.name, 'logs');
|
||||
}
|
||||
|
||||
if ($scope.allLogs) {
|
||||
url = UtilService.getRestUrl('superuser', 'logs')
|
||||
}
|
||||
|
||||
url += '?starttime=' + encodeURIComponent(getDateString($scope.options.logStartDate));
|
||||
url += '&endtime=' + encodeURIComponent(getDateString($scope.options.logEndDate));
|
||||
|
||||
if ($scope.performer) {
|
||||
url += '&performer=' + encodeURIComponent($scope.performer.name);
|
||||
}
|
||||
|
||||
var loadLogs = Restangular.one(url);
|
||||
var logsUrl = getUrl('logs') + '&page=' + ($scope.currentPage + 1);
|
||||
var loadLogs = Restangular.one(logsUrl);
|
||||
loadLogs.customGET().then(function(resp) {
|
||||
$scope.logsPath = '/api/v1/' + url;
|
||||
resp.logs.forEach(function(log) {
|
||||
$scope.logs.push(log);
|
||||
});
|
||||
|
||||
if (!$scope.chart) {
|
||||
$scope.chart = new LogUsageChart(logKinds);
|
||||
$($scope.chart).bind('filteringChanged', function(e) {
|
||||
$scope.$apply(function() { $scope.kindsAllowed = e.allowed; });
|
||||
});
|
||||
}
|
||||
|
||||
$scope.chart.draw('bar-chart', resp.logs, $scope.options.logStartDate, $scope.options.logEndDate);
|
||||
$scope.kindsAllowed = null;
|
||||
$scope.logs = resp.logs;
|
||||
$scope.loading = false;
|
||||
$scope.currentPage = resp.page;
|
||||
$scope.hasAdditional = resp.has_additional;
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -318,8 +342,9 @@ angular.module('quay').directive('logsView', function () {
|
|||
return allowed == null || allowed.hasOwnProperty(kind);
|
||||
};
|
||||
|
||||
$scope.getColor = function(kind) {
|
||||
return $scope.chart.getColor(kind);
|
||||
$scope.getColor = function(kind, chart) {
|
||||
if (!chart) { return ''; }
|
||||
return chart.getColor(kind);
|
||||
};
|
||||
|
||||
$scope.getDescription = function(log) {
|
||||
|
|
|
@ -1616,22 +1616,22 @@ UsageChart.prototype.draw = function(container) {
|
|||
function LogUsageChart(titleMap) {
|
||||
this.titleMap_ = titleMap;
|
||||
this.colorScale_ = d3.scale.category20();
|
||||
this.entryMap_ = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the D3-representation of the data.
|
||||
*/
|
||||
LogUsageChart.prototype.buildData_ = function(logs) {
|
||||
LogUsageChart.prototype.buildData_ = function(aggregatedLogs) {
|
||||
var parseDate = d3.time.format("%a, %d %b %Y %H:%M:%S %Z").parse
|
||||
|
||||
// Build entries for each kind of event that occurred, on each day. We have one
|
||||
// entry per {kind, day} pair.
|
||||
var map = {};
|
||||
var entries = [];
|
||||
for (var i = 0; i < logs.length; ++i) {
|
||||
var log = logs[i];
|
||||
var title = this.titleMap_[log.kind] || log.kind;
|
||||
var datetime = parseDate(log.datetime);
|
||||
for (var i = 0; i < aggregatedLogs.length; ++i) {
|
||||
var aggregated = aggregatedLogs[i];
|
||||
var title = this.titleMap_[aggregated.kind] || aggregated.kind;
|
||||
var datetime = parseDate(aggregated.datetime);
|
||||
var dateDay = datetime.getDate();
|
||||
if (dateDay < 10) {
|
||||
dateDay = '0' + dateDay;
|
||||
|
@ -1640,24 +1640,18 @@ LogUsageChart.prototype.buildData_ = function(logs) {
|
|||
var formatted = (datetime.getMonth() + 1) + '/' + dateDay;
|
||||
var adjusted = new Date(datetime.getFullYear(), datetime.getMonth(), datetime.getDate());
|
||||
var key = title + '_' + formatted;
|
||||
var found = map[key];
|
||||
if (!found) {
|
||||
found = {
|
||||
'kind': log.kind,
|
||||
'title': title,
|
||||
'adjusted': adjusted,
|
||||
'formatted': datetime.getDate(),
|
||||
'count': 0
|
||||
};
|
||||
var entry = {
|
||||
'kind': aggregated.kind,
|
||||
'title': title,
|
||||
'adjusted': adjusted,
|
||||
'formatted': datetime.getDate(),
|
||||
'count': aggregated.count
|
||||
};
|
||||
|
||||
map[key] = found;
|
||||
entries.push(found);
|
||||
}
|
||||
found['count']++;
|
||||
entries.push(entry);
|
||||
this.entryMap_[key] = entry;
|
||||
}
|
||||
|
||||
this.entries_ = map;
|
||||
|
||||
// Build the data itself. We create a single entry for each possible kind of data, and then add (x, y) pairs
|
||||
// for the number of times that kind of event occurred on a particular day.
|
||||
var dataArray = [];
|
||||
|
@ -1727,7 +1721,7 @@ LogUsageChart.prototype.renderTooltip_ = function(d, e) {
|
|||
}
|
||||
|
||||
var key = d + '_' + e;
|
||||
var entry = this.entries_[key];
|
||||
var entry = this.entryMap_[key];
|
||||
if (!entry) {
|
||||
entry = {'count': 0};
|
||||
}
|
||||
|
|
|
@ -174,7 +174,7 @@ function($rootScope, $interval, UserService, ApiService, StringBuilderService, P
|
|||
if (!kindInfo) {
|
||||
return '(Unknown notification kind: ' + notification['kind'] + ')';
|
||||
}
|
||||
return StringBuilderService.buildString(kindInfo['message'], notification['metadata']);
|
||||
return StringBuilderService.buildTrustedString(kindInfo['message'], notification['metadata']);
|
||||
};
|
||||
|
||||
notificationService.getClass = function(notification) {
|
||||
|
|
|
@ -39,6 +39,10 @@ angular.module('quay').factory('StringBuilderService', ['$sce', 'UtilService', f
|
|||
return url;
|
||||
};
|
||||
|
||||
stringBuilderService.buildTrustedString = function(value_or_func, metadata, opt_codetag) {
|
||||
return $sce.trustAsHtml(stringBuilderService.buildString(value_or_func, metadata, opt_codetag));
|
||||
};
|
||||
|
||||
stringBuilderService.buildString = function(value_or_func, metadata, opt_codetag) {
|
||||
var fieldIcons = {
|
||||
'inviter': 'user',
|
||||
|
@ -113,7 +117,7 @@ angular.module('quay').factory('StringBuilderService', ['$sce', 'UtilService', f
|
|||
'<' + codeTag + ' title="' + safe + '">' + markedDown + '</' + codeTag + '>');
|
||||
}
|
||||
}
|
||||
return $sce.trustAsHtml(description.replace('\n', '<br>'));
|
||||
return description.replace('\n', '<br>');
|
||||
};
|
||||
|
||||
return stringBuilderService;
|
||||
|
|
Reference in a new issue