[ARVADOS] updated: 2.1.0-451-g040d4a95a

Git user git at public.arvados.org
Sat Mar 6 16:17:32 UTC 2021


Summary of changes:
 services/api/app/models/group.rb | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

  discards  9509ff434e679028689669cdc2efc2282319aed1 (commit)
  discards  aea50fee4651b773431c533eedb10d3b257b4224 (commit)
  discards  d2f6583ef5d588fdfe7dd65a2deb75b41d9498a0 (commit)
  discards  7a3215a77922f308857881f108cfabc3b85d2378 (commit)
       via  040d4a95a989bca060a40435c86e5410e469969d (commit)
       via  70d00282f19c8ce7dc54c9dc3289e6e31446816b (commit)
       via  8347fca787700f142014a6571c181d90c04e0251 (commit)
       via  8480cc77dbb55340b07946eae5130c3db96b8136 (commit)

This update added new revisions after undoing existing revisions.  That is
to say, the old revision is not a strict subset of the new revision.  This
situation occurs when you --force push a change and generate a repository
containing something like this:

 * -- * -- B -- O -- O -- O (9509ff434e679028689669cdc2efc2282319aed1)
            \
             N -- N -- N (040d4a95a989bca060a40435c86e5410e469969d)

When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.

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 040d4a95a989bca060a40435c86e5410e469969d
Author: Ward Vandewege <ward at curii.com>
Date:   Fri Mar 5 18:13:44 2021 -0500

    17199: add (rudimentary) filter group support to workbench.
    
    Arvados-DCO-1.1-Signed-off-by: Ward Vandewege <ward at curii.com>

diff --git a/apps/workbench/app/assets/javascripts/components/search.js b/apps/workbench/app/assets/javascripts/components/search.js
index fc6308678..83ed1a68d 100644
--- a/apps/workbench/app/assets/javascripts/components/search.js
+++ b/apps/workbench/app/assets/javascripts/components/search.js
@@ -127,6 +127,12 @@ window.Search = {
                             filters: [['group_class', '=', 'project']],
                             description: 'project',
                         },
