Revision 363cab56
Added by Ohad Levy over 7 years ago
app/controllers/concerns/foreman/controller/parameters/notification_recipient.rb | ||
---|---|---|
module Foreman::Controller::Parameters::NotificationRecipient
|
||
extend ActiveSupport::Concern
|
||
|
||
class_methods do
|
||
def notification_recipient_params_filter
|
||
Foreman::ParameterFilter.new(::NotificationRecipient).tap do |filter|
|
||
filter.permit :seen
|
||
end
|
||
end
|
||
end
|
||
|
||
def notification_recipient_params
|
||
self.class.notification_recipient_params_filter.filter_params(params, parameter_filter_context)
|
||
end
|
||
end
|
app/controllers/notification_recipients_controller.rb | ||
---|---|---|
class NotificationRecipientsController < Api::V2::BaseController
|
||
include Foreman::Controller::Parameters::NotificationRecipient
|
||
|
||
before_action :require_login
|
||
before_action :find_resource, :only => [:update, :destroy]
|
||
|
||
def index
|
||
@notifications = NotificationRecipient.
|
||
where(:user_id => User.current.id).
|
||
order(:created_at).
|
||
eager_load(:notification, :notification_blueprint)
|
||
|
||
render :json => {
|
||
:notifications => @notifications.paginate(paginate_options).map(&:payload),
|
||
:total => @notifications.count
|
||
}
|
||
end
|
||
|
||
def update
|
||
process_response @notification_recipient.update_attributes(notification_recipient_params)
|
||
end
|
||
|
||
def destroy
|
||
process_response @notification_recipient.destroy
|
||
end
|
||
|
||
private
|
||
|
||
def require_login
|
||
not_found unless SETTINGS[:login]
|
||
end
|
||
|
||
def find_resource
|
||
super
|
||
@notification_recipient.current_user? || not_found
|
||
end
|
||
end
|
app/models/notification.rb | ||
---|---|---|
# frozen_string_literal: true
|
||
|
||
# Stores the information related to serving the notification to multiple users
|
||
# This class' responsibility is, given a notification blueprint, to determine
|
||
# who are the notification recipients
|
||
class Notification < ActiveRecord::Base
|
||
AUDIENCE_USER = 'user'
|
||
AUDIENCE_GROUP = 'usergroup'
|
||
AUDIENCE_TAXONOMY = 'taxonomy'
|
||
AUDIENCE_GLOBAL = 'global'
|
||
AUDIENCE_ADMIN = 'admin'
|
||
|
||
belongs_to :notification_blueprint
|
||
belongs_to :initiator, :class_name => User, :foreign_key => 'user_id'
|
||
has_many :notification_recipients, :dependent => :delete_all
|
||
has_many :recipients, :class_name => User, :through => :notification_recipients, :source => :user
|
||
|
||
validates :notification_blueprint, :presence => true
|
||
validates :initiator, :presence => true
|
||
validates :audience, :inclusion => {
|
||
:in => [AUDIENCE_USER, AUDIENCE_GROUP, AUDIENCE_TAXONOMY,
|
||
AUDIENCE_GLOBAL, AUDIENCE_ADMIN]
|
||
}, :presence => true
|
||
before_create :calculate_expiry, :set_notification_recipients,
|
||
:set_default_initiator
|
||
|
||
scope :active, -> { where('expired_at >= :now', {:now => Time.now.utc}) }
|
||
scope :expired, -> { where('expired_at < :now', {:now => Time.now.utc}) }
|
||
|
||
def expired?
|
||
Time.now.utc > expired_at
|
||
end
|
||
|
||
def subscriber_ids
|
||
case audience
|
||
when AUDIENCE_GLOBAL
|
||
User.reorder('').pluck(:id)
|
||
when AUDIENCE_TAXONOMY
|
||
notification_blueprint.subject.user_ids.uniq
|
||
when AUDIENCE_USER
|
||
[initiator.id]
|
||
when AUDIENCE_ADMIN
|
||
User.only_admin.reorder('').uniq.pluck(:id)
|
||
when AUDIENCE_GROUP
|
||
notification_blueprint.subject.all_users.uniq.map(&:id) # This needs to be rewritten in usergroups.
|
||
end
|
||
end
|
||
|
||
private
|
||
|
||
def calculate_expiry
|
||
self.expired_at = Time.now.utc + notification_blueprint.expires_in
|
||
end
|
||
|
||
def set_default_initiator
|
||
self.initiator = User.anonymous_admin
|
||
end
|
||
|
||
def set_notification_recipients
|
||
subscribers = subscriber_ids
|
||
notification_recipients.build subscribers.map{|id| { :user_id => id}}
|
||
end
|
||
end
|
app/models/notification_blueprint.rb | ||
---|---|---|
# Model to store all information related to a notification.
|
||
# The audience for this notification is calculated on real time by
|
||
# Notification.
|
||
#
|
||
# Foreman and plugins should define NotificationBlueprints for various
|
||
# actions that are notification-worthy. This keeps the responsibilities
|
||
# separate, Notifications taking care of serving the content, and
|
||
# NotificationBlueprint of storing it.
|
||
class NotificationBlueprint < ActiveRecord::Base
|
||
has_many :notifications
|
||
belongs_to :subject, :polymorphic => true
|
||
|
||
validates :message, :presence => true
|
||
validates :group, :presence => true
|
||
validates :name, :presence => true
|
||
validates :level, :inclusion => { :in => %w(success error warning info) }, :presence => true
|
||
validates :expires_in, :numericality => {:greater_than => 0}
|
||
before_validation :set_default_expiry
|
||
|
||
private
|
||
|
||
def set_default_expiry
|
||
self.expires_in ||= 24.hours
|
||
end
|
||
end
|
app/models/notification_recipient.rb | ||
---|---|---|
class NotificationRecipient < ActiveRecord::Base
|
||
belongs_to :notification
|
||
belongs_to :user
|
||
has_one :notification_blueprint, :through => :notification
|
||
|
||
validates :notification, :presence => true
|
||
validates :user, :presence => true
|
||
|
||
scope :unseen, -> { where(:seen => false) }
|
||
|
||
def payload
|
||
{
|
||
:id => id,
|
||
:seen => seen,
|
||
:level => notification_blueprint.level,
|
||
:text => notification_blueprint.message,
|
||
:subject => notification_blueprint.subject,
|
||
:created_at => notification.created_at,
|
||
:group => notification_blueprint.group,
|
||
}
|
||
end
|
||
|
||
def current_user?
|
||
return true unless SETTINGS[:login]
|
||
User.current.id == user_id
|
||
end
|
||
end
|
app/services/foreman/access_permissions.rb | ||
---|---|---|
Foreman::AccessControl.map do |permission_set|
|
||
permission_set.security_block :public do |map|
|
||
map.permission :user_logout, { :users => [:logout] }, :public => true
|
||
map.permission :my_account, { :users => [:edit] }, :public => true
|
||
map.permission :my_account, { :users => [:edit],
|
||
:notification_recipients => [:index, :update, :destroy] }, :public => true
|
||
map.permission :api_status, { :"api/v1/home" => [:status], :"api/v2/home" => [:status]}, :public => true
|
||
map.permission :about_index, { :about => [:index] }, :public => true
|
||
end
|
config/routes.rb | ||
---|---|---|
get :random_name
|
||
end
|
||
end
|
||
|
||
resources :notification_recipients, :only => [:index, :update, :destroy]
|
||
end
|
db/migrate/20160927071039_create_notification_blueprints.rb | ||
---|---|---|
class CreateNotificationBlueprints < ActiveRecord::Migration
|
||
def change
|
||
create_table :notification_blueprints do |t|
|
||
t.string :group, index: true
|
||
t.string :level
|
||
t.string :message
|
||
t.text :name
|
||
t.integer :expires_in
|
||
t.timestamps null: false
|
||
t.references :subject, polymorphic: true
|
||
end
|
||
end
|
||
end
|
db/migrate/20160927071147_create_notifications.rb | ||
---|---|---|
class CreateNotifications < ActiveRecord::Migration
|
||
def change
|
||
create_table :notifications do |t|
|
||
t.references :notification_blueprint, index: true, foreign_key: true, null: false
|
||
t.references :user, index: true, foreign_key: true
|
||
t.string :audience
|
||
t.timestamp :expired_at
|
||
t.timestamps null: false
|
||
end
|
||
end
|
||
end
|
db/migrate/20160927073233_create_notification_recipients.rb | ||
---|---|---|
class CreateNotificationRecipients < ActiveRecord::Migration
|
||
def change
|
||
create_table :notification_recipients do |t|
|
||
t.references :notification, index: true, foreign_key: true
|
||
t.references :user, index: true, foreign_key: true
|
||
t.boolean :seen, default: false, index: true
|
||
|
||
t.timestamps null: false
|
||
end
|
||
end
|
||
end
|
test/controllers/notification_recipients_controller_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class NotificationRecipientsControllerTest < ActionController::TestCase
|
||
setup do
|
||
@request.headers['Accept'] = Mime::JSON
|
||
@request.headers['Content-Type'] = Mime::JSON.to_s
|
||
end
|
||
|
||
test "should get index" do
|
||
get :index, { :format => 'json' }, set_session_user
|
||
assert_response :success
|
||
response = ActiveSupport::JSON.decode(@response.body)
|
||
assert_empty response['notifications']
|
||
assert_equal 0, response['total']
|
||
end
|
||
|
||
test "should get index" do
|
||
notification = add_notification
|
||
|
||
get :index, { :format => 'json' }, set_session_user
|
||
assert_response :success
|
||
assert_equal notification.notification_blueprint.message, response['notifications'][0]['text']
|
||
end
|
||
|
||
test "should be able to update seen flag" do
|
||
add_notification
|
||
get :index, { :format=>'json' }, set_session_user
|
||
put :update, { :format => 'json', :id => first_notification,
|
||
:notification_recipient => {:seen => true} }, set_session_user
|
||
assert_response :success
|
||
assert response['seen']
|
||
end
|
||
|
||
test "should be able to delete notification" do
|
||
add_notification
|
||
get :index, { :format=>'json' }, set_session_user
|
||
notice_id = first_notification
|
||
assert NotificationRecipient.find(notice_id)
|
||
delete :destroy, { :id => notice_id }
|
||
refute NotificationRecipient.find_by_id(notice_id)
|
||
assert_response :success
|
||
end
|
||
|
||
test "should get 404 on invalid notification deletion" do
|
||
get :index, { :format=>'json' }, set_session_user
|
||
notice_id = 1
|
||
refute response['notifications'].map{|n| n['id']}.include?(notice_id)
|
||
refute NotificationRecipient.find_by_id(notice_id)
|
||
delete :destroy, { :id => notice_id }
|
||
assert_response :not_found
|
||
end
|
||
|
||
test "should not get notifications if settings login is disabled" do
|
||
SETTINGS[:login] = false
|
||
get :index, { :format=>'json' }, {}
|
||
SETTINGS[:login] = true
|
||
assert_response :not_found
|
||
end
|
||
|
||
private
|
||
|
||
def add_notification
|
||
type = FactoryGirl.create(:notification_blueprint,
|
||
:message => 'this test just executed successfully')
|
||
FactoryGirl.create(:notification, :notification_blueprint => type, :audience => 'global')
|
||
end
|
||
|
||
def response
|
||
ActiveSupport::JSON.decode(@response.body)
|
||
end
|
||
|
||
def first_notification
|
||
response['notifications'].first['id']
|
||
end
|
||
end
|
test/factories/notification.rb | ||
---|---|---|
FactoryGirl.define do
|
||
factory :notification do
|
||
notification_blueprint
|
||
association :initiator, :factory => :user
|
||
audience 'user'
|
||
end
|
||
end
|
test/factories/notification_blueprint.rb | ||
---|---|---|
FactoryGirl.define do
|
||
factory :notification_blueprint do
|
||
sequence(:group) { |n| "notification_blueprint_#{n}" }
|
||
sequence(:message) { |n| "message_#{n}" }
|
||
sequence(:name) { |n| "name_#{n}" }
|
||
subject User.first
|
||
level 'info'
|
||
expires_in 24.hours
|
||
end
|
||
end
|
test/factories/notification_recipient.rb | ||
---|---|---|
FactoryGirl.define do
|
||
factory :notification_recipient do
|
||
notification
|
||
user
|
||
end
|
||
end
|
test/models/notification_blueprint_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class NotificationBlueprintTest < ActiveSupport::TestCase
|
||
should validate_presence_of(:message)
|
||
should validate_presence_of(:level)
|
||
should validate_presence_of(:group)
|
||
should validate_presence_of(:name)
|
||
end
|
test/models/notification_recipient_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class NotificationRecipientTest < ActiveSupport::TestCase
|
||
should validate_presence_of(:notification)
|
||
should validate_presence_of(:user)
|
||
|
||
test "seen should defaults to false" do
|
||
recipient = NotificationRecipient.new
|
||
assert_equal false, recipient.seen
|
||
end
|
||
|
||
test "should return unseen notifications" do
|
||
id = FactoryGirl.create(:notification_recipient).id
|
||
assert NotificationRecipient.unseen.pluck(:id).include?(id)
|
||
end
|
||
end
|
test/models/notification_test.rb | ||
---|---|---|
require 'test_helper'
|
||
|
||
class NotificationTest < ActiveSupport::TestCase
|
||
setup do
|
||
User.current = users :admin
|
||
end
|
||
|
||
should validate_presence_of(:initiator)
|
||
should validate_presence_of(:notification_blueprint)
|
||
should validate_presence_of(:audience)
|
||
|
||
test 'should be able to create notification' do
|
||
blueprint = FactoryGirl.create(
|
||
:notification_blueprint,
|
||
:message => 'this test just executed successfully',
|
||
:subject => nil
|
||
)
|
||
notice = FactoryGirl.create(:notification,
|
||
:audience => 'global',
|
||
:notification_blueprint => blueprint)
|
||
assert notice.valid?
|
||
assert_equal blueprint.message, notice.notification_blueprint.message
|
||
assert_equal User.all, notice.recipients
|
||
end
|
||
|
||
test 'should return active notifications' do
|
||
blueprint = FactoryGirl.create(
|
||
:notification_blueprint,
|
||
:expires_in => 5.minutes
|
||
)
|
||
notice = FactoryGirl.create(:notification,
|
||
:audience => Notification::AUDIENCE_ADMIN,
|
||
:notification_blueprint => blueprint)
|
||
assert_includes Notification.active, notice
|
||
end
|
||
|
||
test 'user notifications should subscribe only to itself' do
|
||
notification = FactoryGirl.build(:notification,
|
||
:audience => Notification::AUDIENCE_USER)
|
||
assert_equal [notification.initiator.id], notification.subscriber_ids
|
||
end
|
||
|
||
test 'usergroup notifications should subscribe to all of its members' do
|
||
group = FactoryGirl.create(:usergroup)
|
||
group.users = FactoryGirl.create_list(:user,25)
|
||
notification = FactoryGirl.build(:notification,
|
||
:audience => Notification::AUDIENCE_GROUP)
|
||
notification.notification_blueprint.subject = group
|
||
assert group.all_users.any?
|
||
assert_equal group.all_users.map(&:id),
|
||
notification.subscriber_ids
|
||
end
|
||
|
||
test 'Organization notifications should subscribe to all of its members' do
|
||
org = FactoryGirl.create(:organization)
|
||
org.users = FactoryGirl.create_list(:user,25)
|
||
notification = FactoryGirl.build(:notification,
|
||
:audience => Notification::AUDIENCE_TAXONOMY)
|
||
notification.notification_blueprint.subject = org
|
||
assert org.user_ids.any?
|
||
assert_equal org.user_ids, notification.subscriber_ids
|
||
end
|
||
|
||
test 'Location notifications should subscribe to all of its members' do
|
||
loc = FactoryGirl.create(:location)
|
||
loc.users = FactoryGirl.create_list(:user,25)
|
||
notification = FactoryGirl.build(:notification,
|
||
:audience => Notification::AUDIENCE_TAXONOMY)
|
||
notification.notification_blueprint.subject = loc
|
||
assert loc.user_ids.any?
|
||
assert_equal loc.user_ids, notification.subscriber_ids
|
||
end
|
||
|
||
test 'Global notifications should subscribe to all users' do
|
||
notification = FactoryGirl.build(:notification,
|
||
:audience => Notification::AUDIENCE_GLOBAL)
|
||
assert User.count > 0
|
||
assert_equal User.reorder('').pluck(:id),
|
||
notification.subscriber_ids
|
||
end
|
||
|
||
test 'Admin notifications should subscribe to all admin users' do
|
||
notification = FactoryGirl.build(:notification,
|
||
:audience => Notification::AUDIENCE_ADMIN)
|
||
assert User.only_admin.count > 0
|
||
assert_equal User.only_admin.reorder('').pluck(:id).sort,
|
||
notification.subscriber_ids.sort
|
||
end
|
||
end
|
Also available in: Unified diff
fixes #17545 - adds UI notification support
- Initial data model for notification support, based on manageiq design.
- addes notification JSON endpoints.