* Added used_by query for collections
authorPeter Amstutz <peter.amstutz@curoverse.com>
Tue, 4 Feb 2014 20:48:46 +0000 (15:48 -0500)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Tue, 4 Feb 2014 20:48:46 +0000 (15:48 -0500)
* Fixed svg div javascript/layouts after Tom's changes
* Added hover css effect for graph lines
* Added graph to pipeline instance comparison page

14 files changed:
apps/workbench/app/controllers/collections_controller.rb
apps/workbench/app/controllers/pipeline_instances_controller.rb
apps/workbench/app/helpers/provenance_helper.rb
apps/workbench/app/models/collection.rb
apps/workbench/app/views/application/_svg_div.html.erb
apps/workbench/app/views/application/index.html.erb
apps/workbench/app/views/application/show.html.erb
apps/workbench/app/views/collections/_show_provenance_graph.html.erb
apps/workbench/app/views/collections/_show_used_by.html.erb [new file with mode: 0644]
apps/workbench/app/views/collections/show.html.erb [deleted file]
apps/workbench/app/views/pipeline_instances/compare.html.erb
services/api/app/controllers/application_controller.rb
services/api/app/controllers/arvados/v1/collections_controller.rb
services/api/config/routes.rb

index 7feba20c881d5ca2c32493d8a784b9c5c0bb2a05..c6a7eafafd3de19fcf93dfb0d70ddb5e5aa255ca 100644 (file)
@@ -3,7 +3,7 @@ class CollectionsController < ApplicationController
   skip_before_filter :check_user_agreements, :only => [:show_file]
 
   def show_pane_list
-    %w(files attributes provenance provenance_graph)
+    %w(files attributes provenance provenance_graph used_by)
   end
   def index
     if params[:search].andand.length.andand > 0
@@ -103,6 +103,7 @@ class CollectionsController < ApplicationController
     
     Collection.where(uuid: @object.uuid).each do |u|
       @prov_svg = ProvenanceHelper::create_provenance_graph u.provenance, "provenance_svg", {:direction => :bottom_up, :combine_jobs => :script_only}
+      @used_by_svg = ProvenanceHelper::create_provenance_graph u.used_by, "used_by_svg", {:direction => :top_down, :combine_jobs => :script_only, :pdata_only => true}
     end
   end
 
index a42a734394243d7deeab805e195348ef5e9d15fd..98ae0e6472f6476d9b253a86c32777c2c32db844 100644 (file)
@@ -3,21 +3,13 @@ class PipelineInstancesController < ApplicationController
   before_filter :find_objects_by_uuid, only: :compare
   include PipelineInstancesHelper
 
-  def show
-    @pipelines = [@object]
-
-    if params[:compare]
-      PipelineInstance.where(uuid: params[:compare]).each do |p|
-        @pipelines << p
-      end
-    end
-
+  def graph(pipelines)
     count = {}    
     provenance = {}
     pips = {}
     n = 1
 
-    @pipelines.each do |p|
+    pipelines.each do |p|
       collections = []
 
       p.components.each do |k, v|
@@ -49,6 +41,20 @@ class PipelineInstancesController < ApplicationController
       n = n << 1
     end
 
