diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 14c724b5b918b9726aba2b69787ff47f2d916757..efc6ce163c04066c6ebddbc02e022841c273d8d6 100644
--- a/app/mailers/emails/projects.rb
+++ b/app/mailers/emails/projects.rb
@@ -68,6 +68,20 @@ def prometheus_alert_fired_email(project, user, alert)
mail(to: user.notification_email_for(@project.group), subject: subject(subject_text))
end
+ def inactive_project_deletion_warning_email(project, user, deletion_date)
+ @project = project
+ @user = user
+ @deletion_date = deletion_date
+ subject_text = "Action required: Project #{project.name} is scheduled to be deleted on " \
+ "#{deletion_date} due to inactivity"
+
+ mail(to: user.notification_email_for(project.group),
+ subject: subject(subject_text)) do |format|
+ format.html { render layout: 'mailer' }
+ format.text { render layout: 'mailer' }
+ end
+ end
+
private
def add_alert_headers
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index e7c8964a7334c08cd2b1887eb01bc1d866c08fe0..60d594651655d4ec41372a91f4994bb626bb9983 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -201,6 +201,10 @@ def merge_when_pipeline_succeeds_email
Notify.merge_when_pipeline_succeeds_email(user.id, merge_request.id, user.id).message
end
+ def inactive_project_deletion_warning
+ Notify.inactive_project_deletion_warning_email(project, user, '2022-04-22').message
+ end
+
private
def project
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 062841e3aa5da29cbe81b218b85d9d54a59118d1..32b23d4978f54b3bb15f990c2b5aba51cac7b66b 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -769,6 +769,12 @@ def unapprove_mr(merge_request, current_user)
unapprove_mr_email(merge_request, merge_request.target_project, current_user)
end
+ def inactive_project_deletion_warning(project, deletion_date)
+ owners_and_maintainers_without_invites(project).each do |recipient|
+ mailer.inactive_project_deletion_warning_email(project, recipient.user, deletion_date).deliver_later
+ end
+ end
+
protected
def new_resource_email(target, current_user, method)
diff --git a/app/views/notify/inactive_project_deletion_warning_email.html.haml b/app/views/notify/inactive_project_deletion_warning_email.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..52253ce30767a847e7abfefefd9232dacabb8200
--- /dev/null
+++ b/app/views/notify/inactive_project_deletion_warning_email.html.haml
@@ -0,0 +1,28 @@
+- project_link = link_to(_("%{project_name}") % { project_name: @project.name }, @project.http_url_to_repo)
+- projects_api_link = link_to(_("Projects API"), help_page_url('api/projects'))
+- events_api_link = link_to(_("Events API"), help_page_url('api/events', anchor: 'list-a-projects-visible-events'))
+
+%p
+ = _('Hi %{username},') % { username: sanitize_name(@user.name) }
+
+%p
+ = html_escape(_("Due to inactivity, the %{project_link} project is scheduled to be deleted on %{b_open}%{deletion_date}%{b_close}. To unschedule the deletion of %{project_link}, perform some activity on it. For example:")) % { project_link: project_link.html_safe, deletion_date: @deletion_date, b_open: ''.html_safe, b_close: ''.html_safe }
+
+%p
+ %ul
+ %li= _("Create or close an issue.")
+ %li= _("Create, update, or delete a merge request.")
+ %li= _("Push code to the repository.")
+ %li= _("Add or remove a user.")
+
+%p
+ = html_escape(_("To ensure %{project_link} is unscheduled for deletion, check that activity has been logged by GitLab. For example:")) %{project_link: project_link.html_safe}
+
+%p
+ %ul
+ %li= html_escape(_("Go to the %{b_open}Activity%{b_close} page for %{project_link}.")) % { project_link: project_link, b_open: ''.html_safe, b_close: ''.html_safe }
+ %li= html_escape(_("View the %{code_open}last_activity_at%{code_close} attribute for %{project_link} using the %{projects_api_link}.")) % { project_link: project_link.html_safe, projects_api_link: projects_api_link.html_safe, code_open: ''.html_safe, code_close: '
'.html_safe }
+ %li= html_escape(_("List the visible events for %{project_link} using the %{events_api_link}.")) % { project_link: project_link.html_safe, events_api_link: events_api_link.html_safe }
+
+%p
+ = html_escape(_("This email supersedes any previous emails about scheduled deletion you may have received for %{project_link}.")) % { project_link: project_link.html_safe }
diff --git a/app/views/notify/inactive_project_deletion_warning_email.text.erb b/app/views/notify/inactive_project_deletion_warning_email.text.erb
new file mode 100644
index 0000000000000000000000000000000000000000..a0b79967817494d5d2c72a745a1c4e1df508b802
--- /dev/null
+++ b/app/views/notify/inactive_project_deletion_warning_email.text.erb
@@ -0,0 +1,17 @@
+<%= _('Hi %{username},') % { username: sanitize_name(@user.name) } %>
+
+<%= _("Due to inactivity, the %{project_name} (%{project_link}) project is scheduled to be deleted on %{deletion_date}. To unschedule the deletion of %{project_name}, perform some activity on it. For example:") %
+ { project_name: @project.name, project_link: @project.http_url_to_repo, deletion_date: @deletion_date } %>
+
+<%= _("- Create or close an issue.") %>
+<%= _("- Create, update, or delete a merge request.") %>
+<%= _("- Push code to the repository.") %>
+<%= _("- Add or remove a user.") %>
+
+<%= _("To ensure %{project_name} is unscheduled for deletion, check that activity has been logged by GitLab. For example:") % { project_name: @project.name } %>
+
+<%= _("- Go to the Activity page for %{project_name}.") % { project_name: @project.name } %>
+<%= _("- View the last_activity_at attribute for %{project_name} using the Project API %{projects_api_link}.") % { project_name: @project.name, projects_api_link: help_page_url('api/projects') } %>
+<%= _("- List the visible events for %{project_name} using the Events API %{events_api_link}.") % { project_name: @project.name, events_api_link: help_page_url('api/events', anchor: 'list-a-projects-visible-events') } %>
+
+<%= _("This email supersedes any previous emails about scheduled deletion you may have received for %{project_name}.") % { project_name: @project.name } %>
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0cdece3990da409e454b24576536f3b25ac1800d..6cce83d7d00904cf170e808bcde19c19351d15bc 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -896,6 +896,9 @@ msgstr ""
msgid "%{policy_link} (notifying after %{elapsed_time} minutes unless %{status})"
msgstr ""
+msgid "%{project_name}"
+msgstr ""
+
msgid "%{project_path} is a project that you can use to add a README to your GitLab profile. Create a public project and initialize the repository with a README to get started. %{help_link_start}Learn more.%{help_link_end}"
msgstr ""
@@ -1290,17 +1293,35 @@ msgstr ""
msgid "- %{policy_name} (notifying after %{elapsed_time} minutes unless %{status})"
msgstr ""
+msgid "- Add or remove a user."
+msgstr ""
+
msgid "- Available to run jobs."
msgstr ""
+msgid "- Create or close an issue."
+msgstr ""
+
+msgid "- Create, update, or delete a merge request."
+msgstr ""
+
msgid "- Event"
msgid_plural "- Events"
msgstr[0] ""
msgstr[1] ""
+msgid "- Go to the Activity page for %{project_name}."
+msgstr ""
+
+msgid "- List the visible events for %{project_name} using the Events API %{events_api_link}."
+msgstr ""
+
msgid "- Not available to run jobs."
msgstr ""
+msgid "- Push code to the repository."
+msgstr ""
+
msgid "- Select -"
msgstr ""
@@ -1309,6 +1330,9 @@ msgid_plural "- Users"
msgstr[0] ""
msgstr[1] ""
+msgid "- View the last_activity_at attribute for %{project_name} using the Project API %{projects_api_link}."
+msgstr ""
+
msgid "- of - issues closed"
msgstr ""
@@ -2225,6 +2249,9 @@ msgstr ""
msgid "Add new directory"
msgstr ""
+msgid "Add or remove a user."
+msgstr ""
+
msgid "Add or remove previously merged commits"
msgstr ""
@@ -10419,6 +10446,9 @@ msgstr ""
msgid "Create one"
msgstr ""
+msgid "Create or close an issue."
+msgstr ""
+
msgid "Create or import your first project"
msgstr ""
@@ -10458,6 +10488,9 @@ msgstr ""
msgid "Create your group"
msgstr ""
+msgid "Create, update, or delete a merge request."
+msgstr ""
+
msgid "Create/import your first project"
msgstr ""
@@ -13326,6 +13359,12 @@ msgstr ""
msgid "Due date"
msgstr ""
+msgid "Due to inactivity, the %{project_link} project is scheduled to be deleted on %{b_open}%{deletion_date}%{b_close}. To unschedule the deletion of %{project_link}, perform some activity on it. For example:"
+msgstr ""
+
+msgid "Due to inactivity, the %{project_name} (%{project_link}) project is scheduled to be deleted on %{deletion_date}. To unschedule the deletion of %{project_name}, perform some activity on it. For example:"
+msgstr ""
+
msgid "Duplicate page: %{error_message}"
msgstr ""
@@ -14835,6 +14874,9 @@ msgstr ""
msgid "Events"
msgstr ""
+msgid "Events API"
+msgstr ""
+
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
msgstr ""
@@ -17350,6 +17392,9 @@ msgstr ""
msgid "Go to snippets"
msgstr ""
+msgid "Go to the %{b_open}Activity%{b_close} page for %{project_link}."
+msgstr ""
+
msgid "Go to the 'Admin area > Sign-up restrictions', and check 'Allowed domains for sign-ups'."
msgstr ""
@@ -18557,6 +18602,9 @@ msgstr ""
msgid "Hi %{username}!"
msgstr ""
+msgid "Hi %{username},"
+msgstr ""
+
msgid "Hidden"
msgstr ""
@@ -22746,6 +22794,9 @@ msgstr ""
msgid "List the merge requests that must be merged before this one."
msgstr ""
+msgid "List the visible events for %{project_link} using the %{events_api_link}."
+msgstr ""
+
msgid "List view"
msgstr ""
@@ -29900,6 +29951,9 @@ msgstr ""
msgid "Projects (%{count})"
msgstr ""
+msgid "Projects API"
+msgstr ""
+
msgid "Projects Successfully Retrieved"
msgstr ""
@@ -30623,6 +30677,9 @@ msgstr ""
msgid "Push an existing folder"
msgstr ""
+msgid "Push code to the repository."
+msgstr ""
+
msgid "Push commits to the source branch or add previously merged commits to review them."
msgstr ""
@@ -38359,6 +38416,12 @@ msgstr ""
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
msgstr ""
+msgid "This email supersedes any previous emails about scheduled deletion you may have received for %{project_link}."
+msgstr ""
+
+msgid "This email supersedes any previous emails about scheduled deletion you may have received for %{project_name}."
+msgstr ""
+
msgid "This endpoint has been requested too many times. Try again later."
msgstr ""
@@ -39206,6 +39269,12 @@ msgstr ""
msgid "To enable Registration Features, first enable Service Ping."
msgstr ""
+msgid "To ensure %{project_link} is unscheduled for deletion, check that activity has been logged by GitLab. For example:"
+msgstr ""
+
+msgid "To ensure %{project_name} is unscheduled for deletion, check that activity has been logged by GitLab. For example:"
+msgstr ""
+
msgid "To ensure no loss of personal content, this account should only be used for matters related to %{group_name}."
msgstr ""
@@ -41486,6 +41555,9 @@ msgstr ""
msgid "View supported languages and frameworks"
msgstr ""
+msgid "View the %{code_open}last_activity_at%{code_close} attribute for %{project_link} using the %{projects_api_link}."
+msgstr ""
+
msgid "View the documentation"
msgstr ""
diff --git a/spec/mailers/emails/projects_spec.rb b/spec/mailers/emails/projects_spec.rb
index b9c71e35bc66e34e8f973a62df128e181fd0741c..ef3c21b32cee041dbf755b1a5ef2bc8d1b04ede6 100644
--- a/spec/mailers/emails/projects_spec.rb
+++ b/spec/mailers/emails/projects_spec.rb
@@ -180,4 +180,32 @@
end
end
end
+
+ describe '.inactive_project_deletion_warning_email' do
+ let(:recipient) { user }
+ let(:deletion_date) { "2022-01-10" }
+
+ subject { Notify.inactive_project_deletion_warning_email(project, user, deletion_date) }
+
+ it_behaves_like 'an email sent to a user'
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+ it_behaves_like 'appearance header and footer enabled'
+ it_behaves_like 'appearance header and footer not enabled'
+
+ it 'has the correct subject and body' do
+ project_link = "#{project.name}"
+
+ is_expected.to have_subject("#{project.name} | Action required: Project #{project.name} is scheduled to be " \
+ "deleted on 2022-01-10 due to inactivity")
+ is_expected.to have_body_text(project.http_url_to_repo)
+ is_expected.to have_body_text("Due to inactivity, the #{project_link} project is scheduled to be deleted " \
+ "on 2022-01-10")
+ is_expected.to have_body_text("To ensure #{project_link} is unscheduled for deletion, check that activity has " \
+ "been logged by GitLab")
+ is_expected.to have_body_text("This email supersedes any previous emails about scheduled deletion you may " \
+ "have received for #{project_link}.")
+ end
+ end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 956a95eafb7aa9c6488d1b19e982a8e44ef86b35..743a04eabe6cac1822ec93f9fb8500a0cbe7a13c 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -3708,6 +3708,26 @@ def create_pipeline(user, status)
end
end
+ describe '#inactive_project_deletion_warning' do
+ let_it_be(:deletion_date) { Date.current }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:maintainer) { create(:user) }
+ let_it_be(:developer) { create(:user) }
+
+ before do
+ project.add_maintainer(maintainer)
+ end
+
+ subject { notification.inactive_project_deletion_warning(project, deletion_date) }
+
+ it "sends email to project owners and maintainers" do
+ expect { subject }.to have_enqueued_email(project, maintainer, deletion_date,
+ mail: "inactive_project_deletion_warning_email")
+ expect { subject }.not_to have_enqueued_email(project, developer, deletion_date,
+ mail: "inactive_project_deletion_warning_email")
+ end
+ end
+
def build_team(project)
@u_watcher = create_global_setting_for(create(:user), :watch)
@u_participating = create_global_setting_for(create(:user), :participating)