1
0
Эх сурвалжийг харах

Merge pull request #777 from cantino/js_agent_can_be_dryrun

Allow JavaScriptAgent to dry run and have inline syntax highlighting
Andrew Cantino 9 жил өмнө
parent
commit
79c89ee115

+ 1 - 1
Gemfile

@@ -86,7 +86,7 @@ gem 'sass-rails',   '~> 5.0'
 gem 'select2-rails', '~> 3.5.4'
 gem 'spectrum-rails'
 gem 'string-scrub'	# for ruby <2.1
-gem 'therubyracer', '~> 0.12.1'
+gem 'therubyracer', '~> 0.12.2'
 gem 'typhoeus', '~> 0.6.3'
 gem 'uglifier', '>= 1.3.0'
 

+ 2 - 2
Gemfile.lock

@@ -427,7 +427,7 @@ GEM
     systemu (2.6.4)
     term-ansicolor (1.3.0)
       tins (~> 1.0)
-    therubyracer (0.12.1)
+    therubyracer (0.12.2)
       libv8 (~> 3.16.14.0)
       ref
     thor (0.19.1)
@@ -565,7 +565,7 @@ DEPENDENCIES
   spring (~> 1.3.0)
   spring-commands-rspec
   string-scrub
-  therubyracer (~> 0.12.1)
+  therubyracer (~> 0.12.2)
   tumblr_client
   twilio-ruby (~> 3.11.5)
   twitter (~> 5.14.0)

+ 4 - 0
app/assets/javascripts/ace.js.coffee

@@ -0,0 +1,4 @@
+#= require ace/ace
+#= require ace/mode-javascript.js
+#= require ace/mode-markdown.js
+#= require ace/mode-coffee.js

+ 35 - 2
app/assets/javascripts/pages/agent-edit-page.js.coffee

@@ -2,6 +2,7 @@ class @AgentEditPage
   constructor: ->
     $("#agent_source_ids").on "change", @showEventDescriptions
     @showCorrectRegionsOnStartup()
+    $("form.agent-form").on "submit", => @updateFromEditors()
 
     $("#agent_name").each ->
       # Select the number suffix if this is a cloned agent.
@@ -17,6 +18,7 @@ class @AgentEditPage
       @handleTypeChange(true)
     else
       @enableDryRunButton()
+      @buildAce()
 
   handleTypeChange: (firstTime) ->
     $(".event-descriptions").html("").hide()
@@ -61,6 +63,7 @@ class @AgentEditPage
           window.jsonEditor = setupJsonEditor()[0]
 
         @enableDryRunButton()
+        @buildAce()
 
         window.initializeFormCompletable()
 
@@ -134,15 +137,45 @@ class @AgentEditPage
       else
         @hideEventCreation()
 
+  buildAce: ->
+    $(".ace-editor").each ->
+      unless $(this).data('initialized')
+        $(this).data('initialized', true)
+        $source = $($(this).data('source')).hide()
+        editor = ace.edit(this)
+        $(this).data('ace-editor', editor)
+        session = editor.getSession()
+        session.setTabSize(2)
+        session.setUseSoftTabs(true)
+        session.setUseWrapMode(false)
+        editor.setTheme("ace/theme/chrome")
+
+        setSyntax = ->
+          switch $("[name='agent[options][language]']").val()
+            when 'JavaScript' then session.setMode("ace/mode/javascript")
+            when 'CoffeeScript' then session.setMode("ace/mode/coffee")
+            else session.setMode("ace/mode/text")
+
+        $("[name='agent[options][language]']").on 'change', setSyntax
+        setSyntax()
+
+        session.setValue($source.val())
+
+  updateFromEditors: ->
+    $(".ace-editor").each ->
+      $source = $($(this).data('source'))
+      $source.val($(this).data('ace-editor').getSession().getValue())
+
   enableDryRunButton: ->
     $(".agent-dry-run-button").prop('disabled', false).off().on "click", @invokeDryRun
 
   disableDryRunButton: ->
     $(".agent-dry-run-button").prop('disabled', true)
 
-  invokeDryRun: (e) ->
+  invokeDryRun: (e) =>
     e.preventDefault()
