Performance improvements for the repo API and the new repo UI

This commit is contained in:
Joseph Schorr 2015-03-18 14:47:53 -04:00
parent bc6baae050
commit ab2331a486
9 changed files with 119 additions and 100 deletions

View file

@ -1146,6 +1146,9 @@ def get_repo_image_extended(namespace_name, repository_name, docker_image_id):
return images[0] return images[0]
def is_repository_public(repository):
return repository.visibility == _get_public_repo_visibility()
def repository_is_public(namespace_name, repository_name): def repository_is_public(namespace_name, repository_name):
try: try:
(Repository (Repository
@ -1579,9 +1582,15 @@ def _tag_alive(query):
(RepositoryTag.lifetime_end_ts > int(time.time()))) (RepositoryTag.lifetime_end_ts > int(time.time())))
def list_repository_tags(namespace_name, repository_name, include_hidden=False): def list_repository_tags(namespace_name, repository_name, include_hidden=False,
include_storage=False):
toSelect = (RepositoryTag, Image)
if include_storage:
toSelect = (RepositoryTag, Image, ImageStorage)
query = _tag_alive(RepositoryTag query = _tag_alive(RepositoryTag
.select(RepositoryTag, Image) .select(*toSelect)
.join(Repository) .join(Repository)
.join(Namespace, on=(Repository.namespace_user == Namespace.id)) .join(Namespace, on=(Repository.namespace_user == Namespace.id))
.switch(RepositoryTag) .switch(RepositoryTag)
@ -1592,6 +1601,9 @@ def list_repository_tags(namespace_name, repository_name, include_hidden=False):
if not include_hidden: if not include_hidden:
query = query.where(RepositoryTag.hidden == False) query = query.where(RepositoryTag.hidden == False)
if include_storage:
query = query.switch(Image).join(ImageStorage)
return query return query

View file

@ -17,7 +17,7 @@ def image_view(image, image_map):
command = extended_props.command command = extended_props.command
def docker_id(aid): def docker_id(aid):
if not aid: if not aid or not aid in image_map:
return '' return ''
return image_map[aid] return image_map[aid]
@ -51,19 +51,26 @@ class RepositoryImageList(RepositoryParamResource):
all_tags = model.list_repository_tags(namespace, repository) all_tags = model.list_repository_tags(namespace, repository)
tags_by_image_id = defaultdict(list) tags_by_image_id = defaultdict(list)
found_image_ids = set()
for tag in all_tags: for tag in all_tags:
tags_by_image_id[tag.image.docker_image_id].append(tag.name) tags_by_image_id[tag.image.docker_image_id].append(tag.name)
found_image_ids.add(str(tag.image.id))
found_image_ids.update(tag.image.ancestors.split('/')[1:-1])
image_map = {} image_map = {}
filtered_images = []
for image in all_images: for image in all_images:
if str(image.id) in found_image_ids:
image_map[str(image.id)] = image.docker_image_id image_map[str(image.id)] = image.docker_image_id
filtered_images.append(image)
def add_tags(image_json): def add_tags(image_json):
image_json['tags'] = tags_by_image_id[image_json['id']] image_json['tags'] = tags_by_image_id[image_json['id']]
return image_json return image_json
return { return {
'images': [add_tags(image_view(image, image_map)) for image in all_images] 'images': [add_tags(image_view(image, image_map)) for image in filtered_images]
} }

View file

@ -188,16 +188,9 @@ class Repository(RepositoryParamResource):
return tag_info return tag_info
organization = None
try:
organization = model.get_organization(namespace)
except model.InvalidOrganizationException:
pass
is_public = model.repository_is_public(namespace, repository)
repo = model.get_repository(namespace, repository) repo = model.get_repository(namespace, repository)
if repo: if repo:
tags = model.list_repository_tags(namespace, repository) tags = model.list_repository_tags(namespace, repository, include_storage=True)
tag_dict = {tag.name: tag_view(tag) for tag in tags} tag_dict = {tag.name: tag_view(tag) for tag in tags}
can_write = ModifyRepositoryPermission(namespace, repository).can() can_write = ModifyRepositoryPermission(namespace, repository).can()
can_admin = AdministerRepositoryPermission(namespace, repository).can() can_admin = AdministerRepositoryPermission(namespace, repository).can()
@ -206,6 +199,7 @@ class Repository(RepositoryParamResource):
is_starred = (model.repository_is_starred(get_authenticated_user(), repo) is_starred = (model.repository_is_starred(get_authenticated_user(), repo)
if get_authenticated_user() else False) if get_authenticated_user() else False)
is_public = model.is_repository_public(repo)
return { return {
'namespace': namespace, 'namespace': namespace,
@ -216,7 +210,7 @@ class Repository(RepositoryParamResource):
'can_admin': can_admin, 'can_admin': can_admin,
'is_public': is_public, 'is_public': is_public,
'is_building': len(list(active_builds)) > 0, 'is_building': len(list(active_builds)) > 0,
'is_organization': bool(organization), 'is_organization': repo.namespace_user.organization,
'is_starred': is_starred, 'is_starred': is_starred,
'status_token': repo.badge_token if not is_public else '', 'status_token': repo.badge_token if not is_public else '',
'stats': { 'stats': {

View file

@ -1,4 +1,6 @@
<div class="repo-panel-changes-element"> <div class="repo-panel-changes-element">
<div class="resource-view" resource="imagesResource"
error-message="'Could not load repository images'">
<!-- No Tags Selected --> <!-- No Tags Selected -->
<div class="empty" ng-if="!selectedTags.length"> <div class="empty" ng-if="!selectedTags.length">
<div class="empty-primary-msg">No tags selected to view</div> <div class="empty-primary-msg">No tags selected to view</div>
@ -57,6 +59,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
<div class="tag-operations-dialog" repository="repository" images="images" <div class="tag-operations-dialog" repository="repository" images="images"

View file

@ -1,7 +1,6 @@
<div class="repo-panel-tags-element"> <div class="repo-panel-tags-element">
<h3 class="tab-header">Repository Tags</h3> <h3 class="tab-header">Repository Tags</h3>
<div class="resource-view" resource="imagesResource" error-message="'Could not load images'"> <div class="resource-view" resource="imagesResource" error-message="'Could not load images'">
<div class="co-check-bar"> <div class="co-check-bar">
<span class="cor-checkable-menu" controller="checkedTags"> <span class="cor-checkable-menu" controller="checkedTags">
<div class="cor-checkable-menu-item" item-filter="allTagFilter"> <div class="cor-checkable-menu-item" item-filter="allTagFilter">

View file

@ -89,6 +89,10 @@ angular.module('quay').directive('repoPanelChanges', function () {
scope: { scope: {
'repository': '=repository', 'repository': '=repository',
'selectedTags': '=selectedTags', 'selectedTags': '=selectedTags',
'imagesResource': '=imagesResource',
'images': '=images',
'isEnabled': '=isEnabled' 'isEnabled': '=isEnabled'
}, },
controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) { controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
@ -99,13 +103,24 @@ angular.module('quay').directive('repoPanelChanges', function () {
$scope.currentImage = null; $scope.currentImage = null;
$scope.currentTag = null; $scope.currentTag = null;
if (!$scope.imagesResource) { if (!$scope.tracker) {
loadImages(); updateImages();
}
};
var updateImages = function() {
if (!$scope.repository || !$scope.images) { return; }
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
if ($scope.selectedTags && $scope.selectedTags.length) {
refreshTree();
} }
}; };
$scope.$watch('selectedTags', update) $scope.$watch('selectedTags', update)
$scope.$watch('repository', update); $scope.$watch('repository', update);
$scope.$watch('images', updateImages);
$scope.$watch('isEnabled', function(isEnabled) { $scope.$watch('isEnabled', function(isEnabled) {
if (isEnabled) { if (isEnabled) {
@ -147,23 +162,6 @@ angular.module('quay').directive('repoPanelChanges', function () {
} }
}; };
var loadImages = function(opt_callback) {
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
$scope.imagesResource = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
$scope.images = resp.images;
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
if ($scope.selectedTags && $scope.selectedTags.length) {
refreshTree();
}
opt_callback && opt_callback();
});
};
$scope.setImage = function(image_id) { $scope.setImage = function(image_id) {
$scope.currentTag = null; $scope.currentTag = null;
$scope.currentImage = image_id; $scope.currentImage = image_id;

View file

@ -10,7 +10,9 @@ angular.module('quay').directive('repoPanelTags', function () {
restrict: 'C', restrict: 'C',
scope: { scope: {
'repository': '=repository', 'repository': '=repository',
'selectedTags': '=selectedTags' 'selectedTags': '=selectedTags',
'imagesResource': '=imagesResource',
'images': '=images',
}, },
controller: function($scope, $element, $filter, $location, ApiService, UIService) { controller: function($scope, $element, $filter, $location, ApiService, UIService) {
var orderBy = $filter('orderBy'); var orderBy = $filter('orderBy');
@ -24,16 +26,6 @@ angular.module('quay').directive('repoPanelTags', function () {
$scope.iterationState = {}; $scope.iterationState = {};
$scope.tagActionHandler = null; $scope.tagActionHandler = null;
var loadImages = function() {
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
$scope.imagesResource = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
$scope.images = resp.images;
});
};
var setTagState = function() { var setTagState = function() {
if (!$scope.repository || !$scope.selectedTags) { return; } if (!$scope.repository || !$scope.selectedTags) { return; }
@ -124,9 +116,6 @@ angular.module('quay').directive('repoPanelTags', function () {
$scope.$watch('repository', function(repository) { $scope.$watch('repository', function(repository) {
if (!repository) { return; } if (!repository) { return; }
// Load the repository's images.
loadImages();
// Process each of the tags. // Process each of the tags.
setTagState(); setTagState();
}); });

View file

@ -21,6 +21,8 @@
$scope.viewScope = { $scope.viewScope = {
'selectedTags': [], 'selectedTags': [],
'repository': null, 'repository': null,
'images': null,
'imagesResource': null,
'builds': null, 'builds': null,
'changesVisible': false 'changesVisible': false
}; };
@ -70,6 +72,16 @@
}); });
}; };
var loadImages = function() {
var params = {
'repository': $scope.namespace + '/' + $scope.name
};
$scope.viewScope.imagesResource = ApiService.listRepositoryImagesAsResource(params).get(function(resp) {
$scope.viewScope.images = resp.images;
});
};
var loadRepositoryBuilds = function(callback) { var loadRepositoryBuilds = function(callback) {
var params = { var params = {
'repository': $scope.namespace + '/' + $scope.name, 'repository': $scope.namespace + '/' + $scope.name,
@ -86,8 +98,9 @@
}, errorHandler); }, errorHandler);
}; };
// Load the repository. // Load the repository and images.
loadRepository(); loadRepository();
loadImages();
$scope.setTags = function(tagNames) { $scope.setTags = function(tagNames) {
if (!tagNames) { if (!tagNames) {

View file

@ -59,6 +59,8 @@
<div id="tags" class="tab-pane"> <div id="tags" class="tab-pane">
<div class="repo-panel-tags" <div class="repo-panel-tags"
repository="viewScope.repository" repository="viewScope.repository"
images="viewScope.images"
images-resource="viewScope.imagesResource"
selected-tags="viewScope.selectedTags"></div> selected-tags="viewScope.selectedTags"></div>
</div> </div>
@ -73,6 +75,8 @@
<div id="changes" class="tab-pane"> <div id="changes" class="tab-pane">
<div class="repo-panel-changes" <div class="repo-panel-changes"
repository="viewScope.repository" repository="viewScope.repository"
images="viewScope.images"
images-resource="viewScope.imagesResource"
selected-tags="viewScope.selectedTags" selected-tags="viewScope.selectedTags"
is-enabled="viewScope.changesVisible"></div> is-enabled="viewScope.changesVisible"></div>
</div> </div>