- UI improvements in prep for adding undo ability

- Move the tag history into its own directive and clean up the code
This commit is contained in:
Joseph Schorr 2015-04-15 18:39:05 -04:00
parent 20b399318e
commit 2a77bd2c92
6 changed files with 335 additions and 312 deletions

View file

@ -119,136 +119,11 @@ angular.module('quay').directive('repoPanelTags', function () {
// Process each of the tags.
setTagState();
if ($scope.showingHistory) {
loadTimeline();
}
});
var loadTimeline = function() {
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
ApiService.listRepoTags(null, params).then(function(resp) {
var tagData = [];
var currentTags = {};
resp.tags.forEach(function(tag) {
var tagName = tag.name;
var imageId = tag.docker_image_id;
var oldImageId = null;
if (tag.end_ts) {
var action = 'delete';
// If the end time matches the existing start time for this tag, then this is a move
// instead of a delete.
var currentTime = tag.end_ts * 1000;
if (currentTags[tagName] && currentTags[tagName].start_ts == tag.end_ts) {
action = 'move';
// Remove the create.
var index = tagData.indexOf(currentTags[tagName]);
var createEntry = tagData.splice(index, 1)[0];
imageId = createEntry.docker_image_id;
oldImageId = tag.docker_image_id;
}
// Add the delete/move.
tagData.push({
'tag_name': tagName,
'action': action,
'start_ts': tag.start_ts,
'end_ts': tag.end_ts,
'time': currentTime,
'docker_image_id': imageId,
'old_docker_image_id': oldImageId || ''
})
}
if (tag.start_ts) {
var currentTime = tag.start_ts * 1000;
var create = {
'tag_name': tagName,
'action': 'create',
'start_ts': tag.start_ts,
'end_ts': tag.end_ts,
'time': currentTime,
'docker_image_id': tag.docker_image_id,
'old_docker_image_id': ''
};
tagData.push(create);
currentTags[tagName] = create;
}
});
tagData.sort(function(a, b) {
return b.time - a.time;
});
for (var i = tagData.length - 1; i >= 1; --i) {
var current = tagData[i];
var next = tagData[i - 1];
if (new Date(current.time).getDate() != new Date(next.time).getDate()) {
tagData.splice(i, 0, {
'date_break': true,
'date': new Date(current.time)
});
i--;
}
}
if (tagData.length > 0) {
tagData.splice(0, 0, {
'date_break': true,
'date': new Date(tagData[0].time)
});
}
$scope.tagHistoryData = tagData;
});
};
$scope.getEntryClasses = function(entry, historyFilter) {
var classes = entry.action + ' ';
if (!historyFilter || !entry.action) {
return classes;
}
var parts = (historyFilter || '').split(',');
var isMatch = parts.some(function(part) {
if (part && entry.tag_name) {
isMatch = entry.tag_name.indexOf(part) >= 0;
isMatch = isMatch || entry.docker_image_id.indexOf(part) >= 0;
isMatch = isMatch || entry.old_docker_image_id.indexOf(part) >= 0;
return isMatch;
}
});
classes += isMatch ? 'filtered-match' : 'filtered-mismatch';
return classes;
};
$scope.showHistory = function(value, opt_tagname) {
if (opt_tagname) {
$scope.options.historyFilter = opt_tagname;
} else {
$scope.options.historyFilter = '';
}
if ($scope.showingHistory == value) {
return;
}
$scope.options.historyFilter = opt_tagname ? opt_tagname : '';
$scope.showingHistory = value;
if ($scope.showingHistory) {
loadTimeline();
}
};
$scope.toggleHistory = function() {
@ -349,14 +224,6 @@ angular.module('quay').directive('repoPanelTags', function () {
return names.join(',');
};
$scope.isChecked = function(tagName, checked) {
return checked.some(function(tag) {
if (tag.name == tagName) {
return true;
}
});
};
}
};
return directiveDefinitionObject;

View file

