Merge pull request #410 from knu/provide_access_to_agent

EventFormattingAgent: Provide access to upstream agent

Andrew Cantino 10 years ago
parent
commit
e746bee3dc

+ 1 - 1
app/assets/javascripts/application.js.coffee.erb

@@ -68,7 +68,7 @@ $(document).ready ->
68 68
     setTimeout((-> $(".flash").slideUp(-> $(".flash").remove())), 5000)
69 69
 
70 70
   # Help popovers
71
-  $('.hover-help').popover(trigger: 'hover')
71
+  $('.hover-help').popover(trigger: 'hover', html: true)
72 72
 
73 73
   # Agent Navigation
74 74
   $agentNavigate = $('#agent-navigate')

+ 6 - 0
app/assets/stylesheets/application.css.scss.erb

@@ -156,6 +156,12 @@ span.not-applicable:after {
156 156
   top: 2px;
157 157
 }
158 158
 
159
+.popover {
160
+  dd {
161
+    margin-left: 1em;
162
+  }
163
+}
164
+
159 165
 h2 .scenario, a span.label.scenario {
160 166
   position: relative;
161 167
   top: -2px;

+ 17 - 0
app/concerns/liquid_droppable.rb

@@ -0,0 +1,17 @@
1
+module LiquidDroppable
2
+  extend ActiveSupport::Concern
3
+
4
+  class Drop < Liquid::Drop
5
+    def initialize(object)
6
+      @object = object
7
+    end
8
+  end
9
+
10
+  included do
11
+    const_set :Drop, Kernel.const_set("#{name}Drop", Class.new(Drop))
12
+  end
13
+
14
+  def to_liquid(*args)
15
+    self.class::Drop.new(self, *args)
16
+  end
17
+end

+ 9 - 9
app/concerns/liquid_interpolatable.rb

@@ -1,28 +1,28 @@
1 1
 module LiquidInterpolatable
2 2
   extend ActiveSupport::Concern
3 3
 
4
-  def interpolate_options(options, payload = {})
4
+  def interpolate_options(options, event = {})
5 5
     case options
6 6
       when String
7
-        interpolate_string(options, payload)
7
+        interpolate_string(options, event)
8 8
       when ActiveSupport::HashWithIndifferentAccess, Hash
9
-        options.inject(ActiveSupport::HashWithIndifferentAccess.new) { |memo, (key, value)| memo[key] = interpolate_options(value, payload); memo }
9
+        options.inject(ActiveSupport::HashWithIndifferentAccess.new) { |memo, (key, value)| memo[key] = interpolate_options(value, event); memo }
10 10
       when Array
11
-        options.map { |value| interpolate_options(value, payload) }
11
+        options.map { |value| interpolate_options(value, event) }
12 12
       else
13 13
         options
14 14
     end
15 15
   end
16 16
 
17
-  def interpolated(payload = {})
18
-    key = [options, payload]
17
+  def interpolated(event = {})
18
+    key = [options, event]
19 19
     @interpolated_cache ||= {}
20
-    @interpolated_cache[key] ||= interpolate_options(options, payload)
20
+    @interpolated_cache[key] ||= interpolate_options(options, event)
21 21
     @interpolated_cache[key]
22 22
   end
23 23
 
24
-  def interpolate_string(string, payload)
25
-    Liquid::Template.parse(string).render!(payload, registers: {agent: self})
24
+  def interpolate_string(string, event)
25
+    Liquid::Template.parse(string).render!(event.to_liquid, registers: {agent: self})
26 26
   end
27 27
 
28 28
   require 'uri'

+ 38 - 0
app/models/agent.rb

@@ -14,6 +14,7 @@ class Agent < ActiveRecord::Base
14 14
   include WorkingHelpers
15 15
   include LiquidInterpolatable
16 16
   include HasGuid
17
+  include LiquidDroppable
17 18
 
18 19
   markdown_class_attributes :description, :event_description
19 20
 
@@ -66,6 +67,10 @@ class Agent < ActiveRecord::Base
66 67
     where(:type => type)
67 68
   }
68 69
 
70
+  def short_type
71
+    type.demodulize
72
+  end
73
+
69 74
   def check
70 75
     # Implement me in your subclass of Agent.
71 76
   end
