whenauser 0.6.4 → 0.7.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +39 -17
- data/lib/whenauser.rb +34 -20
- data/lib/whenauser/pageviews.rb +16 -21
- data/lib/whenauser/railtie.rb +8 -4
- data/lib/whenauser/version.rb +1 -1
- metadata +69 -63
data/README.md
CHANGED
@@ -3,12 +3,12 @@ WhenAUser
|
|
3
3
|
|
4
4
|
[WhenAUser.com](http://whenauser.com) is a rules engine that reacts to things users do or experience in your software, and makes things happen in 3rd party SaaS APIs -- without your having to write any code. Rather than implementing the most rapidly evolving parts of your application's business logic in code, your team can use the WhenAUser web app to specify "when", "how", and "who", with rules like these:
|
5
5
|
|
6
|
-
* when a user gets a validation error
|
7
|
-
* when a premium customer hasn't logged in for a month, flag them in
|
6
|
+
* when a user gets a form validation error three times in an hour, send an email to Frank
|
7
|
+
* when a premium customer hasn't logged in for a month, flag them in your CRM
|
8
8
|
* when a user gets a 500 response, create a ticket in Zendesk
|
9
9
|
* when a user invites ten friends, add them to the "well-connected" segment in MailChimp
|
10
10
|
|
11
|
-
This gem contains Rack middleware that automatically generates two event streams, one for exceptions and the other for pageviews, that can be used to trigger rules in WhenAUser. You can
|
11
|
+
This gem contains Rack middleware that automatically generates two event streams, one for exceptions and the other for pageviews, that can be used to trigger rules in WhenAUser. You can also send more specific events manually.
|
12
12
|
|
13
13
|
Setup
|
14
14
|
-----
|
@@ -30,9 +30,31 @@ You should create two incoming channels (event streams) in WhenAUser, and config
|
|
30
30
|
###As general-purpose Rack middleware, with or without Rails
|
31
31
|
|
32
32
|
config.middleware.insert 0, 'WhenAUser::Rack', :token => 'CHANNEL_TOKEN'
|
33
|
-
config.middleware.
|
33
|
+
config.middleware.use 'WhenAUser::Pageviews'
|
34
34
|
config.middleware.use 'WhenAUser::Exceptions', :token => 'ERROR_CHANNEL_TOKEN'
|
35
35
|
|
36
|
+
The current user
|
37
|
+
----------------
|
38
|
+
|
39
|
+
WhenAUser can take advantage of knowing about the user behind each event, which is supplied by the \_actor field. This gem employs a number of heuristics to determine the current user, but you can also help it out. If, for example, you want to use current_user.id as the \_actor for every event (and 0 for the logged out user), you could do this via:
|
40
|
+
|
41
|
+
controller_data '{:_actor => current_user.try(:id) || 0}'
|
42
|
+
|
43
|
+
The string will be evaluated in the context of your controller.
|
44
|
+
|
45
|
+
Sending other events
|
46
|
+
--------------------
|
47
|
+
|
48
|
+
To manually send an event when a user upgrades to a "premium" account:
|
49
|
+
|
50
|
+
WhenAUser.send_event(
|
51
|
+
:_actor => current_user.unique_id,
|
52
|
+
:_timestamp => Time.now.to_f,
|
53
|
+
:_domain => 'account',
|
54
|
+
:_name => 'upgrade',
|
55
|
+
:user_email => current_user.email,
|
56
|
+
:plan => 'premium' )
|
57
|
+
|
36
58
|
Using girl_friday for asynchronous communication and persistence
|
37
59
|
-----------------
|
38
60
|
|
@@ -66,7 +88,7 @@ WhenAUser::Rack accepts these options:
|
|
66
88
|
* `webhook_url` -- defaults to 'http://whenauser.com/events'
|
67
89
|
* `middleware` -- takes the symbol for a middleware and a block, configuring it
|
68
90
|
* `queue` -- takes the class used for queuing (default: WhenAUser::MemoryQueue), and an optional hash
|
69
|
-
* `
|
91
|
+
* `controller_data` -- a string evaluated in the context of the Rails controller (if any) handling the request; it should return a hash to be merged into every event
|
70
92
|
|
71
93
|
The `exceptions` middleware accepts these options:
|
72
94
|
|
@@ -80,6 +102,7 @@ The `pageviews` middleware accepts these options:
|
|
80
102
|
|
81
103
|
* `ignore_crawlers` -- an array of strings to match against the user agent, includes a number of webcrawlers by default
|
82
104
|
* `ignore_if` -- this proc is passed env; if it returns true, the pageview is not reported to WhenAUser
|
105
|
+
* `ignore_if_controller` -- a string to be evaluated in the context of the Rails controller instance
|
83
106
|
* `custom_data` -- this proc is passed env, and should return a hash to be merged into each automatically generated event
|
84
107
|
|
85
108
|
The WhenAUser::Pageviews middleware uses the same token as WhenAUser::Rack.
|
@@ -90,18 +113,17 @@ Here's an example of how to skip sending any pageview events for all requests to
|
|
90
113
|
ignore_if lambda { |env| env['action_controller.instance'].is_a? SillyController }
|
91
114
|
end
|
92
115
|
|
93
|
-
|
94
|
-
--------------------
|
116
|
+
To make life easier in the case where you want a condition evaluated in the context of a Rails controller, you can do the same thing like this. (Only the pageviews middleware supports ignore_if_controller.)
|
95
117
|
|
96
|
-
|
118
|
+
middleware :pageviews do
|
119
|
+
ignore_if_controller 'self.is_a?(EventsController)'
|
120
|
+
end
|
97
121
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
:user_email => current_user.email,
|
104
|
-
:plan => 'premium' )
|
122
|
+
Or if you want to skip sending pageview events for requests from pingdom.com:
|
123
|
+
|
124
|
+
middleware :pageviews do
|
125
|
+
ignore_crawlers WhenAUser.default_ignored_crawlers + ['Pingdom.com_bot']
|
126
|
+
end
|
105
127
|
|
106
128
|
Use Cases
|
107
129
|
---------
|
@@ -109,7 +131,7 @@ Use Cases
|
|
109
131
|
### Example rule triggers
|
110
132
|
|
111
133
|
* whenever a `UserIsHavingAVeryBadDay` exception is raised
|
112
|
-
* the first time any exception occurs
|
134
|
+
* the first time any particular exception occurs
|
113
135
|
* whenever a request takes more than 20 seconds to process
|
114
136
|
* whenever someone upgrades their account
|
115
137
|
* whenever someone does comment#create more than 10 times in a day
|
@@ -120,7 +142,7 @@ Use Cases
|
|
120
142
|
* send yourself an email or a mobile push message
|
121
143
|
* send a user an email or a mobile push message
|
122
144
|
* create a ticket in your ticketing system
|
123
|
-
* add a data point to a Librato graph
|
145
|
+
* add a data point to a Librato or StatsMix graph
|
124
146
|
* tag a user in WhenAUser, or in your CRM
|
125
147
|
* segment a user in your email campaign tool
|
126
148
|
|
data/lib/whenauser.rb
CHANGED
@@ -8,24 +8,23 @@ require 'net/http'
|
|
8
8
|
require 'uri'
|
9
9
|
require 'logger'
|
10
10
|
require 'active_support/core_ext/module/attribute_accessors'
|
11
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
11
12
|
|
12
13
|
module WhenAUser
|
13
|
-
mattr_accessor :filter_parameters, :buffer, :token, :webhook_url, :queue, :queue_options, :
|
14
|
+
mattr_accessor :filter_parameters, :buffer, :token, :webhook_url, :queue, :queue_options, :controller_data, :logger
|
14
15
|
|
15
16
|
def self.default_ignored_crawlers
|
16
|
-
%w(Baidu Gigabot Googlebot libwww-perl lwp-trivial msnbot SiteUptime Slurp WordPress ZIBB ZyBorg Yandex Jyxobot Huaweisymantecspider ApptusBot)
|
17
|
+
%w(Baidu Gigabot Googlebot libwww-perl lwp-trivial msnbot SiteUptime Slurp WordPress ZIBB ZyBorg Yandex Jyxobot Huaweisymantecspider ApptusBot NewRelicPinger)
|
17
18
|
end
|
18
19
|
|
19
20
|
def self.send_event(event)
|
20
|
-
logger.debug "============= #{event.inspect} ============="
|
21
21
|
buffer << event
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.flush(env={})
|
25
25
|
return if (events = WhenAUser.buffer).empty?
|
26
26
|
WhenAUser.buffer = []
|
27
|
-
events.
|
28
|
-
WhenAUser.queue.push(:payload => events)
|
27
|
+
WhenAUser.queue.push(:payload => events.map {|event| WhenAUser.prepare_event(event, env)})
|
29
28
|
# WhenAUser.post_payload_to_token events.to_json, WhenAUser.token
|
30
29
|
end
|
31
30
|
|
@@ -53,19 +52,39 @@ module WhenAUser
|
|
53
52
|
end
|
54
53
|
|
55
54
|
private
|
55
|
+
def self.page_event_name(request, params)
|
56
|
+
if params && params['controller']
|
57
|
+
"#{params['controller']}##{params['action']}"
|
58
|
+
else
|
59
|
+
request.path.gsub('/', '-')[1..-1]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
56
63
|
def self.prepare_event(event, env)
|
57
|
-
event
|
58
|
-
|
64
|
+
event = event.with_indifferent_access
|
65
|
+
|
66
|
+
if controller = env['action_controller.instance']
|
67
|
+
data = controller.instance_eval(WhenAUser.controller_data)
|
68
|
+
event.merge!(data)
|
69
|
+
end
|
70
|
+
|
71
|
+
event[:_actor] = current_user(env) || 'anonymous' unless event[:_actor].present?
|
72
|
+
event[:_timestamp] ||= Time.now.to_f
|
59
73
|
event[:rails_env] = Rails.env if defined?(Rails)
|
74
|
+
|
60
75
|
unless env.empty?
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
event[:
|
76
|
+
env['rack.input'].rewind
|
77
|
+
request = defined?(Rails) ? ActionDispatch::Request.new(env) : ::Rack::Request.new(env)
|
78
|
+
params = request.params
|
79
|
+
|
80
|
+
event[:_name] ||= page_event_name(request, params)
|
81
|
+
event[:request_url] = request.url
|
82
|
+
event[:request_method] = request.request_method
|
83
|
+
event[:user_agent] = request.user_agent
|
84
|
+
event[:referer_url] = request.referer if request.referer.present?
|
85
|
+
event[:params] = params
|
67
86
|
end
|
68
|
-
|
87
|
+
|
69
88
|
event
|
70
89
|
end
|
71
90
|
|
@@ -79,16 +98,11 @@ module WhenAUser
|
|
79
98
|
WhenAUser.token = options[:token]
|
80
99
|
WhenAUser.queue = options[:queue] || WhenAUser::MemoryQueue
|
81
100
|
WhenAUser.queue_options = options[:queue_options] || {}
|
82
|
-
WhenAUser.
|
101
|
+
WhenAUser.controller_data = options[:controller_data] || '{}'
|
83
102
|
end
|
84
103
|
|
85
104
|
def call(env)
|
86
105
|
WhenAUser.buffer = []
|
87
|
-
request = ActionDispatch::Request.new(env)
|
88
|
-
env['whenauser.request_url'] = request.url
|
89
|
-
env['whenauser.request_method'] = request.request_method
|
90
|
-
env['whenauser.user_agent'] = request.user_agent
|
91
|
-
env['whenauser.referer_url'] = request.referer
|
92
106
|
@app.call(env)
|
93
107
|
ensure
|
94
108
|
WhenAUser.flush(env)
|
data/lib/whenauser/pageviews.rb
CHANGED
@@ -10,13 +10,13 @@ module WhenAUser
|
|
10
10
|
|
11
11
|
def initialize(app, options={})
|
12
12
|
@app, @options = app, options
|
13
|
-
@options[:ignore_crawlers]
|
14
|
-
@options[:ignore_if]
|
15
|
-
@options[:
|
13
|
+
@options[:ignore_crawlers] ||= WhenAUser.default_ignored_crawlers
|
14
|
+
@options[:ignore_if] ||= lambda { |env| false }
|
15
|
+
@options[:ignore_if_controller] ||= 'false'
|
16
|
+
@options[:custom_data] ||= lambda { |env| {} }
|
16
17
|
end
|
17
18
|
|
18
19
|
def call(env)
|
19
|
-
request = ActionDispatch::Request.new(env)
|
20
20
|
before = Time.now
|
21
21
|
status, headers, response = @app.call(env)
|
22
22
|
[status, headers, response]
|
@@ -25,38 +25,33 @@ module WhenAUser
|
|
25
25
|
raise e
|
26
26
|
ensure
|
27
27
|
after = Time.now
|
28
|
-
WhenAUser.send_event event(env,
|
28
|
+
WhenAUser.send_event event(env, status, after - before) unless should_be_ignored(env)
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
32
|
-
def rails_asset_request?(env
|
32
|
+
def rails_asset_request?(env)
|
33
33
|
defined?(Rails) && env['action_controller.instance'].nil?
|
34
34
|
end
|
35
35
|
|
36
|
-
def should_be_ignored(env
|
37
|
-
rails_asset_request?(env
|
36
|
+
def should_be_ignored(env)
|
37
|
+
rails_asset_request?(env) ||
|
38
38
|
from_crawler(@options[:ignore_crawlers], env['HTTP_USER_AGENT']) ||
|
39
|
-
conditionally_ignored(@options[:ignore_if], env)
|
39
|
+
conditionally_ignored(@options[:ignore_if], env) ||
|
40
|
+
conditionally_ignored_controller(@options[:ignore_if_controller], env)
|
40
41
|
end
|
41
42
|
|
42
|
-
def
|
43
|
-
|
44
|
-
|
45
|
-
false
|
43
|
+
def conditionally_ignored_controller(condition, env)
|
44
|
+
controller = env['action_controller.instance']
|
45
|
+
controller.instance_eval condition
|
46
46
|
end
|
47
47
|
|
48
|
-
def
|
49
|
-
|
50
|
-
"#{params['controller']}##{params['action']}"
|
51
|
-
else
|
52
|
-
request.path.gsub('/', '-')
|
53
|
-
end
|
48
|
+
def conditionally_ignored(ignore_proc, env)
|
49
|
+
ignore_proc.call(env)
|
54
50
|
end
|
55
51
|
|
56
|
-
def event(env,
|
52
|
+
def event(env, status, duration)
|
57
53
|
event = {
|
58
54
|
:_domain => (status.to_i >= 400) ? 'pageerror' : 'pageview',
|
59
|
-
:_name => page_event_name(request),
|
60
55
|
:status => status,
|
61
56
|
:duration => "%.2f" % (duration * 1000)
|
62
57
|
}
|
data/lib/whenauser/railtie.rb
CHANGED
@@ -3,7 +3,7 @@ require 'active_record'
|
|
3
3
|
|
4
4
|
module Whenauser
|
5
5
|
class RailsConfigurator
|
6
|
-
attr_accessor :token, :webhook_url, :middlewares, :queue, :queue_options
|
6
|
+
attr_accessor :token, :webhook_url, :middlewares, :queue, :controller_data, :queue_options
|
7
7
|
def initialize
|
8
8
|
@webhook_url = 'http://www.whenauser.com/events/'
|
9
9
|
@middlewares = {}
|
@@ -26,6 +26,10 @@ module Whenauser
|
|
26
26
|
@queue_options = options
|
27
27
|
end
|
28
28
|
|
29
|
+
def controller_data(data)
|
30
|
+
@controller_data = data
|
31
|
+
end
|
32
|
+
|
29
33
|
def girl_friday_options(options)
|
30
34
|
@girl_friday_options = options
|
31
35
|
end
|
@@ -73,10 +77,10 @@ module Whenauser
|
|
73
77
|
:webhook_url => @webhook_url,
|
74
78
|
:token => @token,
|
75
79
|
:queue => @queue,
|
76
|
-
:queue_options => @queue_options
|
80
|
+
:queue_options => @queue_options,
|
81
|
+
:controller_data => @controller_data
|
82
|
+
::Rails.configuration.middleware.use('WhenAUser::Pageviews', @middlewares[:pageviews].configuration) if @middlewares.has_key?(:pageviews)
|
77
83
|
::Rails.configuration.middleware.use('WhenAUser::Exceptions', @middlewares[:exceptions].configuration) if @middlewares.has_key?(:exceptions)
|
78
|
-
# puts "configuration with: #{@middlewares[:pageviews].configuration}"
|
79
|
-
::Rails.configuration.middleware.insert_after('WhenAUser::Rack', 'WhenAUser::Pageviews', @middlewares[:pageviews].configuration) if @middlewares.has_key?(:pageviews)
|
80
84
|
end
|
81
85
|
end
|
82
86
|
end
|
data/lib/whenauser/version.rb
CHANGED
metadata
CHANGED
@@ -1,72 +1,71 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: whenauser
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 7
|
8
|
+
- 1
|
9
|
+
version: 0.7.1
|
6
10
|
platform: ruby
|
7
|
-
authors:
|
11
|
+
authors:
|
8
12
|
- David Anderson
|
9
13
|
- Chris Weis
|
10
14
|
autorequire:
|
11
15
|
bindir: bin
|
12
16
|
cert_chain: []
|
13
|
-
|
14
|
-
|
15
|
-
|
17
|
+
|
18
|
+
date: 2012-08-11 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
16
22
|
name: activesupport
|
17
|
-
requirement: !ruby/object:Gem::Requirement
|
18
|
-
none: false
|
19
|
-
requirements:
|
20
|
-
- - ! '>='
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '0'
|
23
|
-
type: :runtime
|
24
23
|
prerelease: false
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
name: actionpack
|
33
|
-
requirement: !ruby/object:Gem::Requirement
|
34
|
-
none: false
|
35
|
-
requirements:
|
36
|
-
- - ! '>='
|
37
|
-
- !ruby/object:Gem::Version
|
38
|
-
version: '0'
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
39
31
|
type: :runtime
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: actionpack
|
40
35
|
prerelease: false
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
name: girl_friday
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
|
-
requirements:
|
52
|
-
- - ~>
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: 0.10.0
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
version: "0"
|
55
43
|
type: :runtime
|
44
|
+
version_requirements: *id002
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: girl_friday
|
56
47
|
prerelease: false
|
57
|
-
|
58
|
-
|
59
|
-
requirements:
|
48
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
60
50
|
- - ~>
|
61
|
-
- !ruby/object:Gem::Version
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
segments:
|
53
|
+
- 0
|
54
|
+
- 10
|
55
|
+
- 0
|
62
56
|
version: 0.10.0
|
57
|
+
type: :runtime
|
58
|
+
version_requirements: *id003
|
63
59
|
description: Rack middleware for connecting to WhenAUser
|
64
|
-
email:
|
60
|
+
email:
|
65
61
|
- david@alpinegizmo.com
|
66
62
|
executables: []
|
63
|
+
|
67
64
|
extensions: []
|
65
|
+
|
68
66
|
extra_rdoc_files: []
|
69
|
-
|
67
|
+
|
68
|
+
files:
|
70
69
|
- .gitignore
|
71
70
|
- Gemfile
|
72
71
|
- README.md
|
@@ -81,28 +80,35 @@ files:
|
|
81
80
|
- lib/whenauser/railtie.rb
|
82
81
|
- lib/whenauser/version.rb
|
83
82
|
- whenauser.gemspec
|
83
|
+
has_rdoc: true
|
84
84
|
homepage: https://github.com/tractionlabs/whenauser
|
85
85
|
licenses: []
|
86
|
+
|
86
87
|
post_install_message:
|
87
88
|
rdoc_options: []
|
88
|
-
|
89
|
+
|
90
|
+
require_paths:
|
89
91
|
- lib
|
90
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
requirements:
|
99
|
-
- -
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
segments:
|
104
|
+
- 0
|
105
|
+
version: "0"
|
102
106
|
requirements: []
|
107
|
+
|
103
108
|
rubyforge_project:
|
104
|
-
rubygems_version: 1.
|
109
|
+
rubygems_version: 1.3.6
|
105
110
|
signing_key:
|
106
111
|
specification_version: 3
|
107
112
|
summary: Rack middleware for connecting to WhenAUser
|
108
113
|
test_files: []
|
114
|
+
|