Commit 25856a47e5a757e88a33218cc51367f6171db1c4
1 parent
3c3baf8f
Exists in
master
and in
4 other branches
save each notification setting with ajax on change
Showing
11 changed files
with
148 additions
and
76 deletions
Show diff stats
app/assets/stylesheets/sections/profile.scss
app/controllers/notifications_controller.rb
| ... | ... | @@ -3,11 +3,19 @@ class NotificationsController < ApplicationController |
| 3 | 3 | |
| 4 | 4 | def show |
| 5 | 5 | @notification = current_user.notification |
| 6 | - @projects = current_user.authorized_projects | |
| 6 | + @users_projects = current_user.users_projects | |
| 7 | 7 | end |
| 8 | 8 | |
| 9 | 9 | def update |
| 10 | - current_user.notification_level = params[:notification_level] | |
| 11 | - @saved = current_user.save | |
| 10 | + type = params[:notification_type] | |
| 11 | + | |
| 12 | + @saved = if type == 'global' | |
| 13 | + current_user.notification_level = params[:notification_level] | |
| 14 | + current_user.save | |
| 15 | + else | |
| 16 | + users_project = current_user.users_projects.find(params[:notification_id]) | |
| 17 | + users_project.notification_level = params[:notification_level] | |
| 18 | + users_project.save | |
| 19 | + end | |
| 12 | 20 | end |
| 13 | 21 | end | ... | ... |
app/models/notification.rb
| ... | ... | @@ -5,26 +5,35 @@ class Notification |
| 5 | 5 | N_DISABLED = 0 |
| 6 | 6 | N_PARTICIPATING = 1 |
| 7 | 7 | N_WATCH = 2 |
| 8 | + N_GLOBAL = 3 | |
| 8 | 9 | |
| 9 | - attr_accessor :user | |
| 10 | + attr_accessor :target | |
| 10 | 11 | |
| 11 | 12 | def self.notification_levels |
| 12 | 13 | [N_DISABLED, N_PARTICIPATING, N_WATCH] |
| 13 | 14 | end |
| 14 | 15 | |
| 15 | - def initialize(user) | |
| 16 | - @user = user | |
| 16 | + def self.project_notification_levels | |
| 17 | + [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL] | |
| 18 | + end | |
| 19 | + | |
| 20 | + def initialize(target) | |
| 21 | + @target = target | |
| 17 | 22 | end |
| 18 | 23 | |
| 19 | 24 | def disabled? |
| 20 | - user.notification_level == N_DISABLED | |
| 25 | + target.notification_level == N_DISABLED | |
| 21 | 26 | end |
| 22 | 27 | |
| 23 | 28 | def participating? |
| 24 | - user.notification_level == N_PARTICIPATING | |
| 29 | + target.notification_level == N_PARTICIPATING | |
| 25 | 30 | end |
| 26 | 31 | |
| 27 | 32 | def watch? |
| 28 | - user.notification_level == N_WATCH | |
| 33 | + target.notification_level == N_WATCH | |
| 34 | + end | |
| 35 | + | |
| 36 | + def global? | |
| 37 | + target.notification_level == N_GLOBAL | |
| 29 | 38 | end |
| 30 | 39 | end | ... | ... |
app/models/project.rb
app/models/users_project.rb
| ... | ... | @@ -2,12 +2,13 @@ |
| 2 | 2 | # |
| 3 | 3 | # Table name: users_projects |
| 4 | 4 | # |
| 5 | -# id :integer not null, primary key | |
| 6 | -# user_id :integer not null | |
| 7 | -# project_id :integer not null | |
| 8 | -# created_at :datetime not null | |
| 9 | -# updated_at :datetime not null | |
| 10 | -# project_access :integer default(0), not null | |
| 5 | +# id :integer not null, primary key | |
| 6 | +# user_id :integer not null | |
| 7 | +# project_id :integer not null | |
| 8 | +# created_at :datetime not null | |
| 9 | +# updated_at :datetime not null | |
| 10 | +# project_access :integer default(0), not null | |
| 11 | +# notification_level :integer default(3), not null | |
| 11 | 12 | # |
| 12 | 13 | |
| 13 | 14 | class UsersProject < ActiveRecord::Base |
| ... | ... | @@ -29,6 +30,7 @@ class UsersProject < ActiveRecord::Base |
| 29 | 30 | validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" } |
| 30 | 31 | validates :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, presence: true |
| 31 | 32 | validates :project, presence: true |
| 33 | + validates :notification_level, inclusion: { in: Notification.project_notification_levels }, presence: true | |
| 32 | 34 | |
| 33 | 35 | delegate :name, :username, :email, to: :user, prefix: true |
| 34 | 36 | |
| ... | ... | @@ -134,4 +136,8 @@ class UsersProject < ActiveRecord::Base |
| 134 | 136 | def skip_git? |
| 135 | 137 | !!@skip_git |
| 136 | 138 | end |
| 139 | + | |
| 140 | + def notification | |
| 141 | + @notification ||= Notification.new(self) | |
| 142 | + end | |
| 137 | 143 | end | ... | ... |
app/services/notification_service.rb
| ... | ... | @@ -80,7 +80,7 @@ class NotificationService |
| 80 | 80 | # * project team members with notification level higher then Participating |
| 81 | 81 | # |
| 82 | 82 | def merge_mr(merge_request) |
| 83 | - recipients = reject_muted_users([merge_request.author, merge_request.assignee]) | |
| 83 | + recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.project) | |
| 84 | 84 | recipients = recipients.concat(project_watchers(merge_request.project)).uniq |
| 85 | 85 | |
| 86 | 86 | recipients.each do |recipient| |
| ... | ... | @@ -122,7 +122,7 @@ class NotificationService |
| 122 | 122 | recipients = recipients.concat(project_watchers(note.project)).compact.uniq |
| 123 | 123 | |
| 124 | 124 | # Reject mutes users |
| 125 | - recipients = reject_muted_users(recipients) | |
| 125 | + recipients = reject_muted_users(recipients, note.project) | |
| 126 | 126 | |
| 127 | 127 | # Reject author |
| 128 | 128 | recipients.delete(note.author) |
| ... | ... | @@ -147,19 +147,41 @@ class NotificationService |
| 147 | 147 | |
| 148 | 148 | # Get project users with WATCH notification level |
| 149 | 149 | def project_watchers(project) |
| 150 | - project.users.where(notification_level: Notification::N_WATCH) | |
| 150 | + | |
| 151 | + # Get project notification settings since it has higher priority | |
| 152 | + user_ids = project.users_projects.where(notification_level: Notification::N_WATCH).pluck(:user_id) | |
| 153 | + project_watchers = User.where(id: user_ids) | |
| 154 | + | |
| 155 | + # next collect users who use global settings with watch state | |
| 156 | + user_ids = project.users_projects.where(notification_level: Notification::N_GLOBAL).pluck(:user_id) | |
| 157 | + project_watchers += User.where(id: user_ids, notification_level: Notification::N_WATCH) | |
| 158 | + | |
| 159 | + project_watchers.uniq | |
| 151 | 160 | end |
| 152 | 161 | |
| 153 | 162 | # Remove users with disabled notifications from array |
| 154 | 163 | # Also remove duplications and nil recipients |
| 155 | - def reject_muted_users(users) | |
| 156 | - users.compact.uniq.reject do |user| | |
| 157 | - user.notification.disabled? | |
| 164 | + def reject_muted_users(users, project = nil) | |
| 165 | + users = users.compact.uniq | |
| 166 | + | |
| 167 | + users.reject do |user| | |
| 168 | + next user.notification.disabled? unless project | |
| 169 | + | |
| 170 | + tm = project.users_projects.find_by_user_id(user.id) | |
| 171 | + | |
| 172 | + # reject users who globally disabled notification and has no membership | |
| 173 | + next user.notification.disabled? unless tm | |
| 174 | + | |
| 175 | + # reject users who disabled notification in project | |
| 176 | + next true if tm.notification.disabled? | |
| 177 | + | |
| 178 | + # reject users who have N_GLOBAL in project and disabled in global settings | |
| 179 | + tm.notification.global? && user.notification.disabled? | |
| 158 | 180 | end |
| 159 | 181 | end |
| 160 | 182 | |
| 161 | 183 | def new_resource_email(target, method) |
| 162 | - recipients = reject_muted_users([target.assignee]) | |
| 184 | + recipients = reject_muted_users([target.assignee], target.project) | |
| 163 | 185 | recipients = recipients.concat(project_watchers(target.project)).uniq |
| 164 | 186 | recipients.delete(target.author) |
| 165 | 187 | |
| ... | ... | @@ -169,7 +191,7 @@ class NotificationService |
| 169 | 191 | end |
| 170 | 192 | |
| 171 | 193 | def close_resource_email(target, current_user, method) |
| 172 | - recipients = reject_muted_users([target.author, target.assignee]) | |
| 194 | + recipients = reject_muted_users([target.author, target.assignee], target.project) | |
| 173 | 195 | recipients = recipients.concat(project_watchers(target.project)).uniq |
| 174 | 196 | recipients.delete(current_user) |
| 175 | 197 | |
| ... | ... | @@ -185,7 +207,7 @@ class NotificationService |
| 185 | 207 | recipients = recipients.concat(project_watchers(target.project)) |
| 186 | 208 | |
| 187 | 209 | # reject users with disabled notifications |
| 188 | - recipients = reject_muted_users(recipients) | |
| 210 | + recipients = reject_muted_users(recipients, target.project) | |
| 189 | 211 | |
| 190 | 212 | # Reject me from recipients if I reassign an item |
| 191 | 213 | recipients.delete(current_user) | ... | ... |
app/views/notifications/show.html.haml
| ... | ... | @@ -13,56 +13,65 @@ |
| 13 | 13 | – You will receive all notifications from projects in which you participate |
| 14 | 14 | %hr |
| 15 | 15 | |
| 16 | -= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do | |
| 17 | - %ul.well-list | |
| 16 | +.row | |
| 17 | + .span4 | |
| 18 | + %h5 Global | |
| 19 | + .span7 | |
| 20 | + = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do | |
| 21 | + = hidden_field_tag :notification_type, 'global' | |
| 22 | + | |
| 23 | + = label_tag do | |
| 24 | + = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit' | |
| 25 | + %span Disabled | |
| 26 | + | |
| 27 | + = label_tag do | |
| 28 | + = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit' | |
| 29 | + %span Participating | |
| 30 | + | |
| 31 | + = label_tag do | |
| 32 | + = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit' | |
| 33 | + %span Watch | |
| 34 | + | |
| 35 | +%hr | |
| 36 | += link_to '#', class: 'js-toggle-visibility-link' do | |
| 37 | + %h6.btn.btn-tiny | |
| 38 | + %i.icon-chevron-down | |
| 39 | + %span Per project notifications settings | |
| 40 | + | |
| 41 | +%ul.well-list.js-toggle-visibility-container.hide | |
| 42 | + - @users_projects.each do |users_project| | |
| 43 | + - notification = Notification.new(users_project) | |
| 18 | 44 | %li |
| 19 | 45 | .row |
| 20 | 46 | .span4 |
| 21 | - %h5 Global | |
| 47 | + %span | |
| 48 | + = link_to_project(users_project.project) | |
| 22 | 49 | .span7 |
| 23 | - = label_tag do | |
| 24 | - = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled? | |
| 25 | - %span Disabled | |
| 26 | - | |
| 27 | - = label_tag do | |
| 28 | - = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating? | |
| 29 | - %span Participating | |
| 30 | - | |
| 31 | - = label_tag do | |
| 32 | - = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch? | |
| 33 | - %span Watch | |
| 50 | + = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do | |
| 51 | + = hidden_field_tag :notification_type, 'project', id: dom_id(users_project, 'notification_type') | |
| 52 | + = hidden_field_tag :notification_id, users_project.id, id: dom_id(users_project, 'notification_id') | |
| 34 | 53 | |
| 54 | + = label_tag do | |
| 55 | + = radio_button_tag :notification_level, Notification::N_GLOBAL, notification.global?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit' | |
| 56 | + %span Use global settings | |
| 35 | 57 | |
| 36 | - = link_to '#', class: 'js-toggle-visibility-link' do | |
| 37 | - %h6.btn.btn-tiny | |
| 38 | - %i.icon-chevron-down | |
| 39 | - %span Per project notifications settings | |
| 40 | - %ul.well-list.js-toggle-visibility-container.hide | |
| 41 | - - @projects.each do |project| | |
| 42 | - %li | |
| 43 | - .row | |
| 44 | - .span4 | |
| 45 | - %span | |
| 46 | - = project.name_with_namespace | |
| 47 | - .span7 | |
| 48 | 58 | = label_tag do |
| 49 | - = radio_button_tag :"notification_level[#{project.id}]", Notification::N_DISABLED, @notification.disabled?, disabled: true | |
| 59 | + = radio_button_tag :notification_level, Notification::N_DISABLED, notification.disabled?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit' | |
| 50 | 60 | %span Disabled |
| 51 | 61 | |
| 52 | 62 | = label_tag do |
| 53 | - = radio_button_tag :"notification_level[#{project.id}]", Notification::N_PARTICIPATING, @notification.participating?, disabled: true | |
| 63 | + = radio_button_tag :notification_level, Notification::N_PARTICIPATING, notification.participating?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit' | |
| 54 | 64 | %span Participating |
| 55 | 65 | |
| 56 | 66 | = label_tag do |
| 57 | - = radio_button_tag :"notification_level[#{project.id}]", Notification::N_WATCH, @notification.watch?, disabled: true | |
| 67 | + = radio_button_tag :notification_level, Notification::N_WATCH, notification.watch?, id: dom_id(users_project, 'notification_level'), class: 'trigger-submit' | |
| 58 | 68 | %span Watch |
| 59 | 69 | |
| 60 | 70 | |
| 61 | - .form-actions | |
| 62 | - = submit_tag 'Save', class: 'btn btn-save' | |
| 63 | - %span.update-success.cgreen.hide | |
| 64 | - %i.icon-ok | |
| 65 | - Saved | |
| 66 | - %span.update-failed.cred.hide | |
| 67 | - %i.icon-remove | |
| 68 | - Failed | |
| 71 | +.save-status-fixed | |
| 72 | + %span.update-success.cgreen.hide | |
| 73 | + %i.icon-ok | |
| 74 | + Saved | |
| 75 | + %span.update-failed.cred.hide | |
| 76 | + %i.icon-remove | |
| 77 | + Failed | ... | ... |
app/views/notifications/update.js.haml
| 1 | 1 | - if @saved |
| 2 | 2 | :plain |
| 3 | - $('.update-notifications .update-success').showAndHide(); | |
| 3 | + $('.save-status-fixed .update-success').showAndHide(); | |
| 4 | 4 | - else |
| 5 | 5 | :plain |
| 6 | - $('.update-notifications .update-failed').showAndHide(); | |
| 7 | - | |
| 6 | + $('.save-status-fixed .update-failed').showAndHide(); | ... | ... |
db/schema.rb
| ... | ... | @@ -11,7 +11,7 @@ |
| 11 | 11 | # |
| 12 | 12 | # It's strongly recommended to check this file into your version control system. |
| 13 | 13 | |
| 14 | -ActiveRecord::Schema.define(:version => 20130325173941) do | |
| 14 | +ActiveRecord::Schema.define(:version => 20130404164628) do | |
| 15 | 15 | |
| 16 | 16 | create_table "events", :force => true do |t| |
| 17 | 17 | t.string "target_type" |
| ... | ... | @@ -156,9 +156,11 @@ ActiveRecord::Schema.define(:version => 20130325173941) do |
| 156 | 156 | t.string "issues_tracker", :default => "gitlab", :null => false |
| 157 | 157 | t.string "issues_tracker_id" |
| 158 | 158 | t.boolean "snippets_enabled", :default => true, :null => false |
| 159 | + t.datetime "last_activity_at" | |
| 159 | 160 | end |
| 160 | 161 | |
| 161 | 162 | add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id" |
| 163 | + add_index "projects", ["last_activity_at"], :name => "index_projects_on_last_activity_at" | |
| 162 | 164 | add_index "projects", ["namespace_id"], :name => "index_projects_on_namespace_id" |
| 163 | 165 | |
| 164 | 166 | create_table "protected_branches", :force => true do |t| |
| ... | ... | @@ -281,11 +283,12 @@ ActiveRecord::Schema.define(:version => 20130325173941) do |
| 281 | 283 | add_index "users", ["username"], :name => "index_users_on_username" |
| 282 | 284 | |
| 283 | 285 | create_table "users_projects", :force => true do |t| |
| 284 | - t.integer "user_id", :null => false | |
| 285 | - t.integer "project_id", :null => false | |
| 286 | - t.datetime "created_at", :null => false | |
| 287 | - t.datetime "updated_at", :null => false | |
| 288 | - t.integer "project_access", :default => 0, :null => false | |
| 286 | + t.integer "user_id", :null => false | |
| 287 | + t.integer "project_id", :null => false | |
| 288 | + t.datetime "created_at", :null => false | |
| 289 | + t.datetime "updated_at", :null => false | |
| 290 | + t.integer "project_access", :default => 0, :null => false | |
| 291 | + t.integer "notification_level", :default => 3, :null => false | |
| 289 | 292 | end |
| 290 | 293 | |
| 291 | 294 | add_index "users_projects", ["project_access"], :name => "index_users_projects_on_project_access" | ... | ... |
spec/models/project_spec.rb
spec/models/users_project_spec.rb
| ... | ... | @@ -2,12 +2,13 @@ |
| 2 | 2 | # |
| 3 | 3 | # Table name: users_projects |
| 4 | 4 | # |
| 5 | -# id :integer not null, primary key | |
| 6 | -# user_id :integer not null | |
| 7 | -# project_id :integer not null | |
| 8 | -# created_at :datetime not null | |
| 9 | -# updated_at :datetime not null | |
| 10 | -# project_access :integer default(0), not null | |
| 5 | +# id :integer not null, primary key | |
| 6 | +# user_id :integer not null | |
| 7 | +# project_id :integer not null | |
| 8 | +# created_at :datetime not null | |
| 9 | +# updated_at :datetime not null | |
| 10 | +# project_access :integer default(0), not null | |
| 11 | +# notification_level :integer default(3), not null | |
| 11 | 12 | # |
| 12 | 13 | |
| 13 | 14 | require 'spec_helper' | ... | ... |