Merge pull request #1483 from kreuzwerker/feature/dry-run-events

Show recently received events in dry run modal

Dominik Sander 8 years ago
parent
commit
5832d1f2e5

+ 53 - 78
app/assets/javascripts/components/utils.js.coffee

@@ -43,90 +43,65 @@ class @Utils
43 43
 
44 44
     if with_event_mode is 'no'
45 45
       return @invokeDryRun(url, data, cleanup)
46
+    $.ajax url,
47
+      method: 'GET',
48
+      data:
49
+        with_event_mode: with_event_mode
50
+        source_ids: $.map($(".link-region select option:selected"), (el) -> $(el).val() )
51
+      success: (modal_data) =>
52
+        Utils.showDynamicModal modal_data,
53
+          body: (body) =>
54
+            form = $(body).find('.dry-run-form')
55
+            payload_editor = form.find('.payload-editor')
46 56
 
47
-    Utils.showDynamicModal """
48
-      <h5>Event to send#{if with_event_mode is 'maybe' then ' (Optional)' else ''}</h5>
49
-      <form class="dry-run-form" method="post">
50
-        <div class="form-group">
51
-          <textarea rows="10" name="event" class="payload-editor" data-height="200">
52
-            {}
53
-          </textarea>
54
-        </div>
55
-        <div class="form-group">
56
-          <input value="Dry Run" class="btn btn-primary" type="submit" />
57
-        </div>
58
-      </form>
59
-      """,
60
-      body: (body) =>
61
-        form = $(body).find('.dry-run-form')
62
-        payload_editor = form.find('.payload-editor')
63
-        if previous = $(button).data('payload')
64
-          payload_editor.text(previous)
65
-        window.setupJsonEditor(payload_editor)
66
-        form.submit (e) =>
67
-          e.preventDefault()
68
-          json = $(e.target).find('.payload-editor').val()
69
-          json = '{}' if json == ''
70
-          try
71
-            payload = JSON.parse(json)
72
-            throw true unless payload.constructor is Object
73
-            if Object.keys(payload).length == 0
74
-              json = ''
75
-            else
76
-              json = JSON.stringify(payload)
77
-          catch
78
-            alert 'Invalid JSON object.'
79
-            return
80
-          if json == ''
81
-            if with_event_mode is 'yes'
82
-              alert 'Event is required for this agent to run.'
83
-              return
84
-            dry_run_data = data
85
-            $(button).data('payload', null)
86
-          else
87
-            dry_run_data = "event=#{encodeURIComponent(json)}&#{data}"
88
-            $(button).data('payload', json)
89
-          $(body).closest('[role=dialog]').on 'hidden.bs.modal', =>
90
-            @invokeDryRun(url, dry_run_data, cleanup)
91
-          .modal('hide')
92
-        $(body).closest('[role=dialog]').on 'shown.bs.modal', ->
93
-          $(this).find('.btn-primary').focus()
94
-      title: 'Dry Run'
95
-      onHide: cleanup
57
+            if previous = $(button).data('payload')
58
+              payload_editor.text(previous)
59
+
60
+            editor = window.setupJsonEditor(payload_editor)[0]
61
+
62
+            $(body).find('.dry-run-event-sample').click (e) =>
63
+              e.preventDefault()
64
+              editor.json = $(e.currentTarget).data('payload')
65
+              editor.rebuild()
66
+
67
+            form.submit (e) =>
68
+              e.preventDefault()
69
+              json = $(e.target).find('.payload-editor').val()
70
+              json = '{}' if json == ''
71
+              try
72
+                payload = JSON.parse(json)
73
+                throw true unless payload.constructor is Object
74
+                if Object.keys(payload).length == 0
75
+                  json = ''
76
+                else
77
+                  json = JSON.stringify(payload)
78
+              catch
79
+                alert 'Invalid JSON object.'
80
+                return
81
+              if json == ''
82
+                if with_event_mode is 'yes'
83
+                  alert 'Event is required for this agent to run.'
84
+                  return
85
+                dry_run_data = data
86
+                $(button).data('payload', null)
87
+              else
88
+                dry_run_data = "event=#{encodeURIComponent(json)}&#{data}"
89
+                $(button).data('payload', json)
90
+              $(body).closest('[role=dialog]').on 'hidden.bs.modal', =>
91
+                @invokeDryRun(url, dry_run_data, cleanup)
92
+              .modal('hide')
93
+            $(body).closest('[role=dialog]').on 'shown.bs.modal', ->
94
+              $(this).find('.btn-primary').focus()
95
+          title: 'Dry Run'
96
+          onHide: cleanup
96 97
 
