Merge branch '4000-rerun-pipeline-changed-template'
authorTim Pierce <twp@curoverse.com>
Wed, 1 Oct 2014 18:59:30 +0000 (14:59 -0400)
committerTim Pierce <twp@curoverse.com>
Wed, 1 Oct 2014 19:11:12 +0000 (15:11 -0400)
Closes #4000.

18 files changed:
apps/workbench/app/controllers/application_controller.rb
apps/workbench/app/helpers/application_helper.rb
apps/workbench/app/models/arvados_base.rb
apps/workbench/app/models/collection.rb
apps/workbench/app/models/node.rb
apps/workbench/app/models/pipeline_instance.rb
apps/workbench/app/models/user.rb
apps/workbench/app/models/virtual_machine.rb
apps/workbench/app/views/projects/_show_dashboard.html.erb
apps/workbench/test/integration/pipeline_instances_test.rb
apps/workbench/test/integration/projects_test.rb
sdk/cli/bin/arv-run-pipeline-instance
services/api/app/controllers/application_controller.rb
services/api/app/controllers/arvados/v1/jobs_controller.rb
services/api/test/fixtures/collections.yml
services/api/test/functional/arvados/v1/collections_controller_test.rb
services/api/test/functional/arvados/v1/groups_controller_test.rb
services/fuse/tests/test_mount.py

index 4b775c6036af8bd37c848c2dfc5bf227a41cbfb7..0313111ab9e1f34e75b530b396846111fcc9f568 100644 (file)
@@ -704,14 +704,16 @@ class ApplicationController < ActionController::Base
       end
     end
 
-    Job.filter([["uuid", "in", jobs.keys]]).each do |j|
-      jobs[j[:uuid]] = j
-    end
+    if jobs.keys.any?
+      Job.filter([["uuid", "in", jobs.keys]]).each do |j|
+        jobs[j[:uuid]] = j
+      end
 
-    pi.each do |pl|
-      pl.components.each do |k,v|
-        if v.is_a? Hash and v[:job]
-          v[:job] = jobs[v[:job][:uuid]]
+      pi.each do |pl|
+        pl.components.each do |k,v|
+          if v.is_a? Hash and v[:job]
+            v[:job] = jobs[v[:job][:uuid]]
+          end
         end
       end
     end
index 624cb3d4c0e0991e656a90baf9b0729df86877ff..d343249eaf7bef019fdd5efda4d28ec9521451d2 100644 (file)
@@ -93,7 +93,7 @@ module ApplicationHelper
 
         if opts[:friendly_name]
           if attrvalue.respond_to? :friendly_link_name
-            link_name = attrvalue.friendly_link_name
+            link_name = attrvalue.friendly_link_name opts[:lookup]
           else
             begin
               if resource_class.name == 'Collection'
index b691e6d2bafefaf42c13b9e27711de76f7404cd9..e0e93b9e2d0828cef0149f95fe51c817260ef3c7 100644 (file)
@@ -379,7 +379,7 @@ class ArvadosBase < ActiveRecord::Base
     self.class.to_s.underscore
   end
 
-  def friendly_link_name
+  def friendly_link_name lookup=nil
     (name if self.respond_to? :name) || default_name
   end
 
index 40fd930e38a99ae453ff11e01defcedec35af5fb..87a083e24be4ee62d3e52cc5336bb6516ef2d5b2 100644 (file)
@@ -102,7 +102,7 @@ class Collection < ArvadosBase
     end
   end
 
-  def friendly_link_name
+  def friendly_link_name lookup=nil
     if self.respond_to? :name
       self.name
     else
index 6518047b8d161df66605bb5f34ad61132cd3bb8c..e66be83078c9f5dbe2fff3be09dcba17c5fb4bf9 100644 (file)
@@ -2,7 +2,7 @@ class Node < ArvadosBase
   def self.creatable?
     current_user and current_user.is_admin
   end
-  def friendly_link_name
+  def friendly_link_name lookup=nil
     (hostname && !hostname.empty?) ? hostname : uuid
   end
 end
