event-tracker 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bc7102810ab7b2ebbedcdd8d6384f2388ed86e9d
4
+ data.tar.gz: a9e5c3a2c4a846b7d99e36a83918d10bc8c043b7
5
+ SHA512:
6
+ metadata.gz: bd06c36ce0da7b40fab4e7f1931e88717be67440eb391cc2104801414f7d92d0bf41c31c70c94fc7316571d024e0a4df398e96de57c5a8c2934ea99afef27748
7
+ data.tar.gz: 81d71fa242817d86beb2b528dd602110c4a4e6b9d6d61e187b9c82daa90cb89620edc1fb21d0c5ce21dff6a11972a780520ca69b2095293b6e0cb7fd6279c33b
@@ -0,0 +1,19 @@
1
+ # Copyright (c) 2014 Donald Piret.
2
+ Permission is hereby granted, free of charge, to any person obtaining
3
+ a copy of this software and associated documentation files (the
4
+ "Software"), to deal in the Software without restriction, including
5
+ without limitation the rights to use, copy, modify, merge, publish,
6
+ distribute, sublicense, and/or sell copies of the Software, and to
7
+ permit persons to whom the Software is furnished to do so, subject to
8
+ the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be
11
+ included in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
15
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
16
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
17
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
19
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,4 @@
1
+ = Event Tracker
2
+
3
+ This library allows the easy implementation of server-side and client side tracking of various events.
4
+ It is currently designed to work with Segment.io (http://segment.io), but should be able to support other trackers as well.
@@ -0,0 +1,9 @@
1
+ class Hash
2
+ def merge_if(condition, target = nil)
3
+ condition ? self.merge(target ? target : condition) : self
4
+ end
5
+
6
+ def deep_merge_if(condition, target)
7
+ condition ? self.deep_merge(target) : self
8
+ end
9
+ end
@@ -0,0 +1,157 @@
1
+ require 'core_ext/hash'
2
+ require 'event_tracker/tracker'
3
+ require 'event_tracker/segment_io'
4
+
5
+ module EventTracker
6
+ class Config
7
+ attr_accessor :segment_io_key
8
+ attr_accessor :disabled
9
+ end
10
+
11
+ def self.config
12
+ @config ||= Config.new
13
+ end
14
+
15
+ def self.configure
16
+ yield self.config
17
+ end
18
+
19
+ def self.disabled?
20
+ @config.disabled == true
21
+ end
22
+
23
+ # Defines all the helper methods that become available at the controller level
24
+ module HelperMethods
25
+ def track_event(event_name, args = {})
26
+ (session[:tracker_events] ||= []) << [event_name, _sanitize_args(args)]
27
+ end
28
+
29
+ def register_properties(args)
30
+ (session[:tracker_properties] ||= {}).merge!(_sanitize_args(args))
31
+ end
32
+
33
+ def track_pageview(name, category, args = {})
34
+ (session[:tracker_pageviews] ||= []) << [name, category, _sanitize_args(args)]
35
+ end
36
+
37
+ def create_alias(identity1, identity2)
38
+ (session[:tracker_alias] ||= []) << [identity1, identity2]
39
+ end
40
+
41
+ def track_transaction(event_name, args = {})
42
+ (session[:tracker_transactions] ||= []) << [event_name, _sanitize_args(args)]
43
+ end
44
+
45
+ def identify_for_user(user, with_info = false)
46
+ _trackers.each do |tracker|
47
+ tracker.identify_for_identity(_tracker_identity(user), with_info)
48
+ end
49
+ end
50
+
51
+ def create_alias_for_user(identity1, identity2)
52
+ _trackers.each do |tracker|
53
+ tracker.create_alias_for_identity(identity1, identity2)
54
+ end
55
+ end
56
+
57
+ def track_event_for_user(user, event_name, args = {})
58
+ _trackers.each do |tracker|
59
+ tracker.track_event_for_identity(_tracker_identity(user), event_name, _sanitize_args(args))
60
+ end
61
+ end
62
+
63
+ def track_transaction_for_user(user, event_name, args = {})
64
+ _trackers.each do |tracker|
65
+ tracker.track_transaction_for_identity(_tracker_identity(user), event_name, _sanitize_args(args))
66
+ end
67
+ end
68
+ end
69
+
70
+ module ActionControllerExtension
71
+ def append_tracker
72
+ return if EventTracker.disabled? || _trackers.empty?
73
+ body = response.body
74
+ head_insert_at = body.index('</head')
75
+ return unless head_insert_at
76
+ body.insert head_insert_at, view_context.javascript_tag(_trackers.map {|t| t.init }.join("\n"))
77
+ body_insert_at = body.index('</body')
78
+ return unless body_insert_at
79
+ a = [] # Array of all javascript strings to insert
80
+
81
+ properties = session.delete(:tracker_properties)
82
+ events = session.delete(:tracker_events)
83
+ pageviews = session.delete(:tracker_pageviews)
84
+ alias_list = session.delete(:tracker_alias)
85
+ transactions = session.delete(:tracker_transactions)
86
+
87
+ _trackers.each do |tracker|
88
+ tracker.set_options(request: request)
89
+
90
+ a << tracker.identify(_tracker_identity)
91
+
92
+ # a << tracker.track_pageview() # No need for this anymore. Done by default
93
+ if pageviews.present?
94
+ pageviews.each do |url, properties|
95
+ a << tracker.track_pageview(url, properties)
96
+ end
97
+ end
98
+
99
+ a << tracker.add_properties(properties) if properties.present?
100
+
101
+ if events.present?
102
+ events.each do |event_name, properties|
103
+ a << tracker.track_event(event_name, properties)
104
+ end
105
+ end
106
+
107
+ if alias_list.present?
108
+ alias_list.each do |identity1, identity2|
109
+ a << tracker.create_alias(identity1, identity2)
110
+ end
111
+ end
112
+
113
+ if transactions.present?
114
+ transactions.each do |event_name, properties|
115
+ a << tracker.track_transaction(event_name, properties)
116
+ end
117
+ end
118
+ end
119
+
120
+ body.insert body_insert_at, view_context.javascript_tag(a.compact.join("\n"))
121
+ response.body = body
122
+ end
123
+
124
+ def _trackers
125
+ return [] if EventTracker.disabled?
126
+ @_trackers ||= begin
127
+ t = []
128
+ t << _segment_io_tracker if _segment_io_tracker
129
+ t
130
+ end
131
+ end
132
+
133
+ def _sanitize_args(args)
134
+ args.each do |k, v|
135
+ if v.is_a?(Hash)
136
+ args[k] = _sanitize_args(v)
137
+ elsif v.is_a?(String)
138
+ args[k] = ActionController::Base.helpers.sanitize(v)
139
+ else
140
+ args[k] = v
141
+ end
142
+ end
143
+ return args
144
+ end
145
+
146
+ def _tracker_identity(user = nil)
147
+ respond_to?(:analytics_identity, true) ? send(:analytics_identity, user) : nil
148
+ end
149
+
150
+ def _segment_io_tracker
151
+ @_segment_io_tracker ||= begin
152
+ key = EventTracker.config.segment_io_key
153
+ key ? EventTracker::SegmentIo.new(key: key) : nil
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,10 @@
1
+ module EventTracker
2
+ require 'rails'
3
+
4
+ class Railtie < Rails::Railtie
5
+
6
+ initializer 'event_tracker' do |app|
7
+
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,126 @@
1
+ module EventTracker
2
+ class SegmentIo < Tracker
3
+ JS_ESCAPE_MAP = {
4
+ '\\' => '\\\\',
5
+ '</' => '<\/',
6
+ "\r\n" => '\n',
7
+ "\n" => '\n',
8
+ "\r" => '\n',
9
+ '"' => '\\"',
10
+ "'" => "\\'"
11
+ }
12
+
13
+
14
+ def initialize(options = {})
15
+ @key = options[:key]
16
+ end
17
+
18
+ def init
19
+ <<-EOD
20
+ window.analytics=window.analytics||[],window.analytics.methods=["identify","group","track","page","pageview","alias","ready","on","once","off","trackLink","trackForm","trackClick","trackSubmit"],window.analytics.factory=function(t){return function(){var a=Array.prototype.slice.call(arguments);return a.unshift(t),window.analytics.push(a),window.analytics}};for(var i=0;i<window.analytics.methods.length;i++){var key=window.analytics.methods[i];window.analytics[key]=window.analytics.factory(key)}window.analytics.load=function(t){if(!document.getElementById("analytics-js")){var a=document.createElement("script");a.type="text/javascript",a.id="analytics-js",a.async=!0,a.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.io/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n)}},window.analytics.SNIPPET_VERSION="2.0.9",
21
+ window.analytics.load("#{@key}");
22
+ window.analytics.page();
23
+ EOD
24
+ end
25
+
26
+ def identify(identity = nil)
27
+ if identity.present? && identity.has_key?(:id)
28
+ %Q{analytics.identify('#{identity[:id]}');}
29
+ elsif identity.present?
30
+ %Q{analytics.identify(#{ruby_hash_to_js(identity)});}
31
+ else
32
+ nil
33
+ end
34
+ end
35
+
36
+ def create_alias(identity1, identity2)
37
+ %Q{analytics.alias('#{identity1}', '#{identity2}');}
38
+ end
39
+
40
+ def add_properties(properties = nil)
41
+ %Q{analytics.track('set', #{ruby_hash_to_js(properties)});}
42
+ end
43
+
44
+ def track_pageview(name = nil, category = nil, properties = {}, options = {})
45
+ p = properties.empty? ? "" : ", #{ruby_hash_to_js(properties)}"
46
+ if category.present? && name.present?
47
+ %Q{analytics.page('#{category}', '#{name}'#{p});}
48
+ elsif name.present?
49
+ %Q{analytics.page('#{name}'#{p});}
50
+ else
51
+ %Q{analytics.page('');}
52
+ end
53
+ end
54
+
55
+ def track_event(event_name, properties)
56
+ p = properties.empty? ? "" : ", #{ruby_hash_to_js(properties.except(:analytics))}"
57
+ %Q{analytics.track('#{event_name}'#{p});}
58
+ end
59
+
60
+ def track_transaction(event_name, properties = {})
61
+ track_event(event_name, properties)
62
+ end
63
+
64
+ def identify_for_identity(identity, with_info = false)
65
+ return if EventTracker.disabled?
66
+ if identity.present? && identity.has_key?(:id)
67
+ client.identify({
68
+ user_id: "#{identity[:id]}",
69
+ }.merge_if(with_info, { traits: camelize_hash(identity.except(:id)) }))
70
+ end
71
+ end
72
+
73
+ def create_alias_for_identity(identity1, identity2)
74
+ return if EventTracker.disabled?
75
+ if identity1.present? && identity2.present?
76
+ client.alias(from: identity1, to: identity2)
77
+ end
78
+ end
79
+
80
+ def track_event_for_identity(identity, event_name, properties = {})
81
+ return if EventTracker.disabled?
82
+ if identity.present? && identity.has_key?(:id)
83
+ client.track(
84
+ user_id: "#{identity[:id]}",
85
+ event: "#{event_name}",
86
+ properties: camelize_hash(properties.except(:analytics))
87
+ )
88
+ end
89
+ end
90
+
91
+ def track_transaction_for_identity(identity, event_name, properties = {})
92
+ track_event_for_identity(identity, event_name, properties)
93
+ end
94
+
95
+ private
96
+
97
+ def camelize_hash(hash)
98
+ Hash[hash.map { |k, v| [k.to_s.camelize(:lower), v] }]
99
+ end
100
+
101
+ def ruby_hash_to_js(hash)
102
+ "{#{hash.collect{|key, val| "#{key.to_s.camelize(:lower)}: #{value_for_js(val)}" }.join(', ')}}"
103
+ end
104
+
105
+ def value_for_js(value)
106
+ case value
107
+ when Numeric, TrueClass, FalseClass
108
+ value
109
+ else
110
+ "'#{escape_javascript(value.try(:to_s))}'"
111
+ end
112
+ end
113
+
114
+ def escape_javascript(javascript)
115
+ if javascript
116
+ javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
117
+ else
118
+ ''
119
+ end
120
+ end
121
+
122
+ def client
123
+ AnalyticsRuby
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,52 @@
1
+ module EventTracker
2
+ # Abstract class from which every tracker inherits. Shows all supported methods
3
+ class Tracker
4
+ def init
5
+
6
+ end
7
+
8
+ def set_options(options = {})
9
+
10
+ end
11
+
12
+ def identify(identity = nil)
13
+
14
+ end
15
+
16
+ def create_alias(identity1, identity2)
17
+
18
+ end
19
+
20
+ def add_properties(properties = nil)
21
+
22
+ end
23
+
24
+ def track_pageview(url = nil, properties = {})
25
+
26
+ end
27
+
28
+ def track_event(event_name = nil, properties = {})
29
+
30
+ end
31
+
32
+ def track_transaction(event_name, properties = {})
33
+
34
+ end
35
+
36
+ def identify_for_identity(identity = nil, with_info = false)
37
+
38
+ end
39
+
40
+ def create_alias_for_identity(identity1, identity2)
41
+
42
+ end
43
+
44
+ def track_event_for_identity(identity, event_name = nil, properties = {})
45
+
46
+ end
47
+
48
+ def track_transaction_for_identity(identity, event_name, properties = {})
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module EventTracker
2
+ VERSION = '0.1'
3
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe EventsController, type: :controller do
4
+ render_views
5
+
6
+ describe '#index' do
7
+ it 'includes the segment.io javascript in the user browser' do
8
+ get :index
9
+ expect(response.body).to include('http://")+"cdn.segment.io/analytics.js/v1/"+t+"/analytics.min.js"') # Invluding the JS
10
+ end
11
+
12
+ it 'identifies the user for segment.io' do
13
+ get :index
14
+ expect(response.body).to include('analytics.identify(\'dummyId\');') # Identifying the user
15
+ end
16
+
17
+ it 'tracks the event for segment.io' do
18
+ get :index
19
+ expect(response.body).to include('analytics.track(\'List events\');') # Tracking the event
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe EventTracker do
4
+
5
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: event-tracker
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ platform: ruby
6
+ authors:
7
+ - Donald Piret
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: analytics-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ - - "<="
35
+ - !ruby/object:Gem::Version
36
+ version: '5.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ - - "<="
45
+ - !ruby/object:Gem::Version
46
+ version: '5.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: rails
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '3.0'
54
+ - - "<="
55
+ - !ruby/object:Gem::Version
56
+ version: '5.0'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: '3.0'
64
+ - - "<="
65
+ - !ruby/object:Gem::Version
66
+ version: '5.0'
67
+ - !ruby/object:Gem::Dependency
68
+ name: bundler
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '1.3'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - "~>"
79
+ - !ruby/object:Gem::Version
80
+ version: '1.3'
81
+ - !ruby/object:Gem::Dependency
82
+ name: rake
83
+ requirement: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - "~>"
86
+ - !ruby/object:Gem::Version
87
+ version: '10.0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - "~>"
93
+ - !ruby/object:Gem::Version
94
+ version: '10.0'
95
+ - !ruby/object:Gem::Dependency
96
+ name: rspec-rails
97
+ requirement: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '3.0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - "~>"
107
+ - !ruby/object:Gem::Version
108
+ version: '3.0'
109
+ - !ruby/object:Gem::Dependency
110
+ name: sqlite3
111
+ requirement: !ruby/object:Gem::Requirement
112
+ requirements:
113
+ - - "~>"
114
+ - !ruby/object:Gem::Version
115
+ version: '1.3'
116
+ type: :development
117
+ prerelease: false
118
+ version_requirements: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - "~>"
121
+ - !ruby/object:Gem::Version
122
+ version: '1.3'
123
+ description: This library allows the easy implementation of server-side and client
124
+ side tracking of various events.
125
+ email: donald@donaldpiret.com
126
+ executables: []
127
+ extensions: []
128
+ extra_rdoc_files: []
129
+ files:
130
+ - MIT-LICENSE
131
+ - README.rdoc
132
+ - lib/core_ext/hash.rb
133
+ - lib/event_tracker.rb
134
+ - lib/event_tracker/railtie.rb
135
+ - lib/event_tracker/segment_io.rb
136
+ - lib/event_tracker/tracker.rb
137
+ - lib/event_tracker/version.rb
138
+ - spec/controllers/events_controller_spec.rb
139
+ - spec/event_tracker_spec.rb
140
+ homepage: https://github.com/donaldpiret/event-tracker
141
+ licenses:
142
+ - MIT
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: 1.9.3
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.2.2
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Quick and easy tracking of server-side and client side events for Rails
164
+ test_files:
165
+ - spec/controllers/events_controller_spec.rb
166
+ - spec/event_tracker_spec.rb