Skip to content

Commit

Permalink
Merge pull request huginn#939 from cantino/dry_run_with_event
Browse files Browse the repository at this point in the history
Add support for sending an event in a "Dry Run"
  • Loading branch information
knu committed Aug 3, 2015
2 parents 3888648 + 47d1947 commit 67ff37a
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 28 deletions.
65 changes: 61 additions & 4 deletions app/assets/javascripts/components/utils.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,67 @@ class @Utils
body?(modal.querySelector('.modal-body'))
$(modal).modal('show')

@handleDryRunButton: (button, data = $(button.form).serialize()) ->
@handleDryRunButton: (button, data = if button.form then $(':input[name!="_method"]', button.form).serialize() else '') ->
$(button).prop('disabled', true)
cleanup = -> $(button).prop('disabled', false)

url = $(button).data('action-url')
with_event_mode = $(button).data('with-event-mode')

if with_event_mode is 'no'
return @invokeDryRun(url, data, cleanup)

Utils.showDynamicModal """
<h5>Event to send#{if with_event_mode is 'maybe' then ' (Optional)' else ''}</h5>
<form class="dry-run-form" method="post">
<div class="form-group">
<textarea rows="10" name="event" class="payload-editor" data-height="200">
{}
</textarea>
</div>
<div class="form-group">
<input value="Dry Run" class="btn btn-primary" type="submit" />
</div>
</form>
""",
body: (body) =>
form = $(body).find('.dry-run-form')
payload_editor = form.find('.payload-editor')
if previous = $(button).data('payload')
payload_editor.text(previous)
window.setupJsonEditor(payload_editor)
form.submit (e) =>
e.preventDefault()
json = $(e.target).find('.payload-editor').val()
json = '{}' if json == ''
try
payload = JSON.parse(json)
throw true unless payload.constructor is Object
if Object.keys(payload).length == 0
json = ''
else
json = JSON.stringify(payload)
catch
alert 'Invalid JSON object.'
return
if json == ''
if with_event_mode is 'yes'
alert 'Event is required for this agent to run.'
return
dry_run_data = data
$(button).data('payload', null)
else
dry_run_data = "event=#{encodeURIComponent(json)}&#{data}"
$(button).data('payload', json)
$(body).closest('[role=dialog]').on 'hidden.bs.modal', =>
@invokeDryRun(url, dry_run_data, cleanup)
.modal('hide')
title: 'Dry Run'
onHide: cleanup

@invokeDryRun: (url, data, callback) ->
$('body').css(cursor: 'progress')
$.ajax type: 'POST', url: $(button).data('action-url'), dataType: 'json', data: data
$.ajax type: 'POST', url: url, dataType: 'json', data: data
.always =>
$('body').css(cursor: 'auto')
.done (json) =>
Expand All @@ -55,7 +112,7 @@ class @Utils
find('.agent-dry-run-events').text(json.events).end().
find('.agent-dry-run-memory').text(json.memory)
title: 'Dry Run Results',
onHide: -> $(button).prop('disabled', false)
onHide: callback
.fail (xhr, status, error) ->
alert('Error: ' + error)
$(button).prop('disabled', false)
callback()
9 changes: 7 additions & 2 deletions app/concerns/dry_runnable.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module DryRunnable
extend ActiveSupport::Concern

def dry_run!
def dry_run!(event = nil)
@dry_run = true

log = StringIO.new
Expand All @@ -13,7 +13,12 @@ def dry_run!
begin
raise "#{short_type} does not support dry-run" unless can_dry_run?
readonly!
check
if event
raise "This agent cannot receive an event!" unless can_receive_events?
receive([event])
else
check
end
rescue => e
error "Exception during dry-run. #{e.message}: #{e.backtrace.join("\n")}"
end
Expand Down
10 changes: 8 additions & 2 deletions app/controllers/agents_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def run
def dry_run
attrs = params[:agent] || {}
if agent = current_user.agents.find_by(id: params[:id])
# PUT /agents/:id/dry_run
# POST /agents/:id/dry_run
if attrs.present?
type = agent.type
agent = Agent.build_for_type(type, current_user, attrs)
Expand All @@ -50,7 +50,13 @@ def dry_run
agent.name ||= '(Untitled)'

if agent.valid?
results = agent.dry_run!
if event_payload = params[:event]
dummy_agent = Agent.build_for_type('ManualEventAgent', current_user, name: 'Dry-Runner')
dummy_agent.readonly!
event = dummy_agent.events.build(user: current_user, payload: event_payload)
end

results = agent.dry_run!(event)

render json: {
log: results[:log],
Expand Down
12 changes: 12 additions & 0 deletions app/helpers/agent_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,16 @@ def agent_controllers(agent, delimiter = ', ')
}.join(delimiter).html_safe
end
end

