@@ -8,13 +8,14 @@ |
||
| 8 | 8 |
#= require ./worker-checker |
| 9 | 9 |
#= require_self |
| 10 | 10 |
|
| 11 |
-setupJsonEditor = -> |
|
| 11 |
+window.setupJsonEditor = ($editor = $(".live-json-editor")) ->
|
|
| 12 | 12 |
JSONEditor.prototype.ADD_IMG = '<%= image_path 'json-editor/add.png' %>' |
| 13 | 13 |
JSONEditor.prototype.DELETE_IMG = '<%= image_path 'json-editor/delete.png' %>' |
| 14 |
- if $(".live-json-editor").length
|
|
| 15 |
- window.jsonEditor = new JSONEditor($(".live-json-editor"), 400, 500)
|
|
| 16 |
- window.jsonEditor.doTruncation true |
|
| 17 |
- window.jsonEditor.showFunctionButtons() |
|
| 14 |
+ if $editor.length |
|
| 15 |
+ jsonEditor = new JSONEditor($editor, $editor.data('width') || 400, $editor.data('height') || 500)
|
|
| 16 |
+ jsonEditor.doTruncation true |
|
| 17 |
+ jsonEditor.showFunctionButtons() |
|
| 18 |
+ return jsonEditor |
|
| 18 | 19 |
|
| 19 | 20 |
hideSchedule = -> |
| 20 | 21 |
$(".schedule-region select").hide()
|
@@ -45,7 +46,7 @@ showEventDescriptions = -> |
||
| 45 | 46 |
|
| 46 | 47 |
$(document).ready -> |
| 47 | 48 |
# JSON Editor |
| 48 |
- setupJsonEditor() |
|
| 49 |
+ window.jsonEditor = setupJsonEditor() |
|
| 49 | 50 |
|
| 50 | 51 |
# Select2 Selects |
| 51 | 52 |
$(".select2").select2(width: 'resolve')
|
@@ -54,7 +55,35 @@ $(document).ready -> |
||
| 54 | 55 |
if $(".flash").length
|
| 55 | 56 |
setTimeout((-> $(".flash").slideUp(-> $(".flash").remove())), 5000)
|
| 56 | 57 |
|
| 57 |
- # Agent Show |
|
| 58 |
+ # Agent Navigation |
|
| 59 |
+ $agentNavigate = $('#agent-navigate')
|
|
| 60 |
+ $agentNavigate.typeahead( |
|
| 61 |
+ minLength: 0, |
|
| 62 |
+ items: 15, |
|
| 63 |
+ source: agentNames |
|
| 64 |
+ ).on("change", (e) ->
|
|
| 65 |
+ if agentPaths[$agentNavigate.val()] |
|
| 66 |
+ $('#agent-navigate').closest(".navbar-search").find(".spinner").show()
|
|
| 67 |
+ navigationData = agentPaths[$agentNavigate.val()] |
|
| 68 |
+ if !(navigationData instanceof Object) || !navigationData.method || navigationData.method == 'GET' |
|
| 69 |
+ window.location = navigationData.url || navigationData |
|
| 70 |
+ else |
|
| 71 |
+ $("<a href='#{navigationData.url}' data-method='#{navigationData.method}'></a>").appendTo($("body")).click()
|
|
| 72 |
+ |
|
| 73 |
+ ).on("focus", (e) ->
|
|
| 74 |
+ $agentNavigate.val '' |
|
| 75 |
+ ).on("blur", (e) ->
|
|
| 76 |
+ $agentNavigate.val '' |
|
| 77 |
+ ) |
|
| 78 |
+ |
|
| 79 |
+ # Pressing '/' selects the search box. |
|
| 80 |
+ $("body").on "keypress", (e) ->
|
|
| 81 |
+ if e.keyCode == 47 # The '/' key |
|
| 82 |
+ if e.target.nodeName == "BODY" |
|
| 83 |
+ e.preventDefault() |
|
| 84 |
+ $agentNavigate.focus() |
|
| 85 |
+ |
|
| 86 |
+# Agent Show |
|
| 58 | 87 |
fetchLogs = (e) -> |
| 59 | 88 |
agentId = $(e.target).closest("[data-agent-id]").data("agent-id")
|
| 60 | 89 |
e.preventDefault() |
@@ -76,16 +105,19 @@ $(document).ready -> |
||
| 76 | 105 |
$("#logs .spinner").stop(true, true).fadeOut ->
|
| 77 | 106 |
$("#logs .refresh, #logs .clear").show()
|
| 78 | 107 |
|
| 79 |
- $(".agent-show #show-tabs a[href='#logs']").on "click", fetchLogs
|
|
| 80 |
- $("#logs .refresh").on "click", fetchLogs
|
|
| 81 |
- $("#logs .clear").on "click", clearLogs
|
|
| 108 |
+ $(".agent-show #show-tabs a[href='#logs'], #logs .refresh").on "click", fetchLogs
|
|
| 109 |
+ $(".agent-show #logs .clear").on "click", clearLogs
|
|
| 110 |
+ |
|
| 111 |
+ if tab = window.location.href.match(/tab=(\w+)\b/i)?[1] |
|
| 112 |
+ if tab in ["details", "logs"] |
|
| 113 |
+ $(".agent-show .nav-tabs li a[href='##{tab}']").click()
|
|
| 82 | 114 |
|
| 83 | 115 |
# Editing Agents |
| 84 | 116 |
$("#agent_source_ids").on "change", showEventDescriptions
|
| 85 | 117 |
|
| 86 | 118 |
$("#agent_type").on "change", ->
|
| 87 | 119 |
if window.jsonEditor? |
| 88 |
- $(".spinner").fadeIn();
|
|
| 120 |
+ $("#agent-spinner").fadeIn();
|
|
| 89 | 121 |
$("#agent_source_ids").select2("val", {});
|
| 90 | 122 |
$(".event-descriptions").html("").hide()
|
| 91 | 123 |
$.getJSON "/agents/type_details", { type: $(@).val() }, (json) =>
|
@@ -104,7 +136,7 @@ $(document).ready -> |
||
| 104 | 136 |
window.jsonEditor.json = json.options |
| 105 | 137 |
window.jsonEditor.rebuild() |
| 106 | 138 |
|
| 107 |
- $(".spinner").stop(true, true).fadeOut();
|
|
| 139 |
+ $("#agent-spinner").stop(true, true).fadeOut();
|
|
| 108 | 140 |
|
| 109 | 141 |
$("#agent_type").change() if $("#agent_type").length
|
| 110 | 142 |
|
@@ -51,10 +51,6 @@ table.events {
|
||
| 51 | 51 |
margin-left: 0 !important; |
| 52 | 52 |
} |
| 53 | 53 |
|
| 54 |
-#job-indicator, #event-indicator {
|
|
| 55 |
- display: none; |
|
| 56 |
-} |
|
| 57 |
- |
|
| 58 | 54 |
img.odin {
|
| 59 | 55 |
position: relative; |
| 60 | 56 |
top: -32px; |
@@ -86,14 +82,30 @@ img.spinner {
|
||
| 86 | 82 |
overflow: hidden; |
| 87 | 83 |
} |
| 88 | 84 |
|
| 85 |
+span.not-applicable:after {
|
|
| 86 |
+ color: #bbbbbb; |
|
| 87 |
+ content: "n/a"; |
|
| 88 |
+} |
|
| 89 |
+ |
|
| 90 |
+// Navbar |
|
| 91 |
+ |
|
| 92 |
+#job-indicator, #event-indicator {
|
|
| 93 |
+ display: none; |
|
| 94 |
+} |
|
| 95 |
+ |
|
| 96 |
+.navbar-search > .spinner {
|
|
| 97 |
+ position: absolute; |
|
| 98 |
+ top: -1px; |
|
| 99 |
+ right: 1px; |
|
| 100 |
+} |
|
| 101 |
+ |
|
| 89 | 102 |
// Flash |
| 90 | 103 |
|
| 91 | 104 |
.flash {
|
| 92 | 105 |
position: fixed; |
| 93 |
- width: 500px; |
|
| 106 |
+ width: 210px; |
|
| 94 | 107 |
z-index: 99999; |
| 95 |
- top: 1px; |
|
| 96 |
- margin-left: 250px; |
|
| 108 |
+ right: 20px; |
|
| 97 | 109 |
|
| 98 | 110 |
.alert {
|
| 99 | 111 |
} |
@@ -109,4 +121,4 @@ img.spinner {
|
||
| 109 | 121 |
&.refresh {
|
| 110 | 122 |
margin: 0 10px; |
| 111 | 123 |
} |
| 112 |
-} |
|
| 124 |
+} |
@@ -8,6 +8,16 @@ class AgentsController < ApplicationController |
||
| 8 | 8 |
end |
| 9 | 9 |
end |
| 10 | 10 |
|
| 11 |
+ def handle_details_post |
|
| 12 |
+ @agent = current_user.agents.find(params[:id]) |
|
| 13 |
+ if @agent.respond_to?(:handle_details_post) |
|
| 14 |
+ render :json => @agent.handle_details_post(params) || {}
|
|
| 15 |
+ else |
|
| 16 |
+ @agent.error "#handle_details_post called on an instance of #{@agent.class} that does not define it."
|
|
| 17 |
+ head 500 |
|
| 18 |
+ end |
|
| 19 |
+ end |
|
| 20 |
+ |
|
| 11 | 21 |
def run |
| 12 | 22 |
agent = current_user.agents.find(params[:id]) |
| 13 | 23 |
Agent.async_check(agent.id) |
@@ -11,7 +11,7 @@ module ApplicationHelper |
||
| 11 | 11 |
if agent.working? |
| 12 | 12 |
'<span class="label label-success">Yes</span>'.html_safe |
| 13 | 13 |
else |
| 14 |
- '<span class="label label-warning">No</span>'.html_safe |
|
| 14 |
+ link_to '<span class="label label-warning">No</span>'.html_safe, agent_path(agent, :tab => (agent.recent_error_logs? ? 'logs' : 'details')) |
|
| 15 | 15 |
end |
| 16 | 16 |
end |
| 17 | 17 |
end |
@@ -2,7 +2,7 @@ class SystemMailer < ActionMailer::Base |
||
| 2 | 2 |
default :from => ENV['EMAIL_FROM_ADDRESS'] || 'you@example.com' |
| 3 | 3 |
|
| 4 | 4 |
def send_message(options) |
| 5 |
- @lines = options[:lines] |
|
| 5 |
+ @groups = options[:groups] |
|
| 6 | 6 |
@headline = options[:headline] |
| 7 | 7 |
mail :to => options[:to], :subject => options[:subject] |
| 8 | 8 |
end |
@@ -88,7 +88,11 @@ class Agent < ActiveRecord::Base |
||
| 88 | 88 |
end |
| 89 | 89 |
|
| 90 | 90 |
def create_event(attrs) |
| 91 |
- events.create!({ :user => user }.merge(attrs))
|
|
| 91 |
+ if can_create_events? |
|
| 92 |
+ events.create!({ :user => user }.merge(attrs))
|
|
| 93 |
+ else |
|
| 94 |
+ error "This Agent cannot create events!" |
|
| 95 |
+ end |
|
| 92 | 96 |
end |
| 93 | 97 |
|
| 94 | 98 |
def validate_schedule |
@@ -117,7 +121,7 @@ class Agent < ActiveRecord::Base |
||
| 117 | 121 |
end |
| 118 | 122 |
|
| 119 | 123 |
def last_event_at |
| 120 |
- @memoized_last_event_at ||= events.select(:created_at).first.try(:created_at) |
|
| 124 |
+ @memoized_last_event_at ||= most_recent_event.try(:created_at) |
|
| 121 | 125 |
end |
| 122 | 126 |
|
| 123 | 127 |
def default_schedule |
@@ -140,6 +144,14 @@ class Agent < ActiveRecord::Base |
||
| 140 | 144 |
!cannot_receive_events? |
| 141 | 145 |
end |
| 142 | 146 |
|
| 147 |
+ def cannot_create_events? |
|
| 148 |
+ self.class.cannot_create_events? |
|
| 149 |
+ end |
|
| 150 |
+ |
|
| 151 |
+ def can_create_events? |
|
| 152 |
+ !cannot_create_events? |
|
| 153 |
+ end |
|
| 154 |
+ |
|
| 143 | 155 |
def set_last_checked_event_id |
| 144 | 156 |
if newest_event_id = Event.order("id desc").limit(1).pluck(:id).first
|
| 145 | 157 |
self.last_checked_event_id = newest_event_id |
@@ -170,6 +182,14 @@ class Agent < ActiveRecord::Base |
||
| 170 | 182 |
@default_schedule |
| 171 | 183 |
end |
| 172 | 184 |
|
| 185 |
+ def cannot_create_events! |
|
| 186 |
+ @cannot_create_events = true |
|
| 187 |
+ end |
|
| 188 |
+ |
|
| 189 |
+ def cannot_create_events? |
|
| 190 |
+ !!@cannot_create_events |
|
| 191 |
+ end |
|
| 192 |
+ |
|
| 173 | 193 |
def cannot_receive_events! |
| 174 | 194 |
@cannot_receive_events = true |
| 175 | 195 |
end |
@@ -3,6 +3,8 @@ module Agents |
||
| 3 | 3 |
MAIN_KEYS = %w[title message text main value].map(&:to_sym) |
| 4 | 4 |
default_schedule "5am" |
| 5 | 5 |
|
| 6 |
+ cannot_create_events! |
|
| 7 |
+ |
|
| 6 | 8 |
description <<-MD |
| 7 | 9 |
The DigestEmailAgent collects any Events sent to it and sends them all via email when run. |
| 8 | 10 |
The email will be sent to your account's address and will have a `subject` and an optional `headline` before |
@@ -37,30 +39,28 @@ module Agents |
||
| 37 | 39 |
|
| 38 | 40 |
def check |
| 39 | 41 |
if self.memory[:queue] && self.memory[:queue].length > 0 |
| 40 |
- lines = self.memory[:queue].map {|item| present(item) }
|
|
| 42 |
+ groups = self.memory[:queue].map { |payload| present(payload) }
|
|
| 41 | 43 |
log "Sending digest mail to #{user.email}"
|
| 42 |
- SystemMailer.delay.send_message(:to => user.email, :subject => options[:subject], :headline => options[:headline], :lines => lines) |
|
| 44 |
+ SystemMailer.delay.send_message(:to => user.email, :subject => options[:subject], :headline => options[:headline], :groups => groups) |
|
| 43 | 45 |
self.memory[:queue] = [] |
| 44 | 46 |
end |
| 45 | 47 |
end |
| 46 | 48 |
|
| 47 |
- def present(item) |
|
| 48 |
- if item.is_a?(Hash) |
|
| 49 |
+ def present(payload) |
|
| 50 |
+ if payload.is_a?(Hash) |
|
| 51 |
+ payload = ActiveSupport::HashWithIndifferentAccess.new(payload) |
|
| 49 | 52 |
MAIN_KEYS.each do |key| |
| 50 |
- if item.has_key?(key) |
|
| 51 |
- return "#{item[key]}" + ((item.length > 1 && item.length < 5) ? " (#{present_hash item, key})" : "")
|
|
| 52 |
- elsif item.has_key?(key.to_s) |
|
| 53 |
- return "#{item[key.to_s]}" + ((item.length > 1 && item.length < 5) ? " (#{present_hash item, key.to_s})" : "")
|
|
| 54 |
- end |
|
| 53 |
+ return { :title => payload[key].to_s, :entries => present_hash(payload, key) } if payload.has_key?(key)
|
|
| 55 | 54 |
end |
| 56 |
- present_hash item |
|
| 55 |
+ |
|
| 56 |
+ { :title => "Event", :entries => present_hash(payload) }
|
|
| 57 | 57 |
else |
| 58 |
- item.to_s |
|
| 58 |
+ { :title => payload.to_s, :entries => [] }
|
|
| 59 | 59 |
end |
| 60 | 60 |
end |
| 61 | 61 |
|
| 62 | 62 |
def present_hash(hash, skip_key = nil) |
| 63 |
- hash.to_a.sort_by {|a| a.first.to_s }.map { |k, v| "#{k}: #{v}" unless [skip_key].include?(k) }.compact.to_sentence
|
|
| 63 |
+ hash.to_a.sort_by {|a| a.first.to_s }.map { |k, v| "#{k}: #{v}" unless k.to_s == skip_key.to_s }.compact
|
|
| 64 | 64 |
end |
| 65 | 65 |
end |
| 66 |
-end |
|
| 66 |
+end |
@@ -0,0 +1,32 @@ |
||
| 1 |
+module Agents |
|
| 2 |
+ class ManualEventAgent < Agent |
|
| 3 |
+ cannot_be_scheduled! |
|
| 4 |
+ cannot_receive_events! |
|
| 5 |
+ |
|
| 6 |
+ description <<-MD |
|
| 7 |
+ Use this Agent to manually create Events for testing or other purposes. |
|
| 8 |
+ MD |
|
| 9 |
+ |
|
| 10 |
+ event_description "User determined" |
|
| 11 |
+ |
|
| 12 |
+ def default_options |
|
| 13 |
+ { "no options" => "are needed" }
|
|
| 14 |
+ end |
|
| 15 |
+ |
|
| 16 |
+ def handle_details_post(params) |
|
| 17 |
+ if params[:payload] |
|
| 18 |
+ create_event(:payload => params[:payload]) |
|
| 19 |
+ { :success => true }
|
|
| 20 |
+ else |
|
| 21 |
+ { :success => false, :error => "You must provide a JSON payload" }
|
|
| 22 |
+ end |
|
| 23 |
+ end |
|
| 24 |
+ |
|
| 25 |
+ def working? |
|
| 26 |
+ true |
|
| 27 |
+ end |
|
| 28 |
+ |
|
| 29 |
+ def validate_options |
|
| 30 |
+ end |
|
| 31 |
+ end |
|
| 32 |
+end |
@@ -1,6 +1,7 @@ |
||
| 1 | 1 |
module Agents |
| 2 | 2 |
class PostAgent < Agent |
| 3 | 3 |
cannot_be_scheduled! |
| 4 |
+ cannot_create_events! |
|
| 4 | 5 |
|
| 5 | 6 |
description <<-MD |
| 6 | 7 |
Post Agent receives events from other agents and send those events as the contents of a post request to a specified url. `post_url` field must specify where you would like to receive post requests and do not forget to include URI scheme (`http` or `https`) |
@@ -4,6 +4,7 @@ require 'securerandom' |
||
| 4 | 4 |
module Agents |
| 5 | 5 |
class TwilioAgent < Agent |
| 6 | 6 |
cannot_be_scheduled! |
| 7 |
+ cannot_create_events! |
|
| 7 | 8 |
|
| 8 | 9 |
description <<-MD |
| 9 | 10 |
The TwilioAgent receives and collects events and sends them via text message or gives you a call when scheduled. |
@@ -47,8 +47,9 @@ |
||
| 47 | 47 |
<div class="control-group"> |
| 48 | 48 |
<%= f.label :sources, :class => 'control-label' %> |
| 49 | 49 |
<div class="controls link-region" data-can-receive-events="<%= @agent.can_receive_events? %>"> |
| 50 |
+ <% eventSources = (current_user.agents - [@agent]).find_all { |a| a.can_create_events? } %>
|
|
| 50 | 51 |
<%= f.select(:source_ids, |
| 51 |
- options_for_select((current_user.agents - [@agent]).map {|s| [s.name, s.id] },
|
|
| 52 |
+ options_for_select(eventSources.map {|s| [s.name, s.id] },
|
|
| 52 | 53 |
@agent.source_ids), |
| 53 | 54 |
{}, { :multiple => true, :size => 5, :class => 'span4 select2' }) %>
|
| 54 | 55 |
<span class='cannot-receive-events text-info'>This type of Agent cannot receive events.</span> |
@@ -0,0 +1,42 @@ |
||
| 1 |
+<h3>Manually Create Events</h3> |
|
| 2 |
+ |
|
| 3 |
+<h4 id='event-creation-status'></h4> |
|
| 4 |
+ |
|
| 5 |
+<%= form_tag handle_details_post_agent_path(@agent), :id => "create-event-form" do %> |
|
| 6 |
+ <div class="control-group"> |
|
| 7 |
+ <textarea rows="10" id="payload" name="payload" class="span8 payload-editor" data-height="200"> |
|
| 8 |
+ {}
|
|
| 9 |
+ </textarea> |
|
| 10 |
+ </div> |
|
| 11 |
+ |
|
| 12 |
+ <div class='form-actions' style='clear: both'> |
|
| 13 |
+ <%= submit_tag "Submit", :class => "btn btn-primary" %> |
|
| 14 |
+ </div> |
|
| 15 |
+<% end %> |
|
| 16 |
+ |
|
| 17 |
+<script> |
|
| 18 |
+ $(function () {
|
|
| 19 |
+ var payloadJsonEditor = window.setupJsonEditor($(".payload-editor"));
|
|
| 20 |
+ $("#create-event-form").submit(function (e) {
|
|
| 21 |
+ e.preventDefault(); |
|
| 22 |
+ var $form = $("#create-event-form");
|
|
| 23 |
+ var $status = $("#event-creation-status");
|
|
| 24 |
+ $.ajax({
|
|
| 25 |
+ url: $form.attr('action'),
|
|
| 26 |
+ method: "post", |
|
| 27 |
+ data: { payload: JSON.parse($form.find("textarea").val()) },
|
|
| 28 |
+ dataType: "JSON", |
|
| 29 |
+ success: function(json) {
|
|
| 30 |
+ if (json.success) {
|
|
| 31 |
+ $status.text("Success!");
|
|
| 32 |
+ } else {
|
|
| 33 |
+ $status.text("An error occurred: " + json.error);
|
|
| 34 |
+ } |
|
| 35 |
+ }, |
|
| 36 |
+ error: function(response) {
|
|
| 37 |
+ $status.text("An error occurred: " + response.responseText)
|
|
| 38 |
+ } |
|
| 39 |
+ }); |
|
| 40 |
+ }); |
|
| 41 |
+ }); |
|
| 42 |
+</script> |
@@ -4,7 +4,7 @@ |
||
| 4 | 4 |
<div class="page-header"> |
| 5 | 5 |
<h2> |
| 6 | 6 |
Editing your <%= @agent.short_type %> |
| 7 |
- <%= image_tag "spinner-arrows.gif", :class => "spinner" %> |
|
| 7 |
+ <%= image_tag "spinner-arrows.gif", :class => "spinner", :id => 'agent-spinner' %> |
|
| 8 | 8 |
</h2> |
| 9 | 9 |
</div> |
| 10 | 10 |
|
@@ -8,53 +8,71 @@ |
||
| 8 | 8 |
<table class='table table-striped'> |
| 9 | 9 |
<tr> |
| 10 | 10 |
<th>Name</th> |
| 11 |
+ <th>Schedule</th> |
|
| 11 | 12 |
<th>Last Check</th> |
| 12 | 13 |
<th>Last Event Out</th> |
| 13 | 14 |
<th>Last Event In</th> |
| 14 |
- <th>Events</th> |
|
| 15 |
- <th>Schedule</th> |
|
| 15 |
+ <th>Events Created</th> |
|
| 16 | 16 |
<th>Working?</th> |
| 17 | 17 |
<th></th> |
| 18 | 18 |
</tr> |
| 19 | 19 |
|
| 20 | 20 |
<% @agents.each do |agent| %> |
| 21 |
- <tr> |
|
| 22 |
- <td> |
|
| 23 |
- <%= agent.name %> |
|
| 24 |
- <br/> |
|
| 25 |
- <span class='muted'><%= agent.short_type.titleize %></span> |
|
| 26 |
- </td> |
|
| 27 |
- <td> |
|
| 28 |
- <% if agent.cannot_be_scheduled? %> |
|
| 29 |
- N/A |
|
| 30 |
- <% else %> |
|
| 31 |
- <%= agent.last_check_at ? time_ago_in_words(agent.last_check_at) + " ago" : "never" %> |
|
| 32 |
- <% end %> |
|
| 33 |
- </td> |
|
| 34 |
- <td><%= agent.last_event_at ? time_ago_in_words(agent.last_event_at) + " ago" : "never" %></td> |
|
| 35 |
- <td> |
|
| 36 |
- <% if agent.cannot_receive_events? %> |
|
| 37 |
- N/A |
|
| 21 |
+ <tr> |
|
| 22 |
+ <td> |
|
| 23 |
+ <%= agent.name %> |
|
| 24 |
+ <br/> |
|
| 25 |
+ <span class='muted'><%= agent.short_type.titleize %></span> |
|
| 26 |
+ </td> |
|
| 27 |
+ <td> |
|
| 28 |
+ <% if agent.can_be_scheduled? %> |
|
| 29 |
+ <%= agent.schedule.to_s.humanize.titleize %> |
|
| 30 |
+ <% else %> |
|
| 31 |
+ <span class='not-applicable'></span> |
|
| 32 |
+ <% end %> |
|
| 33 |
+ </td> |
|
| 34 |
+ <td> |
|
| 35 |
+ <% if agent.can_be_scheduled? %> |
|
| 36 |
+ <%= agent.last_check_at ? time_ago_in_words(agent.last_check_at) + " ago" : "never" %> |
|
| 37 |
+ <% else %> |
|
| 38 |
+ <span class='not-applicable'></span> |
|
| 39 |
+ <% end %> |
|
| 40 |
+ </td> |
|
| 41 |
+ <td> |
|
| 42 |
+ <% if agent.can_create_events? %> |
|
| 43 |
+ <%= agent.last_event_at ? time_ago_in_words(agent.last_event_at) + " ago" : "never" %> |
|
| 44 |
+ <% else %> |
|
| 45 |
+ <span class='not-applicable'></span> |
|
| 46 |
+ <% end %> |
|
| 47 |
+ </td> |
|
| 48 |
+ <td> |
|
| 49 |
+ <% if agent.can_receive_events? %> |
|
| 50 |
+ <%= agent.last_receive_at ? time_ago_in_words(agent.last_receive_at) + " ago" : "never" %> |
|
| 51 |
+ <% else %> |
|
| 52 |
+ <span class='not-applicable'></span> |
|
| 53 |
+ <% end %> |
|
| 54 |
+ </td> |
|
| 55 |
+ <td> |
|
| 56 |
+ <% if agent.can_create_events? %> |
|
| 57 |
+ <%= link_to(agent.events_count || 0, events_path(:agent => agent.to_param)) %> |
|
| 58 |
+ <% else %> |
|
| 59 |
+ <span class='not-applicable'></span> |
|
| 60 |
+ <% end %> |
|
| 61 |
+ </td> |
|
| 62 |
+ <td><%= working(agent) %></td> |
|
| 63 |
+ <td> |
|
| 64 |
+ <div class="btn-group"> |
|
| 65 |
+ <%= link_to 'Show', agent_path(agent), class: "btn btn-mini" %> |
|
| 66 |
+ <%= link_to 'Edit', edit_agent_path(agent), class: "btn btn-mini" %> |
|
| 67 |
+ <%= link_to 'Delete', agent_path(agent), method: :delete, data: { confirm: 'Are you sure?' }, class: "btn btn-mini" %>
|
|
| 68 |
+ <% if agent.can_be_scheduled? %> |
|
| 69 |
+ <%= link_to 'Run', run_agent_path(agent, :return => "index"), method: :post, class: "btn btn-mini" %> |
|
| 38 | 70 |
<% else %> |
| 39 |
- <%= agent.last_receive_at ? time_ago_in_words(agent.last_receive_at) + " ago" : "never" %> |
|
| 71 |
+ <%= link_to 'Run', "#", class: "btn btn-mini disabled" %> |
|
| 40 | 72 |
<% end %> |
| 41 |
- </td> |
|
| 42 |
- <td><%= link_to(agent.events_count || 0, events_path(:agent => agent.to_param)) %></td> |
|
| 43 |
- <td><%= (agent.schedule || "n/a").to_s.humanize.titleize %></td> |
|
| 44 |
- <td><%= working(agent) %></td> |
|
| 45 |
- <td> |
|
| 46 |
- <div class="btn-group"> |
|
| 47 |
- <%= link_to 'Show', agent_path(agent), class: "btn btn-mini" %> |
|
| 48 |
- <%= link_to 'Edit', edit_agent_path(agent), class: "btn btn-mini" %> |
|
| 49 |
- <%= link_to 'Delete', agent_path(agent), method: :delete, data: {confirm: 'Are you sure?'}, class: "btn btn-mini" %>
|
|
| 50 |
- <% if agent.can_be_scheduled? %> |
|
| 51 |
- <%= link_to 'Run', run_agent_path(agent, :return => "index"), method: :post, class: "btn btn-mini" %> |
|
| 52 |
- <% else %> |
|
| 53 |
- <%= link_to 'Run', "#", class: "btn btn-mini disabled" %> |
|
| 54 |
- <% end %> |
|
| 55 |
- </div> |
|
| 56 |
- </td> |
|
| 57 |
- </tr> |
|
| 73 |
+ </div> |
|
| 74 |
+ </td> |
|
| 75 |
+ </tr> |
|
| 58 | 76 |
<% end %> |
| 59 | 77 |
</table> |
| 60 | 78 |
|
@@ -4,7 +4,7 @@ |
||
| 4 | 4 |
<div class="page-header"> |
| 5 | 5 |
<h2> |
| 6 | 6 |
Create a new Agent |
| 7 |
- <%= image_tag "spinner-arrows.gif", :class => "spinner" %> |
|
| 7 |
+ <%= image_tag "spinner-arrows.gif", :class => "spinner", :id => 'agent-spinner' %> |
|
| 8 | 8 |
</h2> |
| 9 | 9 |
</div> |
| 10 | 10 |
|
@@ -13,7 +13,7 @@ |
||
| 13 | 13 |
<% end %> |
| 14 | 14 |
<li><a href="#logs" data-toggle="tab" data-agent-id="<%= @agent.id %>"><i class='icon-list-alt'></i> Logs</a></li> |
| 15 | 15 |
|
| 16 |
- <% if @agent.events.count > 0 %> |
|
| 16 |
+ <% if @agent.can_create_events? && @agent.events.count > 0 %> |
|
| 17 | 17 |
<li><%= link_to '<i class="icon-random"></i> Events'.html_safe, events_path(:agent => @agent.to_param) %></li> |
| 18 | 18 |
<% end %> |
| 19 | 19 |
<li><%= link_to '<i class="icon-chevron-left"></i> Back'.html_safe, agents_path %></li> |
@@ -29,7 +29,7 @@ |
||
| 29 | 29 |
</li> |
| 30 | 30 |
<% end %> |
| 31 | 31 |
|
| 32 |
- <% if @agent.events.count > 0 %> |
|
| 32 |
+ <% if @agent.can_create_events? && @agent.events.count > 0 %> |
|
| 33 | 33 |
<li> |
| 34 | 34 |
<%= link_to '<i class="icon-trash"></i> Delete all events'.html_safe, remove_events_agent_path(@agent), method: :delete, data: {confirm: 'Are you sure you want to delete ALL events for this Agent?'}, :tabindex => "-1" %>
|
| 35 | 35 |
</li> |
@@ -70,38 +70,60 @@ |
||
| 70 | 70 |
<%= @agent.short_type.titleize %> |
| 71 | 71 |
</p> |
| 72 | 72 |
|
| 73 |
- <p> |
|
| 74 |
- <b>Schedule:</b> |
|
| 75 |
- <%= (@agent.schedule || "n/a").humanize.titleize %> |
|
| 76 |
- </p> |
|
| 73 |
+ <% if @agent.can_be_scheduled? %> |
|
| 74 |
+ <p> |
|
| 75 |
+ <b>Schedule:</b> |
|
| 76 |
+ <%= (@agent.schedule || "n/a").humanize.titleize %> |
|
| 77 |
+ </p> |
|
| 77 | 78 |
|
| 78 |
- <p> |
|
| 79 |
- <b>Last checked:</b> |
|
| 80 |
- <% if @agent.cannot_be_scheduled? %> |
|
| 81 |
- N/A |
|
| 82 |
- <% else %> |
|
| 79 |
+ <p> |
|
| 80 |
+ <b>Last checked:</b> |
|
| 83 | 81 |
<%= @agent.last_check_at ? time_ago_in_words(@agent.last_check_at) + " ago" : "never" %> |
| 84 |
- <% end %> |
|
| 85 |
- </p> |
|
| 82 |
+ </p> |
|
| 83 |
+ <% end %> |
|
| 86 | 84 |
|
| 87 |
- <p> |
|
| 88 |
- <b>Last event created:</b> |
|
| 89 |
- <%= @agent.last_event_at ? time_ago_in_words(@agent.last_event_at) + " ago" : "never" %> |
|
| 90 |
- </p> |
|
| 85 |
+ <% if @agent.can_create_events? %> |
|
| 86 |
+ <p> |
|
| 87 |
+ <b>Last event created:</b> |
|
| 88 |
+ <%= @agent.last_event_at ? time_ago_in_words(@agent.last_event_at) + " ago" : "never" %> |
|
| 89 |
+ </p> |
|
| 90 |
+ <% end %> |
|
| 91 | 91 |
|
| 92 |
- <p> |
|
| 93 |
- <b>Last received event:</b> |
|
| 94 |
- <% if @agent.cannot_receive_events? %> |
|
| 95 |
- N/A |
|
| 96 |
- <% else %> |
|
| 92 |
+ <% if @agent.can_receive_events? %> |
|
| 93 |
+ <p> |
|
| 94 |
+ <b>Last received event:</b> |
|
| 97 | 95 |
<%= @agent.last_receive_at ? time_ago_in_words(@agent.last_receive_at) + " ago" : "never" %> |
| 98 |
- <% end %> |
|
| 99 |
- </p> |
|
| 96 |
+ </p> |
|
| 97 |
+ <% end %> |
|
| 100 | 98 |
|
| 101 |
- <p> |
|
| 102 |
- <b>Event count:</b> |
|
| 103 |
- <%= link_to @agent.events.count, events_path(:agent => @agent.to_param) %> |
|
| 104 |
- </p> |
|
| 99 |
+ <% if @agent.can_create_events? %> |
|
| 100 |
+ <p> |
|
| 101 |
+ <b>Events created:</b> |
|
| 102 |
+ <%= link_to @agent.events.count, events_path(:agent => @agent.to_param) %> |
|
| 103 |
+ </p> |
|
| 104 |
+ <% end %> |
|
| 105 |
+ |
|
| 106 |
+ <% if @agent.can_receive_events? %> |
|
| 107 |
+ <p> |
|
| 108 |
+ <b>Event sources:</b> |
|
| 109 |
+ <% if @agent.sources.length %> |
|
| 110 |
+ <%= @agent.sources.map { |source_agent| link_to(source_agent.name, agent_path(source_agent)) }.to_sentence.html_safe %>
|
|
| 111 |
+ <% else %> |
|
| 112 |
+ None |
|
| 113 |
+ <% end %> |
|
| 114 |
+ </p> |
|
| 115 |
+ <% end %> |
|
| 116 |
+ |
|
| 117 |
+ <% if @agent.can_create_events? %> |
|
| 118 |
+ <p> |
|
| 119 |
+ <b>Event receivers:</b> |
|
| 120 |
+ <% if @agent.receivers.length %> |
|
| 121 |
+ <%= @agent.receivers.map { |receiver_agent| link_to(receiver_agent.name, agent_path(receiver_agent)) }.to_sentence.html_safe %>
|
|
| 122 |
+ <% else %> |
|
| 123 |
+ None |
|
| 124 |
+ <% end %> |
|
| 125 |
+ </p> |
|
| 126 |
+ <% end %> |
|
| 105 | 127 |
|
| 106 | 128 |
<p> |
| 107 | 129 |
<b>Working:</b> |
@@ -110,12 +132,12 @@ |
||
| 110 | 132 |
|
| 111 | 133 |
<p> |
| 112 | 134 |
<b>Options:</b> |
| 113 |
- <pre><%= JSON.pretty_generate @agent.options %></pre> |
|
| 135 |
+ <pre><%= JSON.pretty_generate @agent.options || {} %></pre>
|
|
| 114 | 136 |
</p> |
| 115 | 137 |
|
| 116 | 138 |
<p> |
| 117 | 139 |
<b>Memory:</b> |
| 118 |
- <pre><%= JSON.pretty_generate @agent.memory %></pre> |
|
| 140 |
+ <pre><%= JSON.pretty_generate @agent.memory || {} %></pre>
|
|
| 119 | 141 |
</p> |
| 120 | 142 |
</div> |
| 121 | 143 |
</div> |
@@ -16,6 +16,7 @@ |
||
| 16 | 16 |
</tr> |
| 17 | 17 |
|
| 18 | 18 |
<% @events.each do |event| %> |
| 19 |
+ <% next unless event.agent %> |
|
| 19 | 20 |
<tr> |
| 20 | 21 |
<td><%= link_to event.agent.name, agent_path(event.agent) %></td> |
| 21 | 22 |
<td><%= time_ago_in_words event.created_at %> ago</td> |
@@ -7,7 +7,7 @@ |
||
| 7 | 7 |
|
| 8 | 8 |
<p> |
| 9 | 9 |
<b>Payload:</b> |
| 10 |
- <pre><%= JSON.pretty_generate @event.payload %></pre> |
|
| 10 |
+ <pre><%= JSON.pretty_generate @event.payload || {} %></pre>
|
|
| 11 | 11 |
</p> |
| 12 | 12 |
|
| 13 | 13 |
<% if @event.lat && @event.lng %> |
@@ -9,11 +9,18 @@ |
||
| 9 | 9 |
|
| 10 | 10 |
<ul class="nav pull-right"> |
| 11 | 11 |
<% if user_signed_in? %> |
| 12 |
+ |
|
| 13 |
+ <form class="navbar-search pull-left"> |
|
| 14 |
+ <input type="text" class="search-query" id='agent-navigate' autocomplete="off" placeholder="Search"> |
|
| 15 |
+ <%= image_tag "spinner-arrows.gif", :class => "spinner" %> |
|
| 16 |
+ </form> |
|
| 17 |
+ |
|
| 12 | 18 |
<li id='job-indicator'> |
| 13 | 19 |
<a href="/delayed_job"> |
| 14 | 20 |
<span class="badge"><i class="icon-refresh icon-white"></i> <span class='number'>0</span></span> |
| 15 | 21 |
</a> |
| 16 | 22 |
</li> |
| 23 |
+ |
|
| 17 | 24 |
<li id='event-indicator'> |
| 18 | 25 |
<a href="#"> |
| 19 | 26 |
<span class="badge"><i class="icon-random icon-white"></i> <span class='number'>0</span> new events</span> |
@@ -36,6 +43,10 @@ |
||
| 36 | 43 |
</li> |
| 37 | 44 |
|
| 38 | 45 |
<li> |
| 46 |
+ <%= link_to 'About', 'https://github.com/cantino/huginn', :tabindex => "-1" %> |
|
| 47 |
+ </li> |
|
| 48 |
+ |
|
| 49 |
+ <li> |
|
| 39 | 50 |
<% if user_signed_in? %> |
| 40 | 51 |
<%= link_to 'Logout', destroy_user_session_path, :method => :delete, :tabindex => "-1" %> |
| 41 | 52 |
<% else %> |
@@ -45,3 +56,4 @@ |
||
| 45 | 56 |
</ul> |
| 46 | 57 |
</li> |
| 47 | 58 |
</ul> |
| 59 |
+ |
@@ -32,5 +32,21 @@ |
||
| 32 | 32 |
</div> |
| 33 | 33 |
</div> |
| 34 | 34 |
</div> |
| 35 |
+ |
|
| 36 |
+ <script> |
|
| 37 |
+ var agentPaths = {};
|
|
| 38 |
+ <% if current_user -%> |
|
| 39 |
+ var myAgents = <%= Utils.jsonify(current_user.agents.inject({}) {|m, a| m[a.name] = agent_path(a) unless a.new_record?; m }) %>;
|
|
| 40 |
+ $.extend(agentPaths, myAgents); |
|
| 41 |
+ <% end -%> |
|
| 42 |
+ agentPaths["All Agents Index"] = <%= Utils.jsonify agents_path %>; |
|
| 43 |
+ agentPaths["New Agent"] = <%= Utils.jsonify new_agent_path %>; |
|
| 44 |
+ agentPaths["Account"] = <%= Utils.jsonify edit_user_registration_path %>; |
|
| 45 |
+ agentPaths["Events Index"] = <%= Utils.jsonify events_path %>; |
|
| 46 |
+ agentPaths["View Agent Diagram"] = <%= Utils.jsonify diagram_agents_path %>; |
|
| 47 |
+ agentPaths["Run Event Propagation"] = { url: <%= Utils.jsonify propagate_agents_path %>, method: 'POST' };
|
|
| 48 |
+ var agentNames = []; |
|
| 49 |
+ $.each(agentPaths, function(name, v) { agentNames.push(name); });
|
|
| 50 |
+ </script> |
|
| 35 | 51 |
</body> |
| 36 |
-</html> |
|
| 52 |
+</html> |
@@ -7,10 +7,15 @@ |
||
| 7 | 7 |
<% if @headline %> |
| 8 | 8 |
<h1><%= @headline %></h1> |
| 9 | 9 |
<% end %> |
| 10 |
- <% @lines.each do |line| %> |
|
| 11 |
- <p> |
|
| 12 |
- <%= line %> |
|
| 13 |
- </p> |
|
| 10 |
+ <% @groups.each do |group| %> |
|
| 11 |
+ <div style='margin-bottom: 10px;'> |
|
| 12 |
+ <div><%= group[:title] %></div> |
|
| 13 |
+ <% group[:entries].each do |entry| %> |
|
| 14 |
+ <div style='margin-left: 10px;'> |
|
| 15 |
+ <%= entry %> |
|
| 16 |
+ </div> |
|
| 17 |
+ <% end %> |
|
| 18 |
+ </div> |
|
| 14 | 19 |
<% end %> |
| 15 | 20 |
</body> |
| 16 | 21 |
</html> |
@@ -1,5 +1,7 @@ |
||
| 1 | 1 |
<% if @headline %><%= @headline %> |
| 2 | 2 |
|
| 3 |
-<% end %><% @lines.each do |line| %><%= line %> |
|
| 4 |
- |
|
| 3 |
+<% end %><% @groups.each do |group| %><%= group[:title] %> |
|
| 4 |
+<% group[:entries].each do |entry| %> <%= entry %> |
|
| 5 | 5 |
<% end %> |
| 6 |
+ |
|
| 7 |
+<% end %> |
@@ -2,6 +2,7 @@ Huginn::Application.routes.draw do |
||
| 2 | 2 |
resources :agents do |
| 3 | 3 |
member do |
| 4 | 4 |
post :run |
| 5 |
+ post :handle_details_post |
|
| 5 | 6 |
delete :remove_events |
| 6 | 7 |
end |
| 7 | 8 |
|
@@ -70,4 +70,8 @@ module Utils |
||
| 70 | 70 |
result |
| 71 | 71 |
end |
| 72 | 72 |
end |
| 73 |
+ |
|
| 74 |
+ def self.jsonify(thing) |
|
| 75 |
+ thing.to_json.gsub('</', '<\/').html_safe
|
|
| 76 |
+ end |
|
| 73 | 77 |
end |
@@ -18,6 +18,22 @@ describe AgentsController do |
||
| 18 | 18 |
end |
| 19 | 19 |
end |
| 20 | 20 |
|
| 21 |
+ describe "POST handle_details_post" do |
|
| 22 |
+ it "passes control to handle_details_post on the agent" do |
|
| 23 |
+ sign_in users(:bob) |
|
| 24 |
+ post :handle_details_post, :id => agents(:bob_manual_event_agent).to_param, :payload => { :foo => "bar" }
|
|
| 25 |
+ JSON.parse(response.body).should == { "success" => true }
|
|
| 26 |
+ agents(:bob_manual_event_agent).events.last.payload.should == { :foo => "bar" }
|
|
| 27 |
+ end |
|
| 28 |
+ |
|
| 29 |
+ it "can only be accessed by the Agent's owner" do |
|
| 30 |
+ sign_in users(:jane) |
|
| 31 |
+ lambda {
|
|
| 32 |
+ post :handle_details_post, :id => agents(:bob_manual_event_agent).to_param, :payload => { :foo => :bar }
|
|
| 33 |
+ }.should raise_error(ActiveRecord::RecordNotFound) |
|
| 34 |
+ end |
|
| 35 |
+ end |
|
| 36 |
+ |
|
| 21 | 37 |
describe "GET show" do |
| 22 | 38 |
it "only shows Agents for the current user" do |
| 23 | 39 |
sign_in users(:bob) |
@@ -92,3 +92,8 @@ bob_twitter_user_agent: |
||
| 92 | 92 |
:oauth_token => "---", |
| 93 | 93 |
:oauth_token_secret => "---" |
| 94 | 94 |
}.to_yaml.inspect %> |
| 95 |
+ |
|
| 96 |
+bob_manual_event_agent: |
|
| 97 |
+ type: Agents::ManualEventAgent |
|
| 98 |
+ user: bob |
|
| 99 |
+ name: "Bob's event testing agent" |
@@ -114,13 +114,23 @@ describe Agent do |
||
| 114 | 114 |
end |
| 115 | 115 |
|
| 116 | 116 |
describe "#create_event" do |
| 117 |
+ before do |
|
| 118 |
+ @checker = Agents::SomethingSource.new(:name => "something") |
|
| 119 |
+ @checker.user = users(:bob) |
|
| 120 |
+ @checker.save! |
|
| 121 |
+ end |
|
| 122 |
+ |
|
| 117 | 123 |
it "should use the checker's user" do |
| 118 |
- checker = Agents::SomethingSource.new(:name => "something") |
|
| 119 |
- checker.user = users(:bob) |
|
| 120 |
- checker.save! |
|
| 124 |
+ @checker.check |
|
| 125 |
+ Event.last.user.should == @checker.user |
|
| 126 |
+ end |
|
| 121 | 127 |
|
| 122 |
- checker.check |
|
| 123 |
- Event.last.user.should == checker.user |
|
| 128 |
+ it "should log an error if the Agent has been marked with 'cannot_create_events!'" do |
|
| 129 |
+ mock(@checker).can_create_events? { false }
|
|
| 130 |
+ lambda {
|
|
| 131 |
+ @checker.check |
|
| 132 |
+ }.should_not change { Event.count }
|
|
| 133 |
+ @checker.logs.first.message.should =~ /cannot create events/i |
|
| 124 | 134 |
end |
| 125 | 135 |
end |
| 126 | 136 |
|
@@ -11,6 +11,10 @@ describe Agents::DigestEmailAgent do |
||
| 11 | 11 |
@checker.save! |
| 12 | 12 |
end |
| 13 | 13 |
|
| 14 |
+ after do |
|
| 15 |
+ ActionMailer::Base.deliveries = [] |
|
| 16 |
+ end |
|
| 17 |
+ |
|
| 14 | 18 |
describe "#receive" do |
| 15 | 19 |
it "queues any payloads it receives" do |
| 16 | 20 |
event1 = Event.new |
@@ -33,14 +37,38 @@ describe Agents::DigestEmailAgent do |
||
| 33 | 37 |
Agents::DigestEmailAgent.async_check(@checker.id) |
| 34 | 38 |
ActionMailer::Base.deliveries.should == [] |
| 35 | 39 |
|
| 36 |
- @checker.memory[:queue] = ["Something you should know about", { :title => "Foo", :url => "http://google.com", :bar => 2 }, { "message" => "hi", :woah => "there" }]
|
|
| 40 |
+ @checker.memory[:queue] = ["Something you should know about", |
|
| 41 |
+ { :title => "Foo", :url => "http://google.com", :bar => 2 },
|
|
| 42 |
+ { "message" => "hi", :woah => "there" },
|
|
| 43 |
+ { "test" => 2 }]
|
|
| 37 | 44 |
@checker.save! |
| 38 | 45 |
|
| 39 | 46 |
Agents::DigestEmailAgent.async_check(@checker.id) |
| 40 | 47 |
ActionMailer::Base.deliveries.last.to.should == ["bob@example.com"] |
| 41 | 48 |
ActionMailer::Base.deliveries.last.subject.should == "something interesting" |
| 42 |
- get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip.should == "Something you should know about\n\nFoo (bar: 2 and url: http://google.com)\n\nhi (woah: there)" |
|
| 43 |
- @checker.reload.memory[:queue].should == [] |
|
| 49 |
+ get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip.should == "Something you should know about\n\nFoo\n bar: 2\n url: http://google.com\n\nhi\n woah: there\n\nEvent\n test: 2" |
|
| 50 |
+ @checker.reload.memory[:queue].should be_empty |
|
| 51 |
+ end |
|
| 52 |
+ |
|
| 53 |
+ it "can receive complex events and send them on" do |
|
| 54 |
+ stub_request(:any, /wunderground/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/weather.json")), :status => 200)
|
|
| 55 |
+ stub.any_instance_of(Agents::WeatherAgent).is_tomorrow?(anything) { true }
|
|
| 56 |
+ @checker.sources << agents(:bob_weather_agent) |
|
| 57 |
+ |
|
| 58 |
+ Agent.async_check(agents(:bob_weather_agent).id) |
|
| 59 |
+ |
|
| 60 |
+ Agent.receive! |
|
| 61 |
+ @checker.reload.memory[:queue].should_not be_empty |
|
| 62 |
+ |
|
| 63 |
+ Agents::DigestEmailAgent.async_check(@checker.id) |
|
| 64 |
+ |
|
| 65 |
+ plain_email_text = get_message_part(ActionMailer::Base.deliveries.last, /plain/).strip |
|
| 66 |
+ html_email_text = get_message_part(ActionMailer::Base.deliveries.last, /html/).strip |
|
| 67 |
+ |
|
| 68 |
+ plain_email_text.should =~ /avehumidity/ |
|
| 69 |
+ html_email_text.should =~ /avehumidity/ |
|
| 70 |
+ |
|
| 71 |
+ @checker.reload.memory[:queue].should be_empty |
|
| 44 | 72 |
end |
| 45 | 73 |
end |
| 46 | 74 |
end |
@@ -69,10 +69,6 @@ describe Agents::PeakDetectorAgent do |
||
| 69 | 69 |
:pattern => { :filter => "something" })
|
| 70 | 70 |
@agent.memory[:peaks][:something].length.should == 2 |
| 71 | 71 |
end |
| 72 |
- |
|
| 73 |
- it "works on real world data" do |
|
| 74 |
- pending "need examples" |
|
| 75 |
- end |
|
| 76 | 72 |
end |
| 77 | 73 |
|
| 78 | 74 |
describe "validation" do |
@@ -95,4 +91,4 @@ describe Agents::PeakDetectorAgent do |
||
| 95 | 91 |
@agent.should_not be_valid |
| 96 | 92 |
end |
| 97 | 93 |
end |
| 98 |
-end |
|
| 94 |
+end |
@@ -52,8 +52,12 @@ |
||
| 52 | 52 |
display: block; |
| 53 | 53 |
float: right; |
| 54 | 54 |
text-decoration: none; |
| 55 |
- padding-left: 5px; |
|
| 55 |
+ padding: 0 5px; |
|
| 56 | 56 |
border: 0 !important; |
| 57 | 57 |
color: blue; |
| 58 |
+ |
|
| 59 |
+ &:hover {
|
|
| 60 |
+ background-color: #bbb; |
|
| 61 |
+ } |
|
| 58 | 62 |
} |
| 59 | 63 |
} |