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
include Emails::Members
include Emails::AutoDevops
include Emails::RemoteMirrors
include Emails::Releases
helper MilestonesHelper
helper MergeRequestsHelper
......
......@@ -25,6 +25,7 @@ class NotificationSetting < ApplicationRecord
end
EMAIL_EVENTS = [
:new_release,
:new_note,
:new_issue,
:reopen_issue,
......
......@@ -26,10 +26,12 @@ class Release < ApplicationRecord
validates_associated :milestone_releases, message: -> (_, obj) { obj[:value].map(&:errors).map(&:full_messages).join(",") }
scope :sorted, -> { order(released_at: :desc) }
scope :with_project_and_namespace, -> { includes(project: :namespace) }
delegate :repository, to: :project
after_commit :create_evidence!, on: :create
after_commit :notify_new_release, on: :create
def commit
strong_memoize(:commit) do
......@@ -73,6 +75,10 @@ class Release < ApplicationRecord
def create_evidence!
CreateEvidenceWorker.perform_async(self.id)
end
def notify_new_release
NewReleaseWorker.perform_async(id)
end
end
Release.prepend_if_ee('EE::Release')
......@@ -28,6 +28,10 @@ module NotificationRecipientService
Builder::ProjectMaintainers.new(*args).notification_recipients
end
def self.build_new_release_recipients(*args)
Builder::NewRelease.new(*args).notification_recipients
end
module Builder
class Base
def initialize(*)
......@@ -359,6 +363,26 @@ module NotificationRecipientService
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
attr_reader :target
def initialize(merge_request)
......
......@@ -289,6 +289,15 @@ class NotificationService
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
def new_access_request(member)
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 @@
- container_repository:delete_container_repository
- container_repository:cleanup_container_repository
- notifications:new_release
- default
- 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 @@
- [process_commit, 3]
- [new_note, 2]
- [new_issue, 2]
- [notifications, 2]
- [new_merge_request, 2]
- [pipeline_processing, 5]
- [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
t.boolean "issue_due"
t.boolean "new_epic"
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 ["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"
......
......@@ -65,6 +65,18 @@ project.
![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
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:
| 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 |
| Project moved | Project members (1) | (1) not disabled |
| New release | Project members | Custom notification |
### Issue / Epics / Merge request events
......
......@@ -12,6 +12,7 @@ describe NotificationSetting do
it 'appends EE specific events' do
expect(subject).to eq(
[
:new_release,
:new_note,
:new_issue,
:reopen_issue,
......@@ -38,6 +39,7 @@ describe NotificationSetting do
it 'returns CE list' do
expect(subject).to eq(
[
:new_release,
:new_note,
:new_issue,
:reopen_issue,
......@@ -63,6 +65,7 @@ describe NotificationSetting do
it 'appends EE specific events' do
expect(subject).to eq(
[
:new_release,
:new_note,
:new_issue,
:reopen_issue,
......
......@@ -659,6 +659,12 @@ msgstr ""
msgid "A merge request approval is required when the license compliance report contains a blacklisted license."
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."
msgstr ""
......@@ -2009,6 +2015,9 @@ msgstr ""
msgid "Assets"
msgstr ""
msgid "Assets:"
msgstr ""
msgid "Assign"
msgstr ""
......@@ -5709,6 +5718,12 @@ msgstr ""
msgid "Download"
msgstr ""
msgid "Download %{format}"
msgstr ""
msgid "Download %{format}:"
msgstr ""
msgid "Download CSV"
msgstr ""
......@@ -11139,6 +11154,9 @@ msgstr ""
msgid "NotificationEvent|New note"
msgstr ""
msgid "NotificationEvent|New release"
msgstr ""
msgid "NotificationEvent|Reassign issue"
msgstr ""
......@@ -13492,6 +13510,9 @@ msgstr ""
msgid "Release notes"
msgstr ""
msgid "Release notes:"
msgstr ""
msgid "Release title"
msgstr ""
......
......@@ -14,3 +14,4 @@ N_('NotificationEvent|Close merge request')
N_('NotificationEvent|Reassign merge request')
N_('NotificationEvent|Merge merge request')
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
it 'returns email events' do
expect(subject).to include(
:new_release,
:new_note,
:new_issue,
:reopen_issue,
......
......@@ -109,4 +109,24 @@ RSpec.describe Release do
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
......@@ -678,6 +678,27 @@ describe NotificationService, :mailer do
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
let!(:group) { create(:group) }
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