Skip to content

Default to delayed deletion for projects not in personal namespace

What does this MR do and why?

  1. To simplify recovery of accidentally deleted project on Gitlab.com, we will use the ::Projects::MarkForDeletionService for projects under free namespace as well.
  2. Once deleted, these projects will not be visible anywhere on the dashboard or the via the API to the user and there is no way for the user to recover this project back. This is because we will set the column hidden as true for the free projects. Any project that is hidden will not be visible to the users.
  3. These projects will be listed in the pending deletion dashboard to the instance admin. The admin can also restore this project if required.
  4. Update the GitLab.com settings documentation.
  5. For these projects, when deleted via the UI, we will display that this action deletes project/xyz on YYYY-MM-DD... and that there is no going back.

Database

Migration logs

AddHiddenToProjects
== 20220203134942 AddHiddenToProjects: migrating ==============================
-- add_column(:projects, :hidden, :boolean, {:default=>false, :null=>false})
   -> 0.0046s
== 20220203134942 AddHiddenToProjects: migrated (0.0046s) =====================
Revert AddHiddenToProjects
== 20220203134942 AddHiddenToProjects: reverting ==============================
-- remove_column(:projects, :hidden, :boolean, {:default=>false, :null=>false})
   -> 0.0015s
== 20220203134942 AddHiddenToProjects: reverted (0.0033s) =====================
UpdateApiIndexesForProjects
== 20220207080758 UpdateApiIndexesForProjects: migrating ======================
-- transaction_open?()
   -> 0.0000s
-- index_exists?(:projects, [:created_at, :id], {:where=>"archived = true AND pending_delete = false AND hidden = false", :name=>"idx_projects_api_created_at_id_for_archived", :algorithm=>:concurrently})
   -> 0.0308s
-- execute("SET statement_timeout TO 0")
   -> 0.0008s
-- add_index(:projects, [:created_at, :id], {:where=>"archived = true AND pending_delete = false AND hidden = false", :name=>"idx_projects_api_created_at_id_for_archived", :algorithm=>:concurrently})
   -> 0.0100s
-- execute("RESET statement_timeout")
   -> 0.0049s
-- transaction_open?()
   -> 0.0000s
-- index_exists?(:projects, [:created_at, :id], {:where=>"archived = true AND visibility_level = 20 AND pending_delete = false AND hidden = false", :name=>"idx_projects_api_created_at_id_for_archived_vis20", :algorithm=>:concurrently})
   -> 0.0244s
-- add_index(:projects, [:created_at, :id], {:where=>"archived = true AND visibility_level = 20 AND pending_delete = false AND hidden = false", :name=>"idx_projects_api_created_at_id_for_archived_vis20", :algorithm=>:concurrently})
   -> 0.0037s
-- transaction_open?()
   -> 0.0000s
-- index_exists?(:projects, [:created_at, :id], {:where=>"visibility_level = 10 AND pending_delete = false AND hidden = false", :name=>"idx_projects_api_created_at_id_for_vis10", :algorithm=>:concurrently})
   -> 0.0252s
-- add_index(:projects, [:created_at, :id], {:where=>"visibility_level = 10 AND pending_delete = false AND hidden = false", :name=>"idx_projects_api_created_at_id_for_vis10", :algorithm=>:concurrently})
   -> 0.0034s
-- transaction_open?()
   -> 0.0000s
-- indexes(:projects)
   -> 0.0235s
-- remove_index(:projects, {:algorithm=>:concurrently, :name=>"index_projects_api_created_at_id_for_archived"})
   -> 0.0065s
-- transaction_open?()
   -> 0.0000s
-- indexes(:projects)
   -> 0.0248s
-- remove_index(:projects, {:algorithm=>:concurrently, :name=>"index_projects_api_created_at_id_for_archived_vis20"})
   -> 0.0038s
-- transaction_open?()
   -> 0.0000s
-- indexes(:projects)
   -> 0.0236s
-- remove_index(:projects, {:algorithm=>:concurrently, :name=>"index_projects_api_created_at_id_for_vis10"})
   -> 0.0039s
== 20220207080758 UpdateApiIndexesForProjects: migrated (0.2358s) =============
Restore UpdateApiIndexesForProjects
-- transaction_open?()
   -> 0.0000s
-- index_exists?(:projects, [:created_at, :id], {:where=>"archived = true AND pending_delete = false", :name=>"index_projects_api_created_at_id_for_archived", :algorithm=>:concurrently})
   -> 0.0170s