index 89acbb0dbb9438bbbb80ba2bc97cc1ce93eac9fa..936905713e44891f22261bf6e1c0ba19e598ae75 100644 (file)
@@ -5,10 +5,14 @@ class PipelineInstance < ArvadosBase
     true
   end
 
-  def friendly_link_name
+  def friendly_link_name lookup=nil
     pipeline_name = self.name
     if pipeline_name.nil? or pipeline_name.empty?
-      template = PipelineTemplate.where(uuid: self.pipeline_template_uuid).first
+      template = if lookup and lookup[self.pipeline_template_uuid]
+                   lookup[self.pipeline_template_uuid]
+                 else
+                   PipelineTemplate.where(uuid: self.pipeline_template_uuid).first
+                 end
       if template
         template.name
       else
index 87ea5faefa85976b8d11350b291dffc28b2514d7..967ea2ad7d238aebfc9f8df1d4b6e171949479cf 100644 (file)
@@ -39,7 +39,7 @@ class User < ArvadosBase
     (not (self.uuid.andand.match(/000000000000000$/) and self.is_admin)) and super
   end
 
-  def friendly_link_name
+  def friendly_link_name lookup=nil
     [self.first_name, self.last_name].compact.join ' '
   end
 
index 978964196d412b76f993e46b717a7977ea773ecc..083aae31ecb10c0f7b5b97e0e6d7b9c5129cff2e 100644 (file)
@@ -15,7 +15,7 @@ class VirtualMachine < ArvadosBase
             {current_user_logins: {column_heading: "logins", type: 'array'}},
             super]
   end
-  def friendly_link_name
+  def friendly_link_name lookup=nil
     (hostname && !hostname.empty?) ? hostname : uuid
   end
 end
index d80282544072e34e318c5c7c4709e1231e7920e8..9ddd1d59d84c070f5d8ede4ff596954897f49b83 100644 (file)
@@ -1,7 +1,7 @@
 
   <div class="row">
     <div class="col-md-6">
