Merge branch 'master' into 2525-java-sdk
authorradhika <radhika@radhika>
Wed, 30 Apr 2014 10:53:13 +0000 (06:53 -0400)
committerradhika <radhika@radhika>
Wed, 30 Apr 2014 10:53:13 +0000 (06:53 -0400)
20 files changed:
apps/workbench/app/assets/javascripts/application.js
apps/workbench/app/assets/javascripts/collections.js [new file with mode: 0644]
apps/workbench/app/assets/javascripts/collections.js.coffee [deleted file]
apps/workbench/app/assets/stylesheets/collections.css.scss
apps/workbench/app/controllers/collections_controller.rb
apps/workbench/app/controllers/users_controller.rb
apps/workbench/app/views/collections/_index_tbody.html.erb
apps/workbench/app/views/collections/_show_files.html.erb
apps/workbench/app/views/collections/_show_recent.html.erb
apps/workbench/app/views/collections/_toggle_persist.html.erb [new file with mode: 0644]
apps/workbench/app/views/users/_tables.html.erb
apps/workbench/config/routes.rb
apps/workbench/test/integration/collections_test.rb [new file with mode: 0644]
sdk/ruby/lib/arvados.rb
services/api/app/controllers/application_controller.rb
services/api/app/controllers/arvados/v1/nodes_controller.rb
services/api/app/models/arvados_model.rb
services/api/app/models/node.rb
services/api/test/functional/arvados/v1/nodes_controller_test.rb
services/api/test/integration/login_workflow_test.rb [new file with mode: 0644]

index 6afc8c3b040c31cf83375ac7051dc63328851902..189063bcffbcc8f1e9daf64bbb4a02b9dbb6a2db 100644 (file)
@@ -19,6 +19,7 @@
 //= require bootstrap/popover
 //= require bootstrap/collapse
 //= require bootstrap/modal
+//= require bootstrap/button
 //= require bootstrap3-editable/bootstrap-editable
 //= require_tree .
 
@@ -105,6 +106,12 @@ jQuery(function($){
             return false;
         });
 
+    $(document).
+        on('ajax:complete ready', function() {
+            // See http://getbootstrap.com/javascript/#buttons
+            $('.btn').button();
+        });
+
     HeaderRowFixer = function(selector) {
         this.duplicateTheadTr = function() {
             $(selector).each(function() {
diff --git a/apps/workbench/app/assets/javascripts/collections.js b/apps/workbench/app/assets/javascripts/collections.js
new file mode 100644 (file)
index 0000000..7f4b510
--- /dev/null
@@ -0,0 +1,59 @@
+jQuery(function($){
+    $(document).on('click', '.toggle-persist button', function() {
+        var toggle_group = $(this).parents('[data-remote-href]').first();
+        var want_persist = !toggle_group.find('button').hasClass('active');
+        var want_state = want_persist ? 'persistent' : 'cache';
+        console.log(want_persist);
+        toggle_group.find('button').
+            toggleClass('active', want_persist).
+            html(want_persist ? 'Persistent' : 'Cache');
+        $.ajax(toggle_group.attr('data-remote-href'),
+               {dataType: 'json',
+                type: 'POST',
+                data: {
+                    value: want_state
+                },
+                context: {
+                    toggle_group: toggle_group,
+                    want_state: want_state,
+                    button: this
+                }
+               }).
+            done(function(data, status, jqxhr) {
+                var context = this;
+                $(document).trigger('ajax:complete');
+                // Remove "danger" status in case a previous action failed
+                $('.btn-danger', context.toggle_group).
+                    addClass('btn-info').
+                    removeClass('btn-danger');
+                // Update last-saved-state
+                context.toggle_group.
+                    attr('data-persistent-state', context.want_state);
+            }).
+            fail(function(jqxhr, status, error) {
+                var context = this;
+                var saved_state;
+                $(document).trigger('ajax:complete');
+                // Add a visual indication that something failed
+                $(context.button).
+                    addClass('btn-danger').
+                    removeClass('btn-info');
+                // Change to the last-saved-state
+                saved_state = context.toggle_group.attr('data-persistent-state');
+                $(context.button).
+                    toggleClass('active', saved_state == 'persistent').
+                    html(saved_state == 'persistent' ? 'Persistent' : 'Cache');
+
+                if (jqxhr.readyState == 0 || jqxhr.status == 0) {
+                    // Request cancelled due to page reload.
+                    // Displaying an alert would be rather annoying.
+                } else if (jqxhr.responseJSON && jqxhr.responseJSON.errors) {
+                    window.alert("Request failed: " +
+                                 jqxhr.responseJSON.errors.join("; "));
+                } else {
+                    window.alert("Request failed.");
+                }
+            });
+        $(document).trigger('ajax:send');
+    });
+});
diff --git a/apps/workbench/app/assets/javascripts/collections.js.coffee b/apps/workbench/app/assets/javascripts/collections.js.coffee
deleted file mode 100644 (file)
index 7615679..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-# Place all the behaviors and hooks related to the matching controller here.
-# All this logic will automatically be available in application.js.
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/
index c21e96d80fa86f8f3cc24179dfd31afb81f53578..24b08fa03ce9fbd039131a04c998e1c760cb84ec 100644 (file)
@@ -1,3 +1,18 @@
-// Place all the styles related to the Collections controller here.
-// They will automatically be included in application.css.
-// You can use Sass (SCSS) here: http://sass-lang.com/
+/*
+  "active" and "inactive" colors are too similar for a toggle switch
+  in the default bootstrap theme.
+  */
+
+$inactive-bg: #5bc0de;
+$active-bg: #39b3d7;
+
+.btn-group.toggle-persist .btn {
+    width: 6em;
+}
+.btn-group.toggle-persist .btn-info {
+    background-color: lighten($inactive-bg, 15%);
+}
+
+.btn-group.toggle-persist .btn-info.active {
+    background-color: $active-bg;
+}
index a5ac21595e36e5943a007b64f12b0c156eae54ed..3089a1ed3fecea72797904b604df0b69e55b1577 100644 (file)
@@ -6,6 +6,36 @@ class CollectionsController < ApplicationController
     %w(Files Attributes Metadata Provenance_graph Used_by JSON API)
   end
 
+  def set_persistent
+    case params[:value]
+    when 'persistent', 'cache'
+      persist_links = Link.filter([['owner_uuid', '=', current_user.uuid],
+                                   ['link_class', '=', 'resources'],
+                                   ['name', '=', 'wants'],
+                                   ['tail_uuid', '=', current_user.uuid],
+                                   ['head_uuid', '=', @object.uuid]])
+      logger.debug persist_links.inspect
+    else
+      return unprocessable "Invalid value #{value.inspect}"
+    end
+    if params[:value] == 'persistent'
+      if not persist_links.any?
+        Link.create(link_class: 'resources',
+                    name: 'wants',
+                    tail_uuid: current_user.uuid,
+                    head_uuid: @object.uuid)
+      end
+    else
+      persist_links.each do |link|
+        link.destroy || raise
+      end
+    end
+
+    respond_to do |f|
+      f.json { render json: @object }
+    end
+  end
+
   def index
     if params[:search].andand.length.andand > 0
       tags = Link.where(any: ['contains', params[:search]])
@@ -68,7 +98,6 @@ class CollectionsController < ApplicationController
     self.response_body = FileStreamer.new opts
   end
 
-
   def show
     return super if !@object
     @provenance = []
@@ -100,6 +129,9 @@ class CollectionsController < ApplicationController
     Link.where(head_uuid: @sourcedata.keys | @output2job.keys).each do |link|
       if link.link_class == 'resources' and link.name == 'wants'
         @protected[link.head_uuid] = true
+        if link.tail_uuid == current_user.uuid
+          @is_persistent = true
+        end
       end
     end
     Link.where(tail_uuid: @sourcedata.keys).each do |link|
index 8a3d35aaf0eade15bf9545410447ec1e1b8d273e..863876137fdab5f941740e5f7bd30187415c6099 100644 (file)
@@ -129,10 +129,24 @@ class UsersController < ApplicationController
       limit(10).
       order('created_at desc').
       where(created_by: current_user.uuid)
+    collection_uuids = @my_collections.collect &:uuid
 
-    Link.limit(1000).where(head_uuid: @my_collections.collect(&:uuid),
-                           link_class: 'tag').each do |link|
-      (@my_tag_links[link.head_uuid] ||= []) << link
+    @persist_state = {}
+    collection_uuids.each do |uuid|
+      @persist_state[uuid] = 'cache'
+    end
+
+    Link.limit(1000).filter([['head_uuid', 'in', collection_uuids],
+                             ['link_class', 'in', ['tag', 'resources']]]).
+      each do |link|
+      case link.link_class
+      when 'tag'
+        (@my_tag_links[link.head_uuid] ||= []) << link
+      when 'resources'
+        if link.name == 'wants'
+          @persist_state[link.head_uuid] = 'persistent'
+        end
+      end
     end
 
     @my_pipelines = PipelineInstance.
@@ -140,22 +154,6 @@ class UsersController < ApplicationController
       order('created_at desc').
       where(created_by: current_user.uuid)
 
-
-    # A Tutorial is a Link which has link_class "resources" and name
-    # "wants", and is owned by the Tutorials Group (i.e., named
-    # "Arvados Tutorials" and owned by the system user).
-    @tutorial_group = Group.where(owner_uuid: User.system.uuid,
-                                  name: 'Arvados Tutorials').first
-    if @tutorial_group
-      @tutorial_links = Link.where(tail_uuid: @tutorial_group.uuid,
-                                   link_class: 'resources',
-                                   name: 'wants')
-    else
-      @tutorial_links = []
-    end
-    @tutorial_complete = {
-      'Run a job' => @my_last_job
-    }
     respond_to do |f|
       f.js { render template: 'users/home.js' }
       f.html { render template: 'users/home' }
index 75745376c0bb5c560dda892a016fd4c917594a66..207ae13a740885f7099b9701ff1dfacb082f9725 100644 (file)
       &vellip;
     <% end %>
   </td>
-  <td><%= link_to_if_arvados_object c.owner_uuid, friendly_name: true %></td>
   <td>
     <%= raw(distance_of_time_in_words(c.created_at, Time.now).sub('about ','~').sub(' ','&nbsp;')) if c.created_at %>
   </td>
   <td>
-    <% if @collection_info[c.uuid][:wanted_by_me] %>
-      <span class="label label-info">2&times;</span>
-    <% elsif @collection_info[c.uuid][:wanted] %>
-      <span class="label">2&times;</span>
-    <% else %>
-      <span class="label">cache</span>
-    <% end %>
+    <% current_state = @collection_info[c.uuid][:wanted_by_me] ? 'persistent' : 'cache' %>
+    <%= render partial: 'toggle_persist', locals: { uuid: c.uuid, current_state: current_state } %>
   </td>
   <td class="add-tag-button">
     <a class="btn btn-xs btn-info add-tag-button pull-right" data-remote-href="<%= url_for(controller: 'links', action: 'create') %>" data-remote-method="post"><i class="glyphicon glyphicon-plus"></i>&nbsp;Add</a>
index c3a2c789641b272ec04977d73fd1fbd87445d8a0..4b63162be0550f32a4e74ca636739fc27149b286 100644 (file)
@@ -5,6 +5,18 @@
 }
 <% end %>
 
+<% content_for :tab_line_buttons do %>
+<div class="row">
+  <div class="col-md-6"></div>
+  <div class="col-md-6">
+    <div class="pull-right">
+      Collection storage status:
+      <%= render partial: 'toggle_persist', locals: { uuid: @object.uuid, current_state: (@is_persistent ? 'persistent' : 'cache') } %>
+    </div>
+  </div>
+</div>
+<% end %>
+
 <table class="table table-condensed table-fixedlayout">
   <colgroup>
     <col width="4%" />
index e0a12ffbf63ab9e34286744dd867e99422510685..2f4ab64771158001cc6fad08833cfd6456cead5b 100644 (file)
   <colgroup>
     <col width="4%" />
     <col width="10%" />
-    <col width="36%" />
+    <col width="34%" />
     <col width="15%" />
-    <col width="8%" />
-    <col width="8%" />
-    <col width="23%" />
+    <col width="12%" />
+    <col width="29%" />
   </colgroup>
   <thead>
     <tr class="contain-align-left">
       <th></th>
       <th>uuid</th>
       <th>contents</th>
-      <th>owner</th>
       <th>age</th>
       <th>storage</th>
       <th>tags</th>
diff --git a/apps/workbench/app/views/collections/_toggle_persist.html.erb b/apps/workbench/app/views/collections/_toggle_persist.html.erb
new file mode 100644 (file)
index 0000000..aa6ed81
--- /dev/null
@@ -0,0 +1,3 @@
+<div class="btn-group btn-group-xs toggle-persist" data-remote-href="<%= set_persistent_collection_path(id: uuid) %>" data-persistent-state="<%= current_state %>">
+  <button type="button" class="btn btn-info <%= 'active' if current_state == 'persistent' %>"><%= current_state.capitalize %></button>
+</div>
index 9e1c220fd66d928a4b2630a23d32d5f7b75a93ad..10592f5009c9cd80c25b87482cf2c30acb50d582 100644 (file)
         </colgroup>
 
         <tr>
-         <th>Script</th>
-         <th>Output</th>
-         <th>Log</th>
-         <th>Age</th>
-         <th>Status</th>
-         <th>Progress</th>
-       </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>
+          <tr data-object-uuid="<%= j.uuid %>">
             <td>
               <small>
-               <%= link_to((j.script.andand[0..31] || j.uuid), job_path(j.uuid)) %>
+                <%= link_to((j.script.andand[0..31] || j.uuid), job_path(j.uuid)) %>
               </small>
             </td>
 
-           <td>
-             <small>
-               <% if j.success and j.output %>
+            <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>
+                  <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>
+        <% end %>
+        </small>
 </td>
 
 <td>
@@ -67,8 +67,7 @@
 
 <td>
   <small>
-    <%= raw(distance_of_time_in_words(j.created_at, Time.now).sub('about
-','~').sub(' ','&nbsp;')) if j.created_at %>
+    <%= raw(distance_of_time_in_words(j.created_at, Time.now).sub('about ','~').sub(' ','&nbsp;')) if j.created_at %>
   </small>
 </td>
 
       </colgroup>
 
       <tr>
-       <th>Instance</th>
-       <th>Template</th>
-       <th>Age</th>
-       <th>Status</th>
-       <th>Progress</th>
+        <th>Instance</th>
+        <th>Template</th>
+        <th>Age</th>
+        <th>Status</th>
+        <th>Progress</th>
       </tr>
 
       <% @my_pipelines[0..6].each do |p| %>
-        <tr>
+        <tr data-object-uuid="<%= p.uuid %>">
           <td>
             <small>
-             <%= link_to_if_arvados_object p.uuid, friendly_name: true %>
+              <%= 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 %>
+              <%= 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(' ','&nbsp;')) if p.created_at %>
+              <%= raw(distance_of_time_in_words(p.created_at, Time.now).sub('about ','~').sub(' ','&nbsp;')) if p.created_at %>
             </small>
           </td>
 
     <table class="table table-bordered table-condensed table-fixedlayout">
       <colgroup>
         <col width="46%" />
-        <col width="27%" />
-        <col width="27%" />
+        <col width="32%" />
+        <col width="10%" />
+        <col width="12%" />
       </colgroup>
 
       <tr>
-       <th>Contents</th>
-       <th>Tags</th>
-       <th>Age</th>
+        <th>Contents</th>
+        <th>Tags</th>
+        <th>Age</th>
+        <th>Storage</th>
       </tr>
 
       <% @my_collections[0..6].each do |c| %>
-        <tr>
+        <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>
+              <a href="<%= collection_path(c.uuid) %>">
+                <% c.files.each do |file| %>
+                  <%= file[0] == '.' ? file[1] : "#{file[0]}/#{file[1]}" %>
+                <% end %>
+              </a>
             </small>
           </td>
           <td>
           </td>
           <td>
             <small>
-             <%= raw(distance_of_time_in_words(c.created_at, Time.now).sub('about
-','~').sub(' ','&nbsp;')) if c.created_at %>
+              <%= raw(distance_of_time_in_words(c.created_at, Time.now).sub('about ','~').sub(' ','&nbsp;')) 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>
     <div class="col-sm-8">
       <h2>Welcome to Arvados, <%= current_user.first_name %>!</h2>
       <div class="well">
-       <p>
-         Your account must be activated by an Arvados administrator.  If this
-         is your first time accessing Arvados and would like to request
-         access, or you believe you are seeing the page in error, please
-         <%= link_to "contact us", Rails.configuration.activation_contact_link %>.
-         You should receive an email at the address you used to log in when
-         your account is activated.  In the mean time, you can
-         <%= link_to "learn more about Arvados", "https://arvados.org/projects/arvados/wiki/Introduction_to_Arvados" %>,
-         and <%= link_to "read the Arvados user guide", "http://doc.arvados.org/user" %>.
-       </p>
-       <p style="padding-bottom: 1em">
-         <%= link_to raw('Contact us &#x2709;'),
-             Rails.configuration.activation_contact_link, class: "pull-right btn btn-primary" %></p>
+        <p>
+          Your account must be activated by an Arvados administrator.  If this
+          is your first time accessing Arvados and would like to request
+          access, or you believe you are seeing the page in error, please
+          <%= link_to "contact us", Rails.configuration.activation_contact_link %>.
+          You should receive an email at the address you used to log in when
+          your account is activated.  In the mean time, you can
+          <%= link_to "learn more about Arvados", "https://arvados.org/projects/arvados/wiki/Introduction_to_Arvados" %>,
+          and <%= link_to "read the Arvados user guide", "http://doc.arvados.org/user" %>.
+        </p>
+        <p style="padding-bottom: 1em">
+          <%= link_to raw('Contact us &#x2709;'),
+              Rails.configuration.activation_contact_link, class: "pull-right btn btn-primary" %></p>
       </div>
     </div>
   </div>
index 9890ce47b29112a8e08620691121246eaa9b55ed..f915671b530cd23b575ee74dcabbb034d331ae56 100644 (file)
@@ -40,7 +40,9 @@ ArvadosWorkbench::Application.routes.draw do
   end
   resources :links
   match '/collections/graph' => 'collections#graph'
-  resources :collections
+  resources :collections do
+    post 'set_persistent', on: :member
+  end
   get '/collections/:uuid/*file' => 'collections#show_file', :format => false
 
   post 'actions' => 'actions#post'
diff --git a/apps/workbench/test/integration/collections_test.rb b/apps/workbench/test/integration/collections_test.rb
new file mode 100644 (file)
index 0000000..bd426f7
--- /dev/null
@@ -0,0 +1,42 @@
+require 'integration_helper'
+require 'selenium-webdriver'
+require 'headless'
+
+class CollectionsTest < ActionDispatch::IntegrationTest
+
+  def change_persist oldstate, newstate
+    find "div[data-persistent-state='#{oldstate}']"
+    page.assert_no_selector "div[data-persistent-state='#{newstate}']"
+    find('.btn', text: oldstate.capitalize).click
+    find '.btn', text: newstate.capitalize
+    page.assert_no_selector '.btn', text: oldstate.capitalize
+    find "div[data-persistent-state='#{newstate}']"
+    page.assert_no_selector "div[data-persistent-state='#{oldstate}']"
+  end
+
+  ['/collections', '/'].each do |path|
+    test "Flip persistent switch at #{path}" do
+      Capybara.current_driver = Capybara.javascript_driver
+      uuid = api_fixture('collections')['foo_file']['uuid']
+      visit page_with_token('active', path)
+      within "tr[data-object-uuid='#{uuid}']" do
+        change_persist 'cache', 'persistent'
+      end
+      # Refresh page and make sure the change was committed.
+      visit current_path
+      within "tr[data-object-uuid='#{uuid}']" do
+        change_persist 'persistent', 'cache'
+      end
+    end
+  end
+
+  test 'Flip persistent switch on collection#show' do
+    Capybara.current_driver = Capybara.javascript_driver
+    uuid = api_fixture('collections')['foo_file']['uuid']
+    visit page_with_token('active', "/collections/#{uuid}")
+    change_persist 'cache', 'persistent'
+    visit current_path
+    change_persist 'persistent', 'cache'
+  end
+
+end
index a94eb1db4224bca9245b805c9d58e18be5c1f04a..567423ff4f154f0bd684669fad1f7e96b6b96c5b 100644 (file)
@@ -35,9 +35,8 @@ class Arvados
     @application_version ||= 0.0
     @application_name ||= File.split($0).last
 
-    @arvados_api_version = opts[:api_version] ||
-      config['ARVADOS_API_VERSION'] ||
-      'v1'
+    @arvados_api_version = opts[:api_version] || 'v1'
+
     @arvados_api_host = opts[:api_host] ||
       config['ARVADOS_API_HOST'] or
       raise "#{$0}: no :api_host or ENV[ARVADOS_API_HOST] provided."
@@ -46,7 +45,8 @@ class Arvados
       raise "#{$0}: no :api_token or ENV[ARVADOS_API_TOKEN] provided."
 
     if (opts[:suppress_ssl_warnings] or
-        config['ARVADOS_API_HOST_INSECURE'])
+        %w(1 true yes).index(config['ARVADOS_API_HOST_INSECURE'].
+                             andand.downcase))
       suppress_warnings do
         OpenSSL::SSL.const_set 'VERIFY_PEER', OpenSSL::SSL::VERIFY_NONE
       end
@@ -142,6 +142,10 @@ class Arvados
     $stderr.puts "#{File.split($0).last} #{$$}: #{message}" if @@debuglevel >= verbosity
   end
 
+  def debuglog *args
+    self.class.debuglog *args
+  end
+
   def config(config_file_path="~/.config/arvados/settings.conf")
     return @@config if @@config
 
@@ -150,7 +154,19 @@ class Arvados
     config['ARVADOS_API_HOST']          = ENV['ARVADOS_API_HOST']
     config['ARVADOS_API_TOKEN']         = ENV['ARVADOS_API_TOKEN']
     config['ARVADOS_API_HOST_INSECURE'] = ENV['ARVADOS_API_HOST_INSECURE']
-    config['ARVADOS_API_VERSION']       = ENV['ARVADOS_API_VERSION']
+
+    if config['ARVADOS_API_HOST'] and config['ARVADOS_API_TOKEN']
+      # Environment variables take precedence over the config file, so
+      # there is no point reading the config file. If the environment
+      # specifies a _HOST without asking for _INSECURE, we certainly
+      # shouldn't give the config file a chance to create a
+      # system-wide _INSECURE state for this user.
+      #
+      # Note: If we start using additional configuration settings from
+      # this file in the future, we might have to read the file anyway
+      # instead of returning here.
+      return (@@config = config)
+    end
 
     begin
       expanded_path = File.expand_path config_file_path
@@ -162,16 +178,18 @@ class Arvados
           # skip comments and blank lines
           next if line.match('^\s*#') or not line.match('\S')
           var, val = line.chomp.split('=', 2)
+          var.strip!
+          val.strip!
           # allow environment settings to override config files.
-          if var and val
+          if !var.empty? and val
             config[var] ||= val
           else
-            warn "#{expanded_path}: #{lineno}: could not parse `#{line}'"
+            debuglog "#{expanded_path}: #{lineno}: could not parse `#{line}'", 0
           end
         end
       end
-    rescue
-      debuglog "HOME environment variable (#{ENV['HOME']}) not set, not using #{config_file_path}", 0
+    rescue StandardError => e
+      debuglog "Ignoring error reading #{config_file_path}: #{e}", 0
     end
 
     @@config = config
index b091397681f05ba6ae7297a1ad90d6d8a023a316..d40c6de47ee2cfff4c5de92a31cc3d062cf8686c 100644 (file)
@@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
   protect_from_forgery
   around_filter :thread_with_auth_info, :except => [:render_error, :render_not_found]
 
+  before_filter :respond_with_json_by_default
   before_filter :remote_ip
   before_filter :require_auth_scope, :except => :render_not_found
   before_filter :catch_redirect_hint
@@ -319,6 +320,9 @@ class ApplicationController < ActionController::Base
               value[0] == 'contains' then
             ilikes = []
             model_class.searchable_columns('ilike').each do |column|
+              # Including owner_uuid in an "any column" search will
+              # probably just return a lot of false positives.
+              next if column == 'owner_uuid'
               ilikes << "#{ar_table_name}.#{column} ilike ?"
               conditions << "%#{value[1]}%"
             end
@@ -473,6 +477,13 @@ class ApplicationController < ActionController::Base
   end
   # /Authentication
 
+  def respond_with_json_by_default
+    html_index = request.accepts.index(Mime::HTML)
+    if html_index.nil? or request.accepts[0...html_index].include?(Mime::JSON)
+      request.format = :json
+    end
+  end
+
   def model_class
     controller_name.classify.constantize
   end
index d7a477d337ffe9655b609bcda6f99adc32284f5d..990397bf7cee77928ecc3d889ab313c173197c2a 100644 (file)
@@ -6,7 +6,7 @@ class Arvados::V1::NodesController < ApplicationController
   def create
     @object = Node.new
     @object.save!
-    @object.start!(lambda { |h| arvados_v1_ping_node_url(h) })
+    @object.start!(lambda { |h| ping_arvados_v1_node_url(h) })
     show
   end
 
index 25d73178c771a47c085fa32bd19b9c6e9676a1c7..e42194b07151468327233c4c1958b2a2b2a10dd7 100644 (file)
@@ -47,9 +47,7 @@ class ArvadosModel < ActiveRecord::Base
   def self.searchable_columns operator
     textonly_operator = !operator.match(/[<=>]/)
     self.columns.collect do |col|
-      if col.name == 'owner_uuid'
-        nil
-      elsif [:string, :text].index(col.type)
+      if [:string, :text].index(col.type)
         col.name
       elsif !textonly_operator and [:datetime, :integer].index(col.type)
         col.name
index 805e1ccd41cabd1a681e901b4cdb38f7d375067a..b88d4a50d037a30a80e8fcec6d9269dd384ef1a2 100644 (file)
@@ -120,7 +120,7 @@ class Node < ArvadosModel
 
   def start!(ping_url_method)
     ensure_permission_to_update
-    ping_url = ping_url_method.call({ uuid: self.uuid, ping_secret: self.info[:ping_secret] })
+    ping_url = ping_url_method.call({ id: self.uuid, ping_secret: self.info[:ping_secret] })
     if (Rails.configuration.compute_node_ec2run_args and
         Rails.configuration.compute_node_ami)
       ec2_args = ["--user-data '#{ping_url}'",
index 386c839aeda07e1157b5de34d5cd972a188561fb..e096a045c60c81b9aef6bf1fcc08d714a48077e9 100644 (file)
@@ -66,4 +66,13 @@ class Arvados::V1::NodesControllerTest < ActionController::TestCase
     assert_response 401
   end
 
+  test "create node" do
+    authorize_with :admin
+    post :create
+    assert_response :success
+    assert_not_nil json_response['uuid']
+    assert_not_nil json_response['info'].is_a? Hash
+    assert_not_nil json_response['info']['ping_secret']
+  end
+
 end
diff --git a/services/api/test/integration/login_workflow_test.rb b/services/api/test/integration/login_workflow_test.rb
new file mode 100644 (file)
index 0000000..e0d6968
--- /dev/null
@@ -0,0 +1,25 @@
+require 'test_helper'
+
+class LoginWorkflowTest < ActionDispatch::IntegrationTest
+  test "default prompt to login is JSON" do
+    post('/arvados/v1/specimens', {specimen: {}},
+         {'HTTP_ACCEPT' => ''})
+    assert_response 401
+    assert_includes(json_response['errors'], "Not logged in")
+  end
+
+  test "login prompt respects JSON Accept header" do
+    post('/arvados/v1/specimens', {specimen: {}},
+         {'HTTP_ACCEPT' => 'application/json'})
+    assert_response 401
+    assert_includes(json_response['errors'], "Not logged in")
+  end
+
+  test "login prompt respects HTML Accept header" do
+    post('/arvados/v1/specimens', {specimen: {}},
+         {'HTTP_ACCEPT' => 'text/html'})
+    assert_response 302
+    assert_match(%r{/auth/joshid$}, @response.headers['Location'],
+                 "HTML login prompt did not include expected redirect")
+  end
+end