Show recently received events in dry run modal

This commit is contained in:
Dominik Sander 2016-04-22 16:17:00 +02:00
parent 9202c84406
commit d573b98a68
10 changed files with 255 additions and 169 deletions

View file

@ -43,90 +43,65 @@ class @Utils
if with_event_mode is 'no'
return @invokeDryRun(url, data, cleanup)
$.ajax url,
method: 'GET',
data:
with_event_mode: with_event_mode
source_ids: $.map($(".link-region select option:selected"), (el) -> $(el).val() )
success: (modal_data) =>
Utils.showDynamicModal modal_data,
body: (body) =>
form = $(body).find('.dry-run-form')
payload_editor = form.find('.payload-editor')
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')
$(body).closest('[role=dialog]').on 'shown.bs.modal', ->
$(this).find('.btn-primary').focus()
title: 'Dry Run'
onHide: cleanup
if previous = $(button).data('payload')
payload_editor.text(previous)
editor = window.setupJsonEditor(payload_editor)[0]
$(body).find('.dry-run-event-sample').click (e) =>
e.preventDefault()
editor.json = $(e.currentTarget).data('payload')
editor.rebuild()
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')
$(body).closest('[role=dialog]').on 'shown.bs.modal', ->
$(this).find('.btn-primary').focus()
title: 'Dry Run'
onHide: cleanup
@invokeDryRun: (url, data, callback) ->
$('body').css(cursor: 'progress')
$.ajax type: 'POST', url: url, dataType: 'json', data: data
$.ajax type: 'POST', url: url, dataType: 'html', data: data
.always =>
$('body').css(cursor: 'auto')
.done (json) =>
Utils.showDynamicModal """
<!-- Nav tabs -->
<ul id="resultTabs" class="nav nav-tabs agent-dry-run-tabs" role="tablist">
<li role="presentation"><a href="#tabEvents" aria-controls="tabEvents" role="tab" data-toggle="tab">Events</a></li>
<li role="presentation"><a href="#tabLog" aria-controls="tabLog" role="tab" data-toggle="tab">Log</a></li>
<li role="presentation"><a href="#tabMemory" aria-controls="tabMemory" role="tab" data-toggle="tab">Memory</a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane" id="tabEvents">
<pre class="agent-dry-run-events"></pre>
</div>
<div role="tabpanel" class="tab-pane" id="tabLog">
<pre><small class="agent-dry-run-log"></small></pre>
</div>
<div role="tabpanel" class="tab-pane" id="tabMemory">
<pre class="agent-dry-run-memory"></pre>
</div>
</div>
""",
body: (body) ->
$(body).
find('.agent-dry-run-log').text(json.log).end().
find('.agent-dry-run-events').text(json.events).end().
find('.agent-dry-run-memory').text(json.memory)
active = if json.events.match(/^\[?\s*\]?$/) then 'tabLog' else 'tabEvents'
$('#resultTabs a[href="#' + active + '"]').tab('show')
.done (modal_data) =>
Utils.showDynamicModal modal_data,
title: 'Dry Run Results',
onHide: callback
.fail (xhr, status, error) ->

View file

@ -0,0 +1,50 @@
module Agents
class DryRunsController < ApplicationController
include ActionView::Helpers::TextHelper
def index
@events = if params[:agent_id]
current_user.agents.find_by(id: params[:agent_id]).received_events.limit(5)
elsif params[:source_ids]
Event.where(agent_id: current_user.agents.where(id: params[:source_ids]).pluck(:id))
.order("id DESC").limit(5)
end
render layout: false
end
def create
attrs = params[:agent] || {}
if agent = current_user.agents.find_by(id: params[:agent_id])
# POST /agents/:id/dry_run
if attrs.present?
type = agent.type
agent = Agent.build_for_type(type, current_user, attrs)
end
else
# POST /agents/dry_run
type = attrs.delete(:type)
agent = Agent.build_for_type(type, current_user, attrs)
end
agent.name ||= '(Untitled)'
if agent.valid?
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)
else
@results = { events: [], memory: [],
log: [
"#{pluralize(agent.errors.count, "error")} prohibited this Agent from being saved:",
*agent.errors.full_messages
].join("\n- ") }
end
render layout: false
end
end
end

View file