97 98
   @invokeDryRun: (url, data, callback) ->
98 99
     $('body').css(cursor: 'progress')
99
-    $.ajax type: 'POST', url: url, dataType: 'json', data: data
100
+    $.ajax type: 'POST', url: url, dataType: 'html', data: data
100 101
       .always =>
101 102
         $('body').css(cursor: 'auto')
102
-      .done (json) =>
103
-        Utils.showDynamicModal """
104
-          <!-- Nav tabs -->
105
-          <ul id="resultTabs" class="nav nav-tabs agent-dry-run-tabs" role="tablist">
106
-            <li role="presentation"><a href="#tabEvents" aria-controls="tabEvents" role="tab" data-toggle="tab">Events</a></li>
107
-            <li role="presentation"><a href="#tabLog" aria-controls="tabLog" role="tab" data-toggle="tab">Log</a></li>
108
-            <li role="presentation"><a href="#tabMemory" aria-controls="tabMemory" role="tab" data-toggle="tab">Memory</a></li>
109
-          </ul>
110
-          <!-- Tab panes -->
111
-          <div class="tab-content">
112
-            <div role="tabpanel" class="tab-pane" id="tabEvents">
113
-              <pre class="agent-dry-run-events"></pre>
114
-            </div>
115
-            <div role="tabpanel" class="tab-pane" id="tabLog">
116
-              <pre><small class="agent-dry-run-log"></small></pre>
117
-            </div>
118
-            <div role="tabpanel" class="tab-pane" id="tabMemory">
119
-              <pre class="agent-dry-run-memory"></pre>
120
-            </div>
121
-          </div>
122
-          """,
123
-          body: (body) ->
124
-            $(body).
125
-              find('.agent-dry-run-log').text(json.log).end().
126
-              find('.agent-dry-run-events').text(json.events).end().
127
-              find('.agent-dry-run-memory').text(json.memory)
128
-            active = if json.events.match(/^\[?\s*\]?$/) then 'tabLog' else 'tabEvents'
129
-            $('#resultTabs a[href="#' + active + '"]').tab('show')
103
+      .done (modal_data) =>
104
+        Utils.showDynamicModal modal_data,
130 105
           title: 'Dry Run Results',
131 106
           onHide: callback
132 107
       .fail (xhr, status, error) ->

+ 50 - 0
app/controllers/agents/dry_runs_controller.rb

@@ -0,0 +1,50 @@
1
+module Agents
2
+  class DryRunsController < ApplicationController
3
+    include ActionView::Helpers::TextHelper
4
+
5
+    def index
6
+      @events = if params[:agent_id]
7
+                  current_user.agents.find_by(id: params[:agent_id]).received_events.limit(5)
8
+                elsif params[:source_ids]
9
+                  Event.where(agent_id: current_user.agents.where(id: params[:source_ids]).pluck(:id))
10
+                       .order("id DESC").limit(5)
11
+                end
12
+
13
+      render layout: false
14
+    end
15
+
16
+    def create
17
+      attrs = params[:agent] || {}
18
+      if agent = current_user.agents.find_by(id: params[:agent_id])
19
+        # POST /agents/:id/dry_run
20
+        if attrs.present?
21
+          type = agent.type
22
+          agent = Agent.build_for_type(type, current_user, attrs)
23
+        end
24
+      else
25
+        # POST /agents/dry_run
26
+        type = attrs.delete(:type)
27
+        agent = Agent.build_for_type(type, current_user, attrs)
28
+      end
29
+      agent.name ||= '(Untitled)'
30
+
31
+      if agent.valid?
32
+        if event_payload = params[:event]
33
+          dummy_agent = Agent.build_for_type('ManualEventAgent', current_user, name: 'Dry-Runner')
34
+          dummy_agent.readonly!
35
+          event = dummy_agent.events.build(user: current_user, payload: event_payload)
36
+        end
37
+
38
+        @results = agent.dry_run!(event)
39
+      else
40
+        @results = { events: [], memory: [],
41
+                     log:  [
42
+                       "#{pluralize(agent.errors.count, "error")} prohibited this Agent from being saved:",
43
+                       *agent.errors.full_messages
44
+                     ].join("\n- ") }
45
+      end
46
+
47
+      render layout: false
48
+    end
49
+  end
50
+end

