[ARVADOS] created: 834dff7e81a3b9b616b35510a2e1b15abc8b42c7

git at public.curoverse.com git at public.curoverse.com
Sun May 18 00:11:09 EDT 2014


        at  834dff7e81a3b9b616b35510a2e1b15abc8b42c7 (commit)


commit 834dff7e81a3b9b616b35510a2e1b15abc8b42c7
Author: Tom Clegg <tom at curoverse.com>
Date:   Sun May 18 00:10:41 2014 -0400

    2760: Replace dashboard tables with news feed. More ability to link via names rather than just bare objects.

diff --git a/apps/workbench/app/assets/stylesheets/application.css.scss b/apps/workbench/app/assets/stylesheets/application.css.scss
index 51c96d7..ef9366a 100644
--- a/apps/workbench/app/assets/stylesheets/application.css.scss
+++ b/apps/workbench/app/assets/stylesheets/application.css.scss
@@ -105,6 +105,10 @@ form.small-form-margin {
     overflow: auto;
 }
 
+.table-condensed div.progress {
+    margin-bottom: 0;
+}
+
 .inline-progress-container div.progress {
     margin-bottom: 0;
 }
diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb
index 78df220..f508aa2 100644
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@ -1,5 +1,6 @@
 class ApplicationController < ActionController::Base
   include ArvadosApiClientHelper
+  helper PipelineInstancesHelper
 
   respond_to :html, :json, :js
   protect_from_forgery
@@ -66,28 +67,69 @@ class ApplicationController < ActionController::Base
     self.render_error status: 404
   end
 
+  def load_name_links_for uuids
+    @name_links_cache ||= {}
+    uuids.each do |uuid|
+      @name_links_cache[uuid] = []
+    end
+    offset = 0
+    while true
+      name_links = Link.
+        filter([['link_class', '=', 'name'],
+                ['head_uuid', 'in', uuids]]).
+        offset(offset).
+        order(['uuid'])
+      name_links.each do |link|
+        @name_links_cache[link.head_uuid] << link
+      end
+      offset += name_links.result_limit
+      break if offset >= name_links.items_available
+    end
+  end
+
+  helper_method :name_links_for
   def name_links_for object=nil
     if !@name_links_cache or !@name_links_cache[object.uuid]
       @name_links_cache ||= {}
       uuids = @objects.collect(&:uuid) + [object.uuid] - @name_links_cache.keys
-      uuids.each do |uuid|
-        @name_links_cache[uuid] = []
-      end
-      offset = 0
-      while true
-        name_links = Link.
-          filter([['link_class', '=', 'name'],
-                  ['head_uuid', 'in', uuids]]).
-          offset(offset).
-          order(['uuid'])
-        name_links.each do |link|
-          @name_links_cache[link.head_uuid] << link
+      load_name_links_for uuids
+    end
+    @name_links_cache[object.uuid] || []
+  end
+
+  helper_method :feed_items
+  def feed_items
+    return @feed_items if @feed_items
+    @feed_items = []
+    named_uuids = Link.filter([['link_class','=','name']]).
+      order(['modified_at', 'desc']).
+      collect(&:head_uuid)
+    named_uuids.
+      collect { |x| ArvadosBase::resource_class_for_uuid(x) }.
+      uniq.
+      each do |klass|
+      @feed_items += klass.
+        order(['modified_at','desc']).
+        filter([['uuid','in',named_uuids]])
+    end
+    # Don't show jobs separately if they already appear in pipelines
+    jobs_in_pipelines = {}
+    @feed_items.each do |item|
+      if item.class == PipelineInstance
+        item.components.each do |cname, component|
+          if component[:job]
+            jobs_in_pipelines[component[:job][:uuid]] = true
+          end
         end
-        offset += name_links.result_limit
-        break if offset >= name_links.items_available
       end
     end
-    @name_links_cache[object.uuid] || []
+    @feed_items.reject! do |item|
+      jobs_in_pipelines[item.uuid]
+    end
+    @feed_items.sort_by!(&:created_at)
+    @feed_items = @feed_items[0..9]
+    load_name_links_for @feed_items.collect(&:uuid)
+    @feed_items
   end
 
   def index
diff --git a/apps/workbench/app/controllers/links_controller.rb b/apps/workbench/app/controllers/links_controller.rb
index 78529c1..5fbd8ed 100644
--- a/apps/workbench/app/controllers/links_controller.rb
+++ b/apps/workbench/app/controllers/links_controller.rb
@@ -1,2 +1,17 @@
 class LinksController < ApplicationController
+  def show
+    if @object and request.method == 'GET' and action_name == 'show'
+      if @object.link_class == 'name' and @object.head_uuid
+        # Rather than show a name link, show the named object
+        klass = ArvadosBase.
+          resource_class_for_uuid(@object.head_uuid) rescue nil
+        if klass and klass != Link
+          return redirect_to(action: :show,
+                             controller: klass.to_s.tableize,
+                             id: @object.uuid)
+        end
+      end
+    end
+    super
+  end
 end
diff --git a/apps/workbench/app/helpers/application_helper.rb b/apps/workbench/app/helpers/application_helper.rb
index dbb05d6..f24cdb7 100644
--- a/apps/workbench/app/helpers/application_helper.rb
+++ b/apps/workbench/app/helpers/application_helper.rb
@@ -15,6 +15,24 @@ module ApplicationHelper
     raw RedCloth.new(markup).to_html
   end
 
