1 module ProvenanceHelper
4 def initialize(pdata, opts)
11 def self.collection_uuid(uuid)
12 m = /^([a-f0-9]{32}(\+[0-9]+)?)(\+.*)?$/.match(uuid.to_s)
17 # Collection.where(uuid: ['contains', m[1]]).each do |u|
18 # puts "fixup #{uuid} to #{u.uuid}"
27 def describe_node(uuid)
28 rsc = ArvadosBase::resource_class_for_uuid uuid.to_s
30 href = "/#{rsc.to_s.underscore.pluralize rsc}/#{uuid}"
32 #"\"#{uuid}\" [label=\"#{rsc}\\n#{uuid}\",href=\"#{href}\"];\n"
36 if @pdata[uuid][:name]
37 return "\"#{uuid}\" [label=\"#{@pdata[uuid][:name]}\",href=\"#{href}\",shape=oval];\n"
40 if @pdata[uuid].respond_to? :files
41 files = @pdata[uuid].files
42 elsif @pdata[uuid][:files]
43 files = @pdata[uuid][:files]
49 while i < 3 and i < files.length
50 label += "\\n" unless label == ""
55 label += "\\n⋮"
57 return "\"#{uuid}\" [label=\"#{label}\",href=\"#{href}\",shape=oval];\n"
61 return "\"#{uuid}\" [label=\"#{rsc}\",href=\"#{href}\"];\n"
68 if @opts[:combine_jobs]
69 uuid = "#{job[:script]}"
71 uuid = "#{job[:uuid]}"
74 @jobs[uuid] = [] unless @jobs[uuid]
75 @jobs[uuid] << job unless @jobs[uuid].include? job
80 def edge(tail, head, extra)
81 if @opts[:direction] == :bottom_up
82 gr = "\"#{tail}\" -> \"#{head}\""
84 gr = "\"#{head}\" -> \"#{tail}\""
89 gr += "#{k}=\"#{v}\","
97 def script_param_edges(job, prefix, sp)
99 if sp and not sp.empty?
104 k = prefix + "::" + k.to_s
106 gr += script_param_edges(job, k.to_s, v)
112 if GenerateGraph::collection_uuid(v)
113 gr += script_param_edges(job, "#{prefix}[#{i}]", v)
115 node += "', '" unless node == ""
116 node = "['" if node == ""
124 #id = "#{job[:uuid]}_#{prefix}"
125 gr += "\"#{node}\" [label=\"#{node}\"];\n"
126 gr += edge(job_uuid(job), node, {:label => prefix})
129 m = GenerateGraph::collection_uuid(sp)
131 gr += edge(job_uuid(job), m, {:label => prefix})
132 gr += generate_provenance_edges(m)
133 elsif @opts[:all_script_parameters]
134 #id = "#{job[:uuid]}_#{prefix}"
135 gr += "\"#{sp}\" [label=\"#{sp}\"];\n"
136 gr += edge(job_uuid(job), sp, {:label => prefix})
143 def generate_provenance_edges(uuid)
145 m = GenerateGraph::collection_uuid(uuid)
148 uuid = uuid.intern if uuid
150 if (not uuid) or uuid.empty? or @visited[uuid]
152 #puts "already @visited #{uuid}"
156 if not @pdata[uuid] then
157 return describe_node(uuid)
159 @visited[uuid] = true
162 #puts "visiting #{uuid}"
165 # uuid is a collection
166 gr += describe_node(uuid)
168 @pdata.each do |k, job|
169 if job[:output] == uuid.to_s
170 gr += edge(uuid, job_uuid(job), {:label => "output"})
171 gr += generate_provenance_edges(job[:uuid])
173 if job[:log] == uuid.to_s
174 gr += edge(uuid, job_uuid(job), {:label => "log"})
175 gr += generate_provenance_edges(job[:uuid])
179 # uuid is something else
180 rsc = ArvadosBase::resource_class_for_uuid uuid.to_s
185 gr += script_param_edges(job, "", job[:script_parameters])
187 if @opts[:script_version_nodes]
188 gr += edge(job_uuid(job), job[:script_version], {:label => "script_version"})
192 gr += describe_node(uuid)
196 @pdata.each do |k, link|
197 if link[:head_uuid] == uuid.to_s and link[:link_class] == "provenance"
198 gr += describe_node(link[:tail_uuid])
199 gr += edge(link[:head_uuid], link[:tail_uuid], {:label => link[:name], :href => "/links/#{link[:uuid]}"})
200 gr += generate_provenance_edges(link[:tail_uuid])
204 #puts "finished #{uuid}"
212 gr += "\"#{k}\" [href=\"/jobs?"
215 gr += "uuid%5b%5d=#{u[:uuid]}&"
219 gr += if @opts[:combine_jobs] then "#{script}" else "#{script}\\n#{v[0][:finished_at]}" end
227 def self.create_provenance_graph(pdata, opts={})
228 if pdata.is_a? Array or pdata.is_a? ArvadosResourceList
231 p2[k[:uuid].intern] = k if k[:uuid]
236 unless pdata.is_a? Hash
237 raise "create_provenance_graph accepts Array or Hash for pdata only, pdata is #{pdata.class}"
240 gr = """strict digraph {
241 node [fontsize=8,shape=box];
245 if opts[:direction] == :bottom_up
246 gr += "edge [dir=back];"
249 #puts "@pdata is #{pdata}"
251 g = GenerateGraph.new(pdata, opts)
254 gr += g.generate_provenance_edges(k)
257 gr += g.add_jobs_href
266 Open3.popen2("dot", "-Tsvg") do |stdin, stdout, wait_thr|
274 svg = svg.sub(/<\?xml.*?\?>/m, "")
275 svg = svg.sub(/<!DOCTYPE.*?>/m, "")
278 def self.find_collections(sp)
280 if sp and not sp.empty?
284 c.concat(find_collections(v))
288 c.concat(find_collections(v))
291 m = GenerateGraph::collection_uuid(sp)