+                        {
+                            wb_path: 'projects',
+                            api_path: 'arvados/v1/groups',
+                            filters: [['group_class', '=', 'filter']],
+                            description: 'project',
+                        },
                         {
                             wb_path: 'collections',
                             api_path: 'arvados/v1/collections',
diff --git a/apps/workbench/app/controllers/actions_controller.rb b/apps/workbench/app/controllers/actions_controller.rb
index 885f53936..b0b7a0b64 100644
--- a/apps/workbench/app/controllers/actions_controller.rb
+++ b/apps/workbench/app/controllers/actions_controller.rb
@@ -34,7 +34,7 @@ class ActionsController < ApplicationController
         @object.link_class == 'name' and
         ArvadosBase::resource_class_for_uuid(@object.head_uuid) == Collection
       redirect_to collection_path(id: @object.uuid)
-    elsif @object.is_a?(Group) and @object.group_class == 'project'
+    elsif @object.is_a?(Group) and (@object.group_class == 'project' or @object.group_class == 'filter')
       redirect_to project_path(id: @object.uuid)
     elsif @object
       redirect_to @object
diff --git a/apps/workbench/app/controllers/application_controller.rb b/apps/workbench/app/controllers/application_controller.rb
index 6d139cd5f..04449d5f1 100644
--- a/apps/workbench/app/controllers/application_controller.rb
+++ b/apps/workbench/app/controllers/application_controller.rb
@@ -95,7 +95,7 @@ class ApplicationController < ActionController::Base
     # exception here than in a template.)
     unless current_user.nil?
       begin
-        my_starred_projects current_user
+        my_starred_projects current_user, 'project'
         build_my_wanted_projects_tree current_user
       rescue ArvadosApiClient::ApiError
         # Fall back to the default-setting code later.
@@ -824,7 +824,7 @@ class ApplicationController < ActionController::Base
   helper_method :all_projects
   def all_projects
     @all_projects ||= Group.
-      filter([['group_class','=','project']]).order('name')
+      filter([['group_class','IN',['project','filter']]]).order('name')
   end
 
   helper_method :my_projects
@@ -925,13 +925,17 @@ class ApplicationController < ActionController::Base
   end
 
   helper_method :my_starred_projects
-  def my_starred_projects user
+  def my_starred_projects user, group_class
     return if defined?(@starred_projects) && @starred_projects
     links = Link.filter([['owner_uuid', 'in', ["#{Rails.configuration.ClusterID}-j7d0g-publicfavorites", user.uuid]],
                          ['link_class', '=', 'star'],
                          ['head_uuid', 'is_a', 'arvados#group']]).with_count("none").select(%w(head_uuid))
     uuids = links.collect { |x| x.head_uuid }
-    starred_projects = Group.filter([['uuid', 'in', uuids]]).order('name').with_count("none")
+    if group_class == ""
+      starred_projects = Group.filter([['uuid', 'in', uuids]]).order('name').with_count("none")
+    else
+      starred_projects = Group.filter([['uuid', 'in', uuids],['group_class', '=', group_class]]).order('name').with_count("none")
+    end
     @starred_projects = starred_projects.results
   end
 
@@ -949,7 +953,7 @@ class ApplicationController < ActionController::Base
     @too_many_projects = false
     @reached_level_limit = false
     while from_top.size <= page_size*2
-      current_level = Group.filter([['group_class','=','project'],
+      current_level = Group.filter([['group_class','IN',['project','filter']],
                                     ['owner_uuid', 'in', uuids]])
                       .order('name').limit(page_size*2)
       break if current_level.results.size == 0
diff --git a/apps/workbench/app/controllers/groups_controller.rb b/apps/workbench/app/controllers/groups_controller.rb
index 5da55be0b..6abd2ff11 100644
--- a/apps/workbench/app/controllers/groups_controller.rb
+++ b/apps/workbench/app/controllers/groups_controller.rb
@@ -4,7 +4,7 @@
 
 class GroupsController < ApplicationController
   def index
-    @groups = Group.filter [['group_class', '!=', 'project']]
+    @groups = Group.filter [['group_class', '!=', 'project'], ['group_class', '!=', 'filter']]
     @group_uuids = @groups.collect &:uuid
     @links_from = Link.where(link_class: 'permission', tail_uuid: @group_uuids).with_count("none")
     @links_to = Link.where(link_class: 'permission', head_uuid: @group_uuids).with_count("none")
@@ -12,7 +12,7 @@ class GroupsController < ApplicationController
   end
 
   def show
-    if @object.group_class == 'project'
+    if @object.group_class == 'project' or @object.group_class == 'filter'
       redirect_to(project_path(@object))
     else
       super
diff --git a/apps/workbench/app/helpers/application_helper.rb b/apps/workbench/app/helpers/application_helper.rb
index 786716eb3..f22ab5016 100644
--- a/apps/workbench/app/helpers/application_helper.rb
+++ b/apps/workbench/app/helpers/application_helper.rb
@@ -176,7 +176,7 @@ module ApplicationHelper
         raw(link_name)
       else
         controller_class = resource_class.to_s.tableize
-        if controller_class.eql?('groups') and object.andand.group_class.eql?('project')
+        if controller_class.eql?('groups') and (object.andand.group_class.eql?('project') or object.andand.group_class.eql?('filter'))
           controller_class = 'projects'
         end
         (link_to raw(link_name), { controller: controller_class, action: 'show', id: ((opts[:name_link].andand.uuid) || link_uuid) }, style_opts) + raw(tags)
diff --git a/apps/workbench/app/models/group.rb b/apps/workbench/app/models/group.rb
index 08b13bf34..ea3da2db5 100644
--- a/apps/workbench/app/models/group.rb
+++ b/apps/workbench/app/models/group.rb
@@ -20,6 +20,13 @@ class Group < ArvadosBase
     ret
   end
 
+  def editable?
+    if group_class == 'filter'
+      return false
+    end
+    super
+  end
+
   def contents params={}
     res = arvados_api_client.api self.class, "/#{self.uuid}/contents", {
       _method: 'GET'
@@ -30,7 +37,7 @@ class Group < ArvadosBase
   end
 
   def class_for_display
-    group_class == 'project' ? 'Project' : super
+    (group_class == 'project' or group_class == 'filter') ? 'Project' : super
   end
 
   def textile_attributes
diff --git a/apps/workbench/app/views/application/_projects_tree_menu.html.erb b/apps/workbench/app/views/application/_projects_tree_menu.html.erb
index 08d3b8111..805d5279c 100644
--- a/apps/workbench/app/views/application/_projects_tree_menu.html.erb
+++ b/apps/workbench/app/views/application/_projects_tree_menu.html.erb
@@ -2,7 +2,7 @@
 
 SPDX-License-Identifier: AGPL-3.0 %>
 
-<% starred_projects = my_starred_projects current_user%>
+<% starred_projects = my_starred_projects current_user, '' %>
 <% if starred_projects.andand.any? %>
   <li role="presentation" class="dropdown-header">
     My favorite projects
diff --git a/apps/workbench/app/views/projects/_choose.html.erb b/apps/workbench/app/views/projects/_choose.html.erb
index 8e5695e6d..633a9ba33 100644
--- a/apps/workbench/app/views/projects/_choose.html.erb
+++ b/apps/workbench/app/views/projects/_choose.html.erb
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0 %>
 
       <div class="modal-body">
         <div class="selectable-container" style="height: 15em; overflow-y: scroll">
-          <% starred_projects = my_starred_projects current_user%>
+          <% starred_projects = my_starred_projects current_user, 'project' %>
           <% if starred_projects.andand.any? %>
             <% writable_projects = starred_projects.select(&:editable?) %>
             <% writable_projects.each do |projectnode| %>
diff --git a/apps/workbench/app/views/projects/show.html.erb b/apps/workbench/app/views/projects/show.html.erb
index 6066335a1..6ad8742a3 100644
--- a/apps/workbench/app/views/projects/show.html.erb
+++ b/apps/workbench/app/views/projects/show.html.erb
@@ -11,6 +11,9 @@ SPDX-License-Identifier: AGPL-3.0 %>
       <%= render_editable_attribute @object, 'name', nil, { 'data-emptytext' => "New project" } %>
     <% end %>
   </h2>
+  <% if @object.group_class == 'filter' %>
+    This is a filter group.
+  <% end %>
 <% end %>
 
 <%

commit 70d00282f19c8ce7dc54c9dc3289e6e31446816b
Author: Ward Vandewege <ward at curii.com>
Date:   Fri Mar 5 10:44:23 2021 -0500

    17199: add filter group support to arv-mount.
    
    Arvados-DCO-1.1-Signed-off-by: Ward Vandewege <ward at curii.com>

diff --git a/services/fuse/arvados_fuse/fusedir.py b/services/fuse/arvados_fuse/fusedir.py
index 1fab2e0fb..cfef43fdb 100644
--- a/services/fuse/arvados_fuse/fusedir.py
+++ b/services/fuse/arvados_fuse/fusedir.py
@@ -683,9 +683,11 @@ and the directory will appear if it exists.
 
             if group_uuid_pattern.match(k):
                 project = self.api.groups().list(
-                    filters=[['group_class', '=', 'project'], ["uuid", "=", k]]).execute(num_retries=self.num_retries)
+                    filters=[["uuid", "=", k]]).execute(num_retries=self.num_retries)
                 if project[u'items_available'] == 0:
                     return False
+                if project[u'items'][0][u'group_class'] != u'project' and project[u'items'][0][u'group_class'] != u'filter':
+                    return False
                 e = self.inodes.add_entry(ProjectDirectory(
                     self.inode, self.inodes, self.api, self.num_retries, project[u'items'][0]))
             else:
diff --git a/services/fuse/tests/test_mount.py b/services/fuse/tests/test_mount.py
index b2816ac16..54316bb9a 100644
--- a/services/fuse/tests/test_mount.py
+++ b/services/fuse/tests/test_mount.py
@@ -129,7 +129,9 @@ class FuseMagicTest(MountTestBase):
 
         self.test_project = run_test_server.fixture('groups')['aproject']['uuid']
         self.non_project_group = run_test_server.fixture('groups')['public_role']['uuid']
+        self.filter_group = run_test_server.fixture('groups')['afiltergroup']['uuid']
         self.collection_in_test_project = run_test_server.fixture('collections')['foo_collection_in_aproject']['name']
+        self.collection_in_filter_group = run_test_server.fixture('collections')['baz_file']['name']
 
         cw = arvados.CollectionWriter()
 
@@ -157,6 +159,11 @@ class FuseMagicTest(MountTestBase):
                       llfuse.listdir(os.path.join(self.mounttmp, self.test_project)))
         self.assertIn(self.collection_in_test_project,
                       llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.test_project)))
