Skip to content

Commit

Permalink
Merge pull request #3248 from mdeniz/rss_notifications_ui
Browse files Browse the repository at this point in the history
Rss Notifications
  • Loading branch information
Evan Rolfe committed Jun 29, 2017
2 parents eca42ca + e61f5ab commit 6660282
Show file tree
Hide file tree
Showing 42 changed files with 461 additions and 174 deletions.
1 change: 1 addition & 0 deletions ReleaseNotes-2.9
Expand Up @@ -32,6 +32,7 @@ UI:
kerberos authentication. Read more in the OBS Admin Guide.
* Users can see the job history list of a specific package via the package binaries page
* New GPG key details dialog
* RSS Feeds for User's Notifications is now available

API:
* Kerberos authentication mode
Expand Down
6 changes: 3 additions & 3 deletions src/api/app/controllers/person_controller.rb
Expand Up @@ -266,7 +266,7 @@ def change_password(login, password)
# GET /person/<login>/token
def tokenlist
user = User.get_by_login(params[:login])
@list = user.tokens
@list = user.service_tokens
end

# POST /person/<login>/token
Expand All @@ -280,7 +280,7 @@ def command_token
if params[:project] || params[:package]
pkg = Package.get_by_project_and_name( params[:project], params[:package] )
end
@token = Token.create( user: user, package: pkg )
@token = Token::Service.create( user: user, package: pkg )
end

class TokenNotFound < APIException
Expand All @@ -291,7 +291,7 @@ class TokenNotFound < APIException
def delete_token
user = User.get_by_login(params[:login])

token = Token.where( user_id: user.id, id: params[:id] ).first
token = Token::Service.where( user_id: user.id, id: params[:id] ).first
raise TokenNotFound, "Specified token \"#{params[:id]}\" got not found" unless token
token.destroy
render_ok
Expand Down
2 changes: 1 addition & 1 deletion src/api/app/controllers/trigger_controller.rb
Expand Up @@ -19,7 +19,7 @@ def runservice
return
end

token = Token.find_by_string auth[6..-1]
token = Token::Service.find_by_string(auth[6..-1])

unless token
render_error message: "Token not found", status: 404
Expand Down
13 changes: 13 additions & 0 deletions src/api/app/controllers/webui/feeds_controller.rb
Expand Up @@ -40,4 +40,17 @@ def commits
@commits = @commits.where(["datetime <= ?", @finish]) unless @finish.nil?
@commits = @commits.order("datetime desc")
end

def notifications
token = Token::Rss.find_by_string(params[:token])
if token
@configuration = ::Configuration.first
@user = token.user
@notifications = token.user.combined_rss_feed_items
@host = ::Configuration.obs_url
else
flash[:error] = "Unknown Token for RSS feed"
redirect_back(fallback_location: root_path)
end
end
end
20 changes: 20 additions & 0 deletions src/api/app/controllers/webui/users/rss_tokens_controller.rb
@@ -0,0 +1,20 @@
module Webui
module Users
class RssTokensController < WebuiController
before_action :require_login

def create
token = User.current.rss_token
if token
flash[:success] = "Successfully re-generated your RSS feed url"
token.regenerate_string
token.save
else
flash[:success] = "Successfully generated your RSS feed url"
User.current.create_rss_token
end
redirect_back(fallback_location: user_notifications_path)
end
end
end
end
2 changes: 1 addition & 1 deletion src/api/app/jobs/cleanup_notifications.rb
@@ -1,5 +1,5 @@
class CleanupNotifications < ApplicationJob
def perform
Notifications::RssFeedItem.cleanup
Notification::RssFeedItem.cleanup
end
end
23 changes: 14 additions & 9 deletions src/api/app/jobs/send_event_emails.rb
Expand Up @@ -16,16 +16,21 @@ def perform
subscribers = event.subscribers
next if subscribers.empty?
EventMailer.event(subscribers, event).deliver_now

event.subscriptions.each do |subscription|
Notifications::RssFeedItem.create(
subscriber: subscription.subscriber,
event_type: event.eventtype,
event_payload: event.payload,
subscription_receiver_role: subscription.receiver_role
)
end
create_rss_notifications(event)
end
true
end

private

def create_rss_notifications(event)
event.subscriptions.each do |subscription|
Notification::RssFeedItem.create(
subscriber: subscription.subscriber,
event_type: event.eventtype,
event_payload: event.payload,
subscription_receiver_role: subscription.receiver_role
)
end
end
end
1 change: 0 additions & 1 deletion src/api/app/models/event_subscription.rb
Expand Up @@ -60,7 +60,6 @@ def enabled?
# created_at :datetime
# updated_at :datetime
# group_id :integer indexed
# receive :boolean
# channel :integer default("disabled"), not null
#
# Indexes
Expand Down
2 changes: 1 addition & 1 deletion src/api/app/models/group.rb
Expand Up @@ -11,7 +11,7 @@ class Group < ApplicationRecord
has_many :event_subscriptions, dependent: :destroy, inverse_of: :group
has_many :reviews, dependent: :nullify, as: :reviewable

