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

Model changes for alerts #13233

Merged
merged 1 commit into from
Jan 18, 2017
Merged

Model changes for alerts #13233

merged 1 commit into from
Jan 18, 2017

Conversation

moolitayer
Copy link

@moolitayer moolitayer commented Dec 18, 2016

Model changes extending ManageIQ alerting capability to support operation teams use cases:

  • A ticketing system where privileged users could assign alerts for users to work on, assigned users can acknowledge them and everyone can comment on them
  • Alerts could be coming in from external systems and can have severity / informational url attached to them
  • Alerts can represent a situation going bad (e.g agent reports overheating CPU) and they can represent the situation returning to normal again (e.g agent reports CPU temperature is normal again)

These new entities are used in #11962 and #13025

@moolitayer moolitayer changed the title Alert tickets Alert Model changes Dec 18, 2016
@moolitayer moolitayer force-pushed the alert_tickets branch 3 times, most recently from 6ea83f2 to f1a65dd Compare December 18, 2016 16:47
@moolitayer moolitayer mentioned this pull request Dec 18, 2016
@moolitayer
Copy link
Author

@miq-bot add_label enhancement
@simon3z FYI

@moolitayer moolitayer force-pushed the alert_tickets branch 3 times, most recently from dedf472 to a0083fc Compare December 19, 2016 13:37
@moolitayer
Copy link
Author

@simon3z @Fryguy please review

@moolitayer
Copy link
Author

@simon3z PTAL

@moolitayer moolitayer changed the title Alert Model changes Model changes for alerts Dec 29, 2016
Copy link
Member

@gtanzillo gtanzillo left a comment

Choose a reason for hiding this comment

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

This looks good to me 👍 @Fryguy are you ok with merging this?

virtual_column :assignee, :type => :string

def assignee
miq_alert_status_actions.where(:action_type => %w(assign unassign)).last.try(:assignee)
Copy link
Contributor

Choose a reason for hiding this comment

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

@moolitayer no need for explicit ordering here? Is there a guarantee for the actions to be ordered or is it just a coincidence?

Also, to simplify, isn't unassign just an assign to nil? Can't you just use assign?

Copy link
Author

Choose a reason for hiding this comment

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

  • The order comes from the association there is a test for this here

  • I chose to use unassign since I must have an unacknowledge, for consistency

Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps encapsulate the :action_type => %w(assign unassign), or even more of this logic, in a MiqAlertStatusAction scope.

Ordered association: neat, though I've seen a recommendation to avoid implicit ordering, or capture it in separate scope: http://blog.carbonfive.com/2016/11/16/rails-database-best-practices/.

LGTM either way.

Copy link
Author

Choose a reason for hiding this comment

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

Perhaps encapsulate the :action_type => %w(assign unassign), or even more of this logic, in a MiqAlertStatusAction scope.

I could not find an elegant way of doing that, do you have a suggestion?

Ordered association: neat, though I've seen a recommendation to avoid implicit ordering, or capture it in separate scope: http://blog.carbonfive.com/2016/11/16/rails-database-best-practices/.

LGTM either way.

This seems to be a good use case since I do not have a consumer that would require a different order

Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps encapsulate the :action_type => %w(assign unassign), or even more of this logic, in a MiqAlertStatusAction scope.

Yeah, I forgot it's not purely actions responsibility, it's only actions that belong to this status, which is easier expressed on this side.
The most I can think of (untested):

# in MiqAlertStatusAction
  scope :assignee_changes, -> { where(:action_type => %w(assign unassign)) }

# in MiqAlertStatus
  def assignee
    miq_alert_status_actions.assignee_changes.last.try(:assignee)

Fine as-is.

@moolitayer
Copy link
Author

@enoodle @cben @zeari please review

Copy link
Contributor

@cben cben left a comment

Choose a reason for hiding this comment

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

Looks pretty good. Didn't carefully review tests.

  • How is severity used? Do you plan a severity-changing action?
  • How is ancestry used?
  • I'm curious, why maintain acknowledged column in sync on miq_alert_statuses vs computing last ack/unack like you did for assignee?

virtual_column :assignee, :type => :string

def assignee
miq_alert_status_actions.where(:action_type => %w(assign unassign)).last.try(:assignee)
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps encapsulate the :action_type => %w(assign unassign), or even more of this logic, in a MiqAlertStatusAction scope.

Ordered association: neat, though I've seen a recommendation to avoid implicit ordering, or capture it in separate scope: http://blog.carbonfive.com/2016/11/16/rails-database-best-practices/.

LGTM either way.

@@ -0,0 +1,30 @@
class MiqAlertStatusAction < ApplicationRecord
Copy link
Contributor

Choose a reason for hiding this comment

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

I know we don't do comments in MIQ :-| but IMHO a few words on a model are worth it.
Specifically, mentioning that this table is intended to be append-only would be useful.

Copy link
Author

Choose a reason for hiding this comment

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

I'd be interested in having that enforced, did not find a way to do that. @kbrock do we have a mechanism for read only records?
They should only be created & deleted but never updated

Copy link
Member

Choose a reason for hiding this comment

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

We've been overriding active record callbacks.
I'm not thrilled with this method, but that is what we've been doing.
Maybe @Fryguy or @chrisarcand has an alternative way

Copy link
Member

Choose a reason for hiding this comment

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

It's easy to mark a relation as read-only, but read only implies you can't update or delete records. I believe you'll have to utilize callbacks as @kbrock mentions if you want to allow deletes but not updates.

Copy link
Author

Choose a reason for hiding this comment

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

@chrisarcand that might be exactly what I need, I need to allow deletion of status actions only when the owning status is destroyed. I could not find a way to use readonly except as a callback:

def readonly?
  !new_record?
end

I have a feeling you meant something else?

after_save :update_status_acknowledgement

def only_assignee_can_acknowledge
if action_type == 'acknowledge' && (miq_alert_status.nil? || miq_alert_status.assignee.id != user.id)
Copy link
Contributor

Choose a reason for hiding this comment

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

why allow miq_alert_status.nil?

Copy link
Contributor

Choose a reason for hiding this comment

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

ah, you're not "allowing" it to be nil, you're considering it an error.
not sure it's needed (you have validation it's present, you assume it in other places) but harmless.

elsif "acknowledge" == action_type
miq_alert_status.update(:acknowledged => true)
end
end
Copy link
Contributor

Choose a reason for hiding this comment

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

If a non-assignee tries "acknowledge", is only_assignee_can_acknowledge validated before or after this function? Could .update(:acknowledged => true) write to DB before validation failed?

class MiqAlertStatus < ApplicationRecord
SEVERITY_LEVELS = %w(error warning info).freeze
Copy link
Contributor

Choose a reason for hiding this comment

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

do you want :accept => SEVERITY_LEVELS validation?

Copy link
Author

Choose a reason for hiding this comment

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

Nice! I seem to have dropped it by mistake. Added a test as well


def update_status_acknowledgement
if %w(assign unassign unacknowledge).include?(action_type)
miq_alert_status.update(:acknowledged => false)
Copy link
Contributor

@cben cben Jan 10, 2017

Choose a reason for hiding this comment

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

  • assign > ack > unassign would remove ack preserving only_assignee_can_acknowledge invariant. Good.

Can you re-"assign" to other (or even same) person without "unassign" first?

Copy link
Author

Choose a reason for hiding this comment

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

yes you can.

end

def assigned?
!assignee.nil?
Copy link

@zeari zeari Jan 10, 2017

Choose a reason for hiding this comment

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

assignee.present? is much nicer

@moolitayer
Copy link
Author

moolitayer commented Jan 10, 2017

How is severity used? Do you plan a severity-changing action?

in the pushing side openshift collector logic should allow changing severity based on metadata, details not yet finalized.
can you explain what you mean by severity-changing action?

How is ancestry used?

ancestry will be used to link an alert status with its resolver. More details on it will be added in the resolve collection patch, but wanted to add the base here

I'm curious, why maintain acknowledged column in sync on miq_alert_statuses vs computing last ack/unack like you did for assignee?

Turns out it is quite hard to calculate, since we need to look at entire history of ack unack assign unassign actions Meaning there needs to be an ack that is not followed by unack assign or unassign. Since this property should be supplied in the API for every miq_alert_status I found It helpful to save on the alert status.

@moolitayer
Copy link
Author

@cben @zeari thanks for reviewing. addressed comments, PTAL

after_save :update_status_acknowledgement

def only_assignee_can_acknowledge
if action_type == 'acknowledge' && (miq_alert_status.nil? || miq_alert_status.assignee.id != user.id)
Copy link
Contributor

Choose a reason for hiding this comment

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

ah, you're not "allowing" it to be nil, you're considering it an error.
not sure it's needed (you have validation it's present, you assume it in other places) but harmless.

let(:alert) { FactoryGirl.create(:miq_alert_status) }
let(:user1) { FactoryGirl.create(:user, :name => 'user1') }
let(:user2) { FactoryGirl.create(:user, :name => 'user2') }
let(:acknowledgement_ticket) do
Copy link
Contributor

Choose a reason for hiding this comment

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

s/ticket/action/g

virtual_column :assignee, :type => :string

def assignee
miq_alert_status_actions.where(:action_type => %w(assign unassign)).last.try(:assignee)
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps encapsulate the :action_type => %w(assign unassign), or even more of this logic, in a MiqAlertStatusAction scope.

