OSDN Git Service

save each notification setting with ajax on change
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Thu, 4 Apr 2013 19:11:51 +0000 (22:11 +0300)
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Thu, 4 Apr 2013 19:11:51 +0000 (22:11 +0300)
app/assets/stylesheets/sections/profile.scss
app/controllers/notifications_controller.rb
app/models/notification.rb
app/models/project.rb
app/models/users_project.rb
app/services/notification_service.rb
app/views/notifications/show.html.haml
app/views/notifications/update.js.haml
db/schema.rb
spec/models/project_spec.rb
spec/models/users_project_spec.rb

index 607daf7..c34cd23 100644 (file)
     border: 1px solid #ddd;
   }
 }
+
+.save-status-fixed {
+  position: fixed;
+  left: 20px;
+  bottom: 50px;
+}
+
+.update-notifications {
+  margin-bottom: 0;
+  label {
+    margin-bottom: 0;
+  }
+}
index e44e0aa..4aa3172 100644 (file)
@@ -3,11 +3,19 @@ class NotificationsController < ApplicationController
 
   def show
     @notification = current_user.notification
-    @projects = current_user.authorized_projects
+    @users_projects = current_user.users_projects
   end
 
   def update
-    current_user.notification_level = params[:notification_level]
-    @saved = current_user.save
+    type = params[:notification_type]
+
+    @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
index bfd1e2c..ff6a18d 100644 (file)
@@ -5,26 +5,35 @@ class Notification
   N_DISABLED = 0
   N_PARTICIPATING = 1
   N_WATCH = 2
+  N_GLOBAL = 3
 
-  attr_accessor :user
+  attr_accessor :target
 
   def self.notification_levels
     [N_DISABLED, N_PARTICIPATING, N_WATCH]
   end
 
-  def initialize(user)
-    @user = user
+  def self.project_notification_levels
+    [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL]
+  end
+
+  def initialize(target)
+    @target = target
   end
 
   def disabled?
-    user.notification_level == N_DISABLED
+    target.notification_level == N_DISABLED
   end
 
   def participating?
-    user.notification_level == N_PARTICIPATING
+    target.notification_level == N_PARTICIPATING
   end
 
   def watch?
-    user.notification_level == N_WATCH
+    target.notification_level == N_WATCH
+  end
+
+  def global?
+    target.notification_level == N_GLOBAL
   end
 end
index 0263ffe..53b318d 100644 (file)
@@ -19,6 +19,7 @@
 #  issues_tracker         :string(255)      default("gitlab"), not null
 #  issues_tracker_id      :string(255)
 #  snippets_enabled       :boolean          default(TRUE), not null
+#  last_activity_at       :datetime
 #
 
 require "grit"
index c32edf3..935eced 100644 (file)
@@ -2,12 +2,13 @@
 #
 # Table name: users_projects
 #
-#  id             :integer          not null, primary key
-#  user_id        :integer          not null
-#  project_id     :integer          not null
-#  created_at     :datetime         not null
-#  updated_at     :datetime         not null
-#  project_access :integer          default(0), not null
+#  id                 :integer          not null, primary key
+#  user_id            :integer          not null
+#  project_id         :integer          not null
+#  created_at         :datetime         not null
+#  updated_at         :datetime         not null
+#  project_access     :integer          default(0), not null
+#  notification_level :integer          default(3), not null
 #
 
 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 :project_access, inclusion: { in: [GUEST, REPORTER, DEVELOPER, MASTER] }, 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
 
@@ -134,4 +136,8 @@ class UsersProject < ActiveRecord::Base
   def skip_git?
     !!@skip_git
   end
+
+  def notification
+    @notification ||= Notification.new(self)
+  end
 end
index f8779fd..4b3c982 100644 (file)
@@ -80,7 +80,7 @@ class NotificationService
   #  * project team members with notification level higher then Participating
   #
   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.each do |recipient|
@@ -122,7 +122,7 @@ class NotificationService
     recipients = recipients.concat(project_watchers(note.project)).compact.uniq
 
     # Reject mutes users
-    recipients = reject_muted_users(recipients)
+    recipients = reject_muted_users(recipients, note.project)
 
     # Reject author
     recipients.delete(note.author)
@@ -147,19 +147,41 @@ class NotificationService
 
   # Get project users with WATCH notification level
   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
 
   # Remove users with disabled notifications from array
   # Also remove duplications and nil recipients
-  def reject_muted_users(users)
-    users.compact.uniq.reject do |user|
-      user.notification.disabled?
+  def reject_muted_users(users, project = nil)
+    users = users.compact.uniq
+
+    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
 
   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.delete(target.author)
 
@@ -169,7 +191,7 @@ class NotificationService
   end
 
   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.delete(current_user)
 
@@ -185,7 +207,7 @@ class NotificationService
     recipients = recipients.concat(project_watchers(target.project))
 
     # 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
     recipients.delete(current_user)
index d8ab93b..38e492f 100644 (file)
   &ndash; You will receive all notifications from projects in which you participate
 %hr
 
-= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
-  %ul.well-list
+.row
+  .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
       .row
         .span4
-          %h5 Global
+          %span
+            = link_to_project(users_project.project)
         .span7
-          = label_tag do
-            = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?
-            %span Disabled
-
-          = 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
+          = form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications' do
+            = hidden_field_tag :notification_type, 'project', id: dom_id(users_project, 'notification_type')
+            = 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_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
-              = 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
 
             = 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
 
             = 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
 
 
-  .form-actions
-    = submit_tag 'Save', class: 'btn btn-save'
-    %span.update-success.cgreen.hide
-      %i.icon-ok
-      Saved
-    %span.update-failed.cred.hide
-      %i.icon-remove
-      Failed
+.save-status-fixed
+  %span.update-success.cgreen.hide
+    %i.icon-ok
+    Saved
+  %span.update-failed.cred.hide
+    %i.icon-remove
+    Failed
index 4468004..88e74d5 100644 (file)
@@ -1,7 +1,6 @@
 - if @saved
   :plain
-    $('.update-notifications .update-success').showAndHide();
+    $('.save-status-fixed .update-success').showAndHide();
 - else
   :plain
-    $('.update-notifications .update-failed').showAndHide();
-
+    $('.save-status-fixed .update-failed').showAndHide();
index 3c8b9ea..0f7827a 100644 (file)
@@ -11,7 +11,7 @@
 #
 # 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|
     t.string   "target_type"
@@ -156,9 +156,11 @@ ActiveRecord::Schema.define(:version => 20130325173941) do
     t.string   "issues_tracker",         :default => "gitlab", :null => false
     t.string   "issues_tracker_id"
     t.boolean  "snippets_enabled",       :default => true,     :null => false
+    t.datetime "last_activity_at"
   end
 
   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"
 
   create_table "protected_branches", :force => true do |t|
@@ -281,11 +283,12 @@ ActiveRecord::Schema.define(:version => 20130325173941) do
   add_index "users", ["username"], :name => "index_users_on_username"
 
   create_table "users_projects", :force => true do |t|
-    t.integer  "user_id",                       :null => false
-    t.integer  "project_id",                    :null => false
-    t.datetime "created_at",                    :null => false
-    t.datetime "updated_at",                    :null => false
-    t.integer  "project_access", :default => 0, :null => false
+    t.integer  "user_id",                           :null => false
+    t.integer  "project_id",                        :null => false
+    t.datetime "created_at",                        :null => false
+    t.datetime "updated_at",                        :null => false
+    t.integer  "project_access",     :default => 0, :null => false
+    t.integer  "notification_level", :default => 3, :null => false
   end
 
   add_index "users_projects", ["project_access"], :name => "index_users_projects_on_project_access"
index cbc7f27..3ecf697 100644 (file)
@@ -19,6 +19,7 @@
 #  issues_tracker         :string(255)      default("gitlab"), not null
 #  issues_tracker_id      :string(255)
 #  snippets_enabled       :boolean          default(TRUE), not null
+#  last_activity_at       :datetime
 #
 
 require 'spec_helper'
index e8f5b64..e289a59 100644 (file)
@@ -2,12 +2,13 @@
 #
 # Table name: users_projects
 #
-#  id             :integer          not null, primary key
-#  user_id        :integer          not null
-#  project_id     :integer          not null
-#  created_at     :datetime         not null
-#  updated_at     :datetime         not null
-#  project_access :integer          default(0), not null
+#  id                 :integer          not null, primary key
+#  user_id            :integer          not null
+#  project_id         :integer          not null
+#  created_at         :datetime         not null
+#  updated_at         :datetime         not null
+#  project_access     :integer          default(0), not null
+#  notification_level :integer          default(3), not null
 #
 
 require 'spec_helper'