12032: Test for ensure_unique_name when untrashing groups.
authorPeter Amstutz <peter.amstutz@curoverse.com>
Thu, 31 Aug 2017 16:10:11 +0000 (12:10 -0400)
committerPeter Amstutz <peter.amstutz@curoverse.com>
Thu, 14 Sep 2017 23:39:06 +0000 (19:39 -0400)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <pamstutz@veritasgenetics.com>

doc/api/methods/collections.html.textile.liquid
doc/api/methods/groups.html.textile.liquid
services/api/db/migrate/20170824202826_trashable_groups.rb
services/api/db/structure.sql
services/api/test/functional/arvados/v1/groups_controller_test.rb

index ce1e0c7141354f7165be15514754c67d34a1c730..d753f0990f71facaa7580ac1a3bee8d1f69829a5 100644 (file)
@@ -32,6 +32,9 @@ table(table table-bordered table-condensed).
 |replication_desired|number|Minimum storage replication level desired for each data block referenced by this collection. A value of @null@ signifies that the site default replication level (typically 2) is desired.|@2@|
 |replication_confirmed|number|Replication level most recently confirmed by the storage system. This field is null when a collection is first created, and is reset to null when the manifest_text changes in a way that introduces a new data block. An integer value indicates the replication level of the _least replicated_ data block in the collection.|@2@, null|
 |replication_confirmed_at|datetime|When replication_confirmed was confirmed. If replication_confirmed is null, this field is also null.||
+|trash_at|datetime|If @trash_at@ is non-null and in the past, this collection will be hidden from API calls.  May be untrashed.||
+|delete_at|datetime|If @delete_at@ is non-null and in the past, the collection may be permanently deleted.||
+|is_trashed|datetime|True if @trash_at@ is in the past, false if not.||
 
 h3. Conditions of creating a Collection
 
@@ -61,7 +64,7 @@ table(table table-bordered table-condensed).
 
 h3. delete
 
-Delete an existing Collection.
+Put a Collection in the trash.  This sets the @trash_at@ field to @now@ and @delete_at@ field to @now@ + token TTL.  A trashed group is invisible to most API calls unless the @include_trash@ parameter is true.
 
 Arguments:
 
