From 746c092d51bb32f7f4d11a39f76dd29bea7e60c1 Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Fri, 24 Jan 2014 18:04:31 +0500 Subject: [PATCH 01/15] initial work on the code agent, almost works! --- app/models/agents/code_agent.rb | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 app/models/agents/code_agent.rb diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb new file mode 100644 index 00000000..d9270f22 --- /dev/null +++ b/app/models/agents/code_agent.rb @@ -0,0 +1,38 @@ +require 'date' +require 'cgi' +module Agents + class CodeAgent < Agent + def example_js + <<-H + function Agent(m, e, o, agent){ + this.memory = JSON.parse(m); + this.events = JSON.parse(e); + this.options = JSON.parse(o); + this.agent = JSON.parse(agent); + } + Agent.prototype.print_memory = function(){ + return this.memory; + } + Agent.prototype.run = function(){ + //have access to this.memory, this.events, this.options, and this.agent; + // do computation... + //... + var pd = JSON.stringify({hello: "doctor", dil: "chori"}); + create_event(pd); + } + H + end + def execute_js + context = V8::Context.new + context.eval(example_js) + context.eval(options['code']) # should override the run function. + # + context["create_event"] = lambda {|payload| create_event :payload => {:no => "true"}} + context.eval("a = new Agent(1,1,1,1)") + context.eval("a.run();") + context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} + #cxt["create_event"] = lambda {|this, payload| puts a.inspect} + + end + end +end From afcffeea921c4c9aa73eadd05cc8f6689003dbb7 Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Mon, 27 Jan 2014 19:58:09 +0500 Subject: [PATCH 02/15] working implementation. now the arguments to the js code are passed correctly, the rest is the work of the js code --- app/models/agents/code_agent.rb | 50 ++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb index d9270f22..d1f5557e 100644 --- a/app/models/agents/code_agent.rb +++ b/app/models/agents/code_agent.rb @@ -2,6 +2,39 @@ require 'date' require 'cgi' module Agents class CodeAgent < Agent + description <<-MD + Here is an agent that gives you the ability to specify your own code. We have already provided you + a javascript object that has read access to this agent's memory, events, options and the attributes of the agent. + We also provide you with a method to create events on the server. + You will be provided with an instance of the Agent object in javascript, with access to the above data. + You can create events based on your own logic. + Specifically, you have the following class + + function Agent(m, e, o, agent){ + this.memory = JSON.parse(m); + this.events = JSON.parse(e); + this.options = JSON.parse(o); + this.agent = JSON.parse(agent); + } + Agent.prototype.print_memory = function(){ + return this.memory; + } + Agent.prototype.run = function(){ + //Implement me + // Example create a new event with the following code: + //var new_event = JSON.stringify({key1: "val1", key2: "val2"}); + //create_event(pd); + } + You need to provide the code for the Agent::run function. You code will override any methods already present in the Agent if it has to, and you can use other methods as well with access to the agent properties. You need to at least provide the implementation of Agent.prototype.run so that it can be called periodically, or it can execute when an event happens. + + We will yield control to your implementation in the following way: + + context.eval("a = new Agent(memory, events, options, agent)") + context.eval("a.run();") + + You need to provide the run() implementation, as well as other methods it may need to interact with. + + MD def example_js <<-H function Agent(m, e, o, agent){ @@ -17,22 +50,25 @@ module Agents //have access to this.memory, this.events, this.options, and this.agent; // do computation... //... - var pd = JSON.stringify({hello: "doctor", dil: "chori"}); + var pd = JSON.stringify({memory: this.memory, events: this.events, options: this.options}); create_event(pd); } H end + + def working? + true + end + def execute_js context = V8::Context.new context.eval(example_js) context.eval(options['code']) # should override the run function. - # - context["create_event"] = lambda {|payload| create_event :payload => {:no => "true"}} - context.eval("a = new Agent(1,1,1,1)") - context.eval("a.run();") context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} - #cxt["create_event"] = lambda {|this, payload| puts a.inspect} - + a, m, e, o = [self.attributes.to_json, self.memory.to_json, self.events.to_json, self.options.to_json] + string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" + context.eval(string) + context.eval("a.run();") end end end From 69849732670e2032039eaefe074e3b3d726633be Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Mon, 27 Jan 2014 19:59:12 +0500 Subject: [PATCH 03/15] update Gemfile to include therubyracer gem --- Gemfile | 2 ++ Gemfile.lock | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/Gemfile b/Gemfile index 994cdf90..ce463fc9 100644 --- a/Gemfile +++ b/Gemfile @@ -37,6 +37,8 @@ gem 'twitter-stream', '>=0.1.16' gem 'em-http-request' gem 'weibo_2' +gem 'therubyracer' + platforms :ruby_18 do gem 'system_timer' gem 'fastercsv' diff --git a/Gemfile.lock b/Gemfile.lock index bf373842..47bd01d9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -118,6 +118,7 @@ GEM actionpack (>= 3.0.0) activesupport (>= 3.0.0) kramdown (1.1.0) + libv8 (3.16.14.3) mail (2.5.4) mime-types (~> 1.16) treetop (~> 1.4.8) @@ -168,6 +169,7 @@ GEM rake (10.1.0) rdoc (3.12.2) json (~> 1.4) + ref (1.0.5) rest-client (1.6.7) mime-types (>= 1.16) rr (1.1.2) @@ -218,6 +220,9 @@ GEM system_timer (1.2.4) term-ansicolor (1.2.2) tins (~> 0.8) + therubyracer (0.12.0) + libv8 (~> 3.16.14.0) + ref thor (0.18.1) tilt (1.4.1) tins (0.13.1) @@ -292,6 +297,7 @@ DEPENDENCIES select2-rails shoulda-matchers system_timer + therubyracer twilio-ruby twitter twitter-stream (>= 0.1.16) From 0f1490452c342c580bd5ef6ad1a37e1c1f64e553 Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Tue, 28 Jan 2014 15:02:13 +0500 Subject: [PATCH 04/15] a working code agent --- app/models/agents/code_agent.rb | 41 +++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb index d1f5557e..7f14ebf3 100644 --- a/app/models/agents/code_agent.rb +++ b/app/models/agents/code_agent.rb @@ -2,13 +2,15 @@ require 'date' require 'cgi' module Agents class CodeAgent < Agent + #cannot_receive_events! + #cannot_be_scheduled! description <<-MD Here is an agent that gives you the ability to specify your own code. We have already provided you a javascript object that has read access to this agent's memory, events, options and the attributes of the agent. We also provide you with a method to create events on the server. You will be provided with an instance of the Agent object in javascript, with access to the above data. You can create events based on your own logic. - Specifically, you have the following class + Specifically, you have the following class, lets say, present is a string "js_code". function Agent(m, e, o, agent){ this.memory = JSON.parse(m); @@ -16,9 +18,6 @@ module Agents this.options = JSON.parse(o); this.agent = JSON.parse(agent); } - Agent.prototype.print_memory = function(){ - return this.memory; - } Agent.prototype.run = function(){ //Implement me // Example create a new event with the following code: @@ -29,7 +28,9 @@ module Agents We will yield control to your implementation in the following way: + context.eval(js_code); //this is the code that declares the class Agent, and provides a global create_event method. context.eval("a = new Agent(memory, events, options, agent)") + context.eval(options['code']) context.eval("a.run();") You need to provide the run() implementation, as well as other methods it may need to interact with. @@ -47,11 +48,6 @@ module Agents return this.memory; } Agent.prototype.run = function(){ - //have access to this.memory, this.events, this.options, and this.agent; - // do computation... - //... - var pd = JSON.stringify({memory: this.memory, events: this.events, options: this.options}); - create_event(pd); } H end @@ -70,5 +66,32 @@ module Agents context.eval(string) context.eval("a.run();") end + def check + context = V8::Context.new + context.eval(example_js) + context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} + context.eval(options['code']) # should override the run function. + a, m, e, o = [self.attributes.to_json, self.memory.to_json, "".to_json, self.options.to_json] + string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" + context.eval(string) + context.eval("a.run();") + end + + def receive(incoming_events) + context = V8::Context.new + context.eval(example_js) + context.eval(options['code']) # should override the run function. + context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} + a, m, e, o = [self.attributes.to_json, self.memory.to_json, incoming_events.to_json, self.options.to_json] + string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" + context.eval(string) + context.eval("a.run();") + end + def default_options + js_code = "Agent.prototype.run = function(){ var pd = JSON.stringify({memory: this.memory, events: this.events, options: this.options});create_event(pd); }" + { + code: js_code + } + end end end From ca60b7d4092426232a5338081fda4de5ec9480e7 Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Tue, 28 Jan 2014 15:16:14 +0500 Subject: [PATCH 05/15] code refactor to remove repetition --- app/models/agents/code_agent.rb | 31 +++++++++---------------------- 1 file changed, 9 insertions(+), 22 deletions(-) diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb index 7f14ebf3..e0537266 100644 --- a/app/models/agents/code_agent.rb +++ b/app/models/agents/code_agent.rb @@ -56,37 +56,24 @@ module Agents true end - def execute_js - context = V8::Context.new - context.eval(example_js) - context.eval(options['code']) # should override the run function. - context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} - a, m, e, o = [self.attributes.to_json, self.memory.to_json, self.events.to_json, self.options.to_json] - string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" - context.eval(string) - context.eval("a.run();") - end - def check + def execute_js(incoming_events) context = V8::Context.new context.eval(example_js) context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} context.eval(options['code']) # should override the run function. - a, m, e, o = [self.attributes.to_json, self.memory.to_json, "".to_json, self.options.to_json] - string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" - context.eval(string) - context.eval("a.run();") - end - - def receive(incoming_events) - context = V8::Context.new - context.eval(example_js) - context.eval(options['code']) # should override the run function. - context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} a, m, e, o = [self.attributes.to_json, self.memory.to_json, incoming_events.to_json, self.options.to_json] string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" context.eval(string) context.eval("a.run();") end + def check + execute_js("") + end + + def receive(incoming_events) + execute_js(incoming_events) + end + def default_options js_code = "Agent.prototype.run = function(){ var pd = JSON.stringify({memory: this.memory, events: this.events, options: this.options});create_event(pd); }" { From 7d0f1da488c49b26989306fd39946c128353e696 Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Fri, 31 Jan 2014 17:09:55 +0500 Subject: [PATCH 06/15] intermediate commit to get access to memory using ruby lambda --- app/models/agents/code_agent.rb | 39 ++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb index e0537266..df3c64ab 100644 --- a/app/models/agents/code_agent.rb +++ b/app/models/agents/code_agent.rb @@ -45,7 +45,16 @@ module Agents this.agent = JSON.parse(agent); } Agent.prototype.print_memory = function(){ - return this.memory; + return JSON.stringify(this.memory); + } + Agent.prototype.memry = function(key,value){ + if (typeof(key) != "undefined" && typeof(value) != "undefined") { + this.memory = JSON.parse(access_memory(JSON.stringify(key), JSON.stringify(value))); + return JSON.stringify(this.memory); + } else { + this.memory = JSON.parse(access_memory()); + return JSON.stringify(this.memory); + } } Agent.prototype.run = function(){ } @@ -53,13 +62,32 @@ module Agents end def working? + return false if recent_error_logs? + if options['expected_update_period_in_days'].present? + return false unless event_created_within?(options['expected_update_period_in_days']) + end + if options['expected_receive_period_in_days'].present? + return false unless last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago + end true end + def setter_and_getter_memory(incoming_events = "") + context = V8::Context.new + context.eval(example_js) + context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} + context["access_memory"] = lambda {|a, x, y| x && y ? (memory[x] = y; memory.to_json) : memory.to_json } + context.eval(options['code']) # should override the run function. + a, m, e, o = [self.attributes.to_json, self.memory.to_json, incoming_events.to_json, self.options.to_json] + string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" + context.eval(string) + context.eval("a.memry()") + end def execute_js(incoming_events) context = V8::Context.new context.eval(example_js) context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} + context.eval(options['code']) # should override the run function. a, m, e, o = [self.attributes.to_json, self.memory.to_json, incoming_events.to_json, self.options.to_json] string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" @@ -75,10 +103,11 @@ module Agents end def default_options - js_code = "Agent.prototype.run = function(){ var pd = JSON.stringify({memory: this.memory, events: this.events, options: this.options});create_event(pd); }" - { - code: js_code - } + js_code = "Agent.prototype.run = function(){ var pd = JSON.stringify({memory: this.memory, events: this.events, options: this.options});create_event(pd); }" + { + "code" => js_code, + 'expected_receive_period_in_days' => "2" + } end end end From 0315576a79efa0f12e623280f8a107c10870b822 Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Fri, 31 Jan 2014 17:39:38 +0500 Subject: [PATCH 07/15] now, the memory behaves dynamically, still to remove testing code --- app/models/agents/code_agent.rb | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb index df3c64ab..e09bd156 100644 --- a/app/models/agents/code_agent.rb +++ b/app/models/agents/code_agent.rb @@ -29,7 +29,7 @@ module Agents We will yield control to your implementation in the following way: context.eval(js_code); //this is the code that declares the class Agent, and provides a global create_event method. - context.eval("a = new Agent(memory, events, options, agent)") + context.eval("a = new Agent(events, options, agent)") context.eval(options['code']) context.eval("a.run();") @@ -38,22 +38,18 @@ module Agents MD def example_js <<-H - function Agent(m, e, o, agent){ - this.memory = JSON.parse(m); + function Agent(e, o, agent){ this.events = JSON.parse(e); this.options = JSON.parse(o); this.agent = JSON.parse(agent); } - Agent.prototype.print_memory = function(){ - return JSON.stringify(this.memory); - } - Agent.prototype.memry = function(key,value){ + Agent.prototype.memory = function(key,value){ if (typeof(key) != "undefined" && typeof(value) != "undefined") { - this.memory = JSON.parse(access_memory(JSON.stringify(key), JSON.stringify(value))); - return JSON.stringify(this.memory); + var mem = JSON.parse(access_memory(JSON.stringify(key), JSON.stringify(value))); + return JSON.stringify(mem); } else { - this.memory = JSON.parse(access_memory()); - return JSON.stringify(this.memory); + var mem = JSON.parse(access_memory()); + return JSON.stringify(mem); } } Agent.prototype.run = function(){ @@ -78,20 +74,23 @@ module Agents context["access_memory"] = lambda {|a, x, y| x && y ? (memory[x] = y; memory.to_json) : memory.to_json } context.eval(options['code']) # should override the run function. - a, m, e, o = [self.attributes.to_json, self.memory.to_json, incoming_events.to_json, self.options.to_json] - string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" + a, e, o = [self.attributes.to_json, incoming_events.to_json, self.options.to_json] + string = "a = new Agent('#{e}','#{o}','#{a}');" context.eval(string) - context.eval("a.memry()") + binding.pry + context.eval("a.memory()") end def execute_js(incoming_events) context = V8::Context.new context.eval(example_js) context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} + context["access_memory"] = lambda {|a, x, y| x && y ? (memory[x] = y; memory.to_json) : memory.to_json } context.eval(options['code']) # should override the run function. - a, m, e, o = [self.attributes.to_json, self.memory.to_json, incoming_events.to_json, self.options.to_json] - string = "a = new Agent('#{m}','#{e}','#{o}','#{a}');" + a, e, o = [self.attributes.to_json, incoming_events.to_json, self.options.to_json] + string = "a = new Agent('#{e}','#{o}','#{a}');" context.eval(string) + context.eval("a.memory('5','6')") # set memory for testing context.eval("a.run();") end def check @@ -103,7 +102,7 @@ module Agents end def default_options - js_code = "Agent.prototype.run = function(){ var pd = JSON.stringify({memory: this.memory, events: this.events, options: this.options});create_event(pd); }" + js_code = "Agent.prototype.run = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); }" { "code" => js_code, 'expected_receive_period_in_days' => "2" From e7e3b852c5e3200c10fab3f24128587bbd929845 Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Fri, 31 Jan 2014 17:41:37 +0500 Subject: [PATCH 08/15] remove testing code, no need to now pass memory as parameter, see prev commit --- app/models/agents/code_agent.rb | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb index e09bd156..c9b84565 100644 --- a/app/models/agents/code_agent.rb +++ b/app/models/agents/code_agent.rb @@ -67,19 +67,7 @@ module Agents end true end - def setter_and_getter_memory(incoming_events = "") - context = V8::Context.new - context.eval(example_js) - context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} - context["access_memory"] = lambda {|a, x, y| x && y ? (memory[x] = y; memory.to_json) : memory.to_json } - context.eval(options['code']) # should override the run function. - a, e, o = [self.attributes.to_json, incoming_events.to_json, self.options.to_json] - string = "a = new Agent('#{e}','#{o}','#{a}');" - context.eval(string) - binding.pry - context.eval("a.memory()") - end def execute_js(incoming_events) context = V8::Context.new context.eval(example_js) @@ -90,9 +78,9 @@ module Agents a, e, o = [self.attributes.to_json, incoming_events.to_json, self.options.to_json] string = "a = new Agent('#{e}','#{o}','#{a}');" context.eval(string) - context.eval("a.memory('5','6')") # set memory for testing context.eval("a.run();") end + def check execute_js("") end From c2e27043069c8372ae61628ff9cc731e1fb54f66 Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Sun, 2 Feb 2014 15:39:53 +0500 Subject: [PATCH 09/15] provide both a check method and a receive method in js --- app/models/agents/code_agent.rb | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb index c9b84565..c3abfeda 100644 --- a/app/models/agents/code_agent.rb +++ b/app/models/agents/code_agent.rb @@ -6,7 +6,7 @@ module Agents #cannot_be_scheduled! description <<-MD Here is an agent that gives you the ability to specify your own code. We have already provided you - a javascript object that has read access to this agent's memory, events, options and the attributes of the agent. + a javascript object that has read and write access to this agent's memory, and read access to events, options and the attributes of the agent. We also provide you with a method to create events on the server. You will be provided with an instance of the Agent object in javascript, with access to the above data. You can create events based on your own logic. @@ -24,16 +24,26 @@ module Agents //var new_event = JSON.stringify({key1: "val1", key2: "val2"}); //create_event(pd); } - You need to provide the code for the Agent::run function. You code will override any methods already present in the Agent if it has to, and you can use other methods as well with access to the agent properties. You need to at least provide the implementation of Agent.prototype.run so that it can be called periodically, or it can execute when an event happens. + Agent.prototype.check = function(){ + // Implement me + } + Agent.prototype.receive = function(){ + // Implement me + } + You need to provide the code for the Agent::check and Agent::receive function. You code will override any methods already present in the Agent if it has to, and you can use other methods as well with access to the agent properties. You need to at least provide the implementation of Agent.prototype.check and Agent.prototype.receive so that it can be called periodically, or it can execute when an event happens. We will yield control to your implementation in the following way: context.eval(js_code); //this is the code that declares the class Agent, and provides a global create_event method. context.eval("a = new Agent(events, options, agent)") context.eval(options['code']) - context.eval("a.run();") + If you agent fires periodically { + context.eval("a.check();") + } else if your agent responds when an event happens { + context.eval("a.receive();") + } - You need to provide the run() implementation, as well as other methods it may need to interact with. + If your agent responds to events it receive, you need to implement the receive() method, and if you agent fires periodically, you need to implement check(). If your agent does both, please implement both! MD def example_js @@ -54,6 +64,10 @@ module Agents } Agent.prototype.run = function(){ } + Agent.prototype.check = function(){ + } + Agent.prototype.receive = function(){ + } H end @@ -68,7 +82,8 @@ module Agents true end - def execute_js(incoming_events) + def execute_js(incoming_events, js_function = "check") + js_function = (js_function == "check" ? "check" : "receive") context = V8::Context.new context.eval(example_js) context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} @@ -78,7 +93,8 @@ module Agents a, e, o = [self.attributes.to_json, incoming_events.to_json, self.options.to_json] string = "a = new Agent('#{e}','#{o}','#{a}');" context.eval(string) - context.eval("a.run();") + runner = "a.#{js_function}();" + context.eval(runner) end def check @@ -90,7 +106,7 @@ module Agents end def default_options - js_code = "Agent.prototype.run = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); }" + js_code = "Agent.prototype.run = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); };Agent.prototype.check = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); };Agent.prototype.receive = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); }" { "code" => js_code, 'expected_receive_period_in_days' => "2" From e86e052a713b3d08645080c5742a8885ce1f274d Mon Sep 17 00:00:00 2001 From: "Umar M. Sheikh" Date: Tue, 4 Feb 2014 15:25:31 +0500 Subject: [PATCH 10/15] remove the run method as it is no longer needed, and redo explanation --- app/models/agents/code_agent.rb | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb index c3abfeda..35feb99b 100644 --- a/app/models/agents/code_agent.rb +++ b/app/models/agents/code_agent.rb @@ -12,18 +12,11 @@ module Agents You can create events based on your own logic. Specifically, you have the following class, lets say, present is a string "js_code". - function Agent(m, e, o, agent){ - this.memory = JSON.parse(m); + function Agent(e, o, agent){ this.events = JSON.parse(e); this.options = JSON.parse(o); this.agent = JSON.parse(agent); } - Agent.prototype.run = function(){ - //Implement me - // Example create a new event with the following code: - //var new_event = JSON.stringify({key1: "val1", key2: "val2"}); - //create_event(pd); - } Agent.prototype.check = function(){ // Implement me } @@ -62,8 +55,6 @@ module Agents return JSON.stringify(mem); } } - Agent.prototype.run = function(){ - } Agent.prototype.check = function(){ } Agent.prototype.receive = function(){ @@ -106,10 +97,11 @@ module Agents end def default_options - js_code = "Agent.prototype.run = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); };Agent.prototype.check = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); };Agent.prototype.receive = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); }" + js_code = "Agent.prototype.check = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); };Agent.prototype.receive = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); }" { "code" => js_code, - 'expected_receive_period_in_days' => "2" + 'expected_receive_period_in_days' => "2", + 'expected_update_period_in_days' => "2" } end end From e8260ee61e0e7be2450586606995e8647ced4254 Mon Sep 17 00:00:00 2001 From: Andrew Cantino Date: Sun, 9 Feb 2014 14:08:18 -0800 Subject: [PATCH 11/15] add basic JavaScriptAgent --- app/models/agent.rb | 3 +- app/models/agents/code_agent.rb | 108 ---------- app/models/agents/java_script_agent.rb | 169 ++++++++++++++++ spec/fixtures/events.yml | 4 +- spec/models/agents/java_script_agent_spec.rb | 200 +++++++++++++++++++ 5 files changed, 373 insertions(+), 111 deletions(-) delete mode 100644 app/models/agents/code_agent.rb create mode 100644 app/models/agents/java_script_agent.rb create mode 100644 spec/models/agents/java_script_agent_spec.rb diff --git a/app/models/agent.rb b/app/models/agent.rb index f7e6bbd1..858ad757 100644 --- a/app/models/agent.rb +++ b/app/models/agent.rb @@ -16,7 +16,7 @@ class Agent < ActiveRecord::Base load_types_in "Agents" SCHEDULES = %w[every_2m every_5m every_10m every_30m every_1h every_2h every_5h every_12h every_1d every_2d every_7d - midnight 1am 2am 3am 4am 5am 6am 7am 8am 9am 10am 11am noon 1pm 2pm 3pm 4pm 5pm 6pm 7pm 8pm 9pm 10pm 11pm] + midnight 1am 2am 3am 4am 5am 6am 7am 8am 9am 10am 11am noon 1pm 2pm 3pm 4pm 5pm 6pm 7pm 8pm 9pm 10pm 11pm never] EVENT_RETENTION_SCHEDULES = [["Forever", 0], ["1 day", 1], *([2, 3, 4, 5, 7, 14, 21, 30, 45, 90, 180, 365].map {|n| ["#{n} days", n] })] @@ -296,6 +296,7 @@ class Agent < ActiveRecord::Base # Given a schedule name, run `check` via `bulk_check` on all Agents with that schedule. # This is called by bin/schedule.rb for each schedule in `SCHEDULES`. def run_schedule(schedule) + return if schedule == 'never' types = where(:schedule => schedule).group(:type).pluck(:type) types.each do |type| type.constantize.bulk_check(schedule) diff --git a/app/models/agents/code_agent.rb b/app/models/agents/code_agent.rb deleted file mode 100644 index 35feb99b..00000000 --- a/app/models/agents/code_agent.rb +++ /dev/null @@ -1,108 +0,0 @@ -require 'date' -require 'cgi' -module Agents - class CodeAgent < Agent - #cannot_receive_events! - #cannot_be_scheduled! - description <<-MD - Here is an agent that gives you the ability to specify your own code. We have already provided you - a javascript object that has read and write access to this agent's memory, and read access to events, options and the attributes of the agent. - We also provide you with a method to create events on the server. - You will be provided with an instance of the Agent object in javascript, with access to the above data. - You can create events based on your own logic. - Specifically, you have the following class, lets say, present is a string "js_code". - - function Agent(e, o, agent){ - this.events = JSON.parse(e); - this.options = JSON.parse(o); - this.agent = JSON.parse(agent); - } - Agent.prototype.check = function(){ - // Implement me - } - Agent.prototype.receive = function(){ - // Implement me - } - You need to provide the code for the Agent::check and Agent::receive function. You code will override any methods already present in the Agent if it has to, and you can use other methods as well with access to the agent properties. You need to at least provide the implementation of Agent.prototype.check and Agent.prototype.receive so that it can be called periodically, or it can execute when an event happens. - - We will yield control to your implementation in the following way: - - context.eval(js_code); //this is the code that declares the class Agent, and provides a global create_event method. - context.eval("a = new Agent(events, options, agent)") - context.eval(options['code']) - If you agent fires periodically { - context.eval("a.check();") - } else if your agent responds when an event happens { - context.eval("a.receive();") - } - - If your agent responds to events it receive, you need to implement the receive() method, and if you agent fires periodically, you need to implement check(). If your agent does both, please implement both! - - MD - def example_js - <<-H - function Agent(e, o, agent){ - this.events = JSON.parse(e); - this.options = JSON.parse(o); - this.agent = JSON.parse(agent); - } - Agent.prototype.memory = function(key,value){ - if (typeof(key) != "undefined" && typeof(value) != "undefined") { - var mem = JSON.parse(access_memory(JSON.stringify(key), JSON.stringify(value))); - return JSON.stringify(mem); - } else { - var mem = JSON.parse(access_memory()); - return JSON.stringify(mem); - } - } - Agent.prototype.check = function(){ - } - Agent.prototype.receive = function(){ - } - H - end - - def working? - return false if recent_error_logs? - if options['expected_update_period_in_days'].present? - return false unless event_created_within?(options['expected_update_period_in_days']) - end - if options['expected_receive_period_in_days'].present? - return false unless last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago - end - true - end - - def execute_js(incoming_events, js_function = "check") - js_function = (js_function == "check" ? "check" : "receive") - context = V8::Context.new - context.eval(example_js) - context["create_event"] = lambda {|x,y| puts x; puts y; create_event payload: JSON.parse(y)} - context["access_memory"] = lambda {|a, x, y| x && y ? (memory[x] = y; memory.to_json) : memory.to_json } - - context.eval(options['code']) # should override the run function. - a, e, o = [self.attributes.to_json, incoming_events.to_json, self.options.to_json] - string = "a = new Agent('#{e}','#{o}','#{a}');" - context.eval(string) - runner = "a.#{js_function}();" - context.eval(runner) - end - - def check - execute_js("") - end - - def receive(incoming_events) - execute_js(incoming_events) - end - - def default_options - js_code = "Agent.prototype.check = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); };Agent.prototype.receive = function(){ var pd = JSON.stringify({memory: this.memory(), events: this.events, options: this.options});create_event(pd); }" - { - "code" => js_code, - 'expected_receive_period_in_days' => "2", - 'expected_update_period_in_days' => "2" - } - end - end -end diff --git a/app/models/agents/java_script_agent.rb b/app/models/agents/java_script_agent.rb new file mode 100644 index 00000000..0f26f3bb --- /dev/null +++ b/app/models/agents/java_script_agent.rb @@ -0,0 +1,169 @@ +require 'date' +require 'cgi' + +module Agents + class JavaScriptAgent < Agent + default_schedule "never" + + description <<-MD + This Agent allows you to write code in JavaScript that can create and receive events. If other Agents aren't meeting your needs, try this one! + + At the moment, all code should be written in the `code` option. In the future, a full editor will be provided. + + You can implement `Agent.check` and `Agent.receive` as you see fit. The following methods will be available on Agent in the JavaScript environment: + + * `this.createEvent(payload)` + * `this.incomingEvents()` + * `this.memory()` + * `this.memory(key)` + * `this.memory(keyToSet, valueToSet)` + * `this.options()` + * `this.options(key)` + * `this.log(message)` + * `this.error(message)` + + MD + + def validate_options + errors.add(:base, "The 'code' option is required") unless options['code'].present? + end + + def working? + return false if recent_error_logs? + + if options['expected_update_period_in_days'].present? + return false unless event_created_within?(options['expected_update_period_in_days']) + end + + if options['expected_receive_period_in_days'].present? + return false unless last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago + end + + true + end + + def check + log_errors do + execute_js("check") + end + end + + def receive(incoming_events) + log_errors do + execute_js("receive", incoming_events) + end + end + + def default_options + js_code = <<-JS + Agent.check = function() { + if (this.options('make_event')) { + this.createEvent({ 'message': 'I made an event!' }); + var callCount = this.memory('callCount') || 0; + this.memory('callCount', callCount + 1); + } + }; + + Agent.receive = function() { + var events = this.incomingEvents(); + for(var i = 0; i < events.length; i++) { + this.createEvent({ 'message': 'I got an event!', 'event_was': events[i].payload }); + } + } + JS + + { + "code" => js_code.gsub(/[\n\r\t]/, '').strip, + 'expected_receive_period_in_days' => "2", + 'expected_update_period_in_days' => "2" + } + end + + private + + def execute_js(js_function, incoming_events = []) + js_function = js_function == "check" ? "check" : "receive" + context = V8::Context.new + context.eval(setup_javascript) + + context["doCreateEvent"] = lambda { |a, y| create_event(payload: clean_nans(JSON.parse(y))).payload.to_json } + context["getIncomingEvents"] = lambda { |a| incoming_events.to_json } + context["getOptions"] = lambda { |a, x| options.to_json } + context["doLog"] = lambda { |a, x| log x } + context["doError"] = lambda { |a, x| error x } + context["getMemory"] = lambda do |a, x, y| + if x && y + memory[x] = clean_nans(y) + else + memory.to_json + end + end + + context.eval(options['code']) + context.eval("Agent.#{js_function}();") + end + + def setup_javascript + <<-JS + function Agent() {}; + + Agent.createEvent = function(opts) { + return JSON.parse(doCreateEvent(JSON.stringify(opts))); + } + + Agent.incomingEvents = function() { + return JSON.parse(getIncomingEvents()); + } + + Agent.memory = function(key, value) { + if (typeof(key) !== "undefined" && typeof(value) !== "undefined") { + getMemory(key, value); + } else if (typeof(key) !== "undefined") { + return JSON.parse(getMemory())[key]; + } else { + return JSON.parse(getMemory()); + } + } + + Agent.options = function(key) { + if (typeof(key) !== "undefined") { + return JSON.parse(getOptions())[key]; + } else { + return JSON.parse(getOptions()); + } + } + + Agent.log = function(message) { + doLog(message); + } + + Agent.error = function(message) { + doError(message); + } + + Agent.check = function(){}; + Agent.receive = function(){}; + JS + end + + def log_errors + begin + yield + rescue V8::Error => e + error "JavaScript error: #{e.message}" + end + end + + def clean_nans(input) + if input.is_a?(Array) + input.map {|v| clean_nans(v) } + elsif input.is_a?(Hash) + input.inject({}) { |m, (k, v)| m[k] = clean_nans(v); m } + elsif input.is_a?(Float) && input.nan? + 'NaN' + else + input + end + end + end +end diff --git a/spec/fixtures/events.yml b/spec/fixtures/events.yml index 4ed2e86d..20104e05 100644 --- a/spec/fixtures/events.yml +++ b/spec/fixtures/events.yml @@ -1,9 +1,9 @@ bob_website_agent_event: user: bob agent: bob_website_agent - payload: <%= [{ :title => "foo", :url => "http://foo.com" }].to_json.inspect %> + payload: <%= { :title => "foo", :url => "http://foo.com" }.to_json.inspect %> jane_website_agent_event: user: jane agent: jane_website_agent - payload: <%= [{ :title => "foo", :url => "http://foo.com" }].to_json.inspect %> + payload: <%= { :title => "foo", :url => "http://foo.com" }.to_json.inspect %> diff --git a/spec/models/agents/java_script_agent_spec.rb b/spec/models/agents/java_script_agent_spec.rb new file mode 100644 index 00000000..14ac5bf9 --- /dev/null +++ b/spec/models/agents/java_script_agent_spec.rb @@ -0,0 +1,200 @@ +require 'spec_helper' + +describe Agents::JavaScriptAgent do + before do + @valid_params = { + :name => "somename", + :options => { + :code => "Agent.check = function() { this.createEvent({ 'message': 'hi' }); };", + } + } + + @agent = Agents::JavaScriptAgent.new(@valid_params) + @agent.user = users(:jane) + @agent.save! + end + + describe "validations" do + it "requires 'code'" do + @agent.should be_valid + @agent.options['code'] = '' + @agent.should_not be_valid + @agent.options.delete('code') + @agent.should_not be_valid + end + end + + describe "#working?" do + describe "when expected_update_period_in_days is set" do + it "returns false when more than expected_update_period_in_days have passed since the last event creation" do + @agent.options['expected_update_period_in_days'] = 1 + @agent.save! + @agent.should_not be_working + @agent.check + @agent.reload.should be_working + three_days_from_now = 3.days.from_now + stub(Time).now { three_days_from_now } + @agent.should_not be_working + end + end + + describe "when expected_receive_period_in_days is set" do + it "returns false when more than expected_receive_period_in_days have passed since the last event was received" do + @agent.options['expected_receive_period_in_days'] = 1 + @agent.save! + @agent.should_not be_working + Agents::JavaScriptAgent.async_receive @agent.id, [events(:bob_website_agent_event).id] + @agent.reload.should be_working + two_days_from_now = 2.days.from_now + stub(Time).now { two_days_from_now } + @agent.reload.should_not be_working + end + end + end + + describe "executing code" do + it "works by default" do + @agent.options = @agent.default_options + @agent.options['make_event'] = true; + @agent.save! + + lambda { + lambda { + @agent.receive([events(:bob_website_agent_event)]) + @agent.check + }.should_not change { AgentLog.count } + }.should change { Event.count }.by(2) + end + + describe "error handling" do + it "should log an error when V8 has issues" do + @agent.options['code'] = 'syntax error!' + @agent.save! + lambda { + lambda { + @agent.check + }.should_not raise_error + }.should change { AgentLog.count }.by(1) + AgentLog.last.message.should =~ /Unexpected identifier/ + AgentLog.last.level.should == 4 + end + + it "should log an error when JavaScript throws" do + @agent.options['code'] = 'Agent.check = function() { throw "oh no"; };' + @agent.save! + lambda { + lambda { + @agent.check + }.should_not raise_error + }.should change { AgentLog.count }.by(1) + AgentLog.last.message.should =~ /oh no/ + AgentLog.last.level.should == 4 + end + + it "won't store NaNs" do + @agent.options['code'] = 'Agent.check = function() { this.memory("foo", NaN); };' + @agent.save! + @agent.check + @agent.memory['foo'].should == 'NaN' # string + @agent.save! + lambda { @agent.reload.memory }.should_not raise_error + end + end + + describe "creating events" do + it "creates events with this.createEvent in the JavaScript environment" do + @agent.options['code'] = 'Agent.check = function() { this.createEvent({ message: "This is an event!", stuff: { foo: 5 } }); };' + @agent.save! + lambda { + lambda { + @agent.check + }.should_not change { AgentLog.count } + }.should change { Event.count }.by(1) + created_event = @agent.events.last + created_event.payload.should == { 'message' => "This is an event!", 'stuff' => { 'foo' => 5 } } + end + end + + describe "logging" do + it "can output AgentLogs with this.log and this.error in the JavaScript environment" do + @agent.options['code'] = 'Agent.check = function() { this.log("woah"); this.error("WOAH!"); };' + @agent.save! + lambda { + lambda { + @agent.check + }.should_not raise_error + }.should change { AgentLog.count }.by(2) + + log1, log2 = AgentLog.last(2) + + log1.message.should == "woah" + log1.level.should == 3 + log2.message.should == "WOAH!" + log2.level.should == 4 + end + end + + describe "getting incoming events" do + it "can access incoming events in the JavaScript enviroment via this.incomingEvents" do + event = Event.new + event.agent = agents(:bob_rain_notifier_agent) + event.payload = { :data => "Something you should know about" } + event.save! + event.reload + + @agent.options['code'] = <<-JS + Agent.receive = function() { + var events = this.incomingEvents(); + for(var i = 0; i < events.length; i++) { + this.createEvent({ 'message': 'I got an event!', 'event_was': events[i].payload }); + } + } + JS + + @agent.save! + lambda { + lambda { + @agent.receive([events(:bob_website_agent_event), event]) + }.should_not change { AgentLog.count } + }.should change { Event.count }.by(2) + created_event = @agent.events.first + created_event.payload.should == { 'message' => "I got an event!", 'event_was' => { 'data' => "Something you should know about" } } + end + end + + describe "getting and setting memory, getting options" do + it "can access options via this.options and work with memory via this.memory" do + @agent.options['code'] = <<-JS + Agent.check = function() { + if (this.options('make_event')) { + var callCount = this.memory('callCount') || 0; + this.memory('callCount', callCount + 1); + } + }; + JS + + @agent.save! + + lambda { + lambda { + + @agent.check + @agent.memory['callCount'].should_not be_present + + @agent.options['make_event'] = true + @agent.check + @agent.memory['callCount'].should == 1 + + @agent.check + @agent.memory['callCount'].should == 2 + + @agent.memory['callCount'] = 20 + @agent.check + @agent.memory['callCount'].should == 21 + + }.should_not change { AgentLog.count } + }.should_not change { Event.count } + end + end + end +end \ No newline at end of file From d52f8e7d4a346886790d0e6b75abd96be11a97b8 Mon Sep 17 00:00:00 2001 From: Andrew Cantino Date: Sun, 9 Feb 2014 14:14:25 -0800 Subject: [PATCH 12/15] add spec about "never" schedule --- spec/models/agent_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/models/agent_spec.rb b/spec/models/agent_spec.rb index 01925142..e68b863c 100644 --- a/spec/models/agent_spec.rb +++ b/spec/models/agent_spec.rb @@ -25,6 +25,12 @@ describe Agent do do_not_allow(Agents::WebsiteAgent).async_check Agent.run_schedule("blah") end + + it "will not run the 'never' schedule" do + agents(:bob_weather_agent).update_attribute 'schedule', 'never' + do_not_allow(Agents::WebsiteAgent).async_check + Agent.run_schedule("never") + end end describe "credential" do From 62b60d14137c5b0365f25c65af0629af2c078a61 Mon Sep 17 00:00:00 2001 From: Andrew Cantino Date: Sun, 9 Feb 2014 21:41:26 -0800 Subject: [PATCH 13/15] allow credentials to provide the code --- app/models/agents/java_script_agent.rb | 23 +++++++++++++-- spec/models/agents/java_script_agent_spec.rb | 30 +++++++++++++++++++- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/app/models/agents/java_script_agent.rb b/app/models/agents/java_script_agent.rb index 0f26f3bb..c5c1351a 100644 --- a/app/models/agents/java_script_agent.rb +++ b/app/models/agents/java_script_agent.rb @@ -21,11 +21,15 @@ module Agents * `this.options(key)` * `this.log(message)` * `this.error(message)` - MD def validate_options - errors.add(:base, "The 'code' option is required") unless options['code'].present? + cred_name = credential_referenced_by_code + if cred_name + errors.add(:base, "The credential '#{cred_name}' referenced by code cannot be found") unless credential(cred_name).present? + else + errors.add(:base, "The 'code' option is required") unless options['code'].present? + end end def working? @@ -99,10 +103,23 @@ module Agents end end - context.eval(options['code']) + context.eval(code) context.eval("Agent.#{js_function}();") end + def code + cred = credential_referenced_by_code + if cred + credential(cred) || 'Agent.check = function() { this.error("Unable to find credential"); };' + else + options['code'] + end + end + + def credential_referenced_by_code + options['code'] =~ /\Acredential:(.*)\Z/ && $1 + end + def setup_javascript <<-JS function Agent() {}; diff --git a/spec/models/agents/java_script_agent_spec.rb b/spec/models/agents/java_script_agent_spec.rb index 14ac5bf9..687c3b69 100644 --- a/spec/models/agents/java_script_agent_spec.rb +++ b/spec/models/agents/java_script_agent_spec.rb @@ -22,6 +22,14 @@ describe Agents::JavaScriptAgent do @agent.options.delete('code') @agent.should_not be_valid end + + it "accepts a credential, but it must exist" do + @agent.should be_valid + @agent.options['code'] = 'credential:foo' + @agent.should_not be_valid + users(:jane).user_credentials.create! :credential_name => "foo", :credential_value => "bar" + @agent.reload.should be_valid + end end describe "#working?" do @@ -55,7 +63,7 @@ describe Agents::JavaScriptAgent do describe "executing code" do it "works by default" do @agent.options = @agent.default_options - @agent.options['make_event'] = true; + @agent.options['make_event'] = true @agent.save! lambda { @@ -66,6 +74,26 @@ describe Agents::JavaScriptAgent do }.should 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.save! + end + + it "accepts credentials" do + @agent.check + AgentLog.last.message.should == "ran it" + end + + it "logs an error when the credential goes away" do + @agent.user.user_credentials.delete_all + @agent.reload.check + AgentLog.last.message.should == "Unable to find credential" + end + end + describe "error handling" do it "should log an error when V8 has issues" do @agent.options['code'] = 'syntax error!' From c043a4086884af9639cd8c3b56a1342bd3991d0b Mon Sep 17 00:00:00 2001 From: Andrew Cantino Date: Sun, 9 Feb 2014 23:16:52 -0800 Subject: [PATCH 14/15] add Ace editor for editing JavaScript in credentials --- Gemfile | 1 + Gemfile.lock | 2 ++ .../javascripts/user_credentials.js.coffee | 27 +++++++++++++++++++ .../stylesheets/application.css.scss.erb | 8 ++++++ app/models/user_credential.rb | 10 ++++++- app/views/agents/_form.html.erb | 1 - app/views/user_credentials/_form.html.erb | 10 +++++++ config/environments/production.rb | 2 +- ...0210062747_add_mode_to_user_credentials.rb | 5 ++++ db/schema.rb | 13 ++++----- spec/fixtures/user_credentials.yml | 4 +++ 11 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 app/assets/javascripts/user_credentials.js.coffee create mode 100644 db/migrate/20140210062747_add_mode_to_user_credentials.rb diff --git a/Gemfile b/Gemfile index a91a469f..c5b7f791 100644 --- a/Gemfile +++ b/Gemfile @@ -24,6 +24,7 @@ gem 'coffee-rails', '~> 3.2.1' gem 'uglifier', '>= 1.0.3' gem 'select2-rails' gem 'jquery-rails' +gem 'ace-rails-ap' gem 'geokit-rails3' gem 'kramdown' diff --git a/Gemfile.lock b/Gemfile.lock index a8e3efa6..ec92c4c6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,7 @@ GEM remote: https://rubygems.org/ specs: + ace-rails-ap (2.0.1) actionmailer (3.2.13) actionpack (= 3.2.13) mail (~> 2.5.3) @@ -272,6 +273,7 @@ PLATFORMS ruby DEPENDENCIES + ace-rails-ap better_errors binding_of_caller bootstrap-kaminari-views diff --git a/app/assets/javascripts/user_credentials.js.coffee b/app/assets/javascripts/user_credentials.js.coffee new file mode 100644 index 00000000..bad5812e --- /dev/null +++ b/app/assets/javascripts/user_credentials.js.coffee @@ -0,0 +1,27 @@ +#= require ace/ace +#= require ace/mode-javascript.js +#= require ace/mode-markdown.js +#= require_self + +$ -> + 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()) \ No newline at end of file diff --git a/app/assets/stylesheets/application.css.scss.erb b/app/assets/stylesheets/application.css.scss.erb index 588debf3..7ada6293 100644 --- a/app/assets/stylesheets/application.css.scss.erb +++ b/app/assets/stylesheets/application.css.scss.erb @@ -126,3 +126,11 @@ span.not-applicable:after { #show-tabs li a.recent-errors { font-weight: bold; } + +// Credentials + +#ace-credential-value { + position: relative; + width: 940px; + height: 400px; +} \ No newline at end of file diff --git a/app/models/user_credential.rb b/app/models/user_credential.rb index 7cf78d9f..1858ac6d 100644 --- a/app/models/user_credential.rb +++ b/app/models/user_credential.rb @@ -1,13 +1,17 @@ class UserCredential < ActiveRecord::Base - attr_accessible :credential_name, :credential_value + MODES = %w[text java_script] + + attr_accessible :credential_name, :credential_value, :mode belongs_to :user validates_presence_of :credential_name validates_presence_of :credential_value + validates_inclusion_of :mode, :in => MODES validates_presence_of :user_id validates_uniqueness_of :credential_name, :scope => :user_id + before_validation :default_mode_to_text before_save :trim_fields protected @@ -16,4 +20,8 @@ class UserCredential < ActiveRecord::Base credential_name.strip! credential_value.strip! end + + def default_mode_to_text + self.mode = 'text' unless mode.present? + end end diff --git a/app/views/agents/_form.html.erb b/app/views/agents/_form.html.erb index ea5df988..f1491542 100644 --- a/app/views/agents/_form.html.erb +++ b/app/views/agents/_form.html.erb @@ -44,7 +44,6 @@ - +
+ <%= f.label :mode, :class => 'control-label' %> +
+ <%= f.select :mode, options_for_select(UserCredential::MODES.map {|s| [s.classify, s] }, @user_credential.mode), {}, :class => 'span4' %> +
+
+
<%= f.label :credential_value, :class => 'control-label' %>
<%= f.text_area :credential_value, :class => 'span8', :rows => 10 %> +
@@ -28,3 +36,5 @@ <%= f.submit "Save Credential", :class => "btn btn-primary" %> <% end %> + +<%= javascript_include_tag "user_credentials" %> diff --git a/config/environments/production.rb b/config/environments/production.rb index dfc920aa..05cc1c25 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -48,7 +48,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( graphing.js ) + config.assets.precompile += %w( graphing.js user_credentials.js ) # Enable threaded mode # config.threadsafe! diff --git a/db/migrate/20140210062747_add_mode_to_user_credentials.rb b/db/migrate/20140210062747_add_mode_to_user_credentials.rb new file mode 100644 index 00000000..8b30a47d --- /dev/null +++ b/db/migrate/20140210062747_add_mode_to_user_credentials.rb @@ -0,0 +1,5 @@ +class AddModeToUserCredentials < ActiveRecord::Migration + def change + add_column :user_credentials, :mode, :string, :default => 'text', :null => false + end +end diff --git a/db/schema.rb b/db/schema.rb index d265a3a6..a2b2ea37 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140127164931) do +ActiveRecord::Schema.define(:version => 20140210062747) do create_table "agent_logs", :force => true do |t| t.integer "agent_id", :null => false @@ -96,11 +96,12 @@ ActiveRecord::Schema.define(:version => 20140127164931) do add_index "links", ["source_id", "receiver_id"], :name => "index_links_on_source_id_and_receiver_id" create_table "user_credentials", :force => true do |t| - t.integer "user_id", :null => false - t.string "credential_name", :null => false - t.text "credential_value", :null => false - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.integer "user_id", :null => false + t.string "credential_name", :null => false + t.text "credential_value", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "mode", :default => "text", :null => false end add_index "user_credentials", ["user_id", "credential_name"], :name => "index_user_credentials_on_user_id_and_credential_name", :unique => true diff --git a/spec/fixtures/user_credentials.yml b/spec/fixtures/user_credentials.yml index 4f250315..1492d2b6 100644 --- a/spec/fixtures/user_credentials.yml +++ b/spec/fixtures/user_credentials.yml @@ -2,15 +2,19 @@ bob_aws_key: user: bob credential_name: aws_key credential_value: 2222222222-bob + mode: text bob_aws_secret: user: bob credential_name: aws_secret credential_value: 1111111111-bob + mode: text jane_aws_key: user: jane credential_name: aws_key credential_value: 2222222222-jane + mode: text jane_aws_secret: user: jane credential_name: aws_secret credential_value: 1111111111-jabe + mode: text \ No newline at end of file From 457020a0aba25c8bdcfe883d94880e0e0d7016c7 Mon Sep 17 00:00:00 2001 From: Andrew Cantino Date: Tue, 11 Feb 2014 22:55:40 -0800 Subject: [PATCH 15/15] update doc [ci skip] --- app/models/agents/java_script_agent.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/agents/java_script_agent.rb b/app/models/agents/java_script_agent.rb index c5c1351a..7f4a37cf 100644 --- a/app/models/agents/java_script_agent.rb +++ b/app/models/agents/java_script_agent.rb @@ -8,7 +8,7 @@ module Agents description <<-MD This Agent allows you to write code in JavaScript that can create and receive events. If other Agents aren't meeting your needs, try this one! - At the moment, all code should be written in the `code` option. In the future, a full editor will be provided. + You can put code in the `code` option, or put your code in a Credential and reference it from `code` with `credential:` (recommended). You can implement `Agent.check` and `Agent.receive` as you see fit. The following methods will be available on Agent in the JavaScript environment: