Quellcode durchsuchen

Make {{agent}} universally accessible from any agent.

To implement this, EventDrop is introduced to represent an Event object
in interpolation.
Akinori MUSHA vor 10 Jahren
Ursprung
Commit
c1f114d56b

+ 9 - 9
app/concerns/liquid_interpolatable.rb

@@ -1,28 +1,28 @@
 module LiquidInterpolatable
   extend ActiveSupport::Concern
 
-  def interpolate_options(options, payload = {})
+  def interpolate_options(options, event = {})
     case options
       when String
-        interpolate_string(options, payload)
+        interpolate_string(options, event)
       when ActiveSupport::HashWithIndifferentAccess, Hash
-        options.inject(ActiveSupport::HashWithIndifferentAccess.new) { |memo, (key, value)| memo[key] = interpolate_options(value, payload); memo }
+        options.inject(ActiveSupport::HashWithIndifferentAccess.new) { |memo, (key, value)| memo[key] = interpolate_options(value, event); memo }
       when Array
-        options.map { |value| interpolate_options(value, payload) }
+        options.map { |value| interpolate_options(value, event) }
       else
         options
     end
   end
 
-  def interpolated(payload = {})
-    key = [options, payload]
+  def interpolated(event = {})
+    key = [options, event]
     @interpolated_cache ||= {}
-    @interpolated_cache[key] ||= interpolate_options(options, payload)
+    @interpolated_cache[key] ||= interpolate_options(options, event)
     @interpolated_cache[key]
   end
 
-  def interpolate_string(string, payload)
-    Liquid::Template.parse(string).render!(payload, registers: {agent: self})
+  def interpolate_string(string, event)
+    Liquid::Template.parse(string).render!(event.to_liquid, registers: {agent: self})
   end
 
   require 'uri'

+ 7 - 0
app/models/agent.rb

@@ -402,5 +402,12 @@ class AgentDrop < Liquid::Drop
     def to_liquid
       AgentDrop.new(self)
     end
+
+    def to_h
+      drop = to_liquid
+      AgentDrop.public_instance_methods(false).each_with_object({}) { |attr, hash|
+        hash[attr.to_s] = drop.__send__(attr)
+      }
+    end
   end
 end

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

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

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

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

+ 2 - 3
app/models/agents/event_formatting_agent.rb

@@ -106,9 +106,8 @@ module Agents
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        agent = Agent.find(event.agent_id)
-        payload = perform_matching({ 'agent' => agent }.update(event.payload))
-        opts = interpolated(payload)
+        payload = perform_matching({ 'agent' => event.agent.to_h }.merge(event.payload))
+        opts = interpolated(EventDrop.new(event, payload))
         formatted_event = opts['mode'].to_s == "merge" ? event.payload.dup : {}
         formatted_event.merge! opts['instructions']
         formatted_event['created_at'] = event.created_at unless opts['skip_created_at'].to_s == "true"

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

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

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

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

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

@@ -60,7 +60,7 @@ module Agents
     end
 
     def body(event)
-      interpolated(event.payload)['message']
+      interpolated(event)['message']
     end
   end
 end

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

@@ -106,7 +106,7 @@ module Agents
     def receive(incoming_events)
       mqtt_client.connect do |c|
         incoming_events.each do |event|
-          c.publish(interpolated(event.payload)['topic'], event.payload)
+          c.publish(interpolated(event)['topic'], event)
         end
 
         c.disconnect
@@ -136,4 +136,4 @@ module Agents
     end
 
   end
-end
+end

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

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

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

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

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

@@ -49,7 +49,7 @@ module Agents
     private
 
     def query_options(event)
-      mo = interpolated(event.payload)
+      mo = interpolated(event)
       {
         :basic_auth => {:username => mo[:api_key], :password => ''},
         :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
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        payload_interpolated = interpolated(event.payload)
+        payload_interpolated = interpolated(event)
         message = (event.payload['message'].presence || event.payload['text'].presence || payload_interpolated['message']).to_s
         if message.present?
           post_params = {

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

@@ -61,7 +61,7 @@ module Agents
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        handle(interpolated(event.payload), event)
+        handle(interpolated(event), event)
       end
     end
 
@@ -109,4 +109,4 @@ module Agents
       [result, errors, exit_status]
     end
   end
-end
+end

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

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

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

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

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

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

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

@@ -44,13 +44,13 @@ module Agents
       incoming_events.each do |event|
         message = (event.payload['message'].presence || event.payload['text'].presence || event.payload['sms'].presence).to_s
         if message.present?
-          if interpolated(event.payload)['receive_call'].to_s == 'true'
+          if interpolated(event)['receive_call'].to_s == 'true'
             secret = SecureRandom.hex 3
             memory['pending_calls'][secret] = message
             make_call secret
           end
 
-          if interpolated(event.payload)['receive_text'].to_s == 'true'
+          if interpolated(event)['receive_text'].to_s == 'true'
             message = message.slice 0..160
             send_message message
           end
@@ -86,4 +86,4 @@ module Agents
       end
     end
   end
-end
+end

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

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

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

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

+ 32 - 0
app/models/event.rb

@@ -41,3 +41,35 @@ class Event < ActiveRecord::Base
     Agent.receive!(:only_receivers => propagate_ids) unless propagate_ids.empty?
   end
 end
+
+class EventDrop < Liquid::Drop
+  def initialize(event, payload = event.payload)
+    @event = event
+    @payload = payload
+  end
+
+  def before_method(key)
+    if @payload.key?(key)
+      @payload[key]
+    else
+      case key
+      when 'agent'
+        @event.agent
+      end
+    end
+  end
+
+  # Allow iteration using a "for" loop.  Including Enumerable will
+  # enable methods like max, min and sort, but it does not make much
+  # sense since this is a hash-like object.
+  def each(&block)
+    return to_enum(__method__) unless block
+    @payload.each(&block)
+  end
+
+  class ::Event
+    def to_liquid
+      EventDrop.new(self)
+    end
+  end
+end

+ 9 - 2
spec/models/agents/event_formatting_agent_spec.rb

@@ -8,7 +8,8 @@ describe Agents::EventFormattingAgent do
             :instructions => {
                 :message => "Received {{content.text}} from {{content.name}} .",
                 :subject => "Weather looks like {{conditions}} according to the forecast at {{pretty_date.time}}",
-                :agent => "{{agent.type}}",
+                :agent_type => "{{agent.type}}",
+                :agent_type_via_matching => "{{agent_info.type}}",
             },
             :mode => "clean",
             :matchers => [
@@ -17,6 +18,11 @@ describe Agents::EventFormattingAgent do
                     :regexp => "\\A(?<time>\\d\\d:\\d\\d [AP]M [A-Z]+)",
                     :to => "pretty_date",
                 },
+                {
+                    :path => "{{agent.type}}",
+                    :regexp => "\\A(?<type>.*)\\z",
+                    :to => "agent_info",
+                },
             ],
             :skip_created_at => "false"
         }
