@@ -40,3 +40,6 @@ EMAIL_FROM_ADDRESS=from_address@gmail.com |
||
40 | 40 |
# This invitation code will be required for users to signup with your Huginn installation. |
41 | 41 |
# You can see its use in user.rb. |
42 | 42 |
INVITATION_CODE=try-huginn |
43 |
+ |
|
44 |
+# Number of lines of log messages to keep per Agent |
|
45 |
+AGENT_LOG_LENGTH=100 |
@@ -8,10 +8,6 @@ |
||
8 | 8 |
#= require ./worker-checker |
9 | 9 |
#= require_self |
10 | 10 |
|
11 |
-# Place all the behaviors and hooks related to the matching controller here. |
|
12 |
-# All this logic will automatically be available in application.js. |
|
13 |
-# You can use CoffeeScript in this file: http://jashkenas.github.com/coffee-script/ |
|
14 |
- |
|
15 | 11 |
setupJsonEditor = -> |
16 | 12 |
JSONEditor.prototype.ADD_IMG = '<%= image_path 'json-editor/add.png' %>' |
17 | 13 |
JSONEditor.prototype.DELETE_IMG = '<%= image_path 'json-editor/delete.png' %>' |
@@ -48,12 +44,43 @@ showEventDescriptions = -> |
||
48 | 44 |
$(".event-descriptions").html("").hide() |
49 | 45 |
|
50 | 46 |
$(document).ready -> |
47 |
+ # JSON Editor |
|
51 | 48 |
setupJsonEditor() |
52 |
- $(".select2").select2(width: 'resolve') |
|
53 | 49 |
|
54 |
- if $(".top-flash").length |
|
55 |
- setTimeout((-> $(".top-flash").slideUp(-> $(".top-flash").remove())), 5000) |
|
50 |
+ # Select2 Selects |
|
51 |
+ $(".select2").select2(width: 'resolve') |
|
56 | 52 |
|
53 |
+ # Flash |
|
54 |
+ if $(".flash").length |
|
55 |
+ setTimeout((-> $(".flash").slideUp(-> $(".flash").remove())), 5000) |
|
56 |
+ |
|
57 |
+ # Agent Show |
|
58 |
+ fetchLogs = (e) -> |
|
59 |
+ agentId = $(e.target).closest("[data-agent-id]").data("agent-id") |
|
60 |
+ e.preventDefault() |
|
61 |
+ $("#logs .spinner").show() |
|
62 |
+ $("#logs .refresh, #logs .clear").hide() |
|
63 |
+ $.get "/agents/#{agentId}/logs", (html) => |
|
64 |
+ $("#logs .logs").html html |
|
65 |
+ $("#logs .spinner").stop(true, true).fadeOut -> |
|
66 |
+ $("#logs .refresh, #logs .clear").show() |
|
67 |
+ |
|
68 |
+ clearLogs = (e) -> |
|
69 |
+ if confirm("Are you sure you want to clear all logs for this Agent?") |
|
70 |
+ agentId = $(e.target).closest("[data-agent-id]").data("agent-id") |
|
71 |
+ e.preventDefault() |
|
72 |
+ $("#logs .spinner").show() |
|
73 |
+ $("#logs .refresh, #logs .clear").hide() |
|
74 |
+ $.post "/agents/#{agentId}/logs/clear", { "_method": "DELETE" }, (html) => |
|
75 |
+ $("#logs .logs").html html |
|
76 |
+ $("#logs .spinner").stop(true, true).fadeOut -> |
|
77 |
+ $("#logs .refresh, #logs .clear").show() |
|
78 |
+ |
|
79 |
+ $(".agent-show #show-tabs a[href='#logs']").on "click", fetchLogs |
|
80 |
+ $("#logs .refresh").on "click", fetchLogs |
|
81 |
+ $("#logs .clear").on "click", clearLogs |
|
82 |
+ |
|
83 |
+ # Editing Agents |
|
57 | 84 |
$("#agent_source_ids").on "change", showEventDescriptions |
58 | 85 |
|
59 | 86 |
$("#agent_type").on "change", -> |
@@ -1,7 +1,11 @@ |
||
1 | 1 |
$ -> |
2 |
+ firstEventCount = null |
|
3 |
+ |
|
2 | 4 |
if $("#job-indicator").length |
3 | 5 |
check = -> |
4 | 6 |
$.getJSON "/worker_status", (json) -> |
7 |
+ firstEventCount = json.event_count unless firstEventCount? |
|
8 |
+ |
|
5 | 9 |
if json.pending? && json.pending > 0 |
6 | 10 |
tooltipOptions = { |
7 | 11 |
title: "#{json.pending} pending, #{json.awaiting_retry} awaiting retry, and #{json.recent_failures} recent failures" |
@@ -12,5 +16,20 @@ $ -> |
||
12 | 16 |
$("#job-indicator").tooltip('destroy').tooltip(tooltipOptions).fadeIn().find(".number").text(json.pending) |
13 | 17 |
else |
14 | 18 |
$("#job-indicator:visible").tooltip('destroy').fadeOut() |
19 |
+ |
|
20 |
+ if firstEventCount? && json.event_count > firstEventCount |
|
21 |
+ $("#event-indicator").tooltip('destroy'). |
|
22 |
+ tooltip(title: "Click to reload", delay: 0, placement: "bottom", trigger: "hover"). |
|
23 |
+ fadeIn(). |
|
24 |
+ find(".number"). |
|
25 |
+ text(json.event_count - firstEventCount) |
|
26 |
+ else |
|
27 |
+ $("#event-indicator").tooltip('destroy').fadeOut() |
|
28 |
+ |
|
15 | 29 |
window.workerCheckTimeout = setTimeout check, 2000 |
30 |
+ |
|
16 | 31 |
check() |
32 |
+ |
|
33 |
+ $("#event-indicator a").on "click", (e) -> |
|
34 |
+ e.preventDefault() |
|
35 |
+ window.location.reload() |
@@ -51,7 +51,7 @@ table.events { |
||
51 | 51 |
margin-left: 0 !important; |
52 | 52 |
} |
53 | 53 |
|
54 |
-#job-indicator { |
|
54 |
+#job-indicator, #event-indicator { |
|
55 | 55 |
display: none; |
56 | 56 |
} |
57 | 57 |
|
@@ -85,3 +85,28 @@ img.spinner { |
||
85 | 85 |
.show-view { |
86 | 86 |
overflow: hidden; |
87 | 87 |
} |
88 |
+ |
|
89 |
+// Flash |
|
90 |
+ |
|
91 |
+.flash { |
|
92 |
+ position: fixed; |
|
93 |
+ width: 500px; |
|
94 |
+ z-index: 99999; |
|
95 |
+ top: 1px; |
|
96 |
+ margin-left: 250px; |
|
97 |
+ |
|
98 |
+ .alert { |
|
99 |
+ } |
|
100 |
+} |
|
101 |
+ |
|
102 |
+// Logs |
|
103 |
+ |
|
104 |
+#logs .action-icon { |
|
105 |
+ height: 16px; |
|
106 |
+ display: inline-block; |
|
107 |
+ vertical-align: inherit; |
|
108 |
+ |
|
109 |
+ &.refresh { |
|
110 |
+ margin: 0 10px; |
|
111 |
+ } |
|
112 |
+} |
@@ -9,8 +9,13 @@ class AgentsController < ApplicationController |
||
9 | 9 |
end |
10 | 10 |
|
11 | 11 |
def run |
12 |
- Agent.async_check(current_user.agents.find(params[:id]).id) |
|
13 |
- redirect_to agents_path, notice: "Agent run queued" |
|
12 |
+ agent = current_user.agents.find(params[:id]) |
|
13 |
+ Agent.async_check(agent.id) |
|
14 |
+ if params[:return] == "show" |
|
15 |
+ redirect_to agent_path(agent), notice: "Agent run queued" |
|
16 |
+ else |
|
17 |
+ redirect_to agents_path, notice: "Agent run queued" |
|
18 |
+ end |
|
14 | 19 |
end |
15 | 20 |
|
16 | 21 |
def type_details |
@@ -0,0 +1,19 @@ |
||
1 |
+class LogsController < ApplicationController |
|
2 |
+ before_filter :load_agent |
|
3 |
+ |
|
4 |
+ def index |
|
5 |
+ @logs = @agent.logs.all |
|
6 |
+ render :action => :index, :layout => false |
|
7 |
+ end |
|
8 |
+ |
|
9 |
+ def clear |
|
10 |
+ @agent.logs.delete_all |
|
11 |
+ index |
|
12 |
+ end |
|
13 |
+ |
|
14 |
+ protected |
|
15 |
+ |
|
16 |
+ def load_agent |
|
17 |
+ @agent = current_user.agents.find(params[:agent_id]) |
|
18 |
+ end |
|
19 |
+end |
@@ -1,12 +1,11 @@ |
||
1 | 1 |
class WorkerStatusController < ApplicationController |
2 |
- skip_before_filter :authenticate_user! |
|
3 |
- |
|
4 | 2 |
def show |
5 | 3 |
start = Time.now.to_f |
6 | 4 |
render :json => { |
7 | 5 |
:pending => Delayed::Job.where("run_at <= ? AND locked_at IS NULL AND attempts = 0", Time.now).count, |
8 | 6 |
:awaiting_retry => Delayed::Job.where("failed_at IS NULL AND attempts > 0").count, |
9 | 7 |
:recent_failures => Delayed::Job.where("failed_at IS NOT NULL AND failed_at > ?", 5.days.ago).count, |
8 |
+ :event_count => current_user.events.count, |
|
10 | 9 |
:compute_time => Time.now.to_f - start |
11 | 10 |
} |
12 | 11 |
end |
@@ -0,0 +1,2 @@ |
||
1 |
+module LogsHelper |
|
2 |
+end |
@@ -30,6 +30,7 @@ class Agent < ActiveRecord::Base |
||
30 | 30 |
|
31 | 31 |
belongs_to :user, :inverse_of => :agents |
32 | 32 |
has_many :events, :dependent => :delete_all, :inverse_of => :agent, :order => "events.id desc" |
33 |
+ has_many :logs, :dependent => :delete_all, :inverse_of => :agent, :class_name => "AgentLog", :order => "agent_logs.id desc" |
|
33 | 34 |
has_many :received_events, :through => :sources, :class_name => "Event", :source => :events, :order => "events.id desc" |
34 | 35 |
has_many :links_as_source, :dependent => :delete_all, :foreign_key => "source_id", :class_name => "Link", :inverse_of => :source |
35 | 36 |
has_many :links_as_receiver, :dependent => :delete_all, :foreign_key => "receiver_id", :class_name => "Link", :inverse_of => :receiver |
@@ -139,6 +140,10 @@ class Agent < ActiveRecord::Base |
||
139 | 140 |
end |
140 | 141 |
end |
141 | 142 |
|
143 |
+ def log(message, options = {}) |
|
144 |
+ AgentLog.log_for_agent(self, message, options) |
|
145 |
+ end |
|
146 |
+ |
|
142 | 147 |
# Class Methods |
143 | 148 |
class << self |
144 | 149 |
def cannot_be_scheduled! |
@@ -0,0 +1,23 @@ |
||
1 |
+class AgentLog < ActiveRecord::Base |
|
2 |
+ attr_accessible :agent, :inbound_event, :level, :message, :outbound_event |
|
3 |
+ |
|
4 |
+ belongs_to :agent |
|
5 |
+ belongs_to :inbound_event, :class_name => "Event" |
|
6 |
+ belongs_to :outbound_event, :class_name => "Event" |
|
7 |
+ |
|
8 |
+ validates_presence_of :agent, :message |
|
9 |
+ validates_numericality_of :level, :only_integer => true, :greater_than_or_equal_to => 0, :less_than => 5 |
|
10 |
+ |
|
11 |
+ def self.log_for_agent(agent, message, options = {}) |
|
12 |
+ log = agent.logs.create! options.merge(:message => message) |
|
13 |
+ if agent.logs.count > log_length |
|
14 |
+ oldest_id_to_keep = agent.logs.limit(1).offset(log_length - 1).pluck("agent_logs.id") |
|
15 |
+ agent.logs.where("agent_logs.id < ?", oldest_id_to_keep).delete_all |
|
16 |
+ end |
|
17 |
+ log |
|
18 |
+ end |
|
19 |
+ |
|
20 |
+ def self.log_length |
|
21 |
+ ENV['AGENT_LOG_LENGTH'].present? ? ENV['AGENT_LOG_LENGTH'].to_i : 100 |
|
22 |
+ end |
|
23 |
+end |
@@ -66,29 +66,38 @@ module Agents |
||
66 | 66 |
|
67 | 67 |
def check |
68 | 68 |
hydra = Typhoeus::Hydra.new |
69 |
+ log "Fetching #{options[:url]}" |
|
69 | 70 |
request = Typhoeus::Request.new(options[:url], :followlocation => true) |
70 |
- request.on_complete do |response| |
|
71 |
+ request.on_failure do |response| |
|
72 |
+ log "Failed: #{response.inspect}" |
|
73 |
+ end |
|
74 |
+ request.on_success do |response| |
|
71 | 75 |
doc = parse(response.body) |
72 | 76 |
output = {} |
73 | 77 |
options[:extract].each do |name, extraction_details| |
74 |
- if extraction_type == "json" |
|
75 |
- output[name] = Utils.values_at(doc, extraction_details[:path]) |
|
76 |
- else |
|
77 |
- output[name] = doc.css(extraction_details[:css]).map { |node| |
|
78 |
- if extraction_details[:attr] |
|
79 |
- node.attr(extraction_details[:attr]) |
|
80 |
- elsif extraction_details[:text] |
|
81 |
- node.text() |
|
82 |
- else |
|
83 |
- raise StandardError, ":attr or :text is required on HTML or XML extraction patterns" |
|
84 |
- end |
|
85 |
- } |
|
86 |
- end |
|
78 |
+ result = if extraction_type == "json" |
|
79 |
+ output[name] = Utils.values_at(doc, extraction_details[:path]) |
|
80 |
+ else |
|
81 |
+ output[name] = doc.css(extraction_details[:css]).map { |node| |
|
82 |
+ if extraction_details[:attr] |
|
83 |
+ node.attr(extraction_details[:attr]) |
|
84 |
+ elsif extraction_details[:text] |
|
85 |
+ node.text() |
|
86 |
+ else |
|
87 |
+ log ":attr or :text is required on HTML or XML extraction patterns" |
|
88 |
+ return |
|
89 |
+ end |
|
90 |
+ } |
|
91 |
+ end |
|
92 |
+ log "Extracting #{extraction_type} at #{extraction_details[:path] || extraction_details[:css]}: #{result}" |
|
87 | 93 |
end |
88 | 94 |
|
89 | 95 |
num_unique_lengths = options[:extract].keys.map { |name| output[name].length }.uniq |
90 | 96 |
|
91 |
- raise StandardError, "Got an uneven number of matches for #{options[:name]}: #{options[:extract].inspect}" unless num_unique_lengths.length == 1 |
|
97 |
+ if num_unique_lengths.length != 1 |
|
98 |
+ log "Got an uneven number of matches for #{options[:name]}: #{options[:extract].inspect}", :level => 4 |
|
99 |
+ return |
|
100 |
+ end |
|
92 | 101 |
|
93 | 102 |
previous_payloads = events.order("id desc").limit(UNIQUENESS_LOOK_BACK).pluck(:payload).map(&:to_json) if options[:mode].to_s == "on_change" |
94 | 103 |
num_unique_lengths.first.times do |index| |
@@ -101,7 +110,7 @@ module Agents |
||
101 | 110 |
end |
102 | 111 |
|
103 | 112 |
if !options[:mode] || options[:mode].to_s == "all" || (options[:mode].to_s == "on_change" && !previous_payloads.include?(result.to_json)) |
104 |
- Rails.logger.info "Storing new result for '#{name}': #{result.inspect}" |
|
113 |
+ log "Storing new result for '#{name}': #{result.inspect}" |
|
105 | 114 |
create_event :payload => result |
106 | 115 |
end |
107 | 116 |
end |
@@ -23,6 +23,7 @@ class User < ActiveRecord::Base |
||
23 | 23 |
|
24 | 24 |
has_many :events, :order => "events.created_at desc", :dependent => :delete_all, :inverse_of => :user |
25 | 25 |
has_many :agents, :order => "agents.created_at desc", :dependent => :destroy, :inverse_of => :user |
26 |
+ has_many :logs, :through => :agents, :class_name => "AgentLog" |
|
26 | 27 |
|
27 | 28 |
# Allow users to login via either email or username. |
28 | 29 |
def self.find_first_by_auth_conditions(warden_conditions) |
@@ -48,7 +48,7 @@ |
||
48 | 48 |
<%= link_to 'Edit', edit_agent_path(agent), class: "btn btn-mini" %> |
49 | 49 |
<%= link_to 'Delete', agent_path(agent), method: :delete, data: {confirm: 'Are you sure?'}, class: "btn btn-mini" %> |
50 | 50 |
<% if agent.can_be_scheduled? %> |
51 |
- <%= link_to 'Run', run_agent_path(agent), method: :post, class: "btn btn-mini" %> |
|
51 |
+ <%= link_to 'Run', run_agent_path(agent, :return => "index"), method: :post, class: "btn btn-mini" %> |
|
52 | 52 |
<% else %> |
53 | 53 |
<%= link_to 'Run', "#", class: "btn btn-mini disabled" %> |
54 | 54 |
<% end %> |
@@ -1,4 +1,4 @@ |
||
1 |
-<div class='container'> |
|
1 |
+<div class='container agent-show'> |
|
2 | 2 |
<div class='row'> |
3 | 3 |
<div class='span12'> |
4 | 4 |
|
@@ -11,6 +11,7 @@ |
||
11 | 11 |
<li class='disabled'><a><i class='icon-picture'></i> Summary</a></li> |
12 | 12 |
<li class='active'><a href="#details" data-toggle="tab"><i class='icon-indent-left'></i> Details</a></li> |
13 | 13 |
<% end %> |
14 |
+ <li><a href="#logs" data-toggle="tab" data-agent-id="<%= @agent.id %>"><i class='icon-list-alt'></i> Logs</a></li> |
|
14 | 15 |
|
15 | 16 |
<% if @agent.events.count > 0 %> |
16 | 17 |
<li><%= link_to '<i class="icon-random"></i> Events'.html_safe, events_path(:agent => @agent.to_param) %></li> |
@@ -18,17 +19,24 @@ |
||
18 | 19 |
<li><%= link_to '<i class="icon-chevron-left"></i> Back'.html_safe, agents_path %></li> |
19 | 20 |
<li><%= link_to '<i class="icon-pencil"></i> Edit'.html_safe, edit_agent_path(@agent) %></li> |
20 | 21 |
|
21 |
- <% if @agent.events.count > 0 %> |
|
22 |
+ <% if @agent.can_be_scheduled? || @agent.events.count > 0 %> |
|
22 | 23 |
<li class="dropdown"> |
23 | 24 |
<a class="dropdown-toggle" data-toggle="dropdown" href="#">Actions <b class="caret"></b></a> |
24 | 25 |
<ul class="dropdown-menu" role="menu" aria-labelledby="dLabel"> |
26 |
+ <% if @agent.can_be_scheduled? %> |
|
27 |
+ <li> |
|
28 |
+ <%= link_to '<i class="icon-refresh"></i> Run'.html_safe, run_agent_path(@agent, :return => "show"), method: :post, :tabindex => "-1" %> |
|
29 |
+ </li> |
|
30 |
+ <% end %> |
|
31 |
+ |
|
32 |
+ <% if @agent.events.count > 0 %> |
|
25 | 33 |
<li> |
26 | 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" %> |
27 | 35 |
</li> |
36 |
+ <% end %> |
|
28 | 37 |
</ul> |
29 | 38 |
</li> |
30 | 39 |
<% end %> |
31 |
- |
|
32 | 40 |
</ul> |
33 | 41 |
|
34 | 42 |
<div class="tab-content"> |
@@ -42,6 +50,18 @@ |
||
42 | 50 |
<% end %> |
43 | 51 |
</div> |
44 | 52 |
|
53 |
+ <div class="tab-pane" id="logs" data-agent-id="<%= @agent.id %>"> |
|
54 |
+ <h2> |
|
55 |
+ <%= @agent.name %> Logs |
|
56 |
+ <%= image_tag "spinner-arrows.gif", :class => "spinner" %> |
|
57 |
+ <i class="icon-refresh action-icon refresh"></i> |
|
58 |
+ <i class="icon-trash action-icon clear"></i> |
|
59 |
+ </h2> |
|
60 |
+ <div class='logs'> |
|
61 |
+ Just a moment... |
|
62 |
+ </div> |
|
63 |
+ </div> |
|
64 |
+ |
|
45 | 65 |
<div class="tab-pane <%= agent_show_view(@agent).present? ? "" : "active" %>" id="details"> |
46 | 66 |
<h2><%= @agent.name %> Details</h2> |
47 | 67 |
|
@@ -1,6 +1,10 @@ |
||
1 |
-<% flash.each do |name, msg| %> |
|
2 |
- <div class="top-flash alert alert-<%= name == :notice ? "success" : "error" %>"> |
|
3 |
- <a class="close" data-dismiss="alert">×</a> |
|
4 |
- <%= content_tag :div, msg, :id => "flash_#{name}" if msg.is_a?(String) %> |
|
1 |
+<% if flash.keys.length > 0 %> |
|
2 |
+ <div class="flash"> |
|
3 |
+ <% flash.each do |name, msg| %> |
|
4 |
+ <div class="alert alert-<%= name == :notice ? "success" : "error" %>"> |
|
5 |
+ <a class="close" data-dismiss="alert">×</a> |
|
6 |
+ <%= content_tag :div, msg, :id => "flash_#{name}" if msg.is_a?(String) %> |
|
7 |
+ </div> |
|
8 |
+ <% end %> |
|
5 | 9 |
</div> |
6 | 10 |
<% end %> |
@@ -14,6 +14,11 @@ |
||
14 | 14 |
<span class="badge"><i class="icon-refresh icon-white"></i> <span class='number'>0</span></span> |
15 | 15 |
</a> |
16 | 16 |
</li> |
17 |
+ <li id='event-indicator'> |
|
18 |
+ <a href="#"> |
|
19 |
+ <span class="badge"><i class="icon-random icon-white"></i> <span class='number'>0</span> new events</span> |
|
20 |
+ </a> |
|
21 |
+ </li> |
|
17 | 22 |
<% end %> |
18 | 23 |
|
19 | 24 |
<li class="dropdown"> |
@@ -0,0 +1,30 @@ |
||
1 |
+<table class='table table-striped logs'> |
|
2 |
+ <tr> |
|
3 |
+ <th>Message</th> |
|
4 |
+ <th>When</th> |
|
5 |
+ <th></th> |
|
6 |
+ </tr> |
|
7 |
+ |
|
8 |
+ <% @logs.each do |log| %> |
|
9 |
+ <tr> |
|
10 |
+ <td><%= truncate log.message, :length => 200, :omission => "..." %></td> |
|
11 |
+ <td><%= time_ago_in_words log.created_at %> ago</td> |
|
12 |
+ |
|
13 |
+ <td> |
|
14 |
+ <div class="btn-group"> |
|
15 |
+ <% if log.inbound_event_id.present? %> |
|
16 |
+ <%= link_to 'Event In', event_path(log.inbound_event), class: "btn btn-mini" %> |
|
17 |
+ <% else %> |
|
18 |
+ <%= link_to 'Event In', '#', class: "btn btn-mini disabled" %> |
|
19 |
+ <% end %> |
|
20 |
+ |
|
21 |
+ <% if log.outbound_event_id.present? %> |
|
22 |
+ <%= link_to 'Event Out', event_path(log.outbound_event), class: "btn btn-mini" %> |
|
23 |
+ <% else %> |
|
24 |
+ <%= link_to 'Event Out', '#', class: "btn btn-mini disabled" %> |
|
25 |
+ <% end %> |
|
26 |
+ </div> |
|
27 |
+ </td> |
|
28 |
+ </tr> |
|
29 |
+ <% end %> |
|
30 |
+</table> |
@@ -11,6 +11,12 @@ Huginn::Application.routes.draw do |
||
11 | 11 |
get :event_descriptions |
12 | 12 |
get :diagram |
13 | 13 |
end |
14 |
+ |
|
15 |
+ resources :logs, :only => [:index] do |
|
16 |
+ collection do |
|
17 |
+ delete :clear |
|
18 |
+ end |
|
19 |
+ end |
|
14 | 20 |
end |
15 | 21 |
resources :events, :only => [:index, :show, :destroy] |
16 | 22 |
match "/worker_status" => "worker_status#show" |
@@ -0,0 +1,13 @@ |
||
1 |
+class CreateAgentLogs < ActiveRecord::Migration |
|
2 |
+ def change |
|
3 |
+ create_table :agent_logs do |t| |
|
4 |
+ t.integer :agent_id, :null => false |
|
5 |
+ t.text :message, :null => false |
|
6 |
+ t.integer :level, :default => 3, :null => false |
|
7 |
+ t.integer :inbound_event_id |
|
8 |
+ t.integer :outbound_event_id |
|
9 |
+ |
|
10 |
+ t.timestamps |
|
11 |
+ end |
|
12 |
+ end |
|
13 |
+end |
@@ -11,7 +11,17 @@ |
||
11 | 11 |
# |
12 | 12 |
# It's strongly recommended to check this file into your version control system. |
13 | 13 |
|
14 |
-ActiveRecord::Schema.define(:version => 20130509053743) do |
|
14 |
+ActiveRecord::Schema.define(:version => 20130819160603) do |
|
15 |
+ |
|
16 |
+ create_table "agent_logs", :force => true do |t| |
|
17 |
+ t.integer "agent_id", :null => false |
|
18 |
+ t.text "message", :null => false |
|
19 |
+ t.integer "level", :default => 3, :null => false |
|
20 |
+ t.integer "inbound_event_id" |
|
21 |
+ t.integer "outbound_event_id" |
|
22 |
+ t.datetime "created_at", :null => false |
|
23 |
+ t.datetime "updated_at", :null => false |
|
24 |
+ end |
|
15 | 25 |
|
16 | 26 |
create_table "agents", :force => true do |t| |
17 | 27 |
t.integer "user_id" |
@@ -12,10 +12,21 @@ describe EventsController do |
||
12 | 12 |
get :index |
13 | 13 |
assigns(:events).all? {|i| i.user.should == users(:bob) }.should be_true |
14 | 14 |
end |
15 |
+ |
|
16 |
+ it "can filter by Agent" do |
|
17 |
+ sign_in users(:bob) |
|
18 |
+ get :index, :agent => agents(:bob_website_agent) |
|
19 |
+ assigns(:events).length.should == agents(:bob_website_agent).events.length |
|
20 |
+ assigns(:events).all? {|i| i.agent.should == agents(:bob_website_agent) }.should be_true |
|
21 |
+ |
|
22 |
+ lambda { |
|
23 |
+ get :index, :agent => agents(:jane_website_agent) |
|
24 |
+ }.should raise_error(ActiveRecord::RecordNotFound) |
|
25 |
+ end |
|
15 | 26 |
end |
16 | 27 |
|
17 | 28 |
describe "GET show" do |
18 |
- it "only shows Agents for the current user" do |
|
29 |
+ it "only shows Events for the current user" do |
|
19 | 30 |
sign_in users(:bob) |
20 | 31 |
get :show, :id => events(:bob_website_agent_event).to_param |
21 | 32 |
assigns(:event).should eq(events(:bob_website_agent_event)) |
@@ -0,0 +1,37 @@ |
||
1 |
+require 'spec_helper' |
|
2 |
+ |
|
3 |
+describe LogsController do |
|
4 |
+ describe "GET index" do |
|
5 |
+ it "can filter by Agent" do |
|
6 |
+ sign_in users(:bob) |
|
7 |
+ get :index, :agent_id => agents(:bob_weather_agent).id |
|
8 |
+ assigns(:logs).length.should == agents(:bob_weather_agent).logs.length |
|
9 |
+ assigns(:logs).all? {|i| i.agent.should == agents(:bob_weather_agent) }.should be_true |
|
10 |
+ end |
|
11 |
+ |
|
12 |
+ it "only loads Agents owned by the current user" do |
|
13 |
+ sign_in users(:bob) |
|
14 |
+ lambda { |
|
15 |
+ get :index, :agent_id => agents(:jane_weather_agent).id |
|
16 |
+ }.should raise_error(ActiveRecord::RecordNotFound) |
|
17 |
+ end |
|
18 |
+ end |
|
19 |
+ |
|
20 |
+ describe "DELETE clear" do |
|
21 |
+ it "deletes all logs for a specific Agent" do |
|
22 |
+ sign_in users(:bob) |
|
23 |
+ lambda { |
|
24 |
+ delete :clear, :agent_id => agents(:bob_weather_agent).id |
|
25 |
+ }.should change { AgentLog.count }.by(-1 * agents(:bob_weather_agent).logs.count) |
|
26 |
+ assigns(:logs).length.should == 0 |
|
27 |
+ agents(:bob_weather_agent).logs.count.should == 0 |
|
28 |
+ end |
|
29 |
+ |
|
30 |
+ it "only deletes logs for an Agent owned by the current user" do |
|
31 |
+ sign_in users(:bob) |
|
32 |
+ lambda { |
|
33 |
+ delete :clear, :agent_id => agents(:jane_weather_agent).id |
|
34 |
+ }.should raise_error(ActiveRecord::RecordNotFound) |
|
35 |
+ end |
|
36 |
+ end |
|
37 |
+end |
@@ -0,0 +1,15 @@ |
||
1 |
+log_for_jane_website_agent: |
|
2 |
+ agent: jane_website_agent |
|
3 |
+ message: "fetching some website data" |
|
4 |
+ |
|
5 |
+log_for_bob_website_agent: |
|
6 |
+ agent: bob_website_agent |
|
7 |
+ message: "fetching some other website data" |
|
8 |
+ |
|
9 |
+first_log_for_bob_weather_agent: |
|
10 |
+ agent: bob_weather_agent |
|
11 |
+ message: "checking the weather" |
|
12 |
+ |
|
13 |
+second_log_for_bob_weather_agent: |
|
14 |
+ agent: bob_weather_agent |
|
15 |
+ message: "checking the weather again" |
@@ -0,0 +1,15 @@ |
||
1 |
+require 'spec_helper' |
|
2 |
+ |
|
3 |
+# Specs in this file have access to a helper object that includes |
|
4 |
+# the AgentLogsHelper. For example: |
|
5 |
+# |
|
6 |
+# describe AgentLogsHelper do |
|
7 |
+# describe "string concat" do |
|
8 |
+# it "concats two strings with spaces" do |
|
9 |
+# expect(helper.concat_strings("this","that")).to eq("this that") |
|
10 |
+# end |
|
11 |
+# end |
|
12 |
+# end |
|
13 |
+describe LogsHelper do |
|
14 |
+ pending "add some examples to (or delete) #{__FILE__}" |
|
15 |
+end |
@@ -0,0 +1,77 @@ |
||
1 |
+require 'spec_helper' |
|
2 |
+ |
|
3 |
+describe AgentLog do |
|
4 |
+ describe "validations" do |
|
5 |
+ before do |
|
6 |
+ @log = AgentLog.new(:agent => agents(:jane_website_agent), :message => "The agent did something", :level => 3) |
|
7 |
+ @log.should be_valid |
|
8 |
+ end |
|
9 |
+ |
|
10 |
+ it "requires an agent" do |
|
11 |
+ @log.agent = nil |
|
12 |
+ @log.should_not be_valid |
|
13 |
+ @log.should have(1).error_on(:agent) |
|
14 |
+ end |
|
15 |
+ |
|
16 |
+ it "requires a message" do |
|
17 |
+ @log.message = "" |
|
18 |
+ @log.should_not be_valid |
|
19 |
+ @log.message = nil |
|
20 |
+ @log.should_not be_valid |
|
21 |
+ @log.should have(1).error_on(:message) |
|
22 |
+ end |
|
23 |
+ |
|
24 |
+ it "requires a valid log level" do |
|
25 |
+ @log.level = nil |
|
26 |
+ @log.should_not be_valid |
|
27 |
+ @log.should have(1).error_on(:level) |
|
28 |
+ |
|
29 |
+ @log.level = -1 |
|
30 |
+ @log.should_not be_valid |
|
31 |
+ @log.should have(1).error_on(:level) |
|
32 |
+ |
|
33 |
+ @log.level = 5 |
|
34 |
+ @log.should_not be_valid |
|
35 |
+ @log.should have(1).error_on(:level) |
|
36 |
+ |
|
37 |
+ @log.level = 4 |
|
38 |
+ @log.should be_valid |
|
39 |
+ |
|
40 |
+ @log.level = 0 |
|
41 |
+ @log.should be_valid |
|
42 |
+ end |
|
43 |
+ end |
|
44 |
+ |
|
45 |
+ describe "#log_for_agent" do |
|
46 |
+ it "creates AgentLogs" do |
|
47 |
+ log = AgentLog.log_for_agent(agents(:jane_website_agent), "some message", :level => 4, :outbound_event => events(:jane_website_agent_event)) |
|
48 |
+ log.should_not be_new_record |
|
49 |
+ log.agent.should == agents(:jane_website_agent) |
|
50 |
+ log.outbound_event.should == events(:jane_website_agent_event) |
|
51 |
+ log.message.should == "some message" |
|
52 |
+ log.level.should == 4 |
|
53 |
+ end |
|
54 |
+ |
|
55 |
+ it "cleans up old logs when there are more than log_length" do |
|
56 |
+ stub(AgentLog).log_length { 4 } |
|
57 |
+ AgentLog.log_for_agent(agents(:jane_website_agent), "message 1") |
|
58 |
+ AgentLog.log_for_agent(agents(:jane_website_agent), "message 2") |
|
59 |
+ AgentLog.log_for_agent(agents(:jane_website_agent), "message 3") |
|
60 |
+ AgentLog.log_for_agent(agents(:jane_website_agent), "message 4") |
|
61 |
+ agents(:jane_website_agent).logs.order("agent_logs.id desc").first.message.should == "message 4" |
|
62 |
+ agents(:jane_website_agent).logs.order("agent_logs.id desc").last.message.should == "message 1" |
|
63 |
+ AgentLog.log_for_agent(agents(:jane_website_agent), "message 5") |
|
64 |
+ agents(:jane_website_agent).logs.order("agent_logs.id desc").first.message.should == "message 5" |
|
65 |
+ agents(:jane_website_agent).logs.order("agent_logs.id desc").last.message.should == "message 2" |
|
66 |
+ AgentLog.log_for_agent(agents(:jane_website_agent), "message 6") |
|
67 |
+ agents(:jane_website_agent).logs.order("agent_logs.id desc").first.message.should == "message 6" |
|
68 |
+ agents(:jane_website_agent).logs.order("agent_logs.id desc").last.message.should == "message 3" |
|
69 |
+ end |
|
70 |
+ end |
|
71 |
+ |
|
72 |
+ describe "#log_length" do |
|
73 |
+ it "defaults to 100" do |
|
74 |
+ AgentLog.log_length.should == 100 |
|
75 |
+ end |
|
76 |
+ end |
|
77 |
+end |
@@ -35,11 +35,10 @@ describe Agents::WebsiteAgent do |
||
35 | 35 |
end |
36 | 36 |
|
37 | 37 |
it "should log an error if the number of results for a set of extraction patterns differs" do |
38 |
- lambda { |
|
39 |
- @site[:extract][:url][:css] = "div" |
|
40 |
- @checker.options = @site |
|
41 |
- @checker.check |
|
42 |
- }.should raise_error(StandardError, /Got an uneven number of matches/) |
|
38 |
+ @site[:extract][:url][:css] = "div" |
|
39 |
+ @checker.options = @site |
|
40 |
+ @checker.check |
|
41 |
+ @checker.logs.first.message.should =~ /Got an uneven number of matches/ |
|
43 | 42 |
end |
44 | 43 |
end |
45 | 44 |
|