+  def human_readable_time_html t
+    begin
+      t = Time.parse(t) unless t.is_a? Time
+    rescue
+      return ''
+    end
+    now = Time.now
+    delta = (now - t).abs
+    if delta < 12.hours
+      t.strftime('%I:%M%P').downcase.sub(/^0/,'')
+    elsif (delta < 2.months and now>t) or t.strftime('%Y')==now.strftime('%Y')
+      # recent, or this year (hopefully unambiguous in context)
+      t.strftime('%b %d').sub(/ 0/,' ')
+    else
+      t.strftime('%b %d, %Y').sub(/ 0/,' ')
+    end
+  end
+
   def human_readable_bytes_html(n)
     return h(n) unless n.is_a? Fixnum
 
@@ -83,7 +101,9 @@ module ApplicationHelper
       if !link_name
         link_name = link_uuid
 
-        if opts[:friendly_name]
+        if opts[:name_link]
+          link_name = opts[:name_link].name
+        elsif opts[:friendly_name]
           if attrvalue.respond_to? :friendly_link_name
             link_name = attrvalue.friendly_link_name
           else
@@ -98,7 +118,9 @@ module ApplicationHelper
         if opts[:with_class_name]
           link_name = "#{resource_class.to_s}: #{link_name}"
         end
-        if !opts[:no_tags] and resource_class == Collection
+        if !opts[:name_link] and
+            !opts[:no_tags] and
+            resource_class == Collection
           Link.where(head_uuid: link_uuid, link_class: ["tag", "identifier"]).each do |tag|
             link_name += ' <span class="label label-info">' + html_escape(tag.name) + '</span>'
           end
diff --git a/apps/workbench/app/helpers/pipeline_instances_helper.rb b/apps/workbench/app/helpers/pipeline_instances_helper.rb
index e3a2b62..8c5100c 100644
--- a/apps/workbench/app/helpers/pipeline_instances_helper.rb
+++ b/apps/workbench/app/helpers/pipeline_instances_helper.rb
@@ -9,8 +9,8 @@ module PipelineInstancesHelper
     end
   end
 
-  def render_pipeline_jobs
-    pipeline_jobs.collect do |pj|
+  def render_pipeline_jobs object=nil
+    pipeline_jobs(object).collect do |pj|
       render_pipeline_job pj
     end
   end
diff --git a/apps/workbench/app/views/application/_content.html.erb b/apps/workbench/app/views/application/_content.html.erb
index 8a0624b..abb720c 100644
--- a/apps/workbench/app/views/application/_content.html.erb
+++ b/apps/workbench/app/views/application/_content.html.erb
@@ -1,3 +1,12 @@
+<% if @object %>
+  <% content_for :content_top do %>
+    <h3>
+      <%= render_editable_attribute @name_link, 'name', nil, { 'data-emptytext' => "Unnamed #{@object.andand.class_for_display}" } %>
+    </h3>
+    <%= render partial: 'tags_for_object', locals: {object: @object} %>
+  <% end %>
+<% end %>
+
 <% content_for :tab_panes do %>
 
 <% comparable = controller.respond_to? :compare %>
diff --git a/apps/workbench/app/views/application/_feed_item.html.erb b/apps/workbench/app/views/application/_feed_item.html.erb
new file mode 100644
index 0000000..8490401
--- /dev/null
+++ b/apps/workbench/app/views/application/_feed_item.html.erb
@@ -0,0 +1 @@
+<%= name_links_for(object).first.andand.name || object.friendly_link_name %>
diff --git a/apps/workbench/app/views/application/_feed_item_div.html.erb b/apps/workbench/app/views/application/_feed_item_div.html.erb
new file mode 100644
index 0000000..40bf05f
--- /dev/null
+++ b/apps/workbench/app/views/application/_feed_item_div.html.erb
@@ -0,0 +1,25 @@
+<% name_link = name_links_for(object).first %>
+<div class="panel panel-default arv-feed-item" data-object-uuid="<%= object.uuid %>">
+  <div class="panel-heading">
+    <div class="pull-right">
+      <%= human_readable_time_html (name_link || object).modified_at %>
+      <%= render partial: 'show_object_button', locals: {object: (name_link || object), size: 'xs'} %>
+    </div>
+    <%= name_link ? name_link.name : object.class.to_s.underscore.humanize %>
+    <div class="deemphasize">
+      <% if object.owner_uuid != current_user.uuid %>
+        <%= link_to_if_arvados_object object.owner_uuid, friendly_name: true %>
+        →
+      <% end %>
+      <%= object.class_for_display.underscore.humanize.downcase %>
+    </div>
+  </div>
+  <div class="panel-body">
+    <%= begin
+        render(partial: "#{object.class.to_s.tableize}/feed_item",
+        locals: {object: object})
+        rescue ActionView::MissingTemplate
+        render partial: "application/feed_item", locals: {object: object}
+        end %>
+  </div>
+</div>
diff --git a/apps/workbench/app/views/application/_tags_for_object.html.erb b/apps/workbench/app/views/application/_tags_for_object.html.erb
new file mode 100644
index 0000000..1743aa7
--- /dev/null
+++ b/apps/workbench/app/views/application/_tags_for_object.html.erb
@@ -0,0 +1,10 @@
+<% tags = Link.where(head_uuid: object.uuid, link_class: ["tag", "identifier"]) %>
+<% if tags.any? %>
+  <div style="margin-bottom:.5em;">
+    <% tags.each do |tag| %>
+      <span class="label label-info">
+        <%= tag.name %>
+      </span>
+    <% end %>
+  </div>
+<% end %>
diff --git a/apps/workbench/app/views/collections/_feed_item.html.erb b/apps/workbench/app/views/collections/_feed_item.html.erb
new file mode 100644
index 0000000..8472796
--- /dev/null
+++ b/apps/workbench/app/views/collections/_feed_item.html.erb
@@ -0,0 +1,36 @@
+<table class="table table-condensed table-justforlayout">
+  <colgroup>
+    <col style="width: 70%" />
+    <col style="width: 30%" />
+  </colgroup>
+  <thead>
+  </thead>
+  <tbody>
+    <% maxfiles = 5 %>
+    <% object.files[0..(maxfiles-1)].each do |dirname, basename, filesize| %>
+    <tr>
+      <td>
+        <%= dirname == '.' ? '' : '.../' %><%= basename %>
+      </td><td>
+        <%= human_readable_bytes_html filesize %>
+      </td>
+    </tr>
+    <% end %>
+    <% if object.files.count > maxfiles %>
+    <tr>
+      <td>⋮</td><td>⋮</td>
+    </tr>
+    <% end %>
+  </tbody>
+  <tfoot>
+    <% if object.files.count > 1 %>
+    <tr>
+      <td>
+        <%= object.files.count %> files
+      </td><td>
+        <%= human_readable_bytes_html object.total_bytes %>
+      </td>
+    </tr>
+    <% end %>
+  </tfoot>
+</table>
diff --git a/apps/workbench/app/views/jobs/_feed_item.html.erb b/apps/workbench/app/views/jobs/_feed_item.html.erb
new file mode 100644
index 0000000..f661d10
--- /dev/null
+++ b/apps/workbench/app/views/jobs/_feed_item.html.erb
@@ -0,0 +1,20 @@
+<table class="table table-condensed table-justforlayout">
+  <colgroup>
+    <col style="width: 30%" />
+    <col style="width: 40%" />
+    <col style="width: 30%" />
+  </colgroup>
+  <thead></thead>
+  <tbody>
+    <tr>
+      <td>
+        <%= object.script %>
+      </td><td>
+        <%= render(partial: 'job_progress', locals: { j: object }) %>
+      </td><td>
+        <%= render(partial: 'job_status_label', locals: { j: object }) %>
+      </td>
+    </tr>
+  </tbody>
+  <tfoot></tfoot>
+</table>
diff --git a/apps/workbench/app/views/layouts/application.html.erb b/apps/workbench/app/views/layouts/application.html.erb
index 2d3c4c0..a6450e3 100644
--- a/apps/workbench/app/views/layouts/application.html.erb
+++ b/apps/workbench/app/views/layouts/application.html.erb
@@ -152,7 +152,7 @@
                   <span class="glyphicon glyphicon-arrow-right"></span>
                 </li>
                 <li>
-                  <%= link_to_if_arvados_object @object, {friendly_name: true}, {data: {object_uuid: @object.andand.uuid, name: 'name'}} %>
+                  <%= link_to_if_arvados_object @name_link, {friendly_name: true}, {data: {object_uuid: @name_link.andand.uuid, name: 'name'}} %>
                 </li>
                 <li style="padding: 14px 0 14px">
                   <%= form_tag do |f| %>
diff --git a/apps/workbench/app/views/pipeline_instances/_feed_item.html.erb b/apps/workbench/app/views/pipeline_instances/_feed_item.html.erb
new file mode 100644
index 0000000..0ea3860
--- /dev/null
+++ b/apps/workbench/app/views/pipeline_instances/_feed_item.html.erb
@@ -0,0 +1,22 @@
+<table class="table table-condensed table-justforlayout">
+  <colgroup>
+    <col style="width: 30%" />
+    <col style="width: 40%" />
+    <col style="width: 30%" />
+  </colgroup>
+  <thead></thead>
+  <tbody>
+    <% render_pipeline_jobs(object).each do |pj| %>
+    <tr>
+      <td>
+        <%= pj[:name] %>
+      </td><td>
+        <%= pj[:progress_bar] %>
+      </td><td>
+        <%= render(partial: 'job_status_label', locals: { :j => pj[:job] }) %>
+      </td>
+    </tr>
+    <% end %>
+  </tbody>
+  <tfoot></tfoot>
+</table>
diff --git a/apps/workbench/app/views/pipeline_instances/_show_components.html.erb b/apps/workbench/app/views/pipeline_instances/_show_components.html.erb
index fc673e1..6ca5488 100644
--- a/apps/workbench/app/views/pipeline_instances/_show_components.html.erb
+++ b/apps/workbench/app/views/pipeline_instances/_show_components.html.erb
@@ -5,9 +5,6 @@
 <% template = PipelineTemplate.find(@object.pipeline_template_uuid) rescue nil %>
 
 <%= content_for :content_top do %>
-  <h2>
-    <%= render_editable_attribute @name_link, 'name', nil, { 'data-emptytext' => 'Unnamed pipeline' } %>
-  </h2>
   <% if template %>
   <h4>
     From template:
