diff --git a/client/galaxy/scripts/mvc/history/history-list.js b/client/galaxy/scripts/mvc/history/history-list.js index 98f0ad17f6f..5fda9c9be7d 100644 --- a/client/galaxy/scripts/mvc/history/history-list.js +++ b/client/galaxy/scripts/mvc/history/history-list.js @@ -1,11 +1,60 @@ import _l from "utils/localization"; -/** This class renders the grid list. */ +import AjaxQueue from "utils/ajax-queue"; import Utils from "utils/utils"; +/** This class renders the grid list. */ import GridView from "mvc/grid/grid-view"; import HistoryModel from "mvc/history/history-model"; import historyCopyDialog from "mvc/history/copy-dialog"; var HistoryGridView = GridView.extend({ + initialize: function(grid_config) { + this.ajaxQueue = new AjaxQueue.AjaxQueue(); + GridView.prototype.initialize.call(this, grid_config); + }, + + init_grid_elements: function() { + const ajaxQueue = this.ajaxQueue; + ajaxQueue.stop(); + GridView.prototype.init_grid_elements.call(this); + const fetchDetails = $.makeArray( + this.$el.find(".delayed-value-datasets_by_state").map((i, el) => { + return () => { + const historyId = $(el).data("history-id"); + const url = `${ + Galaxy.root + }api/histories/${historyId}?keys=nice_size,contents_active,contents_states`; + const options = {}; + options.url = url; + options.type = "GET"; + options.success = req => { + const contentsStates = req["contents_states"]; + let stateHtml = ""; + for (let state of ["ok", "running", "queued", "new", "error"]) { + const stateCount = contentsStates[state]; + if (stateCount) { + stateHtml += `
${stateCount}
`; + } + } + const contentsActive = req["contents_active"]; + const deleted = contentsActive["deleted"]; + if (deleted) { + stateHtml += `
${deleted}
`; + } + const hidden = contentsActive["hidden"]; + if (hidden) { + stateHtml += `
${hidden}
`; + } + $(`.delayed-value-datasets_by_state[data-history-id='${historyId}']`).html(stateHtml); + $(`.delayed-value-disk_size[data-history-id='${historyId}']`).html(req["nice_size"]); + }; + var xhr = jQuery.ajax(options); + return xhr; + }; + }) + ); + fetchDetails.forEach(fn => ajaxQueue.add(fn)); + ajaxQueue.start(); + }, _showCopyDialog: function(id) { var history = new HistoryModel.History({ id: id }); history diff --git a/client/galaxy/style/less/base.less b/client/galaxy/style/less/base.less index 9f8db6d3181..84267dac07a 100644 --- a/client/galaxy/style/less/base.less +++ b/client/galaxy/style/less/base.less @@ -1181,6 +1181,12 @@ ul.manage-table-actions li { background: @state-deleted-bg; } +.state-color-hidden { + border-color: @state-default-border; + border-style: dotted; + background: @state-default-bg; +} + .state-fg-new { color: #FFB030; } diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 37eaa887e4d..2d97eacd985 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -1574,6 +1574,10 @@ mapper(model.History, model.History.table, properties=dict( average_rating=column_property( select([func.avg(model.HistoryRatingAssociation.table.c.rating)]).where(model.HistoryRatingAssociation.table.c.history_id == model.History.table.c.id), deferred=True + ), + users_shared_with_count=column_property( + select([func.count(model.HistoryUserShareAssociation.table.c.id)]).where(model.History.table.c.id == model.HistoryUserShareAssociation.table.c.history_id), + deferred=True ) )) diff --git a/lib/galaxy/web/framework/helpers/grids.py b/lib/galaxy/web/framework/helpers/grids.py index 2c6da1988c6..9c5ce3d4da3 100644 --- a/lib/galaxy/web/framework/helpers/grids.py +++ b/lib/galaxy/web/framework/helpers/grids.py @@ -822,13 +822,17 @@ class StateColumn(GridColumn): class SharingStatusColumn(GridColumn): """ Grid column to indicate sharing status. """ + def __init__(self, *args, **kwargs): + self.use_shared_with_count = kwargs.pop("use_shared_with_count", False) + super(SharingStatusColumn, self).__init__(*args, **kwargs) + def get_value(self, trans, grid, item): # Delete items cannot be shared. if item.deleted: return "" # Build a list of sharing for this item. sharing_statuses = [] - if item.users_shared_with: + if self._is_shared(item): sharing_statuses.append("Shared") if item.importable: sharing_statuses.append("Accessible") @@ -836,8 +840,16 @@ class SharingStatusColumn(GridColumn): sharing_statuses.append("Published") return ", ".join(sharing_statuses) + def _is_shared(self, item): + if self.use_shared_with_count: + # optimization to skip join for users_shared_with and loading in that data. + return item.users_shared_with_count > 0 + + return item.users_shared_with + def get_link(self, trans, grid, item): - if not item.deleted and (item.users_shared_with or item.importable or item.published): + is_shared = self._is_shared(item) + if not item.deleted and (is_shared or item.importable or item.published): return dict(operation="share or publish", id=item.id) return None diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py index 8e34e557f2c..239b01458e0 100644 --- a/lib/galaxy/webapps/galaxy/controllers/history.py +++ b/lib/galaxy/webapps/galaxy/controllers/history.py @@ -4,7 +4,7 @@ import sets from markupsafe import escape from six import string_types from six.moves.urllib.parse import unquote_plus -from sqlalchemy import and_, false, func, null, true +from sqlalchemy import and_, false, null, true from sqlalchemy.orm import eagerload, eagerload_all, undefer import galaxy.util @@ -16,7 +16,7 @@ from galaxy.model.item_attrs import ( UsesAnnotations, UsesItemRatings ) -from galaxy.util import listify, nice_size, Params, parse_int, sanitize_text +from galaxy.util import listify, Params, parse_int, sanitize_text from galaxy.util.odict import odict from galaxy.util.sanitize_html import sanitize_html from galaxy.web import url_for @@ -44,28 +44,13 @@ class NameColumn(grids.TextColumn): class HistoryListGrid(grids.Grid): # Custom column types - class DatasetsByStateColumn(grids.GridColumn): + class DelayedValueColumn(grids.GridColumn): def get_value(self, trans, grid, history): - # States to show in column. - states_to_show = ('ok', 'running', 'queued', 'new', 'error') + return '
' % (self.key, trans.security.encode_id(history.id)) - # Get dataset counts for each state in a state-count dictionary. - state_counts = dict((state, count) for state, count in - trans.sa_session.query(model.Dataset.state, func.count(model.Dataset.state)) - .join(model.HistoryDatasetAssociation) - .group_by(model.Dataset.state) - .filter(model.HistoryDatasetAssociation.history_id == history.id, - model.HistoryDatasetAssociation.visible == true(), - model.HistoryDatasetAssociation.deleted == false(), - model.Dataset.state.in_(states_to_show))) - - # Create HTML. - rval = '' - for state in states_to_show: - count = state_counts.get(state) - if count: - rval += '
%s
' % (state, count) - return rval + class ItemCountColumn(grids.GridColumn): + def get_value(self, trans, grid, history): + return str(history.hid_counter - 1) class HistoryListNameColumn(NameColumn): def get_link(self, trans, grid, history): @@ -91,17 +76,24 @@ class HistoryListGrid(grids.Grid): query = query.order_by(self.model_class.table.c.purged.desc(), self.model_class.table.c.update_time.desc()) return query + def build_initial_query(self, trans, **kwargs): + # Override to preload sharing information used when fetching data for grid. + query = super(HistoryListGrid, self).build_initial_query(trans, **kwargs) + query = query.options(undefer("users_shared_with_count")) + return query + # Grid definition title = "Saved Histories" model_class = model.History default_sort_key = "-update_time" columns = [ HistoryListNameColumn("Name", key="name", attach_popup=True, filterable="advanced"), - DatasetsByStateColumn("Datasets", key="datasets_by_state", sortable=False, nowrap=True), + ItemCountColumn("Items", key="item_count", sortable=False), + DelayedValueColumn("Datasets", key="datasets_by_state", sortable=False, nowrap=True), grids.IndividualTagsColumn("Tags", key="tags", model_tag_association_class=model.HistoryTagAssociation, filterable="advanced", grid_name="HistoryListGrid"), - grids.SharingStatusColumn("Sharing", key="sharing", filterable="advanced", sortable=False), - grids.GridColumn("Size on Disk", key="disk_size", format=nice_size, sortable=False), + grids.SharingStatusColumn("Sharing", key="sharing", filterable="advanced", sortable=False, use_shared_with_count=True), + DelayedValueColumn("Size on Disk", key="disk_size", sortable=False), grids.GridColumn("Created", key="create_time", format=time_ago), grids.GridColumn("Last Updated", key="update_time", format=time_ago), DeletedColumn("Status", key="deleted", filterable="advanced") @@ -109,7 +101,7 @@ class HistoryListGrid(grids.Grid): columns.append( grids.MulticolFilterColumn( "search history names and tags", - cols_to_filter=[columns[0], columns[2]], + cols_to_filter=[columns[0], columns[3]], key="free-text-search", visible=False, filterable="standard") ) operations = [ diff --git a/test/selenium_tests/test_saved_histories.py b/test/selenium_tests/test_saved_histories.py index c96fc2a2a7f..80a8d75c409 100644 --- a/test/selenium_tests/test_saved_histories.py +++ b/test/selenium_tests/test_saved_histories.py @@ -329,7 +329,7 @@ class SavedHistoriesTestCase(SharedStateSeleniumTestCase): for row in grid.find_elements_by_tag_name('tr'): td = row.find_elements_by_tag_name('td') if td[1].text == history_name: - tags_cell = td[3] + tags_cell = td[4] break if tags_cell is None: