Răsfoiți Sursa

Merge pull request #939 from cantino/dry_run_with_event

Add support for sending an event in a "Dry Run"
Akinori MUSHA 9 ani în urmă
părinte
comite
67ff37a9f8

+ 61 - 4
app/assets/javascripts/components/utils.js.coffee

@@ -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) =>
@@ -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()

+ 7 - 2
app/concerns/dry_runnable.rb

@@ -1,7 +1,7 @@
 module DryRunnable
   extend ActiveSupport::Concern
 
-  def dry_run!
+  def dry_run!(event = nil)
     @dry_run = true
 
     log = StringIO.new
@@ -13,7 +13,12 @@ module DryRunnable
     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

+ 8 - 2
app/controllers/agents_controller.rb

@@ -37,7 +37,7 @@ class AgentsController < ApplicationController
   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)
@@ -50,7 +50,13 @@ class AgentsController < ApplicationController
     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],

+ 12 - 0
app/helpers/agent_helper.rb

@@ -37,4 +37,16 @@ module AgentHelper
       }.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

+ 1 - 1
app/views/agents/_action_menu.html.erb

@@ -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 %>
 

+ 1 - 1
app/views/agents/_options.erb

@@ -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>

+ 1 - 1
config/routes.rb

@@ -2,7 +2,7 @@ Huginn::Application.routes.draw do
   resources :agents do
     member do
       post :run
-      put :dry_run
+      post :dry_run
       post :handle_details_post
       put :leave_scenario
       delete :remove_events

+ 46 - 17
spec/concerns/dry_runnable_spec.rb

@@ -7,10 +7,22 @@ describe DryRunnable do
     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
@@ -46,21 +58,6 @@ describe DryRunnable do
     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 }
 
@@ -77,4 +74,36 @@ describe DryRunnable do
     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 - 0
spec/controllers/agents_controller_spec.rb

@@ -377,6 +377,19 @@ describe AgentsController do
         [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