diff --git a/apps/workbench/app/views/users/_home.html.erb b/apps/workbench/app/views/users/_home.html.erb
index 5e8b3f8..b7cd7b1 100644
--- a/apps/workbench/app/views/users/_home.html.erb
+++ b/apps/workbench/app/views/users/_home.html.erb
@@ -1,34 +1,9 @@
 <% content_for :breadcrumbs do raw '<!-- -->' end %>
-<% content_for :css do %>
-      .dash-list {
-        padding: 9px 0;
-      }
-      .dash-list>ul>li>a>span {
-      min-width: 1.5em;
-      margin-left: auto;
-      margin-right: auto;
-      }
-      .centerme {
-      margin-left: auto;
-      margin-right: auto;
-      text-align: center;
-      }
-      .bigfatnumber {
-      font-size: 4em;
-      font-weight: bold;
-      }
-      .dax {
-      max-width: 10%;
-      margin-right: 1em;
-      float: left
-      }
-      .daxalert {
-      overflow: hidden;
-      }
-<% end %>
-
-<div class="container-fluid" id="home-tables">
-
-    <%= render :partial => 'tables' %>
 
+<div class="container-fluid">
+  <div class="row">
+    <div class="col-md-8">
+      <%= render partial: 'feed_item_div', collection: feed_items, as: :object %>
+    </div>
+  </div>
 </div>
