Aucune description http://j1x-huginn.herokuapp.com

event_formatting_agent.rb 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. module Agents
  2. class EventFormattingAgent < Agent
  3. cannot_be_scheduled!
  4. description <<-MD
  5. An Event Formatting Agent allows you to format incoming Events, adding new fields as needed.
  6. For example, here is a possible Event:
  7. {
  8. "high": {
  9. "celsius": "18",
  10. "fahreinheit": "64"
  11. },
  12. "date": {
  13. "epoch": "1357959600",
  14. "pretty": "10:00 PM EST on January 11, 2013"
  15. },
  16. "conditions": "Rain showers",
  17. "data": "This is some data"
  18. }
  19. You may want to send this event to another Agent, for example a Twilio Agent, which expects a `message` key.
  20. You can use an Event Formatting Agent's `instructions` setting to do this in the following way:
  21. "instructions": {
  22. "message": "Today's conditions look like <$.conditions> with a high temperature of <$.high.celsius> degrees Celsius.",
  23. "subject": "$.data"
  24. }
  25. JSONPaths must be between < and > . Make sure that you don't use these symbols anywhere else.
  26. Events generated by this possible Event Formatting Agent will look like:
  27. {
  28. "message": "Today's conditions look like Rain showers with a high temperature of 18 degrees Celsius.",
  29. "subject": "This is some data"
  30. }
  31. In `matchers` setting you can perform regular expression matching against contents of events and expand the match data for use in `instructions` setting. Here is an example:
  32. {
  33. "matchers": [
  34. {
  35. "path": "$.date.pretty",
  36. "regexp": "\\A(?<time>\\d\\d:\\d\\d [AP]M [A-Z]+)",
  37. "to": "pretty_date",
  38. }
  39. ]
  40. }
  41. This virtually merges the following hash into the original event hash:
  42. "pretty_date": {
  43. "time": "10:00 PM EST",
  44. "0": "10:00 PM EST on January 11, 2013"
  45. "1": "10:00 PM EST",
  46. }
  47. So you can use it in `instructions` like this:
  48. "instructions": {
  49. "message": "Today's conditions look like <$.conditions> with a high temperature of <$.high.celsius> degrees Celsius according to the forecast at <$.pretty_date.time>.",
  50. "subject": "$.data"
  51. }
  52. If you want to retain original contents of events and only add new keys, then set `mode` to `merge`, otherwise set it to `clean`.
  53. By default, the output event will have `agent` and `created_at` fields added as well, reflecting the original Agent type and Event creation time. You can skip these outputs by setting `skip_agent` and `skip_created_at` to `true`.
  54. To CGI escape output (for example when creating a link), prefix with `escape`, like so:
  55. {
  56. "message": "A peak was on Twitter in <$.group_by>. Search: https://twitter.com/search?q=<escape $.group_by>"
  57. }
  58. MD
  59. event_description "User defined"
  60. after_save :clear_matchers
  61. def validate_options
  62. errors.add(:base, "instructions, mode, skip_agent, and skip_created_at all need to be present.") unless options['instructions'].present? and options['mode'].present? and options['skip_agent'].present? and options['skip_created_at'].present?
  63. validate_matchers
  64. end
  65. def default_options
  66. {
  67. 'instructions' => {
  68. 'message' => "You received a text <$.text> from <$.fields.from>",
  69. 'some_other_field' => "Looks like the weather is going to be <$.fields.weather>"
  70. },
  71. 'matchers' => [],
  72. 'mode' => "clean",
  73. 'skip_agent' => "false",
  74. 'skip_created_at' => "false"
  75. }
  76. end
  77. def working?
  78. !recent_error_logs?
  79. end
  80. def receive(incoming_events)
  81. incoming_events.each do |event|
  82. formatted_event = options['mode'].to_s == "merge" ? event.payload.dup : {}
  83. payload = perform_matching(event.payload)
  84. options['instructions'].each_pair {|key, value| formatted_event[key] = Utils.interpolate_jsonpaths(value, payload) }
  85. formatted_event['agent'] = Agent.find(event.agent_id).type.slice!(8..-1) unless options['skip_agent'].to_s == "true"
  86. formatted_event['created_at'] = event.created_at unless options['skip_created_at'].to_s == "true"
  87. create_event :payload => formatted_event
  88. end
  89. end
  90. private
  91. def validate_matchers
  92. matchers = options['matchers'] or return
  93. unless matchers.is_a?(Array)
  94. errors.add(:base, "matchers must be an array if present")
  95. return
  96. end
  97. matchers.each do |matcher|
  98. unless matcher.is_a?(Hash)
  99. errors.add(:base, "each matcher must be a hash")
  100. next
  101. end
  102. regexp, path, to = matcher.values_at(*%w[regexp path to])
  103. if regexp.present?
  104. begin
  105. Regexp.new(regexp)
  106. rescue
  107. errors.add(:base, "bad regexp found in matchers: #{regexp}")
  108. end
  109. else
  110. errors.add(:base, "regexp is mandatory for a matcher and must be a string")
  111. end
  112. errors.add(:base, "path is mandatory for a matcher and must be a string") if !path.present?
  113. errors.add(:base, "to must be a string if present in a matcher") if to.present? && !to.is_a?(String)
  114. end
  115. end
  116. def perform_matching(payload)
  117. matchers.inject(payload.dup) { |hash, matcher|
  118. matcher[hash]
  119. }
  120. end
  121. def matchers
  122. @matchers ||=
  123. if matchers = options['matchers']
  124. matchers.map { |matcher|
  125. regexp, path, to = matcher.values_at(*%w[regexp path to])
  126. re = Regexp.new(regexp)
  127. proc { |hash|
  128. mhash = {}
  129. value = Utils.value_at(hash, path)
  130. if value.is_a?(String) && (m = re.match(value))
  131. m.to_a.each_with_index { |s, i|
  132. mhash[i.to_s] = s
  133. }
  134. m.names.each do |name|
  135. mhash[name] = m[name]
  136. end if m.respond_to?(:names)
  137. end
  138. if to
  139. case value = hash[to]
  140. when Hash
  141. value.update(mhash)
  142. else
  143. hash[to] = mhash
  144. end
  145. else
  146. hash.update(mhash)
  147. end
  148. hash
  149. }
  150. }
  151. else
  152. []
  153. end
  154. end
  155. def clear_matchers
  156. @matchers = nil
  157. end
  158. end
  159. end