trigger_agent.rb 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. module Agents
  2. class TriggerAgent < Agent
  3. cannot_be_scheduled!
  4. VALID_COMPARISON_TYPES = %w[regex !regex field<value field<=value field==value field!=value field>=value field>value]
  5. description <<-MD
  6. Use a TriggerAgent to watch for a specific value in an Event payload.
  7. The `rules` array contains hashes of `path`, `value`, and `type`. The `path` value is a dotted path through a hash in [JSONPaths](http://goessner.net/articles/JsonPath/) syntax.
  8. The `type` can be one of #{VALID_COMPARISON_TYPES.map { |t| "`#{t}`" }.to_sentence} and compares with the `value`. Note that regex patterns are matched case insensitively. If you want case sensitive matching, prefix your pattern with `(?-i)`.
  9. The `value` can be a single value or an array of values. In the case of an array, if one or more values match then the rule matches.
  10. All rules must match for the Agent to match. The resulting Event will have a payload message of `message`. You can use liquid templating in the `message, have a look at the [Wiki](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) for details.
  11. Set `keep_event` to `true` if you'd like to re-emit the incoming event, optionally merged with 'message' when provided.
  12. Set `expected_receive_period_in_days` to the maximum amount of time that you'd expect to pass between Events being received by this Agent.
  13. MD
  14. event_description <<-MD
  15. Events look like this:
  16. { "message": "Your message" }
  17. MD
  18. def validate_options
  19. unless options['expected_receive_period_in_days'].present? && options['rules'].present? &&
  20. options['rules'].all? { |rule| rule['type'].present? && VALID_COMPARISON_TYPES.include?(rule['type']) && rule['value'].present? && rule['path'].present? }
  21. errors.add(:base, "expected_receive_period_in_days, message, and rules, with a type, value, and path for every rule, are required")
  22. end
  23. errors.add(:base, "message is required unless 'keep_event' is 'true'") unless options['message'].present? || keep_event?
  24. errors.add(:base, "keep_event, when present, must be 'true' or 'false'") unless options['keep_event'].blank? || %w[true false].include?(options['keep_event'])
  25. end
  26. def default_options
  27. {
  28. 'expected_receive_period_in_days' => "2",
  29. 'keep_event' => 'false',
  30. 'rules' => [{
  31. 'type' => "regex",
  32. 'value' => "foo\\d+bar",
  33. 'path' => "topkey.subkey.subkey.goal",
  34. }],
  35. 'message' => "Looks like your pattern matched in '{{value}}'!"
  36. }
  37. end
  38. def working?
  39. last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
  40. end
  41. def receive(incoming_events)
  42. incoming_events.each do |event|
  43. opts = interpolated(event)
  44. match = opts['rules'].all? do |rule|
  45. value_at_path = Utils.value_at(event['payload'], rule['path'])
  46. rule_values = rule['value']
  47. rule_values = [rule_values] unless rule_values.is_a?(Array)
  48. match_found = rule_values.any? do |rule_value|
  49. case rule['type']
  50. when "regex"
  51. value_at_path.to_s =~ Regexp.new(rule_value, Regexp::IGNORECASE)
  52. when "!regex"
  53. value_at_path.to_s !~ Regexp.new(rule_value, Regexp::IGNORECASE)
  54. when "field>value"
  55. value_at_path.to_f > rule_value.to_f
  56. when "field>=value"
  57. value_at_path.to_f >= rule_value.to_f
  58. when "field<value"
  59. value_at_path.to_f < rule_value.to_f
  60. when "field<=value"
  61. value_at_path.to_f <= rule_value.to_f
  62. when "field==value"
  63. value_at_path.to_s == rule_value.to_s
  64. when "field!=value"
  65. value_at_path.to_s != rule_value.to_s
  66. else
  67. raise "Invalid type of #{rule['type']} in TriggerAgent##{id}"
  68. end
  69. end
  70. end
  71. if match
  72. if keep_event?
  73. payload = event.payload.dup
  74. payload['message'] = opts['message'] if opts['message'].present?
  75. else
  76. payload = { 'message' => opts['message'] }
  77. end
  78. create_event :payload => payload
  79. end
  80. end
  81. end
  82. def keep_event?
  83. boolify(interpolated['keep_event'])
  84. end
  85. end
  86. end