-- execute("SET statement_timeout TO 0")
   -> 0.0005s
-- add_index(:projects, [:created_at, :id], {:where=>"archived = true AND pending_delete = false", :name=>"index_projects_api_created_at_id_for_archived", :algorithm=>:concurrently})
   -> 0.0038s
-- execute("RESET statement_timeout")
   -> 0.0006s
-- transaction_open?()
   -> 0.0000s
-- index_exists?(:projects, [:created_at, :id], {:where=>"archived = true AND visibility_level = 20 AND pending_delete = false", :name=>"index_projects_api_created_at_id_for_archived_vis20", :algorithm=>:concurrently})
   -> 0.0173s
-- add_index(:projects, [:created_at, :id], {:where=>"archived = true AND visibility_level = 20 AND pending_delete = false", :name=>"index_projects_api_created_at_id_for_archived_vis20", :algorithm=>:concurrently})
   -> 0.0027s
-- transaction_open?()
   -> 0.0000s
-- index_exists?(:projects, [:created_at, :id], {:where=>"visibility_level = 10 AND pending_delete = false", :name=>"index_projects_api_created_at_id_for_vis10", :algorithm=>:concurrently})
   -> 0.0158s
-- add_index(:projects, [:created_at, :id], {:where=>"visibility_level = 10 AND pending_delete = false", :name=>"index_projects_api_created_at_id_for_vis10", :algorithm=>:concurrently})
   -> 0.0024s
-- transaction_open?()
   -> 0.0000s
-- indexes(:projects)
   -> 0.0171s
-- remove_index(:projects, {:algorithm=>:concurrently, :name=>"idx_projects_api_created_at_id_for_archived"})
   -> 0.0035s
-- transaction_open?()
   -> 0.0000s
-- indexes(:projects)
   -> 0.0178s
-- remove_index(:projects, {:algorithm=>:concurrently, :name=>"idx_projects_api_created_at_id_for_archived_vis20"})
   -> 0.0019s
-- transaction_open?()
   -> 0.0000s
-- indexes(:projects)
   -> 0.0158s
-- remove_index(:projects, {:algorithm=>:concurrently, :name=>"idx_projects_api_created_at_id_for_vis10"})
   -> 0.0018s
== 20220207080758 UpdateApiIndexesForProjects: reverted (0.1386s) =============

Screenshots or screen recordings

These are strongly recommended to assist reviewers and reduce the time to merge your change.

Free project Paid project
image image
image image

How to set up and validate locally

  1. Ensure that your GDK/local setup has ultimate license.
  2. Enable the check_namespace_plan via the application settings API so that even if the instance is on an ultimate plan, we can still create groups without plans.
  3. Enable "Default delayed project deletion" from admin settings and set the "Default deletion delay" to a value greater than 0.
  4. Create two groups. Add the ultimate plan to one of these groups from the admin panel (https://gdk.test:3000/admin/groups/group-name/edit). Make sure the other group is not on any plan.
  5. Add non-admin users to these each of these groups and make them the owners of these groups. For example, user1 becomes the owner of group1 which is on no plan and user2 becomes the owner of group2 which is on an ultimate plan.
  6. Login with user1 belonging to the free group1 and create a project.
    1. Navigate to the project settings and delete this project.
    2. The project should be deleted and the user should be navigated to the project dashboard page. A flash alert should be visible saying that the project will be deleted on 'YYYY-MM-DD' date.
    3. Go to the project's homepage and verify that 404 is thrown and the project is not visible.
    4. Navigate to the pending deletion dashboard and verify that the project that was deleted is not visible.
    5. This project should not be visible even via the projects API.
  7. Login with user2 belonging to the paid group2 and create a project.
    1. Navigate to the project settings and delete this project.
    2. The project should be deleted and the user should be navigated to the project's homepage. A flash alert should be visible saying that the project will be deleted on 'YYYY-MM-DD' date.
    3. Navigate to the pending deletion dashboard. The project that was deleted is visible and the user also has an option to restore the project. The project will also be available via the /projects APIs.
  8. Login with an instance administrator and navigate to the pending deletion dashboard.
    1. Both the deleted projects should be visible and the instance admin has the option to restore any of these, even the project belonging to the free group1.
    2. Restore the project belonging to group1.
    3. Restore the project belonging to group2.
  9. Login with user1 again and verify that the project is now restored.
  10. Login with user2 again and verify that the project is now restored.

MR acceptance checklist

This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.

Relates to #343577

Edited by Huzaifa Iftikhar

Merge request reports