+ 0 - 41
app/controllers/agents_controller.rb

@@ -48,47 +48,6 @@ class AgentsController < ApplicationController
48 48
     end
49 49
   end
50 50
 
51
-  def dry_run
52
-    attrs = params[:agent] || {}
53
-    if agent = current_user.agents.find_by(id: params[:id])
54
-      # POST /agents/:id/dry_run
55
-      if attrs.present?
56
-        type = agent.type
57
-        agent = Agent.build_for_type(type, current_user, attrs)
58
-      end
59
-    else
60
-      # POST /agents/dry_run
61
-      type = attrs.delete(:type)
62
-      agent = Agent.build_for_type(type, current_user, attrs)
63
-    end
64
-    agent.name ||= '(Untitled)'
65
-
66
-    if agent.valid?
67
-      if event_payload = params[:event]
68
-        dummy_agent = Agent.build_for_type('ManualEventAgent', current_user, name: 'Dry-Runner')
69
-        dummy_agent.readonly!
70
-        event = dummy_agent.events.build(user: current_user, payload: event_payload)
71
-      end
72
-
73
-      results = agent.dry_run!(event)
74
-
75
-      render json: {
76
-        log: results[:log],
77
-        events: Utils.pretty_print(results[:events], false),
78
-        memory: Utils.pretty_print(results[:memory] || {}, false),
79
-      }
80
-    else
81
-      render json: {
82
-        log: [
83
-          "#{pluralize(agent.errors.count, "error")} prohibited this Agent from being saved:",
84
-          *agent.errors.full_messages
85
-        ].join("\n- "),
86
-        events: '',
87
-        memory: '',
88
-      }
89
-    end
90
-  end
91
-
92 51
   def type_details
93 52
     @agent = Agent.build_for_type(params[:type], current_user, {})
94 53
     initialize_presenter

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

@@ -7,7 +7,7 @@
7 7
 
8 8
   <% if agent.can_dry_run? %>
9 9
     <li>
10
-      <%= 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)" %>
10
+      <%= 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)" %>
11 11
     </li>
12 12
   <% end %>
13 13
 

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

@@ -25,6 +25,6 @@
25 25
 <div class="form-group">
26 26
   <%= submit_tag "Save", :class => "btn btn-primary" %>
27 27
   <% if agent.can_dry_run? %>
28
-    <%= 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 %>
28
+    <%= 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 %>
29 29
   <% end %>
30 30
 </div>

+ 18 - 0
app/views/agents/dry_runs/create.html.erb

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

+ 20 - 0
app/views/agents/dry_runs/index.html.erb

