Fix UI rendering issue when creating/deleting tags from the UI (#3269)

### Description of Changes

Tag operations in UI would not be rendered properly when using the paginated tags endpoint.
When a user would create/delete a tag from the repo-panel-tags, `digest` would be called. This caused the `$scope.repository.tags` to be removed.

To fix this:
* Bind the tags directly to the scope instead of the repository
* Change references to scope.repository.tags to use scope.repositoryTags

---
This commit is contained in:
Kenny Lee Sin Cheong 2018-10-23 13:26:40 -04:00 committed by GitHub
parent bb01e08d44
commit 8b25d5b77b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 69 additions and 37 deletions

View file

@ -347,7 +347,7 @@
</div> </div>
</div> </div>
<div class="tag-operations-dialog" repository="repository" <div class="tag-operations-dialog" repository="repository" repository-tags="repositoryTags"
image-loader="imageLoader" image-loader="imageLoader"
action-handler="tagActionHandler" action-handler="tagActionHandler"
labels-changed="handleLabelsChanged(manifest_digest)"></div> labels-changed="handleLabelsChanged(manifest_digest)"></div>

View file

@ -71,6 +71,7 @@
<div class="tag-specific-images-view" <div class="tag-specific-images-view"
tag="tagToCreate" tag="tagToCreate"
repository="repository" repository="repository"
repository-tags="repositoryTags"
image-cutoff="toTagImage" image-cutoff="toTagImage"
style="margin: 10px; margin-top: 20px; margin-bottom: -10px;" style="margin: 10px; margin-top: 20px; margin-bottom: -10px;"
ng-show="isAnotherImageTag(toTagImage, tagToCreate)" ng-show="isAnotherImageTag(toTagImage, tagToCreate)"
@ -143,7 +144,7 @@
Are you sure you want to delete tag Are you sure you want to delete tag
<span class="label label-default tag">{{ deleteTagInfo.tag }}</span>? <span class="label label-default tag">{{ deleteTagInfo.tag }}</span>?
<div class="tag-specific-images-view" tag="deleteTagInfo.tag" repository="repository" <div class="tag-specific-images-view" tag="deleteTagInfo.tag" repository="repository" repository-tags="repositoryTags"
image-loader="imageLoader" style="margin-top: 20px"> image-loader="imageLoader" style="margin-top: 20px">
The following images and any other images not referenced by a tag will be <span ng-if="repository.tag_expiration_s == 0">deleted</span><span ng-if="repository.tag_expiration_s != 0">unavailable and deleted in {{ getFormattedTimespan(repository.tag_expiration_s) }}</span>: The following images and any other images not referenced by a tag will be <span ng-if="repository.tag_expiration_s == 0">deleted</span><span ng-if="repository.tag_expiration_s != 0">unavailable and deleted in {{ getFormattedTimespan(repository.tag_expiration_s) }}</span>:
</div> </div>

View file

@ -10,6 +10,7 @@ angular.module('quay').directive('repoPanelTags', function () {
restrict: 'C', restrict: 'C',
scope: { scope: {
'repository': '=repository', 'repository': '=repository',
'repositoryTags': '=repositoryTags',
'selectedTags': '=selectedTags', 'selectedTags': '=selectedTags',
'historyFilter': '=historyFilter', 'historyFilter': '=historyFilter',
'imagesResource': '=imagesResource', 'imagesResource': '=imagesResource',
@ -64,14 +65,14 @@ angular.module('quay').directive('repoPanelTags', function () {
}; };
var setTagState = function() { var setTagState = function() {
if (!$scope.repository || !$scope.selectedTags) { return; } if (!$scope.repositoryTags || !$scope.selectedTags) { return; }
// Build a list of all the tags, with extending information. // Build a list of all the tags, with extending information.
var allTags = []; var allTags = [];
for (var tag in $scope.repository.tags) { for (var tag in $scope.repositoryTags) {
if (!$scope.repository.tags.hasOwnProperty(tag)) { continue; } if (!$scope.repositoryTags.hasOwnProperty(tag)) { continue; }
var tagData = $scope.repository.tags[tag]; var tagData = $scope.repositoryTags[tag];
var tagInfo = $.extend(tagData, { var tagInfo = $.extend(tagData, {
'name': tag, 'name': tag,
'last_modified_datetime': TableService.getReversedTimestamp(tagData.last_modified), 'last_modified_datetime': TableService.getReversedTimestamp(tagData.last_modified),
@ -233,17 +234,21 @@ angular.module('quay').directive('repoPanelTags', function () {
if (!selectedTags || !$scope.repository || !$scope.imageMap) { return; } if (!selectedTags || !$scope.repository || !$scope.imageMap) { return; }
$scope.checkedTags.setChecked(selectedTags.map(function(tag) { $scope.checkedTags.setChecked(selectedTags.map(function(tag) {
return $scope.repository.tags[tag]; return $scope.repositoryTags[tag];
})); }));
}, true); }, true);
$scope.$watch('repository', function(updatedRepoObject, previousRepoObject) { $scope.$watch('repository', function(updatedRepoObject, previousRepoObject) {
if (updatedRepoObject.tags === previousRepoObject.tags) { return; }
// Process each of the tags. // Process each of the tags.
setTagState(); setTagState();
loadRepoSignatures(); loadRepoSignatures();
}, true); });
$scope.$watch('repositoryTags', function(tags) {
// Process each of the tags.
setTagState();
loadRepoSignatures();
});
$scope.loadImageVulnerabilities = function(image_id, imageData) { $scope.loadImageVulnerabilities = function(image_id, imageData) {
VulnerabilityService.loadImageVulnerabilities($scope.repository, image_id, function(resp) { VulnerabilityService.loadImageVulnerabilities($scope.repository, image_id, function(resp) {

View file

@ -11,6 +11,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
restrict: 'C', restrict: 'C',
scope: { scope: {
'repository': '=repository', 'repository': '=repository',
'repositoryTags': '=repositoryTags',
'actionHandler': '=actionHandler', 'actionHandler': '=actionHandler',
'imageLoader': '=imageLoader', 'imageLoader': '=imageLoader',
'tagChanged': '&tagChanged', 'tagChanged': '&tagChanged',
@ -20,22 +21,44 @@ angular.module('quay').directive('tagOperationsDialog', function () {
$scope.addingTag = false; $scope.addingTag = false;
$scope.changeTagsExpirationInfo = null; $scope.changeTagsExpirationInfo = null;
var markChanged = function(added, removed) { var reloadTags = function(page, tags, added, removed) {
// Reload the repository. var params = {
$scope.repository.get().then(function(resp) { 'repository': $scope.repository.namespace + '/' + $scope.repository.name,
$scope.repository = resp; 'limit': 100,
$scope.imageLoader.reset() 'page': page,
'onlyActiveTags': true
};
// Note: We need the timeout here so that Angular can $digest the images change ApiService.listRepoTags(null, params).then(function(resp) {
// on the parent scope before the tagChanged callback occurs. var newTags = resp.tags.reduce(function(result, item, index, array) {
$timeout(function() { var tag_name = item['name'];
$scope.tagChanged({ result[tag_name] = item;
'data': { 'added': added, 'removed': removed } return result;
}); }, {});
}, 1);
$.extend(tags, newTags);
if (resp.has_additional) {
loadPaginatedRepositoryTags(page + 1, tags, added, removed);
} else {
$scope.repositoryTags = tags;
$scope.imageLoader.reset();
$timeout(function() {
$scope.tagChanged({
'data': { 'added': added, 'removed': removed }
});
}, 1);
}
}); });
}; };
var markChanged = function(added, removed) {
// Reload the tags
tags = {};
reloadTags(1, tags, added, removed);
};
$scope.alertOnTagOpsDisabled = function() { $scope.alertOnTagOpsDisabled = function() {
if ($scope.repository.tag_operations_disabled) { if ($scope.repository.tag_operations_disabled) {
$('#tagOperationsDisabledModal').modal('show'); $('#tagOperationsDisabledModal').modal('show');
@ -46,17 +69,17 @@ angular.module('quay').directive('tagOperationsDialog', function () {
}; };
$scope.isAnotherImageTag = function(image, tag) { $scope.isAnotherImageTag = function(image, tag) {
if (!$scope.repository.tags) { return; } if (!$scope.repositoryTags) { return; }
var found = $scope.repository.tags[tag]; var found = $scope.repositoryTags[tag];
if (found == null) { return false; } if (found == null) { return false; }
return found.image_id != image; return found.image_id != image;
}; };
$scope.isOwnedTag = function(image, tag) { $scope.isOwnedTag = function(image, tag) {
if (!$scope.repository.tags) { return; } if (!$scope.repositoryTags) { return; }
var found = $scope.repository.tags[tag]; var found = $scope.repositoryTags[tag];
if (found == null) { return false; } if (found == null) { return false; }
return found.image_id == image; return found.image_id == image;
}; };
@ -227,7 +250,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
actions.push({ actions.push({
'action': 'delete', 'action': 'delete',
'label': label 'label': label
}) });
} }
}); });

View file

@ -11,6 +11,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
restrict: 'C', restrict: 'C',
scope: { scope: {
'repository': '=repository', 'repository': '=repository',
'repositoryTags': '=repositoryTags',
'tag': '=tag', 'tag': '=tag',
'imageLoader': '=imageLoader', 'imageLoader': '=imageLoader',
'imageCutoff': '=imageCutoff' 'imageCutoff': '=imageCutoff'
@ -22,7 +23,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
$scope.getImageListingClasses = function(image) { $scope.getImageListingClasses = function(image) {
var classes = ''; var classes = '';
if (!$scope.repository) { if (!$scope.repositoryTags) {
return ''; return '';
} }
@ -30,7 +31,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
classes += 'child '; classes += 'child ';
} }
var currentTag = $scope.repository.tags[$scope.tag]; var currentTag = $scope.repositoryTags[$scope.tag];
if (currentTag && image.id == currentTag.image_id) { if (currentTag && image.id == currentTag.image_id) {
classes += 'tag-image '; classes += 'tag-image ';
} }
@ -39,12 +40,12 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
}; };
var refresh = function() { var refresh = function() {
if (!$scope.repository || !$scope.tag || !$scope.imageLoader) { if (!$scope.repositoryTags || !$scope.tag || !$scope.imageLoader) {
$scope.tagSpecificImages = []; $scope.tagSpecificImages = [];
return; return;
} }
var tag = $scope.repository.tags[$scope.tag]; var tag = $scope.repositoryTags[$scope.tag];
if (!tag) { if (!tag) {
$scope.tagSpecificImages = []; $scope.tagSpecificImages = [];
return; return;
@ -58,6 +59,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
}; };
$scope.$watch('repository', refresh); $scope.$watch('repository', refresh);
$scope.$watch('repositoryTags', refresh);
$scope.$watch('tag', refresh); $scope.$watch('tag', refresh);
} }
}; };

View file

@ -30,7 +30,8 @@
'repository': null, 'repository': null,
'imageLoader': imageLoader, 'imageLoader': imageLoader,
'builds': null, 'builds': null,
'historyFilter': '' 'historyFilter': '',
'repositoryTags': null
}; };
$scope.repositoryTags = {}; $scope.repositoryTags = {};
@ -41,19 +42,20 @@
UserService.updateUserIn($scope); UserService.updateUserIn($scope);
// Watch the repository to filter any tags removed. // Watch the repository to filter any tags removed.
$scope.$watch('viewScope.repository', function(repository) { $scope.$watch('viewScope.repositoryTags', function(repository) {
if (!repository) { return; } if (!repository) { return; }
$scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags); $scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags);
}); });
var filterTags = function(tags) { var filterTags = function(tags) {
return (tags || []).filter(function(tag) { return (tags || []).filter(function(tag) {
return !!$scope.viewScope.repository.tags[tag]; return !!$scope.viewScope.repositoryTags[tag];
}); });
}; };
var loadRepositoryTags = function() { var loadRepositoryTags = function() {
loadPaginatedRepositoryTags(1); loadPaginatedRepositoryTags(1);
$scope.viewScope.repositoryTags = $scope.repositoryTags;
}; };
var loadPaginatedRepositoryTags = function(page) { var loadPaginatedRepositoryTags = function(page) {
@ -82,6 +84,7 @@
var loadRepository = function() { var loadRepository = function() {
// Mark the images to be reloaded. // Mark the images to be reloaded.
$scope.viewScope.images = null; $scope.viewScope.images = null;
loadRepositoryTags();
var params = { var params = {
'repository': $scope.namespace + '/' + $scope.name, 'repository': $scope.namespace + '/' + $scope.name,
@ -91,9 +94,6 @@
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) { $scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
if (repo != undefined) { if (repo != undefined) {
loadRepositoryTags();
repo.tags = $scope.repositoryTags;
$scope.repository = repo; $scope.repository = repo;
$scope.viewScope.repository = repo; $scope.viewScope.repository = repo;

View file

@ -76,6 +76,7 @@
<cor-tab-pane id="tags"> <cor-tab-pane id="tags">
<div class="repo-panel-tags" <div class="repo-panel-tags"
repository="viewScope.repository" repository="viewScope.repository"
repository-tags="viewScope.repositoryTags"
image-loader="viewScope.imageLoader" image-loader="viewScope.imageLoader"
selected-tags="viewScope.selectedTags" selected-tags="viewScope.selectedTags"
history-filter="viewScope.historyFilter" history-filter="viewScope.historyFilter"