X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/42fec323465b515e85a8bd460b2cee7972abd658..262d1e44ddaf3f014423a4acf96a3a6200279820:/apps/workbench/app/helpers/application_helper.rb
diff --git a/apps/workbench/app/helpers/application_helper.rb b/apps/workbench/app/helpers/application_helper.rb
index 11c55c3f77..21879a57a7 100644
--- a/apps/workbench/app/helpers/application_helper.rb
+++ b/apps/workbench/app/helpers/application_helper.rb
@@ -52,6 +52,35 @@ module ApplicationHelper
ArvadosBase::resource_class_for_uuid(attrvalue, opts)
end
+ # When using {remote:true}, or using {method:...} to use an HTTP
+ # method other than GET, move the target URI from href to
+ # data-remote-href. Otherwise, browsers offer features like "open in
+ # new window" and "copy link address" which bypass Rails' click
+ # handler and therefore end up at incorrect/nonexistent routes (by
+ # ignoring data-method) and expect to receive pages rather than
+ # javascript responses.
+ #
+ # See assets/javascripts/link_to_remote.js for supporting code.
+ def link_to *args, &block
+ if (args.last and args.last.is_a? Hash and
+ (args.last[:remote] or
+ (args.last[:method] and
+ args.last[:method].to_s.upcase != 'GET')))
+ if Rails.env.test?
+ # Capybara/phantomjs can't click_link without an href, even if
+ # the click handler means it never gets used.
+ raw super.gsub(' href="', ' href="#" data-remote-href="')
+ else
+ # Regular browsers work as desired: users can click A elements
+ # without hrefs, and click handlers fire; but there's no "copy
+ # link address" option in the right-click menu.
+ raw super.gsub(' href="', ' data-remote-href="')
+ end
+ else
+ super
+ end
+ end
+
##
# Returns HTML that links to the Arvados object specified in +attrvalue+
# Provides various output control and styling options.
@@ -97,7 +126,11 @@ module ApplicationHelper
else
begin
if resource_class.name == 'Collection'
- link_name = collections_for_object(link_uuid).andand.first.andand.friendly_link_name
+ if CollectionsHelper.match(link_uuid)
+ link_name = collection_for_pdh(link_uuid).andand.first.andand.portable_data_hash
+ else
+ link_name = collections_for_object(link_uuid).andand.first.andand.friendly_link_name
+ end
else
link_name = object_for_dataclass(resource_class, link_uuid).andand.friendly_link_name
end
@@ -133,10 +166,14 @@ module ApplicationHelper
end
end
style_opts[:class] = (style_opts[:class] || '') + ' nowrap'
- if opts[:no_link]
+ if opts[:no_link] or (resource_class == User && !current_user)
raw(link_name)
else
- (link_to raw(link_name), { controller: resource_class.to_s.tableize, action: 'show', id: ((opts[:name_link].andand.uuid) || link_uuid) }, style_opts) + raw(tags)
+ controller_class = resource_class.to_s.tableize
+ if controller_class.eql?('groups') and object.andand.group_class.eql?('project')
+ controller_class = 'projects'
+ end
+ (link_to raw(link_name), { controller: controller_class, action: 'show', id: ((opts[:name_link].andand.uuid) || link_uuid) }, style_opts) + raw(tags)
end
else
# just return attrvalue if it is not recognizable as an Arvados object or uuid.
@@ -148,7 +185,51 @@ module ApplicationHelper
end
end
- def render_editable_attribute(object, attr, attrvalue=nil, htmloptions={})
+ def link_to_arvados_object_if_readable(attrvalue, link_text_if_not_readable, opts={})
+ resource_class = resource_class_for_uuid(attrvalue.split('/')[0]) if attrvalue.is_a?(String)
+ if !resource_class
+ return link_to_if_arvados_object attrvalue, opts
+ end
+
+ readable = object_readable attrvalue, resource_class
+ if readable
+ link_to_if_arvados_object attrvalue, opts
+ elsif opts[:required] and current_user # no need to show this for anonymous user
+ raw('
')
+ else
+ link_text_if_not_readable
+ end
+ end
+
+ # This method takes advantage of preloaded collections and objects.
+ # Hence you can improve performance by first preloading objects
+ # related to the page context before using this method.
+ def object_readable attrvalue, resource_class=nil
+ # if it is a collection filename, check readable for the locator
+ attrvalue = attrvalue.split('/')[0] if attrvalue
+
+ resource_class = resource_class_for_uuid(attrvalue) if resource_class.nil?
+ return if resource_class.nil?
+
+ return_value = nil
+ if resource_class.to_s == 'Collection'
+ if CollectionsHelper.match(attrvalue)
+ found = collection_for_pdh(attrvalue)
+ return_value = found.first if found.any?
+ else
+ found = collections_for_object(attrvalue)
+ return_value = found.first if found.any?
+ end
+ else
+ return_value = object_for_dataclass(resource_class, attrvalue)
+ end
+ return_value
+ end
+
+ # Render an editable attribute with the attrvalue of the attr.
+ # The htmloptions are added to the editable element's list of attributes.
+ # The nonhtml_options are only used to customize the display of the element.
+ def render_editable_attribute(object, attr, attrvalue=nil, htmloptions={}, nonhtml_options={})
attrvalue = object.send(attr) if attrvalue.nil?
if not object.attribute_editable?(attr)
if attrvalue && attrvalue.length > 0
@@ -200,9 +281,16 @@ module ApplicationHelper
"id" => span_id,
:class => "editable #{is_textile?( object, attr ) ? 'editable-textile' : ''}"
}.merge(htmloptions).merge(ajax_options)
- edit_button = raw('')
- if htmloptions[:btnplacement] == :left
+
+ edit_tiptitle = 'edit'
+ edit_tiptitle = 'Warning: do not use hyphens in the repository name as they will be stripped' if (object.class.to_s == 'Repository' and attr == 'name')
+
+ edit_button = raw('' + (nonhtml_options[:btntext] || '') + '')
+
+ if nonhtml_options[:btnplacement] == :left
edit_button + ' ' + span_tag
+ elsif nonhtml_options[:btnplacement] == :top
+ edit_button + raw(' ') + span_tag
else
span_tag + ' ' + edit_button
end
@@ -240,7 +328,7 @@ module ApplicationHelper
end
if not object.andand.attribute_editable?(attr)
- return link_to_if_arvados_object attrvalue
+ return link_to_arvados_object_if_readable(attrvalue, attrvalue, {friendly_name: true, required: required})
end
if dataclass
@@ -292,10 +380,11 @@ module ApplicationHelper
success: 'page-refresh'
}.to_json,
})
+
return content_tag('div', :class => 'input-group') do
html = text_field_tag(dn, display_value,
:class =>
- "form-control #{'required' if required}")
+ "form-control #{'required' if required} #{'unreadable-input' if attrvalue.present? and !object_readable(attrvalue, Collection)}")
html + content_tag('span', :class => 'input-group-btn') do
link_to('Choose',
modal_path,
@@ -307,9 +396,7 @@ module ApplicationHelper
end
end
- if dataclass == 'number' or attrvalue.is_a? Fixnum or attrvalue.is_a? Float
- datatype = 'number'
- elsif attrvalue.is_a? String
+ if attrvalue.is_a? String
datatype = 'text'
elsif attrvalue.is_a?(Array) or dataclass.andand.is_a?(Class)
# TODO: find a way to edit with x-editable
@@ -335,6 +422,168 @@ module ApplicationHelper
lt
end
+ def get_cwl_inputs(workflow)
+ if workflow[:inputs]
+ return workflow[:inputs]
+ else
+ workflow[:"$graph"].each do |tool|
+ if tool[:id] == "#main"
+ return tool[:inputs]
+ end
+ end
+ end
+ end
+
+ def cwl_shortname(id)
+ if id[0] == "#"
+ id = id[1..-1]
+ end
+ return id.split("/")[-1]
+ end
+
+ def cwl_input_info(input_schema)
+ required = !(input_schema[:type].include? "null")
+ if input_schema[:type].is_a? Array
+ primary_type = input_schema[:type].select { |n| n != "null" }[0]
+ elsif input_schema[:type].is_a? String
+ primary_type = input_schema[:type]
+ elsif input_schema[:type].is_a? Hash
+ primary_type = input_schema[:type]
+ end
+ param_id = cwl_shortname(input_schema[:id])
+ return required, primary_type, param_id
+ end
+
+ def cwl_input_value(object, input_schema, set_attr_path)
+ dn = ""
+ attrvalue = object
+ set_attr_path.each do |a|
+ dn += "[#{a}]"
+ attrvalue = attrvalue[a.to_sym]
+ end
+ return dn, attrvalue
+ end
+
+ def cwl_inputs_required(object, inputs_schema, set_attr_path)
+ r = 0
+ inputs_schema.each do |input|
+ required, primary_type, param_id = cwl_input_info(input)
+ dn, attrvalue = cwl_input_value(object, input, set_attr_path + [param_id])
+ r += 1 if required and attrvalue.nil?
+ end
+ r
+ end
+
+ def render_cwl_input(object, input_schema, set_attr_path, htmloptions={})
+ required, primary_type, param_id = cwl_input_info(input_schema)
+
+ dn, attrvalue = cwl_input_value(object, input_schema, set_attr_path + [param_id])
+ attrvalue = if attrvalue.nil? then "" else attrvalue end
+
+ id = "#{object.uuid}-#{param_id}"
+
+ opt_empty_selection = if required then [] else [{value: "", text: ""}] end
+
+ if ["Directory", "File"].include? primary_type
+ chooser_title = "Choose a #{primary_type == 'Directory' ? 'dataset' : 'file'}:"
+ selection_param = object.class.to_s.underscore + dn
+ if attrvalue.is_a? Hash
+ display_value = attrvalue[:"arv:collection"] || attrvalue[:location]
+ re = CollectionsHelper.match_uuid_with_optional_filepath(display_value)
+ if re
+ if re[4]
+ display_value = "#{Collection.find(re[1]).name} / #{re[4][1..-1]}"
+ else
+ display_value = Collection.find(re[1]).name
+ end
+ end
+ end
+ modal_path = choose_collections_path \
+ ({ title: chooser_title,
+ filters: [['owner_uuid', '=', object.owner_uuid]].to_json,
+ action_name: 'OK',
+ action_href: container_request_path(id: object.uuid),
+ action_method: 'patch',
+ preconfigured_search_str: "",
+ action_data: {
+ merge: true,
+ use_preview_selection: primary_type == 'File' ? true : nil,
+ selection_param: selection_param,
+ success: 'page-refresh'
+ }.to_json,
+ })
+
+ return content_tag('div', :class => 'input-group') do
+ html = text_field_tag(dn, display_value,
+ :class =>
+ "form-control #{'required' if required}")
+ html + content_tag('span', :class => 'input-group-btn') do
+ link_to('Choose',
+ modal_path,
+ { :class => "btn btn-primary",
+ :remote => true,
+ :method => 'get',
+ })
+ end
+ end
+ elsif "boolean" == primary_type
+ return link_to attrvalue.to_s, '#', {
+ "data-emptytext" => "none",
+ "data-placement" => "bottom",
+ "data-type" => "select",
+ "data-source" => (opt_empty_selection + [{value: "true", text: "true"}, {value: "false", text: "false"}]).to_json,
+ "data-url" => url_for(action: "update", id: object.uuid, controller: object.class.to_s.pluralize.underscore, merge: true),
+ "data-title" => "Set value for #{cwl_shortname(input_schema[:id])}",
+ "data-name" => dn,
+ "data-pk" => "{id: \"#{object.uuid}\", key: \"#{object.class.to_s.underscore}\"}",
+ "data-value" => attrvalue.to_s,
+ # "clear" button interferes with form-control's up/down arrows
+ "data-clear" => false,
+ :class => "editable #{'required' if required} form-control",
+ :id => id
+ }.merge(htmloptions)
+ elsif primary_type.is_a? Hash and primary_type[:type] == "enum"
+ return link_to attrvalue, '#', {
+ "data-emptytext" => "none",
+ "data-placement" => "bottom",
+ "data-type" => "select",
+ "data-source" => (opt_empty_selection + primary_type[:symbols].map {|i| {:value => i, :text => i} }).to_json,
+ "data-url" => url_for(action: "update", id: object.uuid, controller: object.class.to_s.pluralize.underscore, merge: true),
+ "data-title" => "Set value for #{cwl_shortname(input_schema[:id])}",
+ "data-name" => dn,
+ "data-pk" => "{id: \"#{object.uuid}\", key: \"#{object.class.to_s.underscore}\"}",
+ "data-value" => attrvalue,
+ # "clear" button interferes with form-control's up/down arrows
+ "data-clear" => false,
+ :class => "editable #{'required' if required} form-control",
+ :id => id
+ }.merge(htmloptions)
+ elsif primary_type.is_a? String
+ if ["int", "long"].include? primary_type
+ datatype = "number"
+ else
+ datatype = "text"
+ end
+
+ return link_to attrvalue, '#', {
+ "data-emptytext" => "none",
+ "data-placement" => "bottom",
+ "data-type" => datatype,
+ "data-url" => url_for(action: "update", id: object.uuid, controller: object.class.to_s.pluralize.underscore, merge: true),
+ "data-title" => "Set value for #{cwl_shortname(input_schema[:id])}",
+ "data-name" => dn,
+ "data-pk" => "{id: \"#{object.uuid}\", key: \"#{object.class.to_s.underscore}\"}",
+ "data-value" => attrvalue,
+ # "clear" button interferes with form-control's up/down arrows
+ "data-clear" => false,
+ :class => "editable #{'required' if required} form-control",
+ :id => id
+ }.merge(htmloptions)
+ else
+ return "Unable to render editing control for parameter type #{primary_type}"
+ end
+ end
+
def render_arvados_object_list_start(list, button_text, button_href,
params={}, *rest, &block)
show_max = params.delete(:show_max) || 3
@@ -415,6 +664,10 @@ module ApplicationHelper
raw("#{date}")
end
+ def render_time duration, use_words, round_to_min=true
+ render_runtime duration, use_words, round_to_min
+ end
+
private
def is_textile?( object, attr )
is_textile = object.textile_attributes.andand.include?(attr)