def agent_dry_run_with_event_mode(agent)
case
when agent.cannot_receive_events?
'no'.freeze
when agent.cannot_be_scheduled?
# incoming event is the only trigger for the agent
'yes'.freeze
else
'maybe'.freeze
end
end
end
2 changes: 1 addition & 1 deletion app/views/agents/_action_menu.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<% if agent.can_dry_run? %>
<li>
<%= link_to icon_tag('glyphicon-refresh') + ' Dry Run', '#', 'data-action-url' => dry_run_agent_path(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this, '_method=PUT')" %>
<%= link_to icon_tag('glyphicon-refresh') + ' Dry Run', '#', 'data-action-url' => dry_run_agent_path(agent), 'data-with-event-mode' => agent_dry_run_with_event_mode(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this)" %>
</li>
<% end %>

Expand Down
2 changes: 1 addition & 1 deletion app/views/agents/_options.erb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@
<div class="form-group">
<%= submit_tag "Save", :class => "btn btn-primary" %>
<% if agent.can_dry_run? %>
<%= button_tag class: 'btn btn-default agent-dry-run-button', type: 'button', 'data-action-url' => agent.persisted? ? dry_run_agent_path(agent) : dry_run_agents_path do %><%= icon_tag('glyphicon-refresh') %> Dry Run<% end %>
<%= button_tag class: 'btn btn-default agent-dry-run-button', type: 'button', 'data-action-url' => agent.persisted? ? dry_run_agent_path(agent) : dry_run_agents_path, 'data-with-event-mode' => agent_dry_run_with_event_mode(agent) do %><%= icon_tag('glyphicon-refresh') %> Dry Run<% end %>
<% end %>
</div>
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
resources :agents do
member do
post :run
put :dry_run
post :dry_run
post :handle_details_post
put :leave_scenario
delete :remove_events
Expand Down
63 changes: 46 additions & 17 deletions spec/concerns/dry_runnable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,22 @@ class Agents::SandboxedAgent < Agent
can_dry_run!

def check
perform
end

def receive(events)
events.each do |event|
perform(event.payload['prefix'])
end
end

private

def perform(prefix = nil)
log "Logging"
create_event payload: { 'test' => 'foo' }
create_event payload: { 'test' => "#{prefix}foo" }
error "Recording error"
create_event payload: { 'test' => 'bar' }
create_event payload: { 'test' => "#{prefix}bar" }
self.memory = { 'last_status' => 'ok', 'dry_run' => dry_run? }
save!
end
Expand Down Expand Up @@ -46,21 +58,6 @@ def counts
expect(messages).to eq(['Logging', 'Recording error'])
end

it "traps logging, event emission and memory updating, with dry_run? returning true" do
results = nil

expect {
results = @agent.dry_run!
@agent.reload
}.not_to change {
[@agent.memory, counts]
}

expect(results[:log]).to match(/\AI, .+ INFO -- : Logging\nE, .+ ERROR -- : Recording error\n/)
expect(results[:events]).to eq([{ 'test' => 'foo' }, { 'test' => 'bar' }])
expect(results[:memory]).to eq({ 'last_status' => 'ok', 'dry_run' => true })
end

it "does not perform dry-run if Agent does not support dry-run" do
stub(@agent).can_dry_run? { false }

Expand All @@ -77,4 +74,36 @@ def counts
expect(results[:events]).to eq([])
expect(results[:memory]).to eq({})
end

describe "dry_run!" do
it "traps any destructive operations during a run" do
results = nil

expect {
results = @agent.dry_run!
@agent.reload
}.not_to change {
[@agent.memory, counts]
}

expect(results[:log]).to match(/\AI, .+ INFO -- : Logging\nE, .+ ERROR -- : Recording error\n/)
expect(results[:events]).to eq([{ 'test' => 'foo' }, { 'test' => 'bar' }])
expect(results[:memory]).to eq({ 'last_status' => 'ok', 'dry_run' => true })
end

it "traps any destructive operations during a run when an event is given" do
results = nil

expect {
results = @agent.dry_run!(Event.new(payload: { 'prefix' => 'super' }))
@agent.reload
}.not_to change {
[@agent.memory, counts]
}

expect(results[:log]).to match(/\AI, .+ INFO -- : Logging\nE, .+ ERROR -- : Recording error\n/)
expect(results[:events]).to eq([{ 'test' => 'superfoo' }, { 'test' => 'superbar' }])
expect(results[:memory]).to eq({ 'last_status' => 'ok', 'dry_run' => true })
end
end
end
13 changes: 13 additions & 0 deletions spec/controllers/agents_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,19 @@ def valid_attributes(options = {})
[users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at]
}
end

it "accepts an event" do
sign_in users(:bob)
agent = agents(:bob_website_agent)
url_from_event = "http://xkcd.com/?from_event=1".freeze
expect {
post :dry_run, id: agent, event: { url: url_from_event }
}.not_to change {
[users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at]
}
json = JSON.parse(response.body)
expect(json['log']).to match(/^I, .* : Fetching #{Regexp.quote(url_from_event)}$/)
end
end

describe "DELETE memory" do
Expand Down

0 comments on commit 67ff37a

Please sign in to comment.