Commit 25856a47 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

save each notification setting with ajax on change

parent 3c3baf8f
...@@ -20,3 +20,16 @@ ...@@ -20,3 +20,16 @@
border: 1px solid #ddd; border: 1px solid #ddd;
} }
} }
.save-status-fixed {
position: fixed;
left: 20px;
bottom: 50px;
}
.update-notifications {
margin-bottom: 0;
label {
margin-bottom: 0;
}
}
...@@ -3,11 +3,19 @@ class NotificationsController < ApplicationController ...@@ -3,11 +3,19 @@ class NotificationsController < ApplicationController
def show def show
@notification = current_user.notification @notification = current_user.notification
@projects = current_user.authorized_projects @users_projects = current_user.users_projects
end end
def update def update
current_user.notification_level = params[:notification_level] type = params[:notification_type]
@saved = current_user.save
@saved = if type == 'global'
current_user.notification_level = params[:notification_level]
current_user.save
else
users_project = current_user.users_projects.find(params[:notification_id])
users_project.notification_level = params[:notification_level]
users_project.save
end
end end
end end
...@@ -5,26 +5,35 @@ class Notification ...@@ -5,26 +5,35 @@ class Notification
N_DISABLED = 0 N_DISABLED = 0
N_PARTICIPATING = 1 N_PARTICIPATING = 1
N_WATCH = 2 N_WATCH = 2
N_GLOBAL = 3
attr_accessor :user attr_accessor :target
def self.notification_levels def self.notification_levels
[N_DISABLED, N_PARTICIPATING, N_WATCH] [N_DISABLED, N_PARTICIPATING, N_WATCH]
end end
def initialize(user) def self.project_notification_levels
@user = user [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL]
end
def initialize(target)
@target = target
end end
def disabled? def disabled?
user.notification_level == N_DISABLED target.notification_level == N_DISABLED
end end
def participating? def participating?
user.notification_level == N_PARTICIPATING target.notification_level == N_PARTICIPATING
end end
def watch? def watch?
user.notification_level == N_WATCH target.notification_level == N_WATCH
end
def global?
target.notification_level == N_GLOBAL
end end
end end
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
# issues_tracker :string(255) default("gitlab"), not null # issues_tracker :string(255) default("gitlab"), not null
# issues_tracker_id :string(255) # issues_tracker_id :string(255)
# snippets_enabled :boolean default(TRUE), not null # snippets_enabled :boolean default(TRUE), not null
# last_activity_at :datetime
# #
require "grit" require "grit"
......
...@@ -2,12 +2,13 @@ ...@@ -2,12 +2,13 @@
# #
# Table name: users_projects # Table name: users_projects
# #
# id :integer not null, primary key # id :integer not null, primary key
# user_id :integer not null # user_id :integer not null
# project_id :integer not null # project_id :integer not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# project_access :integer default(0), not null # project_access :integer default(0), not null
# notification_level :integer default(3), not null
# #
class UsersProject < ActiveRecord::Base class UsersProject < ActiveRecord::Base
...@@ -29,6 +30,7 @@ class UsersProject < ActiveRecord::Base ...@@ -29,6 +30,7 @@ class UsersProject < ActiveRecord::Base
validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" } validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" }
validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true
validates :project, presence: true validates :project, presence: true
validates :notification_level, inclusion: { in: Notification.project_notification_levels }, presence: true
delegate :name, :username, :email, to: :user, prefix: true delegate :name, :username, :email, to: :user, prefix: true
...@@ -134,4 +136,8 @@ class UsersProject < ActiveRecord::Base ...@@ -134,4 +136,8 @@ class UsersProject < ActiveRecord::Base
def skip_git? def skip_git?
!!@skip_git !!@skip_git
end end
def notification
@notification ||= Notification.new(self)
end
end end
...@@ -80,7 +80,7 @@ class NotificationService ...@@ -80,7 +80,7 @@ class NotificationService
# * project team members with notification level higher then Participating # * project team members with notification level higher then Participating
# #
def merge_mr(merge_request) def merge_mr(merge_request)
recipients = reject_muted_users([merge_request.author, merge_request.assignee]) recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.project)
recipients = recipients.concat(project_watchers(merge_request.project)).uniq recipients = recipients.concat(project_watchers(merge_request.project)).uniq
recipients.each do |recipient| recipients.each do |recipient|
...@@ -122,7 +122,7 @@ class NotificationService ...@@ -122,7 +122,7 @@ class NotificationService
recipients = recipients.concat(project_watchers(note.project)).compact.uniq recipients = recipients.concat(project_watchers(note.project)).compact.uniq
# Reject mutes users # Reject mutes users
recipients = reject_muted_users(recipients) recipients = reject_muted_users(recipients, note.project)
# Reject author # Reject author
recipients.delete(note.author) recipients.delete(note.author)
...@@ -147,19 +147,41 @@ class NotificationService ...@@ -147,19 +147,41 @@ class NotificationService
# Get project users with WATCH notification level # Get project users with WATCH notification level
def project_watchers(project) def project_watchers(project)
project.users.where(notification_level: Notification::N_WATCH)
# Get project notification settings since it has higher priority
user_ids = project.users_projects.where(notification_level: Notification::N_WATCH).pluck(:user_id)
project_watchers = User.where(id: user_ids)
# next collect users who use global settings with watch state
user_ids = project.users_projects.where(notification_level: Notification::N_GLOBAL).pluck(:user_id)
project_watchers += User.where(id: user_ids, notification_level: Notification::N_WATCH)
project_watchers.uniq
end end
# Remove users with disabled notifications from array # Remove users with disabled notifications from array
# Also remove duplications and nil recipients # Also remove duplications and nil recipients
def reject_muted_users(users) def reject_muted_users(users, project = nil)
users.compact.uniq.reject do |user| users = users.compact.uniq
user.notification.disabled?
users.reject do |user|
next user.notification.disabled? unless project
tm = project.users_projects.find_by_user_id(user.id)
# reject users who globally disabled notification and has no membership
next user.notification.disabled? unless tm
# reject users who disabled notification in project
next true if tm.notification.disabled?
# reject users who have N_GLOBAL in project and disabled in global settings
tm.notification.global? && user.notification.disabled?
end end
end end
def new_resource_email(target, method) def new_resource_email(target, method)
recipients = reject_muted_users([target.assignee]) recipients = reject_muted_users([target.assignee], target.project)
recipients = recipients.concat(project_watchers(target.project)).uniq recipients = recipients.concat(project_watchers(target.project)).uniq
recipients.delete(target.author) recipients.delete(target.author)
...@@ -169,7 +191,7 @@ class NotificationService ...@@ -169,7 +191,7 @@ class NotificationService
end end
def close_resource_email(target, current_user, method) def close_resource_email(target, current_user, method)
recipients = reject_muted_users([target.author, target.assignee]) recipients = reject_muted_users([target.author, target.assignee], target.project)
recipients = recipients.concat(project_watchers(target.project)).uniq recipients = recipients.concat(project_watchers(target.project)).uniq
recipients.delete(current_user) recipients.delete(current_user)
...@@ -185,7 +207,7 @@ class NotificationService ...@@ -185,7 +207,7 @@ class NotificationService
recipients = recipients.concat(project_watchers(target.project)) recipients = recipients.concat(project_watchers(target.project))
# reject users with disabled notifications # reject users with disabled notifications
recipients = reject_muted_users(recipients) recipients = reject_muted_users(recipients, target.project)
# Reject me from recipients if I reassign an item # Reject me from recipients if I reassign an item
recipients.delete(current_user) recipients.delete(current_user)
......
...@@ -13,56 +13,65 @@ ...@@ -13,56 +13,65 @@
&ndash; You will receive all notifications from projects in which you participate &ndash; You will receive all notifications from projects in which you participate
%hr %hr
= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do .row
%ul.well-list .span4
%h5 Global
.span7
= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
= hidden_field_tag :notification_type, 'global'
= label_tag do
= radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit'
%span Disabled
= label_tag do
= radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit'
%span Participating
= label_tag do
= radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit'
%span Watch
%hr
= link_to '#', class: 'js-toggle-visibility-link' do
%h6.btn.btn-tiny
%i.icon-chevron-down
%span Per project notifications settings
%ul.well-list.js-toggle-visibility-container.hide
- @users_projects.each do |users_project|
- notification = Notification.new(users_project)
%li %li
.row .row
.span4 .span4
%h5 Global %span
= link_to_project(users_project.project)
.span7 .span7
= label_tag do = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
= radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled? = hidden_field_tag :notification_type, 'project', id: dom_id(users_project, 'notification_type')
%span Disabled = hidden_field_tag :notification_id, users_project.id, id: dom_id(users_project, 'notification_id')
= label_tag do
= radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?
%span Participating
= label_tag do
= radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?
%span Watch
= label_tag do
= radio_button_tag :notification_level, Notification::N_GLOBAL, notification.global?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit'
%span Use global settings
= link_to '#', class: 'js-toggle-visibility-link' do
%h6.btn.btn-tiny
%i.icon-chevron-down
%span Per project notifications settings
%ul.well-list.js-toggle-visibility-container.hide
- @projects.each do |project|
%li
.row
.span4
%span
= project.name_with_namespace
.span7
= label_tag do = label_tag do
= radio_button_tag :"notification_level[#{project.id}]", Notification::N_DISABLED, @notification.disabled?, disabled: true = radio_button_tag :notification_level, Notification::N_DISABLED, notification.disabled?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit'
%span Disabled %span Disabled
= label_tag do = label_tag do
= radio_button_tag :"notification_level[#{project.id}]", Notification::N_PARTICIPATING, @notification.participating?, disabled: true = radio_button_tag :notification_level, Notification::N_PARTICIPATING, notification.participating?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit'
%span Participating %span Participating
= label_tag do = label_tag do
= radio_button_tag :"notification_level[#{project.id}]", Notification::N_WATCH, @notification.watch?, disabled: true = radio_button_tag :notification_level, Notification::N_WATCH, notification.watch?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit'
%span Watch %span Watch
.form-actions .save-status-fixed
= submit_tag 'Save', class: 'btn btn-save' %span.update-success.cgreen.hide
%span.update-success.cgreen.hide %i.icon-ok
%i.icon-ok Saved
Saved %span.update-failed.cred.hide
%span.update-failed.cred.hide %i.icon-remove
%i.icon-remove Failed
Failed
- if @saved - if @saved
:plain :plain
$('.update-notifications .update-success').showAndHide(); $('.save-status-fixed .update-success').showAndHide();
- else - else
:plain :plain
$('.update-notifications .update-failed').showAndHide(); $('.save-status-fixed .update-failed').showAndHide();
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130325173941) do ActiveRecord::Schema.define(:version => 20130404164628) do
create_table "events", :force => true do |t| create_table "events", :force => true do |t|
t.string "target_type" t.string "target_type"
...@@ -156,9 +156,11 @@ ActiveRecord::Schema.define(:version => 20130325173941) do ...@@ -156,9 +156,11 @@ ActiveRecord::Schema.define(:version => 20130325173941) do
t.string "issues_tracker", :default => "gitlab", :null => false t.string "issues_tracker", :default => "gitlab", :null => false
t.string "issues_tracker_id" t.string "issues_tracker_id"
t.boolean "snippets_enabled", :default => true, :null => false t.boolean "snippets_enabled", :default => true, :null => false
t.datetime "last_activity_at"
end end
add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id" add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
add_index "projects", ["last_activity_at"], :name => "index_projects_on_last_activity_at"
add_index "projects", ["namespace_id"], :name => "index_projects_on_namespace_id" add_index "projects", ["namespace_id"], :name => "index_projects_on_namespace_id"
create_table "protected_branches", :force => true do |t| create_table "protected_branches", :force => true do |t|
...@@ -281,11 +283,12 @@ ActiveRecord::Schema.define(:version => 20130325173941) do ...@@ -281,11 +283,12 @@ ActiveRecord::Schema.define(:version => 20130325173941) do
add_index "users", ["username"], :name => "index_users_on_username" add_index "users", ["username"], :name => "index_users_on_username"
create_table "users_projects", :force => true do |t| create_table "users_projects", :force => true do |t|
t.integer "user_id", :null => false t.integer "user_id", :null => false
t.integer "project_id", :null => false t.integer "project_id", :null => false
t.datetime "created_at", :null => false t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false t.datetime "updated_at", :null => false
t.integer "project_access", :default => 0, :null => false t.integer "project_access", :default => 0, :null => false
t.integer "notification_level", :default => 3, :null => false
end end
add_index "users_projects", ["project_access"], :name => "index_users_projects_on_project_access" add_index "users_projects", ["project_access"], :name => "index_users_projects_on_project_access"
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
# issues_tracker :string(255) default("gitlab"), not null # issues_tracker :string(255) default("gitlab"), not null
# issues_tracker_id :string(255) # issues_tracker_id :string(255)
# snippets_enabled :boolean default(TRUE), not null # snippets_enabled :boolean default(TRUE), not null
# last_activity_at :datetime
# #
require 'spec_helper' require 'spec_helper'
......
...@@ -2,12 +2,13 @@ ...@@ -2,12 +2,13 @@
# #
# Table name: users_projects # Table name: users_projects
# #
# id :integer not null, primary key # id :integer not null, primary key
# user_id :integer not null # user_id :integer not null
# project_id :integer not null # project_id :integer not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# project_access :integer default(0), not null # project_access :integer default(0), not null
# notification_level :integer default(3), not null
# #
require 'spec_helper' require 'spec_helper'
......
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