+    return provenance, pips
+  end
+
+  def show
+    @pipelines = [@object]
+
+    if params[:compare]
+      PipelineInstance.where(uuid: params[:compare]).each do |p|
+        @pipelines << p
+      end
+    end
+
+    provenance, pips = graph(@pipelines)
+
     @prov_svg = ProvenanceHelper::create_provenance_graph provenance, "provenance_svg", {
       :all_script_parameters => true, 
       :combine_jobs => :script_and_version,
@@ -112,6 +118,14 @@ class PipelineInstancesController < ApplicationController
         end
       end
     end
+
+    provenance, pips = graph(@objects)
+
+    @prov_svg = ProvenanceHelper::create_provenance_graph provenance, "provenance_svg", {
+      :all_script_parameters => true, 
+      :combine_jobs => :script_and_version,
+      :script_version_nodes => true,
+      :pips => pips }
   end
 
   protected
index 73b4a477676ebb349bfcf2ddda6cf95635cd1453..ef165e86e9eb8be210fcbbf80d93501812d5fb88 100644 (file)
@@ -46,6 +46,12 @@ module ProvenanceHelper
       
         #"\"#{uuid}\" [label=\"#{rsc}\\n#{uuid}\",href=\"#{href}\"];\n"
         if rsc == Collection
+          puts uuid
+          if uuid == :"d41d8cd98f00b204e9800998ecf8427e+0"
+            # special case
+            puts "empty!"
+            return "\"#{uuid}\" [label=\"(empty collection)\"];\n"
+          end
           if @pdata[uuid] 
             #puts @pdata[uuid]
             if @pdata[uuid][:name]
@@ -128,7 +134,7 @@ module ProvenanceHelper
           sp.each do |v|
             if GenerateGraph::collection_uuid(v)
               gr += script_param_edges(job, "#{prefix}[#{i}]", v)
-            else
+            elsif @opts[:all_script_parameters]
               node += "', '" unless node == ""
               node = "['" if node == ""
               node += "#{v}"
@@ -144,7 +150,8 @@ module ProvenanceHelper
           end
         else
           m = GenerateGraph::collection_uuid(sp)
-          if m
+          #puts "#{m} pdata is #{@pdata[m.intern]}"
+          if m and (@pdata[m.intern] or (not @opts[:pdata_only]))
             gr += edge(job_uuid(job), m, {:label => prefix})
             gr += generate_provenance_edges(m)
           elsif @opts[:all_script_parameters]
@@ -182,6 +189,11 @@ module ProvenanceHelper
         # uuid is a collection
         gr += describe_node(uuid)
 
+        if m == :"d41d8cd98f00b204e9800998ecf8427e+0"
+          # empty collection, don't follow any further
+          return gr
+        end
+
         @pdata.each do |k, job|
           if job[:output] == uuid.to_s
             gr += edge(uuid, job_uuid(job), {:label => "output"})
@@ -265,8 +277,8 @@ module ProvenanceHelper
     end
     
     gr = """strict digraph {
-node [fontsize=8,shape=box];
-edge [fontsize=8];
+node [fontsize=10,shape=box];
+edge [fontsize=10];
 """
 
     if opts[:direction] == :bottom_up
index cdd148c0371a297de2f874511e6bf3512a7281ac..bda5523d8cfdd9192aefc7923b9a7ba350f05e4e 100644 (file)
@@ -20,4 +20,8 @@ class Collection < ArvadosBase
   def provenance
     $arvados_api_client.api "collections/#{self.uuid}/", "provenance"
   end
+
+  def used_by
+    $arvados_api_client.api "collections/#{self.uuid}/", "used_by"
+  end
 end
index f3f6a71c12787061bf7d8fb57f85dc35990f992b..1e7fdf7fbb3cd1fb7281c24ee223793eac554bbb 100644 (file)
@@ -1,4 +1,5 @@
 <%= content_for :css do %>
+/* Need separate style for each instance of svg div because javascript will manipulate the properties. */
 #<%= divId %> {
  padding-left: 3px;
  overflow: auto;
@@ -7,27 +8,29 @@
  border-color: gray;
  position: absolute;
  left: 1px;
-
+}
+path:hover {
+stroke-width: 5;
 }
 <% end %>
 
-<div id="_<%= divId %>_container" style="padding-top: 41px; margin-top: -41px">
-  <script>
-    (function() {
+<%= content_for :js do %>
+    $(window).on('load', function() {
       var fn = function () { graph_zoom("<%= divId %>", "<%=svgId %>", 1) };
-      $(window).resize(fn);
-      $(window).load(fn);
-      $(window).scroll(fn);
-    })();
-  </script>
+      $(window).on('resize', fn);
+      $(window).on('scroll', fn);
+    });
+<% end %>
+
+<div id="_<%= divId %>_container" style="padding-top: 41px; margin-top: -41px">
   <div style="text-align: right">
     <a style="cursor: pointer"><span class="icon-zoom-out" onclick="graph_zoom('<%= divId %>', '<%= svgId %>', .9)"></span></a>
     <a style="cursor: pointer"><span class="icon-zoom-in" onclick="graph_zoom('<%= divId %>', '<%= svgId %>', 1./.9)"></span></a>
   </div>
 
   <div id="<%= divId %>">
