dash-fu 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +8 -2
- data/VERSION +1 -1
- data/dash-fu.gemspec +2 -2
- data/lib/dash_fu.rb +187 -0
- metadata +19 -6
- data/lib/dash-fu.rb +0 -94
- data/test/test.rb +0 -62
data/CHANGELOG
CHANGED
@@ -1,6 +1,12 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0 on 2011-10-03
|
2
2
|
|
3
|
-
Added
|
3
|
+
Added DashFu.log method. This is experimental and will be nailed down by next
|
4
|
+
minor update.
|
5
|
+
|
6
|
+
Added multi_json as dependency, required for adding new activities.
|
7
|
+
|
8
|
+
Renamed dash-fu.rb to dash_fu.rb, since this is the more common convention. No
|
9
|
+
change to Gem name yet.
|
4
10
|
|
5
11
|
|
6
12
|
1.0.0 on 2011-08-19
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0
|
data/dash-fu.gemspec
CHANGED
@@ -9,10 +9,10 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.summary = "Use me to push data to Dash-fu in real time"
|
10
10
|
spec.description = ""
|
11
11
|
spec.post_install_message = IO.read("README.md")
|
12
|
-
spec.license
|
12
|
+
spec.license = "Public domain"
|
13
13
|
|
14
14
|
spec.files = Dir["{bin,lib,test}/**/*", "CHANGELOG", "VERSION", "README.md", "*.gemspec"]
|
15
|
-
spec.require_paths = [ "lib" ]
|
16
15
|
|
17
16
|
spec.required_ruby_version = '>= 1.8.7'
|
17
|
+
spec.add_dependency "multi_json"
|
18
18
|
end
|
data/lib/dash_fu.rb
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
require "socket"
|
2
|
+
require 'multi_json'
|
3
|
+
|
4
|
+
|
5
|
+
# Use me to push data to Dash-fu in real time.
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# The only configuration you need is setting the API key in
|
9
|
+
# config/environments/production.rb:
|
10
|
+
#
|
11
|
+
# DashFu.api_key = "<your API key>"
|
12
|
+
#
|
13
|
+
# You only want to push data in production, so make sure the API key is only set
|
14
|
+
# in production environment. Calls to created/active with no API key are
|
15
|
+
# simply ignored.
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# You can send events by calling DashFu.push with UID and event, or using the
|
19
|
+
# specialized created and active methods.
|
20
|
+
#
|
21
|
+
# For example, to assign a user to a cohort, we're going to notify Dash-fu
|
22
|
+
# whenever an acount gets created:
|
23
|
+
#
|
24
|
+
# class User < ActiveRecord::Model
|
25
|
+
# after_create do
|
26
|
+
# DashFu.created id
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# In this example, we consider a user active whenever they post a status update,
|
31
|
+
# and notify Dash-fu accordingly:
|
32
|
+
#
|
33
|
+
# class StatusUpdate < ActiveRecord::Model
|
34
|
+
# after_create do
|
35
|
+
# DashFu.active id
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
module DashFu
|
39
|
+
@mutex = Mutex.new
|
40
|
+
|
41
|
+
class << self
|
42
|
+
# DashFu.host is set by default, this is only useful for testing.
|
43
|
+
attr_accessor :host, :port
|
44
|
+
|
45
|
+
# Set the API key with:
|
46
|
+
# DashFu.api_key = "<your API key>"
|
47
|
+
attr_accessor :api_key
|
48
|
+
|
49
|
+
# Logs an activity. An activity has an actor (the user who performed the
|
50
|
+
# activity), the action performed (e.g. posted, commented) and the object of
|
51
|
+
# the action (e.g. the post).
|
52
|
+
#
|
53
|
+
# You can call this method with actor, action and optional object (two or
|
54
|
+
# three arguments), or with a Hash with the keys actor, action, object and
|
55
|
+
# timestamp.
|
56
|
+
#
|
57
|
+
# For example:
|
58
|
+
# DashFu.log user, 'posted', post.title
|
59
|
+
# DashFu.log actor: user, action: 'posted', object: { content: post.title }
|
60
|
+
#
|
61
|
+
#
|
62
|
+
# The actor argument is either the ID of a user, an object that responds to
|
63
|
+
# to_dashboard, or Hash with the following keys:
|
64
|
+
# - uid -- User identifier (must be unique in application, required)
|
65
|
+
# - created -- Date/time instance when user account was created
|
66
|
+
# - name -- Display name
|
67
|
+
# - email -- Email address
|
68
|
+
# - image -- URL for profile photo
|
69
|
+
# - url -- URL for profile
|
70
|
+
#
|
71
|
+
# UID is required, all other properties are optional.
|
72
|
+
#
|
73
|
+
# When using an object that responds to to_dashboard, the to_dashboard
|
74
|
+
# method must return a Hash with these keys.
|
75
|
+
#
|
76
|
+
#
|
77
|
+
# The action argument is any string, e.g. 'posted', 'commented'.
|
78
|
+
#
|
79
|
+
#
|
80
|
+
# The object argument is either a string containing HTML markup, an object
|
81
|
+
# that respobds to to_dashboard, or Hash with the following keys:
|
82
|
+
# - content -- HTML markup describing the object
|
83
|
+
#
|
84
|
+
# HTML markup may use semantic styling elements such as b, em, p,
|
85
|
+
# blockquote. Styles and scripts are stripped, and only links to HTTP/S
|
86
|
+
# URLs are preserved.
|
87
|
+
#
|
88
|
+
# When using an object that responds to to_dashboard, the to_dashboard
|
89
|
+
# method must return a Hash with these keys.
|
90
|
+
#
|
91
|
+
#
|
92
|
+
# The timestamp argument is the date/time instance the activity occurred.
|
93
|
+
def log(*args)
|
94
|
+
return unless @api_key # in test/dev mode, return quickly
|
95
|
+
|
96
|
+
if args.length == 1 && args[0].respond_to?(:values_at)
|
97
|
+
actor, action, object, timestamp = args[0].values_at(:actor, :action, :object, :timestamp)
|
98
|
+
elsif args.length == 2 || args.length == 3
|
99
|
+
actor, action, object = *args
|
100
|
+
else
|
101
|
+
raise ArgumentError.new("Expected Hash or actor, action, (object)")
|
102
|
+
end
|
103
|
+
|
104
|
+
if actor.respond_to?(:to_dashboard)
|
105
|
+
actor = actor.to_dashboard
|
106
|
+
elsif String === actor
|
107
|
+
actor = { uid: actor }
|
108
|
+
end
|
109
|
+
|
110
|
+
action = action.to_s
|
111
|
+
|
112
|
+
if object.respond_to?(:to_dashboard)
|
113
|
+
object = object.to_dashboard
|
114
|
+
elsif String === object
|
115
|
+
object = { content: object }
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
params = { actor: actor, action: action, object: object, timestamp: timestamp }
|
120
|
+
json = MultiJson.encode(params)
|
121
|
+
puts json
|
122
|
+
if socket = @socket
|
123
|
+
socket.sendmsg_nonblock "POST /v1/activity?api_key=#{@api_key} HTTP/1.1\r\nHost: #{@host}\r\nConnection: keep-alive\r\nContent-Type: application/json\r\nContent-Length: #{json.length}\r\n\r\n#{json}", 0
|
124
|
+
socket.recv_nonblock 512 rescue nil
|
125
|
+
else
|
126
|
+
Thread.new do
|
127
|
+
@mutex.synchronize do
|
128
|
+
@socket ||= TCPSocket.open(@host, @port || 80)
|
129
|
+
end
|
130
|
+
log params
|
131
|
+
end
|
132
|
+
end
|
133
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ETIMEDOUT
|
134
|
+
close
|
135
|
+
retry
|
136
|
+
rescue Errno::ECONNREFUSED, Errno::ENETUNREACH
|
137
|
+
# No @socket so we'll try to connect next time
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# Records that a user has been active in the app.
|
142
|
+
def active(uid)
|
143
|
+
push uid, "active"
|
144
|
+
end
|
145
|
+
|
146
|
+
# Records that a new user account has been created (associate them with
|
147
|
+
# cohort).
|
148
|
+
def created(uid)
|
149
|
+
push uid, "created"
|
150
|
+
end
|
151
|
+
|
152
|
+
# Close connection. If you like crossing your t's you can call this when the
|
153
|
+
# app shutsdown.
|
154
|
+
def close
|
155
|
+
@mutex.synchronize do
|
156
|
+
socket, @socket = @socket, nil
|
157
|
+
socket.close if socket
|
158
|
+
end
|
159
|
+
rescue Exception
|
160
|
+
end
|
161
|
+
|
162
|
+
# Push update for the specified user ID and event. Or you can use
|
163
|
+
# active/created.
|
164
|
+
def push(uid, event)
|
165
|
+
return unless @api_key
|
166
|
+
if socket = @socket
|
167
|
+
socket.sendmsg_nonblock "POST /v1/push?api_key=#{@api_key}&uid=#{uid}&event=#{event} HTTP/1.1\r\nHost: #{@host}\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n", 0
|
168
|
+
socket.recv_nonblock 512 rescue nil
|
169
|
+
else
|
170
|
+
Thread.new do
|
171
|
+
@mutex.synchronize do
|
172
|
+
@socket ||= TCPSocket.open(@host, @port || 80)
|
173
|
+
end
|
174
|
+
push uid, event
|
175
|
+
end
|
176
|
+
end
|
177
|
+
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ETIMEDOUT
|
178
|
+
close
|
179
|
+
retry
|
180
|
+
rescue Errno::ECONNREFUSED, Errno::ENETUNREACH
|
181
|
+
# No @socket so we'll try to connect next time
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Default host name.
|
186
|
+
self.host = "beta.dash-fu.com"
|
187
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dash-fu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,16 +9,26 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
13
|
-
dependencies:
|
12
|
+
date: 2011-10-03 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: multi_json
|
16
|
+
requirement: &70176105576660 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70176105576660
|
14
25
|
description: ''
|
15
26
|
email: assaf@labnotes.org
|
16
27
|
executables: []
|
17
28
|
extensions: []
|
18
29
|
extra_rdoc_files: []
|
19
30
|
files:
|
20
|
-
- lib/
|
21
|
-
- test/test.rb
|
31
|
+
- lib/dash_fu.rb
|
22
32
|
- CHANGELOG
|
23
33
|
- VERSION
|
24
34
|
- README.md
|
@@ -55,9 +65,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
65
|
- - ! '>='
|
56
66
|
- !ruby/object:Gem::Version
|
57
67
|
version: '0'
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
hash: 780141375345856102
|
58
71
|
requirements: []
|
59
72
|
rubyforge_project:
|
60
|
-
rubygems_version: 1.8.
|
73
|
+
rubygems_version: 1.8.10
|
61
74
|
signing_key:
|
62
75
|
specification_version: 3
|
63
76
|
summary: Use me to push data to Dash-fu in real time
|
data/lib/dash-fu.rb
DELETED
@@ -1,94 +0,0 @@
|
|
1
|
-
require "socket"
|
2
|
-
|
3
|
-
|
4
|
-
# Use me to push data to Dash-fu in real time.
|
5
|
-
#
|
6
|
-
#
|
7
|
-
# The only configuration you need is setting the API key in
|
8
|
-
# config/environments/production.rb:
|
9
|
-
#
|
10
|
-
# DashFu.api_key = "<your API key>"
|
11
|
-
#
|
12
|
-
# You only want to push data in production, so make sure the API key is only set
|
13
|
-
# in production environment. Calls to created/active with no API key are
|
14
|
-
# simply ignored.
|
15
|
-
#
|
16
|
-
#
|
17
|
-
# You can send events by calling DashFu.push with UID and event, or using the
|
18
|
-
# specialized created and active methods.
|
19
|
-
#
|
20
|
-
# For example, to assign a user to a cohort, we're going to notify Dash-fu
|
21
|
-
# whenever an acount gets created:
|
22
|
-
#
|
23
|
-
# class User < ActiveRecord::Model
|
24
|
-
# after_create do
|
25
|
-
# DashFu.created id
|
26
|
-
# end
|
27
|
-
# end
|
28
|
-
#
|
29
|
-
# In this example, we consider a user active whenever they post a status update,
|
30
|
-
# and notify Dash-fu accordingly:
|
31
|
-
#
|
32
|
-
# class StatusUpdate < ActiveRecord::Model
|
33
|
-
# after_create do
|
34
|
-
# DashFu.active id
|
35
|
-
# end
|
36
|
-
# end
|
37
|
-
module DashFu
|
38
|
-
@mutex = Mutex.new
|
39
|
-
|
40
|
-
class << self
|
41
|
-
# DashFu.host is set by default, this is only useful for testing.
|
42
|
-
attr_accessor :host, :port
|
43
|
-
|
44
|
-
# Set the API key with:
|
45
|
-
# DashFu.api_key = "<your API key>"
|
46
|
-
attr_accessor :api_key
|
47
|
-
|
48
|
-
# Records that a user has been active in the app.
|
49
|
-
def active(uid)
|
50
|
-
push uid, "active"
|
51
|
-
end
|
52
|
-
|
53
|
-
# Records that a new user account has been created (associate them with
|
54
|
-
# cohort).
|
55
|
-
def created(uid)
|
56
|
-
push uid, "created"
|
57
|
-
end
|
58
|
-
|
59
|
-
# Close connection. If you like crossing your t's you can call this when the
|
60
|
-
# app shutsdown.
|
61
|
-
def close
|
62
|
-
@mutex.synchronize do
|
63
|
-
socket, @socket = @socket, nil
|
64
|
-
socket.close if socket
|
65
|
-
end
|
66
|
-
rescue Exception
|
67
|
-
end
|
68
|
-
|
69
|
-
# Push update for the specified user ID and event. Or you can use
|
70
|
-
# active/created.
|
71
|
-
def push(uid, event)
|
72
|
-
return unless @api_key
|
73
|
-
if socket = @socket
|
74
|
-
socket.sendmsg_nonblock "POST /v1/push?api_key=#{@api_key}&uid=#{uid}&event=#{event} HTTP/1.1\r\nHost: #{@host}\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n", 0
|
75
|
-
socket.recv_nonblock 512 rescue nil
|
76
|
-
else
|
77
|
-
Thread.new do
|
78
|
-
@mutex.synchronize do
|
79
|
-
@socket ||= TCPSocket.open(@host, @port || 80)
|
80
|
-
end
|
81
|
-
push uid, event
|
82
|
-
end
|
83
|
-
end
|
84
|
-
rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ETIMEDOUT
|
85
|
-
close
|
86
|
-
retry
|
87
|
-
rescue Errno::ECONNREFUSED, Errno::ENETUNREACH
|
88
|
-
# No @socket so we'll try to connect next time
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# Default host name.
|
93
|
-
self.host = "beta.dash-fu.com"
|
94
|
-
end
|
data/test/test.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
require "test/unit"
|
2
|
-
require "thread"
|
3
|
-
require "cgi"
|
4
|
-
require "webrick"
|
5
|
-
$: << File.dirname(__FILE__) + "/../lib"
|
6
|
-
$: << File.expand_path(File.dirname(__FILE__) + "/..")
|
7
|
-
require "dash-fu"
|
8
|
-
|
9
|
-
|
10
|
-
$ready = Queue.new
|
11
|
-
$server = WEBrick::HTTPServer.new(:Port=>3001, :StartCallback=>lambda { puts "go"; $ready << "go" }, :Logger=>WEBrick::Log.new(nil, WEBrick::Log::FATAL))
|
12
|
-
servlet = lambda do |request, response|
|
13
|
-
$request = request
|
14
|
-
response.status = 200
|
15
|
-
end
|
16
|
-
$server.mount "/v1/push", WEBrick::HTTPServlet::ProcHandler.new(servlet)
|
17
|
-
trap "INT" do
|
18
|
-
$server.shutdown
|
19
|
-
end
|
20
|
-
DashFu.host = "localhost"
|
21
|
-
DashFu.port = 3001
|
22
|
-
|
23
|
-
|
24
|
-
class Test::Unit::TestCase
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
# Test sending live data.
|
30
|
-
class LiveTest < Test::Unit::TestCase
|
31
|
-
def setup
|
32
|
-
DashFu.api_key = "foobar"
|
33
|
-
Thread.new { $server.start }
|
34
|
-
$ready.pop
|
35
|
-
$request = nil
|
36
|
-
end
|
37
|
-
|
38
|
-
def test_active_request
|
39
|
-
DashFu.active "56789"
|
40
|
-
sleep 0.2
|
41
|
-
assert_equal "/v1/push", $request.path
|
42
|
-
assert_equal "POST", $request.request_method
|
43
|
-
assert $request.keep_alive?
|
44
|
-
end
|
45
|
-
|
46
|
-
def test_active_data
|
47
|
-
DashFu.active "56789"
|
48
|
-
sleep 0.2
|
49
|
-
query = CGI.parse($request.query_string)
|
50
|
-
assert_equal ["foobar"], query["api_key"]
|
51
|
-
assert_equal ["active"], query["event"]
|
52
|
-
assert_equal ["56789"], query["uid"]
|
53
|
-
end
|
54
|
-
|
55
|
-
def teardown
|
56
|
-
$server.shutdown
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
# Test Dashfu API when no API key set (e.g. test and development environments).
|
61
|
-
class InactiveTest < Test::Unit::TestCase
|
62
|
-
end
|