@all_log_collections_for
end
+ # Helper method to get one collection for the given portable_data_hash
+ # This is used to determine if a pdh is readable by the current_user
+ helper_method :collection_for_pdh
+ def collection_for_pdh pdh
+ raise ArgumentError, 'No input argument' unless pdh
+ preload_for_pdhs([pdh])
+ @all_pdhs_for[pdh] ||= []
+ end
+
+ # Helper method to preload one collection each for the given pdhs
+ # This is used to determine if a pdh is readable by the current_user
+ helper_method :preload_for_pdhs
+ def preload_for_pdhs pdhs
+ @all_pdhs_for ||= {}
+
+ raise ArgumentError, 'Argument is not an array' unless pdhs.is_a? Array
+ return @all_pdhs_for if pdhs.empty?
+
+ # if already preloaded for all of these pdhs, return
+ if not pdhs.select { |x| @all_pdhs_for[x].nil? }.any?
+ return @all_pdhs_for
+ end
+
+ pdhs.each do |x|
+ @all_pdhs_for[x] = []
+ end
+
+ Collection.select(%w(portable_data_hash)).where(portable_data_hash: pdhs).distinct().each do |collection|
+ @all_pdhs_for[collection.portable_data_hash] << collection
+ end
+ @all_pdhs_for
+ end
+
# helper method to get object of a given dataclass and uuid
helper_method :object_for_dataclass
def object_for_dataclass dataclass, uuid
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?
+ if not uuids.select { |x| !@objects_for.include?(x) }.any?
return @objects_for
end
+ # preset all uuids to nil
+ uuids.each do |x|
+ @objects_for[x] = nil
+ end
dataclass.where(uuid: uuids).each do |obj|
@objects_for[obj.uuid] = obj
end
end
end
+ def link_to_arvados_object_if_readable(attrvalue, link_text_if_not_readable, opts={})
+ resource_class = resource_class_for_uuid(attrvalue)
+ if !resource_class
+ return link_to_if_arvados_object attrvalue, opts
+ end
+
+ if resource_class.to_s == 'Collection'
+ if CollectionsHelper.match(attrvalue)
+ readable = collection_for_pdh(attrvalue).any?
+ else
+ readable = collections_for_object(attrvalue).any?
+ end
+ else
+ readable = object_for_dataclass(resource_class, attrvalue)
+ end
+
+ if readable
+ link_to_if_arvados_object attrvalue, opts
+ else
+ link_text_if_not_readable
+ end
+ end
+
def render_editable_attribute(object, attr, attrvalue=nil, htmloptions={})
attrvalue = object.send(attr) if attrvalue.nil?
if not object.attribute_editable?(attr)
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)
end
if dataclass
module JobsHelper
- def stderr_log_history(job_uuids)
+ def stderr_log_history(job_uuids, limit=2000)
results = []
log_history = Log.where(event_type: 'stderr',
- object_uuid: job_uuids).limit(2000).order('id DESC')
+ object_uuid: job_uuids).limit(limit).order('id DESC')
if !log_history.results.empty?
reversed_results = log_history.results.reverse
reversed_results.each do |entry|
ArvadosResourceList.new(self).select(*args)
end
+ def self.distinct(*args)
+ ArvadosResourceList.new(self).distinct(*args)
+ end
+
def self.eager(*args)
ArvadosResourceList.new(self).eager(*args)
end
self
end
+ def distinct(bool=true)
+ @distinct = bool
+ self
+ end
+
def limit(max_results)
if not max_results.nil? and not max_results.is_a? Integer
raise ArgumentError("argument to limit() must be an Integer or nil")
api_params[:select] = @select if @select
api_params[:order] = @orderby_spec if @orderby_spec
api_params[:filters] = @filters if @filters
+ api_params[:distinct] = @distinct if @distinct
item_count = 0
<ul class="nav nav-tabs" data-tab-counts-url="<%= url_for(action: :tab_counts) rescue '' %>">
<% pane_list.each_with_index do |pane, i| %>
<% pane_name = (pane.is_a?(Hash) ? pane[:name] : pane) %>
- <li class="<%= 'active' if i==0 %>">
+
+ <% data_toggle = "tab" %>
+ <% tab_tooltip = "" %>
+ <% link_disabled = "" %>
+
+ <% if (pane_name == "Log") and !(ArvadosBase.find(@object.owner_uuid).writable_by.include?(current_user.andand.uuid) rescue nil)
+ if controller.model_class.to_s == 'Job'
+ if @object.log and !@object.log.empty?
+ logCollection = Collection.find? @object.log
+ if !logCollection
+ data_toggle = "disabled"
+ tab_tooltip = "Log data is not available"
+ link_disabled = "disabled"
+ end
+ end
+ elsif controller.model_class.to_s == 'PipelineInstance'
+ log_uuids = [@object.uuid] + pipeline_jobs(@object).collect{|x|x[:job].andand[:uuid]}.compact
+ if stderr_log_history(log_uuids, 1).empty?
+ data_toggle = "disabled"
+ tab_tooltip = "Log data is not available"
+ link_disabled = "disabled"
+ end
+ end
+ end
+ %>
+
+ <li class="<%= 'active' if i==0 %> <%= link_disabled %>" data-toggle="tooltip" data-placement="top" title="<%=tab_tooltip%>">
<a href="#<%= pane_name %>"
id="<%= pane_name %>-tab"
- data-toggle="tab"
+ data-toggle="<%= data_toggle %>"
data-tab-history=true
data-tab-history-update-url=true
>
<%# column offset 8 %>
<div class="col-md-4 text-overflow-ellipsis">
<% if pj[:output_uuid] %>
- <%= link_to_if_arvados_object pj[:output_uuid], friendly_name: true %>
+ <%= link_to_arvados_object_if_readable(pj[:output_uuid], 'Output data not available', friendly_name: true) %>
<% elsif current_job[:output] %>
- <%= link_to_if_arvados_object current_job[:output], link_text: "Output of #{pj[:name]}" %>
+ <%= link_to_arvados_object_if_readable(current_job[:output], 'Output data not available', link_text: "Output of #{pj[:name]}") %>
<% else %>
No output.
<% end %>
docker_image_locator:
</td>
<td>
- <%= link_to_if_arvados_object current_component[:docker_image_locator], friendly_name: true %>
+ <%= link_to_arvados_object_if_readable(current_component[:docker_image_locator],
+ current_component[:docker_image_locator], friendly_name: true) %>
</td>
</tr>
<% else %>
</td>
<td>
<% if k == :uuid %>
- <%= link_to_if_arvados_object current_component[k], link_text: current_component[k] %>
+ <%= link_to_arvados_object_if_readable(current_component[k], current_component[k], link_text: current_component[k]) %>
<% elsif k.to_s.end_with? 'uuid' %>
- <%= link_to_if_arvados_object current_component[k], friendly_name: true %>
+ <%= link_to_arvados_object_if_readable(current_component[k], current_component[k], friendly_name: true) %>
<% elsif k.to_s.end_with? '_at' %>
<%= render_localized_date(current_component[k]) %>
<% else %>
+<%
+ input_uuids = []
+ input_pdhs = []
+ @object.components.each do |k, component|
+ next if !component
+ component[:script_parameters].andand.each do |p, tv|
+ if tv.is_a? Hash and !tv[:value].nil? and (tv[:dataclass] == "Collection")
+ if CollectionsHelper.match(tv[:value])
+ input_pdhs << tv[:value]
+ else
+ input_uuids << tv[:value]
+ end
+ end
+ end
+ end
+
+ preload_collections_for_objects input_uuids if input_uuids.any?
+ preload_for_pdhs input_pdhs if input_pdhs.any?
+%>
+
<table class="table pipeline-components-table" style="margin-top: -.1em">
<colgroup>
<col style="width: 20%" />
<%# Components %>
+<%
+ job_uuids = pipeline_jobs.collect {|j| j[:job][:uuid]}.compact
+ if job_uuids.any?
+ resource_class = resource_class_for_uuid(job_uuids.first, friendly_name: true)
+ preload_objects_for_dataclass resource_class, job_uuids
+ end
+
+ job_collections = pipeline_jobs.collect {|j| j[:job][:output]}.compact
+ job_collections.concat pipeline_jobs.collect {|j| j[:job][:docker_image_locator]}.uniq.compact
+ job_collections_pdhs = job_collections.select {|x| !(m = CollectionsHelper.match(x)).nil?}.uniq.compact
+ job_collections_uuids = job_collections - job_collections_pdhs
+ preload_collections_for_objects job_collections_uuids if job_collections_uuids.any?
+ preload_for_pdhs job_collections_pdhs if job_collections_pdhs.any?
+%>
+
<% pipeline_jobs.each_with_index do |pj, i| %>
<%= render partial: 'running_component', locals: {tasks: tasks, pj: pj, i: i, expanded: false} %>
<% end %>
[:preload_collections_for_objects, [] ],
[:preload_log_collections_for_objects, [] ],
[:preload_objects_for_dataclass, [] ],
+ [:preload_for_pdhs, [] ],
].each do |input|
test "preload data for empty array input #{input}" do
use_token :active
[:preload_objects_for_dataclass, nil],
[:object_for_dataclass, 'some_dataclass', nil],
[:object_for_dataclass, nil, 'some_uuid'],
+ [:preload_for_pdhs, 'input not an array'],
+ [:preload_for_pdhs, nil],
].each do |input|
test "preload data for wrong type input #{input}" do
use_token :active
[:collections_for_object, 'no-such-uuid' ],
[:log_collections_for_object, 'no-such-uuid' ],
[:object_for_dataclass, 'no-such-uuid' ],
+ [:collection_for_pdh, 'no-such-pdh' ],
].each do |input|
test "get data for no such uuid #{input}" do
use_token :active
objects = ac.send input[0], input[1]
assert objects, 'Expected objects'
assert objects.is_a?(Array), 'Expected a array'
+ assert_empty objects
end
end
end
assert users.size == 3, 'Expected two objects in the preloaded hash'
end
+ test "preload one collection each for given portable_data_hash list" do
+ use_token :active
+
+ ac = ApplicationController.new
+
+ pdh1 = api_fixture('collections')['foo_file']['portable_data_hash']
+ pdh2 = api_fixture('collections')['bar_file']['portable_data_hash']
+
+ pdhs = [pdh1, pdh2]
+ collections = ac.send :preload_for_pdhs, pdhs
+
+ assert collections, 'Expected collections map'
+ assert collections.is_a?(Hash), 'Expected a hash'
+ # Each pdh has more than one collection; however, we should get only one for each
+ assert collections.size == 2, 'Expected two objects in the preloaded collection hash'
+ assert collections[pdh1], 'Expected collections for the passed in pdh #{pdh1}'
+ assert_equal collections[pdh1].size, 1, 'Expected one collection for the passed in pdh #{pdh1}'
+ assert collections[pdh2], 'Expected collections for the passed in pdh #{pdh2}'
+ assert_equal collections[pdh2].size, 1, 'Expected one collection for the passed in pdh #{pdh2}'
+ end
+
test "requesting a nonexistent object returns 404" do
# We're really testing ApplicationController's find_object_by_uuid.
# It's easiest to do that by instantiating a concrete controller.
assert_no_selector 'a', text: 'Run this pipeline'
end
+ [
+ ['pipeline_in_publicly_accessible_project', true],
+ ['pipeline_in_publicly_accessible_project_but_other_objects_elsewhere', false],
+ ['pipeline_in_publicly_accessible_project_but_other_objects_elsewhere', false, 'spectator'],
+ ['pipeline_in_publicly_accessible_project_but_other_objects_elsewhere', true, 'admin'],
+
+ ['completed_job_in_publicly_accessible_project', true],
+ ['job_in_publicly_accessible_project_but_other_objects_elsewhere', false],
+ ].each do |fixture, objects_readable, user=nil|
+ test "access #{fixture} in public project with objects readable=#{objects_readable} with user #{user}" do
+ pipeline_page = true if fixture.include?('pipeline')
+
+ if pipeline_page
+ object = api_fixture('pipeline_instances')[fixture]
+ page = "/pipeline_instances/#{object['uuid']}"
+ else # job
+ object = api_fixture('jobs')[fixture]
+ page = "/jobs/#{object['uuid']}"
+ end
+
+ if user
+ visit page_with_token user, page
+ else
+ visit page
+ end
+
+ # click job link, if in pipeline page
+ click_link 'foo' if pipeline_page
+
+ if objects_readable
+ if pipeline_page
+ assert_text 'This pipeline was created from'
+ assert_selector 'a', text: object['components']['foo']['job']['uuid']
+ end
+ assert_no_text 'Output data not available'
+ assert_selector 'a[href="#Log"]', text: 'Log'
+ assert_no_selector 'a[data-toggle="disabled"]', text: 'Log'
+ else
+ if pipeline_page
+ assert_no_text 'This pipeline was created from' # template is not readable
+ assert_no_selector 'a', text: object['components']['foo']['job']['uuid']
+ end
+ assert_text 'Output data not available'
+ assert_text object['job']
+ assert_selector 'a[data-toggle="disabled"]', text: 'Log'
+ end
+
+ click_link 'Log'
+ if objects_readable
+ assert_no_text 'foo' # should be in Log tab
+ assert_text 'stderr crunchstat' if pipeline_page
+ else
+ assert_text 'foo' # Log tab disabled and hence still in first tab
+ assert_no_text 'stderr crunchstat' # log line shouldn't be seen
+ end
+ end
+ end
+
+ [
+ ['new_pipeline_in_publicly_accessible_project', true],
+ ['new_pipeline_in_publicly_accessible_project_but_other_objects_elsewhere', false],
+ ['new_pipeline_in_publicly_accessible_project_but_other_objects_elsewhere', false, 'spectator'],
+ ['new_pipeline_in_publicly_accessible_project_but_other_objects_elsewhere', true, 'admin'],
+ ].each do |fixture, objects_readable, user=nil|
+ test "access #{fixture} in public project with objects readable=#{objects_readable} with user #{user}" do
+ object = api_fixture('pipeline_instances')[fixture]
+ page = "/pipeline_instances/#{object['uuid']}"
+ if user
+ visit page_with_token user, page
+ else
+ visit page
+ end
+
+ # click Components tab
+ click_link 'Components'
+
+ if objects_readable
+ assert_text 'This pipeline was created from'
+ if user == 'admin'
+ assert_text 'input'
+ assert_selector 'a', text: 'Choose'
+ else
+ assert_selector 'a', text: object['components']['foo']['script_parameters']['input']['value']
+ end
+ else
+ assert_no_text 'This pipeline was created from' # template is not readable
+ assert_text object['components']['foo']['script_parameters']['input']['value']
+ assert_no_selector 'a', text: object['components']['foo']['script_parameters']['input']['value']
+ end
+ end
+ end
+
test "anonymous user accesses collection in shared project" do
visit "/collections/#{api_fixture('collections')['public_text_file']['uuid']}"
script_parameters:
input: fa7aeb5140e2848d39b416daeef4ffc5+45
input2: "stuff2"
+ log: ~
+ output: b519d9cb706a29fc7ea24dbea2f05851+93
+
+job_in_publicly_accessible_project_but_other_objects_elsewhere:
+ uuid: zzzzz-8i9sb-jyq01muyhgr4ofj
+ owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ repository: active/foo
+ script: completed_job_script
+ script_version: 4fe459abe02d9b365932b8f5dc419439ab4e2577
+ state: Complete
+ script_parameters:
+ input: fa7aeb5140e2848d39b416daeef4ffc5+45
+ input2: "stuff2"
+ log: zzzzz-4zz18-fy296fx3hot09f7
+ output: zzzzz-4zz18-bv31uwvy3neko21
# Test Helper trims the rest of the file
updated_at: 2014-11-07 23:33:42.347455000 Z
modified_at: 2014-11-07 23:33:42.347455000 Z
object_owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
+
+log_line_for_pipeline_in_publicly_accessible_project:
+ id: 8
+ uuid: zzzzz-57u5n-tmymyrojrjyhb45
+ owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
+ modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ object_uuid: zzzzz-d1hrv-n68vc490mloy4fi
+ event_at: 2014-11-07 23:33:42.347455000 Z
+ event_type: stderr
+ summary: ~
+ properties:
+ text: '2014-11-07_23:33:41 zzzzz-d1hrv-n68vc490mloy4fi 31708 1 stderr crunchstat:
+ cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
+ 0.9900 sys'
+ created_at: 2014-11-07 23:33:42.351913000 Z
+ updated_at: 2014-11-07 23:33:42.347455000 Z
+ modified_at: 2014-11-07 23:33:42.347455000 Z
+ object_owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
+
+log_line_for_pipeline_in_publicly_accessible_project_but_other_objects_elsewhere:
+ id: 9
+ uuid: zzzzz-57u5n-tmyhy56k9lnhb45
+ owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
+ modified_by_client_uuid: zzzzz-ozdt8-obw7foaks3qjyej
+ modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ object_uuid: zzzzz-d1hrv-pisharednotobjs
+ event_at: 2014-11-07 23:33:42.347455000 Z
+ event_type: stderr
+ summary: ~
+ properties:
+ text: '2014-11-07_23:33:41 zzzzz-d1hrv-pisharednotobjs 31708 1 stderr crunchstat:
+ cpu 1935.4300 user 59.4100 sys 8 cpus -- interval 10.0002 seconds 12.9900 user
+ 0.9900 sys'
+ created_at: 2014-11-07 23:33:42.351913000 Z
+ updated_at: 2014-11-07 23:33:42.347455000 Z
+ modified_at: 2014-11-07 23:33:42.347455000 Z
+ object_owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
uuid: zzzzz-d1hrv-n68vc490mloy4fi
owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
name: Pipeline in publicly accessible project
+ pipeline_template_uuid: zzzzz-p5p6p-tmpltpublicproj
state: Complete
created_at: 2014-09-15 12:00:00
components:
required: true
dataclass: Collection
title: foo instance input
+ job:
+ uuid: zzzzz-8i9sb-jyq01m7in1jlofj
+ repository: active/foo
+ script: foo
+ script_version: master
+ script_parameters:
+ input: zzzzz-4zz18-4en62shvi99lxd4
+ log: zzzzz-4zz18-4en62shvi99lxd4
+ output: b519d9cb706a29fc7ea24dbea2f05851+93
+ state: Complete
+
+pipeline_in_publicly_accessible_project_but_other_objects_elsewhere:
+ uuid: zzzzz-d1hrv-pisharednotobjs
+ owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ name: Pipeline in public project with other objects elsewhere
+ pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
+ state: Complete
+ created_at: 2014-09-15 12:00:00
+ components:
+ foo:
+ script: foo
+ script_version: master
+ script_parameters:
+ input:
+ required: true
+ dataclass: Collection
+ title: foo instance input
+ job:
+ uuid: zzzzz-8i9sb-aceg2bnq7jt7kon
+ repository: active/foo
+ script: foo
+ script_version: master
+ script_parameters:
+ input: zzzzz-4zz18-bv31uwvy3neko21
+ log: zzzzz-4zz18-bv31uwvy3neko21
+ output: zzzzz-4zz18-bv31uwvy3neko21
+ state: Complete
+
+new_pipeline_in_publicly_accessible_project:
+ uuid: zzzzz-d1hrv-newpisharedobjs
+ owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ name: Pipeline in New state in publicly accessible project
+ pipeline_template_uuid: zzzzz-p5p6p-tmpltpublicproj
+ state: New
+ created_at: 2014-09-15 12:00:00
+ components:
+ foo:
+ script: foo
+ script_version: master
+ script_parameters:
+ input:
+ required: true
+ dataclass: Collection
+ value: b519d9cb706a29fc7ea24dbea2f05851+93
+
+new_pipeline_in_publicly_accessible_project_but_other_objects_elsewhere:
+ uuid: zzzzz-d1hrv-newsharenotobjs
+ owner_uuid: zzzzz-j7d0g-zhxawtyetzwc5f0
+ name: Pipeline in New state in public project with objects elsewhere
+ pipeline_template_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
+ state: New
+ created_at: 2014-09-15 12:00:00
+ components:
+ foo:
+ script: foo
+ script_version: master
+ script_parameters:
+ input:
+ required: true
+ dataclass: Collection
+ value: zzzzz-4zz18-bv31uwvy3neko21
pipeline_in_running_state:
name: running_with_job