[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 ✉'),
- 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