filtered = m.to_ary.reject do |x|
x.api_client_id == 0 or (x.expires_at and x.expires_at < Time.now) rescue false
end
- ArvadosApiClient::patch_paging_vars(filtered, items_available, offset, limit)
+ ArvadosApiClient::patch_paging_vars(filtered, items_available, offset, limit, nil)
@objects = ArvadosResourceList.new(ApiClientAuthorization)
@objects.results= filtered
super
resp
end
- def self.patch_paging_vars(ary, items_available, offset, limit)
+ def self.patch_paging_vars(ary, items_available, offset, limit, links=nil)
if items_available
(class << ary; self; end).class_eval { attr_accessor :items_available }
ary.items_available = items_available
if limit
(class << ary; self; end).class_eval { attr_accessor :limit }
ary.limit = limit
- end
+ end
+ if links
+ (class << ary; self; end).class_eval { attr_accessor :links }
+ ary.links = links
+ end
ary
end
def unpack_api_response(j, kind=nil)
if j.is_a? Hash and j[:items].is_a? Array and j[:kind].match(/(_list|List)$/)
ary = j[:items].collect { |x| unpack_api_response x, x[:kind] }
- self.class.patch_paging_vars(ary, j[:items_available], j[:offset], j[:limit])
+ links = ArvadosResourceList.new Link
+ links.results = (j[:links] || []).collect do |x|
+ unpack_api_response x, x[:kind]
+ end
+ self.class.patch_paging_vars(ary, j[:items_available], j[:offset], j[:limit], links)
elsif j.is_a? Hash and (kind || j[:kind])
oclass = self.kind_class(kind || j[:kind])
if oclass
class ArvadosResourceList
include Enumerable
- def initialize(resource_class)
+ def initialize resource_class=nil
@resource_class = resource_class
end
results.offset if results.respond_to? :offset
end
+ def result_links
+ results.links if results.respond_to? :links
+ end
+
+ def links_for item_or_uuid, link_class=nil
+ unless @links_for_uuid
+ @links_for_uuid = {}
+ results.links.each do |link|
+ if link.respond_to? :head_uuid
+ @links_for_uuid[link.head_uuid] ||= []
+ @links_for_uuid[link.head_uuid] << link
+ end
+ end
+ end
+ if item_or_uuid.respond_to? :uuid
+ uuid = item_or_uuid.uuid
+ else
+ uuid = item_or_uuid
+ end
+ (@links_for_uuid[uuid] || []).select do |link|
+ link.link_class == link_class
+ end
+ end
+
+ def name_for item_or_uuid
+ links_for(item_or_uuid, 'name').first.name
+ end
+
end
class Group < ArvadosBase
- def self.owned_items
- res = $arvados_api_client.api self, "/#{self.uuid}/owned_items", {}
- $arvados_api_client.unpack_api_response(res)
+ def contents params={}
+ res = $arvados_api_client.api self.class, "/#{self.uuid}/contents", {
+ _method: 'GET'
+ }.merge(params)
+ ret = ArvadosResourceList.new
+ ret.results = $arvados_api_client.unpack_api_response(res)
+ ret
end
end
end
end
- def owned_items
- res = $arvados_api_client.api self.class, "/#{self.uuid}/owned_items"
- $arvados_api_client.unpack_api_response(res)
- end
-
def full_name
(self.first_name || "") + " " + (self.last_name || "")
end
require 'test_helper'
-class ProjectTest < ActiveSupport::TestCase
- # test "the truth" do
- # assert true
- # end
+class GroupTest < ActiveSupport::TestCase
+ test "get contents with names" do
+ use_token :active
+ oi = Group.
+ find(api_fixture('groups')['asubfolder']['uuid']).
+ contents(include_linked: true)
+ assert_operator(0, :<, oi.count,
+ "Expected to find some items belonging to :active user")
+ assert_operator(0, :<, oi.items_available,
+ "Expected contents response to have items_available > 0")
+ assert_operator(0, :<, oi.result_links.count,
+ "Expected to receive name links with contents response")
+ oi_uuids = oi.collect { |i| i['uuid'] }
+
+ expect_uuid = api_fixture('specimens')['in_asubfolder']['uuid']
+ assert_includes(oi_uuids, expect_uuid,
+ "Expected '#{expect_uuid}' in asubfolder's contents")
+
+ expect_uuid = api_fixture('specimens')['in_afolder_linked_from_asubfolder']['uuid']
+ expect_name = api_fixture('links')['specimen_is_in_two_folders']['name']
+ assert_includes(oi_uuids, expect_uuid,
+ "Expected '#{expect_uuid}' in asubfolder's contents")
+ assert_equal(expect_name, oi.name_for(expect_uuid),
+ "Expected name_for '#{expect_uuid}' to be '#{expect_name}'")
+ end
end
require 'test_helper'
class UserTest < ActiveSupport::TestCase
- test "get owned_items" do
- use_token :active
- oi = User.find(api_fixture('users')['active']['uuid']).owned_items
- assert_operator(0, :<, oi.count,
- "Expected to find some items belonging to :active user")
- assert_operator(0, :<, oi.items_available,
- "Expected owned_items response to have items_available > 0")
- oi_uuids = oi.collect { |i| i['uuid'] }
- expect = api_fixture('specimens')['owned_by_active_user']['uuid']
- assert_includes(oi_uuids, expect,
- "Expected active user's owned_items to include #{expect}")
- end
end
Required arguments are displayed in %{background:#ccffcc}green%.
+h2. contents
+
+Retrieve a list of items which are associated with the given group by ownership (i.e., the group owns the item) or a "name" link (i.e., a "name" link referencing the item).
+
+Arguments:
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+{background:#ccffcc}.|uuid|string|The UUID of the group in question.|path||
+|include_linked|boolean|If false, results will only include items whose @owner_uuid@ attribute is the specified group. If true, results will additionally include items for which a "name" link exists.|path|{white-space:nowrap}. @false@ (default)
+@true@|
+
+If @include_linked@ is @true@, the @"links"@ field in the response will contain the "name" links referencing the objects in the @"items"@ field.
+
h2. create
Create a new Group.
|order|string|Order in which to return matching groups.|query||
|filters|array|Conditions for filtering groups.|query||
-h2. owned_items
-
-Retrieve a list of items which are owned by the given group.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the group in question.|path||
-|include_linked|boolean|If true, results will also include items on which the given group has _can_manage_ permission, even if they are owned by different users/groups.|path|{white-space:nowrap}. @false@ (default)
-@true@|
-
h2. show
show groups
|order|string|Order in which to return matching users.|query||
|filters|array|Conditions for filtering users.|query||
-h2. owned_items
-
-Retrieve a list of items which are owned by the given user.
-
-Arguments:
-
-table(table table-bordered table-condensed).
-|_. Argument |_. Type |_. Description |_. Location |_. Example |
-{background:#ccffcc}.|uuid|string|The UUID of the user in question.|path||
-|include_linked|boolean|If true, results will also include items on which the given user has _can_manage_ permission, even if they are owned by different users/groups.|path|{white-space:nowrap}. @false@ (default)
-@true@|
-
h2. show
show users
before_filter :catch_redirect_hint
before_filter(:find_object_by_uuid,
except: [:index, :create] + ERROR_ACTIONS)
- 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 :load_limit_offset_order_params, only: [:index, :contents]
+ before_filter :load_where_param, only: [:index, :contents]
+ before_filter :load_filters_param, only: [:index, :contents]
before_filter :find_objects_for_index, :only => :index
before_filter :reload_object_before_update, :only => :update
before_filter(:render_404_if_no_object,
show
end
- def self._owned_items_requires_parameters
+ def self._contents_requires_parameters
_index_requires_parameters.
merge({
include_linked: {
})
end
- def owned_items
+ def contents
all_objects = []
all_available = 0
# disappointed: when Rails reloads model classes, we get two
# distinct classes called Link which do not equal each
# other. But we can still rely on klass.to_s to be "Link".
- when 'ApiClientAuthorization'
+ when 'ApiClientAuthorization', 'UserAgreement'
# Do not want.
else
@objects = klass.readable_by(*@read_users)
cond_sql = "#{klass.table_name}.owner_uuid = ?"
cond_params = [@object.uuid]
if params[:include_linked]
- @objects = @objects.
- joins("LEFT JOIN links mng_links"\
- " ON mng_links.link_class=#{klass.sanitize 'permission'}"\
- " AND mng_links.name=#{klass.sanitize 'can_manage'}"\
- " AND mng_links.tail_uuid=#{klass.sanitize @object.uuid}"\
- " AND mng_links.head_uuid=#{klass.table_name}.uuid")
- cond_sql += " OR mng_links.uuid IS NOT NULL"
+ cond_sql += " OR #{klass.table_name}.uuid IN (SELECT head_uuid FROM links WHERE link_class=#{klass.sanitize 'name'} AND links.tail_uuid=#{klass.sanitize @object.uuid})"
end
- @objects = @objects.where(cond_sql, *cond_params).order(:uuid)
+ @objects = @objects.where(cond_sql, *cond_params).order("#{klass.table_name}.uuid")
@limit = limit_all - all_objects.count
apply_where_limit_order_params
items_available = @objects.
end
end
@objects = all_objects || []
+ @links = Link.where('link_class=? and owner_uuid=?'\
+ ' and owner_uuid=tail_uuid'\
+ ' and head_uuid in (?)',
+ 'name',
+ @object.uuid,
+ @objects.collect(&:uuid))
@object_list = {
:kind => "arvados#objectList",
:etag => "",
:self_link => "",
+ :links => @links.as_api_response(nil),
:offset => offset_all,
:limit => limit_all,
:items_available => all_available,
# to this row, or to the owner of this row (see join() below).
sql_conds += ["#{table_name}.owner_uuid in (?)",
"#{table_name}.uuid in (?)",
- "permissions.head_uuid IS NOT NULL"]
+ "uuid IN (SELECT head_uuid FROM links WHERE link_class='permission' AND tail_uuid IN (#{sanitized_uuid_list}))"]
sql_params += [uuid_list, user_uuids]
if self == Link and users_list.any?
# user (the identity with authorization to read)
#
# Link class is 'permission' ('write' and 'manage' implicitly include 'read')
-
- joins("LEFT JOIN links permissions ON permissions.head_uuid in (#{table_name}.owner_uuid, #{table_name}.uuid #{or_object_uuid}) AND permissions.tail_uuid in (#{sanitized_uuid_list}) AND permissions.link_class='permission'")
- .where(sql_conds.join(' OR '), *sql_params).uniq
-
+ where(sql_conds.join(' OR '), *sql_params)
else
# At least one user is admin, so don't bother to apply any restrictions.
self
end
-
end
def logged_attributes
get 'used_by', on: :member
end
resources :groups do
- get 'owned_items', on: :member
+ get 'contents', on: :member
end
resources :humans
resources :job_tasks
post 'activate', on: :member
post 'setup', on: :collection
post 'unsetup', on: :member
- get 'owned_items', on: :member
end
resources :virtual_machines do
get 'logins', on: :member
--- /dev/null
+class AddUniqueNameIndexToLinks < ActiveRecord::Migration
+ def change
+ # todo: add "check (link_class is not 'name' or name is not null)"
+ add_index :links, [:tail_uuid, :name], where: "link_class='name'", unique: true
+ end
+end
modified_at: 2014-04-21 15:37:48 -0400
updated_at: 2014-04-21 15:37:48 -0400
name: A Subfolder
- description: Test folder belonging to active user's first test folder
+ description: "Test folder belonging to active user's first test folder"
group_class: folder
specimen_is_in_two_folders:
uuid: zzzzz-o0j2j-ryhm1bn83ni03sn
- owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ owner_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
created_at: 2014-04-21 15:37:48 -0400
modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
updated_at: 2014-04-21 15:37:48 -0400
tail_uuid: zzzzz-j7d0g-axqo7eu9pwvna1x
head_uuid: zzzzz-2x53u-5gid26432uujf79
- link_class: permission
- name: can_manage
+ link_class: name
+ name: "I'm in a subfolder, too"
+ properties: {}
+
+template_name_in_afolder:
+ uuid: zzzzz-o0j2j-4kpwf3d6rwkeqhl
+ owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ created_at: 2014-04-29 16:47:26 -0400
+ modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+ modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ modified_at: 2014-04-29 16:47:26 -0400
+ updated_at: 2014-04-29 16:47:26 -0400
+ tail_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
+ head_uuid: zzzzz-p5p6p-aox0k0ofxrystgw
+ link_class: name
+ name: "I'm a template in a folder"
+ properties: {}
+
+job_name_in_afolder:
+ uuid: zzzzz-o0j2j-1kt6dppqcxbl1yt
+ owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ created_at: 2014-04-29 16:47:26 -0400
+ modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+ modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+ modified_at: 2014-04-29 16:47:26 -0400
+ updated_at: 2014-04-29 16:47:26 -0400
+ tail_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
+ head_uuid: zzzzz-8i9sb-pshmckwoma9plh7
+ link_class: name
+ name: "I'm a job in a folder"
properties: {}
foo_collection_tag:
test 'get group-owned objects' do
authorize_with :active
- get :owned_items, {
+ get :contents, {
id: groups(:afolder).uuid,
format: :json,
+ include_linked: true,
}
assert_response :success
assert_operator 2, :<=, json_response['items_available']
assert_operator 2, :<=, json_response['items'].count
+ kinds = json_response['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
test 'get group-owned objects with limit' do
authorize_with :active
- get :owned_items, {
+ get :contents, {
id: groups(:afolder).uuid,
limit: 1,
format: :json,
test 'get group-owned objects with limit and offset' do
authorize_with :active
- get :owned_items, {
+ get :contents, {
id: groups(:afolder).uuid,
limit: 1,
offset: 12345,
test 'get group-owned objects with additional filter matching nothing' do
authorize_with :active
- get :owned_items, {
+ get :contents, {
id: groups(:afolder).uuid,
filters: [['uuid', 'in', ['foo_not_a_uuid','bar_not_a_uuid']]],
format: :json,
test 'get group-owned objects without include_linked' do
unexpected_uuid = specimens(:in_afolder_linked_from_asubfolder).uuid
authorize_with :active
- get :owned_items, {
+ get :contents, {
id: groups(:asubfolder).uuid,
format: :json,
}
test 'get group-owned objects with include_linked' do
expected_uuid = specimens(:in_afolder_linked_from_asubfolder).uuid
authorize_with :active
- get :owned_items, {
+ get :contents, {
id: groups(:asubfolder).uuid,
include_linked: true,
format: :json,
assert_response :success
uuids = json_response['items'].collect { |i| i['uuid'] }
assert_includes uuids, expected_uuid, "Did not get #{expected_uuid}"
+
+ expected_name = links(:specimen_is_in_two_folders).name
+ found_specimen_name = false
+ assert(json_response['links'].any?,
+ "Expected a non-empty array of links in response")
+ json_response['links'].each do |link|
+ if link['head_uuid'] == expected_uuid
+ if link['name'] == expected_name
+ found_specimen_name = true
+ end
+ end
+ end
+ assert(found_specimen_name,
+ "Expected to find name '#{expected_name}' in response")
+ end
+
+ [false, true].each do |inc_ind|
+ test "get all pages of group-owned #{'and -linked ' if inc_ind}objects" do
+ authorize_with :active
+ limit = 5
+ offset = 0
+ items_available = nil
+ uuid_received = {}
+ owner_received = {}
+ while true
+ # Behaving badly here, using the same controller multiple
+ # times within a test.
+ @json_response = nil
+ get :contents, {
+ id: groups(:afolder).uuid,
+ include_linked: inc_ind,
+ limit: limit,
+ offset: offset,
+ format: :json,
+ }
+ assert_response :success
+ assert_operator(0, :<, json_response['items'].count,
+ "items_available=#{items_available} but received 0 "\
+ "items with offset=#{offset}")
+ items_available ||= json_response['items_available']
+ assert_equal(items_available, json_response['items_available'],
+ "items_available changed between page #{offset/limit} "\
+ "and page #{1+offset/limit}")
+ json_response['items'].each do |item|
+ uuid = item['uuid']
+ assert_equal(nil, uuid_received[uuid],
+ "Received '#{uuid}' again on page #{1+offset/limit}")
+ uuid_received[uuid] = true
+ owner_received[item['owner_uuid']] = true
+ offset += 1
+ if not inc_ind
+ assert_equal groups(:afolder).uuid, item['owner_uuid']
+ end
+ end
+ break if offset >= items_available
+ end
+ if inc_ind
+ assert_operator 0, :<, (json_response.keys - [users(:active).uuid]).count,
+ "Set include_linked=true but did not receive any non-owned items"
+ end
+ end
end
+
+ %w(offset limit).each do |arg|
+ ['foo', '', '1234five', '0x10', '-8'].each do |val|
+ test "Raise error on bogus #{arg} parameter #{val.inspect}" do
+ authorize_with :active
+ get :contents, {
+ :id => groups(:afolder).uuid,
+ :format => :json,
+ arg => val,
+ }
+ assert_response 422
+ end
+ end
+ end
+
end
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,
- limit: 500,
- format: :json,
- }
- assert_response :success
- assert_operator 2, :<=, json_response['items_available']
- assert_operator 2, :<=, json_response['items'].count
- kinds = json_response['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
-
- [false, true].each do |inc_ind|
- test "get all pages of user-owned #{'and -linked ' if inc_ind}objects" do
- authorize_with :active
- limit = 5
- offset = 0
- items_available = nil
- uuid_received = {}
- owner_received = {}
- while true
- # Behaving badly here, using the same controller multiple
- # times within a test.
- @json_response = nil
- get :owned_items, {
- id: users(:active).uuid,
- include_linked: inc_ind,
- limit: limit,
- offset: offset,
- format: :json,
- }
- assert_response :success
- assert_operator(0, :<, json_response['items'].count,
- "items_available=#{items_available} but received 0 "\
- "items with offset=#{offset}")
- items_available ||= json_response['items_available']
- assert_equal(items_available, json_response['items_available'],
- "items_available changed between page #{offset/limit} "\
- "and page #{1+offset/limit}")
- json_response['items'].each do |item|
- uuid = item['uuid']
- assert_equal(nil, uuid_received[uuid],
- "Received '#{uuid}' again on page #{1+offset/limit}")
- uuid_received[uuid] = true
- owner_received[item['owner_uuid']] = true
- offset += 1
- if not inc_ind
- assert_equal users(:active).uuid, item['owner_uuid']
- end
- end
- break if offset >= items_available
- end
- if inc_ind
- assert_operator 0, :<, (json_response.keys - [users(:active).uuid]).count,
- "Set include_linked=true but did not receive any non-owned items"
- end
- end
- end
-
- %w(offset limit).each do |arg|
- ['foo', '', '1234five', '0x10', '-8'].each do |val|
- test "Raise error on bogus #{arg} parameter #{val.inspect}" do
- authorize_with :active
- get :owned_items, {
- :id => users(:active).uuid,
- :format => :json,
- arg => val,
- }
- assert_response 422
- end
- end
- end
end