[ARVADOS] updated: 1b97a93f4a0e51836331c298c3946e02b6fe82ae

git at public.curoverse.com git at public.curoverse.com
Thu Jun 12 12:50:13 EDT 2014


Summary of changes:
 apps/workbench/app/assets/images/ajax-loader.gif   | Bin 0 -> 3208 bytes
 .../app/assets/javascripts/application.js          |  12 +-
 apps/workbench/app/assets/javascripts/editable.js  |   3 +
 apps/workbench/app/assets/javascripts/event_log.js |  25 +-
 .../app/assets/javascripts/pipeline_instances.js   |   9 -
 apps/workbench/app/assets/javascripts/selection.js |  10 +-
 .../app/assets/stylesheets/application.css.scss    |   3 +-
 .../app/controllers/actions_controller.rb          |  80 +++---
 .../app/controllers/application_controller.rb      | 248 ++++++++++++++--
 .../app/controllers/collections_controller.rb      |  10 +-
 .../workbench/app/controllers/groups_controller.rb |   1 +
 apps/workbench/app/controllers/jobs_controller.rb  |  13 +-
 .../controllers/pipeline_instances_controller.rb   |  26 +-
 .../app/controllers/projects_controller.rb         |   3 +
 .../app/controllers/sessions_controller.rb         |   3 +
 apps/workbench/app/helpers/application_helper.rb   |  52 +++-
 apps/workbench/app/models/arvados_api_client.rb    |   4 +
 apps/workbench/app/models/arvados_base.rb          |  11 +-
 apps/workbench/app/models/job.rb                   |   4 +
 .../app/views/application/_content.html.erb        |  75 ++++-
 .../application/_show_advanced_metadata.html.erb   |  12 +
 apps/workbench/app/views/collections/show.html.erb |   7 +-
 .../workbench/app/views/jobs/_show_status.html.erb |  93 ++++++
 .../app/views/layouts/application.html.erb         |   8 +-
 .../app/views/projects/_show_contents.html.erb     |  67 +++--
 .../views/projects/_show_contents_rows.html.erb    |   4 +-
 .../app/views/projects/_show_permissions.html.erb  |  13 +-
 .../workbench/app/views/users/_show_admin.html.erb |   2 -
 apps/workbench/app/views/users/_tables.html.erb    |  51 +++-
 apps/workbench/config/routes.rb                    |   4 +-
 .../test/functional/application_controller_test.rb | 299 ++++++++++++++++++++
 .../test/integration/pipeline_instances_test.rb    |   2 +-
 apps/workbench/test/integration/projects_test.rb   |   5 +-
 apps/workbench/test/integration/users_test.rb      |   3 -
 sdk/cli/bin/arv-run-pipeline-instance              |  20 +-
 sdk/cli/bin/crunch-job                             |   9 +-
 sdk/go/src/arvados.org/keepclient/keepclient.go    |  42 +--
 .../src/arvados.org/keepclient/keepclient_test.go  |  76 ++---
 sdk/go/src/arvados.org/keepclient/support.go       |  51 +---
 sdk/go/src/arvados.org/sdk/sdk.go                  | 232 +++++++++++++++
 sdk/go/src/arvados.org/sdk/sdk_test.go             |  92 ++++++
 services/api/Gemfile                               |   2 +
 services/api/Gemfile.lock                          |   3 +
 .../app/controllers/arvados/v1/jobs_controller.rb  |   2 +-
 .../app/controllers/arvados/v1/nodes_controller.rb |  12 +-
 .../controllers/arvados/v1/schema_controller.rb    |   3 +-
 services/api/app/middlewares/rack_socket.rb        |   2 +-
 services/api/app/models/job.rb                     |   2 +
 services/api/app/models/node.rb                    |   9 +
 .../20140530200539_add_supplied_script_version.rb  |   9 +
 services/api/db/schema.rb                          |   4 +-
 services/api/script/cancel_stale_jobs.rb           |  37 +++
 services/api/script/crunch-dispatch.rb             | 103 +++++--
 services/api/test/fixtures/links.yml               |   2 +-
 services/api/test/fixtures/nodes.yml               |   1 +
 .../arvados/v1/collections_controller_test.rb      |  29 +-
 .../functional/arvados/v1/nodes_controller_test.rb |  16 ++
 services/api/test/unit/node_test.rb                |  22 +-
 services/{keep => crunch/crunchstat}/go.sh         |   2 -
 .../src/arvados.org/crunchstat/crunchstat.go       | 311 +++++++++++++++++++++
 services/fuse/bin/arv-mount                        |  16 +-
 .../keep/src/arvados.org/keepproxy/keepproxy.go    |  32 +--
 .../src/arvados.org/keepproxy/keepproxy_test.go    |   9 +-
 63 files changed, 1939 insertions(+), 373 deletions(-)
 create mode 100644 apps/workbench/app/assets/images/ajax-loader.gif
 create mode 100644 apps/workbench/app/views/jobs/_show_status.html.erb
 create mode 100644 apps/workbench/test/functional/application_controller_test.rb
 create mode 100644 sdk/go/src/arvados.org/sdk/sdk.go
 create mode 100644 sdk/go/src/arvados.org/sdk/sdk_test.go
 create mode 100644 services/api/db/migrate/20140530200539_add_supplied_script_version.rb
 create mode 100755 services/api/script/cancel_stale_jobs.rb
 copy services/{keep => crunch/crunchstat}/go.sh (92%)
 create mode 100644 services/crunch/crunchstat/src/arvados.org/crunchstat/crunchstat.go

       via  1b97a93f4a0e51836331c298c3946e02b6fe82ae (commit)
       via  6a4c33f0b3689f97d18b2d75f54787245c1ebeb9 (commit)
       via  719ba7f6247cf1697d81193b46bd768202ebb064 (commit)
       via  965700e2e2e981e1ba56c2101d3df2fd759ddb81 (commit)
       via  b64228f1f547c720b79a242d6d8d368a64923885 (commit)
       via  70542a2d6d6a2792f1dc68b1531e783e7611b474 (commit)
       via  a959f21c8147f26362df392bc3fd3290db69de85 (commit)
       via  70f1004b17fac3c772c8f938b5a72ff3a3ebc67a (commit)
       via  a9beb0e51fdd2782c4aeaa1f5c2d20d6ad7dd435 (commit)
       via  9bda231180fc58e67b0c322c16be223154965b66 (commit)
       via  85ce092000d72c5dda03bd4763c9613bb9a46437 (commit)
       via  5da7bddd63790e524e4b22a6944a92065409241d (commit)
       via  0548dd424d6f21f3eb97f3e98d37a604b44b025a (commit)
       via  7ccba0ea6cb9916a1bad55f13cc45029f0bc37bd (commit)
       via  eaa312ea7c35e35c64dbef9624be1d9c7df034ef (commit)
       via  70148c4918a95ee9a49a18bf5789801d02e8ef5e (commit)
       via  f0a048b4b9792550f59ee23b13c09cca21681eb4 (commit)
       via  d161e2280a03b4a1c9675f5ed1946310a9942acd (commit)
       via  23fd8b137488baeb3138e2127294e8e14917d3ed (commit)
       via  82c4697bf24b10f3fb66d303ae73499095b5742a (commit)
       via  505f5c37bb9fe1fe93f8bdbd2d2072e783832f20 (commit)
       via  541ce54ba2c7c8f9783da04f947ccf055b72ae2c (commit)
       via  35db7f5c7f1d62f996550f51fc4f0dd4f77627fb (commit)
       via  799e968ef79906bcd8b491f1ad99572b1cc21ccc (commit)
       via  a314e176f65f97059b43f50a84814f4fe5e5d73d (commit)
       via  f883adf66c3da731904d5c25c779a13416ce9193 (commit)
       via  2bd11a587ca12d8921042d86df4d095c79243dbc (commit)
       via  d687fed365e545dea1c9283e917e05bb2a4ed07c (commit)
       via  92594178fbe4155f3122a1b0a52b5b3da995aabb (commit)
       via  4d73cd3503ca20ec4d1f8198617fea3bf481a48e (commit)
       via  40e48b2aac96a71c8ae3c2265f43b882f415e63d (commit)
       via  90f3d66faa1a87b0b230a8a9a7a9879e66f29cd0 (commit)
       via  497ea8e6fd12ce91cad108af78df5e3802a79d69 (commit)
       via  7e865395a4ccf9f17b904c3700064328d52db121 (commit)
       via  9a48ecf1c92b9b8f513a5e621a0e263ccde076e4 (commit)
       via  468a80200c8ef3e4a56700714e9af537d87a20bc (commit)
       via  3692a4ac4d8f76d2e704af5e504652650b4a00cc (commit)
       via  73c0b152d18774266e72916d6f8cedb3199b535c (commit)
       via  0ef7495b59056c8f115a42bd843ce432c9282e8c (commit)
       via  67578498c5de67fe2e821a3d91c1d2b7294df579 (commit)
       via  2cdc648b5367efd4c8e7b2ede93a2b3ba0871c17 (commit)
       via  97a1da1dfde9ef3ef195f7513309da8c6bb17978 (commit)
       via  73688a1ef6387fe8a8b1b2f09a8936198f79d66e (commit)
       via  a2bcc06c1bda757ed8c869d7f9ad235f56abdcf4 (commit)
       via  dd6e25d53f9cf02f91b419584ff4c331e0618a24 (commit)
       via  400829b3835f0a129116a2eed926d12a0636aeab (commit)
       via  f6e5ba4510e2072415a8d6b5369b11b8003ddafa (commit)
       via  55b94087ff4590c54cf1f43d941c6cb24153dded (commit)
       via  ef3e10bdf2324f3c9e6427b3fd085e6b05e3877c (commit)
       via  408649cc0e9ad3d30296b0750680fb62fefe6d82 (commit)
       via  1bd85c64fbf485acceac4711ff06e6b2f548d40a (commit)
       via  879e1f31b0165c6abaa35a023ff66400757bb44b (commit)
       via  987b88a5441387ff91877464def7933064815b59 (commit)
       via  f3da69fb1b882c7fe8feb765ad0558f13706931a (commit)
       via  f0d8ab52b77f74e9294fe634207ce6e1ff9748a1 (commit)
       via  620a10bed55b85294baad9dba965ea8dad59e884 (commit)
       via  488a811374ff4bdeed9f2f2f57d9ef31d9369b5b (commit)
       via  626db8559ebaa13df19dafe9bb03b1bd484b762c (commit)
       via  2f3d49bde80526060d3337f13dfa91cd581ac222 (commit)
       via  6a8463eea9e32cb5cffcaf2f04667520794404ec (commit)
       via  fa87d357fdefd594b378cda9b4d73487df60d262 (commit)
       via  1f760632ae0fbb9f11af5cfb831b7c3ed49a7009 (commit)
       via  428973c03d4b4cd96adc80a514beffbb739d987a (commit)
       via  35c20b4ad8220131f7f6bad6b3806a7d28df3ef3 (commit)
       via  f48482bd37d3ae5a5f1aa488fa330f77c5fd640d (commit)
       via  0e7e57bbd8030c8144a18e43e68945ab11ad094c (commit)
       via  32f6ee6884704bde04878f5cbc176a7a819a50e8 (commit)
       via  38a55a1518838ea68cba69b4e8d1c27d2128328d (commit)
       via  0b4f867ef14af38c07b910643fbe8cc6a93e6bb6 (commit)
       via  cea92754dfacf2b409d1f5b45dd0775fc44c842d (commit)
       via  f0bf04abdbb7e4b47be85585fba5789d695bb31e (commit)
       via  a163b0c7b5afa782281f67f0fa0ca0e4b41c518f (commit)
       via  c4cfb4caca122ba09ab9153453650c308457e675 (commit)
       via  a46f0152c44fe20eba4db38858eaa2f99bae83f2 (commit)
       via  f3b4b263d8c656e15993ea71146747e8cf102bf9 (commit)
       via  16cc50a4161448f9987635e06fe3d772ee0321ab (commit)
       via  25f3a23bc8794b77a1b5fa1e5e7f9c38a152dd8f (commit)
       via  33fed6bb30180c9dedb046d6266acbbc48dcfcd4 (commit)
       via  87dc5129e903cb7452e06c936d8e5e581c4d2481 (commit)
       via  bc784d9eeadfc04aedea9772bb8913f5e8b86cd1 (commit)
       via  10486fd2453c92a62c0a97491259d1391e5cb872 (commit)
       via  cdc46777d33cc341969ba791175a8ca0925e77dc (commit)
       via  570201300b90a676b4bc49ea02627d1057386615 (commit)
       via  3674d731b92e091811872eef0b909171d3574614 (commit)
       via  3273eb53cd985cf13e1e0785ba853af2d0a8c2c6 (commit)
       via  03812184bfa1ab7f17960ef48ff30798333bdd54 (commit)
       via  1ec252c8087c1f167d969e26c584ff346f4ac457 (commit)
       via  6b13646940de2e5a62e600f2d7d82c72e3a4ae59 (commit)
       via  dfe74093596d3d7378e6f0d3e5f843f44d987074 (commit)
       via  bf7687829f5222375441cf75c7d82d82a4f508c4 (commit)
       via  137cd3ce818d4789355ea8d92cd2e2ea73318c4f (commit)
       via  f6f4efcf298d5c37b5eb8a88eb164cf8b77ae928 (commit)
       via  c7a21e6294f1eb905ace3d717f9dcfb4d4c39f0b (commit)
       via  9fd2051619bf6246708489e39515fb94b43a3199 (commit)
       via  ca77755f57b0478e3f4fbe3e6a6d7ade95012808 (commit)
       via  66da5859254325690a78512d03b6b02e3f69a237 (commit)
       via  349ab8f9e11d462c878cd6be6d24bf5790af3d50 (commit)
       via  8c9fa09deea0cb68b68f110fdbb1b1daa50a618c (commit)
       via  161478d5be79e83f3e200b46f0d8d003caa2ffe0 (commit)
       via  ae09643622ecea00bab110f20029f01c83e1cf30 (commit)
       via  2a64eae3cf8363c596feda5337ea20ce356ca11f (commit)
       via  114df81b90be76e6921b9f20c9ddb272567c82e1 (commit)
       via  a276e40691a8f96b321879de2279159ef08b804f (commit)
       via  cb77d123755112d17f2e7bb2bd869d957d8a00f3 (commit)
       via  618a868118721baa683d151949759e1e9e516b4a (commit)
       via  cda441b1b6e50811c7dbd8d5c61ead1b39c91857 (commit)
       via  2c6c1cb11153849e87496bb8d1f5ff24f439a6a4 (commit)
       via  ec08a1b9aefcd801d1f0c0b282b268cbee6939bc (commit)
       via  2f1baf225599d7ae5dec4611696774e6a7100d58 (commit)
       via  f10a7f68c6ef25cf4ba87045201b17093a11249e (commit)
       via  a0aba6d6dec99a25cc2622b49fb7d29634a3e8c3 (commit)
       via  4bf9e0c663789cbb748e628e8e86a8ea9ab047c5 (commit)
       via  2c77b59f16d698fa5368c2aa28f5c304c3f7e53b (commit)
       via  270a94c35d496daeabc39300611f727ce307f353 (commit)
       via  a285726d2297f71254529ab7d5b02f9246a0c413 (commit)
       via  e881778afbede03d32b842334113cba198052cfb (commit)
       via  18e7ea91bc5e9cd7cd1f25bd009a67e6fd2cc06b (commit)
       via  5779020e2f38bbc88dc436b33dcaf7dbd8292752 (commit)
       via  a975c45ec9908660db54b1f22399fc2c43488af5 (commit)
       via  09d3d4d177810ee9aea235eddbda9306f1aa5b7b (commit)
       via  f8d2097375c2d105b53de18a5ec7afdb7eb36b78 (commit)
       via  c67402c3d993007f7d925d605eb486d21df4e278 (commit)
       via  8ca3553c699e5fb2e026aa1ab0c0f20b6c43881f (commit)
      from  2cf679eb06b02a98f0597b709eaea581bbe82273 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 1b97a93f4a0e51836331c298c3946e02b6fe82ae
Author: Tom Clegg <tom at curoverse.com>
Date:   Thu Jun 12 12:49:43 2014 -0400

    2872: Fix selector in test case.

diff --git a/apps/workbench/test/integration/projects_test.rb b/apps/workbench/test/integration/projects_test.rb
index 73733d2..76a61b4 100644
--- a/apps/workbench/test/integration/projects_test.rb
+++ b/apps/workbench/test/integration/projects_test.rb
@@ -70,10 +70,9 @@ class ProjectsTest < ActionDispatch::IntegrationTest
     end
     wait_for_ajax
 
-    click_link 'Permissions'
-    find('input[value="Move to..."]').click
+    click_link 'Move to...'
     find('.selectable', text: 'Project 1234').click
-    find('a,button', text: 'Move').click
+    find('.modal-footer a,button', text: 'Move').click
     wait_for_ajax
 
     # Wait for the page to refresh and show the new parent in Permissions panel

commit 6a4c33f0b3689f97d18b2d75f54787245c1ebeb9
Author: Tom Clegg <tom at curoverse.com>
Date:   Thu Jun 12 11:36:21 2014 -0400

    2872: Add "move to project" button, fix "copy to project" behavior
    (really copy, instead of adding a link).

diff --git a/apps/workbench/app/assets/javascripts/selection.js b/apps/workbench/app/assets/javascripts/selection.js
index 84d65cd..0fa2e9a 100644
--- a/apps/workbench/app/assets/javascripts/selection.js
+++ b/apps/workbench/app/assets/javascripts/selection.js
@@ -57,8 +57,10 @@ jQuery(function($){
         $("#persistent-selection-count").text(lst.length);
         if (lst.length > 0) {
             html = '<li><a href="#" class="btn btn-xs btn-info" id="clear_selections_button"><i class="fa fa-fw fa-ban"></i> Clear selections</a></li>';
-            if (this_object_uuid.match('-j7d0g-'))
-                html += '<li><button class="btn btn-xs btn-info" type="submit" name="copy_selections_into_project" id="copy_selections_into_project"><i class="fa fa-fw fa-folder-open"></i> Copy selections into this project</button></li>';
+            if (this_object_uuid.match('-j7d0g-')) {
+                html += '<li><button class="btn btn-xs btn-info" type="submit" name="copy_selections_into_project" id="copy_selections_into_project"><i class="fa fa-fw fa-copy"></i> Copy selections into this project</button></li>';
+                html += '<li><button class="btn btn-xs btn-info" type="submit" name="move_selections_into_project" id="move_selections_into_project"><i class="fa fa-fw fa-truck"></i> Move selections into this project</button></li>';
+	    }
             html += '<li><button class="btn btn-xs btn-info" type="submit" name="combine_selected_files_into_collection" '
                 + ' id="combine_selected_files_into_collection">'
                 + '<i class="fa fa-fw fa-archive"></i> Combine selected collections and files into a new collection</button></li>'
@@ -200,8 +202,8 @@ function enable_disable_selection_actions() {
     var $checked = $('.persistent-selection:checkbox:checked');
     $('[data-selection-action]').
         closest('div.btn-group-sm').
-        find('*').
-        prop('disabled', ($checked.length == 0));
+        find('ul li').
+        toggleClass('disabled', ($checked.length == 0));
     $('[data-selection-action=compare]').
         closest('li').
         toggleClass('disabled',
diff --git a/apps/workbench/app/controllers/actions_controller.rb b/apps/workbench/app/controllers/actions_controller.rb
index 06775fc..6fbdf29 100644
--- a/apps/workbench/app/controllers/actions_controller.rb
+++ b/apps/workbench/app/controllers/actions_controller.rb
@@ -20,43 +20,51 @@ class ActionsController < ApplicationController
   end
 
   expose_action :copy_selections_into_project do
-    link_selections = Link.filter([['uuid','in',params["selection"]]])
-    link_uuids = link_selections.collect(&:uuid)
-
-    # Given a link uuid, we'll add the link's head_uuid. Given another
-    # type, we'll add the object itself.
-    uuids_to_add = params["selection"] - link_uuids
-    uuids_to_add += link_selections.collect(&:head_uuid)
-
-    # Skip anything that's already here.
-    already_named = Link.
-      filter([['tail_uuid','=', at object.uuid],
-              ['head_uuid','in',uuids_to_add],
-              ['link_class','=','name']]).
-      collect(&:head_uuid)
-    uuids_to_add -= already_named
-
-    # Given a name link, we'll try to add the linked object using the
-    # same name.
-    name_for = {}
-    link_selections.
-      select { |x| x.link_class == 'name' }.
-      each do |link|
-      name_for[link.head_uuid] = link.name
-    end
+    move_or_copy :copy
+  end
+
+  expose_action :move_selections_into_project do
+    move_or_copy :move
+  end
 
-    uuids_to_add.each do |s|
-      name = name_for[s] || s
-      begin
-        Link.create(tail_uuid: @object.uuid,
-                    head_uuid: s,
-                    link_class: 'name',
-                    name: name)
-      rescue
-        Link.create(tail_uuid: @object.uuid,
-                    head_uuid: s,
-                    link_class: 'name',
-                    name: name + " (#{Time.now.localtime})")
+  def move_or_copy action
+    uuids_to_add = params["selection"]
+    uuids_to_add.
+      collect { |x| ArvadosBase::resource_class_for_uuid(x) }.
+      uniq.
+      each do |resource_class|
+      resource_class.filter([['uuid','in',uuids_to_add]]).each do |src|
+        if resource_class == Collection
+          dst = Link.new(owner_uuid: @object.uuid,
+                         tail_uuid: @object.uuid,
+                         head_uuid: src.uuid,
+                         link_class: 'name',
+                         name: @object.uuid)
+        else
+          case action
+          when :copy
+            dst = src.dup
+            if dst.respond_to? :'name='
+              if dst.name
+                dst.name = "Copy of #{dst.name}"
+              else
+                dst.name = "Copy of unnamed #{dst.class_for_display.downcase}"
+              end
+            end
+          when :move
+            dst = src
+          else
+            raise ArgumentError.new "Unsupported action #{action}"
+          end
+          dst.owner_uuid = @object.uuid
+          dst.tail_uuid = @object.uuid if dst.class == Link
+        end
+        begin
+          dst.save!
+        rescue
+          dst.name += " (#{Time.now.localtime})" if dst.respond_to? :name=
+          dst.save!
+        end
       end
     end
     redirect_to @object

commit 719ba7f6247cf1697d81193b46bd768202ebb064
Author: Tom Clegg <tom at curoverse.com>
Date:   Thu Jun 12 11:35:28 2014 -0400

    2872: Fix "remove" icon and pencil icon placement.

diff --git a/apps/workbench/app/views/projects/_show_contents_rows.html.erb b/apps/workbench/app/views/projects/_show_contents_rows.html.erb
index 8b19c00..68561f8 100644
--- a/apps/workbench/app/views/projects/_show_contents_rows.html.erb
+++ b/apps/workbench/app/views/projects/_show_contents_rows.html.erb
@@ -9,7 +9,7 @@
 
       <% if project.editable? %>
         <%= link_to({action: 'remove_item', id: project.uuid, item_uuid: ((name_link && name_link.uuid) || object.uuid)}, method: :delete, remote: true, data: {confirm: "Remove #{object.class_for_display.downcase} #{name_object.name rescue object.uuid} from this project?", toggle: 'tooltip', placement: 'top'}, class: 'btn btn-sm btn-default btn-nodecorate', title: 'remove') do %>
-          <i class="fa fa-fw fa-ban"></i>
+          <i class="fa fa-fw fa-trash-o"></i>
         <% end %>
       <% else %>
         <i class="fa fa-fw"></i><%# placeholder %>
@@ -17,7 +17,7 @@
 
       <%= render :partial => "show_object_button", :locals => {object: object, size: 'sm', name_link: name_link} %>
 
-      <%= render_editable_attribute (name_link || object), 'name', nil, {btnplacement: :left, tiptitle: 'rename'} %>
+      <%= render_editable_attribute (name_link || object), 'name', nil, {tiptitle: 'rename'} %>
     </td>
     <td class="arv-description-in-table">
       <%= render_controller_partial(

commit 965700e2e2e981e1ba56c2101d3df2fd759ddb81
Author: Tom Clegg <tom at curoverse.com>
Date:   Thu Jun 12 11:30:25 2014 -0400

    2872: Muster all the "move" and "add" buttons up to the right side of the tabs.

diff --git a/apps/workbench/app/views/projects/_show_contents.html.erb b/apps/workbench/app/views/projects/_show_contents.html.erb
index 5132796..3b21b40 100644
--- a/apps/workbench/app/views/projects/_show_contents.html.erb
+++ b/apps/workbench/app/views/projects/_show_contents.html.erb
@@ -11,9 +11,47 @@
 <% end %>
 
 <% content_for :tab_line_buttons do %>
-  <% if @objects_and_names.empty? %>
-    <%= button_to(project_path(id: @object.uuid, return_to: projects_path), method: 'delete', class: 'btn btn-sm btn-primary', data: {confirm: "Really delete project '#{@object.name}'?"}) do %>
-      <i class="fa fa-fw fa-trash-o"></i> Delete project
+  <% if @object.editable? %>
+    <%= link_to(
+	  choose_collections_path(
+	    title: 'Add data to project:',
+	    multiple: true,
+	    action_name: 'Add',
+	    action_href: actions_path(id: @object.uuid),
+	    action_method: 'post',
+	    action_data: {selection_param: 'selection[]', copy_selections_into_project: @object.uuid, success: 'page-refresh'}.to_json),
+	  { class: "btn btn-primary btn-sm", remote: true, method: 'get', data: {'event-after-select' => 'page-refresh'} }) do %>
+      <i class="fa fa-fw fa-plus"></i> Add data
+    <% end %>
+    <%= link_to(
+	  choose_pipeline_templates_path(
+	    title: 'Choose a pipeline to run:',
+	    action_name: 'Next: choose inputs <i class="fa fa-fw fa-arrow-circle-right"></i>',
+	    action_href: pipeline_instances_path,
+	    action_method: 'post',
+	    action_data: {'selection_param' => 'pipeline_instance[pipeline_template_uuid]', 'pipeline_instance[owner_uuid]' => @object.uuid, 'success' => 'redirect-to-created-object'}.to_json),
+	  { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %>
+      <i class="fa fa-fw fa-gear"></i> Run a pipeline
+    <% end %>
+    <%= link_to projects_path(method: 'post', owner_uuid: @object.uuid), class: 'btn btn-sm btn-primary' do %>
+      <i class="fa fa-fw fa-plus"></i>
+      Add a subproject
+    <% end %>
+    <%= link_to(
+	choose_projects_path(
+	 title: 'Move this project to...',
+	 editable: true,
+	 action_name: 'Move',
+	 action_href: project_path(@object.uuid),
+	 action_method: 'put',
+	 action_data: {selection_param: 'project[owner_uuid]', success: 'page-refresh'}.to_json),
+	{ class: "btn btn-sm btn-primary arv-move-to-project", remote: true, method: 'get' }) do %>
+      <i class="fa fa-fw fa-truck"></i> Move to...
+    <% end %>
+    <% if @objects_and_names.empty? %>
+      <%= button_to(project_path(id: @object.uuid, return_to: projects_path), method: 'delete', class: 'btn btn-sm btn-primary', data: {confirm: "Really delete project '#{@object.name}'?"}) do %>
+	<i class="fa fa-fw fa-trash-o"></i> Delete project
+      <% end %>
     <% end %>
   <% end %>
 <% end %>
@@ -42,29 +80,6 @@
             %></li>
         </ul>
       </div>
-      <% if @object.editable? %>
-        <%= link_to(
-              choose_collections_path(
-                title: 'Add data to project:',
-                multiple: true,
-                action_name: 'Add',
-                action_href: actions_path(id: @object.uuid),
-                action_method: 'post',
-                action_data: {selection_param: 'selection[]', copy_selections_into_project: @object.uuid, success: 'page-refresh'}.to_json),
-              { class: "btn btn-primary btn-sm", remote: true, method: 'get', data: {'event-after-select' => 'page-refresh'} }) do %>
-          <i class="fa fa-fw fa-plus"></i> Add data
-        <% end %>
-        <%= link_to(
-              choose_pipeline_templates_path(
-                title: 'Choose a pipeline to run:',
-                action_name: 'Next: choose inputs <i class="fa fa-fw fa-arrow-circle-right"></i>',
-                action_href: pipeline_instances_path,
-                action_method: 'post',
-                action_data: {'selection_param' => 'pipeline_instance[pipeline_template_uuid]', 'pipeline_instance[owner_uuid]' => @object.uuid, 'success' => 'redirect-to-created-object'}.to_json),
-              { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %>
-          <i class="fa fa-fw fa-gear"></i> Run a pipeline
-        <% end %>
-      <% end %>
     </div>
     <div class="col-sm-3">
       <form class="form-inline" role="form">
diff --git a/apps/workbench/app/views/projects/_show_permissions.html.erb b/apps/workbench/app/views/projects/_show_permissions.html.erb
index 9d93ce0..0817062 100644
--- a/apps/workbench/app/views/projects/_show_permissions.html.erb
+++ b/apps/workbench/app/views/projects/_show_permissions.html.erb
@@ -1,6 +1,6 @@
 <div>
   <div class="row">
-    <div class="col-md-6">
+    <div class="col-md-6 col-md-push-6">
       <div class="panel panel-default">
         <div class="panel-heading">
           Additional permissions
@@ -19,7 +19,7 @@
         </div>
       </div>
     </div>
-    <div class="col-md-6">
+    <div class="col-md-6 col-md-pull-6">
       <% if @object.owner %>
         <div class="panel panel-default">
           <div class="panel-heading">
@@ -35,15 +35,6 @@
                 <i class="fa fa-fw fa-folder"></i>
               <% end %>
               <%= link_to_if_arvados_object @object.owner_uuid, friendly_name: true %>
-              <%= button_to('Move to...',
-                  choose_projects_path(
-                   title: 'Move to...',
-                   editable: true,
-                   action_name: 'Move',
-                   action_href: project_path(@object.uuid),
-                   action_method: 'put',
-                   action_data: {selection_param: 'project[owner_uuid]', success: 'page-refresh'}.to_json),
-                  { class: "btn btn-default btn-xs arv-move-to-project", remote: true, method: 'get' }) %>
             </p>
           </div>
         </div>

commit b64228f1f547c720b79a242d6d8d368a64923885
Author: Tom Clegg <tom at curoverse.com>
Date:   Thu Jun 12 11:29:01 2014 -0400

    2872: Show projectless jobs/pipelines in "recent" list too.

diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb
index 6457cd0..3a9ccf2 100644
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@ -587,9 +587,8 @@ class ApplicationController < ActionController::Base
 
   helper_method :recent_jobs_and_pipelines
   def recent_jobs_and_pipelines
-    in_my_projects = ['owner_uuid','in',my_projects.collect(&:uuid)]
-    (Job.limit(10).filter([in_my_projects]) |
-     PipelineInstance.limit(10).filter([in_my_projects])).
+    (Job.limit(10) |
+     PipelineInstance.limit(10)).
       sort_by do |x|
       x.finished_at || x.started_at || x.created_at rescue x.created_at
     end

commit 70542a2d6d6a2792f1dc68b1531e783e7611b474
Author: Tom Clegg <tom at curoverse.com>
Date:   Thu Jun 12 10:40:00 2014 -0400

    2872: Fix tests broken in merge.

diff --git a/apps/workbench/app/controllers/projects_controller.rb b/apps/workbench/app/controllers/projects_controller.rb
index f73b6c0..89a50b2 100644
--- a/apps/workbench/app/controllers/projects_controller.rb
+++ b/apps/workbench/app/controllers/projects_controller.rb
@@ -91,6 +91,9 @@ class ProjectsController < ApplicationController
   end
 
   def show
+    if !@object
+      return render_not_found("object not found")
+    end
     @objects = @object.contents(limit: 50,
                                 include_linked: true,
                                 offset: params[:offset] || 0)
diff --git a/apps/workbench/app/views/application/_content.html.erb b/apps/workbench/app/views/application/_content.html.erb
index b7f27df..0cd3a2c 100644
--- a/apps/workbench/app/views/application/_content.html.erb
+++ b/apps/workbench/app/views/application/_content.html.erb
@@ -26,7 +26,7 @@
     if (!tab_pane_valid_state[pane]) {
       tab_pane_valid_state[pane] = true;
       $(document).trigger('ajax:send');
-      $.ajax('<%=j url_for @object %>?tab_pane='+pane, {dataType: 'html', type: 'GET'}).
+      $.ajax('<%=j url_for() %>?tab_pane='+pane, {dataType: 'html', type: 'GET'}).
         done(function(data, status, jqxhr) {
           $('#' + pane + ' > div > div').html(data);
           $(document).trigger('ajax:complete');
diff --git a/apps/workbench/app/views/collections/show.html.erb b/apps/workbench/app/views/collections/show.html.erb
index f91357b..bf2e7aa 100644
--- a/apps/workbench/app/views/collections/show.html.erb
+++ b/apps/workbench/app/views/collections/show.html.erb
@@ -4,7 +4,7 @@
       <div class="panel-heading">
 	<h3 class="panel-title">
           <% i = 0 %>
-            <% @folder_links.each do |l| %>
+            <% @project_links.each do |l| %>
               <%= if i > 0 then ', ' end %>
               <% i += 1 %>
               <%= l.name %>
diff --git a/apps/workbench/app/views/layouts/application.html.erb b/apps/workbench/app/views/layouts/application.html.erb
index ec26409..c42abfe 100644
--- a/apps/workbench/app/views/layouts/application.html.erb
+++ b/apps/workbench/app/views/layouts/application.html.erb
@@ -99,8 +99,8 @@
           </li>
           <% end %>
 
-          <li class="dropdown notification-menu">
-            <a href="#" class="dropdown-toggle" data-toggle="dropdown" id="collections-menu">
+          <li class="dropdown selection-menu">
+            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
               <span class="fa fa-lg fa-paperclip"></span>
               <span class="badge" id="persistent-selection-count"></span>
             </a>
diff --git a/apps/workbench/test/integration/pipeline_instances_test.rb b/apps/workbench/test/integration/pipeline_instances_test.rb
index f0620cb..a85fd99 100644
--- a/apps/workbench/test/integration/pipeline_instances_test.rb
+++ b/apps/workbench/test/integration/pipeline_instances_test.rb
@@ -96,7 +96,7 @@ class PipelineInstancesTest < ActionDispatch::IntegrationTest
     visit '/projects'
     find('.arv-project-list a,button', text: 'A Project').click
 
-    find('#collections-menu').click
+    find('li.selection-menu > a').click
     click_button 'Copy selections into this project'
 
     # create a pipeline instance
diff --git a/services/api/test/fixtures/links.yml b/services/api/test/fixtures/links.yml
index 397659e..1729bd2 100644
--- a/services/api/test/fixtures/links.yml
+++ b/services/api/test/fixtures/links.yml
@@ -247,7 +247,7 @@ foo_repository_writable_by_active:
   tail_uuid: zzzzz-tpzed-xurymjxw79nv3jz
   link_class: permission
   name: can_write
-  head_uuid: zzzzz-2x53u-382brsig8rp3666
+  head_uuid: zzzzz-s0uqq-382brsig8rp3666
   properties: {}
 
 miniadmin_user_is_a_testusergroup_admin:

commit a959f21c8147f26362df392bc3fd3290db69de85
Merge: 70f1004 a9beb0e
Author: Tom Clegg <tom at curoverse.com>
Date:   Wed Jun 11 23:57:37 2014 -0400

    2872: Merge branch 'master' into 2872-folder-nav
    
    Conflicts:
    	apps/workbench/app/assets/javascripts/pipeline_instances.js
    	apps/workbench/app/controllers/application_controller.rb
    	apps/workbench/app/controllers/collections_controller.rb
    	apps/workbench/app/controllers/jobs_controller.rb
    	apps/workbench/app/helpers/application_helper.rb
    	apps/workbench/app/models/job.rb
    	apps/workbench/app/views/application/_content.html.erb
    	apps/workbench/app/views/application/_show_metadata.html.erb
    	apps/workbench/app/views/pipeline_instances/_show_components.html.erb

diff --cc apps/workbench/app/assets/javascripts/pipeline_instances.js
index 54595ab,c61e336..f206213
--- a/apps/workbench/app/assets/javascripts/pipeline_instances.js
+++ b/apps/workbench/app/assets/javascripts/pipeline_instances.js
@@@ -47,51 -47,19 +47,42 @@@ $(document).on('ready ajax:complete', f
      run_pipeline_button_state();
  });
  
- $(document).on('ajax:complete ready', function() {
-   var a = $('.arv-log-event-listener');
-   if (a.length > 0) {
-     $('.arv-log-event-listener').each(function() {
-       subscribeToEventLog(this.id);
-     });
-   }
- });
- 
  $(document).on('arv-log-event', '.arv-log-event-handler-append-logs', function(event, eventData){
 -  var parsedData = JSON.parse(eventData);
 +    var wasatbottom = ($(this).scrollTop() + $(this).height() >=
 +                       this.scrollHeight);
 +    var parsedData = JSON.parse(eventData);
 +    var propertyText = undefined;
 +    var properties = parsedData.properties;
  
 -  var propertyText = undefined
 -
 -  var properties = parsedData.properties;
      if (properties !== null) {
 -      propertyText = properties.text;
 +        propertyText = properties.text;
      }
 -
      if (propertyText !== undefined) {
 -      $(this).append(propertyText + "<br/>");
 +        $(this).append(propertyText + "<br/>");
      } else {
 -      $(this).append(parsedData.summary + "<br/>");
 +        $(this).append(parsedData.summary + "<br/>");
      }
 +    if (wasatbottom)
 +        this.scrollTop = this.scrollHeight;
 +}).on('ready ajax:complete', function(){
 +    $('.arv-log-event-handler-append-logs').each(function() {
 +        this.scrollTop = this.scrollHeight;
 +    });
  });
 +
 +var showhide_compare = function() {
 +    var form = $('form#compare')[0];
 +    $('input[type=hidden][name="uuids[]"]', form).remove();
 +    $('input[type=submit]', form).prop('disabled',true).show();
 +    var checked_inputs = $('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]:checked');
 +    if (checked_inputs.length >= 2 && checked_inputs.length <= 3) {
 +        checked_inputs.each(function(){
 +            if(this.checked) {
 +                $('input[type=submit]', form).prop('disabled',false).show();
 +                $(form).append($('<input type="hidden" name="uuids[]"/>').val(this.value));
 +            }
 +        });
 +    }
 +};
 +$('[data-object-uuid*=-d1hrv-] input[name="uuids[]"]').on('click', showhide_compare);
 +showhide_compare();
diff --cc apps/workbench/app/controllers/application_controller.rb
index 48b508a,a0cadb2..6457cd0
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@@ -85,32 -100,10 +85,44 @@@ class ApplicationController < ActionCon
      end
  
      @objects ||= model_class
 -    @objects = @objects.filter(@filters).limit(@limit).offset(@offset).all
 +    @objects = @objects.filter(@filters).limit(@limit).offset(@offset)
 +  end
 +
++  def render_index
++    respond_to do |f|
++      f.json { render json: @objects }
++      f.html {
++        if params['tab_pane']
++          comparable = self.respond_to? :compare
++          render(partial: 'show_' + params['tab_pane'].downcase,
++                 locals: { comparable: comparable, objects: @objects })
++        else
++          render
++        end
++      }
++      f.js { render }
++    end
++  end
++
++  def index
++    find_objects_for_index if !@objects
+     render_index
+   end
+ 
 +  helper_method :next_page_offset
 +  def next_page_offset
 +    if @objects.respond_to?(:result_offset) and
 +        @objects.respond_to?(:result_limit) and
 +        @objects.respond_to?(:items_available)
 +      next_offset = @objects.result_offset + @objects.result_limit
 +      if next_offset < @objects.items_available
 +        next_offset
 +      else
 +        nil
 +      end
 +    end
 +  end
 +
-   def index
-     find_objects_for_index if !@objects
-     respond_to do |f|
-       f.json { render json: @objects }
-       f.html { render }
-       f.js { render }
-     end
-   end
- 
    def show
      if !@object
        return render_not_found("object not found")
@@@ -118,10 -111,16 +130,14 @@@
      respond_to do |f|
        f.json { render json: @object.attributes.merge(href: url_for(@object)) }
        f.html {
-         if request.method.in? ['GET', 'HEAD']
+         if params['tab_pane']
+           comparable = self.respond_to? :compare
+           render(partial: 'show_' + params['tab_pane'].downcase,
+                  locals: { comparable: comparable, objects: @objects })
++        elsif request.method.in? ['GET', 'HEAD']
 +          render
          else
 -          if request.method == 'GET'
 -            render
 -          else
 -            redirect_to params[:return_to] || @object
 -          end
 +          redirect_to params[:return_to] || @object
          end
        }
        f.js { render }
@@@ -186,29 -163,20 +202,38 @@@
      @new_resource_attrs ||= params[model_class.to_s.underscore.singularize]
      @new_resource_attrs ||= {}
      @new_resource_attrs.reject! { |k,v| k.to_s == 'uuid' }
-     @object ||= model_class.new @new_resource_attrs
-     @object.save!
-     show
+     @object ||= model_class.new @new_resource_attrs, params["options"]
+     if @object.save
+       respond_to do |f|
+         f.json { render json: @object.attributes.merge(href: url_for(@object)) }
+         f.html {
+           redirect_to @object
+         }
+         f.js { render }
+       end
+     else
+       self.render_error status: 422
+     end
    end
  
 +  # Clone the given object, merging any attribute values supplied as
 +  # with a create action.
 +  def copy
 +    @new_resource_attrs ||= params[model_class.to_s.underscore.singularize]
 +    @new_resource_attrs ||= {}
 +    @object = @object.dup
 +    @object.update_attributes @new_resource_attrs
 +    if not @new_resource_attrs[:name] and @object.respond_to? :name
 +      if @object.name and @object.name != ''
 +        @object.name = "Copy of #{@object.name}"
 +      else
 +        @object.name = "Copy of unnamed #{@object.class_for_display.downcase}"
 +      end
 +    end
 +    @object.save!
 +    show
 +  end
 +
    def destroy
      if @object.destroy
        respond_to do |f|
@@@ -325,7 -297,17 +360,17 @@@
          # call to verify its authenticity.
          if verify_api_token
            session[:arvados_api_token] = params[:api_token]
+           u = User.current
+           session[:user] = {
+             uuid: u.uuid,
+             email: u.email,
+             first_name: u.first_name,
+             last_name: u.last_name,
+             is_active: u.is_active,
+             is_admin: u.is_admin,
+             prefs: u.prefs
+           }
 -          if !request.format.json? and request.method == 'GET'
 +          if !request.format.json? and request.method.in? ['GET', 'HEAD']
              # Repeat this request with api_token in the (new) session
              # cookie instead of the query string.  This prevents API
              # tokens from appearing in (and being inadvisedly copied
@@@ -532,60 -498,171 +579,228 @@@
      end
    end
  
 +  helper_method :projects_shared_with_me
 +  def projects_shared_with_me
 +    my_project_uuids = my_projects.collect &:uuid
 +    all_projects.reject { |x| x.uuid.in? my_project_uuids }
 +  end
 +
 +  helper_method :recent_jobs_and_pipelines
 +  def recent_jobs_and_pipelines
 +    in_my_projects = ['owner_uuid','in',my_projects.collect(&:uuid)]
 +    (Job.limit(10).filter([in_my_projects]) |
 +     PipelineInstance.limit(10).filter([in_my_projects])).
 +      sort_by do |x|
 +      x.finished_at || x.started_at || x.created_at rescue x.created_at
 +    end
 +  end
 +
 +  helper_method :get_object
 +  def get_object uuid
 +    if @get_object.nil? and @objects
 +      @get_object = @objects.each_with_object({}) do |object, h|
 +        h[object.uuid] = object
 +      end
 +    end
 +    @get_object ||= {}
 +    @get_object[uuid]
 +  end
 +
 +  helper_method :project_breadcrumbs
 +  def project_breadcrumbs
 +    crumbs = []
 +    current = @name_link || @object
 +    while current
 +      if current.is_a?(Group) and current.group_class.in?(['project','folder'])
 +        crumbs.prepend current
 +      end
 +      if current.is_a? Link
 +        current = Group.find?(current.tail_uuid)
 +      else
 +        current = Group.find?(current.owner_uuid)
 +      end
 +    end
 +    crumbs
 +  end
 +
 +  helper_method :current_project_uuid
 +  def current_project_uuid
 +    if @object.is_a? Group and @object.group_class.in?(['project','folder'])
 +      @object.uuid
 +    elsif @name_link.andand.tail_uuid
 +      @name_link.tail_uuid
 +    elsif @object and resource_class_for_uuid(@object.owner_uuid) == Group
 +      @object.owner_uuid
 +    else
 +      nil
 +    end
 +  end
++
+   # helper method to get links for given object or uuid
+   helper_method :links_for_object
+   def links_for_object object_or_uuid
+     raise ArgumentError, 'No input argument' unless object_or_uuid
+     preload_links_for_objects([object_or_uuid])
+     uuid = object_or_uuid.is_a?(String) ? object_or_uuid : object_or_uuid.uuid
+     @all_links_for[uuid] ||= []
+   end
+ 
+   # helper method to preload links for given objects and uuids
+   helper_method :preload_links_for_objects
+   def preload_links_for_objects objects_and_uuids
+     @all_links_for ||= {}
+ 
+     raise ArgumentError, 'Argument is not an array' unless objects_and_uuids.is_a? Array
+     return @all_links_for if objects_and_uuids.empty?
+ 
+     uuids = objects_and_uuids.collect { |x| x.is_a?(String) ? x : x.uuid }
+ 
+     # if already preloaded for all of these uuids, return
+     if not uuids.select { |x| @all_links_for[x].nil? }.any?
+       return @all_links_for
+     end
+ 
+     uuids.each do |x|
+       @all_links_for[x] = []
+     end
+ 
+     # TODO: make sure we get every page of results from API server
+     Link.filter([['head_uuid', 'in', uuids]]).each do |link|
+       @all_links_for[link.head_uuid] << link
+     end
+     @all_links_for
+   end
+ 
+   # helper method to get a certain number of objects of a specific type
+   # this can be used to replace any uses of: "dataclass.limit(n)"
+   helper_method :get_n_objects_of_class
+   def get_n_objects_of_class dataclass, size
+     @objects_map_for ||= {}
+ 
+     raise ArgumentError, 'Argument is not a data class' unless dataclass.is_a? Class
+     raise ArgumentError, 'Argument is not a valid limit size' unless (size && size>0)
+ 
+     # if the objects_map_for has a value for this dataclass, and the
+     # size used to retrieve those objects is equal, return it
+     size_key = "#{dataclass.name}_size"
+     if @objects_map_for[dataclass.name] && @objects_map_for[size_key] &&
+         (@objects_map_for[size_key] == size)
+       return @objects_map_for[dataclass.name]
+     end
+ 
+     @objects_map_for[size_key] = size
+     @objects_map_for[dataclass.name] = dataclass.limit(size)
+   end
+ 
+   # helper method to get collections for the given uuid
+   helper_method :collections_for_object
+   def collections_for_object uuid
+     raise ArgumentError, 'No input argument' unless uuid
+     preload_collections_for_objects([uuid])
+     @all_collections_for[uuid] ||= []
+   end
+ 
+   # helper method to preload collections for the given uuids
+   helper_method :preload_collections_for_objects
+   def preload_collections_for_objects uuids
+     @all_collections_for ||= {}
+ 
+     raise ArgumentError, 'Argument is not an array' unless uuids.is_a? Array
+     return @all_collections_for if uuids.empty?
+ 
+     # if already preloaded for all of these uuids, return
+     if not uuids.select { |x| @all_collections_for[x].nil? }.any?
+       return @all_collections_for
+     end
+ 
+     uuids.each do |x|
+       @all_collections_for[x] = []
+     end
+ 
+     # TODO: make sure we get every page of results from API server
+     Collection.where(uuid: uuids).each do |collection|
+       @all_collections_for[collection.uuid] << collection
+     end
+     @all_collections_for
+   end
+ 
+   # helper method to get log collections for the given log
+   helper_method :log_collections_for_object
+   def log_collections_for_object log
+     raise ArgumentError, 'No input argument' unless log
+ 
+     preload_log_collections_for_objects([log])
+ 
+     uuid = log
+     fixup = /([a-f0-9]{32}\+\d+)(\+?.*)/.match(log)
+     if fixup && fixup.size>1
+       uuid = fixup[1]
+     end
+ 
+     @all_log_collections_for[uuid] ||= []
+   end
+ 
+   # helper method to preload collections for the given uuids
+   helper_method :preload_log_collections_for_objects
+   def preload_log_collections_for_objects logs
+     @all_log_collections_for ||= {}
+ 
+     raise ArgumentError, 'Argument is not an array' unless logs.is_a? Array
+     return @all_log_collections_for if logs.empty?
+ 
+     uuids = []
+     logs.each do |log|
+       fixup = /([a-f0-9]{32}\+\d+)(\+?.*)/.match(log)
+       if fixup && fixup.size>1
+         uuids << fixup[1]
+       else
+         uuids << log
+       end
+     end
+ 
+     # if already preloaded for all of these uuids, return
+     if not uuids.select { |x| @all_log_collections_for[x].nil? }.any?
+       return @all_log_collections_for
+     end
+ 
+     uuids.each do |x|
+       @all_log_collections_for[x] = []
+     end
+ 
+     # TODO: make sure we get every page of results from API server
+     Collection.where(uuid: uuids).each do |collection|
+       @all_log_collections_for[collection.uuid] << collection
+     end
+     @all_log_collections_for
+   end
+ 
+   # helper method to get object of a given dataclass and uuid
+   helper_method :object_for_dataclass
+   def object_for_dataclass dataclass, uuid
+     raise ArgumentError, 'No input argument dataclass' unless (dataclass && uuid)
+     preload_objects_for_dataclass(dataclass, [uuid])
+     @objects_for[uuid]
+   end
+ 
+   # helper method to preload objects for given dataclass and uuids
+   helper_method :preload_objects_for_dataclass
+   def preload_objects_for_dataclass dataclass, uuids
+     @objects_for ||= {}
+ 
+     raise ArgumentError, 'Argument is not a data class' unless dataclass.is_a? Class
+     raise ArgumentError, 'Argument is not an array' unless uuids.is_a? Array
+ 
+     return @objects_for if uuids.empty?
+ 
+     # if already preloaded for all of these uuids, return
+     if not uuids.select { |x| @objects_for[x].nil? }.any?
+       return @objects_for
+     end
+ 
+     dataclass.where(uuid: uuids).each do |obj|
+       @objects_for[obj.uuid] = obj
+     end
+     @objects_for
+   end
+ 
  end
diff --cc apps/workbench/app/controllers/collections_controller.rb
index 0148b72,6a5df87..88dadbb
--- a/apps/workbench/app/controllers/collections_controller.rb
+++ b/apps/workbench/app/controllers/collections_controller.rb
@@@ -157,10 -143,10 +159,10 @@@ class CollectionsController < Applicati
        end
        @output_of = jobs_with.call(output: @object.uuid)
        @log_of = jobs_with.call(log: @object.uuid)
-       project_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
 -      @folder_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
++      @project_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
          .where(head_uuid: @object.uuid, link_class: 'name').results
-       project_hash = Group.where(uuid: project_links.map(&:tail_uuid)).to_hash
-       @projects = project_links.map { |link| project_hash[link.tail_uuid] }
 -      folder_hash = Group.where(uuid: @folder_links.map(&:tail_uuid)).to_hash
 -      @folders = @folder_links.map { |link| folder_hash[link.tail_uuid] }
++      project_hash = Group.where(uuid: @project_links.map(&:tail_uuid)).to_hash
++      @projects = @project_links.map { |link| project_hash[link.tail_uuid] }
        @permissions = Link.limit(RELATION_LIMIT).order("modified_at DESC")
          .where(head_uuid: @object.uuid, link_class: 'permission',
                 name: 'can_read').results
diff --cc apps/workbench/app/controllers/jobs_controller.rb
index 8743a6f,b7526c9..ff3ac6b
--- a/apps/workbench/app/controllers/jobs_controller.rb
+++ b/apps/workbench/app/controllers/jobs_controller.rb
@@@ -44,6 -53,6 +53,6 @@@ class JobsController < ApplicationContr
    end
  
    def show_pane_list
-     %w(Details Provenance Advanced)
 -    %w(Status Attributes Provenance Metadata JSON API)
++    %w(Status Details Provenance Advanced)
    end
  end
diff --cc apps/workbench/app/controllers/pipeline_instances_controller.rb
index ccbd06f,500927b..a4a9d69
--- a/apps/workbench/app/controllers/pipeline_instances_controller.rb
+++ b/apps/workbench/app/controllers/pipeline_instances_controller.rb
@@@ -3,44 -3,10 +3,46 @@@ class PipelineInstancesController < App
    before_filter :find_objects_by_uuid, only: :compare
    include PipelineInstancesHelper
  
 +  def copy
 +    @object = @object.dup
 +    @object.components.each do |cname, component|
 +      component.delete :job
 +    end
 +    @object.state = 'New'
 +    super
 +  end
 +
 +  def update
 +    @updates ||= params[@object.class.to_s.underscore.singularize.to_sym]
 +    if (components = @updates[:components])
 +      components.each do |cname, component|
 +        if component[:script_parameters]
 +          component[:script_parameters].each do |param, value_info|
 +            if value_info.is_a? Hash
 +              if resource_class_for_uuid(value_info[:value]) == Link
 +                # Use the link target, not the link itself, as script
 +                # parameter; but keep the link info around as well.
 +                link = Link.find value_info[:value]
 +                value_info[:value] = link.head_uuid
 +                value_info[:link_uuid] = link.uuid
 +                value_info[:link_name] = link.name
 +              else
 +                # Delete stale link_uuid and link_name data.
 +                value_info[:link_uuid] = nil
 +                value_info[:link_name] = nil
 +              end
 +            end
 +          end
 +        end
 +      end
 +    end
 +    super
 +  end
 +
    def graph(pipelines)
-     count = {}    
+     return nil, nil if params['tab_pane'] != "Graph"
+ 
+     count = {}
      provenance = {}
      pips = {}
      n = 1
diff --cc apps/workbench/app/helpers/application_helper.rb
index 784958b,2b7ec14..66267e0
--- a/apps/workbench/app/helpers/application_helper.rb
+++ b/apps/workbench/app/helpers/application_helper.rb
@@@ -294,12 -236,37 +300,28 @@@ module ApplicationHelpe
        datatype = 'text'
      end
  
 -    id = "#{object.uuid}-#{subattr.join('-')}"
 -    dn = "[#{attr}]"
 -    subattr.each do |a|
 -      dn += "[#{a}]"
 -    end
 -    if value_info.is_a? Hash
 -      dn += '[value]'
 -    end
 -
+     # preload data
+     preload_uuids = []
+     items = []
      selectables = []
+ 
      attrtext = attrvalue
      if dataclass and dataclass.is_a? Class
+       objects = get_n_objects_of_class dataclass, 10
+       objects.each do |item|
+         items << item
+         preload_uuids << item.uuid
+       end
        if attrvalue and !attrvalue.empty?
-         Link.where(head_uuid: attrvalue, link_class: ["tag", "identifier"]).each do |tag|
-           attrtext += " [#{tag.name}]"
+         preload_uuids << attrvalue
+       end
+       preload_links_for_objects preload_uuids
+ 
+       if attrvalue and !attrvalue.empty?
+         links_for_object(attrvalue).each do |link|
+           if link.link_class.in? ["tag", "identifier"]
+             attrtext += " [#{link.name}]"
+           end
          end
          selectables.append({name: attrtext, uuid: attrvalue, type: dataclass.to_s})
        end
diff --cc apps/workbench/app/models/job.rb
index 2a69c28,173d3a0..aac6168
--- a/apps/workbench/app/models/job.rb
+++ b/apps/workbench/app/models/job.rb
@@@ -15,18 -11,7 +15,22 @@@ class Job < ArvadosBas
      false
    end
  
 +  def default_name
 +    if script
 +      x = "\"#{script}\" job"
 +    else
 +      x = super
 +    end
 +    if finished_at
 +      x += " finished #{finished_at.strftime('%b %-d')}"
 +    elsif started_at
 +      x += " started #{started_at.strftime('%b %-d')}"
 +    elsif created_at
 +      x += " submitted #{created_at.strftime('%b %-d')}"
 +    end
 +  end
++
+   def cancel
+     arvados_api_client.api "jobs/#{self.uuid}/", "cancel", {}
+   end
  end
diff --cc apps/workbench/app/views/application/_content.html.erb
index e634362,353bd74..b7f27df
--- a/apps/workbench/app/views/application/_content.html.erb
+++ b/apps/workbench/app/views/application/_content.html.erb
@@@ -1,24 -1,34 +1,55 @@@
 +<% content_for :content_top do %>
 +  <% if @object and not @object.is_a?(Group) and @object.class.goes_in_projects? and @object.owner_uuid == current_user.uuid %>
 +    <div class="pull-right" style="width: 40%">
 +      <div class="alert alert-warning alert-dismissable">
 +        <button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
 +        <strong>Hey.</strong> This <%= @object.class_for_display.downcase %> belongs to your account, but it's not in any of your projects. If you want it to be easy to find in the future, you should move it to a project.<br />
 +        <%= button_to(choose_projects_path(
 +                   title: 'Move to...',
 +                   editable: true,
 +                   action_name: 'Move',
 +                   action_href: url_for(action: :update),
 +                   action_method: 'patch',
 +                   action_data: {selection_param: @object.resource_param_name+'[owner_uuid]', success: 'page-refresh'}.to_json),
 +                  { class: "btn btn-primary btn-sm", remote: true, method: 'get' }) do %>
 +          <i class="fa fa-fw fa-folder"></i> Choose a project...
 +        <% end %>
 +      </div>
 +    </div>
 +  <% end %>
 +<% end %>
 +
+ <% content_for :js do %>
+   tab_pane_valid_state = {};
+ 
+   function ajaxRefreshTabPane(pane) {
+     if (!tab_pane_valid_state[pane]) {
+       tab_pane_valid_state[pane] = true;
+       $(document).trigger('ajax:send');
+       $.ajax('<%=j url_for @object %>?tab_pane='+pane, {dataType: 'html', type: 'GET'}).
+         done(function(data, status, jqxhr) {
+           $('#' + pane + ' > div > div').html(data);
+           $(document).trigger('ajax:complete');
+           ajaxRefreshTabPane(pane);
+         });
+     }
+   }
+ 
+   $(window).on('load', smart_scroll_fixup);
+   $(document).on('shown.bs.tab', 'ul.nav-tabs > li > a', smart_scroll_fixup);
+ 
+   $(document).on('shown.bs.tab', function(e) {
+     ajaxRefreshTabPane(e.target.id.slice(0, -4));
+   });
+ 
+   $(document).on('arv-log-event', function() {
+     <% pane_list.each do |pane| %>
+     tab_pane_valid_state['<%=j pane %>'] = false;
+     <% end %>
+     ajaxRefreshTabPane($('.tab-pane.active')[0].id);
+   });
+ <% end %>
+ 
  <% content_for :tab_panes do %>
  
  <% comparable = controller.respond_to? :compare %>
@@@ -34,10 -39,36 +60,36 @@@
    <% end %>
  </ul>
  <div class="tab-content">
- <% panes.each_with_index do |(pane, content), i| %>
-   <div id="<%= pane %>" class="tab-pane fade <%= 'in active' if i==0 %>">
+ <% pane_list.each_with_index do |pane, i| %>
+   <div id="<%= pane %>"
+        class="tab-pane fade <%= 'in active' if i==0 %> arv-log-event-listener"
+ <% if controller.action_name == "index" %>
+        data-object-kind="arvados#<%= ArvadosApiClient.class_kind controller.model_class %>"
+ <% else %>
+        data-object-uuid="<%= @object.uuid %>"
+ <% end %>
+ >
+ 
+ <% content_for :js do %>
+   <% if i == 0 %>
+     tab_pane_valid_state['<%=j pane %>'] = true;
+   <% else %>
+     tab_pane_valid_state['<%=j pane %>'] = false;
+     $(document).on('ready', function() {
+       ajaxRefreshTabPane('<%=j pane %>');
+     });
+   <% end %>
+ <% end %>
+ 
 -    <div class="smart-scroll" style="margin-top:0.5em;">
 +    <div id="<%= pane %>-scroll" class="<%= 'smart-scroll' if pane.match(/graph/) %>" style="margin-top:0.5em;">
-       <%= content %>
+       <div class="pane-content">
+         <% if i == 0 %>
+           <%= render(partial: 'show_' + pane.downcase,
+                      locals: { comparable: comparable, objects: @objects }) %>
+           <% else %>
+             <%= image_tag 'ajax-loader.gif' %>
+         <% end %>
+       </div>
      </div>
    </div>
  <% end %>
diff --cc apps/workbench/app/views/application/_show_advanced_metadata.html.erb
index 68e4298,0000000..c036b36
mode 100644,000000..100644
--- a/apps/workbench/app/views/application/_show_advanced_metadata.html.erb
+++ b/apps/workbench/app/views/application/_show_advanced_metadata.html.erb
@@@ -1,44 -1,0 +1,56 @@@
 +<% outgoing = Link.where(tail_uuid: @object.uuid) %>
 +<% incoming = Link.where(head_uuid: @object.uuid) %>
 +
++<%
++  preload_uuids = []
++  preload_head_uuids = []
++  outgoing.results.each do |link|
++    preload_uuids << link.uuid
++    preload_uuids << link.head_uuid
++    preload_head_uuids << link.head_uuid
++  end
++  preload_collections_for_objects preload_uuids
++  preload_links_for_objects preload_head_uuids
++%>
++
 +<% if (outgoing | incoming).any? %>
 +<table class="table topalign">
 +  <colgroup>
 +    <col width="20%" />
 +    <col width="10%" />
 +    <col width="10%" />
 +    <col width="20%" />
 +    <col width="20%" />
 +    <col width="20%" />
 +  </colgroup>
 +  <thead>
 +    <tr>
 +      <th></th>
 +      <th>link_class</th>
 +      <th>name</th>
 +      <th>tail</th>
 +      <th>head</th>
 +      <th>properties</th>
 +    </tr>
 +  </thead>
 +  <tbody>
 +    <% (outgoing | incoming).each do |link| %>
 +      <tr>
 +        <td>
 +          <%= render partial: 'show_object_button', locals: { object: link, size: 'xs' } %>
 +          <span class="arvados-uuid"><%= link.uuid %></span>
 +        </td>
 +        <td><%= link.link_class %></td>
 +        <td><%= link.name %></td>
 +        <td><%= link.tail_uuid == object.uuid ? 'this' : (render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "tail_uuid", attrvalue: link.tail_uuid, editable: false }) %></td>
 +        <td><%= link.head_uuid == object.uuid ? 'this' : (render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "head_uuid", attrvalue: link.head_uuid, editable: false }) %></td>
 +        <td><%= render partial: 'application/arvados_attr_value', locals: { obj: link, attr: "properties", attrvalue: link.properties, editable: false } %></td>
 +      </tr>
 +    <% end %>
 +  </tbody>
 +</table>
 +<% else %>
 +<span class="deemphasize">
 +  (No metadata links found)
 +</span>
 +<% end %>

commit 70f1004b17fac3c772c8f938b5a72ff3a3ebc67a
Author: Tom Clegg <tom at curoverse.com>
Date:   Wed Jun 11 23:31:41 2014 -0400

    2872: Fix bookmark bar causing spurious window width.

diff --git a/apps/workbench/app/assets/stylesheets/application.css.scss b/apps/workbench/app/assets/stylesheets/application.css.scss
index f507ccd..c01195c 100644
--- a/apps/workbench/app/assets/stylesheets/application.css.scss
+++ b/apps/workbench/app/assets/stylesheets/application.css.scss
@@ -104,8 +104,9 @@ nav.navbar-fixed-top {
 .navbar.breadcrumbs {
     line-height: 50px;
     border-radius: 0;
-    width: 10000px;
     margin-bottom: 0;
+    border-right: 0;
+    border-left: 0;
 }
 .navbar.breadcrumbs .nav > li > a,
 .navbar.breadcrumbs .nav > li {

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list