@ -0,0 +1,155 @@
/**
* An element which displays its contents wrapped in an <a> tag, but only if the href is not null.
*/
angular.module('quay').directive('repoTagHistory', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-tag-history.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'repository': '=repository',
'filter': '=filter',
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, ApiService) {
$scope.tagHistoryData = null;
$scope.tagHistoryLeaves = {};
var loadTimeline = function() {
if (!$scope.repository || !$scope.isEnabled) { return; }
var params = {
'repository': $scope.repository.namespace + '/' + $scope.repository.name
};
ApiService.listRepoTags(null, params).then(function(resp) {
processTags(resp.tags);
});
};
$scope.$watch('isEnabled', loadTimeline);
$scope.$watch('repository', loadTimeline);
var processTags = function(tags) {
var entries = [];
var tagEntries = {};
// For each tag, turn the tag into create, move, delete, restore, etc entries.
tags.forEach(function(tag) {
var tagName = tag.name;
var dockerImageId = tag.docker_image_id;
if (!tagEntries[tagName]) {
tagEntries[tagName] = [];
}
var removeEntry = function(entry) {
entries.splice(entries.indexOf(entry), 1);
tagEntries[entry.tag_name].splice(tagEntries[entry.tag_name].indexOf(entry), 1);
};
var addEntry = function(action, time, opt_docker_id, opt_old_docker_id) {
var entry = {
'tag_name': tagName,
'action': action,
'start_ts': tag.start_ts,
'end_ts': tag.end_ts,
'time': time * 1000, // JS expects ms, not s since epoch.
'docker_image_id': opt_docker_id || dockerImageId,
'old_docker_image_id': opt_old_docker_id || ''
};
tagEntries[tagName].push(entry);
entries.push(entry);
};
// If the tag has an end time, it was either deleted or moved.
if (tag.end_ts) {
// If a future entry exists with a start time equal to the end time for this tag,
// then the action was a move, rather than a delete and a create.
var currentEntries = tagEntries[tagName];
var futureEntry = currentEntries.length > 0 ? currentEntries[currentEntries.length - 1] : {};
if (futureEntry.start_ts == tag.end_ts) {
removeEntry(futureEntry);
addEntry('move', tag.end_ts, futureEntry.docker_image_id, dockerImageId);
} else {
addEntry('delete', tag.end_ts)
}
}
// If the tag has a start time, it was created.
if (tag.start_ts) {
addEntry('create', tag.start_ts);
}
});
// Sort the overall entries by datetime descending.
entries.sort(function(a, b) {
return b.time - a.time;
});
// Sort the tag entries by datetime descending.
Object.keys(tagEntries).forEach(function(name) {
var te = tagEntries[name];
te.sort(function(a, b) {
return b.time - a.time;
});
});
// Add date dividers in.
for (var i = entries.length - 1; i >= 1; --i) {
var current = entries[i];
var next = entries[i - 1];
if (new Date(current.time).getDate() != new Date(next.time).getDate()) {
entries.splice(i, 0, {
'date_break': true,
'date': new Date(current.time)
});
i--;
}
}
// Add the top-level date divider.
if (entries.length > 0) {
entries.splice(0, 0, {
'date_break': true,
'date': new Date(entries[0].time)
});
}
$scope.historyEntries = entries;
$scope.historyEntryMap = tagEntries;
};
$scope.getEntryClasses = function(entry, historyFilter) {
if (!entry.action) { return ''; }
var classes = entry.action + ' ';
if ($scope.historyEntryMap[entry.tag_name][0] == entry) {
classes += ' current ';
}
if (!historyFilter || !entry.action) {
return classes;
}
var parts = (historyFilter || '').split(',');
var isMatch = parts.some(function(part) {
if (part && entry.tag_name) {
isMatch = entry.tag_name.indexOf(part) >= 0;
isMatch = isMatch || entry.docker_image_id.indexOf(part) >= 0;
isMatch = isMatch || entry.old_docker_image_id.indexOf(part) >= 0;
return isMatch;
}
});
classes += isMatch ? 'filtered-match' : 'filtered-mismatch';
return classes;
};
}
};
return directiveDefinitionObject;
});