diff --git a/app/assets/javascripts/components/utils.js.coffee b/app/assets/javascripts/components/utils.js.coffee index 605d47df..445d239e 100644 --- a/app/assets/javascripts/components/utils.js.coffee +++ b/app/assets/javascripts/components/utils.js.coffee @@ -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 """ -
Event to send#{if with_event_mode is 'maybe' then ' (Optional)' else ''}
-
-
- -
-
- -
-
- """, - 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 """ - - - -
-
-

-            
-
-
-
-
-

-            
-
- """, - 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) -> diff --git a/app/controllers/agents/dry_runs_controller.rb b/app/controllers/agents/dry_runs_controller.rb new file mode 100644 index 00000000..190bc99c --- /dev/null +++ b/app/controllers/agents/dry_runs_controller.rb @@ -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 \ No newline at end of file diff --git a/app/controllers/agents_controller.rb b/app/controllers/agents_controller.rb index 3f935869..7adda59f 100644 --- a/app/controllers/agents_controller.rb +++ b/app/controllers/agents_controller.rb @@ -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 diff --git a/app/views/agents/_action_menu.html.erb b/app/views/agents/_action_menu.html.erb index e5fd31c7..80d5274a 100644 --- a/app/views/agents/_action_menu.html.erb +++ b/app/views/agents/_action_menu.html.erb @@ -7,7 +7,7 @@ <% if agent.can_dry_run? %>
  • - <%= 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)" %>
  • <% end %> diff --git a/app/views/agents/_options.erb b/app/views/agents/_options.erb index 4d474787..3bff9071 100644 --- a/app/views/agents/_options.erb +++ b/app/views/agents/_options.erb @@ -25,6 +25,6 @@
    <%= 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 %>
    diff --git a/app/views/agents/dry_runs/create.html.erb b/app/views/agents/dry_runs/create.html.erb new file mode 100644 index 00000000..22494a4e --- /dev/null +++ b/app/views/agents/dry_runs/create.html.erb @@ -0,0 +1,18 @@ + + + +
    +
    +
    <%= Utils.pretty_print(@results[:events], false) %>
    +
    +
    +
    <%= @results[:log] %>
    +
    +
    +
    <%= Utils.pretty_print(@results[:memory], false) %>
    +
    +
    diff --git a/app/views/agents/dry_runs/index.html.erb b/app/views/agents/dry_runs/index.html.erb new file mode 100644 index 00000000..d9e5bf11 --- /dev/null +++ b/app/views/agents/dry_runs/index.html.erb @@ -0,0 +1,20 @@ +<% if @events && @events.length > 0 %> +
    Recently received events:
    + <% @events.each do |event| %> + <%= link_to '#', class: 'dry-run-event-sample', 'data-payload' => event.payload.to_json do %> +
    <%= truncate event.payload.to_json, :length => 90, :omission => "" %>
    + <% end %> + <% end %> +<% end %> + +
    Event to send<%= params[:with_event_mode] == 'maybe' ? ' (Optional)' : '' %>
    +
    +
    + +
    +
    + +
    +
    diff --git a/config/routes.rb b/config/routes.rb index 5cb2e3e2..3cb30969 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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] diff --git a/spec/controllers/agents/dry_runs_controller_spec.rb b/spec/controllers/agents/dry_runs_controller_spec.rb new file mode 100644 index 00000000..ca5ce023 --- /dev/null +++ b/spec/controllers/agents/dry_runs_controller_spec.rb @@ -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 diff --git a/spec/controllers/agents_controller_spec.rb b/spec/controllers/agents_controller_spec.rb index de7432b7..e8f758bf 100644 --- a/spec/controllers/agents_controller_spec.rb +++ b/spec/controllers/agents_controller_spec.rb @@ -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)