has_many :rss_feed_items, -> { order(created_at: :desc) }, class_name: 'Notifications::RssFeedItem', dependent: :destroy
has_many :rss_feed_items, -> { order(created_at: :desc) }, class_name: 'Notification::RssFeedItem', as: :subscriber, dependent: :destroy

validates :title,
format: { with: %r{\A[\w\.\-]*\z},
Expand Down
@@ -1,15 +1,10 @@
class Notifications::Base < ApplicationRecord
self.table_name = "notifications"
class Notification < ApplicationRecord
belongs_to :subscriber, polymorphic: true

belongs_to :user
belongs_to :group
serialize :event_payload, Hash

def subscriber=(subscriber)
if subscriber.is_a? User
self.user = subscriber
elsif subscriber.is_a? Group
self.group = subscriber
end
def event
@event ||= event_type.constantize.new(event_payload)
end
end

Expand All @@ -18,18 +13,17 @@ def subscriber=(subscriber)
# Table name: notifications
#
# id :integer not null, primary key
# user_id :integer indexed
# group_id :integer indexed
# type :string(255) not null
# event_type :string(255) not null
# event_payload :text(65535) not null
# subscription_receiver_role :string(255) not null
# delivered :boolean default(FALSE)
# created_at :datetime not null
# updated_at :datetime not null
# subscriber_type :string(255) indexed => [subscriber_id]
# subscriber_id :integer indexed => [subscriber_type]
#
# Indexes
#
# index_notifications_on_group_id (group_id)
# index_notifications_on_user_id (user_id)
# index_notifications_on_subscriber_type_and_subscriber_id (subscriber_type,subscriber_id)
#
@@ -1,21 +1,18 @@
class Notifications::RssFeedItem < Notifications::Base
class Notification::RssFeedItem < Notification
MAX_ITEMS_PER_USER = 10
MAX_ITEMS_PER_GROUP = 10

def self.cleanup
User.all_without_nobody.find_in_batches batch_size: 500 do |batch|
batch.each do |user|
if user.is_active?
ids = user.rss_feed_items.pluck(:id).slice(MAX_ITEMS_PER_USER..-1)
user.rss_feed_items.where(id: ids).delete_all
else
user.rss_feed_items.delete_all
end
offset = user.is_active? ? MAX_ITEMS_PER_USER : 0
ids = user.rss_feed_items.offset(offset).pluck(:id)
user.rss_feed_items.where(id: ids).delete_all
end
end
Group.find_in_batches batch_size: 500 do |batch|
batch.each do |group|
ids = group.rss_feed_items.pluck(:id).slice(MAX_ITEMS_PER_GROUP..-1)
ids = group.rss_feed_items.offset(MAX_ITEMS_PER_GROUP).pluck(:id)
group.rss_feed_items.where(id: ids).delete_all
end
end
Expand All @@ -27,18 +24,17 @@ def self.cleanup
# Table name: notifications
#
# id :integer not null, primary key
# user_id :integer indexed
# group_id :integer indexed
# type :string(255) not null
# event_type :string(255) not null
# event_payload :text(65535) not null
# subscription_receiver_role :string(255) not null
# delivered :boolean default(FALSE)
# created_at :datetime not null
# updated_at :datetime not null
# subscriber_type :string(255) indexed => [subscriber_id]
# subscriber_id :integer indexed => [subscriber_type]
#
# Indexes
#
# index_notifications_on_group_id (group_id)
# index_notifications_on_user_id (user_id)
# index_notifications_on_subscriber_type_and_subscriber_id (subscriber_type,subscriber_id)
#
4 changes: 2 additions & 2 deletions src/api/app/models/package.rb
Expand Up @@ -124,9 +124,9 @@ class PutFileNoPermission < APIException; setup 403; end
validate :valid_name

has_one :backend_package, foreign_key: :package_id, dependent: :destroy, inverse_of: :package
has_one :token, foreign_key: :package_id, dependent: :destroy
has_one :token, class_name: 'Token::Service', foreign_key: :package_id, dependent: :destroy

has_many :tokens, dependent: :destroy, inverse_of: :package
has_many :tokens, class_name: 'Token::Service', dependent: :destroy, inverse_of: :package

def self.check_access?(package)
return false if package.nil?
Expand Down
20 changes: 4 additions & 16 deletions src/api/app/models/token.rb
@@ -1,23 +1,10 @@
class Token < ApplicationRecord
belongs_to :user, foreign_key: 'user_id', inverse_of: :tokens
belongs_to :user, foreign_key: 'user_id', inverse_of: :service_tokens
belongs_to :package, inverse_of: :tokens

validates :user_id, presence: true
after_create :update_token

def self.find_by_string(token)
token = Token.where(string: token.to_s).includes(:package, :user).first
return unless token && token.user_id
has_secure_token :string

# package found and user has write access
token
end

def update_token
# base64 with a length that is a multiple of 3 avoids trailing "=" chars
self.string = SecureRandom.base64(30) # 30 bytes leads to 40 chars string
save!
end
validates :user_id, presence: true
end

# == Schema Information
Expand All @@ -28,6 +15,7 @@ def update_token
# string :string(255) indexed
# user_id :integer not null, indexed
# package_id :integer indexed
# type :string(255)
#
# Indexes
#
Expand Down
24 changes: 24 additions & 0 deletions src/api/app/models/token/rss.rb
@@ -0,0 +1,24 @@
class Token::Rss < Token
end

# == Schema Information
#
# Table name: tokens
#
# id :integer not null, primary key
# string :string(255) indexed
# user_id :integer not null, indexed
# package_id :integer indexed
# type :string(255)
#
# Indexes
#
# index_tokens_on_string (string) UNIQUE
# package_id (package_id)
# user_id (user_id)
#
# Foreign Keys
#
# tokens_ibfk_1 (user_id => users.id)
# tokens_ibfk_2 (package_id => packages.id)
#
24 changes: 24 additions & 0 deletions src/api/app/models/token/service.rb
@@ -0,0 +1,24 @@
class Token::Service < Token
end

# == Schema Information
#
# Table name: tokens
#
# id :integer not null, primary key
# string :string(255) indexed
# user_id :integer not null, indexed
# package_id :integer indexed
# type :string(255)
#
# Indexes
#
# index_tokens_on_string (string) UNIQUE
# package_id (package_id)
# user_id (user_id)
#
# Foreign Keys
#
# tokens_ibfk_1 (user_id => users.id)
# tokens_ibfk_2 (package_id => packages.id)
#
11 changes: 9 additions & 2 deletions src/api/app/models/user.rb
Expand Up @@ -36,7 +36,8 @@ class User < ApplicationRecord
has_many :comments, dependent: :destroy, inverse_of: :user
has_many :status_messages
has_many :messages
has_many :tokens, dependent: :destroy, inverse_of: :user
has_many :service_tokens, class_name: 'Token::Service', dependent: :destroy, inverse_of: :user
has_one :rss_token, class_name: 'Token::Rss', dependent: :destroy

has_many :reviews, dependent: :nullify, as: :reviewable

Expand All @@ -54,7 +55,7 @@ class User < ApplicationRecord
# users have 0..1 user_registration records assigned to them
has_one :user_registration

has_many :rss_feed_items, -> { order(created_at: :desc) }, class_name: 'Notifications::RssFeedItem', dependent: :destroy
has_many :rss_feed_items, -> { order(created_at: :desc) }, class_name: 'Notification::RssFeedItem', as: :subscriber, dependent: :destroy

scope :all_without_nobody, -> { where("login != ?", nobody_login) }

Expand Down Expand Up @@ -904,6 +905,12 @@ def display_name
address.format
end

def combined_rss_feed_items
Notification::RssFeedItem.where(subscriber: self).or(
Notification::RssFeedItem.where(subscriber: groups)
).order(created_at: :desc, id: :desc).limit(Notification::RssFeedItem::MAX_ITEMS_PER_USER)
end

private

def can_modify_project_internal(project, ignore_lock)
Expand Down
4 changes: 2 additions & 2 deletions src/api/app/views/event_mailer/_actions.text.erb
@@ -1,8 +1,8 @@
Actions:
<% event['actions'].each do |a| -%>
<% if %w(submit maintenance_incident maintenance_release).include? a['type'] -%>
<%= render partial: 'submit_action', locals: { a: a } -%>
<%= render partial: 'event_mailer/submit_action', locals: { a: a } -%>
<% else -%>
<%= render partial: "#{a['type']}_action", locals: { a: a } -%>
<%= render partial: "event_mailer/#{a['type']}_action", locals: { a: a } -%>
<% end -%>
<% end -%>
4 changes: 2 additions & 2 deletions src/api/app/views/event_mailer/request_create.text.erb
Expand Up @@ -5,14 +5,14 @@ Description:
<%= event['description']%>
<%-end-%>
<%= render partial: 'actions', locals: { event: event } %>
<%= render partial: 'event_mailer/actions', locals: { event: event } %>

To REVIEW against the previous version:
osc request show --diff <%= event['number']%>

To ACCEPT the request:
osc request accept <%= event['number']%> --message="reviewed ok."

To DECLINE the request:
osc request decline <%= event['number']%> --message="declined for reason xyz (see ... for background / policy / ...)."

Expand Down
Expand Up @@ -9,4 +9,4 @@ Comment:
<%= event['comment'] %>
<% end -%>
<%= render partial: 'actions', locals: { event: event } %>
<%= render partial: 'event_mailer/actions', locals: { event: event } %>
2 changes: 1 addition & 1 deletion src/api/app/views/event_mailer/review_wanted.text.erb
Expand Up @@ -19,4 +19,4 @@ Review reason set by <%= event['who'] %>:
Request description:
<%= event['description'] %>
<%= render partial: 'actions', locals: { event: event } -%>
<%= render partial: 'event_mailer/actions', locals: { event: event } -%>

0 comments on commit 6660282

Please sign in to comment.