mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Bulk delete and hide operations.
This commit is contained in:
@@ -247,35 +247,76 @@ var HistoryViewEdit = _super.extend(
|
||||
* ajax calls made for multiple datasets are queued
|
||||
*/
|
||||
multiselectActions: function() {
|
||||
var Galaxy = getGalaxyInstance();
|
||||
var panel = this;
|
||||
|
||||
const batchUpdate = (key, value) => {
|
||||
const actionModels = {};
|
||||
const items = panel
|
||||
.getSelectedModels()
|
||||
.filter(model => {
|
||||
console.log(model.attributes[key]);
|
||||
return model.attributes[key] != value;
|
||||
})
|
||||
.map(model => {
|
||||
const res = {
|
||||
history_content_type: model.attributes.history_content_type,
|
||||
id: model.attributes.id
|
||||
};
|
||||
actionModels[model.id] = model;
|
||||
// Tried "optimistically" deleting the dataset - but it didn't quite work -
|
||||
// maybe history polling restored the previous state?
|
||||
return res;
|
||||
});
|
||||
|
||||
if (items.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {};
|
||||
options.url = `${Galaxy.root}api/histories/${this.model.id}/contents`;
|
||||
options.type = "PUT";
|
||||
const requestData = { items: items };
|
||||
requestData[key] = value;
|
||||
options.data = JSON.stringify(requestData);
|
||||
options.contentType = "application/json";
|
||||
|
||||
var xhr = jQuery.ajax(options);
|
||||
xhr.done((message, status, responseObj) => {
|
||||
for (let updated of message) {
|
||||
const typeId = updated.history_content_type + "-" + updated.id;
|
||||
actionModels[typeId].set(updated);
|
||||
}
|
||||
});
|
||||
xhr.fail((xhr, status, message) => {
|
||||
console.error(message);
|
||||
});
|
||||
return xhr;
|
||||
};
|
||||
|
||||
var actions = [
|
||||
{
|
||||
html: _l("Hide datasets"),
|
||||
func: function() {
|
||||
var action = HDA_MODEL.HistoryDatasetAssociation.prototype.hide;
|
||||
panel.getSelectedModels().ajaxQueue(action);
|
||||
batchUpdate("visible", false);
|
||||
}
|
||||
},
|
||||
{
|
||||
html: _l("Unhide datasets"),
|
||||
func: function() {
|
||||
var action = HDA_MODEL.HistoryDatasetAssociation.prototype.unhide;
|
||||
panel.getSelectedModels().ajaxQueue(action);
|
||||
batchUpdate("visible", true);
|
||||
}
|
||||
},
|
||||
{
|
||||
html: _l("Delete datasets"),
|
||||
func: function() {
|
||||
var action = HDA_MODEL.HistoryDatasetAssociation.prototype["delete"];
|
||||
panel.getSelectedModels().ajaxQueue(action);
|
||||
func: () => {
|
||||
batchUpdate("deleted", true);
|
||||
}
|
||||
},
|
||||
{
|
||||
html: _l("Undelete datasets"),
|
||||
func: function() {
|
||||
var action = HDA_MODEL.HistoryDatasetAssociation.prototype.undelete;
|
||||
panel.getSelectedModels().ajaxQueue(action);
|
||||
batchUpdate("deleted", false);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -47,6 +47,13 @@ class HDAManager(datasets.DatasetAssociationManager,
|
||||
super(HDAManager, self).__init__(app)
|
||||
self.user_manager = users.UserManager(app)
|
||||
|
||||
def get_owned_ids(self, object_ids, history=None):
|
||||
"""Get owned IDs.
|
||||
"""
|
||||
log.info("object_ids are %s" % object_ids)
|
||||
filters = [self.model_class.id.in_(object_ids), self.model_class.history_id == history.id]
|
||||
return self.list(filters=filters)
|
||||
|
||||
# .... security and permissions
|
||||
def is_accessible(self, hda, user, **kwargs):
|
||||
"""
|
||||
|
||||
@@ -600,15 +600,60 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary
|
||||
self.hda_manager.update_permissions(trans, hda, **kwd)
|
||||
return self.hda_manager.serialize_dataset_association_roles(trans, hda)
|
||||
|
||||
@expose_api_anonymous
|
||||
def update_batch(self, trans, history_id, payload, **kwd):
|
||||
"""
|
||||
update( self, trans, history_id, id, payload, **kwd )
|
||||
* PUT /api/histories/{history_id}/contents
|
||||
|
||||
:type history_id: str
|
||||
:param history_id: encoded id string of the history containing supplied items
|
||||
:type id: str
|
||||
:param id: the encoded id of the history to update
|
||||
:type payload: dict
|
||||
:param payload: a dictionary containing any or all the
|
||||
|
||||
:rtype: dict
|
||||
:returns: an error object if an error occurred or a dictionary containing
|
||||
any values that were different from the original and, therefore, updated
|
||||
"""
|
||||
items = payload.get("items")
|
||||
hda_ids = []
|
||||
hdca_ids = []
|
||||
for item in items:
|
||||
contents_type = item["history_content_type"]
|
||||
if contents_type == "dataset":
|
||||
decoded_id = self.decode_id(item["id"])
|
||||
hda_ids.append(decoded_id)
|
||||
else:
|
||||
hdca_ids.append(item["id"])
|
||||
|
||||
history = self.history_manager.get_owned(self.decode_id(history_id), trans.user,
|
||||
current_history=trans.history)
|
||||
hdas = self.__datasets_for_update(trans, history, hda_ids, payload)
|
||||
rval = []
|
||||
for hda in hdas:
|
||||
self.__deserialize_dataset(hda, payload, trans)
|
||||
rval.append(self.hda_serializer.serialize_to_view(hda,
|
||||
user=trans.user, trans=trans, **self._parse_serialization_params(kwd, 'summary')))
|
||||
for hdca_id in hdca_ids:
|
||||
self.__update_dataset_collection(trans, history_id, hdca_id, payload, **kwd)
|
||||
dataset_collection_instance = self.__get_accessible_collection(trans, hdca_id, history_id)
|
||||
rval.append(self.__collection_dict(trans, dataset_collection_instance, view="summary"))
|
||||
return rval
|
||||
# aggregate by ids.
|
||||
# hdas_manager.by_ids() with history or user.
|
||||
# dataset_collection_service.by_ids() with history or user.
|
||||
|
||||
@expose_api_anonymous
|
||||
def update(self, trans, history_id, id, payload, **kwd):
|
||||
"""
|
||||
update( self, trans, history_id, id, payload, **kwd )
|
||||
* PUT /api/histories/{history_id}/contents/{id}
|
||||
updates the values for the HDA with the given ``id``
|
||||
updates the values for the history content item with the given ``id``
|
||||
|
||||
:type history_id: str
|
||||
:param history_id: encoded id string of the HDA's History
|
||||
:param history_id: encoded id string of the items's History
|
||||
:type id: str
|
||||
:param id: the encoded id of the history to update
|
||||
:type payload: dict
|
||||
@@ -634,13 +679,20 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary
|
||||
def __update_dataset(self, trans, history_id, id, payload, **kwd):
|
||||
# anon user: ensure that history ids match up and the history is the current,
|
||||
# check for uploading, and use only the subset of attribute keys manipulatable by anon users
|
||||
decoded_id = self.decode_id(id)
|
||||
history = self.history_manager.get_owned(self.decode_id(history_id), trans.user,
|
||||
current_history=trans.history)
|
||||
hda = self.__datasets_for_update(trans, history, [decoded_id], payload)[0]
|
||||
if hda:
|
||||
self.__deserialize_dataset(hda, payload, trans)
|
||||
return self.hda_serializer.serialize_to_view(hda,
|
||||
user=trans.user, trans=trans, **self._parse_serialization_params(kwd, 'detailed'))
|
||||
|
||||
return {}
|
||||
|
||||
def __datasets_for_update(self, trans, history, hda_ids, payload):
|
||||
anonymous_user = not trans.user_is_admin and trans.user is None
|
||||
if anonymous_user:
|
||||
hda = self.hda_manager.by_id(self.decode_id(id))
|
||||
if hda.history != trans.history:
|
||||
raise exceptions.AuthenticationRequired('API authentication required for this request')
|
||||
hda = self.hda_manager.error_if_uploading(hda)
|
||||
|
||||
anon_allowed_payload = {}
|
||||
if 'deleted' in payload:
|
||||
anon_allowed_payload['deleted'] = payload['deleted']
|
||||
@@ -648,25 +700,21 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary
|
||||
anon_allowed_payload['visible'] = payload['visible']
|
||||
payload = anon_allowed_payload
|
||||
|
||||
# logged in user: use full payload, check state if deleting, and make sure the history is theirs
|
||||
else:
|
||||
hda = self.hda_manager.get_owned(self.decode_id(id), trans.user, current_history=trans.history, trans=trans)
|
||||
hdas = self.hda_manager.get_owned_ids(hda_ids, history=history)
|
||||
|
||||
# only check_state if not deleting, otherwise cannot delete uploading files
|
||||
check_state = not payload.get('deleted', False)
|
||||
if check_state:
|
||||
# only check_state if not deleting, otherwise cannot delete uploading files
|
||||
check_state = not payload.get('deleted', False)
|
||||
if check_state:
|
||||
for hda in hdas:
|
||||
hda = self.hda_manager.error_if_uploading(hda)
|
||||
|
||||
# make the actual changes
|
||||
if hda:
|
||||
self.hda_deserializer.deserialize(hda, payload, user=trans.user, trans=trans)
|
||||
# TODO: this should be an effect of deleting the hda
|
||||
if payload.get('deleted', False):
|
||||
self.hda_manager.stop_creating_job(hda)
|
||||
return self.hda_serializer.serialize_to_view(hda,
|
||||
user=trans.user, trans=trans, **self._parse_serialization_params(kwd, 'detailed'))
|
||||
return hdas
|
||||
|
||||
return {}
|
||||
def __deserialize_dataset(self, hda, payload, trans):
|
||||
self.hda_deserializer.deserialize(hda, payload, user=trans.user, trans=trans)
|
||||
# TODO: this should be an effect of deleting the hda
|
||||
if payload.get('deleted', False):
|
||||
self.hda_manager.stop_creating_job(hda)
|
||||
|
||||
def __update_dataset_collection(self, trans, history_id, id, payload, **kwd):
|
||||
return trans.app.dataset_collections_service.update(trans, "history", id, payload)
|
||||
@@ -700,8 +748,8 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary
|
||||
:rtype: dict
|
||||
:returns: an error object if an error occurred or a dictionary containing:
|
||||
* id: the encoded id of the history,
|
||||
* deleted: if the history was marked as deleted,
|
||||
* purged: if the history was purged
|
||||
* deleted: if the history content was marked as deleted,
|
||||
* purged: if the history content was purged
|
||||
"""
|
||||
contents_type = kwd.get('type', 'dataset')
|
||||
if contents_type == "dataset":
|
||||
|
||||
@@ -216,6 +216,11 @@ def populate_api_routes(webapp, app):
|
||||
name_prefix='history_',
|
||||
path_prefix='/api/histories/{history_id}',
|
||||
parent_resources=dict(member_name='history', collection_name='histories'))
|
||||
webapp.mapper.connect("history_contents_batch_update",
|
||||
"/api/histories/{history_id}/contents",
|
||||
controller="history_contents",
|
||||
action="update_batch",
|
||||
conditions=dict(method=["PUT"]))
|
||||
webapp.mapper.connect("history_contents_display",
|
||||
"/api/histories/{history_id}/contents/{history_content_id}/display",
|
||||
controller="datasets",
|
||||
|
||||
@@ -218,6 +218,14 @@ class HistoryContentsApiTestCase(api.ApiTestCase, TestsDatasets):
|
||||
# and this is an invalid ID.
|
||||
assert update_response.status_code == 400, update_response.content
|
||||
|
||||
def test_update_batch(self):
|
||||
hda1 = self._wait_for_new_hda()
|
||||
assert str(hda1["deleted"]).lower() == "false"
|
||||
payload = dict(items=[{"history_content_type": "dataset", "id": hda1["id"]}], deleted=True)
|
||||
update_response = self._raw_update_batch(payload)
|
||||
objects = update_response.json()
|
||||
assert objects[0]["deleted"]
|
||||
|
||||
def test_update_type_failures(self):
|
||||
hda1 = self._wait_for_new_hda()
|
||||
update_response = self._raw_update(hda1["id"], dict(deleted='not valid'))
|
||||
@@ -246,6 +254,11 @@ class HistoryContentsApiTestCase(api.ApiTestCase, TestsDatasets):
|
||||
update_response = put(update_url, json=data)
|
||||
return update_response
|
||||
|
||||
def _raw_update_batch(self, data):
|
||||
update_url = self._api_url("histories/%s/contents" % (self.history_id), use_key=True)
|
||||
update_response = put(update_url, json=data)
|
||||
return update_response
|
||||
|
||||
def test_delete(self):
|
||||
hda1 = self._new_dataset(self.history_id)
|
||||
self._wait_for_history(self.history_id)
|
||||
|
||||
Reference in New Issue
Block a user