diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 14c724b5b918b9726aba2b69787ff47f2d916757..35459cceead6190e422cc67581c15cf677424e18 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 = "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)) 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..5c874994d6ed12542d18e73b741ae9b424b141cd 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).to_a.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..154bfbb90a4838c36146bb43739717cdc9287fd5 --- /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 %{deletion_date}. To unschedule the deletion of %{project_link}, perform some activity on it. For example:")) % { project_link: project_link.html_safe, deletion_date: @deletion_date } + +%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: '<b>'.html_safe, b_close: '</b>'.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: '<code>'.html_safe, code_close: '</code>'.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..3a80ede19e6fa9ca84077265093637f44f983aff 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 %{deletion_date}. 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..ec8c8497081bbe9ea0b9f476b4addd4235555e62 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) { Date.current } + + 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 = "<a href=\"#{project.http_url_to_repo}\">#{project.name}</a>" + + is_expected.to have_subject("#{project.name} | Action required: Project #{project.name} is scheduled to be " \ + "deleted on #{deletion_date} 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 #{deletion_date}") + 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)