+        self.assertIn(self.collection_in_filter_group,
+                      llfuse.listdir(os.path.join(self.mounttmp, self.filter_group)))
+        self.assertIn(self.collection_in_filter_group,
+                      llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.filter_group)))
+
 
         mount_ls = llfuse.listdir(self.mounttmp)
         self.assertIn('README', mount_ls)
@@ -166,6 +173,8 @@ class FuseMagicTest(MountTestBase):
         self.assertIn(self.test_project, mount_ls)
         self.assertIn(self.test_project,
                       llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
+        self.assertIn(self.filter_group,
+                      llfuse.listdir(os.path.join(self.mounttmp, 'by_id')))
 
         with self.assertRaises(OSError):
             llfuse.listdir(os.path.join(self.mounttmp, 'by_id', self.non_project_group))

commit 8347fca787700f142014a6571c181d90c04e0251
Author: Ward Vandewege <ward at curii.com>
Date:   Thu Mar 4 17:42:06 2021 -0500

    17119: add documentation.
    
    Arvados-DCO-1.1-Signed-off-by: Ward Vandewege <ward at curii.com>

diff --git a/doc/_config.yml b/doc/_config.yml
index b0355e269..4e0e2f873 100644
--- a/doc/_config.yml
+++ b/doc/_config.yml
@@ -44,6 +44,7 @@ navbar:
       - user/tutorials/tutorial-keep-mount-os-x.html.textile.liquid
       - user/tutorials/tutorial-keep-mount-windows.html.textile.liquid
       - user/tutorials/tutorial-keep-collection-lifecycle.html.textile.liquid
+      - user/topics/projects.html.textile.liquid
       - user/topics/arv-copy.html.textile.liquid
       - user/topics/collection-versioning.html.textile.liquid
       - user/topics/storage-classes.html.textile.liquid
diff --git a/doc/api/methods/groups.html.textile.liquid b/doc/api/methods/groups.html.textile.liquid
index f85e621db..73df7a066 100644
--- a/doc/api/methods/groups.html.textile.liquid
+++ b/doc/api/methods/groups.html.textile.liquid
@@ -25,8 +25,9 @@ Each Group has, in addition to the "Common resource fields":{{site.baseurl}}/api
 table(table table-bordered table-condensed).
 |_. Attribute|_. Type|_. Description|_. Example|
 |name|string|||
-|group_class|string|Type of group. This does not affect behavior, but determines how the group is presented in the user interface. For example, @project@ indicates that the group should be displayed by Workbench and arv-mount as a project for organizing and naming objects.|@"project"@
-null|
+|group_class|string|Type of group. @project@ and @filter@ indicate that the group should be displayed by Workbench and arv-mount as a project for organizing and naming objects. @role@ means FIXME. |@"filter"@
+@"project"@
+@"role"@|
 |description|text|||
 |properties|hash|User-defined metadata, may be used in queries using "subproperty filters":{{site.baseurl}}/api/methods.html#subpropertyfilters ||
 |writable_by|array|List of UUID strings identifying Users and other Groups that have write permission for this Group.  Only users who are allowed to administer the Group will receive a full list.  Other users will receive a partial list that includes the Group's owner_uuid and (if applicable) their own user UUID.||
@@ -34,6 +35,49 @@ null|
 |delete_at|datetime|If @delete_at@ is non-null and in the past, the group and all objects directly or indirectly owned by the group may be permanently deleted.||
 |is_trashed|datetime|True if @trash_at@ is in the past, false if not.||
 
+ at filter@ groups have a special @properties@ field named @filters@, which must be an array of arrays with 3 elements, each describing a filter. @filter@ groups are virtual groups; they can not own other objects. Filter attributes must include the object type (@collections@, @container_requests@, @groups@, @workflows@), separated with a dot from the field to be filtered on. Filters are applied with an implied *and* between them, but each filter only applies to the object type specified. The results are subject to the usual access controls - they are a subset of all objects the user can see. Here is an example:
+
+<pre>
+ "properties":{
+  "filters":[
+   [
+    "groups.name",
+    "like",
+    "Public%"
+   ]
+  ]
+ },
+</pre>
+
+This @filter@ group will return all groups (projects) that have a name starting with the word @Public@ and are visible to the user issuing the query. Because groups can contain many types of object, it will also return all objects of other types that the user can see.
+
+The 'is_a' filter operator is of particular interest to limit the @filter@ group 'content' to the desired object(s). When the 'is_a' operator is used, the attribute must be 'uuid'. The operand may be a string or an array which means objects of either type will match the filter. This example will return all groups (projects) that have a name starting with the word @Public@, as well as all collections that are in the project with uuid @zzzzz-j7d0g-0123456789abcde at .
+
+<pre>
+ "properties":{
+  "filters":[
+   [
+    "groups.name",
+    "like",
+    "Public%"
+   ],
+   [
+    "collections.owner_uuid",
+    "=",
+    "zzzzz-j7d0g-0123456789abcde"
+   ],
+   [
+    "uuid",
+    "is_a",
+    [
+     "arvados#group",
+     "arvados#collection"
+    ]
+   ]
+  ]
+ },
+ </pre>
+
 h2. Methods
 
 See "Common resource methods":{{site.baseurl}}/api/methods.html for more information about @create@, @delete@, @get@, @list@, and @update at .
diff --git a/doc/api/permission-model.html.textile.liquid b/doc/api/permission-model.html.textile.liquid
index 54c4a3331..f54dc8bf2 100644
--- a/doc/api/permission-model.html.textile.liquid
+++ b/doc/api/permission-model.html.textile.liquid
@@ -26,7 +26,7 @@ There are four levels of permission: *none*, *can_read*, *can_write*, and *can_m
 
 h2. Ownership
 
-All Arvados objects have an @owner_uuid@ field. Valid uuid types for @owner_uuid@ are "User" and "Group".  For Group, the @group_class@ must be a "project".
+All Arvados objects have an @owner_uuid@ field. Valid uuid types for @owner_uuid@ are "User" and "Group".  For Group, the @group_class@ must be "filter", "project" or "role".
 
 The User or Group specified by @owner_uuid@ has *can_manage* permission on the object.  This permission is one way: an object that is owned does not get any special permissions on the User or Group that owns it.
 
@@ -63,9 +63,15 @@ h2. Projects and Roles
 A "project" is a subtype of Group that is displayed as a "Project" in Workbench, and as a directory by @arv-mount at .
 * A project can own things (appear in @owner_uuid@)
 * A project can be owned by a user or another project.
-* The name of a project is unique only among projects with the same owner_uuid.
+* The name of a project is unique only among projects and filters with the same owner_uuid.
 * Projects can be targets (@head_uuid@) of permission links, but not origins (@tail_uuid@).  Putting a project in a @tail_uuid@ field is an error.
 
+A "filter" is a subtype of Group that is displayed as a "Project" in Workbench, and as a directory by @arv-mount at . See "the groups API documentation":/api/methods/groups.html for more information.
+* A filter cannot own things (cannot appear in @owner_uuid@).  Putting a role in an @owner_uuid@ field is an error.
+* A filter can be owned by a user or another project.
+* The name of a filter is unique only among projects and filters with the same owner_uuid.
+* Filters can be targets (@head_uuid@) of permission links, but not origins (@tail_uuid@).  Putting a filter in a @tail_uuid@ field is an error.
+
 A "role" is a subtype of Group that is treated in Workbench as a group of users who have permissions in common (typically an organizational group).
 * A role cannot own things (cannot appear in @owner_uuid@).  Putting a role in an @owner_uuid@ field is an error.
 * All roles are owned by the system user.
diff --git a/doc/user/topics/projects.html.textile.liquid b/doc/user/topics/projects.html.textile.liquid
new file mode 100644
index 000000000..f4b236c4c
--- /dev/null
+++ b/doc/user/topics/projects.html.textile.liquid
@@ -0,0 +1,36 @@
+---
+layout: default
+navsection: userguide
+title: "Projects"
+...
+{% comment %}
+Copyright (C) The Arvados Authors. All rights reserved.
+
+SPDX-License-Identifier: CC-BY-SA-3.0
+{% endcomment %}
+
+Arvados @projects@ are used to organize objects. Projects can contain @collections@, @container requests@, @workflows@, etc. Projects can also contain other projects. An object is part of a project if the @owner_uuid@ of the object is set to the uuid of the project.
+
+Projects are implemented as a subtype of the Arvados @group@ object type, with @group_class@ set to the value "project". More information is available in the "groups API reference":/api/methods/groups.html.
+
+Projects can be manipulated via Workbench, the cli tools, the SDKs, and the Arvados APIs.
+
+h2. The home project
+
+Each user has a @home project@, which is implemented differently. This is a virtual project that is comprised of all objects owned by the user, in other words, all objects with the @owner_uuid@ set to the @uuid@ of the user. The home project is accessible via Workbench, which makes it easy view its contents and to move objects from and to the home project. The home project is also accessible via FUSE, WebDAV and the S3 interface.
+
+The same thing can be done via the APIs. To put something in a user's home project via the cli or SDKs, one would set the @owner_uuid@ of the object to the user's @uuid at . This also implies that this user now has full ownership and control over that object.
+
+The contents of the home project can be accessed with the @group contents@ API, e.g. via the cli with this command:
+<pre>arv group contents --uuid zzzzz-tpzed-123456789012345</pre>
+In this command, `zzzzz-tpzed-123456789012345` is a @user@ uuid, which is unusual because we are using it as the argument to a @groups@ API. The @group contents@ API is normally used with a @group@ uuid.
+
+Because the home project is a virtual project, other operations via the @groups@ API are not supported.
+
+h2. Filter groups
+
+Filter groups are another type of virtual project. They are implemented as an Arvados @group@ object with @group_class@ set to the value "filter".
+
+Filter groups define one or more filters which are applied to all objects that the current user can see, and returned as the contents of the @group at . @filter@ groups are described in more detail in the "groups API reference":/api/methods/groups.html.
+
+Filter groups are accessible (read-only) via Workbench and the Arvados FUSE mount, WebDAV and S3 interface. Filter groups must currently be defined via the API, SDK or cli, there is no Workbench support yet.

commit 8480cc77dbb55340b07946eae5130c3db96b8136
Author: Ward Vandewege <ward at curii.com>
Date:   Wed Mar 3 16:04:54 2021 -0500

    17119: add filter validation for filter groups. Add a test for the
           'is_a' filter.
    
    Arvados-DCO-1.1-Signed-off-by: Ward Vandewege <ward at curii.com>

diff --git a/sdk/go/arvados/fs_project_test.go b/sdk/go/arvados/fs_project_test.go
index 9d77c31d8..0564e2fae 100644
--- a/sdk/go/arvados/fs_project_test.go
+++ b/sdk/go/arvados/fs_project_test.go
@@ -64,6 +64,15 @@ func (s *SiteFSSuite) TestFilterGroup(c *check.C) {
 
 	_, err = s.fs.OpenFile("/fg2/A Project", 0, 0)
 	c.Assert(err, check.IsNil)
+
+	// An 'is_a' 'arvados#collection' filter means only collections should be returned.
+	s.fs.MountProject("fg3", fixtureAFilterGroupThreeUUID)
+
+	_, err = s.fs.OpenFile("/fg3/baz_file", 0, 0)
+	c.Assert(err, check.IsNil)
+
+	_, err = s.fs.OpenFile("/fg3/A Subproject", 0, 0)
+	c.Assert(err, check.Not(check.IsNil))
 }
 
 func (s *SiteFSSuite) TestCurrentUserHome(c *check.C) {
diff --git a/sdk/go/arvados/fs_site_test.go b/sdk/go/arvados/fs_site_test.go
index 02f21ded5..b1c627f89 100644
--- a/sdk/go/arvados/fs_site_test.go
+++ b/sdk/go/arvados/fs_site_test.go
@@ -20,6 +20,7 @@ const (
 	fixtureAProjectUUID            = "zzzzz-j7d0g-v955i6s2oi1cbso"
 	fixtureThisFilterGroupUUID     = "zzzzz-j7d0g-thisfiltergroup"
 	fixtureAFilterGroupTwoUUID     = "zzzzz-j7d0g-afiltergrouptwo"
+	fixtureAFilterGroupThreeUUID   = "zzzzz-j7d0g-filtergroupthre"
 	fixtureFooAndBarFilesInDirUUID = "zzzzz-4zz18-foonbarfilesdir"
 	fixtureFooCollectionName       = "zzzzz-4zz18-fy296fx3hot09f7 added sometime"
 	fixtureFooCollectionPDH        = "1f4b0bc7583c2a7f9102c395f4ffc5e3+45"
diff --git a/services/api/app/models/group.rb b/services/api/app/models/group.rb
index 870e0d0c4..6131608c3 100644
--- a/services/api/app/models/group.rb
+++ b/services/api/app/models/group.rb
@@ -18,6 +18,7 @@ class Group < ArvadosModel
 
   validate :ensure_filesystem_compatible_name
   validate :check_group_class
+  validate :check_filter_group_filters
   before_create :assign_name
   after_create :after_ownership_change
   after_create :update_trash
@@ -56,6 +57,40 @@ class Group < ArvadosModel
     end
   end
 
+  def check_filter_group_filters
+    if group_class == 'filter'
+      if !self.properties.key?("filters")
+        return
+      end
+      if !self.properties["filters"].is_a?(Array)
+        errors.add :properties, "filters property must be an array of arrays, each with 3 elements"
+        return
+      end
+      self.properties["filters"].each do |filter|
+        if !filter.is_a?(Array)
+          errors.add :properties, "filters property must be an array of arrays, each with 3 elements"
+          return
+        end
+        if filter.length() != 3
+          errors.add :properties, "filters property must be an array of arrays, each with 3 elements"
+          return
+        end
+        if !filter[0].include?(".") and filter[0].downcase != "uuid"
+          errors.add :properties, "filter attribute must be 'uuid' or contain a dot (e.g. groups.name)"
+          return
+        end
+        if (filter[0].downcase == "uuid" and filter[1].downcase != "is_a") or (filter[0].downcase != "uuid" and filter[1].downcase == "is_a")
+          errors.add :properties, "filter operator must be 'is_a' when attribute is 'uuid'"
+          return
+        end
+        if ! ["=","<","<=",">",">=","!=","like","ilike","in","is_a"].include?(filter[1].downcase)
+          errors.add :properties, "filter operator is not valid (must be =,<,<=,>,>=,!=,like,ilike,in,is_a)"
+          return
+        end
+      end
+    end
+  end
+
   def update_trash
     if saved_change_to_trash_at? or saved_change_to_owner_uuid?
       # The group was added or removed from the trash.
diff --git a/services/api/test/fixtures/groups.yml b/services/api/test/fixtures/groups.yml
index da20f8be9..48925a270 100644
--- a/services/api/test/fixtures/groups.yml
+++ b/services/api/test/fixtures/groups.yml
@@ -133,6 +133,19 @@ afiltergroup2:
   properties:
     filters: []
 
+afiltergroup3:
+  uuid: zzzzz-j7d0g-filtergroupthre
+  owner_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  created_at: 2014-04-21 15:37:48 -0400
+  modified_by_client_uuid: zzzzz-ozdt8-brczlopd8u8d0jr
+  modified_by_user_uuid: zzzzz-tpzed-xurymjxw79nv3jz
+  modified_at: 2014-04-21 15:37:48 -0400
+  updated_at: 2014-04-21 15:37:48 -0400
+  name: A filter group with an is_a collection filter
+  group_class: filter
+  properties:
+    filters: [["uuid", "is_a", "arvados#collection"]]
+
 future_project_viewing_group:
   uuid: zzzzz-j7d0g-futrprojviewgrp
   owner_uuid: zzzzz-tpzed-000000000000000

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list