mirror of
https://github.com/Fishwaldo/huginn.git
synced 2025-03-17 12:21:31 +00:00
Merge pull request #3 from cantino/dsander-omniauth
Moving ENV variables
This commit is contained in:
commit
8cf4b3d3bf
37 changed files with 277 additions and 110 deletions
1
Gemfile
1
Gemfile
|
@ -59,6 +59,7 @@ gem 'faraday', '~> 0.9.0'
|
|||
gem 'faraday_middleware'
|
||||
gem 'typhoeus', '~> 0.6.3'
|
||||
gem 'nokogiri', '~> 1.6.1'
|
||||
gem 'net-ftp-list', '~> 3.2.8'
|
||||
|
||||
gem 'wunderground', '~> 1.2.0'
|
||||
gem 'forecast_io', '~> 2.0.0'
|
||||
|
|
|
@ -188,6 +188,7 @@ GEM
|
|||
multipart-post (2.0.0)
|
||||
mysql2 (0.3.16)
|
||||
naught (1.0.0)
|
||||
net-ftp-list (3.2.8)
|
||||
nokogiri (1.6.3.1)
|
||||
mini_portile (= 0.6.0)
|
||||
oauth (0.4.7)
|
||||
|
@ -418,6 +419,7 @@ DEPENDENCIES
|
|||
liquid (~> 2.6.1)
|
||||
mqtt
|
||||
mysql2 (~> 0.3.16)
|
||||
net-ftp-list (~> 3.2.8)
|
||||
nokogiri (~> 1.6.1)
|
||||
omniauth
|
||||
omniauth-37signals
|
||||
|
|
|
@ -27,6 +27,8 @@ Follow [@tectonic](https://twitter.com/tectonic) for updates as Huginn evolves,
|
|||
|
||||
Want to help with Huginn? All contributions are encouraged! You could make UI improvements, add new Agents, write documentation and tutorials, or try tackling [issues tagged with #help-wanted](https://github.com/cantino/huginn/issues?direction=desc&labels=help-wanted&page=1&sort=created&state=open).
|
||||
|
||||
Really want an issue fixed/feature implemented? Or maybe you just want to solve some community issues and earn some extra coffee money? Then you should take a look at the [current bounties on Bountysource](https://www.bountysource.com/trackers/282580-huginn).
|
||||
|
||||
Have an awesome an idea but not feeling quite up to contributing yet? Head over to our [Official 'suggest an agent' thread ](https://github.com/cantino/huginn/issues/353) and tell us about your cool idea!
|
||||
|
||||
## Examples
|
||||
|
@ -105,5 +107,5 @@ Huginn is a work in progress and is just getting started. Please get involved!
|
|||
|
||||
Please fork, add specs, and send pull requests!
|
||||
|
||||
[](https://travis-ci.org/cantino/huginn) [](https://coveralls.io/r/cantino/huginn) [](https://bitdeli.com/free "Bitdeli Badge") [](https://gemnasium.com/cantino/huginn)
|
||||
[](https://travis-ci.org/cantino/huginn) [](https://coveralls.io/r/cantino/huginn) [](https://bitdeli.com/free "Bitdeli Badge") [](https://gemnasium.com/cantino/huginn) [](https://www.bountysource.com/trackers/282580-huginn?utm_source=282580&utm_medium=shield&utm_campaign=TRACKER_BADGE)
|
||||
|
||||
|
|
20
app/assets/javascripts/diagram.js.coffee
Normal file
20
app/assets/javascripts/diagram.js.coffee
Normal file
|
@ -0,0 +1,20 @@
|
|||
$ ->
|
||||
svg = document.querySelector('.agent-diagram svg.diagram')
|
||||
overlay = document.querySelector('.agent-diagram .overlay')
|
||||
getTopLeft = (node) ->
|
||||
bbox = node.getBBox()
|
||||
point = svg.createSVGPoint()
|
||||
point.x = bbox.x + bbox.width
|
||||
point.y = bbox.y
|
||||
point.matrixTransform(node.getCTM())
|
||||
$(svg).find('g.node[data-badge-id]').each ->
|
||||
tl = getTopLeft(this)
|
||||
$('#' + this.getAttribute('data-badge-id'), overlay).each ->
|
||||
badge = $(this)
|
||||
badge.css
|
||||
left: tl.x - badge.outerWidth() * (2/3)
|
||||
top: tl.y - badge.outerHeight() * (1/3)
|
||||
'background-color': badge.find('.label').css('background-color')
|
||||
.show()
|
||||
return
|
||||
return
|
30
app/assets/stylesheets/diagram.css.scss
Normal file
30
app/assets/stylesheets/diagram.css.scss
Normal file
|
@ -0,0 +1,30 @@
|
|||
.agent-diagram {
|
||||
position: relative;
|
||||
z-index: auto;
|
||||
|
||||
svg.diagram {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.overlay-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: auto;
|
||||
|
||||
.overlay {
|
||||
position: relative;
|
||||
z-index: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.badge {
|
||||
position: absolute;
|
||||
display: none;
|
||||
color: white !important;
|
||||
z-index: 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -98,14 +98,6 @@ class AgentsController < ApplicationController
|
|||
@agent = current_user.agents.find(params[:id])
|
||||
end
|
||||
|
||||
def diagram
|
||||
@agents = if params[:scenario_id].present?
|
||||
current_user.scenarios.find(params[:scenario_id]).agents.includes(:receivers)
|
||||
else
|
||||
current_user.agents.includes(:receivers)
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
@agent = Agent.build_for_type(params[:agent].delete(:type),
|
||||
current_user,
|
||||
|
|
9
app/controllers/diagrams_controller.rb
Normal file
9
app/controllers/diagrams_controller.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class DiagramsController < ApplicationController
|
||||
def show
|
||||
@agents = if params[:scenario_id].present?
|
||||
current_user.scenarios.find(params[:scenario_id]).agents.includes(:receivers)
|
||||
else
|
||||
current_user.agents.includes(:receivers)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,8 +2,8 @@ class EventsController < ApplicationController
|
|||
before_filter :load_event, :except => :index
|
||||
|
||||
def index
|
||||
if params[:agent]
|
||||
@agent = current_user.agents.find(params[:agent])
|
||||
if params[:agent_id]
|
||||
@agent = current_user.agents.find(params[:agent_id])
|
||||
@events = @agent.events.page(params[:page])
|
||||
else
|
||||
@events = current_user.events.preload(:agent).page(params[:page])
|
||||
|
|
|
@ -6,7 +6,7 @@ module DotHelper
|
|||
dot.close_write
|
||||
dot.read
|
||||
} rescue false)
|
||||
svg.html_safe
|
||||
decorate_svg(svg, agents).html_safe
|
||||
else
|
||||
tag('img', src: URI('https://chart.googleapis.com/chart').tap { |uri|
|
||||
uri.query = URI.encode_www_form(cht: 'gv', chl: agents_dot(agents))
|
||||
|
@ -57,6 +57,13 @@ module DotHelper
|
|||
end
|
||||
end
|
||||
|
||||
def ids(values)
|
||||
values.each_with_index { |id, i|
|
||||
raw ' ' if i > 0
|
||||
id id
|
||||
}
|
||||
end
|
||||
|
||||
def attr_list(attrs = nil)
|
||||
return if attrs.nil?
|
||||
attrs = attrs.select { |key, value| value.present? }
|
||||
|
@ -86,16 +93,13 @@ module DotHelper
|
|||
end
|
||||
|
||||
def statement(ids, attrs = nil)
|
||||
Array(ids).each_with_index { |id, i|
|
||||
raw ' ' if i > 0
|
||||
id id
|
||||
}
|
||||
ids Array(ids)
|
||||
attr_list attrs
|
||||
raw ';'
|
||||
end
|
||||
|
||||
def block(title, &block)
|
||||
raw title
|
||||
def block(*ids, &block)
|
||||
ids ids
|
||||
raw '{'
|
||||
block.call
|
||||
raw '}'
|
||||
|
@ -112,11 +116,7 @@ module DotHelper
|
|||
draw(agents: agents,
|
||||
agent_id: ->agent { 'a%d' % agent.id },
|
||||
agent_label: ->agent {
|
||||
if agent.disabled?
|
||||
'%s (Disabled)' % agent.name
|
||||
else
|
||||
agent.name
|
||||
end.gsub(/(.{20}\S*)\s+/) {
|
||||
agent.name.gsub(/(.{20}\S*)\s+/) {
|
||||
# Fold after every 20+ characters
|
||||
$1 + "\n"
|
||||
}
|
||||
|
@ -128,6 +128,7 @@ module DotHelper
|
|||
def agent_node(agent)
|
||||
node(agent_id[agent],
|
||||
label: agent_label[agent],
|
||||
tooltip: (agent.short_type.titleize if rich),
|
||||
URL: (agent_url[agent] if rich),
|
||||
style: ('rounded,dashed' if agent.disabled?),
|
||||
color: (@disabled if agent.disabled?),
|
||||
|
@ -141,7 +142,7 @@ module DotHelper
|
|||
color: (@disabled if agent.disabled? || receiver.disabled?))
|
||||
end
|
||||
|
||||
block('digraph foo') {
|
||||
block('digraph', 'Agent Event Flow') {
|
||||
# statement 'graph', rankdir: 'LR'
|
||||
statement 'node',
|
||||
shape: 'box',
|
||||
|
@ -160,4 +161,60 @@ module DotHelper
|
|||
}
|
||||
}
|
||||
end
|
||||
|
||||
def decorate_svg(xml, agents)
|
||||
svg = Nokogiri::XML(xml).at('svg')
|
||||
|
||||
Nokogiri::HTML::Document.new.tap { |doc|
|
||||
doc << root = Nokogiri::XML::Node.new('div', doc) { |div|
|
||||
div['class'] = 'agent-diagram'
|
||||
}
|
||||
|
||||
svg['class'] = 'diagram'
|
||||
|
||||
root << svg
|
||||
root << overlay_container = Nokogiri::XML::Node.new('div', doc) { |div|
|
||||
div['class'] = 'overlay-container'
|
||||
div['style'] = "width: #{svg['width']}; height: #{svg['height']}"
|
||||
}
|
||||
overlay_container << overlay = Nokogiri::XML::Node.new('div', doc) { |div|
|
||||
div['class'] = 'overlay'
|
||||
}
|
||||
|
||||
svg.xpath('//xmlns:g[@class="node"]', svg.namespaces).each { |node|
|
||||
agent_id = (node.xpath('./xmlns:title/text()', svg.namespaces).to_s[/\d+/] or next).to_i
|
||||
agent = agents.find { |a| a.id == agent_id }
|
||||
|
||||
count = agent.events_count
|
||||
next unless count && count > 0
|
||||
|
||||
overlay << Nokogiri::XML::Node.new('a', doc) { |badge|
|
||||
badge['id'] = id = 'b%d' % agent_id
|
||||
badge['class'] = 'badge'
|
||||
badge['href'] = events_path(agent: agent)
|
||||
badge['target'] = '_blank'
|
||||
badge['title'] = "#{count} events created"
|
||||
badge.content = count.to_s
|
||||
|
||||
node['data-badge-id'] = id
|
||||
|
||||
badge << Nokogiri::XML::Node.new('span', doc) { |label|
|
||||
# a dummy label only to obtain the background color
|
||||
label['class'] = [
|
||||
'label',
|
||||
if agent.disabled?
|
||||
'label-warning'
|
||||
elsif agent.working?
|
||||
'label-success'
|
||||
else
|
||||
'label-danger'
|
||||
end
|
||||
].join(' ')
|
||||
label['style'] = 'display: none';
|
||||
}
|
||||
}
|
||||
}
|
||||
# See also: app/assets/diagram.js.coffee
|
||||
}.at('div.agent-diagram').to_s
|
||||
end
|
||||
end
|
||||
|
|
|
@ -25,9 +25,14 @@ module Agents
|
|||
|
||||
"instructions": {
|
||||
"message": "Today's conditions look like {{conditions}} with a high temperature of {{high.celsius}} degrees Celsius.",
|
||||
"subject": "{{data}}"
|
||||
"subject": "{{data}}",
|
||||
"created_at": "{{created_at}}"
|
||||
}
|
||||
|
||||
Names here like `conditions`, `high` and `data` refer to the corresponding values in the Event hash.
|
||||
|
||||
The special key `created_at` refers to the timestamp of the Event, which can be reformatted by the `date` filter, like `{{created_at | date:"at %I:%M %p" }}`.
|
||||
|
||||
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(', ') }}.
|
||||
|
||||
Have a look at the [Wiki](https://github.com/cantino/huginn/wiki/Formatting-Events-using-Liquid) to learn more about liquid templating.
|
||||
|
@ -68,8 +73,6 @@ module Agents
|
|||
|
||||
If you want to retain original contents of events and only add new keys, then set `mode` to `merge`, otherwise set it to `clean`.
|
||||
|
||||
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`.
|
||||
|
||||
To CGI escape output (for example when creating a link), use the Liquid `uri_escape` filter, like so:
|
||||
|
||||
{
|
||||
|
@ -82,7 +85,7 @@ module Agents
|
|||
after_save :clear_matchers
|
||||
|
||||
def validate_options
|
||||
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?
|
||||
errors.add(:base, "instructions and mode need to be present.") unless options['instructions'].present? && options['mode'].present?
|
||||
|
||||
validate_matchers
|
||||
end
|
||||
|
@ -96,7 +99,6 @@ module Agents
|
|||
},
|
||||
'matchers' => [],
|
||||
'mode' => "clean",
|
||||
'skip_created_at' => "false"
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -110,7 +112,6 @@ module Agents
|
|||
opts = interpolated(event.to_liquid(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"
|
||||
create_event :payload => formatted_event
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'net/ftp'
|
||||
require 'net/ftp/list'
|
||||
require 'uri'
|
||||
require 'time'
|
||||
|
||||
|
@ -105,34 +106,15 @@ module Agents
|
|||
# commands during iteration.
|
||||
list = ftp.list('-a')
|
||||
|
||||
month2year = {}
|
||||
|
||||
list.each do |line|
|
||||
mon, day, smtn, rest = line.split(' ', 9)[5..-1]
|
||||
|
||||
# Remove symlink target part if any
|
||||
filename = rest[/\A(.+?)(?:\s+->\s|\z)/, 1]
|
||||
|
||||
entry = Net::FTP::List.parse line
|
||||
filename = entry.basename
|
||||
mtime = Time.parse(entry.mtime.to_s).utc
|
||||
|
||||
patterns.any? { |pattern|
|
||||
File.fnmatch?(pattern, filename)
|
||||
} or next
|
||||
|
||||
case smtn
|
||||
when /:/
|
||||
if year = month2year[mon]
|
||||
mtime = Time.parse("#{mon} #{day} #{year} #{smtn} GMT")
|
||||
else
|
||||
log "Getting mtime of #{filename}"
|
||||
mtime = ftp.mtime(filename)
|
||||
month2year[mon] = mtime.year
|
||||
end
|
||||
else
|
||||
# Do not bother calling MDTM for old files. Losing the
|
||||
# time part only makes a timestamp go backwards, meaning
|
||||
# that it will trigger no new event.
|
||||
mtime = Time.parse("#{mon} #{day} #{smtn} GMT")
|
||||
end
|
||||
|
||||
after < mtime or next
|
||||
|
||||
yield filename, mtime
|
||||
|
@ -193,7 +175,7 @@ module Agents
|
|||
found_entries[filename]
|
||||
}.each { |filename|
|
||||
create_event :payload => {
|
||||
'url' => (base_uri + filename).to_s,
|
||||
'url' => "#{base_uri}#{filename}",
|
||||
'filename' => filename,
|
||||
'timestamp' => found_entries[filename],
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ module Agents
|
|||
end
|
||||
|
||||
def validate_options
|
||||
errors.add(:base, "you need to specify a hipchat auth_token") unless options['auth_token'].present?
|
||||
errors.add(:base, "you need to specify a hipchat auth_token or provide a credential named hipchat_auth_token") unless options['auth_token'].present? || credential('hipchat_auth_token').present?
|
||||
errors.add(:base, "you need to specify a room_name or a room_name_path") if options['room_name'].blank? && options['room_name_path'].blank?
|
||||
end
|
||||
|
||||
|
@ -40,10 +40,10 @@ module Agents
|
|||
end
|
||||
|
||||
def receive(incoming_events)
|
||||
client = HipChat::Client.new(interpolated[:auth_token])
|
||||
client = HipChat::Client.new(interpolated[:auth_token] || credential('hipchat_auth_token'))
|
||||
incoming_events.each do |event|
|
||||
mo = interpolated(event)
|
||||
client[mo[:room_name]].send(mo[:username], mo[:message], :notify => mo[:notify].to_s == 'true' ? 1 : 0, :color => mo[:color])
|
||||
client[mo[:room_name]].send(mo[:username], mo[:message], :notify => boolify(mo[:notify]) ? 1 : 0, :color => mo[:color])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -17,7 +17,7 @@ module Agents
|
|||
|
||||
Simply choose a topic (think email subject line) to publish/listen to, and configure your service.
|
||||
|
||||
It's easy to setup your own [broker](http://jpmens.net/2013/09/01/installing-mosquitto-on-a-raspberry-pi/) or connect to a [cloud service](www.cloudmqtt.com)
|
||||
It's easy to setup your own [broker](http://jpmens.net/2013/09/01/installing-mosquitto-on-a-raspberry-pi/) or connect to a [cloud service](http://www.cloudmqtt.com)
|
||||
|
||||
Hints:
|
||||
Many services run mqtts (mqtt over SSL) often with a custom certificate.
|
||||
|
|
|
@ -69,7 +69,7 @@ module Agents
|
|||
def receive(incoming_events)
|
||||
incoming_events.each do |event|
|
||||
outgoing = interpolated(event)['payload'].presence || {}
|
||||
if interpolated['no_merge'].to_s == 'true'
|
||||
if boolify(interpolated['no_merge'])
|
||||
handle outgoing, event.payload
|
||||
else
|
||||
handle outgoing.merge(event.payload), event.payload
|
||||
|
|
|
@ -102,7 +102,7 @@ module Agents
|
|||
end
|
||||
|
||||
def keep_event?
|
||||
interpolated['keep_event'] == 'true'
|
||||
boolify(interpolated['keep_event'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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)['receive_call'].to_s == 'true'
|
||||
if boolify(interpolated(event)['receive_call'])
|
||||
secret = SecureRandom.hex 3
|
||||
memory['pending_calls'][secret] = message
|
||||
make_call secret
|
||||
end
|
||||
|
||||
if interpolated(event)['receive_text'].to_s == 'true'
|
||||
if boolify(interpolated(event)['receive_text'])
|
||||
message = message.slice 0..160
|
||||
send_message message
|
||||
end
|
||||
|
|
|
@ -56,6 +56,8 @@ class EventDrop
|
|||
case key
|
||||
when 'agent'
|
||||
@object.agent
|
||||
when 'created_at'
|
||||
@object.created_at
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
</td>
|
||||
<td class='<%= "agent-disabled" if agent.disabled? %>'>
|
||||
<% if agent.can_create_events? %>
|
||||
<%= link_to(agent.events_count || 0, events_path(:agent => agent.to_param)) %>
|
||||
<%= link_to(agent.events_count || 0, agent_events_path(agent)) %>
|
||||
<% else %>
|
||||
<span class='not-applicable'></span>
|
||||
<% end %>
|
||||
|
|
|
@ -12,9 +12,8 @@
|
|||
<div class="btn-group">
|
||||
<%= link_to '<span class="glyphicon glyphicon-plus"></span> New Agent'.html_safe, new_agent_path, class: "btn btn-default" %>
|
||||
<%= link_to '<span class="glyphicon glyphicon-refresh"></span> Run event propagation'.html_safe, propagate_agents_path, method: 'post', class: "btn btn-default" %>
|
||||
<%= link_to '<span class="glyphicon glyphicon-random"></span> View diagram'.html_safe, diagram_agents_path, class: "btn btn-default" %>
|
||||
<%= link_to '<span class="glyphicon glyphicon-random"></span> View diagram'.html_safe, diagram_path, class: "btn btn-default" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<li><a href="#logs" data-toggle="tab" data-agent-id="<%= @agent.id %>" class='<%= @agent.recent_error_logs? ? 'recent-errors' : '' %>'><span class='glyphicon glyphicon-list-alt'></span> Logs</a></li>
|
||||
|
||||
<% if @agent.can_create_events? && @agent.events.count > 0 %>
|
||||
<li><%= link_to '<span class="glyphicon glyphicon-random"></span> Events'.html_safe, events_path(:agent => @agent.to_param) %></li>
|
||||
<li><%= link_to '<span class="glyphicon glyphicon-random"></span> Events'.html_safe, agent_events_path(@agent) %></li>
|
||||
<% else %>
|
||||
<li class='disabled'><a><span class='glyphicon glyphicon-random'></span> Events</a></li>
|
||||
<% end %>
|
||||
|
@ -103,7 +103,7 @@
|
|||
<% if @agent.can_create_events? %>
|
||||
<p>
|
||||
<b>Events created:</b>
|
||||
<%= link_to @agent.events.count, events_path(:agent => @agent.to_param) %>
|
||||
<%= link_to @agent.events.count, agent_events_path(@agent) %>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
<% content_for :head do %>
|
||||
<%= javascript_include_tag "diagram" %>
|
||||
<% end %>
|
||||
|
||||
<div class='container'>
|
||||
<div class='row'>
|
||||
<div class='col-md-12'>
|
|
@ -41,7 +41,7 @@
|
|||
agentPaths["New Agent"] = <%= Utils.jsonify new_agent_path %>;
|
||||
agentPaths["Account"] = <%= Utils.jsonify edit_user_registration_path %>;
|
||||
agentPaths["Events Index"] = <%= Utils.jsonify events_path %>;
|
||||
agentPaths["View Agent Diagram"] = <%= Utils.jsonify diagram_agents_path %>;
|
||||
agentPaths["View Agent Diagram"] = <%= Utils.jsonify diagram_path %>;
|
||||
agentPaths["Run Event Propagation"] = { url: <%= Utils.jsonify propagate_agents_path %>, method: 'POST' };
|
||||
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
<div class="btn-group">
|
||||
<%= link_to '<span class="glyphicon glyphicon-chevron-left"></span> Back'.html_safe, scenarios_path, class: "btn btn-default" %>
|
||||
<%= link_to '<span class="glyphicon glyphicon-random"></span> View Diagram'.html_safe, diagram_agents_path(:scenario_id => @scenario.to_param), class: "btn btn-default" %>
|
||||
<%= link_to '<span class="glyphicon glyphicon-random"></span> View Diagram'.html_safe, scenario_diagram_path(@scenario), class: "btn btn-default" %>
|
||||
<%= link_to '<span class="glyphicon glyphicon-edit"></span> Edit'.html_safe, edit_scenario_path(@scenario), class: "btn btn-default" %>
|
||||
<% if @scenario.source_url.present? %>
|
||||
<%= link_to '<span class="glyphicon glyphicon-plus"></span> Update'.html_safe, new_scenario_imports_path(:url => @scenario.source_url), class: "btn btn-default" %>
|
||||
|
|
|
@ -61,7 +61,7 @@ Huginn::Application.configure do
|
|||
end
|
||||
|
||||
# Precompile additional assets (application.js.coffee.erb, application.css, and all non-JS/CSS are already added)
|
||||
config.assets.precompile += %w( graphing.js user_credentials.js )
|
||||
config.assets.precompile += %w( diagram.js graphing.js user_credentials.js )
|
||||
|
||||
# Ignore bad email addresses and do not raise email delivery errors.
|
||||
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
|
||||
|
|
|
@ -11,7 +11,6 @@ Huginn::Application.routes.draw do
|
|||
post :propagate
|
||||
get :type_details
|
||||
get :event_descriptions
|
||||
get :diagram
|
||||
end
|
||||
|
||||
resources :logs, :only => [:index] do
|
||||
|
@ -19,8 +18,12 @@ Huginn::Application.routes.draw do
|
|||
delete :clear
|
||||
end
|
||||
end
|
||||
|
||||
resources :events, :only => [:index]
|
||||
end
|
||||
|
||||
resource :diagram, :only => [:show]
|
||||
|
||||
resources :events, :only => [:index, :show, :destroy] do
|
||||
member do
|
||||
post :reemit
|
||||
|
@ -36,6 +39,8 @@ Huginn::Application.routes.draw do
|
|||
get :share
|
||||
get :export
|
||||
end
|
||||
|
||||
resource :diagram, :only => [:show]
|
||||
end
|
||||
|
||||
resources :user_credentials, :except => :show
|
||||
|
|
21
db/migrate/20140730005210_convert_efa_skip_created_at.rb
Normal file
21
db/migrate/20140730005210_convert_efa_skip_created_at.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class ConvertEfaSkipCreatedAt < ActiveRecord::Migration
|
||||
def up
|
||||
Agent.where(type: 'Agents::EventFormattingAgent').each do |agent|
|
||||
agent.options_will_change!
|
||||
unless agent.options.delete('skip_created_at').to_s == 'true'
|
||||
agent.options['instructions'] = {
|
||||
'created_at' => '{{created_at}}'
|
||||
}.update(agent.options['instructions'] || {})
|
||||
end
|
||||
agent.save!
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
Agent.where(type: 'Agents::EventFormattingAgent').each do |agent|
|
||||
agent.options_will_change!
|
||||
agent.options['skip_created_at'] = (agent.options['instructions'] || {})['created_at'] == '{{created_at}}'
|
||||
agent.save!
|
||||
end
|
||||
end
|
||||
end
|
|
@ -64,7 +64,7 @@ unless user.agents.where(:name => "Rain Notifier").exists?
|
|||
'value' => "rain|storm",
|
||||
'path' => "conditions"
|
||||
}],
|
||||
'message' => "Just so you know, it looks like '<conditions>' tomorrow in <location>"
|
||||
'message' => "Just so you know, it looks like '{{conditions}}' tomorrow in {{location}}"
|
||||
}).save!
|
||||
end
|
||||
|
||||
|
|
|
@ -15,12 +15,12 @@ describe EventsController do
|
|||
|
||||
it "can filter by Agent" do
|
||||
sign_in users(:bob)
|
||||
get :index, :agent => agents(:bob_website_agent)
|
||||
get :index, :agent_id => agents(:bob_website_agent)
|
||||
assigns(:events).length.should == agents(:bob_website_agent).events.length
|
||||
assigns(:events).all? {|i| i.agent.should == agents(:bob_website_agent) }.should be_true
|
||||
|
||||
lambda {
|
||||
get :index, :agent => agents(:jane_website_agent)
|
||||
get :index, :agent_id => agents(:jane_website_agent)
|
||||
}.should raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
|
|
5
spec/env.test
Normal file
5
spec/env.test
Normal file
|
@ -0,0 +1,5 @@
|
|||
APP_SECRET_TOKEN=notarealappsecrettoken
|
||||
TWITTER_OAUTH_KEY=twitteroauthkey
|
||||
TWITTER_OAUTH_SECRET=twitteroauthsecret
|
||||
THIRTY_SEVEN_SIGNALS_OAUTH_KEY=TESTKEY
|
||||
THIRTY_SEVEN_SIGNALS_OAUTH_SECRET=TESTSECRET
|
4
spec/fixtures/agents.yml
vendored
4
spec/fixtures/agents.yml
vendored
|
@ -72,7 +72,7 @@ jane_rain_notifier_agent:
|
|||
:value => "rain",
|
||||
:path => "conditions"
|
||||
}],
|
||||
:message => "Just so you know, it looks like '<conditions>' tomorrow in <location>"
|
||||
:message => "Just so you know, it looks like '{{conditions}}' tomorrow in {{location}}"
|
||||
}.to_json.inspect %>
|
||||
|
||||
bob_rain_notifier_agent:
|
||||
|
@ -87,7 +87,7 @@ bob_rain_notifier_agent:
|
|||
:value => "rain",
|
||||
:path => "conditions"
|
||||
}],
|
||||
:message => "Just so you know, it looks like '<conditions>' tomorrow in <location>"
|
||||
:message => "Just so you know, it looks like '{{conditions}}' tomorrow in {{location}}"
|
||||
}.to_json.inspect %>
|
||||
|
||||
bob_twitter_user_agent:
|
||||
|
|
|
@ -56,13 +56,13 @@ describe DotHelper do
|
|||
it "generates a DOT script" do
|
||||
agents_dot(@agents).should =~ %r{
|
||||
\A
|
||||
digraph \s foo \{
|
||||
digraph \x20 "Agent \x20 Event \x20 Flow" \{
|
||||
node \[ [^\]]+ \];
|
||||
(?<foo>\w+) \[label=foo\];
|
||||
\k<foo> -> (?<bar1>\w+) \[style=dashed\];
|
||||
\k<foo> -> (?<bar2>\w+) \[color="\#999999"\];
|
||||
\k<bar1> \[label=bar1\];
|
||||
\k<bar2> \[label="bar2 \s \(Disabled\)",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
|
||||
\k<bar2> \[label=bar2,style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
|
||||
\k<bar2> -> (?<bar3>\w+) \[style=dashed,color="\#999999"\];
|
||||
\k<bar3> \[label=bar3\];
|
||||
\}
|
||||
|
@ -73,15 +73,15 @@ describe DotHelper do
|
|||
it "generates a richer DOT script" do
|
||||
agents_dot(@agents, true).should =~ %r{
|
||||
\A
|
||||
digraph \s foo \{
|
||||
digraph \x20 "Agent \x20 Event \x20 Flow" \{
|
||||
node \[ [^\]]+ \];
|
||||
(?<foo>\w+) \[label=foo,URL="#{Regexp.quote(agent_path(@foo))}"\];
|
||||
(?<foo>\w+) \[label=foo,tooltip="Dot \x20 Foo",URL="#{Regexp.quote(agent_path(@foo))}"\];
|
||||
\k<foo> -> (?<bar1>\w+) \[style=dashed\];
|
||||
\k<foo> -> (?<bar2>\w+) \[color="\#999999"\];
|
||||
\k<bar1> \[label=bar1,URL="#{Regexp.quote(agent_path(@bar1))}"\];
|
||||
\k<bar2> \[label="bar2 \s \(Disabled\)",URL="#{Regexp.quote(agent_path(@bar2))}",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
|
||||
\k<bar1> \[label=bar1,tooltip="Dot \x20 Bar",URL="#{Regexp.quote(agent_path(@bar1))}"\];
|
||||
\k<bar2> \[label=bar2,tooltip="Dot \x20 Bar",URL="#{Regexp.quote(agent_path(@bar2))}",style="rounded,dashed",color="\#999999",fontcolor="\#999999"\];
|
||||
\k<bar2> -> (?<bar3>\w+) \[style=dashed,color="\#999999"\];
|
||||
\k<bar3> \[label=bar3,URL="#{Regexp.quote(agent_path(@bar3))}"\];
|
||||
\k<bar3> \[label=bar3,tooltip="Dot \x20 Bar",URL="#{Regexp.quote(agent_path(@bar3))}"\];
|
||||
\}
|
||||
\z
|
||||
}x
|
||||
|
|
|
@ -9,6 +9,8 @@ describe Agents::EventFormattingAgent do
|
|||
:message => "Received {{content.text}} from {{content.name}} .",
|
||||
:subject => "Weather looks like {{conditions}} according to the forecast at {{pretty_date.time}}",
|
||||
:agent => "{{agent.type}}",
|
||||
:created_at => "{{created_at}}",
|
||||
:created_at_iso => "{{created_at | date:'%FT%T%:z'}}",
|
||||
},
|
||||
:mode => "clean",
|
||||
:matchers => [
|
||||
|
@ -18,7 +20,6 @@ describe Agents::EventFormattingAgent do
|
|||
:to => "pretty_date",
|
||||
},
|
||||
],
|
||||
:skip_created_at => "false"
|
||||
}
|
||||
}
|
||||
@checker = Agents::EventFormattingAgent.new(@valid_params)
|
||||
|
@ -53,18 +54,12 @@ describe Agents::EventFormattingAgent do
|
|||
Event.last.payload[:content].should_not == nil
|
||||
end
|
||||
|
||||
it "should accept skip_created_at" do
|
||||
@checker.receive([@event])
|
||||
Event.last.payload[:created_at].should_not == nil
|
||||
@checker.options[:skip_created_at] = "true"
|
||||
@checker.receive([@event])
|
||||
Event.last.payload[:created_at].should == nil
|
||||
end
|
||||
|
||||
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[:created_at].should == @event.created_at.to_s
|
||||
Event.last.payload[:created_at_iso].should == @event.created_at.iso8601
|
||||
end
|
||||
|
||||
it "should handle matchers and Liquid templating in instructions" do
|
||||
|
@ -144,10 +139,5 @@ describe Agents::EventFormattingAgent do
|
|||
@checker.options[:mode] = ""
|
||||
@checker.should_not be_valid
|
||||
end
|
||||
|
||||
it "should validate presence of skip_created_at" do
|
||||
@checker.options[:skip_created_at] = ""
|
||||
@checker.should_not be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,19 +7,23 @@ describe Agents::FtpsiteAgent do
|
|||
@site = {
|
||||
'expected_update_period_in_days' => 1,
|
||||
'url' => "ftp://ftp.example.org/pub/releases/",
|
||||
'patterns' => ["example-*.tar.gz"],
|
||||
'patterns' => ["example*.tar.gz"],
|
||||
}
|
||||
@checker = Agents::FtpsiteAgent.new(:name => "Example", :options => @site, :keep_events_for => 2)
|
||||
@checker.user = users(:bob)
|
||||
@checker.save!
|
||||
stub(@checker).each_entry.returns { |block|
|
||||
block.call("example-latest.tar.gz", Time.parse("2014-04-01T10:00:01Z"))
|
||||
block.call("example-1.0.tar.gz", Time.parse("2013-10-01T10:00:00Z"))
|
||||
block.call("example-1.1.tar.gz", Time.parse("2014-04-01T10:00:00Z"))
|
||||
}
|
||||
end
|
||||
|
||||
describe "#check" do
|
||||
|
||||
before do
|
||||
stub(@checker).each_entry.returns { |block|
|
||||
block.call("example latest.tar.gz", Time.parse("2014-04-01T10:00:01Z"))
|
||||
block.call("example-1.0.tar.gz", Time.parse("2013-10-01T10:00:00Z"))
|
||||
block.call("example-1.1.tar.gz", Time.parse("2014-04-01T10:00:00Z"))
|
||||
}
|
||||
end
|
||||
|
||||
it "should validate the integer fields" do
|
||||
@checker.options['expected_update_period_in_days'] = "nonsense"
|
||||
lambda { @checker.save! }.should raise_error;
|
||||
|
@ -33,7 +37,7 @@ describe Agents::FtpsiteAgent do
|
|||
known_entries.sort_by(&:last).should == [
|
||||
["example-1.0.tar.gz", "2013-10-01T10:00:00Z"],
|
||||
["example-1.1.tar.gz", "2014-04-01T10:00:00Z"],
|
||||
["example-latest.tar.gz", "2014-04-01T10:00:01Z"],
|
||||
["example latest.tar.gz", "2014-04-01T10:00:01Z"],
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -46,7 +50,7 @@ describe Agents::FtpsiteAgent do
|
|||
lambda { @checker.check }.should_not change { Event.count }
|
||||
|
||||
stub(@checker).each_entry.returns { |block|
|
||||
block.call("example-latest.tar.gz", Time.parse("2014-04-02T10:00:01Z"))
|
||||
block.call("example latest.tar.gz", Time.parse("2014-04-02T10:00:01Z"))
|
||||
|
||||
# In the long list format the timestamp may look going
|
||||
# backwards after six months: Oct 01 10:00 -> Oct 01 2013
|
||||
|
@ -62,7 +66,7 @@ describe Agents::FtpsiteAgent do
|
|||
["example-1.0.tar.gz", "2013-10-01T00:00:00Z"],
|
||||
["example-1.1.tar.gz", "2014-04-01T10:00:00Z"],
|
||||
["example-1.2.tar.gz", "2014-04-02T10:00:00Z"],
|
||||
["example-latest.tar.gz", "2014-04-02T10:00:01Z"],
|
||||
["example latest.tar.gz", "2014-04-02T10:00:01Z"],
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -75,5 +79,33 @@ describe Agents::FtpsiteAgent do
|
|||
lambda { @checker.check }.should_not change { Event.count }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#each_entry" do
|
||||
before do
|
||||
stub.any_instance_of(Net::FTP).list.returns [ # Windows format
|
||||
"04-02-14 10:01AM 288720748 example latest.tar.gz",
|
||||
"04-01-14 10:05AM 288720710 no-match-example.tar.gz"
|
||||
]
|
||||
stub(@checker).open_ftp.yields Net::FTP.new
|
||||
end
|
||||
|
||||
it "filters out files that don't match the given format" do
|
||||
entries = []
|
||||
@checker.each_entry { |a, b| entries.push [a, b] }
|
||||
|
||||
entries.size.should == 1
|
||||
filename, mtime = entries.first
|
||||
filename.should == 'example latest.tar.gz'
|
||||
mtime.should == '2014-04-02T10:01:00Z'
|
||||
end
|
||||
|
||||
it "filters out files that are older than the given date" do
|
||||
@checker.options['after'] = '2015-10-21'
|
||||
entries = []
|
||||
@checker.each_entry { |a, b| entries.push [a, b] }
|
||||
entries.size.should == 0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,6 +42,12 @@ describe Agents::HipchatAgent do
|
|||
@checker.should be_valid
|
||||
end
|
||||
|
||||
it "should also allow a credential" do
|
||||
@checker.options['auth_token'] = nil
|
||||
@checker.should_not be_valid
|
||||
@checker.user.user_credentials.create :credential_name => 'hipchat_auth_token', :credential_value => 'something'
|
||||
@checker.reload.should be_valid
|
||||
end
|
||||
end
|
||||
|
||||
describe "#receive" do
|
||||
|
|
|
@ -85,6 +85,7 @@ describe EventDrop do
|
|||
before do
|
||||
@event = Event.new
|
||||
@event.agent = agents(:jane_weather_agent)
|
||||
@event.created_at = Time.at(1400000000)
|
||||
@event.payload = {
|
||||
'title' => 'some title',
|
||||
'url' => 'http://some.site.example.org/',
|
||||
|
@ -111,4 +112,9 @@ describe EventDrop do
|
|||
t = '{{agent.name}}'
|
||||
interpolate(t, @event).should eq('SF Weather')
|
||||
end
|
||||
|
||||
it 'should have created_at' do
|
||||
t = '{{created_at | date:"%FT%T%z" }}'
|
||||
interpolate(t, @event).should eq('2014-05-13T09:53:20-0700')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,8 +59,6 @@ describe Service do
|
|||
stub_request(:post, "https://launchpad.37signals.com/authorization/token?client_id=TESTKEY&client_secret=TESTSECRET&refresh_token=refreshtokentest&type=refresh").
|
||||
to_return(:status => 200, :body => '{"expires_in":1209600,"access_token": "NEWTOKEN"}', :headers => {})
|
||||
@service.provider = '37signals'
|
||||
ENV['THIRTY_SEVEN_SIGNALS_OAUTH_KEY'] = 'TESTKEY'
|
||||
ENV['THIRTY_SEVEN_SIGNALS_OAUTH_SECRET'] = 'TESTSECRET'
|
||||
@service.refresh_token = 'refreshtokentest'
|
||||
@service.refresh_token!
|
||||
@service.token.should == 'NEWTOKEN'
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
||||
ENV["RAILS_ENV"] ||= 'test'
|
||||
|
||||
if ENV['COVERAGE']
|
||||
|
@ -9,6 +8,10 @@ else
|
|||
Coveralls.wear!('rails')
|
||||
end
|
||||
|
||||
# Required ENV variables that are normally set in .env are setup here for the test environment.
|
||||
require 'dotenv'
|
||||
Dotenv.load File.join(File.dirname(__FILE__), "env.test")
|
||||
|
||||
require File.expand_path("../../config/environment", __FILE__)
|
||||
require 'rspec/rails'
|
||||
require 'rspec/autorun'
|
||||
|
@ -19,7 +22,7 @@ WebMock.disable_net_connect!
|
|||
|
||||
# Requires supporting ruby files with custom matchers and macros, etc,
|
||||
# in spec/support/ and its subdirectories.
|
||||
Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
|
||||
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
|
||||
|
||||
ActiveRecord::Migration.maintain_test_schema!
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue