Skip to content
Snippets Groups Projects
Commit 4a3b4370 authored by Douglas Barbosa Alexandre's avatar Douglas Barbosa Alexandre Committed by 🤖 GitLab Bot 🤖
Browse files

Merge branch...

Merge branch '50020-allow-email-notifications-to-be-disabled-for-all-users-of-a-group' into 'master'

Allow email notifications to be disabled for all users of a group

See merge request gitlab-org/gitlab-ce!30755

(cherry picked from commit 1985bafe)

f0a476b5 Allow disabling group/project email notifications
cbc92f85 Refactor shared examples into their own file
c82e9029 Add additional specs
501602ab Address review feedback
319c988f Reverse order of feature check
parent 2e597b5c
No related branches found
No related tags found
No related merge requests found
Showing
with 203 additions and 2 deletions
Loading
Loading
@@ -176,6 +176,7 @@ class GroupsController < Groups::ApplicationController
[
:avatar,
:description,
:emails_disabled,
:lfs_enabled,
:name,
:path,
Loading
Loading
Loading
Loading
@@ -361,6 +361,7 @@ class ProjectsController < Projects::ApplicationController
:container_registry_enabled,
:default_branch,
:description,
:emails_disabled,
:external_authorization_classification_label,
:import_url,
:issues_tracker,
Loading
Loading
Loading
Loading
@@ -172,6 +172,13 @@ class Namespace < ApplicationRecord
end
end
 
# any ancestor can disable emails for all descendants
def emails_disabled?
strong_memoize(:emails_disabled) do
Feature.enabled?(:emails_disabled, self, default_enabled: true) && self_and_ancestors.where(emails_disabled: true).exists?
end
end
def lfs_enabled?
# User namespace will always default to the global setting
Gitlab.config.lfs.enabled
Loading
Loading
Loading
Loading
@@ -4,6 +4,7 @@ class NotificationRecipient
include Gitlab::Utils::StrongMemoize
 
attr_reader :user, :type, :reason
def initialize(user, type, **opts)
unless NotificationSetting.levels.key?(type) || type == :subscription
raise ArgumentError, "invalid type: #{type.inspect}"
Loading
Loading
@@ -30,6 +31,7 @@ class NotificationRecipient
 
def notifiable?
return false unless has_access?
return false if emails_disabled?
return false if own_activity?
 
# even users with :disabled notifications receive manual subscriptions
Loading
Loading
@@ -109,6 +111,12 @@ class NotificationRecipient
 
private
 
# They are disabled if the project or group has disallowed it.
# No need to check the group if there is already a project
def emails_disabled?
@project ? @project.emails_disabled? : @group&.emails_disabled?
end
def read_ability
return if @skip_read_ability
return @read_ability if instance_variable_defined?(:@read_ability)
Loading
Loading
Loading
Loading
@@ -631,6 +631,13 @@ class Project < ApplicationRecord
 
alias_method :ancestors, :ancestors_upto
 
def emails_disabled?
strong_memoize(:emails_disabled) do
# disabling in the namespace overrides the project setting
Feature.enabled?(:emails_disabled, self, default_enabled: true) && (super || namespace.emails_disabled?)
end
end
def lfs_enabled?
return namespace.lfs_enabled? if self[:lfs_enabled].nil?
 
Loading
Loading
Loading
Loading
@@ -24,6 +24,7 @@ class EmailsOnPushService < Service
 
def execute(push_data)
return unless supported_events.include?(push_data[:object_kind])
return if project.emails_disabled?
 
EmailsOnPushWorker.perform_async(
project_id,
Loading
Loading
Loading
Loading
@@ -92,6 +92,7 @@ class GroupPolicy < BasePolicy
enable :change_visibility_level
 
enable :set_note_created_at
enable :set_emails_disabled
end
 
rule { can?(:read_nested_project_resources) }.policy do
Loading
Loading
Loading
Loading
@@ -162,6 +162,7 @@ class ProjectPolicy < BasePolicy
enable :set_issue_created_at
enable :set_issue_updated_at
enable :set_note_created_at
enable :set_emails_disabled
end
 
rule { can?(:guest_access) }.policy do
Loading
Loading
Loading
Loading
@@ -46,6 +46,11 @@ module Groups
params.delete(:parent_id)
end
 
# overridden in EE
def remove_unallowed_params
params.delete(:emails_disabled) unless can?(current_user, :set_emails_disabled, group)
end
def valid_share_with_group_lock_change?
return true unless changing_share_with_group_lock?
return true if can?(current_user, :change_share_with_group_lock, group)
Loading
Loading
Loading
Loading
@@ -321,6 +321,9 @@ class NotificationService
end
 
def decline_project_invite(project_member)
# Must always send, regardless of project/namespace configuration since it's a
# response to the user's action.
mailer.member_invite_declined_email(
project_member.real_source_type,
project_member.project.id,
Loading
Loading
@@ -351,8 +354,8 @@ class NotificationService
end
 
def decline_group_invite(group_member)
# always send this one, since it's a response to the user's own
# action
# Must always send, regardless of project/namespace configuration since it's a
# response to the user's action.
 
mailer.member_invite_declined_email(
group_member.real_source_type,
Loading
Loading
@@ -410,6 +413,10 @@ class NotificationService
end
 
def pipeline_finished(pipeline, recipients = nil)
# Must always check project configuration since recipients could be a list of emails
# from the PipelinesEmailService integration.
return if pipeline.project.emails_disabled?
email_template = "pipeline_#{pipeline.status}_email"
 
return unless mailer.respond_to?(email_template)
Loading
Loading
@@ -428,6 +435,8 @@ class NotificationService
end
 
def autodevops_disabled(pipeline, recipients)
return if pipeline.project.emails_disabled?
recipients.each do |recipient|
mailer.autodevops_disabled_email(pipeline, recipient).deliver_later
end
Loading
Loading
@@ -472,10 +481,14 @@ class NotificationService
end
 
def repository_cleanup_success(project, user)
return if project.emails_disabled?
mailer.send(:repository_cleanup_success_email, project, user).deliver_later
end
 
def repository_cleanup_failure(project, user, error)
return if project.emails_disabled?
mailer.send(:repository_cleanup_failure_email, project, user, error).deliver_later
end
 
Loading
Loading
Loading
Loading
@@ -9,6 +9,7 @@ module Projects
 
# rubocop: disable CodeReuse/ActiveRecord
def execute
remove_unallowed_params
validate!
 
ensure_wiki_exists if enabling_wiki?
Loading
Loading
@@ -54,6 +55,10 @@ module Projects
end
end
 
def remove_unallowed_params
params.delete(:emails_disabled) unless can?(current_user, :set_emails_disabled, project)
end
def after_update
todos_features_changes = %w(
issues_access_level
Loading
Loading
---
title: Allow email notifications to be disabled for all members of a group or project
merge_request: 30755
author: Dustin Spicuzza
type: added
# frozen_string_literal: true
class AddProjectEmailsDisabled < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :projects, :emails_disabled, :boolean
end
end
# frozen_string_literal: true
class AddGroupEmailsDisabled < ActiveRecord::Migration[5.2]
DOWNTIME = false
def change
add_column :namespaces, :emails_disabled, :boolean
end
end
Loading
Loading
@@ -2174,6 +2174,7 @@ ActiveRecord::Schema.define(version: 2019_08_06_071559) do
t.boolean "membership_lock", default: false
t.integer "last_ci_minutes_usage_notification_level"
t.integer "subgroup_creation_level", default: 1
t.boolean "emails_disabled"
t.index ["created_at"], name: "index_namespaces_on_created_at"
t.index ["custom_project_templates_group_id", "type"], name: "index_namespaces_on_custom_project_templates_group_id_and_type", where: "(custom_project_templates_group_id IS NOT NULL)"
t.index ["file_template_project_id"], name: "index_namespaces_on_file_template_project_id"
Loading
Loading
@@ -2744,6 +2745,7 @@ ActiveRecord::Schema.define(version: 2019_08_06_071559) do
t.boolean "reset_approvals_on_push", default: true
t.boolean "service_desk_enabled", default: true
t.integer "approvals_before_merge", default: 0, null: false
t.boolean "emails_disabled"
t.index ["archived", "pending_delete", "merge_requests_require_code_owner_approval"], name: "projects_requiring_code_owner_approval", where: "((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))"
t.index ["created_at"], name: "index_projects_on_created_at"
t.index ["creator_id"], name: "index_projects_on_creator_id"
Loading
Loading
Loading
Loading
@@ -137,6 +137,7 @@ excluded_attributes:
- :packages_enabled
- :mirror_last_update_at
- :mirror_last_successful_update_at
- :emails_disabled
namespaces:
- :runners_token
- :runners_token_encrypted
Loading
Loading
Loading
Loading
@@ -16,6 +16,19 @@ FactoryBot.define do
)
end
 
factory :emails_on_push_service do
project
type 'EmailsOnPushService'
active true
push_events true
tag_push_events true
properties(
recipients: 'test@example.com',
disable_diffs: true,
send_from_committer_email: true
)
end
factory :mock_deployment_service do
project
type 'MockDeploymentService'
Loading
Loading
Loading
Loading
@@ -853,4 +853,64 @@ describe Namespace do
it { is_expected.to be_falsy }
end
end
describe '#emails_disabled?' do
context 'when not a subgroup' do
it 'returns false' do
group = create(:group, emails_disabled: false)
expect(group.emails_disabled?).to be_falsey
end
it 'returns true' do
group = create(:group, emails_disabled: true)
expect(group.emails_disabled?).to be_truthy
end
end
context 'when a subgroup' do
let(:grandparent) { create(:group) }
let(:parent) { create(:group, parent: grandparent) }
let(:group) { create(:group, parent: parent) }
it 'returns false' do
expect(group.emails_disabled?).to be_falsey
end
context 'when ancestor emails are disabled' do
it 'returns true' do
grandparent.update_attribute(:emails_disabled, true)
expect(group.emails_disabled?).to be_truthy
end
end
end
context 'when :emails_disabled feature flag is off' do
before do
stub_feature_flags(emails_disabled: false)
end
context 'when not a subgroup' do
it 'returns false' do
group = create(:group, emails_disabled: true)
expect(group.emails_disabled?).to be_falsey
end
end
context 'when a subgroup and ancestor emails are disabled' do
let(:grandparent) { create(:group) }
let(:parent) { create(:group, parent: grandparent) }
let(:group) { create(:group, parent: parent) }
it 'returns false' do
grandparent.update_attribute(:emails_disabled, true)
expect(group.emails_disabled?).to be_falsey
end
end
end
end
end
Loading
Loading
@@ -9,6 +9,38 @@ describe NotificationRecipient do
 
subject(:recipient) { described_class.new(user, :watch, target: target, project: project) }
 
describe '#notifiable?' do
let(:recipient) { described_class.new(user, :mention, target: target, project: project) }
context 'when emails are disabled' do
it 'returns false if group disabled' do
expect(project.namespace).to receive(:emails_disabled?).and_return(true)
expect(recipient).to receive(:emails_disabled?).and_call_original
expect(recipient.notifiable?).to eq false
end
it 'returns false if project disabled' do
expect(project).to receive(:emails_disabled?).and_return(true)
expect(recipient).to receive(:emails_disabled?).and_call_original
expect(recipient.notifiable?).to eq false
end
end
context 'when emails are enabled' do
it 'returns true if group enabled' do
expect(project.namespace).to receive(:emails_disabled?).and_return(false)
expect(recipient).to receive(:emails_disabled?).and_call_original
expect(recipient.notifiable?).to eq true
end
it 'returns true if project enabled' do
expect(project).to receive(:emails_disabled?).and_return(false)
expect(recipient).to receive(:emails_disabled?).and_call_original
expect(recipient.notifiable?).to eq true
end
end
end
describe '#has_access?' do
before do
allow(user).to receive(:can?).and_call_original
Loading
Loading
Loading
Loading
@@ -20,4 +20,24 @@ describe EmailsOnPushService do
it { is_expected.not_to validate_presence_of(:recipients) }
end
end
context 'project emails' do
let(:push_data) { { object_kind: 'push' } }
let(:project) { create(:project, :repository) }
let(:service) { create(:emails_on_push_service, project: project) }
it 'does not send emails when disabled' do
expect(project).to receive(:emails_disabled?).and_return(true)
expect(EmailsOnPushWorker).not_to receive(:perform_async)
service.execute(push_data)
end
it 'does send emails when enabled' do
expect(project).to receive(:emails_disabled?).and_return(false)
expect(EmailsOnPushWorker).to receive(:perform_async)
service.execute(push_data)
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment