Commit 2eb9c962 authored by Etienne Baqué's avatar Etienne Baqué Committed by Kamil Trzciński

Added NewRelease worker and notification service

Added NewRelease recipient service.
Added new notification service.
Updated release creation process to include notification.
parent 6eab4b16
# frozen_string_literal: true
module Emails
module Releases
def new_release_email(user_id, release, reason = nil)
@release = release
@project = @release.project
@target_url = namespace_project_releases_url(
namespace_id: @project.namespace,
project_id: @project
)
user = User.find(user_id)
mail(
to: user.notification_email_for(@project.group),
subject: subject(release_email_subject)
)
end
private
def release_email_subject
release_info = [@release.name, @release.tag].select(&:presence).join(' - ')
"New release: #{release_info}"
end
end
end
...@@ -16,6 +16,7 @@ class Notify < BaseMailer ...@@ -16,6 +16,7 @@ class Notify < BaseMailer
include Emails::Members include Emails::Members
include Emails::AutoDevops include Emails::AutoDevops
include Emails::RemoteMirrors include Emails::RemoteMirrors
include Emails::Releases
helper MilestonesHelper helper MilestonesHelper
helper MergeRequestsHelper helper MergeRequestsHelper
......
...@@ -25,6 +25,7 @@ class NotificationSetting < ApplicationRecord ...@@ -25,6 +25,7 @@ class NotificationSetting < ApplicationRecord
end end
EMAIL_EVENTS = [ EMAIL_EVENTS = [
:new_release,
:new_note, :new_note,
:new_issue, :new_issue,
:reopen_issue, :reopen_issue,
......
...@@ -26,10 +26,12 @@ class Release < ApplicationRecord ...@@ -26,10 +26,12 @@ class Release < ApplicationRecord
validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") } validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") }
scope :sorted, -> { order(released_at: :desc) } scope :sorted, -> { order(released_at: :desc) }
scope :with_project_and_namespace, -> { includes(project: :namespace) }
delegate :repository, to: :project delegate :repository, to: :project
after_commit :create_evidence!, on: :create after_commit :create_evidence!, on: :create
after_commit :notify_new_release, on: :create
def commit def commit
strong_memoize(:commit) do strong_memoize(:commit) do
...@@ -73,6 +75,10 @@ class Release < ApplicationRecord ...@@ -73,6 +75,10 @@ class Release < ApplicationRecord
def create_evidence! def create_evidence!
CreateEvidenceWorker.perform_async(self.id) CreateEvidenceWorker.perform_async(self.id)
end end
def notify_new_release
NewReleaseWorker.perform_async(id)
end
end end
Release.prepend_if_ee('EE::Release') Release.prepend_if_ee('EE::Release')
...@@ -28,6 +28,10 @@ module NotificationRecipientService ...@@ -28,6 +28,10 @@ module NotificationRecipientService
Builder::ProjectMaintainers.new(*args).notification_recipients Builder::ProjectMaintainers.new(*args).notification_recipients
end end
def self.build_new_release_recipients(*args)
Builder::NewRelease.new(*args).notification_recipients
end
module Builder module Builder
class Base class Base
def initialize(*) def initialize(*)
...@@ -359,6 +363,26 @@ module NotificationRecipientService ...@@ -359,6 +363,26 @@ module NotificationRecipientService
end end
end end
class NewRelease < Base
attr_reader :target
def initialize(target)
@target = target
end
def build!
add_recipients(target.project.authorized_users, :custom, nil)
end
def custom_action
:new_release
end
def acting_user
target.author
end
end
class MergeRequestUnmergeable < Base class MergeRequestUnmergeable < Base
attr_reader :target attr_reader :target
def initialize(merge_request) def initialize(merge_request)
......
...@@ -289,6 +289,15 @@ class NotificationService ...@@ -289,6 +289,15 @@ class NotificationService
end end
end end
# Notify users when a new release is created
def send_new_release_notifications(release)
recipients = NotificationRecipientService.build_new_release_recipients(release)
recipients.each do |recipient|
mailer.new_release_email(recipient.user.id, release, recipient.reason).deliver_later
end
end
# Members # Members
def new_access_request(member) def new_access_request(member)
return true unless member.notifiable?(:subscription) return true unless member.notifiable?(:subscription)
......
- release_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url }
- description_details = { tag: @release.tag, name: @project.name, release_link_start: release_link_start, release_link_end: '</a>'.html_safe }
%div{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" }
%p
= _("A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it.").html_safe % description_details
%p
%h4= _("Assets:")
%ul
- @release.links.each do |link|
%li= link_to(link.name, link.url)
- @release.sources.each do |source|
%li= link_to(_("Download %{format}") % { format: source.format }, source.url)
%p
%h4= _("Release notes:")
= markdown_field(@release, :description)
<%= _("A new Release %{tag} for %{name} was published. Visit the Releases page to read more about it:").html_safe % { tag: @release.tag, name: @project.name } %> <%= @target_url %>
<%= _("Assets:") %>
<% @release.links.each do |link| -%>
- <%= link.name %>: <%= link.url %>
<% end -%>
<% @release.sources.each do |source| -%>
- <%= _("Download %{format}:") % { format: source.format } %> <%= source.url %>
<% end -%>
<%= _("Release notes:") %>
<%= @release.description %>
...@@ -119,6 +119,8 @@ ...@@ -119,6 +119,8 @@
- container_repository:delete_container_repository - container_repository:delete_container_repository
- container_repository:cleanup_container_repository - container_repository:cleanup_container_repository
- notifications:new_release
- default - default
- mailers # ActionMailer::DeliveryJob.queue_name - mailers # ActionMailer::DeliveryJob.queue_name
......
# frozen_string_literal: true
class NewReleaseWorker
include ApplicationWorker
queue_namespace :notifications
def perform(release_id)
release = Release.with_project_and_namespace.find_by_id(release_id)
return unless release
NotificationService.new.send_new_release_notifications(release)
end
end
---
title: Add 'New release' to the project custom notifications
merge_request: 17877
author:
type: added
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
- [process_commit, 3] - [process_commit, 3]
- [new_note, 2] - [new_note, 2]
- [new_issue, 2] - [new_issue, 2]
- [notifications, 2]
- [new_merge_request, 2] - [new_merge_request, 2]
- [pipeline_processing, 5] - [pipeline_processing, 5]
- [pipeline_creation, 4] - [pipeline_creation, 4]
......
# frozen_string_literal: true
class AddNewReleaseToNotificationSettings < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :notification_settings, :new_release, :boolean
end
end
...@@ -2509,6 +2509,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do ...@@ -2509,6 +2509,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.boolean "issue_due" t.boolean "issue_due"
t.boolean "new_epic" t.boolean "new_epic"
t.string "notification_email" t.string "notification_email"
t.boolean "new_release"
t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type" t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type"
t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true
t.index ["user_id"], name: "index_notification_settings_on_user_id" t.index ["user_id"], name: "index_notification_settings_on_user_id"
......
...@@ -65,6 +65,18 @@ project. ...@@ -65,6 +65,18 @@ project.
![Releases list](img/releases.png) ![Releases list](img/releases.png)
## Notification for Releases
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/26001) in GitLab 12.4.
You can be notified by email when a new Release is created for your project.
To subscribe to these notifications, navigate to your **Project**'s landing page, then click on the
bell icon. Choose **Custom** from the dropdown menu. The
following modal window will be then displayed, from which you can select **New release** to complete your subscription to new Releases notifications.
![Custom notification - New release](img/custom_notifications_new_release_v12_4.png)
<!-- ## Troubleshooting <!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues Include any troubleshooting steps that you can foresee. If you know beforehand what issues
......
...@@ -84,6 +84,7 @@ Below is the table of events users can be notified of: ...@@ -84,6 +84,7 @@ Below is the table of events users can be notified of:
| User added to group | User | Sent when user is added to group | | User added to group | User | Sent when user is added to group |
| Group access level changed | User | Sent when user group access level is changed | | Group access level changed | User | Sent when user group access level is changed |
| Project moved | Project members (1) | (1) not disabled | | Project moved | Project members (1) | (1) not disabled |
| New release | Project members | Custom notification |
### Issue / Epics / Merge request events ### Issue / Epics / Merge request events
......
...@@ -12,6 +12,7 @@ describe NotificationSetting do ...@@ -12,6 +12,7 @@ describe NotificationSetting do
it 'appends EE specific events' do it 'appends EE specific events' do
expect(subject).to eq( expect(subject).to eq(
[ [
:new_release,
:new_note, :new_note,
:new_issue, :new_issue,
:reopen_issue, :reopen_issue,
...@@ -38,6 +39,7 @@ describe NotificationSetting do ...@@ -38,6 +39,7 @@ describe NotificationSetting do
it 'returns CE list' do it 'returns CE list' do
expect(subject).to eq( expect(subject).to eq(
[ [
:new_release,
:new_note, :new_note,
:new_issue, :new_issue,
:reopen_issue, :reopen_issue,
...@@ -63,6 +65,7 @@ describe NotificationSetting do ...@@ -63,6 +65,7 @@ describe NotificationSetting do
it 'appends EE specific events' do it 'appends EE specific events' do
expect(subject).to eq( expect(subject).to eq(
[ [
:new_release,
:new_note, :new_note,
:new_issue, :new_issue,
:reopen_issue, :reopen_issue,
......
...@@ -659,6 +659,12 @@ msgstr "" ...@@ -659,6 +659,12 @@ msgstr ""
msgid "A merge request approval is required when the license compliance report contains a blacklisted license." msgid "A merge request approval is required when the license compliance report contains a blacklisted license."
msgstr "" msgstr ""
msgid "A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it."
msgstr ""
msgid "A new Release %{tag} for %{name} was published. Visit the Releases page to read more about it:"
msgstr ""
msgid "A new branch will be created in your fork and a new merge request will be started." msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "" msgstr ""
...@@ -2009,6 +2015,9 @@ msgstr "" ...@@ -2009,6 +2015,9 @@ msgstr ""
msgid "Assets" msgid "Assets"
msgstr "" msgstr ""
msgid "Assets:"
msgstr ""
msgid "Assign" msgid "Assign"
msgstr "" msgstr ""
...@@ -5709,6 +5718,12 @@ msgstr "" ...@@ -5709,6 +5718,12 @@ msgstr ""
msgid "Download" msgid "Download"
msgstr "" msgstr ""
msgid "Download %{format}"
msgstr ""
msgid "Download %{format}:"
msgstr ""
msgid "Download CSV" msgid "Download CSV"
msgstr "" msgstr ""
...@@ -11139,6 +11154,9 @@ msgstr "" ...@@ -11139,6 +11154,9 @@ msgstr ""
msgid "NotificationEvent|New note" msgid "NotificationEvent|New note"
msgstr "" msgstr ""
msgid "NotificationEvent|New release"
msgstr ""
msgid "NotificationEvent|Reassign issue" msgid "NotificationEvent|Reassign issue"
msgstr "" msgstr ""
...@@ -13492,6 +13510,9 @@ msgstr "" ...@@ -13492,6 +13510,9 @@ msgstr ""
msgid "Release notes" msgid "Release notes"
msgstr "" msgstr ""
msgid "Release notes:"
msgstr ""
msgid "Release title" msgid "Release title"
msgstr "" msgstr ""
......
...@@ -14,3 +14,4 @@ N_('NotificationEvent|Close merge request') ...@@ -14,3 +14,4 @@ N_('NotificationEvent|Close merge request')
N_('NotificationEvent|Reassign merge request') N_('NotificationEvent|Reassign merge request')
N_('NotificationEvent|Merge merge request') N_('NotificationEvent|Merge merge request')
N_('NotificationEvent|Failed pipeline') N_('NotificationEvent|Failed pipeline')
N_('NotificationEvent|New release')
# frozen_string_literal: true
require 'spec_helper'
require 'email_spec'
describe Emails::Releases do
include EmailSpec::Matchers
include_context 'gitlab email notification'
describe '#new_release_email' do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let(:release) { create(:release, project: project) }
subject { Notify.new_release_email(user.id, release) }
it_behaves_like 'an email sent from GitLab'
context 'when the release has a name' do
it 'shows the correct subject' do
expected_subject = "#{release.project.name} | New release: #{release.name} - #{release.tag}"
is_expected.to have_subject(expected_subject)
end
end
context 'when the release does not have a name' do
it 'shows the correct subject' do
release.name = nil
expected_subject = "#{release.project.name} | New release: #{release.tag}"
is_expected.to have_subject(expected_subject)
end
end
it 'contains a message with the new release tag' do
message = "A new Release #{release.tag} for #{release.project.name} was published."
is_expected.to have_body_text(message)
end
it 'contains the release assets' do
is_expected.to have_body_text('Assets:')
release.sources do |source|
is_expected.to have_body_text("Download #{source.format}")
end
end
it 'contains the release notes' do
is_expected.to have_body_text('Release notes:')
is_expected.to have_body_text(release.description)
end
end
end
...@@ -98,6 +98,7 @@ RSpec.describe NotificationSetting do ...@@ -98,6 +98,7 @@ RSpec.describe NotificationSetting do
it 'returns email events' do it 'returns email events' do
expect(subject).to include( expect(subject).to include(
:new_release,
:new_note, :new_note,
:new_issue, :new_issue,
:reopen_issue, :reopen_issue,
......
...@@ -109,4 +109,24 @@ RSpec.describe Release do ...@@ -109,4 +109,24 @@ RSpec.describe Release do
end end
end end
end end
describe '#notify_new_release' do
context 'when a release is created' do
it 'instantiates NewReleaseWorker to send notifications' do
expect(NewReleaseWorker).to receive(:perform_async)
create(:release)
end
end
context 'when a release is updated' do
let!(:release) { create(:release) }
it 'does not send any new notification' do
expect(NewReleaseWorker).not_to receive(:perform_async)
release.update!(description: 'new description')
end
end
end
end end
...@@ -678,6 +678,27 @@ describe NotificationService, :mailer do ...@@ -678,6 +678,27 @@ describe NotificationService, :mailer do
end end
end end
describe '#send_new_release_notifications' do
context 'when recipients for a new release exist' do
let(:release) { create(:release) }
it 'calls new_release_email for each relevant recipient' do
user_1 = create(:user)
user_2 = create(:user)
user_3 = create(:user)
recipient_1 = NotificationRecipient.new(user_1, :custom, custom_action: :new_release)
recipient_2 = NotificationRecipient.new(user_2, :custom, custom_action: :new_release)
allow(NotificationRecipientService).to receive(:build_new_release_recipients).and_return([recipient_1, recipient_2])
release
should_email(user_1)
should_email(user_2)
should_not_email(user_3)
end
end
end
describe 'Participating project notification settings have priority over group and global settings if available' do describe 'Participating project notification settings have priority over group and global settings if available' do
let!(:group) { create(:group) } let!(:group) { create(:group) }
let!(:maintainer) { group.add_owner(create(:user, username: 'maintainer')).user } let!(:maintainer) { group.add_owner(create(:user, username: 'maintainer')).user }
......
# frozen_string_literal: true
require 'spec_helper'
describe NewReleaseWorker do
let(:release) { create(:release) }
it 'sends a new release notification' do
expect_any_instance_of(NotificationService).to receive(:send_new_release_notifications).with(release)
described_class.new.perform(release.id)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment