Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #19314 - Notification for subscr. expiring soon #6752

Merged
merged 1 commit into from Mar 22, 2018

Conversation

dLobatog
Copy link
Member

@dLobatog dLobatog commented Apr 19, 2017

class ExpireSoon
class << self
def deliver!
binding.pry

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove debugger entry point binding.pry.

class ExpireSoon
class << self
def deliver!
binding.pry

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove debugger entry point binding.pry.

@@ -0,0 +1,39 @@
module Katello
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file does not belongs to this pr.

:subject => subscription,
:initiator => User.anonymous_admin,
:audience => Notification::AUDIENCE_ADMIN,
:message => ::UINotifications::StringParser.new(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you have the expiry date of the subscription, maybe we should use it for the notification expiry date too?

class << self
def deliver!
subs_expiring_soon.each do |subscription|
next if notification_already_exists?(subscription)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see comments on #6750 around these lines.

::Notification.create!(
:subject => subscription,
:initiator => User.anonymous_admin,
:audience => Notification::AUDIENCE_ADMIN,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMHO this should be the taxonomy users/admins?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but how do you set this? AUDIENCE_TAXONOMY is subject.user_ids - if subject is the subscription, it will not work. The subject can't be the taxonomy


def subs_expiring_soon
result = []
Katello::Subscription.unscoped.all.each do |subscription|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there no better query than go over all? at least I would use select instead of the next statement below.

{
group: _('Proxies'),
name: 'pulp_low_disk_space',
message: _("%{subject}'s disk is %{percentage} full. Since this proxy is running Pulp, it needs disk space to publish content views. Please ensure the disk does not get full."),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not related to this pr.

@@ -0,0 +1,13 @@
namespace :proxy_disk_space do
desc <<-END_DESC
Check the proxy disk space based on Pulp API results and create notifications if
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

@@ -0,0 +1,13 @@
namespace :subscriptions do
desc <<-END_DESC
Check the proxy disk space based on Pulp API results and create notifications if
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong description titles etc.

@dLobatog
Copy link
Member Author

Updated so that:

  • Notifications are expired according to the subscription expiration date
  • Finding expiring_soon subs is simplified by using .select
  • Fixed the typos in the rake task description
  • Removed the Pulp low disk space notification commit (https://github.com/Katello/katello/pull/6750/files) from here.

cc @ohadlevy thanks for taking a look before

@jlsherrill
Copy link
Member

I'm getting an error when running the rake task, i think because Subscription is not taxable:

NoMethodError: undefined method `user_ids' for #<Katello::Subscription:0x00000003164bc8>
/home/vagrant/.rvm/gems/ruby-2.2.4/gems/activemodel-4.2.8/lib/active_model/attribute_methods.rb:433:in `method_missing'
/home/vagrant/git/foreman/app/models/notification.rb:43:in `subscriber_ids'
/home/vagrant/git/foreman/app/models/notification.rb:65:in `set_notification_recipients'
/home/vagrant/.rvm/gems/ruby-2.2.4/gems/activesupport-4.2.8/lib/active_support/callbacks.rb:432:in `block in make_lambda'

@akofink
Copy link
Member

akofink commented Jul 26, 2017

@dLobatog any update here?


# Only create notifications if there isn't a scheduled job
SendExpireSoonNotifications.perform_later if scheduled_job.blank?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 trailing blank lines detected.


def notification_already_exists?(subject)
subs_expiration_notification = Notification.unscoped.find_by(:subject => subject)
return false unless subs_expiration_notification.present?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use if subs_expiration_notification.blank? instead of unless subs_expiration_notification.present?.

self.class.set(:wait => 12.hours).perform_later
end

def perform

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation detected.

@@ -0,0 +1,9 @@
class SendExpireSoonNotifications < ActiveJob::Base
after_perform do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use 2 (not 3) spaces for indentation.

@@ -0,0 +1,9 @@
class SendExpireSoonNotifications < ActiveJob::Base

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jobs should subclass ApplicationJob.

@dLobatog dLobatog force-pushed the 19314 branch 2 times, most recently from d9f7977 to 51af787 Compare August 8, 2017 15:32
Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, @jlsherrill @akofink , the job is scheduled every 12 hours with ActiveJob now

@ares
Copy link
Contributor

ares commented Oct 16, 2017

it seems rubocop is failing, could someone with permissions set WoC? since we don't have the output in jenkins anymore, I'm retriggering tests [test]

Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebased, @ares I think the only failure from hound is that "Jobs should subclass ApplicationJob." - I do agree (it's a convention, not really necessary) but I introduce that class in https://github.com/theforeman/foreman/pull/4240/files in Foreman itself, so at this moment it isn't available here to inherit, and I don't think it's a good idea to have a ApplicationJob class namespaced to be exclusive to Katello.

Best course would be to use ApplicationJob in here when the core PR is merged, but it shouldn't block this one.

@ares
Copy link
Contributor

ares commented Dec 4, 2017

this needs rebase again, ActiveJob is now in core too, but maybe it's better to wait until #6750 gets in

Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ares Updated to use ApplicationJob and ::Foreman::Application.dynflow.config.on_init do |world| like #6750

@ares
Copy link
Contributor

ares commented Jan 2, 2018

LGTM, it would be great if someone from katello could take a look

::Notification.create!(
:subject => subscription,
:initiator => User.anonymous_admin,
:audience => Notification::AUDIENCE_TAXONOMY,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this require some update to foreman? I'm getting an error that this constant isn't defined:

NameError: uninitialized constant Notification::AUDIENCE_TAXONOMY
	from /home/vagrant/git/foreman_hooks/lib/foreman_hooks/as_dependencies_hook.rb:4:in `load_missing_constant'
	from /home/vagrant/git/katello/app/services/katello/ui_notifications/subscriptions/expire_soon.rb:13:in `block in deliver!'
	from /home/vagrant/git/katello/app/services/katello/ui_notifications/subscriptions/expire_soon.rb:7:in `each'
	from /home/vagrant/git/katello/app/services/katello/ui_notifications/subscriptions/expire_soon.rb:7:in `deliver!'

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, there's no such audience, I guess we need to find administrators of a given org for which there's no good option atm, @jlsherrill can we find a user who uploaded the manifest for the organization somehow?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't track who has uploaded or refreshed a manifest. I would suggest notifying anyone who has access to do that for the given org via the 'import_manifest' permission

Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self, I'll make a method notification_recipient_ids on the Subscription object, which contains all of the users with import_manifest permissions. Changing the audience to Notification::AUDIENCE_SUBJECT will also be required.

@ares
Copy link
Contributor

ares commented Jan 15, 2018

I think it's not easy to find easily such recipients. There are no relations like this, especially if you consider filtered permissions, where the assignment might be scoped e.g. with "name ~ my". Perhaps we could only search for user who don't have such permission filtered.

Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ares I've updated this to use the same permission checking as we do on the API to upload a manifest (e.g: if you can upload a manifest to this organization, you get the notification).

I do realize it's a relatively expensive operation, but I don't see any other way of dealing with it. The task ought to run only twice a day though, so I hope that is not a problem. Another advantage is that by leveraging allowed_to? vs writing complicated permissions logic here, if the permissions model change in Foreman core & that method changes, we're covered and this should work - which wouldn't if I write custom logic here.

@ares
Copy link
Contributor

ares commented Jan 22, 2018

@dLobatog I don't think you need to write custom logic, it's fine to ignore filtering conditions, but I believe it should call user.can?(permission) instead of user.allowed_to?(controller_action), the first does not care about specific path that might change but check for permission assignment

Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ares I could use can? but as you say it would mean ignoring specific permissions 'search' scope. With .allowed_to?, I'm able to pass the specific object I want to check the permission for, like:

        user.allowed_to?(
          :controller => 'katello/api/v2/subscriptions',
          :action => 'import',
          :organization_id => organization_id
        )

So it's more precise, I can know for sure the users who should be able to see the notification.

@ares
Copy link
Contributor

ares commented Jan 22, 2018

@dLobatog the allowed_to example that you used only verified that user has permission to access "katello/api/v2/subscriptions/import" route based on mapping in lib/katello/permissions/. The fact you pass organization_id does not matter, parameters are ignored by allowed_to completely. In fact the result is equivalent to user.can?(:create_subscription) which returns true if there's any filter with the this permission assigned to the user. Even better, can? does not have to parse the list of routes and permissions. Using allowed_to? does not make sense since we are really interested in the permission, not whether user has access to specific route. Imagine there would be two routes how you can import manifest...

group: N_('Subscriptions'),
name: 'subs_expire_soon',
message: N_('%{expiring_subs} subscriptions are going to expire soon in %{subject}. Please renew them before they expire to guarantee your hosts will continue receiving content.'),
level: 'warning',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid comma after the last item of a hash.

end


def actions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use empty lines between method definitions.

)
end


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra blank line detected.

def notification_already_exists?(subject)
subs_expiration_notification = Notification.unscoped.find_by(:subject => subject)
return false if subs_expiration_notification.blank?
subs_expiration_notification.notification_blueprint == blueprint

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operator == used in void context.

group: N_('Subscriptions'),
name: 'subs_expire_soon',
message: N_('%{expiring_subs} subscriptions are going to expire soon in %{subject}. Please renew them before they expire to guarantee your hosts will continue receiving content.'),
level: 'warning',

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid comma after the last item of a hash.

end


def actions

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use empty lines between method definitions.

)
end


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra blank line detected.

def notification_already_exists?(subject)
subs_expiration_notification = Notification.unscoped.find_by(:subject => subject)
return false if subs_expiration_notification.blank?
subs_expiration_notification.notification_blueprint == blueprint

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operator == used in void context.

Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jlsherrill @beav Updated, now it creates a notification per organization. It shows the # of subs about to expire in the notification, which links to the search page for that.

Unfortunately for admin users, this will mean that the following case may happen:

  • admin is in org A, B, C
  • admin chooses in the UI to be in org A. By design the /subscriptions page forces you to choose.
  • admin gets notifications about all 3 organizations with subs about to expire.
  • admin clicks on the link for the subs about to expire in A - it works well.
  • admin clicks on the link for the subs about to expire in B - since admin is in org A in the UI, it will not show the correct subs. admin needs to change to org B in the UI, then click on the notification.

I hope it's not a dealbreaker, as it's an unlikely case, and moving the user away from the current organization by clicking on the notification seems like an anti-pattern.

screenshot from 2018-03-01 21-08-32

@dLobatog
Copy link
Member Author

dLobatog commented Mar 5, 2018

[test katello]

1 similar comment
@ares
Copy link
Contributor

ares commented Mar 6, 2018

[test katello]

@jyejare
Copy link

jyejare commented Mar 8, 2018

@dLobatog , Does this PR helps to show an expiration notification before 120 days? Does the notification depend upon the place where the foreman is being accessed (e.g for me, its IST time) ?

@dLobatog
Copy link
Member Author

dLobatog commented Mar 8, 2018

@jyejare Yes, you may change the 120 to any other value in app/models/katello/pool (DAYS_EXPIRING_SOON) although it's not meant to be touched by users. But it can be useful when testing. You can also run Katello::UINotifications::Subscriptions::ExpireSoon.deliver! to ensure it runs the check - otherwise it'll only run every 12 hours.

expiring_soon is an old issue, it should be checking Date.today.utc https://github.com/Katello/katello/blob/master/app/models/katello/pool.rb#L51

@jyejare
Copy link

jyejare commented Mar 8, 2018

@dLobatog @ares , Suggested changes in the content of notification to include the days/date.

Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ares @jyejare @jlsherrill @beav

I've updated the notification to include the number of days. I can't be more specific as I'm creating a notification per organization. This means there are multiple subscriptions there, which may vary in their expiring date, so I can't just say 'will expire in X days' as I did before with the 'per-subscription' notification.
screenshot from 2018-03-09 11-24-32

To test this:

rake db:seed

Then on rake console:

class Organization
  def expiring_subscriptions
    subscriptions
  end
end
Organization.all.each { |o| Notification.where(:subject => o).destroy_all }
Katello::UINotifications::Subscriptions::ExpireSoon.deliver!

def notification_recipients_ids
User.unscoped.all.find_all do |user|
user.can?(:import_manifest, self)
end.pluck(:id)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid chaining a method call on a do...end block.

@dLobatog
Copy link
Member Author

dLobatog commented Mar 9, 2018

[test katello]

@dLobatog
Copy link
Member Author

#7234

Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only tests failing are React ones, seems that the snapshot needs to be updated for ListView, can't be caused by this one, probably theforeman/foreman@e26f63d

@dLobatog
Copy link
Member Author

I was missing - https://github.com/Katello/katello/pull/7228/files on my branch, rebasing & pushing again..

@jyejare
Copy link

jyejare commented Mar 14, 2018

@dLobatog , @ares , I tried accessing notifications via org-admin user and via another admin, but I cant ! Looks like there is some issue with users.

@dLobatog dLobatog force-pushed the 19314 branch 2 times, most recently from e72d549 to 343e7b8 Compare March 21, 2018 21:46
The notifications drawer should show a warning when some subscription is
about to expire. "expiring_soon?" defined in the subscriptions.rb code
says this is 120 days before it expires.
Copy link
Member Author

@dLobatog dLobatog left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jyejare Thanks for the feedback, it definitely did NOT work for org admins, there was a bug in the notification_recipients_ids method.

You also asked to configure the expire soon interval, so thanks for that too 😄 Can you check now? It should work 100% fine now.


def notification_recipients_ids
users = User.unscoped.all.find_all do |user|
user.can?(:import_manifest) && user.can?(:view_organizations, self)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jyejare The problem I had was that previously I was calling user.can?(:import_manifest, self) here. This would not work, because import_manifest is a permission for Subscription, not for Organization. Hence, only admins would return 'true' for that.

tl;dr - It should be fine now for org admins

@jyejare
Copy link

jyejare commented Mar 22, 2018

Tests Performed:

1. Notification should be displayed for expiring subscriptions within given days in settings.
2. Notification should only be shown for expiring subscriptions within given days in settings.
3. Notification should not be shown for subscriptions not expiring within given days in settings.
4. Notification body text should be correct.
5. Notification should be shown for org-admin users as well.
6. No duplication of notifications on multiple time delivers.
7. Pulp Notification for disk storage full with the percent of disk full.

QE ACK.

Copy link
Member

@jlsherrill jlsherrill left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dLobatog !

@jlsherrill jlsherrill merged commit 390b096 into Katello:master Mar 22, 2018
@dLobatog dLobatog deleted the 19314 branch April 3, 2018 08:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
9 participants