From 8b25d5b77bd5e0ac1ee08e469188913d7d9e1d0e Mon Sep 17 00:00:00 2001
From: Kenny Lee Sin Cheong <2530351+kleesc@users.noreply.github.com>
Date: Tue, 23 Oct 2018 13:26:40 -0400
Subject: [PATCH] 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
---
---
.../directives/repo-view/repo-panel-tags.html | 2 +-
static/directives/tag-operations-dialog.html | 3 +-
.../directives/repo-view/repo-panel-tags.js | 21 ++++---
.../js/directives/ui/tag-operations-dialog.js | 57 +++++++++++++------
.../directives/ui/tag-specific-image-view.js | 10 ++--
static/js/pages/repo-view.js | 12 ++--
static/partials/repo-view.html | 1 +
7 files changed, 69 insertions(+), 37 deletions(-)
diff --git a/static/directives/repo-view/repo-panel-tags.html b/static/directives/repo-view/repo-panel-tags.html
index 873be00e6..d34fab7ff 100644
--- a/static/directives/repo-view/repo-panel-tags.html
+++ b/static/directives/repo-view/repo-panel-tags.html
@@ -347,7 +347,7 @@
-
{{ deleteTagInfo.tag }}?
-
The following images and any other images not referenced by a tag will be deletedunavailable and deleted in {{ getFormattedTimespan(repository.tag_expiration_s) }}:
diff --git a/static/js/directives/repo-view/repo-panel-tags.js b/static/js/directives/repo-view/repo-panel-tags.js
index 4e602cd2c..f1f54752f 100644
--- a/static/js/directives/repo-view/repo-panel-tags.js
+++ b/static/js/directives/repo-view/repo-panel-tags.js
@@ -10,6 +10,7 @@ angular.module('quay').directive('repoPanelTags', function () {
restrict: 'C',
scope: {
'repository': '=repository',
+ 'repositoryTags': '=repositoryTags',
'selectedTags': '=selectedTags',
'historyFilter': '=historyFilter',
'imagesResource': '=imagesResource',
@@ -64,14 +65,14 @@ angular.module('quay').directive('repoPanelTags', 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.
var allTags = [];
- for (var tag in $scope.repository.tags) {
- if (!$scope.repository.tags.hasOwnProperty(tag)) { continue; }
+ for (var tag in $scope.repositoryTags) {
+ if (!$scope.repositoryTags.hasOwnProperty(tag)) { continue; }
- var tagData = $scope.repository.tags[tag];
+ var tagData = $scope.repositoryTags[tag];
var tagInfo = $.extend(tagData, {
'name': tag,
'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; }
$scope.checkedTags.setChecked(selectedTags.map(function(tag) {
- return $scope.repository.tags[tag];
+ return $scope.repositoryTags[tag];
}));
}, true);
$scope.$watch('repository', function(updatedRepoObject, previousRepoObject) {
- if (updatedRepoObject.tags === previousRepoObject.tags) { return; }
-
// Process each of the tags.
setTagState();
loadRepoSignatures();
- }, true);
+ });
+
+ $scope.$watch('repositoryTags', function(tags) {
+ // Process each of the tags.
+ setTagState();
+ loadRepoSignatures();
+ });
$scope.loadImageVulnerabilities = function(image_id, imageData) {
VulnerabilityService.loadImageVulnerabilities($scope.repository, image_id, function(resp) {
diff --git a/static/js/directives/ui/tag-operations-dialog.js b/static/js/directives/ui/tag-operations-dialog.js
index c3db786e4..417a997a8 100644
--- a/static/js/directives/ui/tag-operations-dialog.js
+++ b/static/js/directives/ui/tag-operations-dialog.js
@@ -11,6 +11,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
restrict: 'C',
scope: {
'repository': '=repository',
+ 'repositoryTags': '=repositoryTags',
'actionHandler': '=actionHandler',
'imageLoader': '=imageLoader',
'tagChanged': '&tagChanged',
@@ -20,22 +21,44 @@ angular.module('quay').directive('tagOperationsDialog', function () {
$scope.addingTag = false;
$scope.changeTagsExpirationInfo = null;
- var markChanged = function(added, removed) {
- // Reload the repository.
- $scope.repository.get().then(function(resp) {
- $scope.repository = resp;
- $scope.imageLoader.reset()
+ var reloadTags = function(page, tags, added, removed) {
+ var params = {
+ 'repository': $scope.repository.namespace + '/' + $scope.repository.name,
+ 'limit': 100,
+ 'page': page,
+ 'onlyActiveTags': true
+ };
- // Note: We need the timeout here so that Angular can $digest the images change
- // on the parent scope before the tagChanged callback occurs.
- $timeout(function() {
- $scope.tagChanged({
- 'data': { 'added': added, 'removed': removed }
- });
- }, 1);
+ ApiService.listRepoTags(null, params).then(function(resp) {
+ var newTags = resp.tags.reduce(function(result, item, index, array) {
+ var tag_name = item['name'];
+ result[tag_name] = item;
+ return result;
+ }, {});
+
+ $.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() {
if ($scope.repository.tag_operations_disabled) {
$('#tagOperationsDisabledModal').modal('show');
@@ -46,17 +69,17 @@ angular.module('quay').directive('tagOperationsDialog', function () {
};
$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; }
return found.image_id != image;
};
$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; }
return found.image_id == image;
};
@@ -227,7 +250,7 @@ angular.module('quay').directive('tagOperationsDialog', function () {
actions.push({
'action': 'delete',
'label': label
- })
+ });
}
});
diff --git a/static/js/directives/ui/tag-specific-image-view.js b/static/js/directives/ui/tag-specific-image-view.js
index 26fc7c4b9..68a4085f2 100644
--- a/static/js/directives/ui/tag-specific-image-view.js
+++ b/static/js/directives/ui/tag-specific-image-view.js
@@ -11,6 +11,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
restrict: 'C',
scope: {
'repository': '=repository',
+ 'repositoryTags': '=repositoryTags',
'tag': '=tag',
'imageLoader': '=imageLoader',
'imageCutoff': '=imageCutoff'
@@ -22,7 +23,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
$scope.getImageListingClasses = function(image) {
var classes = '';
- if (!$scope.repository) {
+ if (!$scope.repositoryTags) {
return '';
}
@@ -30,7 +31,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
classes += 'child ';
}
- var currentTag = $scope.repository.tags[$scope.tag];
+ var currentTag = $scope.repositoryTags[$scope.tag];
if (currentTag && image.id == currentTag.image_id) {
classes += 'tag-image ';
}
@@ -39,12 +40,12 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
};
var refresh = function() {
- if (!$scope.repository || !$scope.tag || !$scope.imageLoader) {
+ if (!$scope.repositoryTags || !$scope.tag || !$scope.imageLoader) {
$scope.tagSpecificImages = [];
return;
}
- var tag = $scope.repository.tags[$scope.tag];
+ var tag = $scope.repositoryTags[$scope.tag];
if (!tag) {
$scope.tagSpecificImages = [];
return;
@@ -58,6 +59,7 @@ angular.module('quay').directive('tagSpecificImagesView', function () {
};
$scope.$watch('repository', refresh);
+ $scope.$watch('repositoryTags', refresh);
$scope.$watch('tag', refresh);
}
};
diff --git a/static/js/pages/repo-view.js b/static/js/pages/repo-view.js
index a7dcba245..c3b04ba8d 100644
--- a/static/js/pages/repo-view.js
+++ b/static/js/pages/repo-view.js
@@ -30,7 +30,8 @@
'repository': null,
'imageLoader': imageLoader,
'builds': null,
- 'historyFilter': ''
+ 'historyFilter': '',
+ 'repositoryTags': null
};
$scope.repositoryTags = {};
@@ -41,19 +42,20 @@
UserService.updateUserIn($scope);
// Watch the repository to filter any tags removed.
- $scope.$watch('viewScope.repository', function(repository) {
+ $scope.$watch('viewScope.repositoryTags', function(repository) {
if (!repository) { return; }
$scope.viewScope.selectedTags = filterTags($scope.viewScope.selectedTags);
});
var filterTags = function(tags) {
return (tags || []).filter(function(tag) {
- return !!$scope.viewScope.repository.tags[tag];
+ return !!$scope.viewScope.repositoryTags[tag];
});
};
var loadRepositoryTags = function() {
loadPaginatedRepositoryTags(1);
+ $scope.viewScope.repositoryTags = $scope.repositoryTags;
};
var loadPaginatedRepositoryTags = function(page) {
@@ -82,6 +84,7 @@
var loadRepository = function() {
// Mark the images to be reloaded.
$scope.viewScope.images = null;
+ loadRepositoryTags();
var params = {
'repository': $scope.namespace + '/' + $scope.name,
@@ -91,9 +94,6 @@
$scope.repositoryResource = ApiService.getRepoAsResource(params).get(function(repo) {
if (repo != undefined) {
- loadRepositoryTags();
- repo.tags = $scope.repositoryTags;
-
$scope.repository = repo;
$scope.viewScope.repository = repo;
diff --git a/static/partials/repo-view.html b/static/partials/repo-view.html
index dbbb6e28a..046f63fc1 100644
--- a/static/partials/repo-view.html
+++ b/static/partials/repo-view.html
@@ -76,6 +76,7 @@