Further improvements to provenance graphs, added ability to select a few jobs and...
[arvados.git] / apps / workbench / app / controllers / collections_controller.rb
1 class CollectionsController < ApplicationController
2   skip_before_filter :find_object_by_uuid, :only => [:provenance]
3   skip_before_filter :check_user_agreements, :only => [:show_file]
4
5   def index
6     if params[:search].andand.length.andand > 0
7       tags = Link.where(any: ['contains', params[:search]])
8       @collections = (Collection.where(uuid: tags.collect(&:head_uuid)) |
9                       Collection.where(any: ['contains', params[:search]])).
10         uniq { |c| c.uuid }
11     else
12       @collections = Collection.limit(100)
13     end
14     @links = Link.limit(1000).
15       where(head_uuid: @collections.collect(&:uuid))
16     @collection_info = {}
17     @collections.each do |c|
18       @collection_info[c.uuid] = {
19         tags: [],
20         wanted: false,
21         wanted_by_me: false,
22         provenance: [],
23         links: []
24       }
25     end
26     @links.each do |link|
27       @collection_info[link.head_uuid] ||= {}
28       info = @collection_info[link.head_uuid]
29       case link.link_class
30       when 'tag'
31         info[:tags] << link.name
32       when 'resources'
33         info[:wanted] = true
34         info[:wanted_by_me] ||= link.tail_uuid == current_user.uuid
35       when 'provenance'
36         info[:provenance] << link.name
37       end
38       info[:links] << link
39     end
40     @request_url = request.url
41   end
42
43   def show_file
44     opts = params.merge(arvados_api_token: Thread.current[:arvados_api_token])
45     if r = params[:file].match(/(\.\w+)/)
46       ext = r[1]
47     end
48     self.response.headers['Content-Type'] =
49       Rack::Mime::MIME_TYPES[ext] || 'application/octet-stream'
50     self.response.headers['Content-Length'] = params[:size] if params[:size]
51     self.response.headers['Content-Disposition'] = params[:disposition] if params[:disposition]
52     self.response_body = FileStreamer.new opts
53   end
54
55
56   def show
57     return super if !@object
58     @provenance = []
59     @output2job = {}
60     @output2colorindex = {}
61     @sourcedata = {params[:uuid] => {uuid: params[:uuid]}}
62     @protected = {}
63
64     colorindex = -1
65     any_hope_left = true
66     while any_hope_left
67       any_hope_left = false
68       Job.where(output: @sourcedata.keys).sort_by { |a| a.finished_at || a.created_at }.reverse.each do |job|
69         if !@output2colorindex[job.output]
70           any_hope_left = true
71           @output2colorindex[job.output] = (colorindex += 1) % 10
72           @provenance << {job: job, output: job.output}
73           @sourcedata.delete job.output
74           @output2job[job.output] = job
75           job.dependencies.each do |new_source_data|
76             unless @output2colorindex[new_source_data]
77               @sourcedata[new_source_data] = {uuid: new_source_data}
78             end
79           end
80         end
81       end
82     end
83
84     Link.where(head_uuid: @sourcedata.keys | @output2job.keys).each do |link|
85       if link.link_class == 'resources' and link.name == 'wants'
86         @protected[link.head_uuid] = true
87       end
88     end
89     Link.where(tail_uuid: @sourcedata.keys).each do |link|
90       if link.link_class == 'data_origin'
91         @sourcedata[link.tail_uuid][:data_origins] ||= []
92         @sourcedata[link.tail_uuid][:data_origins] << [link.name, link.head_kind, link.head_uuid]
93       end
94     end
95     Collection.where(uuid: @sourcedata.keys).each do |collection|
96       if @sourcedata[collection.uuid]
97         @sourcedata[collection.uuid][:collection] = collection
98       end
99     end
100     
101     Collection.where(uuid: @object.uuid).each do |u|
102       @prov_svg = ProvenanceHelper::create_provenance_graph u.provenance, {:direction => :bottom_up, :combine_jobs => true}
103     end
104   end
105
106   protected
107   class FileStreamer
108     def initialize(opts={})
109       @opts = opts
110     end
111     def each
112       return unless @opts[:uuid] && @opts[:file]
113       env = Hash[ENV].
114         merge({
115                 'ARVADOS_API_HOST' =>
116                 $arvados_api_client.arvados_v1_base.
117                 sub(/\/arvados\/v1/, '').
118                 sub(/^https?:\/\//, ''),
119                 'ARVADOS_API_TOKEN' =>
120                 @opts[:arvados_api_token],
121                 'ARVADOS_API_HOST_INSECURE' =>
122                 Rails.configuration.arvados_insecure_https ? 'true' : 'false'
123               })
124       IO.popen([env, 'arv-get', "#{@opts[:uuid]}/#{@opts[:file]}"],
125                'rb') do |io|
126         while buf = io.read(2**20)
127           yield buf
128         end
129       end
130       Rails.logger.warn("#{@opts[:uuid]}/#{@opts[:file]}: #{$?}") if $? != 0
131     end
132   end
133 end