@@ -378,3 +383,36 @@ class Agent < ActiveRecord::Base
378 383
     handle_asynchronously :async_check
379 384
   end
380 385
 end
386
+
387
+class AgentDrop
388
+  def type
389
+    @object.short_type
390
+  end
391
+
392
+  METHODS = [
393
+    :name,
394
+    :type,
395
+    :options,
396
+    :memory,
397
+    :sources,
398
+    :receivers,
399
+    :schedule,
400
+    :disabled,
401
+    :keep_events_for,
402
+    :propagate_immediately,
403
+  ]
404
+
405
+  METHODS.each { |attr|
406
+    define_method(attr) {
407
+      @object.__send__(attr)
408
+    } unless method_defined?(attr)
409
+  }
410
+
411
+  def each(&block)
412
+    return to_enum(__method__) unless block
413
+
414
+    METHODS.each { |attr|
415
+      yield [attr, __sent__(attr)]
416
+    }
417
+  end
418
+end

+ 1 - 1
app/models/agents/data_output_agent.rb

@@ -83,7 +83,7 @@ module Agents
83 83
     def receive_web_request(params, method, format)
84 84
       if interpolated['secrets'].include?(params['secret'])
85 85
         items = received_events.order('id desc').limit(events_to_show).map do |event|
86
-          interpolated = interpolate_options(options['template']['item'], event.payload)
86
+          interpolated = interpolate_options(options['template']['item'], event)
87 87
           interpolated['guid'] = event.id
88 88
           interpolated['pubDate'] = event.created_at.rfc2822.to_s
89 89
           interpolated

+ 1 - 1
app/models/agents/email_agent.rb

@@ -29,7 +29,7 @@ module Agents
29 29
       incoming_events.each do |event|
30 30
         log "Sending digest mail to #{user.email} with event #{event.id}"
31 31
         recipients(event.payload).each do |recipient|
32
-          SystemMailer.delay.send_message(:to => recipient, :subject => interpolated(event.payload)['subject'], :headline => interpolated(event.payload)['headline'], :groups => [present(event.payload)])
32
+          SystemMailer.delay.send_message(:to => recipient, :subject => interpolated(event)['subject'], :headline => interpolated(event)['headline'], :groups => [present(event.payload)])
33 33
         end
34 34
       end
35 35
     end

+ 7 - 6
app/models/agents/event_formatting_agent.rb

@@ -28,6 +28,8 @@ module Agents
28 28
             "subject": "{{data}}"
29 29
           }
30 30
 
31
+      The upstream agent of each received event is accessible via the key `agent`, which has the following attributes: #{''.tap { |s| s << AgentDrop.instance_methods(false).map { |m| "`#{m}`" }.join(', ') }}.
32
+
31 33
       Have a look at the [Wiki](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) to learn more about liquid templating.
32 34
 
33 35
       Events generated by this possible Event Formatting Agent will look like:
@@ -60,13 +62,13 @@ module Agents
60 62
       So you can use it in `instructions` like this:
61 63
 
62 64
           "instructions": {
63
-            "message": "Today's conditions look like <$.conditions> with a high temperature of {{high.celsius}} degrees Celsius according to the forecast at {{pretty_date.time}}.",
65
+            "message": "Today's conditions look like {{conditions}} with a high temperature of {{high.celsius}} degrees Celsius according to the forecast at {{pretty_date.time}}.",
64 66
             "subject": "{{data}}"
65 67
           }
66 68
 
67 69
       If you want to retain original contents of events and only add new keys, then set `mode` to `merge`, otherwise set it to `clean`.
68 70
 
