Now renders collections using up to the first three files in the collection
[arvados.git] / apps / workbench / app / helpers / provenance_helper.rb
1 module ProvenanceHelper
2   def self.describe_node(pdata, uuid)
3     rsc = ArvadosBase::resource_class_for_uuid uuid.to_s
4     if rsc
5       href = "/#{rsc.to_s.underscore.pluralize rsc}/#{uuid}"
6
7       #"\"#{uuid}\" [label=\"#{rsc}\\n#{uuid}\",href=\"#{href}\"];\n"
8       if rsc == Collection
9         if pdata[uuid] 
10           #puts pdata[uuid]
11           if pdata[uuid][:name]
12             return "\"#{uuid}\" [label=\"#{pdata[uuid][:name]}\",href=\"#{href}\",shape=oval];\n"
13           else
14             files = nil
15             if pdata[uuid].respond_to? :files
16               files = pdata[uuid].files
17             elsif pdata[uuid][:files]
18               files = pdata[uuid][:files]
19             end
20             
21             if files
22               i = 0
23               label = ""
24               while i < 3 and i < files.length
25                 label += "\\n" unless label == ""
26                 label += files[i][1]
27                 i += 1
28               end
29               if i < files.length
30                 label += "\\n&vellip;"
31               end
32               return "\"#{uuid}\" [label=\"#{label}\",href=\"#{href}\",shape=oval];\n"
33             end
34           end  
35         end
36         return "\"#{uuid}\" [label=\"#{rsc}\",href=\"#{href}\"];\n"
37       end
38     end
39     return ""
40   end
41
42   def self.job_uuid(job)
43     # "#{job[:script]}\\n#{job[:script_version]}"
44     "#{job[:script]}"
45   end
46
47   def self.collection_uuid(uuid)
48     m = /([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?/.match(uuid.to_s)
49     if m
50       m[1]
51     else
52       nil
53     end
54   end
55
56   def self.edge(tail, head, extra, opts)
57     if opts[:direction] == :bottom_up
58       gr = "\"#{tail}\" -> \"#{head}\""
59     else
60       gr = "\"#{head}\" -> \"#{tail}\""
61     end
62     if extra.length > 0
63       gr += "["
64       extra.each do |k, v|
65         gr += "#{k}=\"#{v}\","
66       end
67       gr += "]"
68     end
69     gr += ";\n"
70     gr
71   end
72
73   def self.script_param_edges(pdata, visited, job, prefix, sp, opts)
74     gr = ""
75     if sp and not sp.empty?
76       case sp
77       when Hash
78         sp.each do |k, v|
79           if prefix.size > 0
80             k = prefix + "::" + k.to_s
81           end
82           gr += ProvenanceHelper::script_param_edges(pdata, visited, job, k.to_s, v, opts)
83         end
84       when Array
85         i = 0
86         node = ""
87         sp.each do |v|
88           if collection_uuid(v)
89             gr += ProvenanceHelper::script_param_edges(pdata, visited, job, "#{prefix}[#{i}]", v, opts)
90           else
91             node += "', '" unless node == ""
92             node = "['" if node == ""
93             node += "#{v}"
94           end
95           i += 1
96         end
97         unless node == ""
98           node += "']"
99           #puts node
100           #id = "#{job[:uuid]}_#{prefix}"
101           gr += "\"#{node}\" [label=\"#{node}\"];\n"
102           gr += edge(job_uuid(job), node, {:label => prefix}, opts)        
103         end
104       else
105         m = collection_uuid(sp)
106         if m
107           gr += edge(job_uuid(job), m, {:label => prefix}, opts)
108           gr += ProvenanceHelper::generate_provenance_edges(pdata, visited, m, opts)
109         elsif opts[:all_script_parameters]
110           #id = "#{job[:uuid]}_#{prefix}"
111           gr += "\"#{sp}\" [label=\"#{sp}\"];\n"
112           gr += edge(job_uuid(job), sp, {:label => prefix}, opts)
113         end
114       end
115     end
116     gr
117   end
118
119   def self.generate_provenance_edges(pdata, visited, uuid, opts)
120     gr = ""
121     m = ProvenanceHelper::collection_uuid(uuid)
122     uuid = m if m
123
124     uuid = uuid.intern if uuid
125
126     if (not uuid) or uuid.empty? or visited[uuid]
127
128       #puts "already visited #{uuid}"
129       return ""
130     end
131
132     if not pdata[uuid] then 
133       return ProvenanceHelper::describe_node(pdata, uuid)
134     else
135       visited[uuid] = true
136     end
137
138     #puts "visiting #{uuid}"
139
140     if m  
141       # uuid is a collection
142       gr += ProvenanceHelper::describe_node(pdata, uuid)
143
144       pdata.each do |k, job|
145         if job[:output] == uuid.to_s
146           gr += self.edge(uuid, job_uuid(job), {:label => "output"}, opts)
147           gr += ProvenanceHelper::generate_provenance_edges(pdata, visited, job[:uuid], opts)
148         end
149         if job[:log] == uuid.to_s
150           gr += edge(uuid, job_uuid(job), {:label => "log"}, opts)
151           gr += ProvenanceHelper::generate_provenance_edges(pdata, visited, job[:uuid], opts)
152         end
153       end
154     else
155       # uuid is something else
156       rsc = ArvadosBase::resource_class_for_uuid uuid.to_s
157
158       if rsc == Job
159         job = pdata[uuid]
160         if job
161           gr += ProvenanceHelper::script_param_edges(pdata, visited, job, "", job[:script_parameters], opts)
162         end
163       else
164         gr += ProvenanceHelper::describe_node(pdata, uuid)
165       end
166     end
167
168     pdata.each do |k, link|
169       if link[:head_uuid] == uuid.to_s and link[:link_class] == "provenance"
170         gr += ProvenanceHelper::describe_node(pdata, link[:tail_uuid])
171         gr += edge(link[:head_uuid], link[:tail_uuid], {:label => link[:name], :href => "/links/#{link[:uuid]}"}, opts) 
172         gr += ProvenanceHelper::generate_provenance_edges(pdata, visited, link[:tail_uuid], opts)
173       end
174     end
175
176     #puts "finished #{uuid}"
177
178     gr
179   end
180
181   def self.create_provenance_graph(pdata, uuid, opts={})
182     require 'open3'
183     
184     gr = """strict digraph {
185 node [fontsize=8,shape=box];
186 edge [fontsize=8];"""
187
188     if opts[:direction] == :bottom_up
189       gr += "edge [dir=back];"
190     end
191
192     #puts "pdata is #{pdata}"
193
194     visited = {}
195     if uuid.respond_to? :each
196       uuid.each do |u|
197         gr += ProvenanceHelper::generate_provenance_edges(pdata, visited, u, opts)
198       end
199     else
200       gr += ProvenanceHelper::generate_provenance_edges(pdata, visited, uuid, opts)
201     end
202
203     gr += "}"
204     svg = ""
205
206     #puts gr
207
208     Open3.popen2("dot", "-Tsvg") do |stdin, stdout, wait_thr|
209       stdin.print(gr)
210       stdin.close
211       svg = stdout.read()
212       wait_thr.value
213       stdout.close()
214     end
215
216     svg = svg.sub(/<\?xml.*?\?>/m, "")
217     svg = svg.sub(/<!DOCTYPE.*?>/m, "")
218   end
219 end