@@ -97,3 +100,14 @@ table(table table-bordered table-condensed).
 |_. Argument |_. Type |_. Description |_. Location |_. Example |
 {background:#ccffcc}.|uuid|string|The UUID of the Collection in question.|path||
 |collection|object||query||
+
+h3. untrash
+
+Remove a Collection from the trash.  This sets the @trash_at@ and @delete_at@ fields to @null@.
+
+Arguments:
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+{background:#ccffcc}.|uuid|string|The UUID of the Collection to untrash.|path||
+|ensure_unique_name|boolean (default false)|Rename collection uniquely if untrashing it would fail with a unique name conflict.|query||
index 5c8dd4bbbd09126177534e7a425a8748dab77497..2716056caac06ca0976ccd595b0cfa89ae17d438 100644 (file)
@@ -29,6 +29,9 @@ table(table table-bordered table-condensed).
 null|
 |description|text|||
 |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.||
+|trash_at|datetime|If @trash_at@ is non-null and in the past, this group and all objects directly or indirectly owned by the group will be hidden from API calls.  May be untrashed.||
+|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.||
 
 h2. Methods
 
@@ -66,7 +69,7 @@ table(table table-bordered table-condensed).
 
 h3. delete
 
-Delete an existing Group.
+Put a Group in the trash.  This sets the @trash_at@ field to @now@ and @delete_at@ field to @now@ + token TTL.  A trashed group is invisible to most API calls unless the @include_trash@ parameter is true.  All objects directly or indirectly owned by the Group are considered trashed as well.
 
 Arguments:
 
@@ -110,3 +113,14 @@ table(table table-bordered table-condensed).
 |_. Argument |_. Type |_. Description |_. Location |_. Example |
 {background:#ccffcc}.|uuid|string|The UUID of the Group in question.|path||
 |group|object||query||
+
+h3. untrash
+
+Remove a Group from the trash.  This sets the @trash_at@ and @delete_at@ fields to @null@.
+
+Arguments:
+
+table(table table-bordered table-condensed).
+|_. Argument |_. Type |_. Description |_. Location |_. Example |
+{background:#ccffcc}.|uuid|string|The UUID of the Group to untrash.|path||
+|ensure_unique_name|boolean (default false)|Rename project uniquely if untrashing it would fail with a unique name conflict.|query||
index b7e3373c663c4ea601512640b49e6c80e1c3240a..6d2a09aeab5cd227633f2f16c814ed8d1e3ffab2 100644 (file)
@@ -1,7 +1,38 @@
 class TrashableGroups < ActiveRecord::Migration
-  def change
+  def up
     add_column :groups, :trash_at, :datetime
-    add_column :groups, :delete_at, :datetime
+    add_index(:groups, :trash_at)
+
     add_column :groups, :is_trashed, :boolean, null: false, default: false
+    add_index(:groups, :is_trashed)
+
+    add_column :groups, :delete_at, :datetime
+    add_index(:groups, :delete_at)
+
+    Group.reset_column_information
+    add_index(:groups, [:owner_uuid, :name],
+              unique: true,
+              where: 'is_trashed = false',
+              name: 'index_groups_on_owner_uuid_and_name')
+    remove_index(:groups,
+                 name: 'groups_owner_uuid_name_unique')
+  end
+
+  def down
+    Group.transaction do
+      add_index(:groups, [:owner_uuid, :name], unique: true,
+                name: 'groups_owner_uuid_name_unique')
+      remove_index(:groups,
+                   name: 'index_groups_on_owner_uuid_and_name')
+
+      remove_index(:groups, :delete_at)
+      remove_column(:groups, :delete_at)
+
+      remove_index(:groups, :is_trashed)
+      remove_column(:groups, :is_trashed)
+
+      remove_index(:groups, :trash_at)
+      remove_column(:groups, :trash_at)
+    end
   end
 end
index 90aa0a37c1f2cf3dc1ccfd1bc1e6b649f89b07a0..9656e7215bae0322d53126640b254493eefaf971 100644 (file)
@@ -393,8 +393,8 @@ CREATE TABLE groups (
     updated_at timestamp without time zone NOT NULL,
     group_class character varying(255),
     trash_at timestamp without time zone,
-    delete_at timestamp without time zone,
-    is_trashed boolean DEFAULT false NOT NULL
+    is_trashed boolean DEFAULT false NOT NULL,
+    delete_at timestamp without time zone
 );
 
 
@@ -1546,13 +1546,6 @@ CREATE INDEX containers_search_index ON containers USING btree (uuid, owner_uuid
 CREATE INDEX groups_full_text_search_idx ON groups USING gin (to_tsvector('english'::regconfig, (((((((((((((COALESCE(uuid, ''::character varying))::text || ' '::text) || (COALESCE(owner_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(modified_by_client_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(modified_by_user_uuid, ''::character varying))::text) || ' '::text) || (COALESCE(name, ''::character varying))::text) || ' '::text) || (COALESCE(description, ''::character varying))::text) || ' '::text) || (COALESCE(group_class, ''::character varying))::text)));
 
 
---
--- Name: groups_owner_uuid_name_unique; Type: INDEX; Schema: public; Owner: -; Tablespace: 
---
-
-CREATE UNIQUE INDEX groups_owner_uuid_name_unique ON groups USING btree (owner_uuid, name);
-
-
 --
 -- Name: groups_search_index; Type: INDEX; Schema: public; Owner: -; Tablespace: 
 --
@@ -1770,6 +1763,13 @@ CREATE UNIQUE INDEX index_containers_on_uuid ON containers USING btree (uuid);
 CREATE INDEX index_groups_on_created_at ON groups USING btree (created_at);
 
 
+--
+-- Name: index_groups_on_delete_at; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_groups_on_delete_at ON groups USING btree (delete_at);
+
+
 --
 -- Name: index_groups_on_group_class; Type: INDEX; Schema: public; Owner: -; Tablespace: 
 --
@@ -1777,6 +1777,13 @@ CREATE INDEX index_groups_on_created_at ON groups USING btree (created_at);
 CREATE INDEX index_groups_on_group_class ON groups USING btree (group_class);
 
 
+--
+-- Name: index_groups_on_is_trashed; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_groups_on_is_trashed ON groups USING btree (is_trashed);
+
+
 --
 -- Name: index_groups_on_modified_at; Type: INDEX; Schema: public; Owner: -; Tablespace: 
 --
@@ -1791,6 +1798,20 @@ CREATE INDEX index_groups_on_modified_at ON groups USING btree (modified_at);
 CREATE INDEX index_groups_on_owner_uuid ON groups USING btree (owner_uuid);
 
 
+--
+-- Name: index_groups_on_owner_uuid_and_name; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_groups_on_owner_uuid_and_name ON groups USING btree (owner_uuid, name) WHERE (is_trashed = false);
+
+
+--
+-- Name: index_groups_on_trash_at; Type: INDEX; Schema: public; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_groups_on_trash_at ON groups USING btree (trash_at);
+
+
 --
 -- Name: index_groups_on_uuid; Type: INDEX; Schema: public; Owner: -; Tablespace: 
 --
index b80fcb1c50a046112933743eda5b23fed13bcdc7..5db8475541da8874377959e608fb6a6585b08336 100644 (file)
@@ -673,5 +673,21 @@ class Arvados::V1::GroupsControllerTest < ActionController::TestCase
       assert !Group.find_by_uuid(groups(:trashed_project).uuid).is_trashed
     end
 
+    test "untrash project with name conflict #{auth}" do
+      authorize_with auth
+      [:trashed_project].each do |pr|
+        Group.find_by_uuid(groups(pr).uuid).update! is_trashed: false
+      end
+      gc = Group.create!({owner_uuid: "zzzzz-j7d0g-trashedproject1",
+                         name: "trashed subproject 3",
+                         group_class: "project"})
+      post :untrash, {
+            id: groups(:trashed_subproject3).uuid,
+            format: :json,
+            ensure_unique_name: true
+           }
+      assert_response :success
+      assert_match /^trashed subproject 3 \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
+    end
   end
 end