Aucune description http://j1x-huginn.herokuapp.com

java_script_agent.rb 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. require 'date'
  2. require 'cgi'
  3. module Agents
  4. class JavaScriptAgent < Agent
  5. include FormConfigurable
  6. can_dry_run!
  7. default_schedule "never"
  8. description <<-MD
  9. 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!
  10. You can put code in the `code` option, or put your code in a Credential and reference it from `code` with `credential:<name>` (recommended).
  11. You can implement `Agent.check` and `Agent.receive` as you see fit. The following methods will be available on Agent in the JavaScript environment:
  12. * `this.createEvent(payload)`
  13. * `this.incomingEvents()` (the returned event objects will each have a `payload` property)
  14. * `this.memory()`
  15. * `this.memory(key)`
  16. * `this.memory(keyToSet, valueToSet)`
  17. * `this.options()`
  18. * `this.options(key)`
  19. * `this.log(message)`
  20. * `this.error(message)`
  21. MD
  22. form_configurable :language, type: :array, values: %w[JavaScript CoffeeScript]
  23. form_configurable :code, type: :text, ace: true
  24. form_configurable :expected_receive_period_in_days
  25. form_configurable :expected_update_period_in_days
  26. def validate_options
  27. cred_name = credential_referenced_by_code
  28. if cred_name
  29. errors.add(:base, "The credential '#{cred_name}' referenced by code cannot be found") unless credential(cred_name).present?
  30. else
  31. errors.add(:base, "The 'code' option is required") unless options['code'].present?
  32. end
  33. end
  34. def working?
  35. return false if recent_error_logs?
  36. if interpolated['expected_update_period_in_days'].present?
  37. return false unless event_created_within?(interpolated['expected_update_period_in_days'])
  38. end
  39. if interpolated['expected_receive_period_in_days'].present?
  40. return false unless last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago
  41. end
  42. true
  43. end
  44. def check
  45. log_errors do
  46. execute_js("check")
  47. end
  48. end
  49. def receive(incoming_events)
  50. log_errors do
  51. execute_js("receive", incoming_events)
  52. end
  53. end
  54. def default_options
  55. js_code = <<-JS
  56. Agent.check = function() {
  57. if (this.options('make_event')) {
  58. this.createEvent({ 'message': 'I made an event!' });
  59. var callCount = this.memory('callCount') || 0;
  60. this.memory('callCount', callCount + 1);
  61. }
  62. };
  63. Agent.receive = function() {
  64. var events = this.incomingEvents();
  65. for(var i = 0; i < events.length; i++) {
  66. this.createEvent({ 'message': 'I got an event!', 'event_was': events[i].payload });
  67. }
  68. }
  69. JS
  70. {
  71. 'code' => Utils.unindent(js_code),
  72. 'language' => 'JavaScript',
  73. 'expected_receive_period_in_days' => '2',
  74. 'expected_update_period_in_days' => '2'
  75. }
  76. end
  77. private
  78. def execute_js(js_function, incoming_events = [])
  79. js_function = js_function == "check" ? "check" : "receive"
  80. context = V8::Context.new
  81. context.eval(setup_javascript)
  82. context["doCreateEvent"] = lambda { |a, y| create_event(payload: clean_nans(JSON.parse(y))).payload.to_json }
  83. context["getIncomingEvents"] = lambda { |a| incoming_events.to_json }
  84. context["getOptions"] = lambda { |a, x| interpolated.to_json }
  85. context["doLog"] = lambda { |a, x| log x }
  86. context["doError"] = lambda { |a, x| error x }
  87. context["getMemory"] = lambda do |a, x, y|
  88. if x && y
  89. memory[x] = clean_nans(y)
  90. else
  91. memory.to_json
  92. end
  93. end
  94. if (options['language'] || '').downcase == 'coffeescript'
  95. context.eval(CoffeeScript.compile code)
  96. else
  97. context.eval(code)
  98. end
  99. context.eval("Agent.#{js_function}();")
  100. end
  101. def code
  102. cred = credential_referenced_by_code
  103. if cred
  104. credential(cred) || 'Agent.check = function() { this.error("Unable to find credential"); };'
  105. else
  106. interpolated['code']
  107. end
  108. end
  109. def credential_referenced_by_code
  110. interpolated['code'] =~ /\Acredential:(.*)\Z/ && $1
  111. end
  112. def setup_javascript
  113. <<-JS
  114. function Agent() {};
  115. Agent.createEvent = function(opts) {
  116. return JSON.parse(doCreateEvent(JSON.stringify(opts)));
  117. }
  118. Agent.incomingEvents = function() {
  119. return JSON.parse(getIncomingEvents());
  120. }
  121. Agent.memory = function(key, value) {
  122. if (typeof(key) !== "undefined" && typeof(value) !== "undefined") {
  123. getMemory(key, value);
  124. } else if (typeof(key) !== "undefined") {
  125. return JSON.parse(getMemory())[key];
  126. } else {
  127. return JSON.parse(getMemory());
  128. }
  129. }
  130. Agent.options = function(key) {
  131. if (typeof(key) !== "undefined") {
  132. return JSON.parse(getOptions())[key];
  133. } else {
  134. return JSON.parse(getOptions());
  135. }
  136. }
  137. Agent.log = function(message) {
  138. doLog(message);
  139. }
  140. Agent.error = function(message) {
  141. doError(message);
  142. }
  143. Agent.check = function(){};
  144. Agent.receive = function(){};
  145. JS
  146. end
  147. def log_errors
  148. begin
  149. yield
  150. rescue V8::Error => e
  151. error "JavaScript error: #{e.message}"
  152. end
  153. end
  154. def clean_nans(input)
  155. if input.is_a?(Array)
  156. input.map {|v| clean_nans(v) }
  157. elsif input.is_a?(Hash)
  158. input.inject({}) { |m, (k, v)| m[k] = clean_nans(v); m }
  159. elsif input.is_a?(Float) && input.nan?
  160. 'NaN'
  161. else
  162. input
  163. end
  164. end
  165. end
  166. end