This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/static/js/directives/repo-view/repo-panel-changes.js
Joseph Schorr 4160b720f9 UI and API improvements for working with large repositories
- Change the tag check bar to only select the current page (by default), but allow for selecting ALL tags
- Limit the number of tags compared in the visualization view to 10
- Fix the multiselect dropdown to limit itself to 10 items selected
- Remove saving the selected tags in the URL, as it is too slow and overloads the URLs in Chrome when there are 1000+ tags selected
- Change the images API to not return locations: By skipping the extra join and looping, it made the /images API call 10x faster (in hand tests)

Fixes #292
Fixes #293
2015-07-31 16:31:29 -04:00

215 lines
6 KiB
JavaScript

/**
* An element which displays the changes visualization panel for a repository view.
*/
angular.module('quay').directive('repoPanelChanges', function () {
var RepositoryImageTracker = function(repository, images) {
this.repository = repository;
this.images = images;
// Build a map of image ID -> image.
var imageIDMap = {};
this.images.map(function(image) {
imageIDMap[image.id] = image;
});
this.imageMap_ = imageIDMap;
};
RepositoryImageTracker.prototype.imageLink = function(image) {
return '/repository/' + this.repository.namespace + '/' +
this.repository.name + '/image/' + image;
};
RepositoryImageTracker.prototype.getImageForTag = function(tag) {
var tagData = this.lookupTag(tag);
if (!tagData) { return null; }
return this.imageMap_[tagData.image_id];
};
RepositoryImageTracker.prototype.lookupTag = function(tag) {
return this.repository.tags[tag];
};
RepositoryImageTracker.prototype.lookupImage = function(image) {
return this.imageMap_[image];
};
RepositoryImageTracker.prototype.forAllTagImages = function(tag, callback) {
var tagData = this.lookupTag(tag);
if (!tagData) { return; }
var tagImage = this.imageMap_[tagData.image_id];
if (!tagImage) { return; }
// Callback the tag's image itself.
callback(tagImage);
// Callback any parent images.
if (!tagImage.ancestors) { return; }
var ancestors = tagImage.ancestors.split('/');
for (var i = 0; i < ancestors.length; ++i) {
var image = this.imageMap_[ancestors[i]];
if (image) {
callback(image);
}
}
};
RepositoryImageTracker.prototype.getTotalSize = function(tag) {
var size = 0;
this.forAllTagImages(tag, function(image) {
size += image.size;
});
return size;
};
RepositoryImageTracker.prototype.getImagesForTagBySize = function(tag) {
var images = [];
this.forAllTagImages(tag, function(image) {
images.push(image);
});
images.sort(function(a, b) {
return b.size - a.size;
});
return images;
};
///////////////////////////////////////////////////////////////////////////////////////
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-view/repo-panel-changes.html',
replace: false,
transclude: false,
restrict: 'C',
scope: {
'repository': '=repository',
'selectedTags': '=selectedTags',
'imagesResource': '=imagesResource',
'images': '=images',
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, $timeout, ApiService, UtilService, ImageMetadataService) {
$scope.tagNames = [];
$scope.$watch('selectedTags', function(selectedTags) {
if (!selectedTags) { return; }
$scope.selectedTagsSlice = selectedTags.slice(0, 10);
});
var update = function() {
if (!$scope.repository || !$scope.isEnabled) { return; }
$scope.tagNames = Object.keys($scope.repository.tags);
$scope.currentImage = null;
$scope.currentTag = null;
if ($scope.tracker) {
refreshTree();
} else {
updateImages();
}
};
var updateImages = function() {
if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
if ($scope.selectedTagsSlice && $scope.selectedTagsSlice.length) {
refreshTree();
}
};
$scope.$watch('selectedTagsSlice', update)
$scope.$watch('repository', update);
$scope.$watch('isEnabled', update);
$scope.$watch('images', updateImages);
$scope.updateState = function() {
update();
};
var refreshTree = function() {
if (!$scope.repository || !$scope.images || !$scope.isEnabled) { return; }
if ($scope.selectedTagsSlice.length < 1) { return; }
$('#image-history-container').empty();
var tree = new ImageHistoryTree(
$scope.repository.namespace,
$scope.repository.name,
$scope.images,
UtilService.getFirstMarkdownLineAsText,
$scope.getTimeSince,
ImageMetadataService.getEscapedFormattedCommand,
function(tag) {
return $.inArray(tag, $scope.selectedTagsSlice) >= 0;
});
$scope.tree = tree.draw('image-history-container');
if ($scope.tree) {
// Give enough time for the UI to be drawn before we resize the tree.
$timeout(function() {
$scope.tree.notifyResized();
$scope.setTag($scope.selectedTagsSlice[0]);
}, 100);
// Listen for changes to the selected tag and image in the tree.
$($scope.tree).bind('tagChanged', function(e) {
$scope.$apply(function() { $scope.setTag(e.tag); });
});
$($scope.tree).bind('imageChanged', function(e) {
$scope.$apply(function() { $scope.setImage(e.image.id); });
});
}
};
$scope.setImage = function(image_id) {
$scope.currentTag = null;
$scope.currentImage = image_id;
$scope.tree.setImage(image_id);
};
$scope.setTag = function(tag) {
$scope.currentTag = tag;
$scope.currentImage = null;
$scope.tree.setTag(tag);
};
$scope.parseDate = function(dateString) {
return Date.parse(dateString);
};
$scope.getTimeSince = function(createdTime) {
return moment($scope.parseDate(createdTime)).fromNow();
};
$scope.handleTagChanged = function(data) {
$scope.tracker = new RepositoryImageTracker($scope.repository, $scope.images);
data.removed.map(function(tag) {
$scope.currentImage = null;
$scope.currentTag = null;
});
data.added.map(function(tag) {
$scope.selectedTags.push(tag);
$scope.currentTag = tag;
});
refreshTree();
};
}
};
return directiveDefinitionObject;
});