diff --git a/apps/workbench/app/views/users/_tables.html.erb b/apps/workbench/app/views/users/_tables.html.erb
deleted file mode 100644
index f62bd5d..0000000
--- a/apps/workbench/app/views/users/_tables.html.erb
+++ /dev/null
@@ -1,245 +0,0 @@
-<% if current_user.andand.is_active %>
-  <div>
-    <strong>Recent jobs</strong>
-    <%= link_to '(refresh)', request.fullpath, class: 'refresh', remote: true, method: 'get' %>
-    <%= link_to raw("Show all jobs →"), jobs_path, class: 'pull-right' %>
-    <% if not current_user.andand.is_active or @my_jobs.empty? %>
-      <p>(None)</p>
-    <% else %>
-      <table class="table table-bordered table-condensed table-fixedlayout">
-        <colgroup>
-          <col width="20%" />
-          <col width="20%" />
-          <col width="20%" />
-          <col width="13%" />
-          <col width="13%" />
-          <col width="20%" />
-        </colgroup>
-
-        <tr>
-          <th>Script</th>
-          <th>Output</th>
-          <th>Log</th>
-          <th>Age</th>
-          <th>Status</th>
-          <th>Progress</th>
-        </tr>
-
-        <% @my_jobs[0..6].each do |j| %>
-          <tr data-object-uuid="<%= j.uuid %>">
-            <td>
-              <small>
-                <%= link_to((j.script.andand[0..31] || j.uuid), job_path(j.uuid)) %>
-              </small>
-            </td>
-
-            <td>
-              <small>
-                <% if j.success and j.output %>
-
-                  <a href="<%= collection_path(j.output) %>">
-                    <% Collection.limit(1).where(uuid: j.output).each do |c| %>
-                         <% c.files.each do |file| %>
-                      <%= file[0] == '.' ? file[1] : "#{file[0]}/#{file[1]}" %>
-                    <% end %>
-                <% end %>
-                </a>
-
-        <% end %>
-        </small>
-</td>
-
-<td>
-  <small>
-    <% if j.log %>
-      <% fixup = /([a-f0-9]{32}\+\d+)(\+?.*)/.match(j.log)%>
-      <% Collection.limit(1).where(uuid: fixup[1]).each do |c| %>
-        <% c.files.each do |file| %>
-          <a href="<%= collection_path(j.log) %>/<%= file[1] %>?disposition=inline&size=<%= file[2] %>">Log</a>
-        <% end %>
-      <% end %>
-    <% elsif j.respond_to? :log_buffer and j.log_buffer.is_a? String %>
-      <% buf = j.log_buffer.strip.split("\n").last %>
-      <span title="<%= buf %>"><%= buf %></span>
-    <% end %>
-  </small>
-</td>
-
-<td>
-  <small>
-    <%= raw(distance_of_time_in_words(j.created_at, Time.now).sub('about ','~').sub(' ',' ')) if j.created_at %>
-  </small>
-</td>
-
-<td>
-  <%= render partial: 'job_status_label', locals: {:j => j} %>
-</td>
-<td>
-  <div class="inline-progress-container">
-  <%= render partial: 'job_progress', locals: {:j => j} %>
-  </div>
-</td>
-
-</tr>
-<% end %>
-</table>
-<% end %>
-</div>
-
-<div>
-  <strong>Recent pipeline instances</strong>
-  <%= link_to '(refresh)', request.fullpath, class: 'refresh', remote: true, method: 'get' %>
-  <%= link_to raw("Show all pipeline instances →"), pipeline_instances_path, class: 'pull-right' %>
-  <% if not current_user.andand.is_active or @my_pipelines.empty? %>
-    <p>(None)</p>
-  <% else %>
-    <table class="table table-bordered table-condensed table-fixedlayout">
-      <colgroup>
-        <col width="30%" />
-        <col width="30%" />
-        <col width="13%" />
-        <col width="13%" />
-        <col width="20%" />
-      </colgroup>
-
-      <tr>
-        <th>Instance</th>
-        <th>Template</th>
-        <th>Age</th>
-        <th>Status</th>
-        <th>Progress</th>
-      </tr>
-
-      <% @my_pipelines[0..6].each do |p| %>
-        <tr data-object-uuid="<%= p.uuid %>">
-          <td>
-            <small>
-              <%= link_to_if_arvados_object p.uuid, friendly_name: true %>
-            </small>
-          </td>
-
-          <td>
-            <small>
-              <%= link_to_if_arvados_object p.pipeline_template_uuid, friendly_name: true %>
-            </small>
-          </td>
-
-          <td>
-            <small>
-              <%= raw(distance_of_time_in_words(p.created_at, Time.now).sub('about ','~').sub(' ',' ')) if p.created_at %>
-            </small>
-          </td>
-
-          <td>
-            <%= render partial: 'pipeline_status_label', locals: {:p => p} %>
-          </td>
-
-          <td>
-            <div class="inline-progress-container">
-              <%= render partial: 'pipeline_progress', locals: {:p => p} %>
-            </div>
-          </td>
-        </tr>
-      <% end %>
-    </table>
-  <% end %>
-</div>
-
-<div>
-  <strong>Recent collections</strong>
-  <%= link_to '(refresh)', request.fullpath, class: 'refresh', remote: true, method: 'get' %>
-  <%= link_to raw("Show all collections →"), collections_path, class: 'pull-right' %>
-  <div class="pull-right" style="padding-right: 1em; width: 30%;">
-    <%= form_tag collections_path,
-          method: 'get',
-          class: 'form-search small-form-margin' do %>
-    <div class="input-group input-group-sm">
-      <%= text_field_tag :search, params[:search], class: 'form-control', placeholder: 'Search' %>
-      <span class="input-group-btn">
-        <%= button_tag(class: 'btn btn-info') do %>
-        <span class="glyphicon glyphicon-search"></span>
-        <% end %>
-      </span>
-    </div>
-    <% end %>
-  </div>
-  <% if not current_user.andand.is_active or @my_collections.empty? %>
-    <p>(None)</p>
-  <% else %>
-    <table class="table table-bordered table-condensed table-fixedlayout">
-      <colgroup>
-        <col width="46%" />
-        <col width="32%" />
-        <col width="10%" />
-        <col width="12%" />
-      </colgroup>
-
-      <tr>
-        <th>Contents</th>
-        <th>Tags</th>
-        <th>Age</th>
-        <th>Storage</th>
-      </tr>
-
-      <% @my_collections[0..6].each do |c| %>
-        <tr data-object-uuid="<%= c.uuid %>">
-          <td>
-            <small>
-              <a href="<%= collection_path(c.uuid) %>">
-                <% c.files.each do |file| %>
-                  <%= file[0] == '.' ? file[1] : "#{file[0]}/#{file[1]}" %>
-                <% end %>
-              </a>
-            </small>
-          </td>
-          <td>
-            <% if @my_tag_links[c.uuid] %>
-            <small>
-              <%= @my_tag_links[c.uuid].collect(&:name).join(", ") %>
-            </small>
-            <% end %>
-          </td>
-          <td>
-            <small>
-              <%= raw(distance_of_time_in_words(c.created_at, Time.now).sub('about ','~').sub(' ',' ')) if c.created_at %>
-            </small>
-          </td>
-          <td>
-            <%= render partial: 'collections/toggle_persist', locals: { uuid: c.uuid, current_state: @persist_state[c.uuid] } %>
-          </td>
-        </tr>
-      <% end %>
-    </table>
-  <% end %>
-</div>
-
-<% else %>
-
-  <div class="row-fluid">
-    <div class="col-sm-4">
-      <%= image_tag "dax.png", style: "max-width:100%" %>
-    </div>
-    <div class="col-sm-8">
-      <h2>Welcome to Arvados, <%= current_user.first_name %>!</h2>
-      <div class="well">
-        <p>
-          Your account must be activated by an Arvados administrator.  If this
-          is your first time accessing Arvados and would like to request
-          access, or you believe you are seeing the page in error, please
-          <%= link_to "contact us", Rails.configuration.activation_contact_link %>.
-          You should receive an email at the address you used to log in when
-          your account is activated.  In the mean time, you can
-          <%= link_to "learn more about Arvados", "https://arvados.org/projects/arvados/wiki/Introduction_to_Arvados" %>,
-          and <%= link_to "read the Arvados user guide", "http://doc.arvados.org/user" %>.
-        </p>
-        <p style="padding-bottom: 1em">
-          <%= link_to raw('Contact us &#x2709;'),
-              Rails.configuration.activation_contact_link, class: "pull-right btn btn-primary" %></p>
-      </div>
-    </div>
-  </div>
-<% end %>
-
-<% content_for :js do %>
-setInterval(function(){$('a.refresh:eq(0)').click()}, 60000);
-<% end %>

commit 5283047c0d0993133418c47917a5dd42d4fc7450
Author: Tom Clegg <tom at curoverse.com>
Date:   Sat May 17 17:20:03 2014 -0400

    2760: Add "create pipeline instance" test.

diff --git a/apps/workbench/test/integration/pipeline_templates_test.rb b/apps/workbench/test/integration/pipeline_templates_test.rb
new file mode 100644
index 0000000..136496a
--- /dev/null
+++ b/apps/workbench/test/integration/pipeline_templates_test.rb
@@ -0,0 +1,20 @@
+require 'integration_helper'
+require 'selenium-webdriver'
+require 'headless'
+
+class PipelineTemplatesTest < ActionDispatch::IntegrationTest
+
+  test 'Create new pipeline instance from template' do
+    Capybara.current_driver = Capybara.javascript_driver
+    uuid = api_fixture('pipeline_templates')['two_part']['uuid']
+    visit page_with_token('active', '/pipeline_templates')
+    within 'tr', text: uuid do
+      find('button[type=submit]').click
+      wait_for_ajax
+    end
+    page.assert_selector 'tr', text: 'part-one'
+    page.assert_selector 'tr', text: 'part-two'
+    page.assert_selector 'a,button', text: 'Run pipeline'
+  end
+
+end

commit 6a4ddf168d7dd1b6a24ce67492d773492e62c117
Author: Tom Clegg <tom at curoverse.com>
Date:   Fri May 16 18:40:36 2014 -0400

    2760: Make a new/empty name link to show/edit if no existing name is found.

diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb
index 353a188..78df220 100644
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@ -127,6 +127,9 @@ class ApplicationController < ActionController::Base
       f.json { render json: @object.attributes.merge(href: url_for(@object)) }
       f.html {
         if request.method == 'GET'
+          @name_link ||= Link.new(link_class: 'name',
+                                  tail_uuid: current_user.uuid,
+                                  head_uuid: @object.uuid)
           render
         elsif params[:return_to]
           redirect_to params[:return_to]

commit 19d107113c873d7a9b4f3e85d52637224c000724
Merge: 2a30085 90744f4
Author: Tom Clegg <tom at curoverse.com>
Date:   Fri May 16 16:17:01 2014 -0400

    2760: Merge branch 'master' into 2760-linked-names-only refs #2760


commit 2a30085da8772789588a9286829f01c71b98d84a
Author: Tom Clegg <tom at curoverse.com>
Date:   Fri May 16 10:56:33 2014 -0400

    2760: Create name links for new collections. Tolerate old API servers for now.

diff --git a/apps/workbench/app/controllers/actions_controller.rb b/apps/workbench/app/controllers/actions_controller.rb
index 368d9a8..943f1ff 100644
--- a/apps/workbench/app/controllers/actions_controller.rb
+++ b/apps/workbench/app/controllers/actions_controller.rb
@@ -115,6 +115,15 @@ class ActionsController < ApplicationController
       l.save!
     end
 
+    begin
+      Link.create!(link_class: 'name',
+                   tail_uuid: current_user.uuid,
+                   head_uuid: newuuid)
+    rescue
+      # Remove this rescue block when API servers are all upgraded.
+      logger.warn "API server did not assign a generic name."
+    end
+
     redirect_to controller: 'collections', action: :show, id: newc.uuid
   end
 
diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb
index c79d082..353a188 100644
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@ -179,11 +179,16 @@ class ApplicationController < ActionController::Base
     @object ||= model_class.new @new_resource_attrs
     @object.save!
     if model_class != Link
-      @name_link = Link.new(tail_uuid: current_user.uuid,
-                            head_uuid: @object.uuid,
-                            link_class: 'name',
-                            name: params[:name])
-      @name_link.save!
+      begin
+        @name_link = Link.create!(tail_uuid: current_user.uuid,
+                                  head_uuid: @object.uuid,
+                                  link_class: 'name',
+                                  name: params[:name])
+      rescue Exception => e
+        # Remove this rescue block when API servers are all upgraded.
+        raise e if params[:name]
+        logger.warn "API server did not assign a generic name."
+      end
     end
     show
   end

commit 6470b991f98b9121537d8a510f143267990552db
Author: Tom Clegg <tom at curoverse.com>
Date:   Fri May 16 10:55:32 2014 -0400

    2760: Delete name links too when deleting objects in pipeline instance tests.

diff --git a/apps/workbench/test/functional/pipeline_instances_controller_test.rb b/apps/workbench/test/functional/pipeline_instances_controller_test.rb
index c04b5b1..b0a0da3 100644
--- a/apps/workbench/test/functional/pipeline_instances_controller_test.rb
+++ b/apps/workbench/test/functional/pipeline_instances_controller_test.rb
@@ -13,6 +13,11 @@ class PipelineInstancesControllerTest < ActionController::TestCase
     pi_uuid = assigns(:object).uuid
     assert_not_nil assigns(:object)
     yield pi_uuid, pt_fixture
+    use_token :active do
+      Link.where(head_uuid: pi_uuid).each do |link|
+        link.destroy
+      end
+    end
     post :destroy, {
       id: pi_uuid,
       format: :json
diff --git a/apps/workbench/test/test_helper.rb b/apps/workbench/test/test_helper.rb
index c1eed5c..4293e7e 100644
--- a/apps/workbench/test/test_helper.rb
+++ b/apps/workbench/test/test_helper.rb
@@ -31,7 +31,12 @@ class ActiveSupport::TestCase
   fixtures :all
   def use_token token_name
     auth = api_fixture('api_client_authorizations')[token_name.to_s]
+    token_was = Thread.current[:arvados_api_token]
     Thread.current[:arvados_api_token] = auth['api_token']
+    if block_given?
+      yield
+      Thread.current[:arvados_api_token] = token_was
+    end
   end
 
   def teardown

commit 8e4fc832108d9cae376d3eb1b9e62f9d6b468501
Author: Tom Clegg <tom at curoverse.com>
Date:   Fri May 16 09:56:28 2014 -0400

    2760: Assign a generic name if link_class=name and no name is given.

diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb
index 61375b8..c79d082 100644
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@ -179,11 +179,10 @@ class ApplicationController < ActionController::Base
     @object ||= model_class.new @new_resource_attrs
     @object.save!
     if model_class != Link
-      name = params[:name] || "New #{model_class.to_s.underscore.downcase.humanize} created #{Time.now}"
       @name_link = Link.new(tail_uuid: current_user.uuid,
                             head_uuid: @object.uuid,
                             link_class: 'name',
-                            name: name)
+                            name: params[:name])
       @name_link.save!
     end
     show
diff --git a/services/api/app/models/link.rb b/services/api/app/models/link.rb
index af39185..9f4f510 100644
--- a/services/api/app/models/link.rb
+++ b/services/api/app/models/link.rb
@@ -5,6 +5,7 @@ class Link < ArvadosModel
   serialize :properties, Hash
   before_create :permission_to_attach_to_objects
   before_update :permission_to_attach_to_objects
+  before_create :assign_generic_name_if_none_given
   after_update :maybe_invalidate_permissions_cache
   after_create :maybe_invalidate_permissions_cache
   after_destroy :maybe_invalidate_permissions_cache
@@ -40,6 +41,48 @@ class Link < ArvadosModel
 
   protected
 
+  def assign_generic_name_if_none_given
+    if new_record? and link_class == 'name' and (!name or name.empty?)
+      # Creating a name link with no name means "invent a generic
+      # name, like New Foo (1)"
+
+      head_class = ArvadosModel::resource_class_for_uuid(head_uuid)
+      if !head_class
+        errors.add :name, 'cannot be automatically assigned for this uuid'
+        return
+      end
+      name_base = "New " + head_class.to_s.underscore.humanize.downcase
+      if not Link.where(link_class: link_class,
+                        tail_uuid: tail_uuid,
+                        name: name_base).any?
+        self.name = name_base
+      else
+        # Find how many digits the largest N has among "New model (N)" names
+        maxlen = ActiveRecord::Base.connection.
+          execute("SELECT max(length(name)) maxlen FROM links "\
+                  "WHERE link_class='name' "\
+                  "AND tail_uuid=#{Link.sanitize(tail_uuid)} "\
+                  "AND name~#{Link.sanitize "#{name_base} \\([0-9]+\\)"}")[0]
+        if maxlen and maxlen['maxlen']
+          # Find the largest N by sorting alphanumerically
+          maxname = ActiveRecord::Base.connection.
+            execute("SELECT max(name) maxname FROM links "\
+                    "WHERE link_class='name' "\
+                    "AND tail_uuid=#{Link.sanitize(tail_uuid)} "\
+                    "AND length(name)=#{maxlen['maxlen']} "\
+                    "AND name~#{Link.sanitize "#{name_base} \\([0-9]+\\)"}"
+                    )[0]['maxname']
+          n = maxname.match(/\(([0-9]+)\)$/)[1].to_i
+          n += 1
+        else
+          # "New foo" is taken, but "New foo (1)" isn't.
+          n = 1
+        end
+        self.name = name_base + " (#{n})"
+      end
+    end
+  end
+
   def permission_to_attach_to_objects
     # Anonymous users cannot write links
     return false if !current_user
@@ -85,8 +128,13 @@ class Link < ArvadosModel
 
   def name_link_has_valid_name
     if link_class == 'name'
-      unless name.is_a? String and !name.empty?
-        errors.add('name', 'must be a non-empty string')
+      if new_record? and (!name or name.empty?)
+        # Unique name will be assigned in before_create filter
+        true
+      else
+        unless name.is_a? String and !name.empty?
+          errors.add('name', 'must be a non-empty string')
+        end
       end
     else
       true
diff --git a/services/api/test/unit/link_test.rb b/services/api/test/unit/link_test.rb
index 10f2b5e..c04addd 100644
--- a/services/api/test/unit/link_test.rb
+++ b/services/api/test/unit/link_test.rb
@@ -37,12 +37,25 @@ class LinkTest < ActiveSupport::TestCase
   end
 
   [nil, '', false].each do |name|
-    test "name links cannot have name=#{name.inspect}" do
+    test "name links cannot be renamed to name=#{name.inspect}" do
+      a = Link.create!(tail_uuid: groups(:afolder).uuid,
+                       head_uuid: specimens(:owned_by_active_user).uuid,
+                       link_class: 'name',
+                       name: 'temp')
+      a.name = name
+      assert a.invalid?, "invalid name was accepted as valid?"
+    end
+
+    test "name links cannot be created with name=#{name.inspect}" do
       a = Link.create(tail_uuid: groups(:afolder).uuid,
                       head_uuid: specimens(:owned_by_active_user).uuid,
                       link_class: 'name',
                       name: name)
-      assert a.invalid?, "invalid name was accepted as valid?"
+      if a.name and !a.name.empty?
+        assert a.valid?, "name automatically assigned, but record not valid?"
+      else
+        assert a.invalid?, "invalid name was accepted as valid?"
+      end
     end
   end
 
@@ -57,4 +70,34 @@ class LinkTest < ActiveSupport::TestCase
       ob.destroy
     end
   end
+
+  test "assign sequential generic name links" do
+    group = Group.create!(group_class: 'folder')
+    ob = Specimen.create!
+    25.times do |n|
+      link = Link.create!(link_class: 'name',
+                          tail_uuid: group.uuid, head_uuid: ob.uuid)
+      expect_name = 'New specimen' + (n==0 ? "" : " (#{n})")
+      assert_equal expect_name, link.name, "Expected sequential generic names"
+    end
+  end
+
+  test "assign sequential generic name links for a two-word model" do
+    group = Group.create!(group_class: 'folder')
+    ob = VirtualMachine.create!
+    5.times do |n|
+      link = Link.create!(link_class: 'name',
+                          tail_uuid: group.uuid, head_uuid: ob.uuid)
+      expect_name = 'New virtual machine' + (n==0 ? "" : " (#{n})")
+      assert_equal expect_name, link.name, "Expected sequential generic names"
+    end
+  end
+
+  test "cannot assign sequential generic name links for a bogus uuid type" do
+    group = Group.create!(group_class: 'folder')
+    link = Link.create(link_class: 'name',
+                       tail_uuid: group.uuid,
+                       head_uuid: 'zzzzz-abcde-123451234512345')
+    assert link.invalid?, "gave a bogus uuid, got automatic name #{link.name}"
+  end
 end

commit 6ded1bfa2f7ad3a979adc715b537773ab296be95
Author: Tom Clegg <tom at curoverse.com>
Date:   Thu May 15 20:46:44 2014 -0400

    2760: Show pipeline instance name from link, not the object itself.

diff --git a/apps/workbench/app/views/pipeline_instances/_show_components.html.erb b/apps/workbench/app/views/pipeline_instances/_show_components.html.erb
index 61a4ca1..fc673e1 100644
--- a/apps/workbench/app/views/pipeline_instances/_show_components.html.erb
+++ b/apps/workbench/app/views/pipeline_instances/_show_components.html.erb
@@ -6,7 +6,7 @@
 
 <%= content_for :content_top do %>
   <h2>
-    <%= render_editable_attribute @object, 'name', nil, { 'data-emptytext' => 'Unnamed pipeline' } %>
+    <%= render_editable_attribute @name_link, 'name', nil, { 'data-emptytext' => 'Unnamed pipeline' } %>
   </h2>
   <% if template %>
   <h4>

commit 34692a5a08c95024be6e4d1b7e317c5a796c8f3f
Author: Tom Clegg <tom at curoverse.com>
Date:   Thu May 15 20:46:26 2014 -0400

    2760: Create name links as a matter of course. Allow linking to objects via name uuid.

diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb
index b62838f..61375b8 100644
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@ -66,6 +66,30 @@ class ApplicationController < ActionController::Base
     self.render_error status: 404
   end
 
+  def name_links_for object=nil
+    if !@name_links_cache or !@name_links_cache[object.uuid]
+      @name_links_cache ||= {}
+      uuids = @objects.collect(&:uuid) + [object.uuid] - @name_links_cache.keys
+      uuids.each do |uuid|
+        @name_links_cache[uuid] = []
+      end
+      offset = 0
+      while true
+        name_links = Link.
+          filter([['link_class', '=', 'name'],
+                  ['head_uuid', 'in', uuids]]).
+          offset(offset).
+          order(['uuid'])
+        name_links.each do |link|
+          @name_links_cache[link.head_uuid] << link
+        end
+        offset += name_links.result_limit
+        break if offset >= name_links.items_available
+      end
+    end
+    @name_links_cache[object.uuid] || []
+  end
+
   def index
     @limit ||= 200
     if params[:limit]
@@ -104,8 +128,12 @@ class ApplicationController < ActionController::Base
       f.html {
         if request.method == 'GET'
           render
+        elsif params[:return_to]
+          redirect_to params[:return_to]
+        elsif @name_link
+          redirect_to action: :show, id: @name_link.uuid
         else
-          redirect_to params[:return_to] || @object
+          redirect_to @object
         end
       }
       f.js { render }
@@ -150,6 +178,14 @@ class ApplicationController < ActionController::Base
     @new_resource_attrs.reject! { |k,v| k.to_s == 'uuid' }
     @object ||= model_class.new @new_resource_attrs
     @object.save!
+    if model_class != Link
+      name = params[:name] || "New #{model_class.to_s.underscore.downcase.humanize} created #{Time.now}"
+      @name_link = Link.new(tail_uuid: current_user.uuid,
+                            head_uuid: @object.uuid,
+                            link_class: 'name',
+                            name: name)
+      @name_link.save!
+    end
     show
   end
 
@@ -254,6 +290,13 @@ class ApplicationController < ActionController::Base
     elsif params[:uuid].is_a? String
       if params[:uuid].empty?
         @object = nil
+      elsif model_class.to_s != 'Link' and
+          ArvadosBase::resource_class_for_uuid(params[:uuid]).to_s == 'Link'
+        @object = nil
+        if (@name_link = Link.where(uuid: params[:uuid],
+                                    link_class: 'name').first)
+          @object = model_class.where(uuid: @name_link.head_uuid).first
+        end
       else
         @object = model_class.find(params[:uuid])
       end

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list