@@ -0,0 +1,20 @@
1
+<% if @events && @events.length > 0 %>
2
+  <h5>Recently received events: </h5>
3
+  <% @events.each do |event| %>
4
+    <%= link_to '#', class: 'dry-run-event-sample', 'data-payload' => event.payload.to_json do %>
5
+      <pre><%= truncate event.payload.to_json, :length => 90, :omission => "" %></pre>
6
+    <% end %>
7
+  <% end %>
8
+<% end %>
9
+
10
+<h5>Event to send<%= params[:with_event_mode] == 'maybe' ? ' (Optional)' : '' %></h5>
11
+<form class="dry-run-form" method="post">
12
+  <div class="form-group">
13
+    <textarea rows="10" name="event" class="payload-editor" data-height="200">
14
+      {}
15
+    </textarea>
16
+  </div>
17
+  <div class="form-group">
18
+    <input value="Dry Run" class="btn btn-primary" type="submit" />
19
+  </div>
20
+</form>

+ 8 - 2
config/routes.rb

@@ -2,7 +2,6 @@ Huginn::Application.routes.draw do
2 2
   resources :agents do
3 3
     member do
4 4
       post :run
5
-      post :dry_run
6 5
       post :handle_details_post
7 6
       put :leave_scenario
8 7
       delete :remove_events
@@ -13,7 +12,6 @@ Huginn::Application.routes.draw do
13 12
       put :toggle_visibility
14 13
       post :propagate
15 14
       get :type_details
16
-      post :dry_run
17 15
       get :event_descriptions
18 16
       post :validate
19 17
       post :complete
@@ -26,6 +24,14 @@ Huginn::Application.routes.draw do
26 24
     end
27 25
 
28 26
     resources :events, :only => [:index]
27
+
28
+    scope module: :agents do
29
+      resources :dry_runs, only: [:index, :create]
30
+    end
31
+  end
32
+
33
+  scope module: :agents do
34
+    resources :dry_runs, only: [:index, :create]
29 35
   end
30 36
 
31 37
   resource :diagram, :only => [:show]

+ 104 - 0
spec/controllers/agents/dry_runs_controller_spec.rb