Yeah, I forgot it's not purely actions responsibility, it's only actions that belong to this status, which is easier expressed on this side.
The most I can think of (untested):

# in MiqAlertStatusAction
  scope :assignee_changes, -> { where(:action_type => %w(assign unassign)) }

# in MiqAlertStatus
  def assignee
    miq_alert_status_actions.assignee_changes.last.try(:assignee)

Fine as-is.

@moolitayer
Copy link
Author

closed by mistake

@moolitayer moolitayer reopened this Jan 11, 2017
@miq-bot
Copy link
Member

miq-bot commented Jan 11, 2017

Checked commit moolitayer@8056603 with ruby 2.2.5, rubocop 0.37.2, and haml-lint 0.16.1
8 files checked, 1 offense detected

db/migrate/20161113091851_add_miq_alert_status_actions.rb

@moolitayer moolitayer closed this Jan 11, 2017
@moolitayer moolitayer reopened this Jan 11, 2017
@moolitayer
Copy link
Author

@Fryguy PTAL

@moolitayer
Copy link
Author

@simon3z @zeari PTAL

@moolitayer
Copy link
Author

@Fryguy PTAL


def update_status_acknowledgement
if %w(assign unassign unacknowledge).include?(action_type)
miq_alert_status.update(:acknowledged => false)
Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, I see, assign also clears ack, so assign > ack > reassign keeps the invariant. LGTM.

@simon3z
Copy link
Contributor

simon3z commented Jan 17, 2017

@moolitayer @cben what's the reasons to have different approaches for assignment vs acknowledgement?

IIUC for the assignee you have a virtual column on MiqAlertStatus and for the acknowledgement instead you update a real column on MiqAlertStatus as after_save of MiqAlertStatusAction.

Couldn't you have a virtual column for acknowledgement as well?

validates :assignee, :absence => true, :unless => "action_type == 'assign'"
validate :only_assignee_can_acknowledge

after_save :update_status_acknowledgement
Copy link
Author

Choose a reason for hiding this comment

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

@kbrock this saves a column on the owner of this action. Does it make sense to do it this way

@moolitayer
Copy link
Author

@simon3z having a column for acknowledged is an optimization
I found that calculating it is complex and costly
ack = true iff:
there was an ack action, and since then there have been at most comment actions

@simon3z
Copy link
Contributor

simon3z commented Jan 17, 2017

LGTM 👍
ping @Fryguy @chessbyte @gtanzillo @kbrock

belongs_to :miq_alert
belongs_to :resource, :polymorphic => true
has_many :miq_alert_status_actions, -> { order "created_at" }, :dependent => :destroy
virtual_column :assignee, :type => :string
Copy link
Member

Choose a reason for hiding this comment

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

@moolitayer In a follow up PR you can optimize this with a :uses => :miq_alert_status_actions The :uses clause lets the reporting engine know to preload that. Alternately there might be a pure SQL way to do this, which perhaps @kbrock can help you with via the :arel => option.

Copy link
Author

Choose a reason for hiding this comment

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

submitted in #13553

validates :miq_alert_status, :presence => true
validates :comment, :presence => true, :if => "action_type == 'comment'"
validates :assignee, :presence => true, :if => "action_type == 'assign'"
validates :assignee, :absence => true, :unless => "action_type == 'assign'"
Copy link
Member

Choose a reason for hiding this comment

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

I feel like there's a nicer way to do these conditionals without giving SQL fragments. @kbrock ?

Copy link
Member

Choose a reason for hiding this comment

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

ugh. sorry. MIA on this one

@moolitayer using a lambda here would make this better - essentially moving this check to ruby. well, at least the if part of the check, presence is in the database.

Copy link
Author

Choose a reason for hiding this comment

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

Sorry for taking long on this one, was unavailable for a while. Sent fix in #13797

@Fryguy
Copy link
Member

Fryguy commented Jan 18, 2017

@moolitayer I have a couple comments, but they can be addressed in followup PRs, perhaps with some guidance from @kbrock .

@Fryguy Fryguy merged commit 02901ea into ManageIQ:master Jan 18, 2017
@Fryguy Fryguy added this to the Sprint 53 Ending Jan 30, 2017 milestone Jan 18, 2017
FactoryGirl.define do
factory :miq_alert_status_action do
action_type 'comment'
comment 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
Copy link
Member

Choose a reason for hiding this comment

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

I'm disappointed @chrisarcand and @Fryguy didn't see this. 👍 @moolitayer

Copy link
Member

Choose a reason for hiding this comment

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

I showed this to you because I was literally, perfectly rick-rolled looking through commits working on something else. 😅

Copy link
Author

Choose a reason for hiding this comment

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

:bowtie:
The SOP shows up on the monitoring screen (it's supposed to help you solve the alert)
and we had a couple of rick rolling demos of it which was too much so I removed it from setup examples...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants