From: Tom Clegg Date: Thu, 12 Jun 2014 03:57:37 +0000 (-0400) Subject: 2872: Merge branch 'master' into 2872-folder-nav X-Git-Tag: 1.1.0~2551^2~15 X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/a959f21c8147f26362df392bc3fd3290db69de85 2872: Merge branch 'master' into 2872-folder-nav Conflicts: apps/workbench/app/assets/javascripts/pipeline_instances.js apps/workbench/app/controllers/application_controller.rb apps/workbench/app/controllers/collections_controller.rb apps/workbench/app/controllers/jobs_controller.rb apps/workbench/app/helpers/application_helper.rb apps/workbench/app/models/job.rb apps/workbench/app/views/application/_content.html.erb apps/workbench/app/views/application/_show_metadata.html.erb apps/workbench/app/views/pipeline_instances/_show_components.html.erb --- a959f21c8147f26362df392bc3fd3290db69de85 diff --cc apps/workbench/app/assets/javascripts/pipeline_instances.js index 54595ab4f5,c61e336c7a..f206213ed2 --- a/apps/workbench/app/assets/javascripts/pipeline_instances.js +++ b/apps/workbench/app/assets/javascripts/pipeline_instances.js @@@ -47,51 -47,19 +47,42 @@@ $(document).on('ready ajax:complete', f run_pipeline_button_state(); }); - $(document).on('ajax:complete ready', function() { - var a = $('.arv-log-event-listener'); - if (a.length > 0) { - $('.arv-log-event-listener').each(function() { - subscribeToEventLog(this.id); - }); - } - }); - $(document).on('arv-log-event', '.arv-log-event-handler-append-logs', function(event, eventData){ - var parsedData = JSON.parse(eventData); + var wasatbottom = ($(this).scrollTop() + $(this).height() >= + this.scrollHeight); + var parsedData = JSON.parse(eventData); + var propertyText = undefined; + var properties = parsedData.properties; - var propertyText = undefined - - var properties = parsedData.properties; if (properties !== null) { - propertyText = properties.text; + propertyText = properties.text; } - if (propertyText !== undefined) { - $(this).append(propertyText + "
"); + $(this).append(propertyText + "
"); } else { - $(this).append(parsedData.summary + "
"); + $(this).append(parsedData.summary + "
"); } + if (wasatbottom) + this.scrollTop = this.scrollHeight; +}).on('ready ajax:complete', function(){ + $('.arv-log-event-handler-append-logs').each(function() { + this.scrollTop = this.scrollHeight; + }); }); + +var showhide_compare = function() { + var form = $('form#compare')[0]; + $('input[type=hidden][name="uuids[]"]', form).remove(); + $('input[type=submit]', form).prop('disabled',true).show(); + var checked_inputs = $('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]:checked'); + if (checked_inputs.length >= 2 && checked_inputs.length <= 3) { + checked_inputs.each(function(){ + if(this.checked) { + $('input[type=submit]', form).prop('disabled',false).show(); + $(form).append($('').val(this.value)); + } + }); + } +}; +$('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]').on('click', showhide_compare); +showhide_compare(); diff --cc apps/workbench/app/controllers/application_controller.rb index 48b508a4a6,a0cadb2b4c..6457cd0013 --- a/apps/workbench/app/controllers/application_controller.rb +++ b/apps/workbench/app/controllers/application_controller.rb @@@ -85,32 -100,10 +85,44 @@@ class ApplicationController < ActionCon end @objects ||= model_class - @objects = @objects.filter(@filters).limit(@limit).offset(@offset).all + @objects = @objects.filter(@filters).limit(@limit).offset(@offset) + end + ++ def render_index ++ respond_to do |f| ++ f.json { render json: @objects } ++ f.html { ++ if params['tab_pane'] ++ comparable = self.respond_to? :compare ++ render(partial: 'show_' + params['tab_pane'].downcase, ++ locals: { comparable: comparable, objects: @objects }) ++ else ++ render ++ end ++ } ++ f.js { render } ++ end ++ end ++ ++ def index ++ find_objects_for_index if !@objects + render_index + end + + helper_method :next_page_offset + def next_page_offset + if @objects.respond_to?(:result_offset) and + @objects.respond_to?(:result_limit) and + @objects.respond_to?(:items_available) + next_offset = @objects.result_offset + @objects.result_limit + if next_offset < @objects.items_available + next_offset + else + nil + end + end + end + - def index - find_objects_for_index if !@objects - respond_to do |f| - f.json { render json: @objects } - f.html { render } - f.js { render } - end - end - def show if !@object return render_not_found("object not found") @@@ -118,10 -111,16 +130,14 @@@ respond_to do |f| f.json { render json: @object.attributes.merge(href: url_for(@object)) } f.html { - if request.method.in? ['GET', 'HEAD'] + if params['tab_pane'] + comparable = self.respond_to? :compare + render(partial: 'show_' + params['tab_pane'].downcase, + locals: { comparable: comparable, objects: @objects }) ++ elsif request.method.in? ['GET', 'HEAD'] + render else - if request.method == 'GET' - render - else - redirect_to params[:return_to] || @object - end + redirect_to params[:return_to] || @object end } f.js { render } @@@ -186,29 -163,20 +202,38 @@@ @new_resource_attrs ||= params[model_class.to_s.underscore.singularize] @new_resource_attrs ||= {} @new_resource_attrs.reject! { |k,v| k.to_s == 'uuid' } - @object ||= model_class.new @new_resource_attrs - @object.save! - show + @object ||= model_class.new @new_resource_attrs, params["options"] + if @object.save + respond_to do |f| + f.json { render json: @object.attributes.merge(href: url_for(@object)) } + f.html { + redirect_to @object + } + f.js { render } + end + else + self.render_error status: 422 + end end + # Clone the given object, merging any attribute values supplied as + # with a create action. + def copy + @new_resource_attrs ||= params[model_class.to_s.underscore.singularize] + @new_resource_attrs ||= {} + @object = @object.dup + @object.update_attributes @new_resource_attrs + if not @new_resource_attrs[:name] and @object.respond_to? :name + if @object.name and @object.name != '' + @object.name = "Copy of #{@object.name}" + else + @object.name = "Copy of unnamed #{@object.class_for_display.downcase}" + end + end + @object.save! + show + end + def destroy if @object.destroy respond_to do |f| @@@ -325,7 -297,17 +360,17 @@@ # call to verify its authenticity. if verify_api_token session[:arvados_api_token] = params[:api_token] + u = User.current + session[:user] = { + uuid: u.uuid, + email: u.email, + first_name: u.first_name, + last_name: u.last_name, + is_active: u.is_active, + is_admin: u.is_admin, + prefs: u.prefs + } - if !request.format.json? and request.method == 'GET' + if !request.format.json? and request.method.in? ['GET', 'HEAD'] # Repeat this request with api_token in the (new) session # cookie instead of the query string. This prevents API # tokens from appearing in (and being inadvisedly copied @@@ -532,60 -498,171 +579,228 @@@ end end + helper_method :projects_shared_with_me + def projects_shared_with_me + my_project_uuids = my_projects.collect &:uuid + all_projects.reject { |x| x.uuid.in? my_project_uuids } + end + + helper_method :recent_jobs_and_pipelines + def recent_jobs_and_pipelines + in_my_projects = ['owner_uuid','in',my_projects.collect(&:uuid)] + (Job.limit(10).filter([in_my_projects]) | + PipelineInstance.limit(10).filter([in_my_projects])). + sort_by do |x| + x.finished_at || x.started_at || x.created_at rescue x.created_at + end + end + + helper_method :get_object + def get_object uuid + if @get_object.nil? and @objects + @get_object = @objects.each_with_object({}) do |object, h| + h[object.uuid] = object + end + end + @get_object ||= {} + @get_object[uuid] + end + + helper_method :project_breadcrumbs + def project_breadcrumbs + crumbs = [] + current = @name_link || @object + while current + if current.is_a?(Group) and current.group_class.in?(['project','folder']) + crumbs.prepend current + end + if current.is_a? Link + current = Group.find?(current.tail_uuid) + else + current = Group.find?(current.owner_uuid) + end + end + crumbs + end + + helper_method :current_project_uuid + def current_project_uuid + if @object.is_a? Group and @object.group_class.in?(['project','folder']) + @object.uuid + elsif @name_link.andand.tail_uuid + @name_link.tail_uuid + elsif @object and resource_class_for_uuid(@object.owner_uuid) == Group + @object.owner_uuid + else + nil + end + end ++ + # helper method to get links for given object or uuid + helper_method :links_for_object + def links_for_object object_or_uuid + raise ArgumentError, 'No input argument' unless object_or_uuid + preload_links_for_objects([object_or_uuid]) + uuid = object_or_uuid.is_a?(String) ? object_or_uuid : object_or_uuid.uuid + @all_links_for[uuid] ||= [] + end + + # helper method to preload links for given objects and uuids + helper_method :preload_links_for_objects + def preload_links_for_objects objects_and_uuids + @all_links_for ||= {} + + raise ArgumentError, 'Argument is not an array' unless objects_and_uuids.is_a? Array + return @all_links_for if objects_and_uuids.empty? + + uuids = objects_and_uuids.collect { |x| x.is_a?(String) ? x : x.uuid } + + # if already preloaded for all of these uuids, return + if not uuids.select { |x| @all_links_for[x].nil? }.any? + return @all_links_for + end + + uuids.each do |x| + @all_links_for[x] = [] + end + + # TODO: make sure we get every page of results from API server + Link.filter([['head_uuid', 'in', uuids]]).each do |link| + @all_links_for[link.head_uuid] << link + end + @all_links_for + end + + # helper method to get a certain number of objects of a specific type + # this can be used to replace any uses of: "dataclass.limit(n)" + helper_method :get_n_objects_of_class + def get_n_objects_of_class dataclass, size + @objects_map_for ||= {} + + raise ArgumentError, 'Argument is not a data class' unless dataclass.is_a? Class + raise ArgumentError, 'Argument is not a valid limit size' unless (size && size>0) + + # if the objects_map_for has a value for this dataclass, and the + # size used to retrieve those objects is equal, return it + size_key = "#{dataclass.name}_size" + if @objects_map_for[dataclass.name] && @objects_map_for[size_key] && + (@objects_map_for[size_key] == size) + return @objects_map_for[dataclass.name] + end + + @objects_map_for[size_key] = size + @objects_map_for[dataclass.name] = dataclass.limit(size) + end + + # helper method to get collections for the given uuid + helper_method :collections_for_object + def collections_for_object uuid + raise ArgumentError, 'No input argument' unless uuid + preload_collections_for_objects([uuid]) + @all_collections_for[uuid] ||= [] + end + + # helper method to preload collections for the given uuids + helper_method :preload_collections_for_objects + def preload_collections_for_objects uuids + @all_collections_for ||= {} + + raise ArgumentError, 'Argument is not an array' unless uuids.is_a? Array + return @all_collections_for if uuids.empty? + + # if already preloaded for all of these uuids, return + if not uuids.select { |x| @all_collections_for[x].nil? }.any? + return @all_collections_for + end + + uuids.each do |x| + @all_collections_for[x] = [] + end + + # TODO: make sure we get every page of results from API server + Collection.where(uuid: uuids).each do |collection| + @all_collections_for[collection.uuid] << collection + end + @all_collections_for + end + + # helper method to get log collections for the given log + helper_method :log_collections_for_object + def log_collections_for_object log + raise ArgumentError, 'No input argument' unless log + + preload_log_collections_for_objects([log]) + + uuid = log + fixup = /([a-f0-9]{32}\+\d+)(\+?.*)/.match(log) + if fixup && fixup.size>1 + uuid = fixup[1] + end + + @all_log_collections_for[uuid] ||= [] + end + + # helper method to preload collections for the given uuids + helper_method :preload_log_collections_for_objects + def preload_log_collections_for_objects logs + @all_log_collections_for ||= {} + + raise ArgumentError, 'Argument is not an array' unless logs.is_a? Array + return @all_log_collections_for if logs.empty? + + uuids = [] + logs.each do |log| + fixup = /([a-f0-9]{32}\+\d+)(\+?.*)/.match(log) + if fixup && fixup.size>1 + uuids << fixup[1] + else + uuids << log + end + end + + # if already preloaded for all of these uuids, return + if not uuids.select { |x| @all_log_collections_for[x].nil? }.any? + return @all_log_collections_for + end + + uuids.each do |x| + @all_log_collections_for[x] = [] + end + + # TODO: make sure we get every page of results from API server + Collection.where(uuid: uuids).each do |collection| + @all_log_collections_for[collection.uuid] << collection + end + @all_log_collections_for + end + + # helper method to get object of a given dataclass and uuid + helper_method :object_for_dataclass + def object_for_dataclass dataclass, uuid + raise ArgumentError, 'No input argument dataclass' unless (dataclass && uuid) + preload_objects_for_dataclass(dataclass, [uuid]) + @objects_for[uuid] + end + + # helper method to preload objects for given dataclass and uuids + helper_method :preload_objects_for_dataclass + def preload_objects_for_dataclass dataclass, uuids + @objects_for ||= {} + + raise ArgumentError, 'Argument is not a data class' unless dataclass.is_a? Class + raise ArgumentError, 'Argument is not an array' unless uuids.is_a? Array + + return @objects_for if uuids.empty? + + # if already preloaded for all of these uuids, return + if not uuids.select { |x| @objects_for[x].nil? }.any? + return @objects_for + end + + dataclass.where(uuid: uuids).each do |obj| + @objects_for[obj.uuid] = obj + end + @objects_for + end + end diff --cc apps/workbench/app/controllers/collections_controller.rb index 0148b72d26,6a5df8754f..88dadbba62 --- a/apps/workbench/app/controllers/collections_controller.rb +++ b/apps/workbench/app/controllers/collections_controller.rb @@@ -157,10 -143,10 +159,10 @@@ class CollectionsController < Applicati end @output_of = jobs_with.call(output: @object.uuid) @log_of = jobs_with.call(log: @object.uuid) - project_links = Link.limit(RELATION_LIMIT).order("modified_at DESC") - @folder_links = Link.limit(RELATION_LIMIT).order("modified_at DESC") ++ @project_links = Link.limit(RELATION_LIMIT).order("modified_at DESC") .where(head_uuid: @object.uuid, link_class: 'name').results - project_hash = Group.where(uuid: project_links.map(&:tail_uuid)).to_hash - @projects = project_links.map { |link| project_hash[link.tail_uuid] } - folder_hash = Group.where(uuid: @folder_links.map(&:tail_uuid)).to_hash - @folders = @folder_links.map { |link| folder_hash[link.tail_uuid] } ++ project_hash = Group.where(uuid: @project_links.map(&:tail_uuid)).to_hash ++ @projects = @project_links.map { |link| project_hash[link.tail_uuid] } @permissions = Link.limit(RELATION_LIMIT).order("modified_at DESC") .where(head_uuid: @object.uuid, link_class: 'permission', name: 'can_read').results diff --cc apps/workbench/app/controllers/jobs_controller.rb index 8743a6ffff,b7526c949a..ff3ac6b98e --- a/apps/workbench/app/controllers/jobs_controller.rb +++ b/apps/workbench/app/controllers/jobs_controller.rb @@@ -44,6 -53,6 +53,6 @@@ class JobsController < ApplicationContr end def show_pane_list - %w(Details Provenance Advanced) - %w(Status Attributes Provenance Metadata JSON API) ++ %w(Status Details Provenance Advanced) end end diff --cc apps/workbench/app/controllers/pipeline_instances_controller.rb index ccbd06f85d,500927bdb6..a4a9d69bef --- a/apps/workbench/app/controllers/pipeline_instances_controller.rb +++ b/apps/workbench/app/controllers/pipeline_instances_controller.rb @@@ -3,44 -3,10 +3,46 @@@ class PipelineInstancesController < App before_filter :find_objects_by_uuid, only: :compare include PipelineInstancesHelper + def copy + @object = @object.dup + @object.components.each do |cname, component| + component.delete :job + end + @object.state = 'New' + super + end + + def update + @updates ||= params[@object.class.to_s.underscore.singularize.to_sym] + if (components = @updates[:components]) + components.each do |cname, component| + if component[:script_parameters] + component[:script_parameters].each do |param, value_info| + if value_info.is_a? Hash + if resource_class_for_uuid(value_info[:value]) == Link + # Use the link target, not the link itself, as script + # parameter; but keep the link info around as well. + link = Link.find value_info[:value] + value_info[:value] = link.head_uuid + value_info[:link_uuid] = link.uuid + value_info[:link_name] = link.name + else + # Delete stale link_uuid and link_name data. + value_info[:link_uuid] = nil + value_info[:link_name] = nil + end + end + end + end + end + end + super + end + def graph(pipelines) - count = {} + return nil, nil if params['tab_pane'] != "Graph" + + count = {} provenance = {} pips = {} n = 1 diff --cc apps/workbench/app/helpers/application_helper.rb index 784958b8b0,2b7ec147a4..66267e028d --- a/apps/workbench/app/helpers/application_helper.rb +++ b/apps/workbench/app/helpers/application_helper.rb @@@ -294,12 -236,37 +300,28 @@@ module ApplicationHelpe datatype = 'text' end - id = "#{object.uuid}-#{subattr.join('-')}" - dn = "[#{attr}]" - subattr.each do |a| - dn += "[#{a}]" - end - if value_info.is_a? Hash - dn += '[value]' - end - + # preload data + preload_uuids = [] + items = [] selectables = [] + attrtext = attrvalue if dataclass and dataclass.is_a? Class + objects = get_n_objects_of_class dataclass, 10 + objects.each do |item| + items << item + preload_uuids << item.uuid + end if attrvalue and !attrvalue.empty? - Link.where(head_uuid: attrvalue, link_class: ["tag", "identifier"]).each do |tag| - attrtext += " [#{tag.name}]" + preload_uuids << attrvalue + end + preload_links_for_objects preload_uuids + + if attrvalue and !attrvalue.empty? + links_for_object(attrvalue).each do |link| + if link.link_class.in? ["tag", "identifier"] + attrtext += " [#{link.name}]" + end end selectables.append({name: attrtext, uuid: attrvalue, type: dataclass.to_s}) end diff --cc apps/workbench/app/models/job.rb index 2a69c284d8,173d3a0696..aac6168d22 --- a/apps/workbench/app/models/job.rb +++ b/apps/workbench/app/models/job.rb @@@ -15,18 -11,7 +15,22 @@@ class Job < ArvadosBas false end + def default_name + if script + x = "\"#{script}\" job" + else + x = super + end + if finished_at + x += " finished #{finished_at.strftime('%b %-d')}" + elsif started_at + x += " started #{started_at.strftime('%b %-d')}" + elsif created_at + x += " submitted #{created_at.strftime('%b %-d')}" + end + end ++ + def cancel + arvados_api_client.api "jobs/#{self.uuid}/", "cancel", {} + end end diff --cc apps/workbench/app/views/application/_content.html.erb index e6343628f2,353bd74143..b7f27df3d7 --- a/apps/workbench/app/views/application/_content.html.erb +++ b/apps/workbench/app/views/application/_content.html.erb @@@ -1,24 -1,34 +1,55 @@@ +<% content_for :content_top do %> + <% if @object and not @object.is_a?(Group) and @object.class.goes_in_projects? and @object.owner_uuid == current_user.uuid %> +
+
+ + Hey. This <%= @object.class_for_display.downcase %> belongs to your account, but it's not in any of your projects. If you want it to be easy to find in the future, you should move it to a project.
+ <%= button_to(choose_projects_path( + title: 'Move to...', + editable: true, + action_name: 'Move', + action_href: url_for(action: :update), + action_method: 'patch', + action_data: {selection_param: @object.resource_param_name+'[owner_uuid]', success: 'page-refresh'}.to_json), + { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %> + Choose a project... + <% end %> +
+
+ <% end %> +<% end %> + + <% content_for :js do %> + tab_pane_valid_state = {}; + + function ajaxRefreshTabPane(pane) { + if (!tab_pane_valid_state[pane]) { + tab_pane_valid_state[pane] = true; + $(document).trigger('ajax:send'); + $.ajax('<%=j url_for @object %>?tab_pane='+pane, {dataType: 'html', type: 'GET'}). + done(function(data, status, jqxhr) { + $('#' + pane + ' > div > div').html(data); + $(document).trigger('ajax:complete'); + ajaxRefreshTabPane(pane); + }); + } + } + + $(window).on('load', smart_scroll_fixup); + $(document).on('shown.bs.tab', 'ul.nav-tabs > li > a', smart_scroll_fixup); + + $(document).on('shown.bs.tab', function(e) { + ajaxRefreshTabPane(e.target.id.slice(0, -4)); + }); + + $(document).on('arv-log-event', function() { + <% pane_list.each do |pane| %> + tab_pane_valid_state['<%=j pane %>'] = false; + <% end %> + ajaxRefreshTabPane($('.tab-pane.active')[0].id); + }); + <% end %> + <% content_for :tab_panes do %> <% comparable = controller.respond_to? :compare %> @@@ -34,10 -39,36 +60,36 @@@ <% end %>
- <% panes.each_with_index do |(pane, content), i| %> -
+ <% pane_list.each_with_index do |pane, i| %> +
+ data-object-kind="arvados#<%= ArvadosApiClient.class_kind controller.model_class %>" + <% else %> + data-object-uuid="<%= @object.uuid %>" + <% end %> + > + + <% content_for :js do %> + <% if i == 0 %> + tab_pane_valid_state['<%=j pane %>'] = true; + <% else %> + tab_pane_valid_state['<%=j pane %>'] = false; + $(document).on('ready', function() { + ajaxRefreshTabPane('<%=j pane %>'); + }); + <% end %> + <% end %> + -
+
- <%= content %> +
+ <% if i == 0 %> + <%= render(partial: 'show_' + pane.downcase, + locals: { comparable: comparable, objects: @objects }) %> + <% else %> + <%= image_tag 'ajax-loader.gif' %> + <% end %> +
<% end %> diff --cc apps/workbench/app/views/application/_show_advanced_metadata.html.erb index 68e4298cc9,0000000000..c036b362de mode 100644,000000..100644 --- a/apps/workbench/app/views/application/_show_advanced_metadata.html.erb +++ b/apps/workbench/app/views/application/_show_advanced_metadata.html.erb @@@ -1,44 -1,0 +1,56 @@@ +<% outgoing = Link.where(tail_uuid: @object.uuid) %> +<% incoming = Link.where(head_uuid: @object.uuid) %> + ++<% ++ preload_uuids = [] ++ preload_head_uuids = [] ++ outgoing.results.each do |link| ++ preload_uuids << link.uuid ++ preload_uuids << link.head_uuid ++ preload_head_uuids << link.head_uuid ++ end ++ preload_collections_for_objects preload_uuids ++ preload_links_for_objects preload_head_uuids ++%> ++ +<% if (outgoing | incoming).any? %> + + + + + + + + + + + + + + + + + + + + + <% (outgoing | incoming).each do |link| %> + + + + + + + + + <% end %> + +
link_classnametailheadproperties
+ <%= render partial: 'show_object_button', locals: { object: link, size: 'xs' } %> + <%= link.uuid %> + <%= link.link_class %><%= link.name %><%= link.tail_uuid == object.uuid ? 'this' : (render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "tail_uuid", attrvalue: link.tail_uuid, editable: false }) %><%= link.head_uuid == object.uuid ? 'this' : (render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "head_uuid", attrvalue: link.head_uuid, editable: false }) %><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "properties", attrvalue: link.properties, editable: false } %>
+<% else %> + + (No metadata links found) + +<% end %>