@@ -64,12 +70,13 @@ describe Agents::EventFormattingAgent do
     it "should handle Liquid templating in instructions" do
       @checker.receive([@event])
       Event.last.payload[:message].should == "Received Some Lorem Ipsum from somevalue ."
-      Event.last.payload[:agent].should == "WeatherAgent"
+      Event.last.payload[:agent_type].should == "WeatherAgent"
     end
 
     it "should handle matchers and Liquid templating in instructions" do
       @checker.receive([@event])
       Event.last.payload[:subject].should == "Weather looks like someothervalue according to the forecast at 10:00 PM EST"
+      Event.last.payload[:agent_type_via_matching].should == "WeatherAgent"
     end
 
     it "should allow escaping" do

+ 36 - 0
spec/models/event_spec.rb

@@ -76,3 +76,39 @@ describe Event do
     end
   end
 end
+
+describe EventDrop do
+  def interpolate(string, event)
+    event.agent.interpolate_string(string, event.to_liquid)
+  end
+
+  before do
+    @event = Event.new
+    @event.agent = agents(:jane_weather_agent)
+    @event.payload = {
+      'title' => 'some title',
+      'url' => 'http://some.site.example.org/',
+    }
+    @event.save!
+  end
+
+  it 'should be created via Agent#to_liquid' do
+    @event.to_liquid.class.should be(EventDrop)
+  end
+
+  it 'should have attributes of its payload' do
+    t = '{{title}}: {{url}}'
+    interpolate(t, @event).should eq('some title: http://some.site.example.org/')
+  end
+
+  it 'should be iteratable' do
+    # to_liquid returns self
+    t = "{% for pair in to_liquid %}{{pair | join:':' }}\n{% endfor %}"
+    interpolate(t, @event).should eq("title:some title\nurl:http://some.site.example.org/\n")
+  end
+
+  it 'should have agent' do
+    t = '{{agent.name}}'
+    interpolate(t, @event).should eq('SF Weather')
+  end
+end

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

@@ -20,7 +20,7 @@ shared_examples_for LiquidInterpolatable do
 
   describe "interpolating liquid templates" do
     it "should work" do
-      @checker.interpolate_options(@checker.options, @event.payload).should == {
+      @checker.interpolate_options(@checker.options, @event).should == {
           "normal" => "just some normal text",
           "variable" => "hello",
           "text" => "Some test with an embedded hello",
@@ -30,7 +30,7 @@ shared_examples_for LiquidInterpolatable do
 
     it "should work with arrays", focus: true do
       @checker.options = {"value" => ["{{variable}}", "Much array", "Hey, {{hello_world}}"]}
-      @checker.interpolate_options(@checker.options, @event.payload).should == {
+      @checker.interpolate_options(@checker.options, @event).should == {
         "value" => ["hello", "Much array", "Hey, Hello world"]
       }
     end
@@ -38,7 +38,7 @@ shared_examples_for LiquidInterpolatable do
     it "should work recursively" do
       @checker.options['hash'] = {'recursive' => "{{variable}}"}
       @checker.options['indifferent_hash'] = ActiveSupport::HashWithIndifferentAccess.new({'recursive' => "{{variable}}"})
-      @checker.interpolate_options(@checker.options, @event.payload).should == {
+      @checker.interpolate_options(@checker.options, @event).should == {
           "normal" => "just some normal text",
           "variable" => "hello",
           "text" => "Some test with an embedded hello",
@@ -49,8 +49,8 @@ shared_examples_for LiquidInterpolatable do
     end
 
     it "should work for strings" do
-      @checker.interpolate_string("{{variable}}", @event.payload).should == "hello"
-      @checker.interpolate_string("{{variable}} you", @event.payload).should == "hello you"
+      @checker.interpolate_string("{{variable}}", @event).should == "hello"
+      @checker.interpolate_string("{{variable}} you", @event).should == "hello you"
     end
   end