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' | ... | ... |