Commit a3d5343e authored by Douwe Maan's avatar Douwe Maan Committed by Robert Speicher

Merge branch 'issue_12758' into 'master'

Implement custom notification level options

![Screen_Shot_2016-06-17_at_15.31.43](/uploads/3fc47d2f461b3e8b67bb8acaa304cf99/Screen_Shot_2016-06-17_at_15.31.43.png)

![Screenshot_from_2016-06-15_10-52-27](/uploads/88dbdd21d97e80ee772fe08fa0c9b393/Screenshot_from_2016-06-15_10-52-27.png)

part of #12758 

See merge request !4389
parent 0d21f8c3
...@@ -71,6 +71,7 @@ v 8.9.0 (unreleased) ...@@ -71,6 +71,7 @@ v 8.9.0 (unreleased)
- Pipelines can be canceled only when there are running builds - Pipelines can be canceled only when there are running builds
- Allow authentication using personal access tokens - Allow authentication using personal access tokens
- Use downcased path to container repository as this is expected path by Docker - Use downcased path to container repository as this is expected path by Docker
- Custom notification settings
- Projects pending deletion will render a 404 page - Projects pending deletion will render a 404 page
- Measure queue duration between gitlab-workhorse and Rails - Measure queue duration between gitlab-workhorse and Rails
- Added Gfm autocomplete for labels - Added Gfm autocomplete for labels
......
...@@ -78,6 +78,7 @@ class Dispatcher ...@@ -78,6 +78,7 @@ class Dispatcher
when 'projects:show' when 'projects:show'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new NotificationsForm()
new TreeView() if $('#tree-slider').length new TreeView() if $('#tree-slider').length
when 'groups:activity' when 'groups:activity'
new Activities() new Activities()
...@@ -129,6 +130,8 @@ class Dispatcher ...@@ -129,6 +130,8 @@ class Dispatcher
shortcut_handler = new ShortcutsDashboardNavigation() shortcut_handler = new ShortcutsDashboardNavigation()
when 'profiles' when 'profiles'
new Profile() new Profile()
new NotificationsForm()
new NotificationsDropdown()
when 'projects' when 'projects'
new Project() new Project()
new ProjectAvatar() new ProjectAvatar()
...@@ -136,8 +139,12 @@ class Dispatcher ...@@ -136,8 +139,12 @@ class Dispatcher
when 'edit' when 'edit'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ProjectNew() new ProjectNew()
when 'new', 'show' when 'new'
new ProjectNew() new ProjectNew()
when 'show'
new ProjectNew()
new ProjectShow()
new NotificationsDropdown()
when 'wikis' when 'wikis'
new Wikis() new Wikis()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
......
class @NotificationsDropdown
$ ->
$(document)
.off 'click', '.update-notification'
.on 'click', '.update-notification', (e) ->
e.preventDefault()
return if $(this).is('.is-active') and $(this).data('notification-level') is 'custom'
notificationLevel = $(@).data 'notification-level'
label = $(@).data 'notification-title'
form = $(this).parents('.notification-form:first')
form.find('.js-notification-loading').toggleClass 'fa-bell fa-spin fa-spinner'
form.find('#notification_setting_level').val(notificationLevel)
form.submit()
$(document)
.off 'ajax:success', '.notification-form'
.on 'ajax:success', '.notification-form', (e, data) ->
if data.saved
new Flash('Notification settings saved', 'notice')
$(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html)
else
new Flash('Failed to save new settings', 'alert')
class @NotificationsForm
constructor: ->
@removeEventListeners()
@initEventListeners()
removeEventListeners: ->
$(document).off 'change', '.js-custom-notification-event'
initEventListeners: ->
$(document).on 'change', '.js-custom-notification-event', @toggleCheckbox
toggleCheckbox: (e) =>
$checkbox = $(e.currentTarget)
$parent = $checkbox.closest('.checkbox')
@saveEvent($checkbox, $parent)
showCheckboxLoadingSpinner: ($parent) ->
$parent
.addClass 'is-loading'
.find '.custom-notification-event-loading'
.removeClass 'fa-check'
.addClass 'fa-spin fa-spinner'
.removeClass 'is-done'
saveEvent: ($checkbox, $parent) ->
form = $parent.parents('form:first')
$.ajax(
url: form.attr('action')
method: form.attr('method')
dataType: 'json'
data: form.serialize()
beforeSend: =>
@showCheckboxLoadingSpinner($parent)
).done (data) ->
$checkbox.enable()
if data.saved
$parent
.find '.custom-notification-event-loading'
.toggleClass 'fa-spin fa-spinner fa-check is-done'
setTimeout(->
$parent
.removeClass 'is-loading'
.find '.custom-notification-event-loading'
.toggleClass 'fa-spin fa-spinner fa-check is-done'
, 2000)
...@@ -8,6 +8,10 @@ class @Profile ...@@ -8,6 +8,10 @@ class @Profile
$('.js-preferences-form').on 'change.preference', 'input[type=radio]', -> $('.js-preferences-form').on 'change.preference', 'input[type=radio]', ->
$(this).parents('form').submit() $(this).parents('form').submit()
# Automatically submit email form when it changes
$('#user_notification_email').on 'change', ->
$(this).parents('form').submit()
$('.update-username').on 'ajax:before', -> $('.update-username').on 'ajax:before', ->
$('.loading-username').show() $('.loading-username').show()
$(this).find('.update-success').hide() $(this).find('.update-success').hide()
......
...@@ -34,22 +34,6 @@ class @Project ...@@ -34,22 +34,6 @@ class @Project
$(@).parents('.no-password-message').remove() $(@).parents('.no-password-message').remove()
e.preventDefault() e.preventDefault()
$('.update-notification').on 'click', (e) ->
e.preventDefault()
notification_level = $(@).data 'notification-level'
label = $(@).data 'notification-title'
$('#notification_setting_level').val(notification_level)
$('#notification-form').submit()
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active'
$('#notification-form').on 'ajax:success', (e, data) ->
if data.saved
new Flash("Notification settings saved", "notice")
else
new Flash("Failed to save new settings", "alert")
@projectSelectDropdown() @projectSelectDropdown()
......
...@@ -133,11 +133,6 @@ ...@@ -133,11 +133,6 @@
} }
} }
.btn-group:not(:first-child):not(:last-child) > .btn {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
form { form {
margin-left: 10px; margin-left: 10px;
} }
...@@ -607,3 +602,20 @@ pre.light-well { ...@@ -607,3 +602,20 @@ pre.light-well {
} }
} }
} }
.custom-notifications-form {
.is-loading {
.custom-notification-event-loading {
display: inline-block;
}
}
}
.custom-notification-event-loading {
display: none;
margin-left: 5px;
&.is-done {
color: $gl-text-green;
}
}
class Groups::NotificationSettingsController < Groups::ApplicationController
before_action :authenticate_user!
def update
notification_setting = current_user.notification_settings_for(group)
saved = notification_setting.update_attributes(notification_setting_params)
render json: { saved: saved }
end
private
def notification_setting_params
params.require(:notification_setting).permit(:level)
end
end
class NotificationSettingsController < ApplicationController
before_action :authenticate_user!
def create
project = Project.find(params[:project][:id])
return render_404 unless can?(current_user, :read_project, project)
@notification_setting = current_user.notification_settings_for(project)
@saved = @notification_setting.update_attributes(notification_setting_params)
render_response
end
def update
@notification_setting = current_user.notification_settings.find(params[:id])
@saved = @notification_setting.update_attributes(notification_setting_params)
render_response
end
private
def render_response
render json: {
html: view_to_html_string("shared/notifications/_button", notification_setting: @notification_setting),
saved: @saved
}
end
def notification_setting_params
allowed_fields = NotificationSetting::EMAIL_EVENTS.dup
allowed_fields << :level
params.require(:notification_setting).permit(allowed_fields)
end
end
class Profiles::NotificationsController < Profiles::ApplicationController class Profiles::NotificationsController < Profiles::ApplicationController
def show def show
@user = current_user @user = current_user
@group_notifications = current_user.notification_settings.for_groups @group_notifications = current_user.notification_settings.for_groups.order(:id)
@project_notifications = current_user.notification_settings.for_projects @project_notifications = current_user.notification_settings.for_projects.order(:id)
@global_notification_setting = current_user.global_notification_setting @global_notification_setting = current_user.global_notification_setting
end end
def update def update
if current_user.update_attributes(user_params) && update_notification_settings if current_user.update_attributes(user_params)
flash[:notice] = "Notification settings saved" flash[:notice] = "Notification settings saved"
else else
flash[:alert] = "Failed to save new settings" flash[:alert] = "Failed to save new settings"
...@@ -19,16 +19,4 @@ class Profiles::NotificationsController < Profiles::ApplicationController ...@@ -19,16 +19,4 @@ class Profiles::NotificationsController < Profiles::ApplicationController
def user_params def user_params
params.require(:user).permit(:notification_email) params.require(:user).permit(:notification_email)
end end
def global_notification_setting_params
params.require(:global_notification_setting).permit(:level)
end
private
def update_notification_settings
return true unless global_notification_setting_params
current_user.global_notification_setting.update_attributes(global_notification_setting_params)
end
end end
class Projects::NotificationSettingsController < Projects::ApplicationController
before_action :authenticate_user!
def update
notification_setting = current_user.notification_settings_for(project)
saved = notification_setting.update_attributes(notification_setting_params)
render json: { saved: saved }
end
private
def notification_setting_params
params.require(:notification_setting).permit(:level)
end
end
...@@ -34,7 +34,7 @@ module NotificationsHelper ...@@ -34,7 +34,7 @@ module NotificationsHelper
def notification_description(level) def notification_description(level)
case level.to_sym case level.to_sym
when :participating when :participating
'You will only receive notifications from related resources' 'You will only receive notifications for threads you have participated in'
when :mention when :mention
'You will receive notifications only for comments in which you were @mentioned' 'You will receive notifications only for comments in which you were @mentioned'
when :watch when :watch
...@@ -43,6 +43,8 @@ module NotificationsHelper ...@@ -43,6 +43,8 @@ module NotificationsHelper
'You will not get any notifications via email' 'You will not get any notifications via email'
when :global when :global
'Use your global notification setting' 'Use your global notification setting'
when :custom
'You will only receive notifications for the events you choose'
end end
end end
...@@ -62,22 +64,14 @@ module NotificationsHelper ...@@ -62,22 +64,14 @@ module NotificationsHelper
end end
end end
def notification_level_radio_buttons # Identifier to trigger individually dropdowns and custom settings modals in the same view
html = "" def notifications_menu_identifier(type, notification_setting)
"#{type}-#{notification_setting.user_id}-#{notification_setting.source_id}-#{notification_setting.source_type}"
NotificationSetting.levels.each_key do |level| end
level = level.to_sym
next if level == :global
html << content_tag(:div, class: "radio") do
content_tag(:label, { value: level }) do
radio_button_tag(:"global_notification_setting[level]", level, @global_notification_setting.level.to_sym == level) +
content_tag(:div, level.to_s.capitalize, class: "level-title") +
content_tag(:p, notification_description(level))
end
end
end
html.html_safe # Create hidden field to send notification setting source to controller
def hidden_setting_source_input(notification_setting)
return unless notification_setting.source_type
hidden_field_tag "#{notification_setting.source_type.downcase}[id]", notification_setting.source_id
end end
end end
class NotificationSetting < ActiveRecord::Base class NotificationSetting < ActiveRecord::Base
enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0 } enum level: { global: 3, watch: 2, mention: 4, participating: 1, disabled: 0, custom: 5 }
default_value_for :level, NotificationSetting.levels[:global] default_value_for :level, NotificationSetting.levels[:global]
...@@ -15,6 +15,24 @@ class NotificationSetting < ActiveRecord::Base ...@@ -15,6 +15,24 @@ class NotificationSetting < ActiveRecord::Base
scope :for_groups, -> { where(source_type: 'Namespace') } scope :for_groups, -> { where(source_type: 'Namespace') }
scope :for_projects, -> { where(source_type: 'Project') } scope :for_projects, -> { where(source_type: 'Project') }
EMAIL_EVENTS = [
:new_note,
:new_issue,
:reopen_issue,
:close_issue,
:reassign_issue,
:new_merge_request,
:reopen_merge_request,
:close_merge_request,
:reassign_merge_request,
:merge_merge_request
]
store :events, accessors: EMAIL_EVENTS, coder: JSON
before_create :set_events
before_save :events_to_boolean
def self.find_or_create_for(source) def self.find_or_create_for(source)
setting = find_or_initialize_by(source: source) setting = find_or_initialize_by(source: source)
...@@ -24,4 +42,21 @@ class NotificationSetting < ActiveRecord::Base ...@@ -24,4 +42,21 @@ class NotificationSetting < ActiveRecord::Base
setting setting
end end
# Set all event attributes to false when level is not custom or being initialized for UX reasons
def set_events
return if custom?
EMAIL_EVENTS.each do |event|
events[event] = false
end
end
# Validates store accessors values as boolean
# It is a text field so it does not cast correct boolean values in JSON
def events_to_boolean
EMAIL_EVENTS.each do |event|
events[event] = ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(events[event])
end
end
end end
This diff is collapsed.
...@@ -9,5 +9,4 @@ ...@@ -9,5 +9,4 @@
= link_to group.name, group_path(group) = link_to group.name, group_path(group)
.pull-right .pull-right
= form_for [group, setting], remote: true, html: { class: 'update-notifications' } do |f| = render 'shared/notifications/button', notification_setting: setting
= f.select :level, NotificationSetting.levels.keys, {}, class: 'form-control trigger-submit'
...@@ -9,5 +9,4 @@ ...@@ -9,5 +9,4 @@
= link_to_project(project) = link_to_project(project)
.pull-right .pull-right
= form_for [project.namespace.becomes(Namespace), project, setting], remote: true, html: { class: 'update-notifications' } do |f| = render 'shared/notifications/button', notification_setting: setting
= f.select :level, NotificationSetting.levels.keys, {}, class: 'form-control trigger-submit'
...@@ -24,12 +24,15 @@ ...@@ -24,12 +24,15 @@
.form-group .form-group
= f.label :notification_email, class: "label-light" = f.label :notification_email, class: "label-light"
= f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2" = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
.form-group
= f.label :notification_level, class: 'label-light'
= notification_level_radio_buttons
.prepend-top-default = label_tag :global_notification_level, "Global notification level", class: "label-light"
= f.submit 'Update settings', class: "btn btn-create" %br
.clearfix
.form-group.pull-left
= render 'shared/notifications/button', notification_setting: @global_notification_setting, left_align: true
.clearfix
%hr %hr
%h5 %h5
Groups (#{@group_notifications.count}) Groups (#{@group_notifications.count})
......
...@@ -29,13 +29,13 @@ ...@@ -29,13 +29,13 @@
.project-clone-holder .project-clone-holder
= render "shared/clone_panel" = render "shared/clone_panel"
.project-repo-buttons.project-right-buttons .project-repo-buttons.btn-group.project-right-buttons
- if current_user - if current_user
= render 'shared/members/access_request_buttons', source: @project = render 'shared/members/access_request_buttons', source: @project
.btn-group
= render "projects/buttons/download" = render "projects/buttons/download"
= render 'projects/buttons/dropdown' = render 'projects/buttons/dropdown'
= render 'projects/buttons/notifications' = render 'shared/notifications/button', notification_setting: @notification_setting
:javascript :javascript
new Star(); new Star();
- if @notification_setting
= form_for @notification_setting, url: namespace_project_notification_setting_path(@project.namespace.becomes(Namespace), @project), method: :patch, remote: true, html: { class: 'inline', id: 'notification-form' } do |f|
= f.hidden_field :level
.dropdown.hidden-sm
%button.btn.btn-default.notifications-btn#notifications-button{ data: { toggle: "dropdown" }, aria: { haspopup: "true", expanded: "false" } }
= icon('bell')
= notification_title(@notification_setting.level)
= icon('caret-down')
%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-align-right.dropdown-menu-selectable.dropdown-menu-large{ role: "menu" }
- NotificationSetting.levels.each do |level|
= notification_list_item(level.first, @notification_setting)
- left_align = local_assigns[:left_align]
- if notification_setting
.dropdown.notification-dropdown.pull-right
= 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.notifications-btn#notifications-button{ type: "button", data: { toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting) } }
= 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) } }
%span.caret
.sr-only Toggle dropdown
- else
%button.dropdown-new.btn.btn-default.notifications-btn#notifications-button{ type: "button", data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting) } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
= icon("caret-down")
= render "shared/notifications/notification_dropdown", notification_setting: notification_setting, left_align: left_align
= content_for :scripts_body do
= render "shared/notifications/custom_notifications", notification_setting: notification_setting
.modal.fade{ tabindex: "-1", role: "dialog", id: notifications_menu_identifier("modal", notification_setting), aria: { labelledby: "custom-notifications-title" } }
.modal-dialog
.modal-content
.modal-header
%button.close{ type: "button", data: { dismiss: "modal" }, aria: { label: "close" } }
%span{ aria: { hidden: "true" } } ×
%h4#custom-notifications-title.modal-title
Custom notification events
.modal-body
.container-fluid
= form_for notification_setting, html: { class: "custom-notifications-form" } do |f|
= hidden_setting_source_input(notification_setting)
.row
.col-lg-4
%h4.prepend-top-0
Notification events
%p
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
= succeed "." do
%a{ href: "http://docs.gitlab.com/ce/workflow/notifications.html", target: "_blank"} notification emails
.col-lg-8
- NotificationSetting::EMAIL_EVENTS.each_with_index do |event, index|
- field_id = "#{notifications_menu_identifier("modal", notification_setting)}_notification_setting[#{event}]"
.form-group
.checkbox{ class: ("prepend-top-0" if index == 0) }
%label{ for: field_id }
= check_box("notification_setting", event, id: field_id, class: "js-custom-notification-event", checked: notification_setting.events[event])
%strong
= event.to_s.humanize
= icon("spinner spin", class: "custom-notification-event-loading")
- left_align = local_assigns[:left_align]
%ul.dropdown-menu.dropdown-menu-no-wrap.dropdown-menu-selectable.dropdown-menu-large{ role: "menu", class: [notifications_menu_identifier("dropdown", notification_setting), ("dropdown-menu-align-right" unless left_align)] }
- 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 Custom
%span.dropdown-menu-inner-content= notification_description("custom")
...@@ -123,9 +123,17 @@ Rails.application.routes.draw do ...@@ -123,9 +123,17 @@ Rails.application.routes.draw do
end end
end end
#
# Spam reports # Spam reports
#
resources :abuse_reports, only: [:new, :create] resources :abuse_reports, only: [:new, :create]
#
# Notification settings
#
resources :notification_settings, only: [:create, :update]
# #
# Import # Import
# #
...@@ -432,7 +440,6 @@ Rails.application.routes.draw do ...@@ -432,7 +440,6 @@ Rails.application.routes.draw do
resource :avatar, only: [:destroy] resource :avatar, only: [:destroy]
resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create] resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :update, :new, :create]
resource :notification_setting, only: [:update]
end end
end end
...@@ -668,7 +675,6 @@ Rails.application.routes.draw do ...@@ -668,7 +675,6 @@ Rails.application.routes.draw do
resources :forks, only: [:index, :new, :create] resources :forks, only: [:index, :new, :create]
resource :import, only: [:new, :create, :show] resource :import, only: [:new, :create, :show]
resource :notification_setting, only: [:update]
resources :refs, only: [] do resources :refs, only: [] do
collection do collection do
......
class AddEventsToNotificationSettings < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
def change
add_column :notification_settings, :events, :text
end
end
...@@ -4,7 +4,7 @@ GitLab has a notification system in place to notify a user of events that are im ...@@ -4,7 +4,7 @@ GitLab has a notification system in place to notify a user of events that are im
## Notification settings ## Notification settings
Under user profile page you can find the notification settings. You can find notification settings under the user profile.
![notification settings](notifications/settings.png) ![notification settings](notifications/settings.png)
...@@ -20,6 +20,7 @@ Each of these settings have levels of notification: ...@@ -20,6 +20,7 @@ Each of these settings have levels of notification:
* Participating - receive notifications from related resources * Participating - receive notifications from related resources
* Watch - receive notifications from projects or groups user is a member of * Watch - receive notifications from projects or groups user is a member of
* Global - notifications as set at the global settings * Global - notifications as set at the global settings
* Custom - user will receive notifications when mentioned, is participant and custom selected events.
#### Global Settings #### Global Settings
...@@ -55,7 +56,7 @@ Below is the table of events users can be notified of: ...@@ -55,7 +56,7 @@ Below is the table of events users can be notified of:
| User added to project | User | Sent when user is added to project | | User added to project | User | Sent when user is added to project |
| Project access level changed | User | Sent when user project access level is changed | | Project access level changed | User | Sent when user project access level is changed |
| User added to group | User | Sent when user is added to group | | User added to group | User | Sent when user is added to group |
| Group access level changed | User | Sent when user group access level is changed | | Group access level changed | User | Sent when user group access level is changed |
| Project moved | Project members [1] | [1] not disabled | | Project moved | Project members [1] | [1] not disabled |
### Issue / Merge Request events ### Issue / Merge Request events
...@@ -71,6 +72,7 @@ In all of the below cases, the notification will be sent to: ...@@ -71,6 +72,7 @@ In all of the below cases, the notification will be sent to:
- Watchers: users with notification level "Watch" - Watchers: users with notification level "Watch"
- Subscribers: anyone who manually subscribed to the issue/merge request - Subscribers: anyone who manually subscribed to the issue/merge request
- Custom: Users with notification level "custom" who turned on notifications for any of the events present in the table below
| Event | Sent to | | Event | Sent to |
|------------------------|---------| |------------------------|---------|
......
...@@ -11,7 +11,7 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps ...@@ -11,7 +11,7 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps
end end
step 'I select Mention setting from dropdown' do step 'I select Mention setting from dropdown' do
select 'mention', from: 'notification_setting_level' first(:link, "On mention").trigger('click')
end end
step 'I should see Notification saved message' do step 'I should see Notification saved message' do
......
...@@ -126,7 +126,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps ...@@ -126,7 +126,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end end
step 'I click notifications drop down button' do step 'I click notifications drop down button' do
find('#notifications-button').click first('.notifications-btn').click
end end
step 'I choose Mention setting' do step 'I choose Mention setting' do
......
require 'spec_helper'
describe Groups::NotificationSettingsController do
let(:group) { create(:group) }
let(:user) { create(:user) }
describe '#update' do
context 'when not authorized' do
it 'redirects to sign in page' do
put :update,
group_id: group.to_param,
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,
group_id: group.to_param,
notification_setting: { level: :participating }
expect(response.status).to eq 200
end
end
end
end
require 'spec_helper' require 'spec_helper'
describe Projects::NotificationSettingsController do describe NotificationSettingsController do
let(:project) { create(:empty_project) } let(:project) { create(:empty_project) }
let(:user) { create(:user) } let(:user) { create(:user) }
...@@ -8,13 +8,12 @@ describe Projects::NotificationSettingsController do ...@@ -8,13 +8,12 @@ describe Projects::NotificationSettingsController do
project.team << [user, :developer] project.team << [user, :developer]
end end
describe '#update' do describe '#create' do
context 'when not authorized' do context 'when not authorized' do
it 'redirects to sign in page' do it 'redirects to sign in page' do
put :update, post :create,
namespace_id: project.namespace.to_param, project: { id: project.id },
project_id: project.to_param, notification_setting: { level: :participating }
notification_setting: { level: :participating }
expect(response).to redirect_to(new_user_session_path) expect(response).to redirect_to(new_user_session_path)
end end
...@@ -26,23 +25,97 @@ describe Projects::NotificationSettingsController do ...@@ -26,23 +25,97 @@ describe Projects::NotificationSettingsController do
end end
it 'returns success' do it 'returns success' do
put :update, post :create,
namespace_id: project.namespace.to_param, project: { id: project.id },
project_id: project.to_param, notification_setting: { level: :participating }
notification_setting: { level: :participating }
expect(response.status).to eq 200 expect(response.status).to eq 200
end end
context 'and setting custom notification setting' do
let(:custom_events) do
events = {}
NotificationSetting::EMAIL_EVENTS.each do |event|
events[event] = "true"
end
end
it 'returns success' do
post :create,
project: { id: project.id },
notification_setting: { level: :participating, events: custom_events }
expect(response.status).to eq 200
end
end
end end
context 'not authorized' do context 'not authorized' do
let(:private_project) { create(:project, :private) } let(:private_project) { create(:project, :private) }
before { sign_in(user) } before { sign_in(user) }
it 'returns 404' do
post :create,
project: { id: private_project.id },
notification_setting: { level: :participating }
expect(response.status).to eq(404)
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,
id: notification_setting,
notification_setting: { level: :participating }
expect(response).to redirect_to(new_user_session_path)
end
end
context 'when authorized' do
before{ sign_in(user) }
it 'returns success' do
put :update,
id: notification_setting,
notification_setting: { level: :participating }
expect(response.status).to eq 200
end
context 'and setting custom notification setting' do
let(:custom_events) do
events = {}
NotificationSetting::EMAIL_EVENTS.each do |event|
events[event] = "true"
end
end
it 'returns success' do
put :update,
id: notification_setting,
notification_setting: { level: :participating, events: custom_events }
expect(response.status).to eq 200
end
end
end
context 'not authorized' do
let(:other_user) { create(:user) }
before { sign_in(other_user) }
it 'returns 404' do it 'returns 404' do
put :update, put :update,
namespace_id: private_project.namespace.to_param, id: notification_setting,
project_id: private_project.to_param,
notification_setting: { level: :participating } notification_setting: { level: :participating }
expect(response.status).to eq(404) expect(response.status).to eq(404)
......
...@@ -12,5 +12,30 @@ RSpec.describe NotificationSetting, type: :model do ...@@ -12,5 +12,30 @@ RSpec.describe NotificationSetting, type: :model do
it { is_expected.to validate_presence_of(:user) } it { is_expected.to validate_presence_of(:user) }
it { is_expected.to validate_presence_of(:level) } it { is_expected.to validate_presence_of(:level) }
it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) } it { is_expected.to validate_uniqueness_of(:user_id).scoped_to([:source_id, :source_type]).with_message(/already exists in source/) }
context "events" do
let(:user) { create(:user) }
let(:notification_setting) { NotificationSetting.new(source_id: 1, source_type: 'Project', user_id: user.id) }
before do
notification_setting.level = "custom"
notification_setting.new_note = "true"
notification_setting.new_issue = 1
notification_setting.close_issue = "1"
notification_setting.merge_merge_request = "t"
notification_setting.close_merge_request = "nil"
notification_setting.reopen_merge_request = "false"
notification_setting.save
end
it "parses boolean before saving" do
expect(notification_setting.new_note).to eq(true)
expect(notification_setting.new_issue).to eq(true)
expect(notification_setting.close_issue).to eq(true)
expect(notification_setting.merge_merge_request).to eq(true)
expect(notification_setting.close_merge_request).to eq(false)
expect(notification_setting.reopen_merge_request).to eq(false)
end
end
end end
end end
...@@ -428,8 +428,9 @@ describe API::API, api: true do ...@@ -428,8 +428,9 @@ describe API::API, api: true do
describe 'permissions' do describe 'permissions' do
context 'all projects' do context 'all projects' do
it 'Contains permission information' do before { project.team << [user, :master] }
project.team << [user, :master]
it 'contains permission information' do
get api("/projects", user) get api("/projects", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
...@@ -440,7 +441,7 @@ describe API::API, api: true do ...@@ -440,7 +441,7 @@ describe API::API, api: true do
end end
context 'personal project' do context 'personal project' do
it 'Sets project access and returns 200' do it 'sets project access and returns 200' do
project.team << [user, :master] project.team << [user, :master]
get api("/projects/#{project.id}", user) get api("/projects/#{project.id}", user)
...@@ -452,9 +453,11 @@ describe API::API, api: true do ...@@ -452,9 +453,11 @@ describe API::API, api: true do
end end
context 'group project' do context 'group project' do
let(:project2) { create(:project, group: create(:group)) }
before { project2.group.add_owner(user) }
it 'should set the owner and return 200' do it 'should set the owner and return 200' do
project2 = create(:project, group: create(:group))
project2.group.add_owner(user)
get api("/projects/#{project2.id}", user) get api("/projects/#{project2.id}", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
......
This diff is collapsed.
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