border: 1px solid #ddd;
}
}
+
+.save-status-fixed {
+ position: fixed;
+ left: 20px;
+ bottom: 50px;
+}
+
+.update-notifications {
+ margin-bottom: 0;
+ label {
+ margin-bottom: 0;
+ }
+}
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
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
# 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"
#
# 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
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
def skip_git?
!!@skip_git
end
+
+ def notification
+ @notification ||= Notification.new(self)
+ end
end
# * 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|
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)
# 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)
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)
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)
– 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
- 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();
#
# 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"
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|
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"
# 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'
#
# 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'