69
-      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`.
71
+      By default, the output event will have a `created_at` field added as well, reflecting the original Event creation time.  You can skip this output by setting `skip_created_at` to `true`.
70 72
 
71 73
       To CGI escape output (for example when creating a link), use the Liquid `uri_escape` filter, like so:
72 74
 
@@ -80,7 +82,7 @@ module Agents
80 82
     after_save :clear_matchers
81 83
 
82 84
     def validate_options
83
-      errors.add(:base, "instructions, mode, skip_agent, and skip_created_at all need to be present.") unless options['instructions'].present? && options['mode'].present? && options['skip_agent'].present? && options['skip_created_at'].present?
85
+      errors.add(:base, "instructions, mode, and skip_created_at all need to be present.") unless options['instructions'].present? && options['mode'].present? && options['skip_created_at'].present?
84 86
 
85 87
       validate_matchers
86 88
     end
@@ -89,11 +91,11 @@ module Agents
89 91
       {
90 92
         'instructions' => {
91 93
           'message' =>  "You received a text {{text}} from {{fields.from}}",
94
+          'agent' => "{{agent.type}}",
92 95
           'some_other_field' => "Looks like the weather is going to be {{fields.weather}}"
93 96
         },
94 97
         'matchers' => [],
95 98
         'mode' => "clean",
96
-        'skip_agent' => "false",
97 99
         'skip_created_at' => "false"
98 100
       }
99 101
     end
@@ -105,10 +107,9 @@ module Agents
105 107
     def receive(incoming_events)
106 108
       incoming_events.each do |event|
107 109
         payload = perform_matching(event.payload)
108
-        opts = interpolated(payload)
110
+        opts = interpolated(event.to_liquid(payload))
109 111
         formatted_event = opts['mode'].to_s == "merge" ? event.payload.dup : {}
110 112
         formatted_event.merge! opts['instructions']
111
-        formatted_event['agent'] = Agent.find(event.agent_id).type.slice!(8..-1) unless opts['skip_agent'].to_s == "true"
112 113
         formatted_event['created_at'] = event.created_at unless opts['skip_created_at'].to_s == "true"
113 114
         create_event :payload => formatted_event
114 115
       end

+ 2 - 2
app/models/agents/growl_agent.rb

@@ -51,7 +51,7 @@ module Agents
51 51
         message = (event.payload['message'] || event.payload['text']).to_s
52 52
         subject = event.payload['subject'].to_s
53 53
         if message.present? && subject.present?
54
-          log "Sending Growl notification '#{subject}': '#{message}' to #{interpolated(event.payload)['growl_server']} with event #{event.id}"
54
+          log "Sending Growl notification '#{subject}': '#{message}' to #{interpolated(event)['growl_server']} with event #{event.id}"
55 55
           notify_growl(subject,message)
56 56
         else
57 57
           log "Event #{event.id} not sent, message and subject expected"
@@ -59,4 +59,4 @@ module Agents
59 59
       end
60 60
     end
61 61
   end
62
-end
62
+end

+ 1 - 1
app/models/agents/hipchat_agent.rb

@@ -42,7 +42,7 @@ module Agents
42 42
     def receive(incoming_events)
43 43
       client = HipChat::Client.new(interpolated[:auth_token])
44 44
       incoming_events.each do |event|
45
-        mo = interpolated(event.payload)
45
+        mo = interpolated(event)
46 46
         client[mo[:room_name]].send(mo[:username], mo[:message], :notify => mo[:notify].to_s == 'true' ? 1 : 0, :color => mo[:color])
47 47
       end
48 48
     end

+ 1 - 1
app/models/agents/jabber_agent.rb

@@ -60,7 +60,7 @@ module Agents
60 60
     end
61 61
 
62 62
     def body(event)
63
-      interpolated(event.payload)['message']
63
+      interpolated(event)['message']
64 64
     end
65 65
   end
66 66
 end

+ 2 - 2
app/models/agents/mqtt_agent.rb

@@ -106,7 +106,7 @@ module Agents
106 106
     def receive(incoming_events)
107 107
       mqtt_client.connect do |c|
108 108
         incoming_events.each do |event|
109
-          c.publish(interpolated(event.payload)['topic'], event.payload)
109
+          c.publish(interpolated(event)['topic'], event)
110 110
         end
111 111
 
112 112
         c.disconnect
@@ -136,4 +136,4 @@ module Agents
136 136
     end
137 137
 
138 138
   end
139
-end
139
+end

+ 2 - 2
app/models/agents/peak_detector_agent.rb

@@ -67,7 +67,7 @@ module Agents
67 67
         if newest_value > average_value + std_multiple * standard_deviation
68 68
           memory['peaks'][group] << newest_time
69 69
           memory['peaks'][group].reject! { |p| p <= newest_time - window_duration }
70
-          create_event :payload => { 'message' => interpolated(event.payload)['message'], 'peak' => newest_value, 'peak_time' => newest_time, 'grouped_by' => group.to_s }
70
+          create_event :payload => { 'message' => interpolated(event)['message'], 'peak' => newest_value, 'peak_time' => newest_time, 'grouped_by' => group.to_s }
71 71
         end
72 72
       end
73 73
     end
@@ -127,4 +127,4 @@ module Agents
127 127
       memory['data'][group].reject! { |value, time| time <= newest_time - window_duration }
128 128
     end
129 129
   end
130
-end
130
+end

+ 2 - 2
app/models/agents/post_agent.rb

@@ -68,7 +68,7 @@ module Agents
68 68
 
69 69
     def receive(incoming_events)
70 70
       incoming_events.each do |event|
71
-        outgoing = interpolated(event.payload)['payload'].presence || {}
71
+        outgoing = interpolated(event)['payload'].presence || {}
72 72
         if interpolated['no_merge'].to_s == 'true'
73 73
           handle outgoing, event.payload
74 74
         else
@@ -125,4 +125,4 @@ module Agents
125 125
       Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == "https") { |http| http.request(req) }
126 126
     end
127 127
   end
128
-end
128
+end

+ 1 - 1
app/models/agents/pushbullet_agent.rb

@@ -49,7 +49,7 @@ module Agents
49 49
     private
50 50
 
51 51
     def query_options(event)
52
-      mo = interpolated(event.payload)
52
+      mo = interpolated(event)
53 53
       {
54 54
         :basic_auth => {:username => mo[:api_key], :password => ''},
55 55
         :body => {:device_iden => mo[:device_id], :title => mo[:title], :body => mo[:body], :type => 'note'}

+ 1 - 1
app/models/agents/pushover_agent.rb

@@ -58,7 +58,7 @@ module Agents
58 58
 
59 59
     def receive(incoming_events)
60 60
       incoming_events.each do |event|
61
-        payload_interpolated = interpolated(event.payload)
61
+        payload_interpolated = interpolated(event)
62 62
         message = (event.payload['message'].presence || event.payload['text'].presence || payload_interpolated['message']).to_s
63 63
         if message.present?
64 64
           post_params = {

+ 2 - 2
app/models/agents/shell_command_agent.rb

@@ -61,7 +61,7 @@ module Agents
61 61
 
62 62
     def receive(incoming_events)
63 63
       incoming_events.each do |event|
64
-        handle(interpolated(event.payload), event)
64
+        handle(interpolated(event), event)
65 65
       end
66 66
     end
67 67
 
@@ -109,4 +109,4 @@ module Agents
109 109
       [result, errors, exit_status]
110 110
     end
111 111
   end
112
-end
112
+end

+ 1 - 1
app/models/agents/slack_agent.rb

@@ -57,7 +57,7 @@ module Agents
57 57
 
58 58
     def receive(incoming_events)
59 59
       incoming_events.each do |event|
60
-        opts = interpolated(event.payload)
60
+        opts = interpolated(event)
61 61
         slack_notifier.ping opts[:message], channel: opts[:channel], username: opts[:username]
62 62
       end
63 63
     end

+ 1 - 1
app/models/agents/translation_agent.rb

@@ -66,7 +66,7 @@ module Agents
66 66
       access_token = JSON.parse(response.body)["access_token"]
67 67
       incoming_events.each do |event|
68 68
         translated_event = {}
69
-        opts = interpolated(event.payload)
69
+        opts = interpolated(event)
70 70
         opts['content'].each_pair do |key, value|
71 71
           translated_event[key] = translate(value.first, opts['to'], access_token)
72 72
         end

+ 2 - 2
app/models/agents/trigger_agent.rb

@@ -57,7 +57,7 @@ module Agents
57 57
     def receive(incoming_events)
58 58
       incoming_events.each do |event|
59 59
 
60
-        opts = interpolated(event.payload)
60
+        opts = interpolated(event)
61 61
 
62 62
         match = opts['rules'].all? do |rule|
63 63
           value_at_path = Utils.value_at(event['payload'], rule['path'])
@@ -105,4 +105,4 @@ module Agents
105 105
       interpolated['keep_event'] == 'true'
106 106
     end
107 107
   end
108
-end
108
+end

+ 3 - 3
app/models/agents/twilio_agent.rb

@@ -44,13 +44,13 @@ module Agents
44 44
       incoming_events.each do |event|
45 45
         message = (event.payload['message'].presence || event.payload['text'].presence || event.payload['sms'].presence).to_s
46 46
         if message.present?
47
-          if interpolated(event.payload)['receive_call'].to_s == 'true'
47
+          if interpolated(event)['receive_call'].to_s == 'true'
48 48
             secret = SecureRandom.hex 3
49 49
             memory['pending_calls'][secret] = message
50 50
             make_call secret
51 51
           end
52 52
 
53
-          if interpolated(event.payload)['receive_text'].to_s == 'true'
53
+          if interpolated(event)['receive_text'].to_s == 'true'
54 54
             message = message.slice 0..160
55 55
             send_message message
56 56
           end
@@ -86,4 +86,4 @@ module Agents
86 86
       end
87 87
     end
88 88
   end
89
-end
89
+end

+ 2 - 2
app/models/agents/twitter_publish_agent.rb

@@ -41,7 +41,7 @@ module Agents
41 41
         incoming_events = incoming_events.first(20)
42 42
       end
43 43
       incoming_events.each do |event|
44
-        tweet_text = interpolated(event.payload)['message']
44
+        tweet_text = interpolated(event)['message']
45 45
         begin
46 46
           tweet = publish_tweet tweet_text
47 47
           create_event :payload => {
@@ -67,4 +67,4 @@ module Agents
67 67
       twitter.update(text)
68 68
     end
69 69
   end
70
-end
70
+end

+ 2 - 2
app/models/agents/weibo_publish_agent.rb

@@ -47,7 +47,7 @@ module Agents
47 47
         incoming_events = incoming_events.first(20)
48 48
       end
49 49
       incoming_events.each do |event|
50
-        tweet_text = Utils.value_at(event.payload, interpolated(event.payload)['message_path'])
50
+        tweet_text = Utils.value_at(event.payload, interpolated(event)['message_path'])
51 51
         if event.agent.type == "Agents::TwitterUserAgent"
52 52
           tweet_text = unwrap_tco_urls(tweet_text, event.payload)
53 53
         end
@@ -83,4 +83,4 @@ module Agents
83 83
     end
84 84
 
85 85
   end
86
-end
86
+end

+ 24 - 0
app/models/event.rb

@@ -5,6 +5,7 @@ require 'json_serialized_field'
5 5
 # fields.
6 6
 class Event < ActiveRecord::Base
7 7
   include JSONSerializedField
8
+  include LiquidDroppable
8 9
 
9 10
   attr_accessible :lat, :lng, :payload, :user_id, :user, :expires_at
10 11
 
@@ -41,3 +42,26 @@ class Event < ActiveRecord::Base
41 42
     Agent.receive!(:only_receivers => propagate_ids) unless propagate_ids.empty?
42 43
   end
43 44
 end
45
+
46
+class EventDrop
47
+  def initialize(event, payload = event.payload)
48
+    super(event)
49
+    @payload = payload
50
+  end
51
+
52
+  def before_method(key)
53
+    if @payload.key?(key)
54
+      @payload[key]
55
+    else
56
+      case key
57
+      when 'agent'
58
+        @object.agent
59
+      end
60
+    end
61
+  end
62
+
63
+  def each(&block)
64
+    return to_enum(__method__) unless block
65
+    @payload.each(&block)
66
+  end
67
+end

+ 1 - 0
app/views/agents/_form.html.erb

@@ -77,6 +77,7 @@
77 77
         <div class="col-md-12">
78 78
           <div class="form-group">
79 79
             <%= f.label :options %>
80
+            <span class="glyphicon glyphicon-question-sign hover-help" data-content="In this JSON hash, interpolation is available in almost all values using the Liquid templating language.<p>Available template variables include the following:<dl><dt><code>message</code>, <code>url</code>, etc.</dt><dd>Refers to the corresponding key's value of each incoming event's payload.</dd><dt><code>agent</code></dt><dd>Refers to the agent that created each incoming event.  It has attributes like <code>type</code>, <code>name</code> and <code>options</code>, so <code>{{agent.type}}</code> will expand to <code>WebsiteAgent</code> if an incoming event is created by that agent.</dd></dl></p><p>To access user credentials, use the <code>credential</code> tag like this: <code>{% credential <em>bare_key_name</em> %}</code></p>"></span>
80 81
             <textarea rows="15" id="agent_options" name="agent[options]" class="form-control live-json-editor <%= (@agent.new_record? && @agent.options == {}) ? "showing-default" : "" %>">
81 82
               <%= Utils.jsonify((@agent.new_record? && @agent.options == {}) ? @agent.default_options : @agent.options) %>
82 83
             </textarea>

+ 21 - 0
db/migrate/20140722131220_convert_efa_skip_agent.rb

@@ -0,0 +1,21 @@
1
+class ConvertEfaSkipAgent < ActiveRecord::Migration
2
+  def up
3
+    Agent.where(type: 'Agents::EventFormattingAgent').each do |agent|
4
+      agent.options_will_change!
5
+      unless agent.options.delete('skip_agent').to_s == 'true'
6
+        agent.options['instructions'] = {
7
+          'agent' => '{{agent.type}}'
8
+        }.update(agent.options['instructions'] || {})
9
+      end
10
+      agent.save!
11
+    end
12
+  end
13
+
14
+  def down
15
+    Agent.where(type: 'Agents::EventFormattingAgent').each do |agent|
16
+      agent.options_will_change!
17
+      agent.options['skip_agent'] = (agent.options['instructions'] || {})['agent'] == '{{agent.type}}'
18
+      agent.save!
19
+    end
20
+  end
21
+end

+ 102 - 0
spec/models/agent_spec.rb

@@ -132,6 +132,13 @@ describe Agent do
132 132
       it_behaves_like HasGuid
133 133
     end
134 134
 
135
+    describe ".short_type" do
136
+      it "returns a short name without 'Agents::'" do
137
+        Agents::SomethingSource.new.short_type.should == "SomethingSource"
138
+        Agents::CannotBeScheduled.new.short_type.should == "CannotBeScheduled"
139
+      end
140
+    end
141
+
135 142
     describe ".default_schedule" do
136 143
       it "stores the default on the class" do
137 144
         Agents::SomethingSource.default_schedule.should == "2pm"
@@ -729,3 +736,98 @@ describe Agent do
729 736
     end
730 737
   end
731 738
 end
739
+
740
+describe AgentDrop do
741
+  def interpolate(string, agent)
742
+    agent.interpolate_string(string, "agent" => agent)
743
+  end
744
+
745
+  before do
746
+    @wsa1 = Agents::WebsiteAgent.new(
747
+      name: 'XKCD',
748
+      options: {
749
+        expected_update_period_in_days: 2,
750
+        type: 'html',
751
+        url: 'http://xkcd.com/',
752
+        mode: 'on_change',
753
+        extract: {
754
+          url: { css: '#comic img', attr: 'src' },
755
+          title: { css: '#comic img', attr: 'alt' },
756
+        },
757
+      },
758
+      schedule: 'every_1h',
759
+      keep_events_for: 2)
760
+    @wsa1.user = users(:bob)
761
+    @wsa1.save!
762
+
763
+    @wsa2 = Agents::WebsiteAgent.new(
764
+      name: 'Dilbert',
765
+      options: {
766
+        expected_update_period_in_days: 2,
767
+        type: 'html',
768
+        url: 'http://dilbert.com/',
769
+        mode: 'on_change',
770
+        extract: {
771
+          url: { css: '[id^=strip_enlarged_] img', attr: 'src' },
772
+          title: { css: '.STR_DateStrip', text: true },
773
+        },
774
+      },
775
+      schedule: 'every_12h',
776
+      keep_events_for: 2)
777
+    @wsa2.user = users(:bob)
778
+    @wsa2.save!
779
+
780
+    @efa = Agents::EventFormattingAgent.new(
781
+      name: 'Formatter',
782
+      options: {
783
+        instructions: {
784
+          message: '{{agent.name}}: {{title}} {{url}}',
785
+          agent: '{{agent.type}}',
786
+        },
787
+        mode: 'clean',
788
+        matchers: [],
789
+        skip_created_at: 'false',
790
+      },
791
+      keep_events_for: 2,
792
+      propagate_immediately: true)
793
+    @efa.user = users(:bob)
794
+    @efa.sources << @wsa1 << @wsa2
795
+    @efa.memory[:test] = 1
796
+    @efa.save!
797
+  end
798
+
799
+  it 'should be created via Agent#to_liquid' do
800
+    @wsa1.to_liquid.class.should be(AgentDrop)
801
+    @wsa2.to_liquid.class.should be(AgentDrop)
802
+    @efa.to_liquid.class.should be(AgentDrop)
803
+  end
804
+
805
+  it 'should have .type and .name' do
806
+    t = '{{agent.type}}: {{agent.name}}'
807
+    interpolate(t, @wsa1).should eq('WebsiteAgent: XKCD')
808
+    interpolate(t, @wsa2).should eq('WebsiteAgent: Dilbert')
809
+    interpolate(t, @efa).should eq('EventFormattingAgent: Formatter')
810
+  end
811
+
812
+  it 'should have .options' do
813
+    t = '{{agent.options.url}}'
814
+    interpolate(t, @wsa1).should eq('http://xkcd.com/')
815
+    interpolate(t, @wsa2).should eq('http://dilbert.com/')
816
+    interpolate('{{agent.options.instructions.message}}',
817
+                @efa).should eq('{{agent.name}}: {{title}} {{url}}')
818
+  end
819
+
820
+  it 'should have .sources' do
821
+    t = '{{agent.sources.size}}: {{agent.sources | map:"name" | join:", "}}'
822
+    interpolate(t, @wsa1).should eq('0: ')
823
+    interpolate(t, @wsa2).should eq('0: ')
824
+    interpolate(t, @efa).should eq('2: XKCD, Dilbert')
825
+  end
826
+
827
+  it 'should have .receivers' do
828
+    t = '{{agent.receivers.size}}: {{agent.receivers | map:"name" | join:", "}}'
829
+    interpolate(t, @wsa1).should eq('1: Formatter')
830
+    interpolate(t, @wsa2).should eq('1: Formatter')
831
+    interpolate(t, @efa).should eq('0: ')
832
+  end
833
+end

+ 5 - 17
spec/models/agents/event_formatting_agent_spec.rb

@@ -7,7 +7,8 @@ describe Agents::EventFormattingAgent do
7 7
         :options => {
8 8
             :instructions => {
9 9
                 :message => "Received {{content.text}} from {{content.name}} .",
10
-                :subject => "Weather looks like {{conditions}} according to the forecast at {{pretty_date.time}}"
10
+                :subject => "Weather looks like {{conditions}} according to the forecast at {{pretty_date.time}}",
11
+                :agent => "{{agent.type}}",
11 12
             },
12 13
             :mode => "clean",
13 14
             :matchers => [
@@ -17,7 +18,6 @@ describe Agents::EventFormattingAgent do
17 18
                     :to => "pretty_date",
18 19
                 },
19 20
             ],
20
-            :skip_agent => "false",
21 21
             :skip_created_at => "false"
22 22
         }
23 23
     }
@@ -53,14 +53,6 @@ describe Agents::EventFormattingAgent do
53 53
       Event.last.payload[:content].should_not == nil
54 54
     end
55 55
 
56
-    it "should accept skip_agent" do
57
-      @checker.receive([@event])
58
-      Event.last.payload[:agent].should == "WeatherAgent"
59
-      @checker.options[:skip_agent] = "true"
60
-      @checker.receive([@event])
61
-      Event.last.payload[:agent].should == nil
62
-    end
63
-
64 56
     it "should accept skip_created_at" do
65 57
       @checker.receive([@event])
66 58
       Event.last.payload[:created_at].should_not == nil
@@ -69,12 +61,13 @@ describe Agents::EventFormattingAgent do
69 61
       Event.last.payload[:created_at].should == nil
70 62
     end
71 63
 
72
-    it "should handle JSONPaths in instructions" do
64
+    it "should handle Liquid templating in instructions" do
73 65
       @checker.receive([@event])
74 66
       Event.last.payload[:message].should == "Received Some Lorem Ipsum from somevalue ."
67
+      Event.last.payload[:agent].should == "WeatherAgent"
75 68
     end
76 69
 
77
-    it "should handle matchers and JSONPaths in instructions" do
70
+    it "should handle matchers and Liquid templating in instructions" do
78 71
       @checker.receive([@event])
79 72
       Event.last.payload[:subject].should == "Weather looks like someothervalue according to the forecast at 10:00 PM EST"
80 73
     end
@@ -152,11 +145,6 @@ describe Agents::EventFormattingAgent do
152 145
       @checker.should_not be_valid
153 146
     end
154 147
 
155
-    it "should validate presence of skip_agent" do
156
-      @checker.options[:skip_agent] = ""
157
-      @checker.should_not be_valid
158
-    end
159
-
160 148
     it "should validate presence of skip_created_at" do
161 149
       @checker.options[:skip_created_at] = ""
162 150
       @checker.should_not be_valid

+ 36 - 0
spec/models/event_spec.rb

@@ -76,3 +76,39 @@ describe Event do
76 76
     end
77 77
   end
78 78
 end
79
+
80
+describe EventDrop do
81
+  def interpolate(string, event)
82
+    event.agent.interpolate_string(string, event.to_liquid)
83
+  end
84
+
85
+  before do
86
+    @event = Event.new
87
+    @event.agent = agents(:jane_weather_agent)
88
+    @event.payload = {
89
+      'title' => 'some title',
90
+      'url' => 'http://some.site.example.org/',
91
+    }
92
+    @event.save!
93
+  end
94
+
95
+  it 'should be created via Agent#to_liquid' do
96
+    @event.to_liquid.class.should be(EventDrop)
97
+  end
98
+
99
+  it 'should have attributes of its payload' do
100
+    t = '{{title}}: {{url}}'
101
+    interpolate(t, @event).should eq('some title: http://some.site.example.org/')
102
+  end
103
+
104
+  it 'should be iteratable' do
105
+    # to_liquid returns self
106
+    t = "{% for pair in to_liquid %}{{pair | join:':' }}\n{% endfor %}"
107
+    interpolate(t, @event).should eq("title:some title\nurl:http://some.site.example.org/\n")
108
+  end
109
+
110
+  it 'should have agent' do
111
+    t = '{{agent.name}}'
112
+    interpolate(t, @event).should eq('SF Weather')
113
+  end
114
+end

+ 5 - 5
spec/support/shared_examples/liquid_interpolatable.rb

@@ -20,7 +20,7 @@ shared_examples_for LiquidInterpolatable do
20 20
 
21 21
   describe "interpolating liquid templates" do
22 22
     it "should work" do
23
-      @checker.interpolate_options(@checker.options, @event.payload).should == {
23
+      @checker.interpolate_options(@checker.options, @event).should == {
24 24
           "normal" => "just some normal text",
25 25
           "variable" => "hello",
26 26
           "text" => "Some test with an embedded hello",
@@ -30,7 +30,7 @@ shared_examples_for LiquidInterpolatable do
30 30
 
31 31
     it "should work with arrays", focus: true do
32 32
       @checker.options = {"value" => ["{{variable}}", "Much array", "Hey, {{hello_world}}"]}
33
-      @checker.interpolate_options(@checker.options, @event.payload).should == {
33
+      @checker.interpolate_options(@checker.options, @event).should == {
34 34
         "value" => ["hello", "Much array", "Hey, Hello world"]
35 35
       }
36 36
     end
@@ -38,7 +38,7 @@ shared_examples_for LiquidInterpolatable do
38 38
     it "should work recursively" do
39 39
       @checker.options['hash'] = {'recursive' => "{{variable}}"}
40 40
       @checker.options['indifferent_hash'] = ActiveSupport::HashWithIndifferentAccess.new({'recursive' => "{{variable}}"})
41
-      @checker.interpolate_options(@checker.options, @event.payload).should == {
41
+      @checker.interpolate_options(@checker.options, @event).should == {
42 42
           "normal" => "just some normal text",
43 43
           "variable" => "hello",
44 44
           "text" => "Some test with an embedded hello",
@@ -49,8 +49,8 @@ shared_examples_for LiquidInterpolatable do
49 49
     end
50 50
 
51 51
     it "should work for strings" do
52
-      @checker.interpolate_string("{{variable}}", @event.payload).should == "hello"
53
-      @checker.interpolate_string("{{variable}} you", @event.payload).should == "hello you"
52
+      @checker.interpolate_string("{{variable}}", @event).should == "hello"
53
+      @checker.interpolate_string("{{variable}} you", @event).should == "hello you"
54 54
     end
55 55
   end
56 56