mirror of
https://github.com/Fishwaldo/huginn.git
synced 2025-03-15 19:31:26 +00:00
Use select2 for type selection; switch to showing JSON for event information
This commit is contained in:
parent
306237c306
commit
86b5b1f203
23 changed files with 373 additions and 402 deletions
|
@ -49,7 +49,7 @@ showEventDescriptions = ->
|
|||
|
||||
$(document).ready ->
|
||||
setupJsonEditor()
|
||||
$(".multi-select").select2(width: 'resolve')
|
||||
$(".select2").select2(width: 'resolve')
|
||||
|
||||
if $(".top-flash").length
|
||||
setTimeout((-> $(".top-flash").slideUp(-> $(".top-flash").remove())), 5000)
|
||||
|
@ -58,8 +58,8 @@ $(document).ready ->
|
|||
|
||||
$("#agent_type").on "change", ->
|
||||
if window.jsonEditor?
|
||||
$(@).closest(".control-group").find(".spinner").fadeIn();
|
||||
$("#agent_source_ids ").select2("val", {});
|
||||
$(".spinner").fadeIn();
|
||||
$("#agent_source_ids").select2("val", {});
|
||||
$(".event-descriptions").html("").hide()
|
||||
$.getJSON "/agents/type_details", { type: $(@).val() }, (json) =>
|
||||
if json.can_be_scheduled
|
||||
|
@ -77,7 +77,7 @@ $(document).ready ->
|
|||
window.jsonEditor.json = json.options
|
||||
window.jsonEditor.rebuild()
|
||||
|
||||
$(@).closest(".control-group").find(".spinner").stop(true, true).fadeOut();
|
||||
$(".spinner").stop(true, true).fadeOut();
|
||||
|
||||
$("#agent_type").change() if $("#agent_type").length
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ table.events {
|
|||
}
|
||||
}
|
||||
|
||||
.multi-select {
|
||||
.select2 {
|
||||
float: none !important;
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
@ -65,13 +65,8 @@ img.odin {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.type-select {
|
||||
width: 275px;
|
||||
|
||||
img.spinner {
|
||||
display: none;
|
||||
float: right;
|
||||
}
|
||||
img.spinner {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
|
|
|
@ -14,13 +14,17 @@ module Agents
|
|||
event_description <<-MD
|
||||
If flights are present then events look like:
|
||||
|
||||
{ "cost" : 75.23,
|
||||
"date" : "June 25, 2013",
|
||||
"route" : "New York to Chicago" }
|
||||
{
|
||||
"cost": 75.23,
|
||||
"date": "June 25, 2013",
|
||||
"route": "New York to Chicago"
|
||||
}
|
||||
|
||||
otherwise
|
||||
|
||||
{ "nonetodest" : "No flights found to the specified destination" }
|
||||
{
|
||||
"nonetodest": "No flights found to the specified destination"
|
||||
}
|
||||
MD
|
||||
|
||||
def default_options
|
||||
|
|
|
@ -44,9 +44,7 @@ module Agents
|
|||
}
|
||||
MD
|
||||
|
||||
event_description <<-MD
|
||||
User defined
|
||||
MD
|
||||
event_description "User defined"
|
||||
|
||||
def validate_options
|
||||
errors.add(:base, "instructions, mode, skip_agent, and skip_created_at all need to be present.") unless options[:instructions].present? and options[:mode].present? and options[:skip_agent].present? and options[:skip_created_at].present?
|
||||
|
|
|
@ -17,9 +17,14 @@ module Agents
|
|||
MD
|
||||
|
||||
event_description <<-MD
|
||||
Events look like this:
|
||||
Events look like:
|
||||
|
||||
{ :message => "Your message", :peak => 6, :peak_time => 3456789242, :grouped_by => "something" }
|
||||
{
|
||||
"message": "Your message",
|
||||
"peak": 6,
|
||||
"peak_time": 3456789242,
|
||||
"grouped_by": "something"
|
||||
}
|
||||
MD
|
||||
|
||||
def validate_options
|
||||
|
@ -69,13 +74,13 @@ module Agents
|
|||
if newest_value < second_newest_value && second_newest_value > average_value + std_multiple * standard_deviation
|
||||
memory[:peaks][group] << second_newest_time
|
||||
memory[:peaks][group].reject! { |p| p <= second_newest_time - window_duration }
|
||||
create_event :payload => { :message => options[:message], :peak => second_newest_value, :peak_time => second_newest_time, :grouped_by => group.to_s }
|
||||
create_event :payload => {:message => options[:message], :peak => second_newest_value, :peak_time => second_newest_time, :grouped_by => group.to_s}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stats_for(group, options = {})
|
||||
data = memory[:data][group].map {|d| d.first.to_f }
|
||||
data = memory[:data][group].map { |d| d.first.to_f }
|
||||
data = data[0...(memory[:data][group].length - (options[:skip_last] || 0))]
|
||||
length = data.length.to_f
|
||||
mean = 0
|
||||
|
|
|
@ -1,43 +1,41 @@
|
|||
module Agents
|
||||
class PostAgent < Agent
|
||||
cannot_be_scheduled!
|
||||
class PostAgent < Agent
|
||||
cannot_be_scheduled!
|
||||
|
||||
description <<-MD
|
||||
Post Agent receives events from other agents and send those events as the contents of a post request to a specified url. `post_url` field must specify where you would like to receive post requests and do not forget to include URI scheme(`http` or `https`)
|
||||
MD
|
||||
description <<-MD
|
||||
Post Agent receives events from other agents and send those events as the contents of a post request to a specified url. `post_url` field must specify where you would like to receive post requests and do not forget to include URI scheme (`http` or `https`)
|
||||
MD
|
||||
|
||||
event_description <<-MD
|
||||
Does not produce any event.
|
||||
MD
|
||||
event_description "Does not produce events."
|
||||
|
||||
def default_options
|
||||
{
|
||||
:post_url => "http://www.example.com",
|
||||
:expected_receive_period_in_days => 1
|
||||
}
|
||||
end
|
||||
|
||||
def working?
|
||||
last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
|
||||
end
|
||||
|
||||
def validate_options
|
||||
unless options[:post_url].present? && options[:expected_receive_period_in_days].present?
|
||||
errors.add(:base, "post_url and expected_receive_period_in_days are required fields")
|
||||
end
|
||||
end
|
||||
|
||||
def post_event(uri,event)
|
||||
req = Net::HTTP::Post.new(uri.request_uri)
|
||||
req.form_data = event
|
||||
Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == "https") { |http| http.request(req) }
|
||||
end
|
||||
|
||||
def receive(incoming_events)
|
||||
incoming_events.each do |event|
|
||||
uri = URI options[:post_url]
|
||||
post_event uri, event.payload
|
||||
end
|
||||
end
|
||||
def default_options
|
||||
{
|
||||
:post_url => "http://www.example.com",
|
||||
:expected_receive_period_in_days => 1
|
||||
}
|
||||
end
|
||||
|
||||
def working?
|
||||
last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
|
||||
end
|
||||
|
||||
def validate_options
|
||||
unless options[:post_url].present? && options[:expected_receive_period_in_days].present?
|
||||
errors.add(:base, "post_url and expected_receive_period_in_days are required fields")
|
||||
end
|
||||
end
|
||||
|
||||
def post_event(uri, event)
|
||||
req = Net::HTTP::Post.new(uri.request_uri)
|
||||
req.form_data = event
|
||||
Net::HTTP.start(uri.hostname, uri.port, :use_ssl => uri.scheme == "https") { |http| http.request(req) }
|
||||
end
|
||||
|
||||
def receive(incoming_events)
|
||||
incoming_events.each do |event|
|
||||
uri = URI options[:post_url]
|
||||
post_event uri, event.payload
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,84 +1,85 @@
|
|||
require 'csv'
|
||||
|
||||
module Agents
|
||||
class SentimentAgent < Agent
|
||||
class_attribute :anew
|
||||
class SentimentAgent < Agent
|
||||
class_attribute :anew
|
||||
|
||||
cannot_be_scheduled!
|
||||
cannot_be_scheduled!
|
||||
|
||||
description <<-MD
|
||||
The SentimentAgent generates `good-bad` (psychological valence or happiness index), `active-passive` (arousal),
|
||||
and `strong-weak` (dominance) score. It will output a value between 1 and 9. It will only work on English content.
|
||||
description <<-MD
|
||||
The SentimentAgent generates `good-bad` (psychological valence or happiness index), `active-passive` (arousal),
|
||||
and `strong-weak` (dominance) score. It will output a value between 1 and 9. It will only work on English content.
|
||||
|
||||
Make sure the content this agent is analyzing have sufficient length to get respectable results.
|
||||
Make sure the content this agent is analyzing have sufficient length to get respectable results.
|
||||
|
||||
Provide a JSONPath in `content` field where content is residing and set `expected_receive_period_in_days` to the maximum number of days you would allow to be passed between events being received by this agent.
|
||||
MD
|
||||
Provide a JSONPath in `content` field where content is residing and set `expected_receive_period_in_days` to the maximum number of days you would allow to be passed between events being received by this agent.
|
||||
MD
|
||||
|
||||
event_description <<-MD
|
||||
Events look like:
|
||||
{
|
||||
:content => "The quick brown fox jumps over the lazy dog.",
|
||||
:valence => 6.196666666666666,
|
||||
:arousal => 4.993333333333333,
|
||||
:dominance => 5.63
|
||||
}
|
||||
MD
|
||||
event_description <<-MD
|
||||
Events look like:
|
||||
|
||||
def default_options
|
||||
{
|
||||
:content => "$.message.text[*]",
|
||||
:expected_receive_period_in_days => 1
|
||||
}
|
||||
end
|
||||
{
|
||||
"content": "The quick brown fox jumps over the lazy dog.",
|
||||
"valence": 6.196666666666666,
|
||||
"arousal": 4.993333333333333,
|
||||
"dominance": 5.63
|
||||
}
|
||||
MD
|
||||
|
||||
def working?
|
||||
last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
|
||||
end
|
||||
|
||||
def receive(incoming_events)
|
||||
anew = self.class.sentiment_hash
|
||||
incoming_events.each do |event|
|
||||
Utils.values_at(event.payload, options[:content]).each do |content|
|
||||
sent_values = sentiment_values anew, content
|
||||
create_event :payload => {:content => content,
|
||||
:valence => sent_values[0],
|
||||
:arousal => sent_values[1],
|
||||
:dominance => sent_values[2],
|
||||
:original_event => event.payload}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validate_options
|
||||
errors.add(:base, "content and expected_receive_period_in_days must be present") unless options[:content].present? && options[:expected_receive_period_in_days].present?
|
||||
end
|
||||
|
||||
def self.sentiment_hash
|
||||
unless self.anew
|
||||
self.anew = {}
|
||||
CSV.foreach Rails.root.join('data/anew.csv') do |row|
|
||||
self.anew[row[0]] = row.values_at(2,4,6).map {|val| val.to_f}
|
||||
end
|
||||
end
|
||||
self.anew
|
||||
end
|
||||
|
||||
def sentiment_values(anew,text)
|
||||
valence, arousal, dominance, freq = [0] * 4
|
||||
text.downcase.strip.gsub(/[^a-z ]/,"").split.each do |word|
|
||||
if anew.has_key? word
|
||||
valence += anew[word][0]
|
||||
arousal += anew[word][1]
|
||||
dominance += anew[word][2]
|
||||
freq += 1
|
||||
end
|
||||
end
|
||||
if valence != 0
|
||||
[valence/freq, arousal/freq, dominance/freq]
|
||||
else
|
||||
["Insufficient data for meaningful answer"] * 3
|
||||
end
|
||||
end
|
||||
def default_options
|
||||
{
|
||||
:content => "$.message.text[*]",
|
||||
:expected_receive_period_in_days => 1
|
||||
}
|
||||
end
|
||||
|
||||
def working?
|
||||
last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
|
||||
end
|
||||
|
||||
def receive(incoming_events)
|
||||
anew = self.class.sentiment_hash
|
||||
incoming_events.each do |event|
|
||||
Utils.values_at(event.payload, options[:content]).each do |content|
|
||||
sent_values = sentiment_values anew, content
|
||||
create_event :payload => { :content => content,
|
||||
:valence => sent_values[0],
|
||||
:arousal => sent_values[1],
|
||||
:dominance => sent_values[2],
|
||||
:original_event => event.payload }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validate_options
|
||||
errors.add(:base, "content and expected_receive_period_in_days must be present") unless options[:content].present? && options[:expected_receive_period_in_days].present?
|
||||
end
|
||||
|
||||
def self.sentiment_hash
|
||||
unless self.anew
|
||||
self.anew = {}
|
||||
CSV.foreach Rails.root.join('data/anew.csv') do |row|
|
||||
self.anew[row[0]] = row.values_at(2, 4, 6).map { |val| val.to_f }
|
||||
end
|
||||
end
|
||||
self.anew
|
||||
end
|
||||
|
||||
def sentiment_values(anew, text)
|
||||
valence, arousal, dominance, freq = [0] * 4
|
||||
text.downcase.strip.gsub(/[^a-z ]/, "").split.each do |word|
|
||||
if anew.has_key? word
|
||||
valence += anew[word][0]
|
||||
arousal += anew[word][1]
|
||||
dominance += anew[word][2]
|
||||
freq += 1
|
||||
end
|
||||
end
|
||||
if valence != 0
|
||||
[valence/freq, arousal/freq, dominance/freq]
|
||||
else
|
||||
["Insufficient data for meaningful answer"] * 3
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,80 +1,78 @@
|
|||
module Agents
|
||||
class TranslationAgent < Agent
|
||||
class TranslationAgent < Agent
|
||||
|
||||
cannot_be_scheduled!
|
||||
cannot_be_scheduled!
|
||||
|
||||
description <<-MD
|
||||
You can use Translation Agent to translate text between natural languages.
|
||||
Services are provided using Microsoft Translator. You can [sign up](https://datamarket.azure.com/dataset/bing/microsofttranslator) and [register your application](https://datamarket.azure.com/developer/applications/register) to get `client_id` and `client_secret` which are required to use this agent.
|
||||
`to` must be filled with a [translator language code](http://msdn.microsoft.com/en-us/library/hh456380.aspx).
|
||||
description <<-MD
|
||||
You can use Translation Agent to translate text between natural languages.
|
||||
Services are provided using Microsoft Translator. You can [sign up](https://datamarket.azure.com/dataset/bing/microsofttranslator) and [register your application](https://datamarket.azure.com/developer/applications/register) to get `client_id` and `client_secret` which are required to use this agent.
|
||||
`to` must be filled with a [translator language code](http://msdn.microsoft.com/en-us/library/hh456380.aspx).
|
||||
|
||||
Specify what you would like to translate in `content` field, by specifying key and JSONPath of content to be translated.
|
||||
Specify what you would like to translate in `content` field, by specifying key and JSONPath of content to be translated.
|
||||
|
||||
`expected_receive_period_in_days` is the maximum number of days you would allow to pass between events.
|
||||
MD
|
||||
`expected_receive_period_in_days` is the maximum number of days you would allow to pass between events.
|
||||
MD
|
||||
|
||||
event_description <<-MD
|
||||
User defined
|
||||
MD
|
||||
event_description "User defined"
|
||||
|
||||
def default_options
|
||||
{
|
||||
:client_id => "xxxxxx",
|
||||
:client_secret => "xxxxxx" ,
|
||||
:to => "fi",
|
||||
:expected_receive_period_in_days => 1,
|
||||
:content => {
|
||||
:text => "$.message.text",
|
||||
:content => "$.xyz"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def working?
|
||||
last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
|
||||
end
|
||||
|
||||
def translate(text,to,access_token)
|
||||
translate_uri = URI 'http://api.microsofttranslator.com/v2/Ajax.svc/Translate'
|
||||
params = {
|
||||
:text => text,
|
||||
:to => to
|
||||
}
|
||||
translate_uri.query = URI.encode_www_form params
|
||||
request = Net::HTTP::Get.new translate_uri.request_uri
|
||||
request['Authorization'] = "Bearer" + " " + access_token
|
||||
http = Net::HTTP.new translate_uri.hostname, translate_uri.port
|
||||
response = http.request request
|
||||
YAML.load response.body
|
||||
end
|
||||
|
||||
def validate_options
|
||||
unless options[:client_id].present? && options[:client_secret].present? && options[:to].present? && options[:content].present? && options[:expected_receive_period_in_days].present?
|
||||
errors.add :base, "client_id,client_secret,to,expected_receive_period_in_days and content are all required"
|
||||
end
|
||||
end
|
||||
|
||||
def postform(uri,params)
|
||||
req = Net::HTTP::Post.new(uri.request_uri)
|
||||
req.form_data = params
|
||||
Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) { |http| http.request(req) }
|
||||
end
|
||||
|
||||
def receive(incoming_events)
|
||||
auth_uri = URI "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
|
||||
response = postform auth_uri, :client_id => options[:client_id],
|
||||
:client_secret => options[:client_secret],
|
||||
:scope => "http://api.microsofttranslator.com",
|
||||
:grant_type =>"client_credentials"
|
||||
access_token = JSON.parse(response.body)["access_token"]
|
||||
incoming_events.each do |event|
|
||||
translated_event = {}
|
||||
options[:content].each_pair do |key,value|
|
||||
to_be_translated = Utils.values_at event.payload, value
|
||||
translated_event[key] = translate to_be_translated.first, options[:to], access_token
|
||||
end
|
||||
create_event :payload => translated_event
|
||||
end
|
||||
end
|
||||
def default_options
|
||||
{
|
||||
:client_id => "xxxxxx",
|
||||
:client_secret => "xxxxxx",
|
||||
:to => "fi",
|
||||
:expected_receive_period_in_days => 1,
|
||||
:content => {
|
||||
:text => "$.message.text",
|
||||
:content => "$.xyz"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def working?
|
||||
last_receive_at && last_receive_at > options[:expected_receive_period_in_days].to_i.days.ago
|
||||
end
|
||||
|
||||
def translate(text, to, access_token)
|
||||
translate_uri = URI 'http://api.microsofttranslator.com/v2/Ajax.svc/Translate'
|
||||
params = {
|
||||
:text => text,
|
||||
:to => to
|
||||
}
|
||||
translate_uri.query = URI.encode_www_form params
|
||||
request = Net::HTTP::Get.new translate_uri.request_uri
|
||||
request['Authorization'] = "Bearer" + " " + access_token
|
||||
http = Net::HTTP.new translate_uri.hostname, translate_uri.port
|
||||
response = http.request request
|
||||
YAML.load response.body
|
||||
end
|
||||
|
||||
def validate_options
|
||||
unless options[:client_id].present? && options[:client_secret].present? && options[:to].present? && options[:content].present? && options[:expected_receive_period_in_days].present?
|
||||
errors.add :base, "client_id,client_secret,to,expected_receive_period_in_days and content are all required"
|
||||
end
|
||||
end
|
||||
|
||||
def postform(uri, params)
|
||||
req = Net::HTTP::Post.new(uri.request_uri)
|
||||
req.form_data = params
|
||||
Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) { |http| http.request(req) }
|
||||
end
|
||||
|
||||
def receive(incoming_events)
|
||||
auth_uri = URI "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
|
||||
response = postform auth_uri, :client_id => options[:client_id],
|
||||
:client_secret => options[:client_secret],
|
||||
:scope => "http://api.microsofttranslator.com",
|
||||
:grant_type => "client_credentials"
|
||||
access_token = JSON.parse(response.body)["access_token"]
|
||||
incoming_events.each do |event|
|
||||
translated_event = {}
|
||||
options[:content].each_pair do |key, value|
|
||||
to_be_translated = Utils.values_at event.payload, value
|
||||
translated_event[key] = translate to_be_translated.first, options[:to], access_token
|
||||
end
|
||||
create_event :payload => translated_event
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ module Agents
|
|||
event_description <<-MD
|
||||
Events look like this:
|
||||
|
||||
{ :message => "Your message" }
|
||||
{ "message": "Your message" }
|
||||
MD
|
||||
|
||||
def validate_options
|
||||
|
|
|
@ -19,30 +19,33 @@ module Agents
|
|||
When in `counts` mode, TwitterStreamAgent events look like:
|
||||
|
||||
{
|
||||
:filter => "hello world",
|
||||
:count => 25,
|
||||
:time => 3456785456
|
||||
"filter": "hello world",
|
||||
"count": 25,
|
||||
"time": 3456785456
|
||||
}
|
||||
|
||||
When in `events` mode, TwitterStreamAgent events look like:
|
||||
|
||||
{ :filter=>"selectorgadget",
|
||||
{
|
||||
"filter": "selectorgadget",
|
||||
... every Tweet field, including ...
|
||||
:text=> "something",
|
||||
:user=>
|
||||
{ :name=>"Mr. Someone",
|
||||
:screen_name=>"Someone",
|
||||
:location=>"Vancouver BC Canada",
|
||||
:description=> "...",
|
||||
:followers_count=>486,
|
||||
:friends_count=>1983,
|
||||
:created_at=>"Mon Aug 29 23:38:14 +0000 2011",
|
||||
:time_zone=>"Pacific Time (US & Canada)",
|
||||
:statuses_count=>3807,
|
||||
:lang=>"en" },
|
||||
:retweet_count=>0,
|
||||
:entities=> ...
|
||||
:lang=>"en" }
|
||||
"text": "something",
|
||||
"user": {
|
||||
"name": "Mr. Someone",
|
||||
"screen_name": "Someone",
|
||||
"location": "Vancouver BC Canada",
|
||||
"description": "...",
|
||||
"followers_count": 486,
|
||||
"friends_count": 1983,
|
||||
"created_at": "Mon Aug 29 23:38:14 +0000 2011",
|
||||
"time_zone": "Pacific Time (US & Canada)",
|
||||
"statuses_count": 3807,
|
||||
"lang": "en"
|
||||
},
|
||||
"retweet_count": 0,
|
||||
"entities": ...
|
||||
"lang": "en"
|
||||
}
|
||||
MD
|
||||
|
||||
default_schedule "11pm"
|
||||
|
|
|
@ -15,95 +15,25 @@ module Agents
|
|||
event_description <<-MD
|
||||
Events are the raw JSON provided by the Twitter API. Should look something like:
|
||||
|
||||
{
|
||||
:created_at=>"Thu Apr 04 13:27:48 +0000 2013",
|
||||
:id=>319803490421596160,
|
||||
:id_str=>"319803490421596160",
|
||||
:text=>
|
||||
"In which @jeresig goes to an art gallery and is \"the JavaScript programmer\". http://t.co/gt3PT1d3G1",
|
||||
:source=>
|
||||
"<a href=\"http://itunes.apple.com/us/app/twitter/id409789998?mt=12\" rel=\"nofollow\">Twitter for Mac</a>",
|
||||
:truncated=>false,
|
||||
:in_reply_to_status_id=>nil,
|
||||
:in_reply_to_status_id_str=>nil,
|
||||
:in_reply_to_user_id=>nil,
|
||||
:in_reply_to_user_id_str=>nil,
|
||||
:in_reply_to_screen_name=>nil,
|
||||
:user=>
|
||||
{:id=>2341001,
|
||||
:id_str=>"2341001",
|
||||
:name=>"Albert Sun",
|
||||
:screen_name=>"albertsun",
|
||||
:location=>"New York, NY",
|
||||
:description=>
|
||||
"News apps developer at NYT, formerly WSJ. graduated Penn 2010, geek, journalist, data-viz, nlp, gis, digital economics =)",
|
||||
:url=>"http://albertsun.info",
|
||||
:entities=>
|
||||
{:url=>
|
||||
{:urls=>
|
||||
[{:url=>"http://albertsun.info",
|
||||
:expanded_url=>nil,
|
||||
:indices=>[0, 21]}]},
|
||||
:description=>{:urls=>[]}},
|
||||
:protected=>false,
|
||||
:followers_count=>1857,
|
||||
:friends_count=>798,
|
||||
:listed_count=>115,
|
||||
:created_at=>"Mon Mar 26 19:22:05 +0000 2007",
|
||||
:favourites_count=>9,
|
||||
:utc_offset=>-18000,
|
||||
:time_zone=>"Eastern Time (US & Canada)",
|
||||
:geo_enabled=>false,
|
||||
:verified=>false,
|
||||
:statuses_count=>2572,
|
||||
:lang=>"en",
|
||||
:contributors_enabled=>false,
|
||||
:is_translator=>false,
|
||||
:profile_background_color=>"1B2A2B",
|
||||
:profile_background_image_url=>
|
||||
"http://a0.twimg.com/profile_background_images/2802438/twitterbg.jpg",
|
||||
:profile_background_image_url_https=>
|
||||
"https://si0.twimg.com/profile_background_images/2802438/twitterbg.jpg",
|
||||
:profile_background_tile=>false,
|
||||
:profile_image_url=>
|
||||
"http://a0.twimg.com/profile_images/110500205/profile-square_normal.jpg",
|
||||
:profile_image_url_https=>
|
||||
"https://si0.twimg.com/profile_images/110500205/profile-square_normal.jpg",
|
||||
:profile_link_color=>"0000FF",
|
||||
:profile_sidebar_border_color=>"87BC44",
|
||||
:profile_sidebar_fill_color=>"E0FF92",
|
||||
:profile_text_color=>"000000",
|
||||
:profile_use_background_image=>true,
|
||||
:default_profile=>false,
|
||||
:default_profile_image=>false,
|
||||
:following=>false,
|
||||
:follow_request_sent=>false,
|
||||
:notifications=>false},
|
||||
:geo=>nil,
|
||||
:coordinates=>nil,
|
||||
:place=>nil,
|
||||
:contributors=>nil,
|
||||
:retweet_count=>0,
|
||||
:favorite_count=>0,
|
||||
:entities=>
|
||||
{:hashtags=>[],
|
||||
:urls=>
|
||||
[{:url=>"http://t.co/gt3PT1d3G1",
|
||||
:expanded_url=>
|
||||
"http://www.nytimes.com/2013/04/04/fashion/art-and-techology-a-clash-of-cultures.html?pagewanted=all",
|
||||
:display_url=>"nytimes.com/2013/04/04/fas",
|
||||
:indices=>[77, 99]}],
|
||||
:user_mentions=>
|
||||
[{:screen_name=>"jeresig",
|
||||
:name=>"John Resig",
|
||||
:id=>752673,
|
||||
:id_str=>"752673",
|
||||
:indices=>[9, 17]}]},
|
||||
:favorited=>false,
|
||||
:retweeted=>false,
|
||||
:possibly_sensitive=>false,
|
||||
:lang=>"en"
|
||||
}
|
||||
{
|
||||
... every Tweet field, including ...
|
||||
"text": "something",
|
||||
"user": {
|
||||
"name": "Mr. Someone",
|
||||
"screen_name": "Someone",
|
||||
"location": "Vancouver BC Canada",
|
||||
"description": "...",
|
||||
"followers_count": 486,
|
||||
"friends_count": 1983,
|
||||
"created_at": "Mon Aug 29 23:38:14 +0000 2011",
|
||||
"time_zone": "Pacific Time (US & Canada)",
|
||||
"statuses_count": 3807,
|
||||
"lang": "en"
|
||||
},
|
||||
"retweet_count": 0,
|
||||
"entities": ...
|
||||
"lang": "en"
|
||||
}
|
||||
MD
|
||||
|
||||
default_schedule "every_1h"
|
||||
|
|
|
@ -17,15 +17,15 @@ module Agents
|
|||
Assuming you're using the iOS application, events look like this:
|
||||
|
||||
{
|
||||
:latitude => "37.12345",
|
||||
:longitude => "-122.12345",
|
||||
:timestamp => "123456789.0",
|
||||
:altitude => "22.0",
|
||||
:horizontal_accuracy => "5.0",
|
||||
:vertical_accuracy => "3.0",
|
||||
:speed => "0.52595",
|
||||
:course => "72.0703",
|
||||
:device_token => "..."
|
||||
"latitude": "37.12345",
|
||||
"longitude": "-122.12345",
|
||||
"timestamp": "123456789.0",
|
||||
"altitude": "22.0",
|
||||
"horizontal_accuracy": "5.0",
|
||||
"vertical_accuracy": "3.0",
|
||||
"speed": "0.52595",
|
||||
"course": "72.0703",
|
||||
"device_token": "..."
|
||||
}
|
||||
MD
|
||||
|
||||
|
|
|
@ -14,26 +14,24 @@ module Agents
|
|||
Events look like this:
|
||||
|
||||
{
|
||||
:zipcode => 12345,
|
||||
:date => { :epoch=>"1357959600", :pretty=>"10:00 PM EST on January 11, 2013" },
|
||||
:high => { :fahrenheit=>"64", :celsius=>"18" },
|
||||
:low => { :fahrenheit=>"52", :celsius=>"11" },
|
||||
:conditions => "Rain Showers",
|
||||
:icon=>"rain",
|
||||
:icon_url => "http://icons-ak.wxug.com/i/c/k/rain.gif",
|
||||
:skyicon => "mostlycloudy",
|
||||
:pop => 80,
|
||||
:qpf_allday => { :in=>0.24, :mm=>6.1 },
|
||||
:qpf_day => { :in=>0.13, :mm=>3.3 },
|
||||
:qpf_night => { :in=>0.03, :mm=>0.8 },
|
||||
:snow_allday => { :in=>0, :cm=>0 },
|
||||
:snow_day => { :in=>0, :cm=>0 },
|
||||
:snow_night => { :in=>0, :cm=>0 },
|
||||
:maxwind => { :mph=>15, :kph=>24, :dir=>"SSE", :degrees=>160 },
|
||||
:avewind => { :mph=>9, :kph=>14, :dir=>"SSW", :degrees=>194 },
|
||||
:avehumidity => 85,
|
||||
:maxhumidity => 93,
|
||||
:minhumidity => 63
|
||||
"zipcode": 12345,
|
||||
"date": {
|
||||
"epoch": "1357959600",
|
||||
"pretty": "10:00 PM EST on January 11, 2013"
|
||||
},
|
||||
"high": {
|
||||
"fahrenheit": "64",
|
||||
"celsius": "18"
|
||||
},
|
||||
"low": {
|
||||
"fahrenheit": "52",
|
||||
"celsius": "11"
|
||||
},
|
||||
"conditions": "Rain Showers",
|
||||
"icon": "rain",
|
||||
"icon_url": "http://icons-ak.wxug.com/i/c/k/rain.gif",
|
||||
"skyicon": "mostlycloudy",
|
||||
...
|
||||
}
|
||||
MD
|
||||
|
||||
|
|
|
@ -36,11 +36,7 @@ module Agents
|
|||
MD
|
||||
|
||||
event_description do
|
||||
<<-MD
|
||||
Events will have the fields you specified. Your options look like:
|
||||
|
||||
#{PP.pp(options[:extract], "")}
|
||||
MD
|
||||
"Events will have the fields you specified. Your options look like:\n\n #{Utils.pretty_print options[:extract]}"
|
||||
end
|
||||
|
||||
default_schedule "every_12h"
|
||||
|
|
|
@ -16,51 +16,51 @@ module Agents
|
|||
MD
|
||||
|
||||
event_description <<-MD
|
||||
Events are the raw JSON provided by the Twitter API. Should look something like:
|
||||
Events are the raw JSON provided by the Weibo API. Should look something like:
|
||||
|
||||
{
|
||||
"created_at": "Tue May 31 17:46:55 +0800 2011",
|
||||
"id": 11488058246,
|
||||
"text": "求关注。",
|
||||
"source": "<a href=\"http://weibo.com\" rel=\"nofollow\">新浪微博</a>",
|
||||
"favorited": false,
|
||||
"truncated": false,
|
||||
"in_reply_to_status_id": "",
|
||||
"in_reply_to_user_id": "",
|
||||
"in_reply_to_screen_name": "",
|
||||
"geo": null,
|
||||
"mid": "5612814510546515491",
|
||||
"reposts_count": 8,
|
||||
"comments_count": 9,
|
||||
"annotations": [],
|
||||
"user": {
|
||||
"id": 1404376560,
|
||||
"screen_name": "zaku",
|
||||
"name": "zaku",
|
||||
"province": "11",
|
||||
"city": "5",
|
||||
"location": "北京 朝阳区",
|
||||
"description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。",
|
||||
"url": "http://blog.sina.com.cn/zaku",
|
||||
"profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
|
||||
"domain": "zaku",
|
||||
"gender": "m",
|
||||
"followers_count": 1204,
|
||||
"friends_count": 447,
|
||||
"statuses_count": 2908,
|
||||
"favourites_count": 0,
|
||||
"created_at": "Fri Aug 28 00:00:00 +0800 2009",
|
||||
"following": false,
|
||||
"allow_all_act_msg": false,
|
||||
"remark": "",
|
||||
"geo_enabled": true,
|
||||
"verified": false,
|
||||
"allow_all_comment": true,
|
||||
"avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
|
||||
"verified_reason": "",
|
||||
"follow_me": false,
|
||||
"online_status": 0,
|
||||
"bi_followers_count": 215
|
||||
{
|
||||
"created_at": "Tue May 31 17:46:55 +0800 2011",
|
||||
"id": 11488058246,
|
||||
"text": "求关注。",
|
||||
"source": "<a href=\"http://weibo.com\" rel=\"nofollow\">新浪微博</a>",
|
||||
"favorited": false,
|
||||
"truncated": false,
|
||||
"in_reply_to_status_id": "",
|
||||
"in_reply_to_user_id": "",
|
||||
"in_reply_to_screen_name": "",
|
||||
"geo": null,
|
||||
"mid": "5612814510546515491",
|
||||
"reposts_count": 8,
|
||||
"comments_count": 9,
|
||||
"annotations": [],
|
||||
"user": {
|
||||
"id": 1404376560,
|
||||
"screen_name": "zaku",
|
||||
"name": "zaku",
|
||||
"province": "11",
|
||||
"city": "5",
|
||||
"location": "北京 朝阳区",
|
||||
"description": "人生五十年,乃如梦如幻;有生斯有死,壮士复何憾。",
|
||||
"url": "http://blog.sina.com.cn/zaku",
|
||||
"profile_image_url": "http://tp1.sinaimg.cn/1404376560/50/0/1",
|
||||
"domain": "zaku",
|
||||
"gender": "m",
|
||||
"followers_count": 1204,
|
||||
"friends_count": 447,
|
||||
"statuses_count": 2908,
|
||||
"favourites_count": 0,
|
||||
"created_at": "Fri Aug 28 00:00:00 +0800 2009",
|
||||
"following": false,
|
||||
"allow_all_act_msg": false,
|
||||
"remark": "",
|
||||
"geo_enabled": true,
|
||||
"verified": false,
|
||||
"allow_all_comment": true,
|
||||
"avatar_large": "http://tp1.sinaimg.cn/1404376560/180/0/1",
|
||||
"verified_reason": "",
|
||||
"follow_me": false,
|
||||
"online_status": 0,
|
||||
"bi_followers_count": 215
|
||||
}
|
||||
}
|
||||
MD
|
||||
|
|
|
@ -22,10 +22,9 @@
|
|||
|
||||
<% if @agent.new_record? %>
|
||||
<div class="control-group type-select">
|
||||
<%= image_tag "spinner-arrows.gif", :class => "spinner" %>
|
||||
<%= f.label :type, :class => 'control-label' %>
|
||||
<div class="controls">
|
||||
<%= f.select :type, options_for_select(Agent.types.map {|type| [type.to_s.gsub(/^.*::/, ''), type.to_s] }, @agent.type), :class => 'span4' %>
|
||||
<%= f.select :type, options_for_select(Agent.types.map {|type| [type.to_s.gsub(/^.*::/, ''), type.to_s] }, @agent.type), {}, :class => 'span4 select2' %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
@ -40,7 +39,7 @@
|
|||
<div class="control-group">
|
||||
<%= f.label :schedule, :class => 'control-label' %>
|
||||
<div class="controls schedule-region" data-can-be-scheduled="<%= @agent.can_be_scheduled? %>">
|
||||
<%= f.select :schedule, options_for_select(Agent::SCHEDULES.map {|s| [s.humanize.titleize, s] }, @agent.schedule), :class => 'span4' %>
|
||||
<%= f.select :schedule, options_for_select(Agent::SCHEDULES.map {|s| [s.humanize.titleize, s] }, @agent.schedule), {}, :class => 'span4' %>
|
||||
<span class='cannot-be-scheduled text-info'>This type of Agent cannot be scheduled.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -51,7 +50,7 @@
|
|||
<%= f.select(:source_ids,
|
||||
options_for_select((current_user.agents - [@agent]).map {|s| [s.name, s.id] },
|
||||
@agent.source_ids),
|
||||
{}, { :multiple => true, :size => 5, :class => 'span4 multi-select' }) %>
|
||||
{}, { :multiple => true, :size => 5, :class => 'span4 select2' }) %>
|
||||
<span class='cannot-receive-events text-info'>This type of Agent cannot receive events.</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
<div class='row'>
|
||||
<div class='span12'>
|
||||
<div class="page-header">
|
||||
<h2>Editing your <%= @agent.short_type %></h2>
|
||||
<h2>
|
||||
Editing your <%= @agent.short_type %>
|
||||
<%= image_tag "spinner-arrows.gif", :class => "spinner" %>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<%= render 'form' %>
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
<div class='row'>
|
||||
<div class='span12'>
|
||||
<div class="page-header">
|
||||
<h2>Create a new Agent</h2>
|
||||
<h2>
|
||||
Create a new Agent
|
||||
<%= image_tag "spinner-arrows.gif", :class => "spinner" %>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<%= render 'form' %>
|
||||
|
|
|
@ -90,12 +90,12 @@
|
|||
|
||||
<p>
|
||||
<b>Options:</b>
|
||||
<pre><%= PP.pp(@agent.options, "") %></pre>
|
||||
<pre><%= JSON.pretty_generate @agent.options %></pre>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Memory:</b>
|
||||
<pre><%= PP.pp(@agent.memory, "") %></pre>
|
||||
<pre><%= JSON.pretty_generate @agent.memory %></pre>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
<p>
|
||||
<b>Payload:</b>
|
||||
<pre><%= PP.pp(@event.payload, "") %></pre>
|
||||
<pre><%= JSON.pretty_generate @event.payload %></pre>
|
||||
</p>
|
||||
|
||||
<% if @event.lat && @event.lng %>
|
||||
|
|
18
lib/utils.rb
18
lib/utils.rb
|
@ -2,9 +2,23 @@ require 'jsonpath'
|
|||
require 'cgi'
|
||||
|
||||
module Utils
|
||||
# Unindents if the indentation is 2 or more characters.
|
||||
def self.unindent(s)
|
||||
s.gsub(/^#{s.scan(/^\s+/).select {|i| i.length > 1 }.min_by{|l|l.length}}/, "")
|
||||
s = s.gsub(/\t/, ' ').chomp
|
||||
min = ((s.split("\n").find {|l| l !~ /^\s*$/ })[/^\s+/, 0] || "").length
|
||||
if min > 0
|
||||
s.gsub(/^#{" " * min}/, "")
|
||||
else
|
||||
s
|
||||
end
|
||||
end
|
||||
|
||||
def self.pretty_print(struct, indent = true)
|
||||
output = JSON.pretty_generate(struct)
|
||||
if indent
|
||||
output.gsub(/\n/i, "\n ").tap { |a| p a }
|
||||
else
|
||||
output
|
||||
end
|
||||
end
|
||||
|
||||
def self.recursively_symbolize_keys(object)
|
||||
|
|
|
@ -1,6 +1,32 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe Utils do
|
||||
describe "#unindent" do
|
||||
it "unindents to the level of the greatest consistant indention" do
|
||||
Utils.unindent(<<-MD).should == "Hello World"
|
||||
Hello World
|
||||
MD
|
||||
|
||||
Utils.unindent(<<-MD).should == "Hello World\nThis is\nnot indented"
|
||||
Hello World
|
||||
This is
|
||||
not indented
|
||||
MD
|
||||
|
||||
Utils.unindent(<<-MD).should == "Hello World\n This is\n indented\nthough"
|
||||
Hello World
|
||||
This is
|
||||
indented
|
||||
though
|
||||
MD
|
||||
|
||||
Utils.unindent("Hello\n I am indented").should == "Hello\n I am indented"
|
||||
|
||||
a = " Events will have the fields you specified. Your options look like:\n\n {\n \"url\": {\n \"css\": \"#comic img\",\n \"attr\": \"src\"\n },\n \"title\": {\n \"css\": \"#comic img\",\n \"attr\": \"title\"\n }\n }\"\n"
|
||||
Utils.unindent(a).should == "Events will have the fields you specified. Your options look like:\n\n {\n \"url\": {\n\"css\": \"#comic img\",\n\"attr\": \"src\"\n },\n \"title\": {\n\"css\": \"#comic img\",\n\"attr\": \"title\"\n }\n }\""
|
||||
end
|
||||
end
|
||||
|
||||
describe "#value_at" do
|
||||
it "returns the value at a JSON path" do
|
||||
Utils.value_at({ :foo => { :bar => :baz }}.to_json, "foo.bar").should == "baz"
|
||||
|
|
|
@ -212,7 +212,7 @@ JSONEditor.prototype.showFunctionButtons = function(insider) {
|
|||
}).text('Redo')).append($('<a id="toggle_view" href="#" style="padding-right: 10px;"></a>').click(function() {
|
||||
self.toggleBuilder();
|
||||
return false;
|
||||
}).text('Toggle View').css("float", "right"));
|
||||
}).text('Toggle View'));
|
||||
this.container.prepend(this.functionButtons);
|
||||
this.container.height(this.container.height() + this.functionButtons.height() + 5);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue