Add owned_items action for groups and users.
authorTom Clegg <tom@curoverse.com>
Mon, 21 Apr 2014 21:50:49 +0000 (17:50 -0400)
committerTom Clegg <tom@curoverse.com>
Mon, 21 Apr 2014 21:50:49 +0000 (17:50 -0400)
services/api/app/controllers/application_controller.rb
services/api/config/routes.rb
services/api/test/functional/arvados/v1/groups_controller_test.rb
services/api/test/functional/arvados/v1/users_controller_test.rb

index 4b13fca1de45757292592e80152700efe8c83954..4836a7347c3c501feb8cf97767beb6a8a11755f0 100644 (file)
@@ -10,12 +10,13 @@ class ApplicationController < ActionController::Base
   before_filter :require_auth_scope_all, :except => :render_not_found
   before_filter :catch_redirect_hint
 
-  before_filter :load_where_param, :only => :index
-  before_filter :load_filters_param, :only => :index
-  before_filter :find_objects_for_index, :only => :index
   before_filter :find_object_by_uuid, :except => [:index, :create,
                                                   :render_error,
                                                   :render_not_found]
+  before_filter :load_limit_offset_order_params, only: [:index, :owned_items]
+  before_filter :load_where_param, only: [:index, :owned_items]
+  before_filter :load_filters_param, only: [:index, :owned_items]
+  before_filter :find_objects_for_index, :only => :index
   before_filter :reload_object_before_update, :only => :update
   before_filter :render_404_if_no_object, except: [:index, :create,
                                                    :render_error,
@@ -25,6 +26,8 @@ class ApplicationController < ActionController::Base
 
   attr_accessor :resource_attrs
 
+  DEFAULT_LIMIT = 100
+
   def index
     @objects.uniq!(&:id)
     if params[:eager] and params[:eager] != '0' and params[:eager] != 0 and params[:eager] != ''
@@ -56,6 +59,52 @@ class ApplicationController < ActionController::Base
     show
   end
 
+  def owned_items
+    all_objects = []
+    all_available = 0
+
+    # We stuffed params[:uuid] into @where in find_object_by_uuid,
+    # but we don't want it there any more.
+    @where = {}
+    # Order, limit, offset don't work here.
+    limit_all = @limit
+    @limit = DEFAULT_LIMIT
+    offset_all = @offset
+    @offset = 0
+    orders_all = @orders
+    @orders = []
+
+    ArvadosModel.descendants.reject(&:abstract_class?).sort_by(&:to_s).each do |klass|
+      case klass.to_s
+      when *%w(ApiClientAuthorization Link ApiClient)
+        # Do not want.
+      else
+        @objects = klass.
+          readable_by(current_user).
+          where(owner_uuid: @object.uuid)
+        apply_where_limit_order_params
+        # TODO: follow links, too
+        all_available += @objects.
+          except(:limit).except(:offset).
+          count(:id, distinct: true)
+        if all_objects.length < limit_all + offset_all
+          all_objects += @objects.to_a
+        end
+      end
+    end
+    @objects = all_objects[offset_all..(offset_all+limit_all-1)] || []
+    @object_list = {
+      :kind  => "arvados#objectList",
+      :etag => "",
+      :self_link => "",
+      :offset => offset_all,
+      :limit => limit_all,
+      :items_available => all_available,
+      :items => @objects.as_api_response(nil)
+    }
+    render json: @object_list
+  end
+
   def catch_redirect_hint
     if !current_user
       if params.has_key?('redirect_to') then
@@ -135,12 +184,51 @@ class ApplicationController < ActionController::Base
     end
   end
 
+  def load_limit_offset_order_params
+    if params[:limit]
+      begin
+        @limit = params[:limit].to_i
+      rescue
+        raise ArgumentError.new("Invalid value for limit parameter")
+      end
+    else
+      @limit = DEFAULT_LIMIT
+    end
+
+    if params[:offset]
+      begin
+        @offset = params[:offset].to_i
+      rescue
+        raise ArgumentError.new("Invalid value for offset parameter")
+      end
+    else
+      @offset = 0
+    end
+
+    @orders = []
+    if params[:order]
+      params[:order].split(',').each do |order|
+        attr, direction = order.strip.split " "
+        direction ||= 'asc'
+        if attr.match /^[a-z][_a-z0-9]+$/ and
+            model_class.columns.collect(&:name).index(attr) and
+            ['asc','desc'].index direction.downcase
+          @orders << "#{table_name}.#{attr} #{direction.downcase}"
+        end
+      end
+    end
+    if @orders.empty?
+      @orders << "#{table_name}.modified_at desc"
+    end
+  end
+
   def find_objects_for_index
     @objects ||= model_class.readable_by(current_user)
     apply_where_limit_order_params
   end
 
   def apply_where_limit_order_params
+    ar_table_name = @objects.table_name
     if @filters.is_a? Array and @filters.any?
       cond_out = []
       param_out = []
@@ -151,7 +239,7 @@ class ApplicationController < ActionController::Base
         case operator.downcase
         when '=', '<', '<=', '>', '>=', 'like'
           if operand.is_a? String
-            cond_out << "#{table_name}.#{attr} #{operator} ?"
+            cond_out << "#{ar_table_name}.#{attr} #{operator} ?"
             if (# any operator that operates on value rather than
                 # representation:
                 operator.match(/[<=>]/) and
@@ -162,7 +250,7 @@ class ApplicationController < ActionController::Base
           end
         when 'in'
           if operand.is_a? Array
-            cond_out << "#{table_name}.#{attr} IN (?)"
+            cond_out << "#{ar_table_name}.#{attr} IN (?)"
             param_out << operand
           end
         when 'is_a'
@@ -171,7 +259,7 @@ class ApplicationController < ActionController::Base
           operand.each do |op|
               cl = ArvadosModel::kind_class op
               if cl
-                cond << "#{table_name}.#{attr} like ?"
+                cond << "#{ar_table_name}.#{attr} like ?"
                 param_out << cl.uuid_like_pattern
               else
                 cond << "1=0"
@@ -193,7 +281,7 @@ class ApplicationController < ActionController::Base
               value[0] == 'contains' then
             ilikes = []
             model_class.searchable_columns('ilike').each do |column|
-              ilikes << "#{table_name}.#{column} ilike ?"
+              ilikes << "#{ar_table_name}.#{column} ilike ?"
               conditions << "%#{value[1]}%"
             end
             if ilikes.any?
@@ -203,24 +291,24 @@ class ApplicationController < ActionController::Base
         elsif attr.to_s.match(/^[a-z][_a-z0-9]+$/) and
             model_class.columns.collect(&:name).index(attr.to_s)
           if value.nil?
-            conditions[0] << " and #{table_name}.#{attr} is ?"
+            conditions[0] << " and #{ar_table_name}.#{attr} is ?"
             conditions << nil
           elsif value.is_a? Array
             if value[0] == 'contains' and value.length == 2
-              conditions[0] << " and #{table_name}.#{attr} like ?"
+              conditions[0] << " and #{ar_table_name}.#{attr} like ?"
               conditions << "%#{value[1]}%"
             else
-              conditions[0] << " and #{table_name}.#{attr} in (?)"
+              conditions[0] << " and #{ar_table_name}.#{attr} in (?)"
               conditions << value
             end
           elsif value.is_a? String or value.is_a? Fixnum or value == true or value == false
-            conditions[0] << " and #{table_name}.#{attr}=?"
+            conditions[0] << " and #{ar_table_name}.#{attr}=?"
             conditions << value
           elsif value.is_a? Hash
             # Not quite the same thing as "equal?" but better than nothing?
             value.each do |k,v|
               if v.is_a? String
-                conditions[0] << " and #{table_name}.#{attr} ilike ?"
+                conditions[0] << " and #{ar_table_name}.#{attr} ilike ?"
                 conditions << "%#{k}%#{v}%"
               end
             end
@@ -234,46 +322,9 @@ class ApplicationController < ActionController::Base
       end
     end
 
-    if params[:limit]
-      begin
-        @limit = params[:limit].to_i
-      rescue
-        raise ArgumentError.new("Invalid value for limit parameter")
-      end
-    else
-      @limit = 100
-    end
+    @objects = @objects.order(@orders.join ", ") if @orders.any?
     @objects = @objects.limit(@limit)
-
-    orders = []
-
-    if params[:offset]
-      begin
-        @objects = @objects.offset(params[:offset].to_i)
-        @offset = params[:offset].to_i
-      rescue
-        raise ArgumentError.new("Invalid value for limit parameter")
-      end
-    else
-      @offset = 0
-    end
-
-    orders = []
-    if params[:order]
-      params[:order].split(',').each do |order|
-        attr, direction = order.strip.split " "
-        direction ||= 'asc'
-        if attr.match /^[a-z][_a-z0-9]+$/ and
-            model_class.columns.collect(&:name).index(attr) and
-            ['asc','desc'].index direction.downcase
-          orders << "#{table_name}.#{attr} #{direction.downcase}"
-        end
-      end
-    end
-    if orders.empty?
-      orders << "#{table_name}.modified_at desc"
-    end
-    @objects = @objects.order(orders.join ", ")
+    @objects = @objects.offset(@offset)
   end
 
   def resource_attrs
@@ -404,6 +455,10 @@ class ApplicationController < ActionController::Base
       params[:uuid] = params.delete :id
     end
     @where = { uuid: params[:uuid] }
+    @offset = 0
+    @limit = 1
+    @orders = []
+    @filters = []
     find_objects_for_index
     @object = @objects.first
   end
index 211701a05455edc6e3b05d03bd0823137247acec..cfd3b615374de7f429360951676844fc33fa21c3 100644 (file)
@@ -107,9 +107,13 @@ Server::Application.routes.draw do
       resources :pipeline_templates
       resources :pipeline_instances
       resources :specimens
-      resources :groups
+      resources :groups do
+        get 'owned_items', on: :member
+      end
       resources :logs
-      resources :users
+      resources :users do
+        get 'owned_items', on: :member
+      end
       resources :api_clients
       resources :api_client_authorizations
       resources :jobs
index 126499781d75edd4aaec5dbf0bb6e0a9e3a1b34b..5b0fd59b3073b12445bf4c0dc1a279fe8e3ad512 100644 (file)
@@ -27,4 +27,51 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
     assert_not_nil group_uuids.index groups(:asubfolder).uuid
   end
 
+  test 'get group-owned objects' do
+    authorize_with :active
+    get :owned_items, {
+      id: groups(:afolder).uuid,
+      format: :json,
+    }
+    assert_response :success
+    assert_operator 2, :<=, jresponse['items_available']
+    assert_operator 2, :<=, jresponse['items'].count
+  end
+
+  test 'get group-owned objects with limit' do
+    authorize_with :active
+    get :owned_items, {
+      id: groups(:afolder).uuid,
+      limit: 1,
+      format: :json,
+    }
+    assert_response :success
+    assert_operator 1, :<, jresponse['items_available']
+    assert_equal 1, jresponse['items'].count
+  end
+
+  test 'get group-owned objects with limit and offset' do
+    authorize_with :active
+    get :owned_items, {
+      id: groups(:afolder).uuid,
+      limit: 1,
+      offset: 12345,
+      format: :json,
+    }
+    assert_response :success
+    assert_operator 1, :<, jresponse['items_available']
+    assert_equal 0, jresponse['items'].count
+  end
+
+  test 'get group-owned objects with additional filter matching nothing' do
+    authorize_with :active
+    get :owned_items, {
+      id: groups(:afolder).uuid,
+      filters: [['uuid', 'in', ['foo_not_a_uuid','bar_not_a_uuid']]],
+      format: :json,
+    }
+    assert_response :success
+    assert_equal [], jresponse['items']
+    assert_equal 0, jresponse['items_available']
+  end
 end
index 6dc5950dd81a18f32a7ad5175d5b1defc06fea65..c466e901a8d127985ad05ee48914517c7bfb0072 100644 (file)
@@ -880,4 +880,19 @@ class Arvados::V1::UsersControllerTest < ActionController::TestCase
                                tail_uuid: system_group_uuid,
                                head_uuid: user_uuid).count
   end
+
+  test 'get user-owned objects' do
+    authorize_with :active
+    get :owned_items, {
+      id: users(:active).uuid,
+      format: :json,
+    }
+    assert_response :success
+    assert_operator 2, :<=, jresponse['items_available']
+    assert_operator 2, :<=, jresponse['items'].count
+    kinds = jresponse['items'].collect { |i| i['kind'] }.uniq
+    expect_kinds = %w'arvados#group arvados#specimen arvados#pipelineTemplate arvados#job'
+    assert_equal expect_kinds, (expect_kinds & kinds)
+  end
+
 end