mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-21 13:50:20 +08:00
Merge pull request #5091 from jmchilton/open_large_collections
Render arbitrarily large collections in the UI.
This commit is contained in:
@@ -52,6 +52,7 @@ var CollectionView = _super.extend(
|
||||
_queueNewRender: function($newRender, speed) {
|
||||
speed = speed === undefined ? this.fxSpeed : speed;
|
||||
var panel = this;
|
||||
this.handleWarning($newRender);
|
||||
panel.log("_queueNewRender:", $newRender, speed);
|
||||
|
||||
// TODO: jquery@1.12 doesn't change display when the elem has display: flex
|
||||
@@ -61,6 +62,16 @@ var CollectionView = _super.extend(
|
||||
panel.trigger("rendered", panel);
|
||||
},
|
||||
|
||||
handleWarning: function($newRender) {
|
||||
var viewLength = this.views.length;
|
||||
var elementCount = this.model.get("element_count");
|
||||
if (elementCount && elementCount !== viewLength) {
|
||||
var warning = _l(`displaying only ${viewLength} of ${elementCount} items`);
|
||||
var $warns = $newRender.find(".elements-warning");
|
||||
$warns.html(`<div class="warningmessagesmall">${warning}</div>`);
|
||||
}
|
||||
},
|
||||
|
||||
// ------------------------------------------------------------------------ sub-views
|
||||
/** In this override, use model.getVisibleContents */
|
||||
_filterCollection: function() {
|
||||
@@ -149,52 +160,74 @@ var CollectionView = _super.extend(
|
||||
|
||||
//------------------------------------------------------------------------------ TEMPLATES
|
||||
CollectionView.prototype.templates = (() => {
|
||||
var controlsTemplate = BASE_MVC.wrapTemplate(
|
||||
[
|
||||
'<div class="controls">',
|
||||
'<div class="navigation">',
|
||||
'<a class="back" href="javascript:void(0)">',
|
||||
'<span class="fa fa-icon fa-angle-left"></span>',
|
||||
_l("Back to "),
|
||||
"<%- view.parentName %>",
|
||||
"</a>",
|
||||
"</div>",
|
||||
|
||||
'<div class="title">',
|
||||
'<div class="name"><%- collection.name || collection.element_identifier %></div>',
|
||||
'<div class="subtitle">',
|
||||
'<% if( collection.collection_type === "list" ){ %>',
|
||||
_l("a list of datasets"),
|
||||
'<% } else if( collection.collection_type === "paired" ){ %>',
|
||||
_l("a pair of datasets"),
|
||||
'<% } else if( collection.collection_type === "list:paired" ){ %>',
|
||||
_l("a list of paired datasets"),
|
||||
'<% } else if( collection.collection_type === "list:list" ){ %>',
|
||||
_l("a list of dataset lists"),
|
||||
"<% } %>",
|
||||
"</div>",
|
||||
"</div>",
|
||||
|
||||
'<div class="tags-display"></div>',
|
||||
|
||||
'<div class="actions">',
|
||||
'<a class="download-btn icon-btn" ',
|
||||
'href="<%- view.downloadUrl %>',
|
||||
'" title="" download="" data-original-title="Download Collection">',
|
||||
'<span class="fa fa-floppy-o"></span>',
|
||||
"</a>",
|
||||
"</div>",
|
||||
"</div>"
|
||||
],
|
||||
"collection"
|
||||
);
|
||||
var controlsTemplate = (collection, view) => {
|
||||
var subtitle = collectionDescription(view.model);
|
||||
return `
|
||||
<div class="controls">
|
||||
<div class="navigation">
|
||||
<a class="back" href="javascript:void(0)">
|
||||
<span class="fa fa-icon fa-angle-left"></span>
|
||||
${_l("Back to ")}
|
||||
${_.escape(view.parentName)}
|
||||
</a>
|
||||
</div>
|
||||
<div class="title">
|
||||
<div class="name">${_.escape(collection.name) || _.escape(collection.element_identifier)}</div>
|
||||
<div class="subtitle">
|
||||
${subtitle}
|
||||
</div>
|
||||
</div>
|
||||
<div class="elements-warning">
|
||||
</div>
|
||||
<div class="tags-display"></div>
|
||||
<div class="actions">
|
||||
<a class="download-btn icon-btn" href="${view.downloadUrl}"
|
||||
title="" download="" data-original-title="Download Collection">
|
||||
<span class="fa fa-floppy-o"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>`;
|
||||
};
|
||||
|
||||
return _.extend(_.clone(_super.prototype.templates), {
|
||||
controls: controlsTemplate
|
||||
});
|
||||
})();
|
||||
|
||||
function collectionTypeDescription(collection) {
|
||||
var collectionType = collection.get("collection_type");
|
||||
var collectionTypeDescription;
|
||||
if (collectionType == "list") {
|
||||
collectionTypeDescription = _l("list");
|
||||
} else if (collectionType == "paired") {
|
||||
collectionTypeDescription = _l("dataset pair");
|
||||
} else if (collectionType == "list:paired") {
|
||||
collectionTypeDescription = _l("list of pairs");
|
||||
} else {
|
||||
collectionTypeDescription = _l("nested list");
|
||||
}
|
||||
return collectionTypeDescription;
|
||||
}
|
||||
|
||||
function collectionDescription(collection) {
|
||||
var elementCount = collection.get("element_count");
|
||||
|
||||
var itemsDescription = `a ${collectionTypeDescription(collection)}`;
|
||||
if (elementCount) {
|
||||
var countDescription;
|
||||
if (elementCount == 1) {
|
||||
countDescription = "with 1 item";
|
||||
} else if (elementCount) {
|
||||
countDescription = `with ${elementCount} items`;
|
||||
}
|
||||
itemsDescription = `${itemsDescription} ${_l(countDescription)}`;
|
||||
}
|
||||
return itemsDescription;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
export default {
|
||||
collectionTypeDescription: collectionTypeDescription,
|
||||
collectionDescription: collectionDescription,
|
||||
CollectionView: CollectionView
|
||||
};
|
||||
|
||||
@@ -67,30 +67,17 @@ var HDCAListItemView = _super.extend(
|
||||
var elementCount = collection.get("element_count");
|
||||
var jobStateSource = collection.get("job_source_type");
|
||||
var collectionType = this.model.get("collection_type");
|
||||
var collectionTypeDescription;
|
||||
if (collectionType == "list") {
|
||||
collectionTypeDescription = "list";
|
||||
} else if (collectionType == "paired") {
|
||||
collectionTypeDescription = "dataset pair";
|
||||
} else if (collectionType == "list:paired") {
|
||||
collectionTypeDescription = "list of pairs";
|
||||
} else {
|
||||
collectionTypeDescription = "nested list";
|
||||
}
|
||||
var itemsDescription = "";
|
||||
if (elementCount == 1) {
|
||||
itemsDescription = ` with 1 item`;
|
||||
} else if (elementCount) {
|
||||
itemsDescription = ` with ${elementCount} items`;
|
||||
}
|
||||
var collectionTypeDescription = DC_VIEW.collectionTypeDescription(collection);
|
||||
var simpleDescription = DC_VIEW.collectionDescription(collection);
|
||||
var jobStatesSummary = collection.jobStatesSummary;
|
||||
var simpleDescription = `${collectionTypeDescription}${itemsDescription}`;
|
||||
if (!jobStateSource || jobStateSource == "Job") {
|
||||
return `a ${simpleDescription}`;
|
||||
return simpleDescription;
|
||||
} else if (!jobStatesSummary || !jobStatesSummary.hasDetails()) {
|
||||
return `
|
||||
<div class="progress state-progress">
|
||||
<span class="note">Loading job data for ${collectionTypeDescription}.<span class="blinking">..</span></span>
|
||||
<span class="note">Loading job data for ${
|
||||
collectionTypeDescription
|
||||
}.<span class="blinking">..</span></span>
|
||||
<div class="progress-bar info" style="width:100%">
|
||||
</div>`;
|
||||
} else {
|
||||
@@ -106,7 +93,7 @@ var HDCAListItemView = _super.extend(
|
||||
var errorCount = jobStatesSummary.numInError();
|
||||
return `a ${collectionTypeDescription} with ${errorCount} / ${jobCount} jobs in error`;
|
||||
} else if (jobStatesSummary.terminal()) {
|
||||
return `a ${simpleDescription}`;
|
||||
return simpleDescription;
|
||||
} else {
|
||||
var running = jobStatesSummary.states()["running"] || 0;
|
||||
var ok = jobStatesSummary.states()["ok"] || 0;
|
||||
|
||||
@@ -2,6 +2,11 @@ import STATES from "mvc/dataset/states";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import _l from "utils/localization";
|
||||
|
||||
var collectionFuzzyCountDefault = 1000;
|
||||
try {
|
||||
collectionFuzzyCountDefault = localStorage.getItem("collectionFuzzyCountDefault") || collectionFuzzyCountDefault;
|
||||
} catch (err) {}
|
||||
|
||||
//==============================================================================
|
||||
/** @class Mixin for HistoryContents content (HDAs, HDCAs).
|
||||
*/
|
||||
@@ -58,6 +63,11 @@ var HistoryContentMixin = {
|
||||
var historyId = this.get("history_id");
|
||||
var historyContentId = this.get("id");
|
||||
var url = `${this.urlRoot}${historyId}/contents/${historyContentType}s/${historyContentId}`;
|
||||
if (historyContentType == "dataset_collection") {
|
||||
// Don't fetch whole collection - just enought to render outline. Backbone will
|
||||
// make a detailed request if any datasets are expanded beyond that point.
|
||||
url = `${url}?view=element-reference&fuzzy_count=${collectionFuzzyCountDefault}`;
|
||||
}
|
||||
return url;
|
||||
},
|
||||
|
||||
|
||||
@@ -6,6 +6,11 @@ import JOB_STATES_MODEL from "mvc/history/job-states-model";
|
||||
import BASE_MVC from "mvc/base-mvc";
|
||||
import AJAX_QUEUE from "utils/ajax-queue";
|
||||
|
||||
var limitPerPageDefault = 500;
|
||||
try {
|
||||
limitPerPageDefault = localStorage.getItem("historyContentsLimitPerPageDefault") || limitPerPageDefault;
|
||||
} catch (err) {}
|
||||
|
||||
//==============================================================================
|
||||
var _super = CONTROLLED_FETCH_COLLECTION.PaginatedCollection;
|
||||
/** @class Backbone collection for history content.
|
||||
@@ -20,10 +25,10 @@ var HistoryContents = _super.extend(BASE_MVC.LoggableMixin).extend({
|
||||
_logNamespace: "history",
|
||||
|
||||
// ........................................................................ set up
|
||||
limitPerPage: 500,
|
||||
limitPerPage: limitPerPageDefault,
|
||||
|
||||
/** @type {Integer} how many contents per call to fetch when using progressivelyFetchDetails */
|
||||
limitPerProgressiveFetch: 500,
|
||||
limitPerProgressiveFetch: limitPerPageDefault,
|
||||
|
||||
/** @type {String} order used here and when fetching from server */
|
||||
order: "hid",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import math
|
||||
|
||||
from galaxy import exceptions, model, web
|
||||
from galaxy.util import string_as_bool
|
||||
@@ -101,8 +102,9 @@ def get_collection_elements(collection, name=""):
|
||||
return names, hdas
|
||||
|
||||
|
||||
def dictify_dataset_collection_instance(dataset_collection_instance, parent, security, view="element"):
|
||||
dict_value = dataset_collection_instance.to_dict(view=view)
|
||||
def dictify_dataset_collection_instance(dataset_collection_instance, parent, security, view="element", fuzzy_count=None):
|
||||
hdca_view = "element" if view in ["element", "element-reference"] else "collection"
|
||||
dict_value = dataset_collection_instance.to_dict(view=hdca_view)
|
||||
encoded_id = security.encode_id(dataset_collection_instance.id)
|
||||
if isinstance(parent, model.History):
|
||||
encoded_history_id = security.encode_id(parent.id)
|
||||
@@ -112,23 +114,66 @@ def dictify_dataset_collection_instance(dataset_collection_instance, parent, sec
|
||||
encoded_folder_id = security.encode_id(parent.id)
|
||||
# TODO: Work in progress - this end-point is not right yet...
|
||||
dict_value['url'] = web.url_for('library_content', library_id=encoded_library_id, id=encoded_id, folder_id=encoded_folder_id)
|
||||
if view == "element":
|
||||
|
||||
if view in ["element", "element-reference"]:
|
||||
collection = dataset_collection_instance.collection
|
||||
dict_value['elements'] = [dictify_element(_) for _ in collection.elements]
|
||||
dict_value['populated'] = collection.populated
|
||||
rank_fuzzy_counts = gen_rank_fuzzy_counts(collection.collection_type, fuzzy_count)
|
||||
elements, rest_fuzzy_counts = get_fuzzy_count_elements(collection, rank_fuzzy_counts)
|
||||
if view == "element":
|
||||
dict_value['populated'] = collection.populated
|
||||
element_func = dictify_element
|
||||
else:
|
||||
element_func = dictify_element_reference
|
||||
dict_value['elements'] = [element_func(_, rank_fuzzy_counts=rest_fuzzy_counts) for _ in elements]
|
||||
|
||||
security.encode_all_ids(dict_value, recursive=True) # TODO: Use Kyle's recursive formulation of this.
|
||||
return dict_value
|
||||
|
||||
|
||||
def dictify_element(element):
|
||||
def dictify_element_reference(element, rank_fuzzy_counts=None):
|
||||
"""Load minimal details of elements required to show outline of contents in history panel.
|
||||
|
||||
History panel can use this reference to expand to full details if individual dataset elements
|
||||
are clicked.
|
||||
"""
|
||||
dictified = element.to_dict(view="element")
|
||||
element_object = element.element_object
|
||||
if element_object is not None:
|
||||
object_detials = dict(
|
||||
id=element_object.id,
|
||||
model_class=element_object.__class__.__name__,
|
||||
)
|
||||
if element.child_collection:
|
||||
object_detials["collection_type"] = element_object.collection_type
|
||||
child_collection = element.child_collection
|
||||
elements, rest_fuzzy_counts = get_fuzzy_count_elements(child_collection, rank_fuzzy_counts)
|
||||
# Recursively yield elements for each nested collection...
|
||||
object_detials["elements"] = [dictify_element_reference(_, rank_fuzzy_counts=rest_fuzzy_counts) for _ in elements]
|
||||
object_detials["element_count"] = child_collection.element_count
|
||||
else:
|
||||
object_detials["state"] = element_object.state
|
||||
object_detials["hda_ldda"] = 'hda'
|
||||
object_detials["history_id"] = element_object.history_id
|
||||
|
||||
else:
|
||||
object_detials = None
|
||||
|
||||
dictified["object"] = object_detials
|
||||
return dictified
|
||||
|
||||
|
||||
def dictify_element(element, rank_fuzzy_counts=None):
|
||||
dictified = element.to_dict(view="element")
|
||||
element_object = element.element_object
|
||||
if element_object is not None:
|
||||
object_detials = element.element_object.to_dict()
|
||||
if element.child_collection:
|
||||
child_collection = element.child_collection
|
||||
elements, rest_fuzzy_counts = get_fuzzy_count_elements(child_collection, rank_fuzzy_counts)
|
||||
|
||||
# Recursively yield elements for each nested collection...
|
||||
child_collection = element.child_collection
|
||||
object_detials["elements"] = [dictify_element(_) for _ in child_collection.elements]
|
||||
object_detials["elements"] = [dictify_element(_, rank_fuzzy_counts=rest_fuzzy_counts) for _ in elements]
|
||||
object_detials["populated"] = child_collection.populated
|
||||
object_detials["element_count"] = child_collection.element_count
|
||||
else:
|
||||
@@ -138,4 +183,91 @@ def dictify_element(element):
|
||||
return dictified
|
||||
|
||||
|
||||
def get_fuzzy_count_elements(collection, rank_fuzzy_counts):
|
||||
if rank_fuzzy_counts and rank_fuzzy_counts[0]:
|
||||
rank_fuzzy_count = rank_fuzzy_counts[0]
|
||||
elements = collection.elements[0:rank_fuzzy_count]
|
||||
else:
|
||||
elements = collection.elements
|
||||
|
||||
if rank_fuzzy_counts is not None:
|
||||
rest_fuzzy_counts = rank_fuzzy_counts[1:]
|
||||
else:
|
||||
rest_fuzzy_counts = None
|
||||
|
||||
return elements, rest_fuzzy_counts
|
||||
|
||||
|
||||
def gen_rank_fuzzy_counts(collection_type, fuzzy_count=None):
|
||||
"""Turn a global estimate on elements to return to per nested level based on collection type.
|
||||
|
||||
This takes an arbitrary constant and generates an arbitrary constant and is quite messy.
|
||||
None of this should be relied on as a stable API - it is more of a general guideline to
|
||||
restrict within broad ranges the amount of objects returned.
|
||||
|
||||
>>> def is_around(x, y):
|
||||
... return y - 1 < x and y + 1 > y
|
||||
...
|
||||
>>> gen_rank_fuzzy_counts("list", None)
|
||||
[None]
|
||||
>>> gen_rank_fuzzy_counts("list", 500)
|
||||
[500]
|
||||
>>> gen_rank_fuzzy_counts("paired", 500)
|
||||
[2]
|
||||
>>> gen_rank_fuzzy_counts("list:paired", None)
|
||||
[None, None]
|
||||
>>> gen_rank_fuzzy_counts("list:list", 101) # 100 would be edge case at 10 so bump to ensure 11
|
||||
[11, 11]
|
||||
>>> ll, pl = gen_rank_fuzzy_counts("list:paired", 100)
|
||||
>>> pl
|
||||
2
|
||||
>>> is_around(ll, 50)
|
||||
True
|
||||
>>> pl, ll = gen_rank_fuzzy_counts("paired:list", 100)
|
||||
>>> pl
|
||||
2
|
||||
>>> is_around(ll, 50)
|
||||
True
|
||||
>>> gen_rank_fuzzy_counts("list:list:list", 1001)
|
||||
[11, 11, 11]
|
||||
>>> l1l, l2l, l3l, pl = gen_rank_fuzzy_counts("list:list:list:paired", 2000)
|
||||
>>> pl
|
||||
2
|
||||
>>> is_around(10, l1l)
|
||||
True
|
||||
>>> gen_rank_fuzzy_counts("list:list:list", 1)
|
||||
[1, 1, 1]
|
||||
>>> gen_rank_fuzzy_counts("list:list:list", 2)
|
||||
[2, 2, 2]
|
||||
>>> gen_rank_fuzzy_counts("paired:paired", 400)
|
||||
[2, 2]
|
||||
>>> gen_rank_fuzzy_counts("paired:paired", 5)
|
||||
[2, 2]
|
||||
>>> gen_rank_fuzzy_counts("paired:paired", 3)
|
||||
[2, 2]
|
||||
>>> gen_rank_fuzzy_counts("paired:paired", 1)
|
||||
[1, 1]
|
||||
>>> gen_rank_fuzzy_counts("paired:paired", 2)
|
||||
[2, 2]
|
||||
"""
|
||||
rank_collection_types = collection_type.split(":")
|
||||
if fuzzy_count is None:
|
||||
return [None for rt in rank_collection_types]
|
||||
else:
|
||||
# This is a list...
|
||||
paired_count = sum([1 if rt == "paired" else 0 for rt in rank_collection_types])
|
||||
list_count = len(rank_collection_types) - paired_count
|
||||
paired_fuzzy_count_mult = 1 if paired_count == 0 else 2 << (paired_count - 1)
|
||||
list_fuzzy_count_mult = math.floor((fuzzy_count * 1.0) / paired_fuzzy_count_mult)
|
||||
list_rank_fuzzy_count = int(math.floor(math.pow(list_fuzzy_count_mult, 1.0 / list_count)) + 1) if list_count > 0 else 1.0
|
||||
pair_rank_fuzzy_count = 2
|
||||
if list_rank_fuzzy_count > fuzzy_count:
|
||||
list_rank_fuzzy_count = fuzzy_count
|
||||
if pair_rank_fuzzy_count > fuzzy_count:
|
||||
pair_rank_fuzzy_count = fuzzy_count
|
||||
rank_fuzzy_counts = [pair_rank_fuzzy_count if rt == "paired" else list_rank_fuzzy_count for rt in rank_collection_types]
|
||||
|
||||
return rank_fuzzy_counts
|
||||
|
||||
|
||||
__all__ = ('api_payload_to_create_params', 'dictify_dataset_collection_instance')
|
||||
|
||||
@@ -127,9 +127,9 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary
|
||||
|
||||
return rval
|
||||
|
||||
def __collection_dict(self, trans, dataset_collection_instance, view="collection"):
|
||||
def __collection_dict(self, trans, dataset_collection_instance, **kwds):
|
||||
return dictify_dataset_collection_instance(dataset_collection_instance,
|
||||
security=trans.security, parent=dataset_collection_instance.history, view=view)
|
||||
security=trans.security, parent=dataset_collection_instance.history, **kwds)
|
||||
|
||||
@expose_api_anonymous
|
||||
def show(self, trans, id, history_id, **kwd):
|
||||
@@ -145,6 +145,28 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary
|
||||
:param id: 'dataset' or 'dataset_collection'
|
||||
:type history_id: str
|
||||
:param history_id: encoded id string of the HDA's or HDCA's History
|
||||
:type view: str
|
||||
:param view: if fetching a dataset collection - the view style of
|
||||
the dataset collection to produce.
|
||||
'collection' returns no element information, 'element'
|
||||
returns detailed element information for all datasets,
|
||||
'element-reference' returns a minimal set of information
|
||||
about datasets (for instance id, type, and state but not
|
||||
metadata, peek, info, or name). The default is 'element'.
|
||||
:type fuzzy_count: int
|
||||
:param fuzzy_count: this value can be used to broadly restrict the magnitude
|
||||
of the number of elements returned via the API for large
|
||||
collections. The number of actual elements returned may
|
||||
be "a bit" more than this number or "a lot" less - varying
|
||||
on the depth of nesting, balance of nesting at each level,
|
||||
and size of target collection. The consumer of this API should
|
||||
not expect a stable number or pre-calculable number of
|
||||
elements to be produced given this parameter - the only
|
||||
promise is that this API will not respond with an order
|
||||
of magnitude more elements estimated with this value.
|
||||
The UI uses this parameter to fetch a "balanced" concept of
|
||||
the "start" of large collections at every depth of the
|
||||
collection.
|
||||
|
||||
:rtype: dict
|
||||
:returns: dictionary containing detailed HDA or HDCA information
|
||||
@@ -242,7 +264,11 @@ class HistoryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary
|
||||
|
||||
def __show_dataset_collection(self, trans, id, history_id, **kwd):
|
||||
dataset_collection_instance = self.__get_accessible_collection(trans, id, history_id)
|
||||
return self.__collection_dict(trans, dataset_collection_instance, view="element")
|
||||
view = kwd.get("view", "element")
|
||||
fuzzy_count = kwd.get("fuzzy_count", None)
|
||||
if fuzzy_count:
|
||||
fuzzy_count = int(fuzzy_count)
|
||||
return self.__collection_dict(trans, dataset_collection_instance, view=view, fuzzy_count=fuzzy_count)
|
||||
|
||||
def __get_accessible_collection(self, trans, id, history_id):
|
||||
return trans.app.dataset_collections_service.get_dataset_collection_instance(
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
define("mvc/collection/collection-view",["exports","mvc/list/list-view","mvc/collection/collection-model","mvc/collection/collection-li","mvc/base-mvc","utils/localization"],function(e,t,l,i,o,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(e,"__esModule",{value:!0});var n=s(t),c=(s(l),s(i)),d=s(o),r=s(a),p=n.default.ModelListPanel,f=p.extend({_logNamespace:"collections",className:p.prototype.className+" dataset-collection-panel",DatasetDCEViewClass:c.default.DatasetDCEListItemView,modelCollectionKey:"elements",initialize:function(e){p.prototype.initialize.call(this,e),this.linkTarget=e.linkTarget||"_blank",this.hasUser=e.hasUser,this.panelStack=[],this.parentName=e.parentName,this.foldoutStyle=e.foldoutStyle||"foldout",this.downloadUrl=Galaxy.root+"api/dataset_collections/"+this.model.attributes.id+"/download"},getNestedDCDCEViewClass:function(){return c.default.NestedDCDCEListItemView.extend({foldoutPanelClass:f})},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var l=this;l.log("_queueNewRender:",e,t),l._swapNewRender(e),l.trigger("rendered",l)},_filterCollection:function(){return this.model.getVisibleContents()},_getItemViewClass:function(e){switch(e.get("element_type")){case"hda":return this.DatasetDCEViewClass;case"dataset_collection":return this.getNestedDCDCEViewClass()}throw new TypeError("Unknown element type:",e.get("element_type"))},_getItemViewOptions:function(e){var t=p.prototype._getItemViewOptions.call(this,e);return _.extend(t,{linkTarget:this.linkTarget,hasUser:this.hasUser,foldoutStyle:this.foldoutStyle})},_setUpItemViewListeners:function(e){var t=this;return p.prototype._setUpItemViewListeners.call(t,e),t.listenTo(e,{"expanded:drilldown":function(e,t){this._expandDrilldownPanel(t)},"collapsed:drilldown":function(e,t){this._collapseDrilldownPanel(t)}}),this},_expandDrilldownPanel:function(e){this.panelStack.push(e),this.$("> .controls").add(this.$list()).hide(),e.parentName=this.model.get("name"),this.$el.append(e.render().$el)},_collapseDrilldownPanel:function(e){this.panelStack.pop(),this.render()},events:{"click .navigation .back":"close"},close:function(e){this.remove(),this.trigger("close")},toString:function(){return"CollectionView("+(this.model?this.model.get("name"):"")+")"}});f.prototype.templates=function(){var e=d.default.wrapTemplate(['<div class="controls">','<div class="navigation">','<a class="back" href="javascript:void(0)">','<span class="fa fa-icon fa-angle-left"></span>',(0,r.default)("Back to "),"<%- view.parentName %>","</a>","</div>",'<div class="title">','<div class="name"><%- collection.name || collection.element_identifier %></div>','<div class="subtitle">','<% if( collection.collection_type === "list" ){ %>',(0,r.default)("a list of datasets"),'<% } else if( collection.collection_type === "paired" ){ %>',(0,r.default)("a pair of datasets"),'<% } else if( collection.collection_type === "list:paired" ){ %>',(0,r.default)("a list of paired datasets"),'<% } else if( collection.collection_type === "list:list" ){ %>',(0,r.default)("a list of dataset lists"),"<% } %>","</div>","</div>",'<div class="tags-display"></div>','<div class="actions">','<a class="download-btn icon-btn" ','href="<%- view.downloadUrl %>','" title="" download="" data-original-title="Download Collection">','<span class="fa fa-floppy-o"></span>',"</a>","</div>","</div>"],"collection");return _.extend(_.clone(p.prototype.templates),{controls:e})}(),e.default={CollectionView:f}});
|
||||
define("mvc/collection/collection-view",["exports","mvc/list/list-view","mvc/collection/collection-model","mvc/collection/collection-li","mvc/base-mvc","utils/localization"],function(e,t,n,i,l,a){"use strict";function s(e){return e&&e.__esModule?e:{default:e}}function o(e){var t=e.get("collection_type");return"list"==t?(0,p.default)("list"):"paired"==t?(0,p.default)("dataset pair"):"list:paired"==t?(0,p.default)("list of pairs"):(0,p.default)("nested list")}function d(e){var t=e.get("element_count"),n="a "+o(e);if(t){var i;1==t?i="with 1 item":t&&(i="with "+t+" items"),n=n+" "+(0,p.default)(i)}return n}Object.defineProperty(e,"__esModule",{value:!0});var c=s(t),r=(s(n),s(i)),p=(s(l),s(a)),u=c.default.ModelListPanel,f=u.extend({_logNamespace:"collections",className:u.prototype.className+" dataset-collection-panel",DatasetDCEViewClass:r.default.DatasetDCEListItemView,modelCollectionKey:"elements",initialize:function(e){u.prototype.initialize.call(this,e),this.linkTarget=e.linkTarget||"_blank",this.hasUser=e.hasUser,this.panelStack=[],this.parentName=e.parentName,this.foldoutStyle=e.foldoutStyle||"foldout",this.downloadUrl=Galaxy.root+"api/dataset_collections/"+this.model.attributes.id+"/download"},getNestedDCDCEViewClass:function(){return r.default.NestedDCDCEListItemView.extend({foldoutPanelClass:f})},_queueNewRender:function(e,t){t=void 0===t?this.fxSpeed:t;var n=this;this.handleWarning(e),n.log("_queueNewRender:",e,t),n._swapNewRender(e),n.trigger("rendered",n)},handleWarning:function(e){var t=this.views.length,n=this.model.get("element_count");if(n&&n!==t){var i=(0,p.default)("displaying only "+t+" of "+n+" items");e.find(".elements-warning").html('<div class="warningmessagesmall">'+i+"</div>")}},_filterCollection:function(){return this.model.getVisibleContents()},_getItemViewClass:function(e){switch(e.get("element_type")){case"hda":return this.DatasetDCEViewClass;case"dataset_collection":return this.getNestedDCDCEViewClass()}throw new TypeError("Unknown element type:",e.get("element_type"))},_getItemViewOptions:function(e){var t=u.prototype._getItemViewOptions.call(this,e);return _.extend(t,{linkTarget:this.linkTarget,hasUser:this.hasUser,foldoutStyle:this.foldoutStyle})},_setUpItemViewListeners:function(e){var t=this;return u.prototype._setUpItemViewListeners.call(t,e),t.listenTo(e,{"expanded:drilldown":function(e,t){this._expandDrilldownPanel(t)},"collapsed:drilldown":function(e,t){this._collapseDrilldownPanel(t)}}),this},_expandDrilldownPanel:function(e){this.panelStack.push(e),this.$("> .controls").add(this.$list()).hide(),e.parentName=this.model.get("name"),this.$el.append(e.render().$el)},_collapseDrilldownPanel:function(e){this.panelStack.pop(),this.render()},events:{"click .navigation .back":"close"},close:function(e){this.remove(),this.trigger("close")},toString:function(){return"CollectionView("+(this.model?this.model.get("name"):"")+")"}});f.prototype.templates=_.extend(_.clone(u.prototype.templates),{controls:function(e,t){var n=d(t.model);return'\n <div class="controls">\n <div class="navigation">\n <a class="back" href="javascript:void(0)">\n <span class="fa fa-icon fa-angle-left"></span>\n '+(0,p.default)("Back to ")+"\n "+_.escape(t.parentName)+'\n </a>\n </div>\n <div class="title">\n <div class="name">'+(_.escape(e.name)||_.escape(e.element_identifier))+'</div>\n <div class="subtitle">\n '+n+'\n </div>\n </div>\n <div class="elements-warning">\n </div>\n <div class="tags-display"></div>\n <div class="actions">\n <a class="download-btn icon-btn" href="'+t.downloadUrl+'"\n title="" download="" data-original-title="Download Collection">\n <span class="fa fa-floppy-o"></span>\n </a>\n </div>\n </div>'}}),e.default={collectionTypeDescription:o,collectionDescription:d,CollectionView:f}});
|
||||
@@ -1 +1 @@
|
||||
define("mvc/history/hdca-li",["exports","mvc/dataset/states","mvc/collection/collection-li","mvc/collection/collection-view","mvc/base-mvc","mvc/history/history-item-li","utils/localization"],function(t,e,s,n,i,a,r){"use strict";function o(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(t,"__esModule",{value:!0});var l=o(e),d=o(s),c=o(n),p=(o(i),o(a)),u=o(r),m=d.default.DCListItemView,h=m.extend({className:m.prototype.className+" history-content",_setUpListeners:function(){var t=this;m.prototype._setUpListeners.call(this);var e=function(e,s){t.render()};this.model.jobStatesSummary&&this.listenTo(this.model.jobStatesSummary,"change",e),this.listenTo(this.model,{"change:tags change:visible change:state":e})},_getFoldoutPanelClass:function(){return c.default.CollectionView},_swapNewRender:function(t){m.prototype._swapNewRender.call(this,t);var e,s=this.model.jobStatesSummary;e=s?s.new()?"loading":s.errored()?"error":s.terminal()?"ok":s.running()?"running":"queued":this.model.get("job_source_id")?"loading":this.model.get("populated_state")?l.default.OK:l.default.RUNNING,this.$el.addClass("state-"+e);var n=this.stateDescription();return this.$(".state-description").html(n),this.$el},stateDescription:function(){var t,e=this.model,s=e.get("element_count"),n=e.get("job_source_type"),i=this.model.get("collection_type");t="list"==i?"list":"paired"==i?"dataset pair":"list:paired"==i?"list of pairs":"nested list";var a="";1==s?a=" with 1 item":s&&(a=" with "+s+" items");var r=e.jobStatesSummary,o=""+t+a;if(n&&"Job"!=n){if(r&&r.hasDetails()){var l=r.new(),d=l?null:r.jobCount();if(l)return'\n <div class="progress state-progress">\n <span class="note">Creating jobs.<span class="blinking">..</span></span>\n <div class="progress-bar info" style="width:100%">\n </div>';if(r.errored())return"a "+t+" with "+r.numInError()+" / "+d+" jobs in error";if(r.terminal())return"a "+o;var c=r.states().running||0,p=(r.states().ok||0)/(1*d),u=c/(1*d),m=1-p-u;return'\n <div class="progress state-progress">\n <span class="note">'+(d&&d>1?d+" jobs":"a job")+" generating a "+t+'</span>\n <div class="progress-bar ok" style="width:'+100*p+'%"></div>\n <div class="progress-bar running" style="width:'+100*u+'%"></div>\n <div class="progress-bar new" style="width:'+100*m+'%">\n </div>'}return'\n <div class="progress state-progress">\n <span class="note">Loading job data for '+t+'.<span class="blinking">..</span></span>\n <div class="progress-bar info" style="width:100%">\n </div>'}return"a "+o},toString:function(){return"HDCAListItemView("+(this.model?""+this.model:"(no model)")+")"}});h.prototype.templates=function(){var t=_.extend({},m.prototype.templates.warnings,{hidden:function(t){t.visible||(0,u.default)("This collection has been hidden")}});return _.extend({},m.prototype.templates,{warnings:t,titleBar:function(t){return'\n <div class="title-bar clear" tabindex="0">\n <span class="state-icon"></span>\n <div class="title">\n <span class="hid">'+t.hid+'</span>\n <span class="name">'+_.escape(t.name)+'</span>\n </div>\n <div class="state-description">\n </div>\n '+p.default.nametagTemplate(t)+"\n </div>\n "}})}(),t.default={HDCAListItemView:h}});
|
||||
define("mvc/history/hdca-li",["exports","mvc/dataset/states","mvc/collection/collection-li","mvc/collection/collection-view","mvc/base-mvc","mvc/history/history-item-li","utils/localization"],function(e,t,s,n,i,a,o){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(e,"__esModule",{value:!0});var l=r(t),c=r(s),d=r(n),p=(r(i),r(a)),u=r(o),m=c.default.DCListItemView,h=m.extend({className:m.prototype.className+" history-content",_setUpListeners:function(){var e=this;m.prototype._setUpListeners.call(this);var t=function(t,s){e.render()};this.model.jobStatesSummary&&this.listenTo(this.model.jobStatesSummary,"change",t),this.listenTo(this.model,{"change:tags change:visible change:state":t})},_getFoldoutPanelClass:function(){return d.default.CollectionView},_swapNewRender:function(e){m.prototype._swapNewRender.call(this,e);var t,s=this.model.jobStatesSummary;t=s?s.new()?"loading":s.errored()?"error":s.terminal()?"ok":s.running()?"running":"queued":this.model.get("job_source_id")?"loading":this.model.get("populated_state")?l.default.OK:l.default.RUNNING,this.$el.addClass("state-"+t);var n=this.stateDescription();return this.$(".state-description").html(n),this.$el},stateDescription:function(){var e=this.model,t=(e.get("element_count"),e.get("job_source_type")),s=(this.model.get("collection_type"),d.default.collectionTypeDescription(e)),n=d.default.collectionDescription(e),i=e.jobStatesSummary;if(t&&"Job"!=t){if(i&&i.hasDetails()){var a=i.new(),o=a?null:i.jobCount();if(a)return'\n <div class="progress state-progress">\n <span class="note">Creating jobs.<span class="blinking">..</span></span>\n <div class="progress-bar info" style="width:100%">\n </div>';if(i.errored())return"a "+s+" with "+i.numInError()+" / "+o+" jobs in error";if(i.terminal())return n;var r=i.states().running||0,l=(i.states().ok||0)/(1*o),c=r/(1*o),p=1-l-c;return'\n <div class="progress state-progress">\n <span class="note">'+(o&&o>1?o+" jobs":"a job")+" generating a "+s+'</span>\n <div class="progress-bar ok" style="width:'+100*l+'%"></div>\n <div class="progress-bar running" style="width:'+100*c+'%"></div>\n <div class="progress-bar new" style="width:'+100*p+'%">\n </div>'}return'\n <div class="progress state-progress">\n <span class="note">Loading job data for '+s+'.<span class="blinking">..</span></span>\n <div class="progress-bar info" style="width:100%">\n </div>'}return n},toString:function(){return"HDCAListItemView("+(this.model?""+this.model:"(no model)")+")"}});h.prototype.templates=function(){var e=_.extend({},m.prototype.templates.warnings,{hidden:function(e){e.visible||(0,u.default)("This collection has been hidden")}});return _.extend({},m.prototype.templates,{warnings:e,titleBar:function(e){return'\n <div class="title-bar clear" tabindex="0">\n <span class="state-icon"></span>\n <div class="title">\n <span class="hid">'+e.hid+'</span>\n <span class="name">'+_.escape(e.name)+'</span>\n </div>\n <div class="state-description">\n </div>\n '+p.default.nametagTemplate(e)+"\n </div>\n "}})}(),e.default={HDCAListItemView:h}});
|
||||
@@ -1 +1 @@
|
||||
define("mvc/history/history-content-model",["exports","mvc/dataset/states","mvc/base-mvc","utils/localization"],function(t,e,i,s){"use strict";function n(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(t,"__esModule",{value:!0});n(e),n(i),n(s);var r={defaults:{history_id:null,history_content_type:null,hid:null,visible:!0},idAttribute:"type_id",hidden:function(){return!this.get("visible")},isVisible:function(t,e){var i=!0;return t||!this.get("deleted")&&!this.get("purged")||(i=!1),e||this.get("visible")||(i=!1),i},urlRoot:Galaxy.root+"api/histories/",url:function(){var t=this.get("history_content_type"),e=this.get("history_id"),i=this.get("id"),s=""+this.urlRoot+e+"/contents/"+t+"s/"+i;return s},hide:function(t){return this.get("visible")?this.save({visible:!1},t):jQuery.when()},unhide:function(t){return this.get("visible")?jQuery.when():this.save({visible:!0},t)},toString:function(){return[this.get("type_id"),this.get("hid"),this.get("name")].join(":")}};t.default={HistoryContentMixin:r}});
|
||||
define("mvc/history/history-content-model",["exports","mvc/dataset/states","mvc/base-mvc","utils/localization"],function(t,e,i,n){"use strict";function s(t){return t&&t.__esModule?t:{default:t}}Object.defineProperty(t,"__esModule",{value:!0});s(e),s(i),s(n);var o=1e3;try{o=localStorage.getItem("collectionFuzzyCountDefault")||o}catch(t){}var r={defaults:{history_id:null,history_content_type:null,hid:null,visible:!0},idAttribute:"type_id",hidden:function(){return!this.get("visible")},isVisible:function(t,e){var i=!0;return t||!this.get("deleted")&&!this.get("purged")||(i=!1),e||this.get("visible")||(i=!1),i},urlRoot:Galaxy.root+"api/histories/",url:function(){var t=this.get("history_content_type"),e=this.get("history_id"),i=this.get("id"),n=""+this.urlRoot+e+"/contents/"+t+"s/"+i;return"dataset_collection"==t&&(n=n+"?view=element-reference&fuzzy_count="+o),n},hide:function(t){return this.get("visible")?this.save({visible:!1},t):jQuery.when()},unhide:function(t){return this.get("visible")?jQuery.when():this.save({visible:!0},t)},toString:function(){return[this.get("type_id"),this.get("hid"),this.get("name")].join(":")}};t.default={HistoryContentMixin:r}});
|
||||
File diff suppressed because one or more lines are too long
@@ -154,6 +154,14 @@ class NavigatesGalaxy(HasDriver):
|
||||
def switch_to_main_panel(self):
|
||||
self.driver.switch_to.frame("galaxy_main")
|
||||
|
||||
@contextlib.contextmanager
|
||||
def local_storage(self, key, value):
|
||||
self.driver.execute_script('''window.localStorage.setItem("%s", %s);''' % (key, value))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self.driver.execute_script('''window.localStorage.removeItem("%s");''' % key)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def main_panel(self):
|
||||
try:
|
||||
|
||||
@@ -110,8 +110,9 @@ history_panel:
|
||||
_: '.list-panel.dataset-collection-panel'
|
||||
back: '.navigation .back'
|
||||
title: '.dataset-collection-panel .controls .title .editable-text'
|
||||
title_input: '.dataset-collection-panel .controls .title input'
|
||||
title_input: '.dataset-collection-panel .controls .title input'
|
||||
subtitle: '.dataset-collection-panel .controls .title .subtitle'
|
||||
elements_warning: '.dataset-collection-panel .controls .elements-warning'
|
||||
tag_area_input: '.controls .tags-display .tags-input input'
|
||||
list_items: '.dataset-collection-panel .list-items .list-item'
|
||||
|
||||
@@ -139,6 +140,13 @@ history_panel:
|
||||
options_menu: '#history-options-button-menu'
|
||||
multi_view_button: '#history-view-multi-button'
|
||||
|
||||
pagination_pages: '.list-pagination .pages'
|
||||
pagination_pages_options: '.list-pagination .pages option'
|
||||
pagination_pages_selected_option: '.list-pagination .pages option:checked'
|
||||
pagination_next: '.list-pagination button.next'
|
||||
pagination_previous: '.list-pagination button.prev'
|
||||
|
||||
|
||||
text:
|
||||
tooltip_name: 'Click to rename history'
|
||||
new_name: 'Unnamed history'
|
||||
|
||||
@@ -220,6 +220,21 @@ class HistoryPanelCollectionsTestCase(SeleniumTestCase):
|
||||
self._click_and_wait_for_collection_view(collection_hid)
|
||||
self.screenshot("history_panel_collection_view_list_list")
|
||||
|
||||
@selenium_test
|
||||
def test_limiting_collection_rendering(self):
|
||||
history_id = self.current_history_id()
|
||||
collection = self.dataset_collection_populator.create_list_in_history(history_id, contents=["0", "1", "0", "1"]).json()
|
||||
collection_hid = collection["hid"]
|
||||
|
||||
with self.local_storage("collectionFuzzyCountDefault", 2):
|
||||
self.home()
|
||||
|
||||
self.history_panel_wait_for_hid_state(collection_hid, "ok")
|
||||
self._click_and_wait_for_collection_view(collection_hid)
|
||||
self.screenshot("history_panel_collection_view_limiting")
|
||||
warning_text = self.components.history_panel.collection_view.elements_warning.wait_for_text()
|
||||
assert "only 2 of 4 items" in warning_text, warning_text
|
||||
|
||||
def _generate_partially_failed_collection_with_input(self):
|
||||
history_id = self.current_history_id()
|
||||
input_collection = self.dataset_collection_populator.create_list_in_history(history_id, contents=["0", "1", "0", "1"]).json()
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
from base.populators import flakey
|
||||
|
||||
from .framework import (
|
||||
selenium_test,
|
||||
SeleniumTestCase
|
||||
)
|
||||
|
||||
|
||||
class HistoryPanelPaginationTestCase(SeleniumTestCase):
|
||||
|
||||
ensure_registered = True
|
||||
|
||||
@selenium_test
|
||||
@flakey # The next button doesn't always work - maybe a delay in JS callback registering for that.
|
||||
def test_pagination(self):
|
||||
history_id = self.current_history_id()
|
||||
|
||||
self.dataset_populator.new_dataset(history_id, content='1\t2\t3', name="data1")
|
||||
self.dataset_populator.new_dataset(history_id, content='2\t3\t4', name="data2")
|
||||
self.dataset_populator.new_dataset(history_id, content='3\t4\t5', name="data3")
|
||||
self.dataset_populator.new_dataset(history_id, content='4\t5\t6', name="data4")
|
||||
self.dataset_populator.new_dataset(history_id, content='5\t6\t7', name="data5")
|
||||
|
||||
self.home()
|
||||
for hid in [1, 2, 3, 4, 5]:
|
||||
self.history_panel_wait_for_hid_state(hid, "ok")
|
||||
|
||||
with self.local_storage("historyContentsLimitPerPageDefault", 3):
|
||||
self.home()
|
||||
self.history_panel_wait_for_hid_state(5, "ok")
|
||||
self.screenshot("history_panel_pagination_initial")
|
||||
pagination_option_text = self.components.history_panel.pagination_pages_selected_option.wait_for_text()
|
||||
assert "1st of 2 pages" in pagination_option_text
|
||||
self.components.history_panel.pagination_pages.wait_for_and_click()
|
||||
self.screenshot("history_panel_pagination_pages_drop_down")
|
||||
self.components.history_panel.pagination_next.wait_for_and_click()
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
self.screenshot("history_panel_pagination_second")
|
||||
pagination_option_text = self.components.history_panel.pagination_pages_selected_option.wait_for_text()
|
||||
assert "2nd of 2 pages" in pagination_option_text
|
||||
self.components.history_panel.pagination_previous.wait_for_and_click()
|
||||
|
||||
self.sleep_for(self.wait_types.UX_TRANSITION)
|
||||
pagination_option_text = self.components.history_panel.pagination_pages_selected_option.wait_for_text()
|
||||
assert "1st of 2 pages" in pagination_option_text
|
||||
Reference in New Issue
Block a user