-    <span id="_<%= divId %>_center" style="padding-left: 300px"/>
+    <span id="_<%= divId %>_center" style="padding-left: 300px"></span>
     <%= raw(svg) %>
   </div>
-  <div id="_<%= divId %>_padding" style="padding-bottom: 1em" />
+  <div id="_<%= divId %>_padding" style="padding-bottom: 1em"></div>
 </div>
index 95e0b4172af179b1da0457eb8270bf19a6df29fa..167fea16d806ddf33d55de3c10dbc126c87f1bb7 100644 (file)
@@ -17,7 +17,7 @@
 <div class="tabbable">
 <ul class="nav nav-tabs">
   <% pane_list.each_with_index do |pane, i| %>
-  <li class="<%= 'active' if i==0 %>"><a href="#<%= pane %>" data-toggle="tab"><%= pane.capitalize %></a></li>
+  <li class="<%= 'active' if i==0 %>"><a href="#<%= pane %>" data-toggle="tab" id="<%= pane %>-tab"> <%= pane.capitalize %></a></li>
   <% end %>
 </ul>
 
index 348d9d982acdd8920ca9e23c78192948324c1fe5..d2abd72b87c96bf72cc2b957defb8e88edea1ab6 100644 (file)
@@ -26,7 +26,7 @@
 <div class="tabbable">
 <ul class="nav nav-tabs">
   <% pane_list.each_with_index do |pane, i| %>
-  <li class="<%= 'active' if i==0 %>"><a href="#<%= pane %>" data-toggle="tab"><%= pane.capitalize.gsub '_', ' ' %></a></li>
+  <li class="<%= 'active' if i==0 %>"><a href="#<%= pane %>" data-toggle="tab" id="<%= pane %>-tab"><%= pane.capitalize.gsub '_', ' ' %></a></li>
   <% end %>
 </ul>
 
index 1f367c72467e9ba7de9cd60797d9e7485d2d0fa5..d127f4f2de75d7433291326d78554ec1e0a47156 100644 (file)
@@ -1,9 +1,10 @@
 <% content_for :js do %>
+  $(window).on('load',
     $(function() {
-      $('#prov-tab').on('shown', function() { provenance_sizing_fixup("provenance_graph", "provenance_svg"); });
-    })
+      $('#provenance_graph-tab').on('shown', function() { graph_zoom("provenance_graph_div", "provenance_svg", 1); });
+    }))
 <% end %>
 <%= render partial: 'application/svg_div', locals: {
-    divId: "provenance_graph", 
+    divId: "provenance_graph_div", 
     svgId: "provenance_svg", 
     svg: @prov_svg } %>
diff --git a/apps/workbench/app/views/collections/_show_used_by.html.erb b/apps/workbench/app/views/collections/_show_used_by.html.erb
new file mode 100644 (file)
index 0000000..837f30f
--- /dev/null
@@ -0,0 +1,11 @@
+ <% content_for :js do %>
+  $(window).on('load',
+    $(function() {
+      $('#used_by-tab').on('shown', function() { graph_zoom("used_by_graph", "used_by_svg", 1); });
+    }))
+<% end %>
+<%= render partial: 'application/svg_div', locals: {
+    divId: "used_by_graph", 
+    svgId: "used_by_svg", 
+    svg: @used_by_svg } %>
+
diff --git a/apps/workbench/app/views/collections/show.html.erb b/apps/workbench/app/views/collections/show.html.erb
deleted file mode 100644 (file)
index f1943c3..0000000
+++ /dev/null
@@ -1,263 +0,0 @@
-<%= content_for :css do %>
-<%# https://github.com/mbostock/d3/wiki/Ordinal-Scales %>
-<% n=-1; %w(#1f77b4 #ff7f0e #2ca02c #d62728 #9467bd #8c564b #e377c2 #7f7f7f #bcbd22 #17becf).each do |color| %>
-.colorseries-10-<%= n += 1 %>, .btn.colorseries-10-<%= n %>:hover, .label.colorseries-10-<%= n %>:hover {
-  *background-color: <%= color %>;
-  background-color: <%= color %>;
-  background-image: none;
-}
-<% end %>
-.colorseries-nil { }
-.label a {
-  color: inherit;
-}
-
-<% end %>
-
-<div class="tabbable">
-<ul class="nav nav-tabs">
-  <li class="active"><a href="#files" data-toggle="tab">Files (<%= @object.files ? @object.files.size : 0 %>)</a></li>
-  <li><a href="#provenance" data-toggle="tab">Provenance (<%= @provenance.size %>)</a></li>
-  <li><a href="#jobs" data-toggle="tab">Jobs (<%= @provenance.size %>)</a></li>
-  <li><a href="#sourcedata" data-toggle="tab">Source data (<%= @sourcedata.size %>)</a></li>
-  <li><a href="#owner-groups-resources" data-toggle="tab">Owner, groups, resources</a></li>
-  <li><a href="#provenance2" data-toggle="tab" id="prov-tab">Provenance graph</a></li>
-  <li><a href="#used-by" data-toggle="tab">Used by</a></li>
-</ul>
-
-<div class="tab-content">
-  <div id="files" class="tab-pane fade in active">
-    <table class="table table-bordered" style="table-layout: fixed">
-      <thead>
-        <tr>
-          <th>path</th>
-          <th>file</th>
-          <th style="width:1.5em">d/l</th>
-          <th style="width: 7em; text-align:right">size</th>
-        </tr>
-      </thead><tbody>
-        <% if @object then @object.files.sort_by{|f|f[1]}.each do |file| %>
-        <% file_path = "#{file[0]}/#{file[1]}" %>
-        <tr>
-          <td>
-            <%= file[0] %>
-          </td>
-
-          <td>
-            <%= link_to file[1], {controller: 'collections', action: 'show_file', uuid: @object.uuid, file: file_path, size: file[2], disposition: 'inline'}, {title: 'View in browser'} %>
-          </td>
-
-          <td>
-            <div style="display:inline-block">
-            <%= link_to raw('<i class="icon-download"></i>'), {controller: 'collections', action: 'show_file', uuid: @object.uuid, file: file_path, size: file[2], disposition: 'attachment'}, {class: 'label label-info', title: 'Download'} %>
-            </div>
-          </td>
-
-          <td style="text-align:right">
-            <%= raw(human_readable_bytes_html(file[2])) %>
-          </td>
-
-        </tr>
-        <% end; end %>
-      </tbody>
-    </table>
-  </div>
-  <div id="provenance" class="tab-pane fade">
-    <table class="topalign table table-bordered">
-      <thead>
-      </thead>
-      <tbody>
-
-       <% @provenance.reverse.each do |p| %>
-       <% j = p[:job] %>
-
-       <% if j %>
-
-       <tr class="job">
-         <td style="padding-bottom: 3em">
-            <table class="table" style="margin-bottom: 0; background: #f0f0ff">
-             <% j.script_parameters.each do |k,v| %>
-              <tr>
-                <td style="width: 20%">
-                  <%= k.to_s %>
-                </td><td style="width: 60%">
-                 <% if v and @output2job.has_key? v %>
-                 <tt class="label colorseries-10-<%= @output2colorindex[v] %>"><%= link_to_if_arvados_object v %></tt>
-                  <% else %>
-                 <span class="deemphasize"><%= link_to_if_arvados_object v %></span>
-                  <% end %>
-                </td><td style="text-align: center; width: 20%">
-                  <% if v
-                       if @protected[v]
-                         labelclass = 'success'
-                         labeltext = 'keep'
-                       else
-                         labelclass = @output2job.has_key?(v) ? 'warning' : 'danger'
-                         labeltext = 'cache'
-                       end %>
-
-                 <tt class="label label-<%= labelclass %>"><%= labeltext %></tt>
-                  <% end %>
-                </td>
-              </tr>
-             <% end %>
-            </table>
-            <div style="text-align: center">
-              &darr;
-              <br />
-             <span class="label"><%= j.script %><br /><tt><%= link_to_if j.script_version.match(/[0-9a-f]{40}/), j.script_version, "https://arvados.org/projects/arvados/repository/revisions/#{j.script_version}/entry/crunch_scripts/#{j.script}" if j.script_version %></tt></span>
-              <br />
-              &darr;
-              <br />
-             <tt class="label colorseries-10-<%= @output2colorindex[p[:output]] %>"><%= link_to_if_arvados_object p[:output] %></tt>
-            </div>
-         </td>
-          <td>
-           <tt><span class="deemphasize">job:</span><br /><%= link_to_if_arvados_object j %><br /><span class="deemphasize"><%= j.submit_id %></span></tt>
-          </td>
-       </tr>
-
-       <% else %>
-       <tr>
-         <td>
-           <span class="label label-danger">lookup fail</span>
-           <br />
-           <tt class="deemphasize"><%= p[:target] %></tt>
-         </td><td colspan="5">
-         </td>
-       </tr>
-       <% end %>
-
-       <% end %>
-
-      </tbody>
-    </table>
-  </div>
-  <div id="provenance2" class="tab-pane fade">
-<script>
-    $(function() {
-      $('#prov-tab').on('shown', function() { provenance_sizing_fixup("provenance_graph", "provenance_svg"); });
-    })
-</script>
-   <%= render partial: 'application/svg_div', locals: {
-         divId: "provenance_graph", 
-         svgId: "provenance_svg", 
-         svg: @prov_svg } %>
-  </div>
-  <div id="jobs" class="tab-pane fade">
-    <table class="topalign table table-bordered">
-      <thead>
-       <tr class="contain-align-left">
-         <th>
-           job
-         </th><th>
-           version
-         </th><th>
-           status
-         </th><th>
-           start
-         </th><th>
-           finish
-         </th><th>
-           clock time
-         </th>
-       </tr>
-      </thead>
-      <tbody>
-
-       <% @provenance.reverse.each do |p| %>
-       <% j = p[:job] %>
-
-       <% if j %>
-
-       <tr class="job">
-         <td>
-           <tt><%= j.uuid %></tt>
-           <br />
-           <tt class="deemphasize"><%= j.submit_id %></tt>
-         </td><td>
-           <%= j.script_version %>
-         </td><td>
-            <span class="label <%= if j.success then 'label-success'; elsif j.running then 'label-primary'; else 'label-warning'; end %>">
-             <%= j.success || j.running ? 'ok' : 'failed' %>
-            </span>
-         </td><td>
-           <%= j.started_at %>
-         </td><td>
-           <%= j.finished_at %>
-         </td><td>
-           <% if j.started_at and j.finished_at %>
-           <%= raw(distance_of_time_in_words(j.started_at, j.finished_at).sub('about ','~').sub(' ','&nbsp;')) %>
-           <% elsif j.started_at and j.running %>
-           <%= raw(distance_of_time_in_words(j.started_at, Time.now).sub('about ','~').sub(' ','&nbsp;')) %> (running)
-           <% end %>
-         </td>
-       </tr>
-
-       <% else %>
-       <tr>
-         <td>
-           <span class="label label-danger">lookup fail</span>
-           <br />
-           <tt class="deemphasize"><%= p[:target] %></tt>
-         </td><td colspan="4">
-         </td>
-       </tr>
-       <% end %>
-
-       <% end %>
-
-      </tbody>
-    </table>
-  </div>
-  <div id="sourcedata" class="tab-pane fade">
-    <table class="table table-bordered table-striped">
-      <thead>
-       <tr class="contain-align-left">
-         <th>
-           collection
-         </th><th class="data-size">
-           data size
-         </th><th>
-           storage
-         </th><th>
-           origin
-         </th>
-       </tr>
-      </thead>
-      <tbody>
-
-       <% @sourcedata.values.each do |sourcedata| %>
-
-       <tr class="collection">
-         <td>
-           <tt class="label"><%= sourcedata[:uuid] %></tt>
-         </td><td class="data-size">
-           <%= raw(human_readable_bytes_html(sourcedata[:collection].data_size)) if sourcedata[:collection] and sourcedata[:collection].data_size %>
-         </td><td>
-           <% if @protected[sourcedata[:uuid]] %>
-           <span class="label label-success">keep</span>
-           <% else %>
-           <span class="label label-danger">cache</span>
-           <% end %>
-         </td><td>
-           <% if sourcedata[:data_origins] %>
-           <% sourcedata[:data_origins].each do |data_origin| %>
-           <span class="deemphasize"><%= data_origin[0] %></span>
-           <%= data_origin[2] %>
-           <br />
-           <% end %>
-           <% end %>
-         </td>
-       </tr>
-
-       <% end %>
-
-      </tbody>
-    </table>
-  </div>
-  <div id="owner-groups-resources" class="tab-pane fade">
-    <%= render :partial => 'application/arvados_object' %>
-  </div>
-</div>
-</div>
index 8739c8dc82f2efaf55d67989a667b914076488a7..79a1038ec3a63d75bd92cf721026b7f901342539 100644 (file)
 <div class="row" style="padding: .5em">
 </div>
 <% end %>
+
+
+  <div style="text-align: center">
+    <span class="pipeline_color_legend" style="background: #88ff88">This pipeline</span> 
+    <span class="pipeline_color_legend" style="background: #8888ff">Comparison pipeline</span>
+    <span class="pipeline_color_legend" style="background: #88ffff">Shared by both pipelines</span>
+  </div>
+
+   <%= render partial: 'application/svg_div', locals: {
+         divId: "provenance_graph", 
+         svgId: "provenance_svg", 
+         svg: @prov_svg } %>
index f43ab6adeca9e88230cae54d7d86bea2dc26f264..34a22aa809cb8d794056c690ac39eabcf1ad9f8f 100644 (file)
@@ -144,7 +144,7 @@ class ApplicationController < ActionController::Base
             conditions << nil
           elsif value.is_a? Array
             if value[0] == 'contains' and value.length == 2
-              conditions[0] << "and #{table_name}.#{attr} ilike ?"
+              conditions[0] << " and #{table_name}.#{attr} like ?"
               conditions << "%#{value[1]}%"
             else
               conditions[0] << " and #{table_name}.#{attr} in (?)"
index 294e092f6cf7e253994c624e3ff476c265b09db3..feed5cecb0ee4b0fb99275e02934f2a0c56e509b 100644 (file)
@@ -139,6 +139,65 @@ class Arvados::V1::CollectionsController < ApplicationController
     render json: visited
   end
 
+  def generate_used_by_edges(visited, uuid)
+    m = collection_uuid(uuid)
+    uuid = m if m
+
+    if not uuid or uuid.empty? or visited[uuid]
+      return ""
+    end
+
+    logger.debug "visiting #{uuid}"
+
+    if m  
+      # uuid is a collection
+      Collection.readable_by(current_user).where(uuid: uuid).each do |c|
+        visited[uuid] = c.as_api_response
+        visited[uuid][:files] = []
+        c.files.each do |f|
+          visited[uuid][:files] << f
+        end
+      end
+
+      if uuid == "d41d8cd98f00b204e9800998ecf8427e+0"
+        # special case for empty collection
+        return
+      end
+
+      Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
+        generate_used_by_edges(visited, job.uuid)
+      end
+      
+    else
+      # uuid is something else
+      rsc = ArvadosModel::resource_class_for_uuid uuid
+      if rsc == Job
+        Job.readable_by(current_user).where(uuid: uuid).each do |job|
+          visited[uuid] = job.as_api_response
+          generate_used_by_edges(visited, job.output)
+        end
+      elsif rsc != nil
+        rsc.where(uuid: uuid).each do |r|
+          visited[uuid] = r.as_api_response
+        end
+      end
+    end
+
+    Link.readable_by(current_user).
+      where(tail_uuid: uuid, link_class: "provenance").
+      each do |link|
+      visited[link.uuid] = link.as_api_response
+      generate_used_by_edges(visited, link.head_uuid)
+    end
+
+    #puts "finished #{uuid}"
+  end
+
+  def used_by
+    visited = {}
+    generate_used_by_edges(visited, @object[:uuid])
+    render json: visited
+  end
 
   protected
   def find_object_by_uuid
index e837e38617c3351976412ee5d0add87cf91366ec..dffae7fad1725768af1e00b1fbb37ac60da9f650 100644 (file)
@@ -97,6 +97,7 @@ Server::Application.routes.draw do
       get '/user_agreements/signatures' => 'user_agreements#signatures'
       post '/user_agreements/sign' => 'user_agreements#sign'
       get '/collections/:uuid/provenance' => 'collections#provenance'
+      get '/collections/:uuid/used_by' => 'collections#used_by'
       resources :collections
       resources :links
       resources :nodes