-      <div class="panel panel-default" style="min-height: 10em">
+      <div class="panel panel-default" style="min-height: 10.5em">
         <div class="panel-heading"><span class="panel-title">Active pipelines</span>
           <span class="pull-right">
     <%= link_to(
     </span>
         </div>
 
+        <% _running_pipelines = running_pipelines %>
+        <% _finished_pipelines = finished_pipelines(8) %>
+        <% lookup = preload_objects_for_dataclass PipelineTemplate, (_running_pipelines.map(&:pipeline_template_uuid) + _finished_pipelines.map(&:pipeline_template_uuid)) %>
+
         <div class="panel-body">
-          <% if running_pipelines.empty? %>
+          <% if _running_pipelines.empty? %>
             No pipelines are currently running.
-          <% end %>
-          <% running_pipelines.each do |p| %>
+          <% else %>
+          <% _running_pipelines.each do |p| %>
             <div class="dashboard-panel-info-row">
               <div class="clearfix">
-                <%= link_to_if_arvados_object p, friendly_name: true %>
+                <%= link_to_if_arvados_object p, {friendly_name: true, lookup: lookup} %>
 
                 <div class="pull-right" style="width: 40%">
                   <div class="progress" style="margin-bottom: 0px">
               </div>
             </div>
           <% end %>
+          <% end %>
           </div>
       </div>
 
       <div class="panel panel-default">
-        <div class="panel-heading"><span class="panel-title">Recently finished pipelines</span></div>
+        <div class="panel-heading"><span class="panel-title">Recently finished pipelines</span>
+          <span class="pull-right">
+            <%= link_to pipeline_instances_path, class: 'btn btn-default btn-xs' do %>
+              All pipelines <i class="fa fa-fw fa-arrow-circle-right"></i>
+            <% end %>
+          </span>
+        </div>
         <div class="panel-body">
-          <% finished_pipelines(8).each do |p| %>
+          <% _finished_pipelines.each do |p| %>
             <div class="dashboard-panel-info-row">
               <div class="row">
                 <div class="col-md-6 text-overflow-ellipsis">
-                  <%= link_to_if_arvados_object p, friendly_name: true %>
+                  <%= link_to_if_arvados_object p, {friendly_name: true, lookup: lookup} %>
                 </div>
                 <div class="col-md-2">
                   <%= render partial: "pipeline_status_label", locals: {p: p}%>
                     <% end %>
                   </div>
                 </div>
-              </div>              
+              </div>
             </div>
           <% end %>
         </div>
 
     <div class="col-md-6">
       <% nodes = Node.all %>
-      <div class="panel panel-default" style="min-height: 10em">
-        <div class="panel-heading"><span class="panel-title">Compute status</span></div>
+      <div class="panel panel-default" style="min-height: 10.5em">
+        <div class="panel-heading"><span class="panel-title">Compute and job status</span>
+          <span class="pull-right">
+            <%= link_to jobs_path, class: 'btn btn-default btn-xs' do %>
+              All jobs <i class="fa fa-fw fa-arrow-circle-right"></i>
+            <% end %>
+          </span>
+        </div>
         <div class="panel-body">
           <div>
             <%= render partial: 'compute_node_summary', locals: {nodes: nodes} %>
         </div>
       </div>
       <div class="panel panel-default">
-        <div class="panel-heading"><span class="panel-title">Recent collections</span></div>
+        <div class="panel-heading"><span class="panel-title">Recent collections</span>
+          <span class="pull-right">
+            <%= link_to collections_path, class: 'btn btn-default btn-xs' do %>
+              All collections <i class="fa fa-fw fa-arrow-circle-right"></i>
+            <% end %>
+          </span>
+        </div>
         <div class="panel-body">
           <% r = recent_collections(8) %>
           <% r[:collections].each do |p| %>
               <i class="fa fa-fw fa-folder-o"></i><%= link_to_if_arvados_object r[:owners][p[:owner_uuid]], friendly_name: true %>/
               <span class="pull-right"><%= render_localized_date(p[:modified_at], "noseconds") %></span>
             </div>
-            <div class="text-overflow-ellipsis" style="margin-left: 1em; width: 100%"><%= link_to_if_arvados_object p, friendly_name: true %>
+            <div class="text-overflow-ellipsis" style="margin-left: 1em; width: 100%"><%= link_to_if_arvados_object p, {friendly_name: true, no_tags: true} %>
             </div>
             </div>
           <% end %>
index 33e581a0ef07a603acf63a1e902fcb44c091e92b..3e8663daa8086dccd0122f4bbfb056150c9c0ff7 100644 (file)
@@ -146,6 +146,47 @@ class PipelineInstancesTest < ActionDispatch::IntegrationTest
     assert_not page.has_text? 'Graph'
   end
 
+  # Create a pipeline instance from within a project and run
+  test 'Run a pipeline from dashboard' do
+    visit page_with_token('active_trustedclient')
+
+    # create a pipeline instance
+    find('.btn', text: 'Run a pipeline').click
+    within('.modal-dialog') do
+      find('.selectable', text: 'Two Part Pipeline Template').click
+      find('.btn', text: 'Next: choose inputs').click
+    end
+
+    assert find('p', text: 'Provide a value')
+
+    find('div.form-group', text: 'Foo/bar pair').
+      find('.btn', text: 'Choose').
+      click
+
+    within('.modal-dialog') do
+      assert_selector 'button.dropdown-toggle', text: 'Home'
+      wait_for_ajax
+      click_button "Home"
+      click_link "A Project"
+      wait_for_ajax
+      first('span', text: 'foo_tag').click
+      find('button', text: 'OK').click
+    end
+    wait_for_ajax
+
+    # "Run" button present and enabled
+    page.assert_no_selector 'a.disabled,button.disabled', text: 'Run'
+    first('a,button', text: 'Run').click
+
+    # Pipeline is running. We have a "Pause" button instead now.
+    page.assert_no_selector 'a,button', text: 'Run'
+    page.assert_selector 'a,button', text: 'Pause'
+
+    # Since it is test env, no jobs are created to run. So, graph not visible
+    assert_not page.has_text? 'Graph'
+  end
+
+
   test 'view pipeline with job and see graph' do
     visit page_with_token('active_trustedclient')
 
index fccf57e0963bfb6c63ffc6853d2c209d577e36ff..ae156b8a5329f311a0c0061b69ed3737a855acef 100644 (file)
@@ -462,4 +462,15 @@ class ProjectsTest < ActionDispatch::IntegrationTest
     assert page.has_link?('Jobs and pipelines'), 'Jobs and pipelines link not found in project'
   end
 
+  [["jobs", "/jobs"],
+   ["pipelines", "/pipeline_instances"],
+   ["collections", "/collections"]
+  ].each do |target,path|
+    test "Test dashboard button all #{target}" do
+      visit page_with_token 'active', '/'
+      click_link "All #{target}"
+      assert_equal path, current_path
+    end
+  end
+
 end
index ded7ab152049308b8dfd685a2190672f72ad89ab..c6ccf842a5c617422412f52dc549e132053e8939 100755 (executable)
@@ -652,8 +652,9 @@ class WhRunPipelineInstance
                 end
               end
             end
-          elsif c[:job][:state] == "Running"
-            # Job is still running
+          elsif ["Queued", "Running"].include? c[:job][:state]
+            # Job is running or queued to run, so indicate that pipeline
+            # should continue to run
             moretodo = true
           elsif c[:job][:state] == "Cancelled"
             debuglog "component #{cname} job #{c[:job][:uuid]} cancelled."
@@ -686,11 +687,11 @@ class WhRunPipelineInstance
       end
     end
 
-    c_in_state = @components.values.group_by { |c| 
+    c_in_state = @components.values.group_by { |c|
       c[:job] and c[:job][:state]
     }
-    succeeded = c_in_state["Complete"].count
-    failed = c_in_state["Failed"].count + c_in_state["Cancelled"].count
+    succeeded = c_in_state["Complete"].andand.count || 0
+    failed = (c_in_state["Failed"].andand.count || 0) + (c_in_state["Cancelled"].andand.count || 0)
     ended = succeeded + failed
 
     success = (succeeded == @components.length)
index 3465a7832050ff08543e80e9441edd2e60b68c74..381c5c991ff640d20b950ae17f5c6377ec5f5dab 100644 (file)
@@ -101,6 +101,7 @@ class ApplicationController < ActionController::Base
             logger.error "params[:ensure_unique_name] is #{params[:ensure_unique_name]}"
             if params[:ensure_unique_name]
               counter += 1
+              @object.uuid = nil
               @object.name = "#{name_stem} (#{counter})"
               retry_save = true
             end
index b157de42fc8130fb8b1a33f694f0bdd71b58c049..2a123951bcf5e3ac5c9e7badecdada5958a41d21 100644 (file)
@@ -65,9 +65,9 @@ class Arvados::V1::JobsController < ApplicationController
       incomplete_job = nil
       @objects.each do |j|
         if j.nondeterministic != true and
-            ((j.success == true and j.output != nil) or j.running == true) and
+            ["Queued", "Running", "Complete"].include?(j.state) and
             j.script_parameters == resource_attrs[:script_parameters]
-          if j.running && j.owner_uuid == current_user.uuid
+          if j.state != "Complete" && j.owner_uuid == current_user.uuid
             # We'll use this if we don't find a job that has completed
             incomplete_job ||= j
           else
index 16da08f06f4ff6249024c30b10152c97c9215493..817fd5c3d0e4bf0552d16f55e89ded63054855d8 100644 (file)
@@ -10,6 +10,18 @@ user_agreement:
   manifest_text: ". 6a4ff0499484c6c79c95cd8c566bd25f+249025 0:249025:GNU_General_Public_License,_version_3.pdf\n"
   name: user_agreement
 
+collection_owned_by_active:
+  uuid: zzzzz-4zz18-bv31uwvy3neko21
+  portable_data_hash: fa7aeb5140e2848d39b416daeef4ffc5+45
+  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: 2014-02-03T17:22:54Z
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-d9tiejq69daie8f
+  modified_at: 2014-02-03T17:22:54Z
+  updated_at: 2014-02-03T17:22:54Z
+  manifest_text: ". 37b51d194a7513e45b56f6524f2d51f2+3 0:3:bar\n"
+  name: owned_by_active
+
 foo_file:
   uuid: zzzzz-4zz18-znfnqtbbv4spc3w
   portable_data_hash: 1f4b0bc7583c2a7f9102c395f4ffc5e3+45
index 7ffb9068895f50b426afdf0631d247da5248ce40..e5b17dd965c534ac536d88af87610d95ad424fcf 100644 (file)
@@ -171,24 +171,27 @@ EOS
       }
     }
     assert_response 422
+    response_errors = json_response['errors']
+    assert_not_nil response_errors, 'Expected error in response'
+    assert(response_errors.first.include?('duplicate key'),
+           "Expected 'duplicate key' error in #{response_errors.first}")
   end
 
-  test "create succeeds with with duplicate name with ensure_unique_name" do
+  test "create succeeds with duplicate name with ensure_unique_name" do
     permit_unsigned_manifests
-    authorize_with :admin
+    authorize_with :active
     manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
     post :create, {
       collection: {
-        owner_uuid: 'zzzzz-tpzed-000000000000000',
+        owner_uuid: users(:active).uuid,
         manifest_text: manifest_text,
         portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47",
-        name: "foo_file"
+        name: "owned_by_active"
       },
       ensure_unique_name: true
     }
     assert_response :success
-    resp = JSON.parse(@response.body)
-    assert_equal 'foo_file (2)', resp['name']
+    assert_equal 'owned_by_active (2)', json_response['name']
   end
 
   test "create with owner_uuid set to group i can_manage" do
index 5790b74f30b00d957cdad9171670f0b9cab3ec32..ec0e9f5efb57d4f08e23a6a0ec5c4c72794dc119 100644 (file)
@@ -291,4 +291,40 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
                     users(:admin).uuid,
                     "Current user should be included in 'writable_by' field")
   end
+
+  test 'creating subproject with duplicate name fails' do
+    authorize_with :active
+    post :create, {
+      group: {
+        name: 'A Project',
+        owner_uuid: users(:active).uuid,
+        group_class: 'project',
+      },
+    }
+    assert_response 422
+    response_errors = json_response['errors']
+    assert_not_nil response_errors, 'Expected error in response'
+    assert(response_errors.first.include?('duplicate key'),
+           "Expected 'duplicate key' error in #{response_errors.first}")
+  end
+
+  test 'creating duplicate named subproject succeeds with ensure_unique_name' do
+    authorize_with :active
+    post :create, {
+      group: {
+        name: 'A Project',
+        owner_uuid: users(:active).uuid,
+        group_class: 'project',
+      },
+      ensure_unique_name: true
+    }
+    assert_response :success
+    new_project = json_response
+    assert_not_equal(new_project['uuid'],
+                     groups(:aproject).uuid,
+                     "create returned same uuid as existing project")
+    assert_equal(new_project['name'],
+                 'A Project (2)',
+                 "new project name '#{new_project['name']}' was expected to be 'A Project (2)'")
+  end
 end
index e443f0dbab3c3bc5a23d82457eb4b36ff1fd34ba..256f820619f6ee7dbb7da950fd72dea4600618bf 100644 (file)
@@ -263,6 +263,7 @@ class FuseSharedTest(MountTestBase):
                           "Pipeline Template with Jobspec Components.pipelineTemplate",
                           "collection_expires_in_future",
                           "collection_with_same_name_in_aproject_and_home_project",
+                          "owned_by_active",
                           "pipeline_to_merge_params.pipelineInstance",
                           "pipeline_with_job.pipelineInstance",
                           "pipeline_with_tagged_collection_input.pipelineInstance"