@ -48,47 +48,6 @@ class AgentsController < ApplicationController
end
end
def dry_run
attrs = params[:agent] || {}
if agent = current_user.agents.find_by(id: params[:id])
# POST /agents/:id/dry_run
if attrs.present?
type = agent.type
agent = Agent.build_for_type(type, current_user, attrs)
end
else
# POST /agents/dry_run
type = attrs.delete(:type)
agent = Agent.build_for_type(type, current_user, attrs)
end
agent.name ||= '(Untitled)'
if agent.valid?
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],
events: Utils.pretty_print(results[:events], false),
memory: Utils.pretty_print(results[:memory] || {}, false),
}
else
render json: {
log: [
"#{pluralize(agent.errors.count, "error")} prohibited this Agent from being saved:",
*agent.errors.full_messages
].join("\n- "),
events: '',
memory: '',
}
end
end
def type_details
@agent = Agent.build_for_type(params[:type], current_user, {})
initialize_presenter

View file

@ -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), 'data-with-event-mode' => agent_dry_run_with_event_mode(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this)" %>
<%= link_to icon_tag('glyphicon-refresh') + ' Dry Run', '#', 'data-action-url' => agent_dry_runs_path(agent), 'data-with-event-mode' => agent_dry_run_with_event_mode(agent), tabindex: "-1", onclick: "Utils.handleDryRunButton(this)" %>
</li>
<% end %>

View file

@ -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, 'data-with-event-mode' => agent_dry_run_with_event_mode(agent) 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? ? agent_dry_runs_path(agent) : dry_runs_path(type: agent.type), 'data-with-event-mode' => agent_dry_run_with_event_mode(agent) do %><%= icon_tag('glyphicon-refresh') %> Dry Run<% end %>
<% end %>
</div>

View file

@ -0,0 +1,18 @@
<!-- Nav tabs -->
<ul id="resultTabs" class="nav nav-tabs agent-dry-run-tabs" role="tablist">
<li role="presentation" class="<%= @results[:events].empty? ? '' : 'active' %>"><a href="#tabEvents" aria-controls="tabEvents" role="tab" data-toggle="tab">Events</a></li>
<li role="presentation" class="<%= @results[:events].empty? ? 'active' : '' %>"><a href="#tabLog" aria-controls="tabLog" role="tab" data-toggle="tab">Log</a></li>
<li role="presentation"><a href="#tabMemory" aria-controls="tabMemory" role="tab" data-toggle="tab">Memory</a></li>
</ul>
<!-- Tab panes -->
<div class="tab-content">
<div role="tabpanel" class="tab-pane <%= @results[:events].empty? ? '' : 'active' %>" id="tabEvents">
<pre class="agent-dry-run-events"><%= Utils.pretty_print(@results[:events], false) %></pre>
</div>
<div role="tabpanel" class="tab-pane <%= @results[:events].empty? ? 'active' : ''%>" id="tabLog">
<pre><small class="agent-dry-run-log"><%= @results[:log] %></small></pre>
</div>
<div role="tabpanel" class="tab-pane" id="tabMemory">
<pre class="agent-dry-run-memory"><%= Utils.pretty_print(@results[:memory], false) %></pre>
</div>
</div>

View file

@ -0,0 +1,20 @@
<% if @events && @events.length > 0 %>
<h5>Recently received events: </h5>
<% @events.each do |event| %>
<%= link_to '#', class: 'dry-run-event-sample', 'data-payload' => event.payload.to_json do %>
<pre><%= truncate event.payload.to_json, :length => 90, :omission => "" %></pre>
<% end %>
<% end %>
<% end %>
<h5>Event to send<%= params[:with_event_mode] == 'maybe' ? ' (Optional)' : '' %></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>

View file

@ -2,7 +2,6 @@ Huginn::Application.routes.draw do
resources :agents do
member do
post :run
post :dry_run
post :handle_details_post
put :leave_scenario
delete :remove_events
@ -13,7 +12,6 @@ Huginn::Application.routes.draw do
put :toggle_visibility
post :propagate
get :type_details
post :dry_run
get :event_descriptions
post :validate
post :complete
@ -26,6 +24,14 @@ Huginn::Application.routes.draw do
end
resources :events, :only => [:index]
scope module: :agents do
resources :dry_runs, only: [:index, :create]
end
end
scope module: :agents do
resources :dry_runs, only: [:index, :create]
end
resource :diagram, :only => [:show]

View file

@ -0,0 +1,104 @@
require 'rails_helper'
describe Agents::DryRunsController do
def valid_attributes(options = {})
{
type: "Agents::WebsiteAgent",
name: "Something",
options: agents(:bob_website_agent).options,
source_ids: [agents(:bob_weather_agent).id, ""]
}.merge(options)
end
before do
sign_in users(:bob)
end
describe "GET index" do
it "does not load any events without specifing sources" do
get :index, type: 'Agents::WebsiteAgent', source_ids: []
expect(assigns(:events)).to eq([])
end
context "does not load events when the agent is owned by a different user" do
before do
@agent = agents(:jane_website_agent)
@agent.sources << @agent
@agent.save!
expect(@agent.events.count).not_to be(0)
end
it "for new agents" do
get :index, type: 'Agents::WebsiteAgent', source_ids: [@agent.id]
expect(assigns(:events)).to eq([])
end
it "for existing agents" do
expect(@agent.events.count).not_to be(0)
expect { get :index, agent_id: @agent }.to raise_error(NoMethodError)
end
end
context "loads the most recent events" do
before do
@agent = agents(:bob_website_agent)
@agent.sources << @agent
@agent.save!
end
it "load the most recent events when providing source ids" do
get :index, type: 'Agents::WebsiteAgent', source_ids: [@agent.id]
expect(assigns(:events)).to eq([@agent.events.first])
end
it "loads the most recent events for a saved agent" do
get :index, agent_id: @agent
expect(assigns(:events)).to eq([@agent.events.first])
end
end
end
describe "POST create" do
before do
stub_request(:any, /xkcd/).to_return(body: File.read(Rails.root.join("spec/data_fixtures/xkcd.html")), status: 200)
end
it "does not actually create any agent, event or log" do
expect {
post :create, agent: valid_attributes
}.not_to change {
[users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count]
}
results = assigns(:results)
expect(results[:log]).to be_a(String)
expect(results[:log]).to include('Extracting html at')
expect(results[:events]).to be_a(Array)
expect(results[:events].length).to eq(1)
expect(results[:events].map(&:class)).to eq([ActiveSupport::HashWithIndifferentAccess])
expect(results[:memory]).to be_a(Hash)
end
it "does not actually update an agent" do
agent = agents(:bob_weather_agent)
expect {
post :create, agent_id: agent, agent: valid_attributes(name: 'New Name')
}.not_to change {
[users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at]
}
end
it "accepts an event" do
agent = agents(:bob_website_agent)
agent.options['url_from_event'] = '{{ url }}'
agent.save!
url_from_event = "http://xkcd.com/?from_event=1".freeze
expect {
post :create, agent_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]
}
results = assigns(:results)
expect(results[:log]).to match(/^\[\d\d:\d\d:\d\d\] INFO -- : Fetching #{Regexp.quote(url_from_event)}$/)
end
end
end

View file

@ -408,52 +408,6 @@ describe AgentsController do
end
end
describe "POST dry_run" do
before do
stub_request(:any, /xkcd/).to_return(body: File.read(Rails.root.join("spec/data_fixtures/xkcd.html")), status: 200)
end
it "does not actually create any agent, event or log" do
sign_in users(:bob)
expect {
post :dry_run, agent: valid_attributes()
}.not_to change {
[users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count]
}
json = JSON.parse(response.body)
expect(json['log']).to be_a(String)
expect(json['events']).to be_a(String)
expect(JSON.parse(json['events']).map(&:class)).to eq([Hash])
expect(json['memory']).to be_a(String)
expect(JSON.parse(json['memory'])).to be_a(Hash)
end
it "does not actually update an agent" do
sign_in users(:bob)
agent = agents(:bob_weather_agent)
expect {
post :dry_run, id: agent, agent: valid_attributes(name: 'New Name')
}.not_to change {
[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)
agent.options['url_from_event'] = '{{ url }}'
agent.save!
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(/^\[\d\d:\d\d:\d\d\] INFO -- : Fetching #{Regexp.quote(url_from_event)}$/)
end
end
describe "DELETE memory" do
it "clears memory of the agent" do
agent = agents(:bob_website_agent)