diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue index 627d37bac682815fbb0806f01dd018065482ce10..a223a8f5b0816ec38cde02ae8995446fd3540a82 100644 --- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue +++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue @@ -28,6 +28,11 @@ export default { type: Object, required: true, }, + canDisableEmails: { + type: Boolean, + required: false, + default: false, + }, canChangeVisibilityLevel: { type: Boolean, required: false, @@ -104,6 +109,7 @@ export default { lfsEnabled: true, requestAccessEnabled: true, highlightChangesClass: false, + emailsDisabled: false, }; return { ...defaults, ...this.currentSettings }; @@ -341,5 +347,14 @@ export default { /> + + + {{ + __('This setting will override user notification preferences for all project members.') + }} + diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 160f9ac4793920d868133b653e3bc723387d6097..bd26bd01313c6990fc7e5751566a91729f01ba50 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -31,6 +31,11 @@ module GroupsHelper can?(current_user, :change_share_with_group_lock, group) 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:) IssuesFinder .new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 11b9cf22142b5f0e261f72b4f2f284cdb0cb44b2..5678304ffcf03bde0eaf945ed1bea90f0acba101 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -5,7 +5,7 @@ module NotificationsHelper def notification_icon_class(level) case level.to_sym - when :disabled + when :disabled, :owner_disabled 'microphone-slash' when :participating 'volume-up' @@ -18,6 +18,16 @@ module NotificationsHelper 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) icon("#{notification_icon_class(level)} fw", text: text) end @@ -53,6 +63,8 @@ module NotificationsHelper _('Use your global notification setting') when :custom _('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 diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 71c9c121e4819621f8b35dbfe6eb03e6c358c9b9..33bf2d57fae5150c1d51fee494ff158c71d9a5de 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -155,6 +155,12 @@ module ProjectsHelper 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 current_user&.recent_push(@project) end @@ -541,13 +547,15 @@ module ProjectsHelper snippetsAccessLevel: feature.snippets_access_level, pagesAccessLevel: feature.pages_access_level, containerRegistryEnabled: !!project.container_registry_enabled, - lfsEnabled: !!project.lfs_enabled + lfsEnabled: !!project.lfs_enabled, + emailsDisabled: project.emails_disabled? } end def project_permissions_panel_data(project) { currentSettings: project_permissions_settings(project), + canDisableEmails: can_disable_emails?(project, current_user), canChangeVisibilityLevel: can_change_visibility_level?(project, current_user), allowedVisibilityOptions: project_allowed_visibility_levels(project), visibilityHelpPath: help_page_path('public_access/public_access'), diff --git a/app/serializers/issuable_sidebar_basic_entity.rb b/app/serializers/issuable_sidebar_basic_entity.rb index 61de3c93337f6d8dee437c96125679fe472e4927..c02fd024345da5c34790abaf110d014161ab5482 100644 --- a/app/serializers/issuable_sidebar_basic_entity.rb +++ b/app/serializers/issuable_sidebar_basic_entity.rb @@ -98,6 +98,10 @@ class IssuableSidebarBasicEntity < Grape::Entity autocomplete_projects_path(project_id: issuable.project.id) end + expose :project_emails_disabled do |issuable| + issuable.project.emails_disabled? + end + private def current_user diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml index 4daf3683eafa63e05b6072753874f6a5f5ac5a47..e50d2b8e99440d8897246ea3db5e4d760c37448e 100644 --- a/app/views/groups/_home_panel.html.haml +++ b/app/views/groups/_home_panel.html.haml @@ -1,4 +1,5 @@ - can_create_subgroups = can?(current_user, :create_subgroup, @group) +- emails_disabled = @group.emails_disabled? .group-home-panel .row.mb-3 @@ -21,7 +22,7 @@ .home-panel-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end - if current_user .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 - new_project_label = _("New project") - new_subgroup_label = _("New subgroup") diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index 162d28abb8c3655a60ef8c3f26b7f20ccc50121d..94a938021f93ab135544ae6baea0c8a7d4f4bdc0 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -11,12 +11,18 @@ .form-check = 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 - %span + %span.d-block - 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 } - %br %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/allowed_email_domain', f: f, group: @group = render 'groups/settings/lfs', f: f diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml index cf17ee44145af3e5d165c9982da9b5d21daa69ac..1776d260e19c33a15413ae083c5d3079797b6bab 100644 --- a/app/views/profiles/notifications/_group_settings.html.haml +++ b/app/views/profiles/notifications/_group_settings.html.haml @@ -1,16 +1,15 @@ +- emails_disabled = group.emails_disabled? + .gl-responsive-table-row.notification-list-item .table-section.section-40 %span.notification.fa.fa-holder.append-right-5 - - if setting.global? - = notification_icon(current_user.global_notification_setting.level) - - else - = notification_icon(setting.level) + = notification_icon(notification_icon_level(setting, emails_disabled)) %span.str-truncated = link_to group.name, group_path(group) .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 = form_for @user.notification_settings.find { |ns| ns.source == group }, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f| diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml index 823fec3fab405f8450b79f636b487fd7015ed569..63a77b335b6f4145e90cf5d58256a2c16ba1d5de 100644 --- a/app/views/profiles/notifications/_project_settings.html.haml +++ b/app/views/profiles/notifications/_project_settings.html.haml @@ -1,12 +1,11 @@ +- emails_disabled = project.emails_disabled? + %li.notification-list-item %span.notification.fa.fa-holder.append-right-5 - - if setting.global? - = notification_icon(current_user.global_notification_setting.level) - - else - = notification_icon(setting.level) + = notification_icon(notification_icon_level(setting, emails_disabled)) %span.str-truncated = link_to_project(project) .float-right - = render 'shared/notifications/button', notification_setting: setting + = render 'shared/notifications/button', notification_setting: setting, emails_disabled: emails_disabled diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 824fe3c791d71043164886b2334d6dd054d1a10a..4783b10cf6da0c322c39d1ee8e91eb90eb0b2138 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,6 +1,8 @@ - empty_repo = @project.empty_repo? - show_auto_devops_callout = show_auto_devops_callout?(@project) - 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?)] } .row.append-bottom-8 .home-panel-title-row.col-md-12.col-lg-6.d-flex @@ -41,7 +43,7 @@ .project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end - if current_user .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 = render 'projects/buttons/star' diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index b4f8377c008af0d31af64876c789dfc07960bae8..825088a58e7699dc21584b4f49c22d56d18e0eb0 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -137,7 +137,11 @@ .js-sidebar-participants-entry-point - 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] .block.project-reference diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index 749aa258af630122354170b8d67aa4f4db38565c..b4266937a4e98e7adfca3d45bbd6a4e88104ffad 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,6 +1,15 @@ -- 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 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 = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| = hidden_setting_source_input(notification_setting) @@ -8,14 +17,14 @@ .js-notification-toggle-btns %div{ class: ("btn-group" 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") = notification_title(notification_setting.level) %button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } } = icon('caret-down') .sr-only Toggle dropdown - 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 = icon("bell", class: "js-notification-loading") = notification_title(notification_setting.level) diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml index 052e6da5bae1d16e8e55059d57767bcb68fce45a..3c8cc023848233a24aa5f3aa1f63c8f7840dc0b3 100644 --- a/app/views/shared/notifications/_new_button.html.haml +++ b/app/views/shared/notifications/_new_button.html.haml @@ -1,6 +1,13 @@ -- 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 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 = form_for notification_setting, remote: true, html: { class: "inline notification-form no-label" } do |f| = hidden_setting_source_input(notification_setting) @@ -9,14 +16,14 @@ .js-notification-toggle-btns %div{ class: ("btn-group" 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) %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}" } = sprite_icon("arrow-down", css_class: "icon mr-0") .sr-only Toggle dropdown - 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) %span.js-notification-loading.fa.hidden = sprite_icon("arrow-down", css_class: "icon") diff --git a/changelogs/unreleased/50020-fe-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml b/changelogs/unreleased/50020-fe-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml new file mode 100644 index 0000000000000000000000000000000000000000..a5fe7b1d18e1d690815598cca6d4fc0f5308ce90 --- /dev/null +++ b/changelogs/unreleased/50020-fe-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml @@ -0,0 +1,5 @@ +--- +title: UI for disabling group/project email notifications +merge_request: 30961 +author: Dustin Spicuzza +type: added diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 8ebdb760678eae3538227041a5709b238a72662e..d1d4f3740b0084a5eae1844964087693b5aba3a7 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -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. [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 - **Projects**: View all projects within that group, add members to each project, diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 16684b9f72b0d0e973eeefdd13fe993661c96dd4..4fd7c5abf7807d76df003c0b32012c401c1985f3 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -126,6 +126,7 @@ The following table depicts the various user permission levels in a project. | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | | Delete issues | | | | | ✓ | +| Disable notification emails | | | | | ✓ | | Force push to protected branches (*4*) | | | | | | | Remove protected branches (*4*) | | | | | | @@ -220,6 +221,7 @@ group. | Remove group | | | | | ✓ | | Delete group epic **(ULTIMATE)** | | | | | ✓ | | View group Audit Events | | | | | ✓ | +| Disable notification emails | | | | | ✓ | - (1): Groups can be set to [allow either Owners or Owners and Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup) diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index 17ec9ecb5d1ecdb48919dcb15fc5b053a664d15d..4e3db95b6d6cff29800c7cb599be6ef34c8b325b 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -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 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 Add an [issue description template](../description_templates.md#description-templates) to your project, so that every new issue will start with a custom template. diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md index d82f7c6fdc72b17e8c8bdf8e0fd455a837a31129..ccb8844aea337a9a258ef48c48dceb20ba5bf7c7 100644 --- a/doc/workflow/notifications.md +++ b/doc/workflow/notifications.md @@ -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. 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 ![notification settings](img/notification_project_settings.png) @@ -60,6 +64,10 @@ other setting. 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. +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 Below is the table of events users can be notified of: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index afdfd620ca262d7d2df4e1ad1f8436f39366d022..e8d273603953e1f740cad7d5e7ecb403c82a227c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3943,6 +3943,9 @@ msgstr "" msgid "Disable" msgstr "" +msgid "Disable email notifications" +msgstr "" + msgid "Disable for this project" msgstr "" @@ -5481,6 +5484,9 @@ msgstr "" msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group" msgstr "" +msgid "GroupSettings|Disable email notifications" +msgstr "" + msgid "GroupSettings|Learn more about badges." 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." 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" msgstr "" @@ -7592,6 +7601,9 @@ msgstr "" msgid "Notifications" msgstr "" +msgid "Notifications have been disabled by the project or group owner" +msgstr "" + msgid "Notifications off" msgstr "" @@ -11661,6 +11673,9 @@ msgstr "" msgid "This setting can be overridden in each project." 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}" msgstr "" diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb index 942a98894885764e5a1d74fe19565dc822bc2e7e..bcaed2a5f186b96150256a844b72ceeecc54e7c6 100644 --- a/spec/features/groups/show_spec.rb +++ b/spec/features/groups/show_spec.rb @@ -161,4 +161,27 @@ describe 'Group show page' do expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title) 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 diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb index 7a721adc8ddecd0d1aacfec75cbaa27e92d536a6..165d41950da9e7e2d485859babfb44d8c0bcb1f9 100644 --- a/spec/features/issues/user_toggles_subscription_spec.rb +++ b/spec/features/issues/user_toggles_subscription_spec.rb @@ -27,4 +27,14 @@ describe "User toggles subscription", :js do # Check we're unsubscribed. expect(subscription_button).to have_css("button:not(.is-checked)") 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 diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb index 1472cc882a7863772f33cb75365d2614357f3929..d788c0574e277c9644d01073bc76bf41fb4a327b 100644 --- a/spec/features/profiles/user_visits_notifications_tab_spec.rb +++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb @@ -20,4 +20,12 @@ describe 'User visits the notifications tab', :js do expect(page).to have_selector('#notifications-button', text: 'On mention') 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 diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb index 46fd676954d96796ac165b45271a84df11e7a636..0e757e647a02e332a0a50f862eb381366c07b387 100644 --- a/spec/features/projects/settings/visibility_settings_spec.rb +++ b/spec/features/projects/settings/visibility_settings_spec.rb @@ -59,6 +59,12 @@ describe 'Projects > Settings > Visibility settings', :js do 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 context 'as maintainer' 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_content 'The project can be accessed by anyone, regardless of authentication.' 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 diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb index 5e9c98428cfea939e969ac3762a57d48b3cd838c..851a09cf28a0db8c89f4b9eeacf4cd97079dfde4 100644 --- a/spec/features/projects/show/user_manages_notifications_spec.rb +++ b/spec/features/projects/show/user_manages_notifications_spec.rb @@ -65,4 +65,12 @@ describe 'Projects > Show > User manages notifications', :js do 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 diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 037b16c90ed63288f37e4dfac7cb1c5de91fa6f7..98719697cea4a4feb1a451aa2da34ea88d6fb420 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -262,4 +262,44 @@ describe GroupsHelper do expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json) 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 diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb index 9ecaabc04ed56a5233050d5fff7b191a8bbd238d..5717b15d6563a0c5a9e9bd01c8b6cc582af47f5d 100644 --- a/spec/helpers/notifications_helper_spec.rb +++ b/spec/helpers/notifications_helper_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe NotificationsHelper do describe 'notification_icon' do 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(:mention)).to match('class="fa fa-at fa-fw"') } it { expect(notification_icon(:global)).to match('class="fa fa-globe fa-fw"') } @@ -19,4 +20,14 @@ describe NotificationsHelper do it { expect(notification_event_name(:success_pipeline)).to match('Successful pipeline') } it { expect(notification_event_name(:failed_pipeline)).to match('Failed pipeline') } 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 diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 3716879c45833d4b682de98b58a3a1fb3b2c2f2b..a70bfc2adc7ba9c7aa12b15bd7e4d6d777963874 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -107,6 +107,30 @@ describe ProjectsHelper do 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 let(:project) { create(:project, :repository) } @@ -477,6 +501,7 @@ describe ProjectsHelper 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.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)') end