@@ -0,0 +1,104 @@
1
+require 'rails_helper'
2
+
3
+describe Agents::DryRunsController do
4
+  def valid_attributes(options = {})
5
+    {
6
+      type: "Agents::WebsiteAgent",
7
+      name: "Something",
8
+      options: agents(:bob_website_agent).options,
9
+      source_ids: [agents(:bob_weather_agent).id, ""]
10
+    }.merge(options)
11
+  end
12
+
13
+  before do
14
+    sign_in users(:bob)
15
+  end
16
+
17
+  describe "GET index" do
18
+    it "does not load any events without specifing sources" do
19
+      get :index, type: 'Agents::WebsiteAgent', source_ids: []
20
+      expect(assigns(:events)).to eq([])
21
+    end
22
+
23
+    context "does not load events when the agent is owned by a different user" do
24
+      before do
25
+        @agent = agents(:jane_website_agent)
26
+        @agent.sources << @agent
27
+        @agent.save!
28
+        expect(@agent.events.count).not_to be(0)
29
+      end
30
+
31
+      it "for new agents" do
32
+        get :index, type: 'Agents::WebsiteAgent', source_ids: [@agent.id]
33
+        expect(assigns(:events)).to eq([])
34
+      end
35
+
36
+      it "for existing agents" do
37
+        expect(@agent.events.count).not_to be(0)
38
+        expect { get :index, agent_id: @agent }.to raise_error(NoMethodError)
39
+      end
40
+    end
41
+
42
+    context "loads the most recent events" do
43
+      before do
44
+        @agent = agents(:bob_website_agent)
45
+        @agent.sources << @agent
46
+        @agent.save!
47
+      end
48
+
49
+      it "load the most recent events when providing source ids" do
50
+        get :index, type: 'Agents::WebsiteAgent', source_ids: [@agent.id]
51
+        expect(assigns(:events)).to eq([@agent.events.first])
52
+      end
53
+
54
+      it "loads the most recent events for a saved agent" do
55
+        get :index, agent_id: @agent
56
+        expect(assigns(:events)).to eq([@agent.events.first])
57
+      end
58
+    end
59
+  end
60
+
61
+  describe "POST create" do
62
+    before do
63
+      stub_request(:any, /xkcd/).to_return(body: File.read(Rails.root.join("spec/data_fixtures/xkcd.html")), status: 200)
64
+    end
65
+
66
+    it "does not actually create any agent, event or log" do
67
+      expect {
68
+        post :create, agent: valid_attributes
69
+      }.not_to change {
70
+        [users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count]
71
+      }
72
+      results = assigns(:results)
73
+      expect(results[:log]).to be_a(String)
74
+      expect(results[:log]).to include('Extracting html at')
75
+      expect(results[:events]).to be_a(Array)
76
+      expect(results[:events].length).to eq(1)
77
+      expect(results[:events].map(&:class)).to eq([ActiveSupport::HashWithIndifferentAccess])
78
+      expect(results[:memory]).to be_a(Hash)
79
+    end
80
+
81
+    it "does not actually update an agent" do
82
+      agent = agents(:bob_weather_agent)
83
+      expect {
84
+        post :create, agent_id: agent, agent: valid_attributes(name: 'New Name')
85
+      }.not_to change {
86
+        [users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at]
87
+      }
88
+    end
89
+
90
+    it "accepts an event" do
91
+      agent = agents(:bob_website_agent)
92
+      agent.options['url_from_event'] = '{{ url }}'
93
+      agent.save!
94
+      url_from_event = "http://xkcd.com/?from_event=1".freeze
95
+      expect {
96
+        post :create, agent_id: agent, event: { url: url_from_event }
97
+      }.not_to change {
98
+        [users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at]
99
+      }
100
+      results = assigns(:results)
101
+      expect(results[:log]).to match(/^\[\d\d:\d\d:\d\d\] INFO -- : Fetching #{Regexp.quote(url_from_event)}$/)
102
+    end
103
+  end
104
+end

+ 0 - 46
spec/controllers/agents_controller_spec.rb

@@ -408,52 +408,6 @@ describe AgentsController do
408 408
     end
409 409
   end
410 410
 
411
-  describe "POST dry_run" do
412
-    before do
413
-      stub_request(:any, /xkcd/).to_return(body: File.read(Rails.root.join("spec/data_fixtures/xkcd.html")), status: 200)
414
-    end
415
-
416
-    it "does not actually create any agent, event or log" do
417
-      sign_in users(:bob)
418
-      expect {
419
-        post :dry_run, agent: valid_attributes()
420
-      }.not_to change {
421
-        [users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count]
422
-      }
423
-      json = JSON.parse(response.body)
424
-      expect(json['log']).to be_a(String)
425
-      expect(json['events']).to be_a(String)
426
-      expect(JSON.parse(json['events']).map(&:class)).to eq([Hash])
427
-      expect(json['memory']).to be_a(String)
428
-      expect(JSON.parse(json['memory'])).to be_a(Hash)
429
-    end
430
-
431
-    it "does not actually update an agent" do
432
-      sign_in users(:bob)
433
-      agent = agents(:bob_weather_agent)
434
-      expect {
435
-        post :dry_run, id: agent, agent: valid_attributes(name: 'New Name')
436
-      }.not_to change {
437
-        [users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at]
438
-      }
439
-    end
440
-
441
-    it "accepts an event" do
442
-      sign_in users(:bob)
443
-      agent = agents(:bob_website_agent)
444
-      agent.options['url_from_event'] = '{{ url }}'
445
-      agent.save!
446
-      url_from_event = "http://xkcd.com/?from_event=1".freeze
447
-      expect {
448
-        post :dry_run, id: agent, event: { url: url_from_event }
449
-      }.not_to change {
450
-        [users(:bob).agents.count, users(:bob).events.count, users(:bob).logs.count, agent.name, agent.updated_at]
451
-      }
452
-      json = JSON.parse(response.body)
453
-      expect(json['log']).to match(/^\[\d\d:\d\d:\d\d\] INFO -- : Fetching #{Regexp.quote(url_from_event)}$/)
454
-    end
455
-  end
456
-
457 411
   describe "DELETE memory" do
458 412
     it "clears memory of the agent" do
459 413
       agent = agents(:bob_website_agent)