Commit a68a4046 authored by Dylan Griffith's avatar Dylan Griffith

Merge branch '321372-feature-flag-rollout-of-vue_notification_dropdown' into 'master'

[Feature flag] Rollout of "vue_notification_dropdown" [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!54422
parents 82223643 b123ea82
...@@ -291,8 +291,6 @@ linters: ...@@ -291,8 +291,6 @@ linters:
- 'app/views/shared/milestones/_sidebar.html.haml' - 'app/views/shared/milestones/_sidebar.html.haml'
- 'app/views/shared/milestones/_top.html.haml' - 'app/views/shared/milestones/_top.html.haml'
- 'app/views/shared/notes/_hints.html.haml' - 'app/views/shared/notes/_hints.html.haml'
- 'app/views/shared/notifications/_button.html.haml'
- 'app/views/shared/notifications/_new_button.html.haml'
- 'app/views/shared/runners/_runner_description.html.haml' - 'app/views/shared/runners/_runner_description.html.haml'
- 'app/views/shared/runners/show.html.haml' - 'app/views/shared/runners/show.html.haml'
- 'app/views/shared/snippets/_header.html.haml' - 'app/views/shared/snippets/_header.html.haml'
......
...@@ -115,7 +115,11 @@ export default { ...@@ -115,7 +115,11 @@ export default {
<gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3" /> <gl-loading-icon v-if="isLoading" size="lg" class="gl-mt-3" />
<template v-else> <template v-else>
<gl-form-group v-for="event in events" :key="event.id"> <gl-form-group v-for="event in events" :key="event.id">
<gl-form-checkbox v-model="event.enabled" @change="updateEvent($event, event)"> <gl-form-checkbox
v-model="event.enabled"
:data-testid="`notification-setting-${event.id}`"
@change="updateEvent($event, event)"
>
<strong>{{ event.name }}</strong <strong>{{ event.name }}</strong
><gl-loading-icon v-if="event.loading" :inline="true" class="gl-ml-2" /> ><gl-loading-icon v-if="event.loading" :inline="true" class="gl-ml-2" />
</gl-form-checkbox> </gl-form-checkbox>
......
...@@ -128,7 +128,8 @@ export default { ...@@ -128,7 +128,8 @@ export default {
<gl-button-group <gl-button-group
v-if="isCustomNotification" v-if="isCustomNotification"
v-gl-tooltip="{ title: buttonTooltip }" v-gl-tooltip="{ title: buttonTooltip }"
data-testid="notificationButton" data-testid="notification-button"
:class="{ disabled: disabled }"
:size="buttonSize" :size="buttonSize"
> >
<gl-button <gl-button
...@@ -165,12 +166,13 @@ export default { ...@@ -165,12 +166,13 @@ export default {
<gl-dropdown <gl-dropdown
v-else v-else
v-gl-tooltip="{ title: buttonTooltip }" v-gl-tooltip="{ title: buttonTooltip }"
data-testid="notificationButton" data-testid="notification-button"
:text="buttonText" :text="buttonText"
:icon="buttonIcon" :icon="buttonIcon"
:loading="isLoading" :loading="isLoading"
:size="buttonSize" :size="buttonSize"
:disabled="disabled" :disabled="disabled"
:class="{ disabled: disabled }"
> >
<notifications-dropdown-item <notifications-dropdown-item
v-for="item in notificationLevels" v-for="item in notificationLevels"
......
...@@ -33,7 +33,13 @@ export default { ...@@ -33,7 +33,13 @@ export default {
</script> </script>
<template> <template>
<gl-dropdown-item is-check-item :is-checked="isActive" @click="$emit('item-selected', level)"> <gl-dropdown-item
is-check-item
:is-checked="isActive"
:class="{ 'is-active': isActive }"
data-testid="notification-item"
@click="$emit('item-selected', level)"
>
<div class="gl-display-flex gl-flex-direction-column"> <div class="gl-display-flex gl-flex-direction-column">
<span class="gl-font-weight-bold">{{ title }}</span> <span class="gl-font-weight-bold">{{ title }}</span>
<span class="gl-text-gray-500">{{ description }}</span> <span class="gl-text-gray-500">{{ description }}</span>
......
import $ from 'jquery';
import { Rails } from '~/lib/utils/rails_ujs';
import { __ } from '~/locale';
import { deprecatedCreateFlash as Flash } from './flash';
export default function notificationsDropdown() {
$(document).on('click', '.update-notification', function updateNotificationCallback(e) {
e.preventDefault();
if ($(this).is('.is-active') && $(this).data('notificationLevel') === 'custom') {
return;
}
const notificationLevel = $(this).data('notificationLevel');
const form = $(this).parents('.notification-form').first();
form.find('.js-notification-loading').toggleClass('spinner');
if (form.hasClass('no-label')) {
form.find('.js-notification-loading').toggleClass('hidden');
form.find('.js-notifications-icon').toggleClass('hidden');
}
form.find('#notification_setting_level').val(notificationLevel);
Rails.fire(form[0], 'submit');
});
$(document).on('ajax:success', '.notification-form', (e) => {
const data = e.detail[0];
if (data.saved) {
$(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html);
} else {
Flash(__('Failed to save new settings'), 'alert');
}
});
}
import $ from 'jquery';
import { deprecatedCreateFlash as flash } from './flash';
import axios from './lib/utils/axios_utils';
import { __ } from './locale';
export default class NotificationsForm {
constructor() {
this.toggleCheckbox = this.toggleCheckbox.bind(this);
this.initEventListeners();
}
initEventListeners() {
$(document).on('change', '.js-custom-notification-event', this.toggleCheckbox);
}
toggleCheckbox(e) {
const $checkbox = $(e.currentTarget);
const $parent = $checkbox.closest('.form-check');
this.saveEvent($checkbox, $parent);
}
// eslint-disable-next-line class-methods-use-this
showCheckboxLoadingSpinner($parent) {
$parent.find('.is-loading').removeClass('gl-display-none');
$parent.find('.is-done').addClass('gl-display-none');
}
saveEvent($checkbox, $parent) {
const form = $parent.parents('form').first();
this.showCheckboxLoadingSpinner($parent);
axios[form.attr('method')](form.attr('action'), form.serialize())
.then(({ data }) => {
$checkbox.enable();
if (data.saved) {
$parent.find('.is-loading').addClass('gl-display-none');
$parent.find('.is-done').removeClass('gl-display-none');
setTimeout(() => {
$parent.find('.is-done').addClass('gl-display-none');
}, 2000);
}
})
.catch(() => flash(__('There was an error saving your notification settings.')));
}
}
...@@ -7,8 +7,6 @@ import initInviteMembersModal from '~/invite_members/init_invite_members_modal'; ...@@ -7,8 +7,6 @@ import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger'; import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { getPagePath, getDashPath } from '~/lib/utils/common_utils'; import { getPagePath, getDashPath } from '~/lib/utils/common_utils';
import initNotificationsDropdown from '~/notifications'; import initNotificationsDropdown from '~/notifications';
import notificationsDropdown from '~/notifications_dropdown';
import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list'; import ProjectsList from '~/projects_list';
import GroupTabs from './group_tabs'; import GroupTabs from './group_tabs';
...@@ -22,13 +20,8 @@ export default function initGroupDetails(actionName = 'show') { ...@@ -22,13 +20,8 @@ export default function initGroupDetails(actionName = 'show') {
new GroupTabs({ parentEl: '.groups-listing', action }); new GroupTabs({ parentEl: '.groups-listing', action });
new ShortcutsNavigation(); new ShortcutsNavigation();
new NotificationsForm();
if (gon.features?.vueNotificationDropdown) { initNotificationsDropdown();
initNotificationsDropdown();
} else {
notificationsDropdown();
}
new ProjectsList(); new ProjectsList();
......
import notificationsDropdown from '../../../notifications_dropdown';
import NotificationsForm from '../../../notifications_form';
document.addEventListener('DOMContentLoaded', () => {
new NotificationsForm(); // eslint-disable-line no-new
notificationsDropdown();
});
import initNotificationsDropdown from '~/notifications'; import initNotificationsDropdown from '~/notifications';
import notificationsDropdown from '../../../../notifications_dropdown';
import NotificationsForm from '../../../../notifications_form';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
new NotificationsForm(); // eslint-disable-line no-new
notificationsDropdown();
initNotificationsDropdown(); initNotificationsDropdown();
}); });
...@@ -7,16 +7,13 @@ import initInviteMembersModal from '~/invite_members/init_invite_members_modal'; ...@@ -7,16 +7,13 @@ import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger'; import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import leaveByUrl from '~/namespaces/leave_by_url'; import leaveByUrl from '~/namespaces/leave_by_url';
import initVueNotificationsDropdown from '~/notifications'; import initVueNotificationsDropdown from '~/notifications';
import NotificationsForm from '~/notifications_form';
import initReadMore from '~/read_more'; import initReadMore from '~/read_more';
import UserCallout from '~/user_callout'; import UserCallout from '~/user_callout';
import notificationsDropdown from '../../../notifications_dropdown';
import Star from '../../../star'; import Star from '../../../star';
initReadMore(); initReadMore();
new Star(); // eslint-disable-line no-new new Star(); // eslint-disable-line no-new
new NotificationsForm(); // eslint-disable-line no-new
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new UserCallout({ new UserCallout({
setCalloutPerProject: false, setCalloutPerProject: false,
...@@ -43,12 +40,6 @@ if (document.querySelector('.project-show-activity')) { ...@@ -43,12 +40,6 @@ if (document.querySelector('.project-show-activity')) {
leaveByUrl('project'); leaveByUrl('project');
if (gon.features?.vueNotificationDropdown) {
initVueNotificationsDropdown();
} else {
notificationsDropdown();
}
initVueNotificationsDropdown(); initVueNotificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new new ShortcutsNavigation(); // eslint-disable-line no-new
......
...@@ -30,7 +30,6 @@ class GroupsController < Groups::ApplicationController ...@@ -30,7 +30,6 @@ class GroupsController < Groups::ApplicationController
before_action do before_action do
push_frontend_feature_flag(:vue_issuables_list, @group) push_frontend_feature_flag(:vue_issuables_list, @group)
push_frontend_feature_flag(:vue_notification_dropdown, @group, default_enabled: :yaml)
end end
before_action do before_action do
......
# frozen_string_literal: true
class NotificationSettingsController < ApplicationController
before_action :authenticate_user!
feature_category :users
def create
return render_404 unless can_read?(resource)
@notification_setting = current_user.notification_settings_for(resource)
@saved = @notification_setting.update(notification_setting_params_for(resource))
render_response
end
def update
@notification_setting = current_user.notification_settings.find(params[:id])
@saved = @notification_setting.update(notification_setting_params_for(@notification_setting.source))
render_response
end
private
def resource
@resource ||=
if params[:project_id].present?
Project.find(params[:project_id])
elsif params[:namespace_id].present?
Group.find(params[:namespace_id])
end
end
def can_read?(resource)
ability_name = resource.class.name.downcase
ability_name = "read_#{ability_name}".to_sym
can?(current_user, ability_name, resource)
end
def render_response
btn_class = nil
if params[:hide_label].present?
btn_class = 'btn-xs' if params[:project_id].present?
response_template = 'shared/notifications/_new_button'
else
response_template = 'shared/notifications/_button'
end
render json: {
html: view_to_html_string(response_template, notification_setting: @notification_setting, btn_class: btn_class),
saved: @saved
}
end
def notification_setting_params_for(source)
params.require(:notification_setting).permit(NotificationSetting.allowed_fields(source))
end
end
...@@ -31,10 +31,6 @@ class ProjectsController < Projects::ApplicationController ...@@ -31,10 +31,6 @@ class ProjectsController < Projects::ApplicationController
# Project Export Rate Limit # Project Export Rate Limit
before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export] before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export]
before_action do
push_frontend_feature_flag(:vue_notification_dropdown, @project, default_enabled: :yaml)
end
before_action only: [:edit] do before_action only: [:edit] do
push_frontend_feature_flag(:allow_editing_commit_messages, @project) push_frontend_feature_flag(:allow_editing_commit_messages, @project)
end end
......
...@@ -23,11 +23,8 @@ ...@@ -23,11 +23,8 @@
.home-panel-buttons.col-md-12.col-lg-6 .home-panel-buttons.col-md-12.col-lg-6
- if current_user - if current_user
.gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2{ data: { testid: 'group-buttons' } } .gl-display-flex.gl-flex-wrap.gl-lg-justify-content-end.gl-mx-n2{ data: { testid: 'group-buttons' } }
- if Feature.enabled?(:vue_notification_dropdown, @group, default_enabled: :yaml) - if @notification_setting
- if @notification_setting .js-vue-notification-dropdown{ data: { disabled: emails_disabled.to_s, dropdown_items: notification_dropdown_items(@notification_setting).to_json, notification_level: @notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), group_id: @group.id, container_class: 'gl-mr-3 gl-mt-3 gl-vertical-align-top' } }
.js-vue-notification-dropdown{ data: { disabled: emails_disabled, dropdown_items: notification_dropdown_items(@notification_setting).to_json, notification_level: @notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), group_id: @group.id, container_class: 'gl-mr-3 gl-mt-3 gl-vertical-align-top' } }
- else
= render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn gl-button gl-sm-w-auto gl-w-full', dropdown_container_class: 'gl-mr-0 gl-px-2 gl-sm-w-auto gl-w-full', emails_disabled: emails_disabled
- if can_create_subgroups - if can_create_subgroups
.gl-px-2.gl-sm-w-auto.gl-w-full .gl-px-2.gl-sm-w-auto.gl-w-full
= link_to _("New subgroup"), new_group_path(parent_id: @group.id), class: "btn btn-success btn-md gl-button btn-success-secondary gl-mt-3 gl-sm-w-auto gl-w-full", data: { qa_selector: 'new_subgroup_button' } = link_to _("New subgroup"), new_group_path(parent_id: @group.id), class: "btn btn-success btn-md gl-button btn-success-secondary gl-mt-3 gl-sm-w-auto gl-w-full", data: { qa_selector: 'new_subgroup_button' }
......
...@@ -9,11 +9,8 @@ ...@@ -9,11 +9,8 @@
= 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
- if Feature.enabled?(:vue_notification_dropdown, default_enabled: :yaml) - if setting
- if setting .js-vue-notification-dropdown{ data: { disabled: emails_disabled.to_s, dropdown_items: notification_dropdown_items(setting).to_json, notification_level: setting.level, group_id: group.id, container_class: 'gl-mr-3', show_label: "true" } }
.js-vue-notification-dropdown{ data: { disabled: emails_disabled, dropdown_items: notification_dropdown_items(setting).to_json, notification_level: setting.level, group_id: group.id, container_class: 'gl-mr-3', show_label: "true" } }
- else
= render 'shared/notifications/button', notification_setting: setting, emails_disabled: emails_disabled
.table-section.section-30 .table-section.section-30
= form_for setting, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications gl-display-flex' } do |f| = form_for setting, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications gl-display-flex' } do |f|
......
...@@ -8,8 +8,5 @@ ...@@ -8,8 +8,5 @@
= link_to_project(project) = link_to_project(project)
.float-right .float-right
- if Feature.enabled?(:vue_notification_dropdown, default_enabled: :yaml) - if setting
- if setting .js-vue-notification-dropdown{ data: { disabled: emails_disabled.to_s, dropdown_items: notification_dropdown_items(setting).to_json, notification_level: setting.level, project_id: project.id, container_class: 'gl-mr-3', show_label: "true" } }
.js-vue-notification-dropdown{ data: { disabled: emails_disabled, dropdown_items: notification_dropdown_items(setting).to_json, notification_level: setting.level, project_id: project.id, container_class: 'gl-mr-3', show_label: "true" } }
- else
= render 'shared/notifications/button', notification_setting: setting, emails_disabled: emails_disabled
...@@ -32,11 +32,8 @@ ...@@ -32,11 +32,8 @@
%br %br
.clearfix .clearfix
.form-group.float-left.global-notification-setting .form-group.float-left.global-notification-setting
- if Feature.enabled?(:vue_notification_dropdown, default_enabled: :yaml) - if @global_notification_setting
- if @global_notification_setting .js-vue-notification-dropdown{ data: { dropdown_items: notification_dropdown_items(@global_notification_setting).to_json, notification_level: @global_notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), show_label: 'true' } }
.js-vue-notification-dropdown{ data: { dropdown_items: notification_dropdown_items(@global_notification_setting).to_json, notification_level: @global_notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), show_label: 'true' } }
- else
= render 'shared/notifications/button', notification_setting: @global_notification_setting
.clearfix .clearfix
......
...@@ -46,11 +46,8 @@ ...@@ -46,11 +46,8 @@
.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
- if Feature.enabled?(:vue_notification_dropdown, @project, default_enabled: :yaml) - if @notification_setting
- if @notification_setting .js-vue-notification-dropdown{ data: { button_size: "small", disabled: emails_disabled.to_s, dropdown_items: notification_dropdown_items(@notification_setting).to_json, notification_level: @notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), project_id: @project.id, container_class: 'gl-mr-3 gl-mt-5 gl-vertical-align-top' } }
.js-vue-notification-dropdown{ data: { button_size: "small", disabled: emails_disabled, dropdown_items: notification_dropdown_items(@notification_setting).to_json, notification_level: @notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), project_id: @project.id, container_class: 'gl-mr-3 gl-mt-5 gl-vertical-align-top' } }
- else
= render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs', dropdown_container_class: 'gl-mr-3', emails_disabled: emails_disabled
.count-buttons.d-inline-flex .count-buttons.d-inline-flex
= render 'projects/buttons/star' = render 'projects/buttons/star'
......
- 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)
= f.hidden_field :level, class: "notification_setting_level"
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
%button.dropdown-new.btn.btn-default.btn-icon.gl-button.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' } }
= sprite_icon("notifications", css_class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle.gl-display-flex.gl-align-items-center{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= sprite_icon('chevron-down')
.sr-only Toggle dropdown
- else
%button.dropdown-new.btn.btn-default.btn-icon.gl-button.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
= sprite_icon("notifications", css_class: "js-notification-loading")
= notification_title(notification_setting.level)
.float-right
= sprite_icon("chevron-down")
= render "shared/notifications/notification_dropdown", notification_setting: notification_setting
= content_for :scripts_body do
= render "shared/notifications/custom_notifications", notification_setting: notification_setting
- hide_label = local_assigns.fetch(:hide_label, false)
.modal.fade{ tabindex: "-1", role: "dialog", id: notifications_menu_identifier("modal", notification_setting), "aria-labelledby": "custom-notifications-title" }
.modal-dialog
.modal-content
.modal-header
%h4#custom-notifications-title.modal-title
#{ _('Custom notification events') }
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
.modal-body
.container-fluid
= form_for notification_setting, html: { class: "custom-notifications-form" } do |f|
= hidden_setting_source_input(notification_setting)
= hidden_field_tag("hide_label", true) if hide_label
.row
.col-lg-4
%h4.gl-mt-0= _('Notification events')
%p
- notification_link = link_to _('notification emails'), help_page_path('user/profile/notifications'), target: '_blank'
- paragraph = _('Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}.') % { notification_link: notification_link.html_safe }
#{ paragraph.html_safe }
.col-lg-8
- notification_setting.email_events.each_with_index do |event, index|
- field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]"
.form-group
.form-check{ class: ("gl-mt-0" if index == 0) }
= check_box("notification_setting", event, id: field_id, class: "js-custom-notification-event form-check-input", checked: notification_setting.public_send(event))
%label.form-check-label{ for: field_id }
%strong
= notification_event_name(event)
%span.spinner.is-loading.gl-vertical-align-middle.gl-display-none
= sprite_icon('check', css_class: 'is-done gl-display-none gl-vertical-align-middle gl-text-green-600')
- btn_class = local_assigns.fetch(:btn_class, '')
- dropdown_container_class = local_assigns.fetch(:dropdown_container_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.gl-mt-3.dropdown.inline{ class: dropdown_container_class }
= form_for notification_setting, remote: true, html: { class: "notification-form no-label" } do |f|
= hidden_setting_source_input(notification_setting)
= hidden_field_tag "hide_label", true
= f.hidden_field :level, class: "notification_setting_level"
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
%button.dropdown-new.btn.gl-button.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.gl-button.btn-default.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" }, class: "#{btn_class}" }
= sprite_icon("chevron-down", css_class: "icon mr-0")
.sr-only Toggle dropdown
- else
%button.dropdown-new.btn.gl-button.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("chevron-down", css_class: "icon")
= render "shared/notifications/notification_dropdown", notification_setting: notification_setting
= content_for :scripts_body do
= render "shared/notifications/custom_notifications", notification_setting: notification_setting, hide_label: true
%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting)] }
- NotificationSetting.levels.each_key do |level|
- next if level == "custom"
- next if level == "global" && notification_setting.source.nil?
= notification_list_item(level, notification_setting)
%li.divider
%li
%a.update-notification{ href: "#", role: "button", class: ("is-active" if notification_setting.custom?), data: { toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), notification_level: "custom", notification_title: "Custom" } }
%strong.dropdown-menu-inner-title= s_('NotificationSetting|Custom')
%span.dropdown-menu-inner-content= notification_description("custom")
---
title: Add Vue notifications dropdown component
merge_request: 54422
author:
type: other
---
name: vue_notification_dropdown
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52068
rollout_issue_url:
milestone: '13.8'
type: development
group: group::optimize
default_enabled: false
...@@ -166,9 +166,6 @@ Rails.application.routes.draw do ...@@ -166,9 +166,6 @@ Rails.application.routes.draw do
end end
end end
# Notification settings
resources :notification_settings, only: [:create, :update]
resources :invites, only: [:show], constraints: { id: /[A-Za-z0-9_-]+/ } do resources :invites, only: [:show], constraints: { id: /[A-Za-z0-9_-]+/ } do
member do member do
post :accept post :accept
......
...@@ -8986,9 +8986,6 @@ msgstr "" ...@@ -8986,9 +8986,6 @@ msgstr ""
msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notificationLinkStart} notification emails%{notificationLinkEnd}." msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notificationLinkStart} notification emails%{notificationLinkEnd}."
msgstr "" msgstr ""
msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}."
msgstr ""
msgid "Custom project templates" msgid "Custom project templates"
msgstr "" msgstr ""
...@@ -20636,9 +20633,6 @@ msgstr "" ...@@ -20636,9 +20633,6 @@ msgstr ""
msgid "Notification events" msgid "Notification events"
msgstr "" msgstr ""
msgid "Notification setting"
msgstr ""
msgid "Notification setting - %{notification_title}" msgid "Notification setting - %{notification_title}"
msgstr "" msgstr ""
...@@ -20723,9 +20717,6 @@ msgstr "" ...@@ -20723,9 +20717,6 @@ msgstr ""
msgid "NotificationLevel|Watch" msgid "NotificationLevel|Watch"
msgstr "" msgstr ""
msgid "NotificationSetting|Custom"
msgstr ""
msgid "Notifications" msgid "Notifications"
msgstr "" msgstr ""
...@@ -30042,9 +30033,6 @@ msgstr "" ...@@ -30042,9 +30033,6 @@ msgstr ""
msgid "There was an error saving your changes." msgid "There was an error saving your changes."
msgstr "" msgstr ""
msgid "There was an error saving your notification settings."
msgstr ""
msgid "There was an error subscribing to this label." msgid "There was an error subscribing to this label."
msgstr "" msgstr ""
...@@ -35820,9 +35808,6 @@ msgstr "" ...@@ -35820,9 +35808,6 @@ msgstr ""
msgid "not found" msgid "not found"
msgstr "" msgstr ""
msgid "notification emails"
msgstr ""
msgid "nounSeries|%{firstItem} and %{lastItem}" msgid "nounSeries|%{firstItem} and %{lastItem}"
msgstr "" msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe NotificationSettingsController do
let(:project) { create(:project) }
let(:group) { create(:group, :internal) }
let(:user) { create(:user) }
before do
project.add_developer(user)
end
describe '#create' do
context 'when not authorized' do
it 'redirects to sign in page' do
post :create,
params: {
project_id: project.id,
notification_setting: { level: :participating }
}
expect(response).to redirect_to(new_user_session_path)
end
end
context 'when authorized' do
let(:notification_setting) { user.notification_settings_for(source) }
let(:custom_events) do
events = {}
NotificationSetting.email_events(source).each do |event|
events[event.to_s] = true
end
events
end
before do
sign_in(user)
end
context 'for projects' do
let(:source) { project }
it 'creates notification setting' do
post :create,
params: {
project_id: project.id,
notification_setting: { level: :participating }
}
expect(response).to have_gitlab_http_status(:ok)
expect(notification_setting.level).to eq("participating")
expect(notification_setting.user_id).to eq(user.id)
expect(notification_setting.source_id).to eq(project.id)
expect(notification_setting.source_type).to eq("Project")
end
context 'with custom settings' do
it 'creates notification setting' do
post :create,
params: {
project_id: project.id,
notification_setting: { level: :custom }.merge(custom_events)
}
expect(response).to have_gitlab_http_status(:ok)
expect(notification_setting.level).to eq("custom")
custom_events.each do |event, value|
expect(notification_setting.event_enabled?(event)).to eq(value)
end
end
end
end
context 'for groups' do
let(:source) { group }
it 'creates notification setting' do
post :create,
params: {
namespace_id: group.id,
notification_setting: { level: :watch }
}
expect(response).to have_gitlab_http_status(:ok)
expect(notification_setting.level).to eq("watch")
expect(notification_setting.user_id).to eq(user.id)
expect(notification_setting.source_id).to eq(group.id)
expect(notification_setting.source_type).to eq("Namespace")
end
context 'with custom settings' do
it 'creates notification setting' do
post :create,
params: {
namespace_id: group.id,
notification_setting: { level: :custom }.merge(custom_events)
}
expect(response).to have_gitlab_http_status(:ok)
expect(notification_setting.level).to eq("custom")
custom_events.each do |event, value|
expect(notification_setting.event_enabled?(event)).to eq(value)
end
end
end
end
end
context 'not authorized' do
let(:private_project) { create(:project, :private) }
before do
sign_in(user)
end
it 'returns 404' do
post :create,
params: {
project_id: private_project.id,
notification_setting: { level: :participating }
}
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe '#update' do
let(:notification_setting) { user.global_notification_setting }
context 'when not authorized' do
it 'redirects to sign in page' do
put :update,
params: {
id: notification_setting,
notification_setting: { level: :participating }
}
expect(response).to redirect_to(new_user_session_path)
end
end
context 'when authorized' do
before do
sign_in(user)
end
it 'returns success' do
put :update,
params: {
id: notification_setting,
notification_setting: { level: :participating }
}
expect(response).to have_gitlab_http_status(:ok)
end
context 'and setting custom notification setting' do
let(:custom_events) do
events = {}
notification_setting.email_events.each do |event|
events[event] = "true"
end
end
it 'returns success' do
put :update,
params: {
id: notification_setting,
notification_setting: { level: :participating, events: custom_events }
}
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'not authorized' do
let(:other_user) { create(:user) }
before do
sign_in(other_user)
end
it 'returns 404' do
put :update,
params: {
id: notification_setting,
notification_setting: { level: :participating }
}
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
...@@ -163,7 +163,6 @@ RSpec.describe 'Group show page' do ...@@ -163,7 +163,6 @@ RSpec.describe 'Group show page' do
let!(:project) { create(:project, namespace: group) } let!(:project) { create(:project, namespace: group) }
before do before do
stub_feature_flags(vue_notification_dropdown: false)
group.add_maintainer(maintainer) group.add_maintainer(maintainer)
sign_in(maintainer) sign_in(maintainer)
end end
...@@ -171,14 +170,14 @@ RSpec.describe 'Group show page' do ...@@ -171,14 +170,14 @@ RSpec.describe 'Group show page' do
it 'is enabled by default' do it 'is enabled by default' do
visit path visit path
expect(page).to have_selector('.notifications-btn:not(.disabled)', visible: true) expect(page).to have_selector('[data-testid="notification-button"]:not(.disabled)')
end end
it 'is disabled if emails are disabled' do it 'is disabled if emails are disabled' do
group.update_attribute(:emails_disabled, true) group.update_attribute(:emails_disabled, true)
visit path visit path
expect(page).to have_selector('.notifications-btn.disabled', visible: true) expect(page).to have_selector('[data-testid="notification-button"].disabled')
end end
end end
......
...@@ -7,7 +7,6 @@ RSpec.describe 'User visits the notifications tab', :js do ...@@ -7,7 +7,6 @@ RSpec.describe 'User visits the notifications tab', :js do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
stub_feature_flags(vue_notification_dropdown: false)
project.add_maintainer(user) project.add_maintainer(user)
sign_in(user) sign_in(user)
visit(profile_notifications_path) visit(profile_notifications_path)
...@@ -16,17 +15,17 @@ RSpec.describe 'User visits the notifications tab', :js do ...@@ -16,17 +15,17 @@ RSpec.describe 'User visits the notifications tab', :js do
it 'changes the project notifications setting' do it 'changes the project notifications setting' do
expect(page).to have_content('Notifications') expect(page).to have_content('Notifications')
first('#notifications-button').click first('[data-testid="notification-button"]').click
click_link('On mention') click_button('On mention')
expect(page).to have_selector('#notifications-button', text: 'On mention') expect(page).to have_selector('[data-testid="notification-button"]', text: 'On mention')
end end
context 'when project emails are disabled' do context 'when project emails are disabled' do
let(:project) { create(:project, emails_disabled: true) } let(:project) { create(:project, emails_disabled: true) }
it 'notification button is disabled' do it 'notification button is disabled' do
expect(page).to have_selector('.notifications-btn.disabled', visible: true) expect(page).to have_selector('[data-testid="notification-button"].disabled')
end end
end end
end end
...@@ -6,38 +6,36 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do ...@@ -6,38 +6,36 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
before do before do
stub_feature_flags(vue_notification_dropdown: false)
sign_in(project.owner) sign_in(project.owner)
end end
def click_notifications_button def click_notifications_button
first('.notifications-btn').click first('[data-testid="notification-button"]').click
end end
it 'changes the notification setting' do it 'changes the notification setting' do
visit project_path(project) visit project_path(project)
click_notifications_button click_notifications_button
click_link 'On mention' click_button 'On mention'
page.within('.notification-dropdown') do wait_for_requests
expect(page).not_to have_css('.gl-spinner')
end
click_notifications_button click_notifications_button
expect(find('.update-notification.is-active')).to have_content('On mention')
expect(page).to have_css('.notifications-icon[data-testid="notifications-icon"]') page.within first('[data-testid="notification-button"]') do
expect(page.find('.gl-new-dropdown-item.is-active')).to have_content('On mention')
expect(page).to have_css('[data-testid="notifications-icon"]')
end
end end
it 'changes the notification setting to disabled' do it 'changes the notification setting to disabled' do
visit project_path(project) visit project_path(project)
click_notifications_button click_notifications_button
click_link 'Disabled' click_button 'Disabled'
page.within('.notification-dropdown') do page.within first('[data-testid="notification-button"]') do
expect(page).not_to have_css('.gl-spinner') expect(page).to have_css('[data-testid="notifications-off-icon"]')
end end
expect(page).to have_css('.notifications-icon[data-testid="notifications-off-icon"]')
end end
context 'custom notification settings' do context 'custom notification settings' do
...@@ -65,11 +63,13 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do ...@@ -65,11 +63,13 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do
it 'shows notification settings checkbox' do it 'shows notification settings checkbox' do
visit project_path(project) visit project_path(project)
click_notifications_button click_notifications_button
page.find('a[data-notification-level="custom"]').click click_button 'Custom'
wait_for_requests
page.within('.custom-notifications-form') do page.within('#custom-notifications-modal') do
email_events.each do |event_name| email_events.each do |event_name|
expect(page).to have_selector("input[name='notification_setting[#{event_name}]']") expect(page).to have_selector("[data-testid='notification-setting-#{event_name}']")
end end
end end
end end
...@@ -80,7 +80,7 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do ...@@ -80,7 +80,7 @@ RSpec.describe 'Projects > Show > User manages notifications', :js do
it 'is disabled' do it 'is disabled' do
visit project_path(project) visit project_path(project)
expect(page).to have_selector('.notifications-btn.disabled', visible: true) expect(page).to have_selector('[data-testid="notification-button"].disabled', visible: true)
end end
end end
end end
...@@ -162,7 +162,7 @@ describe('NotificationsDropdown', () => { ...@@ -162,7 +162,7 @@ describe('NotificationsDropdown', () => {
initialNotificationLevel: level, initialNotificationLevel: level,
}); });
const tooltipElement = findByTestId('notificationButton'); const tooltipElement = findByTestId('notification-button');
const tooltip = getBinding(tooltipElement.element, 'gl-tooltip'); const tooltip = getBinding(tooltipElement.element, 'gl-tooltip');
expect(tooltip.value.title).toBe(`${tooltipTitlePrefix} - ${title}`); expect(tooltip.value.title).toBe(`${tooltipTitlePrefix} - ${title}`);
......
...@@ -9,7 +9,6 @@ RSpec.describe 'projects/_home_panel' do ...@@ -9,7 +9,6 @@ RSpec.describe 'projects/_home_panel' do
let(:project) { create(:project) } let(:project) { create(:project) }
before do before do
stub_feature_flags(vue_notification_dropdown: false)
assign(:project, project) assign(:project, project)
allow(view).to receive(:current_user).and_return(user) allow(view).to receive(:current_user).and_return(user)
...@@ -25,11 +24,10 @@ RSpec.describe 'projects/_home_panel' do ...@@ -25,11 +24,10 @@ RSpec.describe 'projects/_home_panel' do
assign(:notification_setting, notification_settings) assign(:notification_setting, notification_settings)
end end
it 'makes it possible to set notification level' do it 'renders Vue app root' do
render render
expect(view).to render_template('shared/notifications/_new_button') expect(rendered).to have_selector('.js-vue-notification-dropdown')
expect(rendered).to have_selector('.notification-dropdown')
end end
end end
...@@ -40,10 +38,10 @@ RSpec.describe 'projects/_home_panel' do ...@@ -40,10 +38,10 @@ RSpec.describe 'projects/_home_panel' do
assign(:notification_setting, nil) assign(:notification_setting, nil)
end end
it 'is not possible to set notification level' do it 'does not render Vue app root' do
render render
expect(rendered).not_to have_selector('.notification_dropdown') expect(rendered).not_to have_selector('.js-vue-notification-dropdown')
end end
end end
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