@@ -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') |
@@ -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; |
@@ -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 |
@@ -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' |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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'} |
@@ -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 = { |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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> |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
|