From 4c401cddc1ad8945f2caf6154f17e58116e8d3f2 Mon Sep 17 00:00:00 2001 From: Jeremy Goecks Date: Thu, 8 Oct 2009 13:38:32 -0400 Subject: [PATCH] Enables viewing of an importable/sharable history. Using the syntax /history/view?id= produces a page that renders a history for viewing. A user need not be logged in to view the history. --- lib/galaxy/web/controllers/history.py | 34 +++ static/june_2007_style/blue/history.css | 2 +- static/june_2007_style/history.css.tmpl | 2 +- static/scripts/autocomplete_tagging.js | 196 ++++++------ templates/base_panels.mako | 3 +- templates/history/sharing.mako | 9 +- templates/history/view.mako | 349 ++++++++++++++++++++++ templates/root/history.mako | 2 +- templates/root/history_common.mako | 32 +- templates/tagging_common.mako | 3 +- test/functional/test_history_functions.py | 4 +- 11 files changed, 521 insertions(+), 115 deletions(-) create mode 100644 templates/history/view.mako diff --git a/lib/galaxy/web/controllers/history.py b/lib/galaxy/web/controllers/history.py index 665d64c4ee5..afa966ccb81 100644 --- a/lib/galaxy/web/controllers/history.py +++ b/lib/galaxy/web/controllers/history.py @@ -371,6 +371,7 @@ class HistoryController( BaseController ): # Rename history.name = new_name trans.sa_session.flush() + @web.expose def imp( self, trans, id=None, confirm=False, **kwd ): """Import another user's history via a shared URL""" @@ -423,6 +424,38 @@ class HistoryController( BaseController ): Warning! If you import this history, you will lose your current history. Click here to confirm. """ % web.url_for( id=id, confirm=True ) ) + + @web.expose + def view( self, trans, id=None ): + """View a history. If a history is importable, then it is viewable by any user.""" + + # Get history to view. + if not id: + return trans.show_error_message( "You must specify a history you want to view." ) + history_to_view = get_history( trans, id, False) + + # Integrity checks. + if not history_to_view: + return trans.show_error_message( "The specified history does not exist.") + # TODO: Use a new flag to determine if history is viewable? + if not history_to_view.importable: + error( "The owner of this history has not published this history." ) + + # View history. + query = trans.sa_session.query( model.HistoryDatasetAssociation ) \ + .filter( model.HistoryDatasetAssociation.history == history_to_view ) \ + .options( eagerload( "children" ) ) \ + .join( "dataset" ).filter( model.Dataset.purged == False ) \ + .options( eagerload_all( "dataset.actions" ) ) + # Do not show deleted datasets. + query = query.filter( model.HistoryDatasetAssociation.deleted == False ) + user_owns_history = ( trans.get_user() == history_to_view.user ) + return trans.stream_template_mako( "history/view.mako", + history = history_to_view, + datasets = query.all(), + user_owns_history = user_owns_history, + show_deleted = False ) + @web.expose @web.require_login( "share histories with other users" ) def share( self, trans, id=None, email="", **kwd ): @@ -699,6 +732,7 @@ class HistoryController( BaseController ): @web.expose @web.require_login( "share histories with other users" ) def sharing( self, trans, histories=[], id=None, **kwd ): + """Performs sharing of histories among users.""" # histories looks like: [ historyX, historyY ] params = util.Params( kwd ) msg = util.restore_text ( params.get( 'msg', '' ) ) diff --git a/static/june_2007_style/blue/history.css b/static/june_2007_style/blue/history.css index 25ca777826f..c6eba07578e 100644 --- a/static/june_2007_style/blue/history.css +++ b/static/june_2007_style/blue/history.css @@ -1,7 +1,7 @@ body{background:#C1C9E5;color:#303030;background-image:url(menu_bg.png);background-repeat:repeat-x;background-position:top;margin:5px;border:0;padding:0;} a{color:#303030;} div.historyLinks{padding:5px;margin:5px 0 5px 0;} -div.historyItem{margin:5px -5px 5px 0px;padding:5px 11px 5px 5px;border:solid #888888 1px;border-left:solid #888888 5px;border-right:none;background:#EEEEEE;} +div.historyItem{margin:5px -5px 5px 0px;padding:5px 11px 5px 5px;border:solid #888888 1px;border-left:solid #888888 5px;border-right:solid #888888 1px;background:#EEEEEE;} div.historyItem .state-icon{display:inline-block;vertical-align:middle;width:16px;height:16px;background-position:0 1px;background-repeat:no-repeat;} div.historyItem .historyItemTitle{line-height:16px;} div.historyItem div.historyItem{margin-right:-11px;} diff --git a/static/june_2007_style/history.css.tmpl b/static/june_2007_style/history.css.tmpl index b30e7083636..2b4e95dc1f2 100644 --- a/static/june_2007_style/history.css.tmpl +++ b/static/june_2007_style/history.css.tmpl @@ -25,7 +25,7 @@ div.historyItem { padding: 5px 11px 5px 5px; border: solid $history_queued_border 1px; border-left: solid $history_queued_border 5px; - border-right: none; + border-right: solid $history_queued_border 1px; background: $history_queued_bg; .state-icon { display: inline-block; diff --git a/static/scripts/autocomplete_tagging.js b/static/scripts/autocomplete_tagging.js index 939b6cbfc75..93175f514d6 100644 --- a/static/scripts/autocomplete_tagging.js +++ b/static/scripts/autocomplete_tagging.js @@ -12,30 +12,31 @@ jQuery.fn.autocomplete_tagging = function(options) { // var defaults = { - get_toggle_link_text_fn: function(tags) - { - var text = ""; - var num_tags = array_length(tags); - if (num_tags != 0) - text = num_tags + (num_tags != 0 ? " Tags" : " Tag"); - else - // No tags. - text = "Add tags"; - return text; - }, - tag_click_fn : function (name, value) { }, - input_size: 20, - in_form: false, - tags : {}, - use_toggle_link: true, - item_id: "", - add_tag_img: "", - add_tag_img_rollover: "", - delete_tag_img: "", - ajax_autocomplete_tag_url: "", - ajax_retag_url: "", - ajax_delete_tag_url: "", - ajax_add_tag_url: "" + get_toggle_link_text_fn: function(tags) + { + var text = ""; + var num_tags = array_length(tags); + if (num_tags != 0) + text = num_tags + (num_tags != 0 ? " Tags" : " Tag"); + else + // No tags. + text = "Add tags"; + return text; + }, + tag_click_fn : function (name, value) { }, + editable: true, + input_size: 20, + in_form: false, + tags : {}, + use_toggle_link: true, + item_id: "", + add_tag_img: "", + add_tag_img_rollover: "", + delete_tag_img: "", + ajax_autocomplete_tag_url: "", + ajax_retag_url: "", + ajax_delete_tag_url: "", + ajax_add_tag_url: "" }; // @@ -206,32 +207,32 @@ jQuery.fn.autocomplete_tagging = function(options) { // Delete tag. $.ajax({ - url: settings.ajax_delete_tag_url, - data: { tag_name: tag_name }, - error: function() - { - // Failed. Roll back changes and show alert. - settings.tags[tag_name] = tag_value; - if (prev_button.hasClass("tag-button")) - prev_button.after(tag_button); - else - tag_area.prepend(tag_button); - var new_text = settings.get_toggle_link_text_fn(settings.tags); - alert( "Remove tag failed" ); + url: settings.ajax_delete_tag_url, + data: { tag_name: tag_name }, + error: function() + { + // Failed. Roll back changes and show alert. + settings.tags[tag_name] = tag_value; + if (prev_button.hasClass("tag-button")) + prev_button.after(tag_button); + else + tag_area.prepend(tag_button); + var new_text = settings.get_toggle_link_text_fn(settings.tags); + alert( "Remove tag failed" ); - toggle_link.text(new_text); + toggle_link.text(new_text); - // TODO: no idea why it's necessary to set this up again. - delete_img.mouseenter( function () - { - $(this).attr("src", settings.delete_tag_img_rollover); - }); - delete_img.mouseleave( function () - { - $(this).attr("src", settings.delete_tag_img); - }); - }, - success: function() {} + // TODO: no idea why it's necessary to set this up again. + delete_img.mouseenter( function () + { + $(this).attr("src", settings.delete_tag_img_rollover); + }); + delete_img.mouseleave( function () + { + $(this).attr("src", settings.delete_tag_img); + }); + }, + success: function() {} }); return true; @@ -248,7 +249,9 @@ jQuery.fn.autocomplete_tagging = function(options) { var tag_button = $("").addClass("tag-button"); tag_button.append(tag_name_elt); - tag_button.append(delete_img); + // Allow delete only if element is editable. + if (settings.editable) + tag_button.append(delete_img); return tag_button; }; @@ -411,55 +414,58 @@ jQuery.fn.autocomplete_tagging = function(options) { } }); - tag_area.append(add_tag_button); - tag_area.append(tag_input_field); - tag_input_field.hide(); - - // On click, enable user to add tags. - tag_area.click( function(e) + if (settings.editable) { - var is_active = $(this).hasClass("active-tag-area"); - - // If a "delete image" object was pressed and area is inactive, do nothing. - if ($(e.target).hasClass("delete-tag-img") && !is_active) - return false; - - // If a "tag name" object was pressed and area is inactive, do nothing. - if ($(e.target).hasClass("tag-name") && !is_active) - return false; - - // Hide add tag button, show tag_input field. Change background to show - // area is active. - $(this).addClass("active-tag-area"); - add_tag_button.hide(); - tag_input_field.show(); - tag_input_field.focus(); - - // Add handler to document that will call blur when the tag area is blurred; - // a tag area is blurred when a user clicks on an element outside the area. - var handle_document_click = function(e) + tag_area.append(add_tag_button); + tag_area.append(tag_input_field); + tag_input_field.hide(); + + // On click, enable user to add tags. + tag_area.click( function(e) { - var tag_area_id = tag_area.attr("id"); - // Blur the tag area if the element clicked on is not in the tag area. - if ( - ($(e.target).attr("id") != tag_area_id) && - ($(e.target).parents().filter(tag_area_id).length == 0) - ) - { - tag_area.blur(); - $(document).unbind("click", handle_document_click); - } - }; - // TODO: we should attach the click handler to all frames in order to capture - // clicks outside the frame that this element is in. - //window.parent.document.onclick = handle_document_click; - //var temp = $(window.parent.document.body).contents().find("iframe").html(); - //alert(temp); - //$(document).parent().click(handle_document_click); - $(window).click(handle_document_click); + var is_active = $(this).hasClass("active-tag-area"); + + // If a "delete image" object was pressed and area is inactive, do nothing. + if ($(e.target).hasClass("delete-tag-img") && !is_active) + return false; - return false; - }); + // If a "tag name" object was pressed and area is inactive, do nothing. + if ($(e.target).hasClass("tag-name") && !is_active) + return false; + + // Hide add tag button, show tag_input field. Change background to show + // area is active. + $(this).addClass("active-tag-area"); + add_tag_button.hide(); + tag_input_field.show(); + tag_input_field.focus(); + + // Add handler to document that will call blur when the tag area is blurred; + // a tag area is blurred when a user clicks on an element outside the area. + var handle_document_click = function(e) + { + var tag_area_id = tag_area.attr("id"); + // Blur the tag area if the element clicked on is not in the tag area. + if ( + ($(e.target).attr("id") != tag_area_id) && + ($(e.target).parents().filter(tag_area_id).length == 0) + ) + { + tag_area.blur(); + $(document).unbind("click", handle_document_click); + } + }; + // TODO: we should attach the click handler to all frames in order to capture + // clicks outside the frame that this element is in. + //window.parent.document.onclick = handle_document_click; + //var temp = $(window.parent.document.body).contents().find("iframe").html(); + //alert(temp); + //$(document).parent().click(handle_document_click); + $(window).click(handle_document_click); + + return false; + }); + } // If using toggle link, hide the tag area. Otherwise, if there are no tags, // hide the "add tags" button and show the input field. diff --git a/templates/base_panels.mako b/templates/base_panels.mako index 761fa9fd042..59096af2de9 100644 --- a/templates/base_panels.mako +++ b/templates/base_panels.mako @@ -44,13 +44,14 @@ + ${h.js( 'jquery' )} ## Default late-load javascripts <%def name="late_javascripts()"> ## Scripts can be loaded later since they progressively add features to ## the panels, but do not change layout - ${h.js( 'jquery', 'jquery.event.drag', 'jquery.event.hover', 'jquery.form', 'galaxy.base', 'galaxy.panels' )} + ${h.js( 'jquery.event.drag', 'jquery.event.hover', 'jquery.form', 'galaxy.base', 'galaxy.panels' )} + + + + + + + + + + %if history.deleted: +
+ ${_('You are currently viewing a deleted history!')} +
+

+ %endif + +
+
+ + <%namespace file="../tagging_common.mako" import="render_tagging_element" /> + + %if trans.get_user() is not None: +
+ ${render_tagging_element(history, "history-tag-area", get_toggle_link_text_fn='get_toggle_link_text', editable=user_owns_history)} + %endif + + %if not datasets: + +
+ + %else: + + ## Render requested datasets, ordered from newest to oldest + %for data in reversed( datasets ): + %if data.visible: +
+ ${render_dataset( data, data.hid, show_deleted_on_refresh = show_deleted, user_owns_dataset=user_owns_history )} +
+ %endif + %endfor + + +
+ + + + diff --git a/templates/root/history.mako b/templates/root/history.mako index 55b8d201fb1..98dea4840c6 100644 --- a/templates/root/history.mako +++ b/templates/root/history.mako @@ -321,7 +321,7 @@ var updater_callback = function ( tracked_datasets ) { %for data in reversed( datasets ): %if data.visible:
- ${render_dataset( data, data.hid, show_deleted_on_refresh = show_deleted )} + ${render_dataset( data, data.hid, show_deleted_on_refresh = show_deleted, user_owns_dataset = True )}
%endif %endfor diff --git a/templates/root/history_common.mako b/templates/root/history_common.mako index b565db43434..2fe3de0bfd1 100644 --- a/templates/root/history_common.mako +++ b/templates/root/history_common.mako @@ -1,6 +1,6 @@ <% _=n_ %> ## Render the dataset `data` as history item, using `hid` as the displayed id -<%def name="render_dataset( data, hid, show_deleted_on_refresh = False )"> +<%def name="render_dataset( data, hid, show_deleted_on_refresh = False, user_owns_dataset = True )"> <% if data.state in ['no state','',None]: data_state = "queued" @@ -19,21 +19,27 @@ This dataset has been deleted. Click here to undelete. %endif - + ## Header row for history items (name, state, action buttons)
%if data_state == "upload": - ## TODO: Make these CSS, just adding a "disabled" class to the normal - ## links should be enough. However the number of datasets being uploaded - ## at a time is usually small so the impact of these images is also small. - display data - edit attributes + ## TODO: Make these CSS, just adding a "disabled" class to the normal + ## links should be enough. However the number of datasets being uploaded + ## at a time is usually small so the impact of these images is also small. + display data + %if user_owns_dataset: + edit attributes + %endif %else: - - + + %if user_owns_dataset: + + %endif %endif - + %if user_owns_dataset: + + %endif
${hid}: ${data.display_name().decode('utf-8')} @@ -80,8 +86,10 @@
${_('Info: ')}${data.display_info()}
%if data.has_data: - save - | rerun + save + %if user_owns_dataset: + | rerun + %endif %for display_app in data.datatype.get_display_types(): <% target_frame, display_links = data.datatype.get_display_links( data, display_app, app, request.base ) %> %if len( display_links ) > 0: diff --git a/templates/tagging_common.mako b/templates/tagging_common.mako index c85c7ececbc..26bda0c8570 100644 --- a/templates/tagging_common.mako +++ b/templates/tagging_common.mako @@ -4,7 +4,7 @@ %endif ## Render the tags 'tags' as an autocomplete element. -<%def name="render_tagging_element(tagged_item, elt_id, use_toggle_link='true', in_form='false', input_size='15', tag_click_fn='default_tag_click_fn', get_toggle_link_text_fn='default_get_toggle_link_text_fn')"> +<%def name="render_tagging_element(tagged_item, elt_id, use_toggle_link='true', in_form='false', input_size='15', tag_click_fn='default_tag_click_fn', get_toggle_link_text_fn='default_get_toggle_link_text_fn', editable='true')">