Commit 1068483f authored by Paul Slaughter's avatar Paul Slaughter

Merge branch...

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

UI for disabling group/project email notifications

Closes #50020

See merge request gitlab-org/gitlab-ce!30961
parents 9eabc0d6 9be16e1f
...@@ -28,6 +28,11 @@ export default { ...@@ -28,6 +28,11 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
canDisableEmails: {
type: Boolean,
required: false,
default: false,
},
canChangeVisibilityLevel: { canChangeVisibilityLevel: {
type: Boolean, type: Boolean,
required: false, required: false,
...@@ -104,6 +109,7 @@ export default { ...@@ -104,6 +109,7 @@ export default {
lfsEnabled: true, lfsEnabled: true,
requestAccessEnabled: true, requestAccessEnabled: true,
highlightChangesClass: false, highlightChangesClass: false,
emailsDisabled: false,
}; };
return { ...defaults, ...this.currentSettings }; return { ...defaults, ...this.currentSettings };
...@@ -341,5 +347,14 @@ export default { ...@@ -341,5 +347,14 @@ export default {
/> />
</project-setting-row> </project-setting-row>
</div> </div>
<project-setting-row v-if="canDisableEmails" class="mb-3">
<label class="js-emails-disabled">
<input :value="emailsDisabled" type="hidden" name="project[emails_disabled]" />
<input v-model="emailsDisabled" type="checkbox" /> {{ __('Disable email notifications') }}
</label>
<span class="form-text text-muted">{{
__('This setting will override user notification preferences for all project members.')
}}</span>
</project-setting-row>
</div> </div>
</template> </template>
...@@ -31,6 +31,11 @@ module GroupsHelper ...@@ -31,6 +31,11 @@ module GroupsHelper
can?(current_user, :change_share_with_group_lock, group) can?(current_user, :change_share_with_group_lock, group)
end end
def can_disable_group_emails?(group)
Feature.enabled?(:emails_disabled, group, default_enabled: true) &&
can?(current_user, :set_emails_disabled, group) && !group.parent&.emails_disabled?
end
def group_issues_count(state:) def group_issues_count(state:)
IssuesFinder IssuesFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true) .new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)
......
...@@ -5,7 +5,7 @@ module NotificationsHelper ...@@ -5,7 +5,7 @@ module NotificationsHelper
def notification_icon_class(level) def notification_icon_class(level)
case level.to_sym case level.to_sym
when :disabled when :disabled, :owner_disabled
'microphone-slash' 'microphone-slash'
when :participating when :participating
'volume-up' 'volume-up'
...@@ -18,6 +18,16 @@ module NotificationsHelper ...@@ -18,6 +18,16 @@ module NotificationsHelper
end end
end end
def notification_icon_level(notification_setting, emails_disabled = false)
if emails_disabled
'owner_disabled'
elsif notification_setting.global?
current_user.global_notification_setting.level
else
notification_setting.level
end
end
def notification_icon(level, text = nil) def notification_icon(level, text = nil)
icon("#{notification_icon_class(level)} fw", text: text) icon("#{notification_icon_class(level)} fw", text: text)
end end
...@@ -53,6 +63,8 @@ module NotificationsHelper ...@@ -53,6 +63,8 @@ module NotificationsHelper
_('Use your global notification setting') _('Use your global notification setting')
when :custom when :custom
_('You will only receive notifications for the events you choose') _('You will only receive notifications for the events you choose')
when :owner_disabled
_('Notifications have been disabled by the project or group owner')
end end
end end
......
...@@ -155,6 +155,12 @@ module ProjectsHelper ...@@ -155,6 +155,12 @@ module ProjectsHelper
end end
end end
def can_disable_emails?(project, current_user)
return false if project.group&.emails_disabled?
can?(current_user, :set_emails_disabled, project) && Feature.enabled?(:emails_disabled, project, default_enabled: true)
end
def last_push_event def last_push_event
current_user&.recent_push(@project) current_user&.recent_push(@project)
end end
...@@ -541,13 +547,15 @@ module ProjectsHelper ...@@ -541,13 +547,15 @@ module ProjectsHelper
snippetsAccessLevel: feature.snippets_access_level, snippetsAccessLevel: feature.snippets_access_level,
pagesAccessLevel: feature.pages_access_level, pagesAccessLevel: feature.pages_access_level,
containerRegistryEnabled: !!project.container_registry_enabled, containerRegistryEnabled: !!project.container_registry_enabled,
lfsEnabled: !!project.lfs_enabled lfsEnabled: !!project.lfs_enabled,
emailsDisabled: project.emails_disabled?
} }
end end
def project_permissions_panel_data(project) def project_permissions_panel_data(project)
{ {
currentSettings: project_permissions_settings(project), currentSettings: project_permissions_settings(project),
canDisableEmails: can_disable_emails?(project, current_user),
canChangeVisibilityLevel: can_change_visibility_level?(project, current_user), canChangeVisibilityLevel: can_change_visibility_level?(project, current_user),
allowedVisibilityOptions: project_allowed_visibility_levels(project), allowedVisibilityOptions: project_allowed_visibility_levels(project),
visibilityHelpPath: help_page_path('public_access/public_access'), visibilityHelpPath: help_page_path('public_access/public_access'),
......
...@@ -98,6 +98,10 @@ class IssuableSidebarBasicEntity < Grape::Entity ...@@ -98,6 +98,10 @@ class IssuableSidebarBasicEntity < Grape::Entity
autocomplete_projects_path(project_id: issuable.project.id) autocomplete_projects_path(project_id: issuable.project.id)
end end
expose :project_emails_disabled do |issuable|
issuable.project.emails_disabled?
end
private private
def current_user def current_user
......
- can_create_subgroups = can?(current_user, :create_subgroup, @group) - can_create_subgroups = can?(current_user, :create_subgroup, @group)
- emails_disabled = @group.emails_disabled?
.group-home-panel .group-home-panel
.row.mb-3 .row.mb-3
...@@ -21,7 +22,7 @@ ...@@ -21,7 +22,7 @@
.home-panel-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end .home-panel-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
- if current_user - if current_user
.group-buttons .group-buttons
= render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn' = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn', emails_disabled: emails_disabled
- if can? current_user, :create_projects, @group - if can? current_user, :create_projects, @group
- new_project_label = _("New project") - new_project_label = _("New project")
- new_subgroup_label = _("New subgroup") - new_subgroup_label = _("New subgroup")
......
...@@ -11,12 +11,18 @@ ...@@ -11,12 +11,18 @@
.form-check .form-check
= f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input' = f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
= f.label :share_with_group_lock, class: 'form-check-label' do = f.label :share_with_group_lock, class: 'form-check-label' do
%span %span.d-block
- group_link = link_to @group.name, group_path(@group) - group_link = link_to @group.name, group_path(@group)
= s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link } = s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
%br
%span.descr.text-muted= share_with_group_lock_help_text(@group) %span.descr.text-muted= share_with_group_lock_help_text(@group)
.form-group.append-bottom-default
.form-check
= f.check_box :emails_disabled, checked: @group.emails_disabled?, disabled: !can_disable_group_emails?(@group), class: 'form-check-input'
= f.label :emails_disabled, class: 'form-check-label' do
%span.d-block= s_('GroupSettings|Disable email notifications')
%span.text-muted= s_('GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects.')
= render_if_exists 'groups/settings/ip_restriction', f: f, group: @group = render_if_exists 'groups/settings/ip_restriction', f: f, group: @group
= render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group = render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group
= render 'groups/settings/lfs', f: f = render 'groups/settings/lfs', f: f
......
- emails_disabled = group.emails_disabled?
.gl-responsive-table-row.notification-list-item .gl-responsive-table-row.notification-list-item
.table-section.section-40 .table-section.section-40
%span.notification.fa.fa-holder.append-right-5 %span.notification.fa.fa-holder.append-right-5
- if setting.global? = notification_icon(notification_icon_level(setting, emails_disabled))
= notification_icon(current_user.global_notification_setting.level)
- else
= notification_icon(setting.level)
%span.str-truncated %span.str-truncated
= link_to group.name, group_path(group) = link_to group.name, group_path(group)
.table-section.section-30.text-right .table-section.section-30.text-right
= render 'shared/notifications/button', notification_setting: setting = render 'shared/notifications/button', notification_setting: setting, emails_disabled: emails_disabled
.table-section.section-30 .table-section.section-30
= form_for @user.notification_settings.find { |ns| ns.source == group }, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f| = form_for @user.notification_settings.find { |ns| ns.source == group }, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f|
......
- emails_disabled = project.emails_disabled?
%li.notification-list-item %li.notification-list-item
%span.notification.fa.fa-holder.append-right-5 %span.notification.fa.fa-holder.append-right-5
- if setting.global? = notification_icon(notification_icon_level(setting, emails_disabled))
= notification_icon(current_user.global_notification_setting.level)
- else
= notification_icon(setting.level)
%span.str-truncated %span.str-truncated
= link_to_project(project) = link_to_project(project)
.float-right .float-right
= render 'shared/notifications/button', notification_setting: setting = render 'shared/notifications/button', notification_setting: setting, emails_disabled: emails_disabled
- empty_repo = @project.empty_repo? - empty_repo = @project.empty_repo?
- show_auto_devops_callout = show_auto_devops_callout?(@project) - show_auto_devops_callout = show_auto_devops_callout?(@project)
- max_project_topic_length = 15 - max_project_topic_length = 15
- emails_disabled = @project.emails_disabled?
.project-home-panel{ class: [("empty-project" if empty_repo), ("js-keep-hidden-on-navigation" if vue_file_list_enabled?)] } .project-home-panel{ class: [("empty-project" if empty_repo), ("js-keep-hidden-on-navigation" if vue_file_list_enabled?)] }
.row.append-bottom-8 .row.append-bottom-8
.home-panel-title-row.col-md-12.col-lg-6.d-flex .home-panel-title-row.col-md-12.col-lg-6.d-flex
...@@ -41,7 +43,7 @@ ...@@ -41,7 +43,7 @@
.project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end .project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
- if current_user - if current_user
.d-inline-flex .d-inline-flex
= render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs' = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs', emails_disabled: emails_disabled
.count-buttons.d-inline-flex .count-buttons.d-inline-flex
= render 'projects/buttons/star' = render 'projects/buttons/star'
......
...@@ -137,7 +137,11 @@ ...@@ -137,7 +137,11 @@
.js-sidebar-participants-entry-point .js-sidebar-participants-entry-point
- if signed_in - if signed_in
.js-sidebar-subscriptions-entry-point - if issuable_sidebar[:project_emails_disabled]
.block.js-emails-disabled
= notification_description(:owner_disabled)
- else
.js-sidebar-subscriptions-entry-point
- project_ref = issuable_sidebar[:reference] - project_ref = issuable_sidebar[:reference]
.block.project-reference .block.project-reference
......
- btn_class = local_assigns.fetch(:btn_class, nil) - btn_class = local_assigns.fetch(:btn_class, '')
- emails_disabled = local_assigns.fetch(:emails_disabled, false)
- if notification_setting - if notification_setting
- if emails_disabled
- button_title = notification_description(:owner_disabled)
- aria_label = button_title
- btn_class << " disabled"
- else
- button_title = _("Notification setting")
- aria_label = _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }
.js-notification-dropdown.notification-dropdown.mr-md-2.home-panel-action-button.dropdown.inline .js-notification-dropdown.notification-dropdown.mr-md-2.home-panel-action-button.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
= hidden_setting_source_input(notification_setting) = hidden_setting_source_input(notification_setting)
...@@ -8,14 +17,14 @@ ...@@ -8,14 +17,14 @@
.js-notification-toggle-btns .js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) } %div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom? - if notification_setting.custom?
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading") = icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level) = notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon('caret-down') = icon('caret-down')
.sr-only Toggle dropdown .sr-only Toggle dropdown
- else - else
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
.float-left .float-left
= icon("bell", class: "js-notification-loading") = icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level) = notification_title(notification_setting.level)
......
- btn_class = local_assigns.fetch(:btn_class, nil) - btn_class = local_assigns.fetch(:btn_class, '')
- emails_disabled = local_assigns.fetch(:emails_disabled, false)
- if notification_setting - if notification_setting
- if emails_disabled
- button_title = notification_description(:owner_disabled)
- btn_class << " disabled"
- else
- button_title = _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }
.js-notification-dropdown.notification-dropdown.home-panel-action-button.prepend-top-default.append-right-8.dropdown.inline .js-notification-dropdown.notification-dropdown.home-panel-action-button.prepend-top-default.append-right-8.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form no-label" } do |f| = form_for notification_setting, remote: true, html: { class: "inline notification-form no-label" } do |f|
= hidden_setting_source_input(notification_setting) = hidden_setting_source_input(notification_setting)
...@@ -9,14 +16,14 @@ ...@@ -9,14 +16,14 @@
.js-notification-toggle-btns .js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) } %div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom? - if notification_setting.custom?
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", placement: 'top', toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } } %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= notification_setting_icon(notification_setting) = notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden %span.js-notification-loading.fa.hidden
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" }, class: "#{btn_class}" } %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" }, class: "#{btn_class}" }
= sprite_icon("arrow-down", css_class: "icon mr-0") = sprite_icon("arrow-down", css_class: "icon mr-0")
.sr-only Toggle dropdown .sr-only Toggle dropdown
- else - else
%button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= notification_setting_icon(notification_setting) = notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden %span.js-notification-loading.fa.hidden
= sprite_icon("arrow-down", css_class: "icon") = sprite_icon("arrow-down", css_class: "icon")
......
---
title: UI for disabling group/project email notifications
merge_request: 30961
author: Dustin Spicuzza
type: added
...@@ -411,6 +411,17 @@ To enable this feature, navigate to the group settings page, expand the ...@@ -411,6 +411,17 @@ To enable this feature, navigate to the group settings page, expand the
Define project templates at a group level by setting a group as the template source. Define project templates at a group level by setting a group as the template source.
[Learn more about group-level project templates](custom_project_templates.md). [Learn more about group-level project templates](custom_project_templates.md).
#### Disabling email notifications
You can disable all email notifications related to the group, which also includes
it's subgroups and projects.
To enable this feature:
1. Navigate to the group's **Settings > General** page.
1. Expand the **Permissions, LFS, 2FA** section, and select **Disable email notifications**.
1. Click **Save changes**.
### Advanced settings ### Advanced settings
- **Projects**: View all projects within that group, add members to each project, - **Projects**: View all projects within that group, add members to each project,
......
...@@ -126,6 +126,7 @@ The following table depicts the various user permission levels in a project. ...@@ -126,6 +126,7 @@ The following table depicts the various user permission levels in a project.
| Transfer project to another namespace | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ | | Remove project | | | | | ✓ |
| Delete issues | | | | | ✓ | | Delete issues | | | | | ✓ |
| Disable notification emails | | | | | ✓ |
| Force push to protected branches (*4*) | | | | | | | Force push to protected branches (*4*) | | | | | |
| Remove protected branches (*4*) | | | | | | | Remove protected branches (*4*) | | | | | |
...@@ -220,6 +221,7 @@ group. ...@@ -220,6 +221,7 @@ group.
| Remove group | | | | | ✓ | | Remove group | | | | | ✓ |
| Delete group epic **(ULTIMATE)** | | | | | ✓ | | Delete group epic **(ULTIMATE)** | | | | | ✓ |
| View group Audit Events | | | | | ✓ | | View group Audit Events | | | | | ✓ |
| Disable notification emails | | | | | ✓ |
- (1): Groups can be set to [allow either Owners or Owners and - (1): Groups can be set to [allow either Owners or Owners and
Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup) Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)
......
...@@ -32,6 +32,12 @@ links will be missing from the sidebar UI. ...@@ -32,6 +32,12 @@ links will be missing from the sidebar UI.
You can still access them with direct links if you can access Merge Requests. This is deliberate, if you can see You can still access them with direct links if you can access Merge Requests. This is deliberate, if you can see
Issues or Merge Requests, both of which use Labels and Milestones, then you shouldn't be denied access to Labels and Milestones pages. Issues or Merge Requests, both of which use Labels and Milestones, then you shouldn't be denied access to Labels and Milestones pages.
#### Disabling email notifications
You can disable all email notifications related to the project by selecting the
**Disable email notifications** checkbox. Only the project owner is allowed to change
this setting.
### Issue settings ### Issue settings
Add an [issue description template](../description_templates.md#description-templates) to your project, so that every new issue will start with a custom template. Add an [issue description template](../description_templates.md#description-templates) to your project, so that every new issue will start with a custom template.
......
...@@ -51,6 +51,10 @@ Organization like this is suitable for users that belong to different groups but ...@@ -51,6 +51,10 @@ Organization like this is suitable for users that belong to different groups but
same need for being notified for every group they are member of. same need for being notified for every group they are member of.
These settings can be configured on group page under the name of the group. It will be the dropdown with the bell icon. They can also be configured on the user profile notifications dropdown. These settings can be configured on group page under the name of the group. It will be the dropdown with the bell icon. They can also be configured on the user profile notifications dropdown.
The group owner can disable email notifications for a group, which also includes
it's subgroups and projects. If this is the case, you will not receive any corresponding notifications,
and the notification button will be disabled with an explanatory tooltip.
### Project Settings ### Project Settings
![notification settings](img/notification_project_settings.png) ![notification settings](img/notification_project_settings.png)
...@@ -60,6 +64,10 @@ other setting. ...@@ -60,6 +64,10 @@ other setting.
This is suitable for users that have different needs for notifications per project basis. This is suitable for users that have different needs for notifications per project basis.
These settings can be configured on project page under the name of the project. It will be the dropdown with the bell icon. They can also be configured on the user profile notifications dropdown. These settings can be configured on project page under the name of the project. It will be the dropdown with the bell icon. They can also be configured on the user profile notifications dropdown.
The project owner (or it's group owner) can disable email notifications for the project.
If this is the case, you will not receive any corresponding notifications, and the notification
button will be disabled with an explanatory tooltip.
## Notification events ## Notification events
Below is the table of events users can be notified of: Below is the table of events users can be notified of:
......
...@@ -3943,6 +3943,9 @@ msgstr "" ...@@ -3943,6 +3943,9 @@ msgstr ""
msgid "Disable" msgid "Disable"
msgstr "" msgstr ""
msgid "Disable email notifications"
msgstr ""
msgid "Disable for this project" msgid "Disable for this project"
msgstr "" msgstr ""
...@@ -5481,6 +5484,9 @@ msgstr "" ...@@ -5481,6 +5484,9 @@ msgstr ""
msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group" msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
msgstr "" msgstr ""
msgid "GroupSettings|Disable email notifications"
msgstr ""
msgid "GroupSettings|Learn more about badges." msgid "GroupSettings|Learn more about badges."
msgstr "" msgstr ""
...@@ -5508,6 +5514,9 @@ msgstr "" ...@@ -5508,6 +5514,9 @@ msgstr ""
msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually." msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
msgstr "" msgstr ""
msgid "GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects."
msgstr ""
msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group" msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
msgstr "" msgstr ""
...@@ -7592,6 +7601,9 @@ msgstr "" ...@@ -7592,6 +7601,9 @@ msgstr ""
msgid "Notifications" msgid "Notifications"
msgstr "" msgstr ""
msgid "Notifications have been disabled by the project or group owner"
msgstr ""
msgid "Notifications off" msgid "Notifications off"
msgstr "" msgstr ""
...@@ -11661,6 +11673,9 @@ msgstr "" ...@@ -11661,6 +11673,9 @@ msgstr ""
msgid "This setting can be overridden in each project." msgid "This setting can be overridden in each project."
msgstr "" msgstr ""
msgid "This setting will override user notification preferences for all project members."
msgstr ""
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}" msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr "" msgstr ""
......
...@@ -161,4 +161,27 @@ describe 'Group show page' do ...@@ -161,4 +161,27 @@ describe 'Group show page' do
expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title) expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title)
end end
end end
context 'notification button', :js do
let(:maintainer) { create(:user) }
let!(:project) { create(:project, namespace: group) }
before do
group.add_maintainer(maintainer)
sign_in(maintainer)
end
it 'is enabled by default' do
visit path
expect(page).to have_selector('.notifications-btn:not(.disabled)', visible: true)
end
it 'is disabled if emails are disabled' do
group.update_attribute(:emails_disabled, true)
visit path
expect(page).to have_selector('.notifications-btn.disabled', visible: true)
end
end
end end
...@@ -27,4 +27,14 @@ describe "User toggles subscription", :js do ...@@ -27,4 +27,14 @@ describe "User toggles subscription", :js do
# Check we're unsubscribed. # Check we're unsubscribed.
expect(subscription_button).to have_css("button:not(.is-checked)") expect(subscription_button).to have_css("button:not(.is-checked)")
end end
context 'when project emails are disabled' do
let(:project) { create(:project_empty_repo, :public, emails_disabled: true) }
it 'is disabled' do
expect(page).to have_content('Notifications have been disabled by the project or group owner')
expect(page).to have_selector('.js-emails-disabled', visible: true)
expect(page).not_to have_selector('.js-issuable-subscribe-button')
end
end
end end
...@@ -20,4 +20,12 @@ describe 'User visits the notifications tab', :js do ...@@ -20,4 +20,12 @@ describe 'User visits the notifications tab', :js do
expect(page).to have_selector('#notifications-button', text: 'On mention') expect(page).to have_selector('#notifications-button', text: 'On mention')
end end
context 'when project emails are disabled' do
let(:project) { create(:project, emails_disabled: true) }
it 'notification button is disabled' do
expect(page).to have_selector('.notifications-btn.disabled', visible: true)
end
end
end end
...@@ -59,6 +59,12 @@ describe 'Projects > Settings > Visibility settings', :js do ...@@ -59,6 +59,12 @@ describe 'Projects > Settings > Visibility settings', :js do
end end
end end
end end
context 'disable email notifications' do
it 'is visible' do
expect(page).to have_selector('.js-emails-disabled', visible: true)
end
end
end end
context 'as maintainer' do context 'as maintainer' do
...@@ -76,5 +82,11 @@ describe 'Projects > Settings > Visibility settings', :js do ...@@ -76,5 +82,11 @@ describe 'Projects > Settings > Visibility settings', :js do
expect(visibility_select_container).to have_selector 'select[name="project[visibility_level]"]:disabled' expect(visibility_select_container).to have_selector 'select[name="project[visibility_level]"]:disabled'
expect(visibility_select_container).to have_content 'The project can be accessed by anyone, regardless of authentication.' expect(visibility_select_container).to have_content 'The project can be accessed by anyone, regardless of authentication.'
end end
context 'disable email notifications' do
it 'is not available' do
expect(page).not_to have_selector('.js-emails-disabled', visible: true)
end
end
end end
end end
...@@ -65,4 +65,12 @@ describe 'Projects > Show > User manages notifications', :js do ...@@ -65,4 +65,12 @@ describe 'Projects > Show > User manages notifications', :js do
end end
end end
end end
context 'when project emails are disabled' do
let(:project) { create(:project, :public, :repository, emails_disabled: true) }
it 'is disabled' do
expect(page).to have_selector('.notifications-btn.disabled', visible: true)
end
end
end end
...@@ -262,4 +262,44 @@ describe GroupsHelper do ...@@ -262,4 +262,44 @@ describe GroupsHelper do
expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json) expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json)
end end
end end
describe '#can_disable_group_emails?' do
let(:current_user) { create(:user) }
let(:group) { create(:group, name: 'group') }
let(:subgroup) { create(:group, name: 'subgroup', parent: group) }
before do
allow(helper).to receive(:current_user) { current_user }
end
it 'returns true for the group owner' do
allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, group) { true }
expect(helper.can_disable_group_emails?(group)).to be_truthy
end
it 'returns false for anyone else' do
allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, group) { false }
expect(helper.can_disable_group_emails?(group)).to be_falsey
end
context 'when subgroups' do
before do
allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, subgroup) { true }
end
it 'returns false if parent group is disabling emails' do
allow(group).to receive(:emails_disabled?).and_return(true)
expect(helper.can_disable_group_emails?(subgroup)).to be_falsey
end
it 'returns true if parent group is not disabling emails' do
allow(group).to receive(:emails_disabled?).and_return(false)
expect(helper.can_disable_group_emails?(subgroup)).to be_truthy
end
end
end
end end
...@@ -3,6 +3,7 @@ require 'spec_helper' ...@@ -3,6 +3,7 @@ require 'spec_helper'
describe NotificationsHelper do describe NotificationsHelper do
describe 'notification_icon' do describe 'notification_icon' do
it { expect(notification_icon(:disabled)).to match('class="fa fa-microphone-slash fa-fw"') } it { expect(notification_icon(:disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
it { expect(notification_icon(:owner_disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
it { expect(notification_icon(:participating)).to match('class="fa fa-volume-up fa-fw"') } it { expect(notification_icon(:participating)).to match('class="fa fa-volume-up fa-fw"') }
it { expect(notification_icon(:mention)).to match('class="fa fa-at fa-fw"') } it { expect(notification_icon(:mention)).to match('class="fa fa-at fa-fw"') }
it { expect(notification_icon(:global)).to match('class="fa fa-globe fa-fw"') } it { expect(notification_icon(:global)).to match('class="fa fa-globe fa-fw"') }
...@@ -19,4 +20,14 @@ describe NotificationsHelper do ...@@ -19,4 +20,14 @@ describe NotificationsHelper do
it { expect(notification_event_name(:success_pipeline)).to match('Successful pipeline') } it { expect(notification_event_name(:success_pipeline)).to match('Successful pipeline') }
it { expect(notification_event_name(:failed_pipeline)).to match('Failed pipeline') } it { expect(notification_event_name(:failed_pipeline)).to match('Failed pipeline') }
end end
describe '#notification_icon_level' do
let(:user) { create(:user) }
let(:global_setting) { user.global_notification_setting }
let(:notification_setting) { create(:notification_setting, level: :watch) }
it { expect(notification_icon_level(notification_setting, true)).to eq 'owner_disabled' }
it { expect(notification_icon_level(notification_setting)).to eq 'watch' }
it { expect(notification_icon_level(global_setting)).to eq 'participating' }
end
end end
...@@ -107,6 +107,30 @@ describe ProjectsHelper do ...@@ -107,6 +107,30 @@ describe ProjectsHelper do
end end
end end
describe '#can_disable_emails?' do
let(:project) { create(:project) }
let(:user) { create(:project_member, :maintainer, user: create(:user), project: project).user }
it 'returns true for the project owner' do
allow(helper).to receive(:can?).with(project.owner, :set_emails_disabled, project) { true }
expect(helper.can_disable_emails?(project, project.owner)).to be_truthy
end
it 'returns false for anyone else' do
allow(helper).to receive(:can?).with(user, :set_emails_disabled, project) { false }
expect(helper.can_disable_emails?(project, user)).to be_falsey
end
it 'returns false if group emails disabled' do
project = create(:project, group: create(:group))
allow(project.group).to receive(:emails_disabled?).and_return(true)
expect(helper.can_disable_emails?(project, project.owner)).to be_falsey
end
end
describe "readme_cache_key" do describe "readme_cache_key" do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
...@@ -477,6 +501,7 @@ describe ProjectsHelper do ...@@ -477,6 +501,7 @@ describe ProjectsHelper do
it 'returns the command to push to create project over SSH' do it 'returns the command to push to create project over SSH' do
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:enabled_git_access_protocol) { 'ssh' } allow(Gitlab::CurrentSettings.current_application_settings).to receive(:enabled_git_access_protocol) { 'ssh' }
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return('git@localhost:')
expect(helper.push_to_create_project_command(user)).to eq('git push --set-upstream git@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)') expect(helper.push_to_create_project_command(user)).to eq('git push --set-upstream git@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)')
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