page.
refs #1978
form.small-form-margin {
margin-bottom: 2px;
}
+.nowrap {
+ white-space: nowrap;
+}
class PipelineInstancesController < ApplicationController
skip_before_filter :find_object_by_uuid, only: :compare
before_filter :find_objects_by_uuid, only: :compare
+ include PipelineInstancesHelper
def compare
+ @rows = [] # each is {name: S, components: [...]}
+
+ # Build a table: x=pipeline y=component
+ @objects.each_with_index do |pi, pi_index|
+ pipeline_jobs(pi).each do |component|
+ # Find a cell with the same name as this component but no
+ # entry for this pipeline
+ target_row = nil
+ @rows.each_with_index do |row, row_index|
+ if row[:name] == component[:name] and !row[:components][pi_index]
+ target_row = row
+ end
+ end
+ if !target_row
+ target_row = {name: component[:name], components: []}
+ @rows << target_row
+ end
+ target_row[:components][pi_index] = component
+ end
+ end
+
+ @rows.each do |row|
+ # Build a "normal" pseudo-component for this row by picking the
+ # most common value for each attribute. If all values are
+ # equally common, there is no "normal".
+ normal = {} # attr => most common value
+ highscore = {} # attr => how common "normal" is
+ score = {} # attr => { value => how common }
+ row[:components].each do |pj|
+ pj.each do |k,v|
+ vstr = for_comparison v
+ score[k] ||= {}
+ score[k][vstr] = (score[k][vstr.to_s] || 0) + 1
+ highscore[k] ||= 0
+ if score[k][vstr] == highscore[k]
+ # tie for first place = no "normal"
+ normal.delete k
+ elsif score[k][vstr] == highscore[k] + 1
+ # more pipelines have v than anything else
+ highscore[k] = score[k][vstr]
+ normal[k] = vstr
+ end
+ end
+ end
+
+ # Add a hash in component[:is_normal]: { attr => is_the_value_normal? }
+ row[:components].each do |pj|
+ pj[:is_normal] = {}
+ pj.each do |k,v|
+ pj[:is_normal][k] = (normal.has_key?(k) && normal[k] == for_comparison(v))
+ end
+ end
+ end
end
protected
+ def for_comparison v
+ if v.is_a? Hash or v.is_a? Array
+ v.to_json
+ else
+ v.to_s
+ end
+ end
+
def find_objects_by_uuid
- @objects = model_class.where(uuid: params[:uuid])
+ @objects = model_class.where(uuid: params[:uuid].split('/'))
end
+
end
link_name = "#{resource_class.to_s}: #{link_name}"
end
end
+ style_opts[:class] = (style_opts[:class] || '') + ' nowrap'
link_to link_name, { controller: resource_class.to_s.underscore.pluralize, action: 'show', id: link_uuid }, style_opts
else
attrvalue
module PipelineInstancesHelper
- def pipeline_jobs
- if @object.components[:steps].is_a? Array
- pipeline_jobs_oldschool
- elsif @object.components.is_a? Hash
- pipeline_jobs_newschool
+ def pipeline_jobs object=nil
+ object ||= @object
+ if object.components[:steps].is_a? Array
+ pipeline_jobs_oldschool object
+ elsif object.components.is_a? Hash
+ pipeline_jobs_newschool object
end
end
+ def render_pipeline_jobs
+ pipeline_jobs.collect do |pj|
+ render_pipeline_job pj
+ end
+ end
+
+ def render_pipeline_job pj
+ if pj[:percent_done]
+ pj[:progress_bar] = raw("<div class=\"progress\" style=\"width:100px\"><div class=\"bar bar-success\" style=\"width:#{pj[:percent_done]}%\"></div><div class=\"bar\" style=\"width:#{pj[:percent_running]}%\"></div></div>")
+ elsif pj[:progress]
+ raw("<div class=\"progress\" style=\"width:100px\"><div class=\"bar\" style=\"width:#{pj[:progress]*100}%\"></div></div>")
+ end
+ pj[:output_link] = link_to_if_arvados_object pj[:output]
+ pj[:job_link] = link_to_if_arvados_object pj[:job][:uuid]
+ pj
+ end
+
protected
- def pipeline_jobs_newschool
+ def pipeline_jobs_newschool object
ret = []
i = -1
- @object.components.each do |cname, c|
+ object.components.each do |cname, c|
i += 1
pj = {index: i, name: cname}
pj[:job] = c[:job].is_a?(Hash) ? c[:job] : {}
end
end
pj[:job_id] = pj[:job][:uuid]
- pj[:job_link] = link_to_if_arvados_object pj[:job][:uuid]
+ pj[:script] = pj[:job][:script]
pj[:script_version] = pj[:job][:script_version]
pj[:output] = pj[:job][:output]
pj[:finished_at] = (Time.parse(pj[:job][:finished_at]) rescue nil)
- pj[:progress_bar] = raw("<div class=\"progress\" style=\"width:100px\"><div class=\"bar bar-success\" style=\"width:#{pj[:percent_done]}%\"></div><div class=\"bar\" style=\"width:#{pj[:percent_running]}%\"></div></div>")
- pj[:output_link] = link_to_if_arvados_object pj[:output]
ret << pj
end
ret
end
- def pipeline_jobs_oldschool
+ def pipeline_jobs_oldschool object
ret = []
- @object.components[:steps].each_with_index do |step, i|
+ object.components[:steps].each_with_index do |step, i|
pj = {index: i, name: step[:name]}
if step[:complete] and step[:complete] != 0
if step[:output_data_locator]
pj[:script_version] = (step[:warehousejob][:revision] rescue nil)
pj[:output] = step[:output_data_locator]
pj[:finished_at] = (Time.parse(step[:warehousejob][:finishtime]) rescue nil)
- pj[:progress_bar] = raw("<div class=\"progress\" style=\"width:100px\"><div class=\"bar\" style=\"width:#{pj[:progress]*100}%\"></div></div>")
- pj[:output_link] = link_to_if_arvados_object pj[:output]
ret << pj
end
ret
+<% object ||= @object %>
<% if attrvalue.is_a? Hash then attrvalue.each do |infokey, infocontent| %>
<tr class="info">
<td><%= attr %>[<%= infokey %>]</td>
<td>
- <%= render partial: 'application/arvados_attr_value', locals: { obj: @object, attr: nil, attrvalue: infocontent } %>
+ <%= render partial: 'application/arvados_attr_value', locals: { obj: object, attr: nil, attrvalue: infocontent } %>
</td>
</tr>
<% end %>
<tr class="<%= 'info' if %w(uuid owner_uuid created_at modified_at modified_by_user_uuid modified_by_client_uuid updated_at).index(attr.to_s).nil? %>">
<td><%= attr %></td>
<td>
- <%= render partial: 'application/arvados_attr_value', locals: { obj: @object, attr: attr, attrvalue: attrvalue } %>
+ <%= render partial: 'application/arvados_attr_value', locals: { obj: object, attr: attr, attrvalue: attrvalue } %>
</td>
</tr>
<% end %>
-<table class="table table-condensed table-hover topalign">
- <thead>
- </thead>
- <tbody>
- <tr>
- <% @objects.each do |object| %>
- <td>
+<% content_for :css do %>
+.notnormal {
+ background: #ffffaa;
+}
+.headrow div {
+ padding-top: .5em;
+ padding-bottom: .5em;
+}
+.headrow:first-child {
+ border-bottom: 1px solid black;
+}
+<% end %>
- <table class="table table-condensed table-hover topalign">
- <thead>
- </thead>
- <tbody>
- <% object.attributes_for_display.each do |attr, attrvalue| %>
- <% if attr == 'components' and attrvalue.is_a? Hash %>
+<ul class="breadcrumb">
+ <li><%= link_to 'Pipeline instances', pipeline_instances_path %> <span class="divider">/</span></li>
+ <li class="active">Compare</li>
+</ul>
- <tr class="info"><td><%= attr %></td><td>
- <table class="table">
- <% pipeline_jobs.each do |pj| %>
- <tr><% %w(index name result job_link script script_version progress_detail progress_bar output_link).each do |key| %>
- <td>
- <% if key == 'script_version' %>
- <%= pj[key.to_sym][0..6] rescue '' %>
- <% else %>
- <%= pj[key.to_sym] %>
- <% end %>
- </td>
- <% end %>
- </tr>
- <% end %>
- </table>
- </td></tr>
+<% pi_span = [(10.0/(@objects.count)).floor,1].max %>
+<div>
+<% [['Instance', :uuid], ['Template', :pipeline_template_uuid]].each do |label, attr| %>
+<div class="row headrow">
+ <div class="span2">
+ <%# label %>
+ </div>
+ <% @objects.each do |object| %>
+ <div class="span<%= pi_span %>">
+ <%= render partial: 'application/arvados_attr_value', locals: { obj: object, attr: attr, attrvalue: object.send(attr.to_sym) } %>
+ </div>
+ <% end %>
+</div>
+<% end %>
+</div>
- <% else %>
- <%= render partial: 'application/arvados_object_attr', locals: { attr: attr, attrvalue: attrvalue } %>
- <% end %>
- <% end %>
- </tbody>
- </table>
+<% @rows.each do |row| %>
+<div class="row">
+ <div class="span2">
+ <%= row[:name] %>
+ </div>
+ <% @objects.each_with_index do |_, x| %>
+ <div class="span<%= pi_span %>">
+ <div class="row">
- </td>
- <% end %>
- </tr>
- </tbody>
-</table>
+ <% if row[:components][x] %>
+ <% pj = render_pipeline_job row[:components][x] %>
+
+ <div class="span3">
+ <%= link_to_if_arvados_object pj[:job_id], {friendly_name: true, with_class_name: true}, {class: 'deemphasize'} %>
+ </div>
+
+ <% %w(script script_version script_parameters output).each do |key| %>
+ <div class="span3">
+ <% unless key=='output' and pj[:result] != 'complete' %>
+ <% val = pj[key.to_sym] || pj[:job].andand[key.to_sym] %>
+ <% link_name = case
+ when key == 'script_version' && val.match(/^[0-9a-f]{7,}$/)
+ val = val[0..7] # TODO: leave val alone, make link_to handle git commits
+ when key == 'output'
+ val.sub! /\+K.*$/, ''
+ val[0..12]
+ when key == 'script_parameters'
+ val = val.keys.sort.join(', ')
+ end
+ %>
+ <span class="deemphasize"><%= key %>:</span> <span class="<%= 'notnormal' if !pj[:is_normal][key.to_sym] %>"><%= link_to_if_arvados_object val, {friendly_name: true, link_text: link_name} %></span>
+ <% end %>
+ </div>
+ <% end %>
+ <% else %>
+ None
+ <% end %>
+ </div>
+ </div>
+ <% end %>
+</div>
+<div class="row" style="padding: .5em">
+</div>
+<% end %>
<tr class="info"><td><%= attr %></td><td>
<table class="table">
- <% pipeline_jobs.each do |pj| %>
+ <% render_pipeline_jobs.each do |pj| %>
<tr><% %w(index name result job_link script script_version progress_detail progress_bar output_link).each do |key| %>
<td>
<% if key == 'script_version' %>
resources :groups
resources :specimens
resources :pipeline_templates
- resources :pipeline_instances do
- get 'compare/*uuid' => 'pipeline_instances#compare'
- end
+ resources :pipeline_instances
+ get '/pipeline_instances/compare/*uuid' => 'pipeline_instances#compare'
resources :links
match '/collections/graph' => 'collections#graph'
resources :collections