-    Utils.handleDryRunButton(this)
+    @updateFromEditors()
+    Utils.handleDryRunButton(e.target)
 
 $ ->
   Utils.registerPage(AgentEditPage, forPathsMatching: /^agents/)

+ 26 - 0
app/assets/javascripts/pages/user-credential-page.js.coffee

@@ -0,0 +1,26 @@
+class @UserCredentialPage
+  constructor: ->
+    editor = ace.edit("ace-credential-value")
+    editor.getSession().setTabSize(2)
+    editor.getSession().setUseSoftTabs(true)
+    editor.getSession().setUseWrapMode(false)
+    editor.setTheme("ace/theme/chrome")
+
+    setMode = ->
+      mode = $("#user_credential_mode").val()
+      if mode == 'java_script'
+        editor.getSession().setMode("ace/mode/javascript")
+      else
+        editor.getSession().setMode("ace/mode/text")
+
+    setMode()
+    $("#user_credential_mode").on 'change', setMode
+
+    $textarea = $('#user_credential_credential_value').hide()
+    editor.getSession().setValue($textarea.val())
+
+    $textarea.closest('form').on 'submit', ->
+      $textarea.val(editor.getSession().getValue())
+
+$ ->
+  Utils.registerPage(UserCredentialPage, forPathsMatching: /^user_credentials\/\d+/)

+ 0 - 29
app/assets/javascripts/user_credentials.js.coffee

@@ -1,29 +0,0 @@
-#= require ace/ace
-#= require ace/mode-javascript.js
-#= require ace/mode-markdown.js
-#= require_self
-
-# This is not included in the core application.js bundle.
-
-$ ->
-  editor = ace.edit("ace-credential-value")
-  editor.getSession().setTabSize(2)
-  editor.getSession().setUseSoftTabs(true)
-  editor.getSession().setUseWrapMode(false)
-  editor.setTheme("ace/theme/chrome")
-
-  setMode = ->
-    mode = $("#user_credential_mode").val()
-    if mode == 'java_script'
-      editor.getSession().setMode("ace/mode/javascript")
-    else
-      editor.getSession().setMode("ace/mode/text")
-
-  setMode()
-  $("#user_credential_mode").on 'change', setMode
-
-  $textarea = $('#user_credential_credential_value').hide()
-  editor.getSession().setValue($textarea.val())
-
-  $textarea.closest('form').on 'submit', ->
-    $textarea.val(editor.getSession().getValue())

+ 8 - 2
app/assets/stylesheets/application.css.scss.erb

@@ -179,12 +179,18 @@ span.not-applicable:after {
   cursor: pointer;
 }
 
-// Credentials
+// Credentials and Ace Editor
 
 #ace-credential-value {
   position: relative;
   width: 940px;
-  height: 400px;
+  height: 300px;
+}
+
+.ace-editor {
+  position: relative;
+  width: 550px;
+  height: 300px;
 }
 
 // Disabled

+ 1 - 0
app/concerns/dry_runnable.rb

@@ -60,6 +60,7 @@ module DryRunnable
     def create_event(event_hash)
       if can_create_events?
         @dry_run_results[:events] << event_hash[:payload]
+        events.build({ user: user, expires_at: new_event_expiration_date }.merge(event_hash))
       else
         error "This Agent cannot create events!"
       end

+ 1 - 1
app/concerns/form_configurable.rb

@@ -32,7 +32,7 @@ module FormConfigurable
       options = args.extract_options!.reverse_merge(roles: [], type: :string)
 
       if args.all? { |arg| arg.is_a?(Symbol) }
-        options.assert_valid_keys([:type, :roles, :values])
+        options.assert_valid_keys([:type, :roles, :values, :ace])
       end
 
       if options[:type] == :array && (options[:values].blank? || !options[:values].is_a?(Array))

+ 6 - 0
app/helpers/application_helper.rb

@@ -86,6 +86,12 @@ module ApplicationHelper
     ].join.html_safe, class: "label label-default label-service service-#{service.provider}"
   end
 
+  def load_ace_editor!
+    unless content_for?(:ace_editor_script)
+      content_for :ace_editor_script, javascript_include_tag('ace')
+    end
+  end
+
   def highlighted?(id)
     @highlighted_ranges ||=
       case value = params[:hl].presence

+ 24 - 6
app/models/agents/java_script_agent.rb

@@ -3,6 +3,10 @@ require 'cgi'
 
 module Agents
   class JavaScriptAgent < Agent
+    include FormConfigurable
+
+    can_dry_run!
+
     default_schedule "never"
 
     description <<-MD
@@ -25,6 +29,11 @@ module Agents
       * `this.unescapeHtml(htmlToUnescape)`
     MD
 
+    form_configurable :language, type: :array, values: %w[JavaScript CoffeeScript]
+    form_configurable :code, type: :text, ace: true
+    form_configurable :expected_receive_period_in_days
+    form_configurable :expected_update_period_in_days
+
     def validate_options
       cred_name = credential_referenced_by_code
       if cred_name
@@ -32,6 +41,10 @@ module Agents
       else
         errors.add(:base, "The 'code' option is required") unless options['code'].present?
       end
+
+      if interpolated['language'].present? && !interpolated['language'].downcase.in?(%w[javascript coffeescript])
+        errors.add(:base, "The 'language' must be JavaScript or CoffeeScript")
+      end
     end
 
     def working?
@@ -69,7 +82,7 @@ module Agents
             this.memory('callCount', callCount + 1);
           }
         };
-        
+
         Agent.receive = function() {
           var events = this.incomingEvents();
           for(var i = 0; i < events.length; i++) {
@@ -79,9 +92,10 @@ module Agents
       JS
 
       {
-        "code" => js_code.gsub(/[\n\r\t]/, '').strip,
-        'expected_receive_period_in_days' => "2",
-        'expected_update_period_in_days' => "2"
+        'code' => Utils.unindent(js_code),
+        'language' => 'JavaScript',
+        'expected_receive_period_in_days' => '2',
+        'expected_update_period_in_days' => '2'
       }
     end
 
@@ -107,7 +121,11 @@ module Agents
       context["escapeHtml"] = lambda { |a, x| CGI.escapeHTML(x) }
       context["unescapeHtml"] = lambda { |a, x| CGI.unescapeHTML(x) }
 
-      context.eval(code)
+      if (options['language'] || '').downcase == 'coffeescript'
+        context.eval(CoffeeScript.compile code)
+      else
+        context.eval(code)
+      end
       context.eval("Agent.#{js_function}();")
     end
 
@@ -121,7 +139,7 @@ module Agents
     end
 
     def credential_referenced_by_code
-      interpolated['code'] =~ /\Acredential:(.*)\Z/ && $1
+      (interpolated['code'] || '').strip =~ /\Acredential:(.*)\Z/ && $1
     end
 
     def setup_javascript

+ 1 - 1
app/models/agents/website_agent.rb

@@ -379,7 +379,7 @@ module Agents
       end
     end
 
-    # Wraps Faraday::Utilsa::Headers
+    # Wraps Faraday::Utils::Headers
     class HeaderDrop < LiquidDroppable::Drop
       def before_method(name)
         @object[name.tr('_', '-')]

+ 6 - 1
app/presenters/form_configurable_agent_presenter.rb

@@ -20,7 +20,12 @@ class FormConfigurableAgentPresenter < Decorator
 
     case data[:type]
     when :text
-      @view.text_area_tag "agent[options][#{attribute}]", value, html_options.merge(class: 'form-control', rows: 3)
+      @view.content_tag 'div' do
+        @view.concat @view.text_area_tag("agent[options][#{attribute}]", value, html_options.merge(class: 'form-control', rows: 3))
+        if data[:ace].present?
+          @view.concat @view.content_tag('div', '', class: 'ace-editor', data: { source: "[name='agent[options][#{attribute}]']" })
+        end
+      end
     when :boolean
       @view.content_tag 'div' do
         @view.concat(@view.content_tag('label', class: 'radio-inline') do

+ 6 - 3
app/views/agents/_form.html.erb

@@ -1,3 +1,5 @@
+<% load_ace_editor! %>
+
 <% if @agent.errors.any? %>
   <div class="row well model-errors">
     <h2><%= pluralize(@agent.errors.count, "error") %> prohibited this Agent from being saved:</h2>
@@ -8,9 +10,10 @@
 <% end %>
 
 <%= form_for(@agent,
-             :as => :agent,
-             :url => @agent.new_record? ? agents_path : agent_path(@agent),
-             :method => @agent.new_record? ? "POST" : "PUT") do |f| %>
+             as: :agent,
+             url: @agent.new_record? ? agents_path : agent_path(@agent),
+             method: @agent.new_record? ? "POST" : "PUT",
+             html: { class: 'agent-form' }) do |f| %>
 
   <div class="row">
     <div class="col-md-6">

+ 1 - 0
app/views/layouts/application.html.erb

@@ -9,6 +9,7 @@
     <%= stylesheet_link_tag    "application", :media => "all" %>
     <%= javascript_include_tag "application" %>
     <%= csrf_meta_tags %>
+    <%= yield(:ace_editor_script) %>
     <%= yield(:head) %>
   </head>
   <body>

+ 2 - 2
app/views/user_credentials/_form.html.erb

@@ -1,3 +1,5 @@
+<% load_ace_editor! %>
+
 <%= form_for(@user_credential, :method => @user_credential.new_record? ? "POST" : "PUT") do |f| %>
   <% if @user_credential.errors.any? %>
     <div class="row well">
@@ -40,5 +42,3 @@
     </div>
   </div>
 <% end %>
-
-<%= javascript_include_tag "user_credentials" %>

+ 2 - 0
bin/threaded.rb

@@ -2,6 +2,8 @@ require 'thread'
 require 'huginn_scheduler'
 require 'twitter_stream'
 
+Rails.configuration.cache_classes = true
+
 STDOUT.sync = true
 STDERR.sync = true
 

+ 1 - 1
config/environments/production.rb

@@ -63,7 +63,7 @@ Huginn::Application.configure do
   end
 
   # Precompile additional assets (application.js.coffee.erb, application.css, and all non-JS/CSS are already added)
-  config.assets.precompile += %w( diagram.js graphing.js map_marker.js user_credentials.js )
+  config.assets.precompile += %w( diagram.js graphing.js map_marker.js ace.js )
 
   # Ignore bad email addresses and do not raise email delivery errors.
   # Set this to true and configure the email server for immediate delivery to raise delivery errors.

+ 28 - 2
spec/models/agents/java_script_agent_spec.rb

@@ -23,6 +23,21 @@ describe Agents::JavaScriptAgent do
       expect(@agent).not_to be_valid
     end
 
+    it "checks for a valid 'language', but allows nil" do
+      expect(@agent).to be_valid
+      @agent.options['language'] = ''
+      expect(@agent).to be_valid
+      @agent.options.delete('language')
+      expect(@agent).to be_valid
+      @agent.options['language'] = 'foo'
+      expect(@agent).not_to be_valid
+
+      %w[javascript JavaScript coffeescript CoffeeScript].each do |valid_language|
+        @agent.options['language'] = valid_language
+        expect(@agent).to be_valid
+      end
+    end
+
     it "accepts a credential, but it must exist" do
       expect(@agent).to be_valid
       @agent.options['code'] = 'credential:foo'
@@ -74,11 +89,10 @@ describe Agents::JavaScriptAgent do
       }.to change { Event.count }.by(2)
     end
 
-
     describe "using credentials as code" do
       before do
         @agent.user.user_credentials.create :credential_name => 'code-foo', :credential_value => 'Agent.check = function() { this.log("ran it"); };'
-        @agent.options['code'] = 'credential:code-foo'
+        @agent.options['code'] = "credential:code-foo\n\n"
         @agent.save!
       end
 
@@ -238,5 +252,17 @@ describe Agents::JavaScriptAgent do
         }.not_to change { Event.count }
       end
     end
+
+    describe "using CoffeeScript" do
+      it "will accept a 'language' of 'CoffeeScript'" do
+        @agent.options['code'] = 'Agent.check = -> this.log("hello from coffeescript")'
+        @agent.options['language'] = 'CoffeeScript'
+        @agent.save!
+        expect {
+          @agent.check
+        }.not_to raise_error
+        expect(AgentLog.last.message).to eq("hello from coffeescript")
+      end
+    end
   end
 end