saucy-kiss 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ 0.0.1
2
+
3
+ Initial release. Provides the basic set of KISSmetrics SaaS events:
4
+
5
+ * Visited Site
6
+ * Signed Up
7
+ * Upgraded
8
+ * Downgraded
9
+ * Billed
10
+ * Canceled
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem "rspec", "~> 2.3.0"
7
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jason Morrison
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,157 @@
1
+ = saucy-kiss
2
+
3
+ Saucy-KISS is an extension to the Saucy gem for tracking your SaaS events
4
+ in KISSmetrics.
5
+
6
+ It depends on the unofficial KISSmetrics gem http://rubygems.org/gems/kissmetrics
7
+
8
+ == Features and assumptions
9
+
10
+ We assume that:
11
+
12
+ * You want the KISSmetrics JS on the page at all times, so you can define
13
+ events in KISSmetrics, use their Google Analytics tie-in, etc.
14
+
15
+ * You would like to track events using the JS API as much as possible, to take
16
+ advantage of KISSmetrics' anonymous identity tracking, automatic handling
17
+ of the "Visited Site" metric, bot/crawler blacklisting, etc.
18
+
19
+ By using the kissmetrics gem, we support:
20
+
21
+ * Testing your KISSmetrics integration points.
22
+
23
+ * Using the KISSmetrics JS API from the request/response cycle by storing
24
+ identity, events, and properties in the session and rendering them into your
25
+ view as JS calls, similar to the Rails flash.
26
+
27
+ * Using the KISSmetrics HTTP API from outside the request/response cycle (think
28
+ billing callbacks, or queued operations) delivered directly. Patches for
29
+ queued delivery of HTTP API calls (Resque, Delayed::Job, QueueClassic, etc.)
30
+ are welcome.
31
+
32
+ == Supported events
33
+
34
+ Saucy-kiss records all the KISSmetrics SaaS events http://support.kissmetrics.com/apis/saas_events:
35
+
36
+ * Visited Site (handled automatically by the KISSmetrics JS library)
37
+ * Signed Up
38
+ * Upgraded / Downgraded (distinguised by plan price)
39
+ * Billed
40
+ * Canceled
41
+
42
+ TODO: Additional events provided by Saucy:
43
+
44
+ * Viewed Plan List
45
+ * Viewed Plan
46
+ * Invited New User
47
+ * Accepted Invite
48
+ * Tried to Exceed Project Limit
49
+
50
+ TODO: And events provided by Clearance:
51
+
52
+ * Signed In
53
+ * Signed Out
54
+
55
+ TODO: Examples of additional meta-events?:
56
+
57
+ * Give example of Visit Retention (>1 signins per month with a Visit model and Clearance instrumentation)
58
+
59
+ == Identity
60
+
61
+ It will identify users to the current account where ever possible, and as anonymous
62
+ otherwise. KISSmetrics will correctly handling aliasing between anonymous and
63
+ named identities, and distinguishing between successive named identites, such as
64
+ when you switch accounts. For more information, see:
65
+
66
+ http://support.kissmetrics.com/advanced/identity-management
67
+
68
+ == Installation
69
+
70
+ In your Gemfile:
71
+
72
+ gem "saucy-kiss" # depend on kissmetrics, snogmetrics
73
+ gem "snogmetrics", :git => "git://github.com/jasonm/snogmetrics" # only until snogmetrics accepts my patch
74
+
75
+ Then:
76
+
77
+ $ bundle
78
+
79
+ == Implementation in your application
80
+
81
+ Add an initializer in config/initializers/saucy_kiss.rb:
82
+
83
+ Rails.configuration.after_initialize do
84
+ Saucy::Notifications.register_observer(Saucy::Kiss::Observer.new({
85
+ 'staging' => 'abc123',
86
+ 'production' => 'def456'
87
+ }[Rails.env]))
88
+ end
89
+
90
+ Add a filter to each request in ApplicationController:
91
+
92
+ before_filter Saucy::Kiss::KissmetricsIdentityControllerFilter
93
+
94
+ Note: Saucy::Kiss will automatically instantiate the Snogmetrics gem like this:
95
+
96
+ api_key = Saucy::Kiss::API_KEYS[Rails.env]
97
+ session = data[:request].session
98
+ console_log_output = Rails.env.development?
99
+ Snogmetrics::KissmetricsApi.new(api_key, session, console_log_output)
100
+
101
+ and the Kissmetrics gem like this:
102
+
103
+ Kissmetrics::HttpClient.new(Saucy::Kiss::API_KEYS[Rails.env])
104
+
105
+ TODO: Is that ^ still accurate?
106
+
107
+ == Customization
108
+
109
+ You may like to customize the events delivered to KISSmetrics. For example,
110
+ you may want to track the referring ad campaign in a Signed Up event.
111
+
112
+ To do this, instead of using the Saucy::Kiss::Observer directly,
113
+ create a subclass of the observer and register it instead:
114
+
115
+ class MyAppObserver < Saucy::Kiss::Observer
116
+ def account_created(data)
117
+ javascript_client(data).record('Signed Up', {
118
+ 'Plan Name' => data[:account].plan.name,
119
+ 'Plan Price' => data[:account].plan.price,
120
+ 'Is Trial Plan?' => data[:account].plan.trial,
121
+ 'Campaign Source' => data[:request].session[:campaign_source]
122
+ })
123
+ end
124
+ end
125
+
126
+ Rails.configuration.after_initialize do
127
+ Saucy::Notifications.register_observer(M)yAppObserver.new({
128
+ 'staging' => 'abc123',
129
+ 'production' => 'def456'
130
+ }[Rails.env]))
131
+ end
132
+
133
+ == How it works
134
+
135
+ Saucy accepts observers in its configuration and publishes events for the
136
+ SaaS events it implements. This gem implements an observer for these events,
137
+ which your application registers in an initializer.
138
+
139
+ Saucy-kiss distinguishes between in-request events and out-of-request events,
140
+ because it prefers delivering events to KISSmetrics via the JS API where
141
+ possible; this is only possible for in-request events.
142
+
143
+ == Contributing to saucy-kiss
144
+
145
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
146
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
147
+ * Fork the project
148
+ * Start a feature/bugfix branch
149
+ * Commit and push until you are happy with your contribution
150
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
151
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
152
+
153
+ == Copyright
154
+
155
+ Copyright (c) 2011 Jason Morrison. See LICENSE.txt for
156
+ further details.
157
+
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'rspec/core'
13
+ require 'rspec/core/rake_task'
14
+ RSpec::Core::RakeTask.new(:spec) do |spec|
15
+ spec.pattern = FileList['spec/**/*_spec.rb']
16
+ end
17
+
18
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
19
+ spec.pattern = 'spec/**/*_spec.rb'
20
+ spec.rcov = true
21
+ end
22
+
23
+ task :default => :spec
data/lib/saucy-kiss.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'saucy/kiss/observer'
2
+ require 'saucy/kiss/controller_identity_filter'
@@ -0,0 +1,13 @@
1
+ module Saucy
2
+ module Kiss
3
+ class ControllerIdentityFilter
4
+ def self.filter(controller)
5
+ if controller.current_account
6
+ controller.km.identify("account-#{controller.current_account.id}")
7
+ else
8
+ controller.km.identify(nil)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,73 @@
1
+ require 'snogmetrics'
2
+ require 'kissmetrics'
3
+
4
+ module Saucy
5
+ module Kiss
6
+ class Observer
7
+ def initialize(api_key)
8
+ @api_key = api_key
9
+ end
10
+
11
+ def plan_viewed(data)
12
+ javascript_client(data).record('Viewed Plan', {
13
+ 'Plan Name' => data[:plan].name,
14
+ 'Plan Price' => data[:plan].price,
15
+ 'Is Trial Plan?' => data[:plan].trial
16
+ })
17
+ end
18
+
19
+ def account_created(data)
20
+ javascript_client(data).record('Signed Up', {
21
+ 'Plan Name' => data[:account].plan.name,
22
+ 'Plan Price' => data[:account].plan.price,
23
+ 'Is Trial Plan?' => data[:account].plan.trial
24
+ })
25
+ end
26
+
27
+ def plan_upgraded(data)
28
+ javascript_client(data).record('Upgraded', {
29
+ 'Previous Plan Name' => data[:from_plan].name,
30
+ 'Plan Name' => data[:to_plan].name,
31
+ 'Plan Price' => data[:to_plan].price,
32
+ 'Is Trial Plan?'=> data[:to_plan].trial,
33
+ })
34
+ end
35
+
36
+ def plan_downgraded(data)
37
+ javascript_client(data).record('Downgraded', {
38
+ 'Previous Plan Name' => data[:from_plan].name,
39
+ 'Plan Name' => data[:to_plan].name,
40
+ 'Plan Price' => data[:to_plan].price,
41
+ 'Is Trial Plan?'=> data[:to_plan].trial,
42
+ })
43
+ end
44
+
45
+ def billed(data)
46
+ account_identifier = "account-#{data[:account].id}"
47
+ http_client.record(account_identifier, 'Billed', {
48
+ 'Billing Description' => "Monthly billing for #{data[:account].plan.name} plan",
49
+ 'Billing Amount' => data[:account].plan.price
50
+ })
51
+ end
52
+
53
+ def canceled(data)
54
+ plan = data[:account].plan
55
+ javascript_client(data).record('Canceled', {
56
+ 'Plan Name' => plan.name,
57
+ 'Plan Price' => plan.price,
58
+ 'Is Trial Plan?'=> plan.trial
59
+ })
60
+ end
61
+
62
+ private
63
+
64
+ def javascript_client(data)
65
+ Snogmetrics::KissmetricsApi.new(@api_key, data[:request].session, Rails.env.development?)
66
+ end
67
+
68
+ def http_client
69
+ Kissmetrics::HttpClient.new(@api_key)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ describe Saucy::Kiss::ControllerIdentityFilter do
4
+ let(:km) { stub('kissmetrics client', :identify => true) }
5
+ let(:account) { stub('account', :id => 12345) }
6
+
7
+ context "when there is a current_account" do
8
+ let(:controller) { stub('controller', :km => km, :current_account => account) }
9
+
10
+ it "sets identity based on the account if there is a current_account" do
11
+ controller.should_receive(:km).with()
12
+ km.should_receive(:identify).with("account-12345")
13
+
14
+ Saucy::Kiss::ControllerIdentityFilter.filter(controller)
15
+ end
16
+ end
17
+
18
+ context "when there is not a current_account" do
19
+ let(:controller) { stub('controller', :km => km, :current_account => nil) }
20
+
21
+ it "clears identity" do
22
+ controller.should_receive(:km).with()
23
+ km.should_receive(:identify).with(nil)
24
+ Saucy::Kiss::ControllerIdentityFilter.filter(controller)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,91 @@
1
+ require 'spec_helper'
2
+
3
+ describe Saucy::Kiss::Observer do
4
+ subject do
5
+ Saucy::Kiss::Observer.new('abc123')
6
+ end
7
+
8
+ let(:session) { stub('session') }
9
+ let(:request) { stub('request', :session => session) }
10
+ let(:javascript_client) { stub('javascript client', :record => true) }
11
+ let(:http_client) { stub('http client', :record => true) }
12
+ let(:small_plan) { stub('plan', :name => 'Small', :price => 10, :trial => false) }
13
+ let(:large_plan) { stub('plan', :name => 'Large', :price => 20, :trial => false) }
14
+ let(:account) { stub('account', :plan => small_plan, :id => 12345) }
15
+
16
+ before(:each) do
17
+ Snogmetrics::KissmetricsApi.stub(:new).and_return(javascript_client)
18
+ Kissmetrics::HttpClient.stub(:new).and_return(http_client)
19
+ end
20
+
21
+ it "tells Snogmetrics to use console.log in development" do
22
+ with_rails_env(:development) do
23
+ Snogmetrics::KissmetricsApi.should_receive(:new).
24
+ with(anything(), anything(), true)
25
+
26
+ subject.plan_viewed(:request => request, :plan => small_plan)
27
+ end
28
+ end
29
+
30
+ it "records a Viewed Plan event through the KISSmetrics JS API for #plan_viewed" do
31
+ Snogmetrics::KissmetricsApi.should_receive(:new).
32
+ with('abc123', session, false)
33
+
34
+ javascript_client.should_receive(:record).
35
+ with('Viewed Plan', { 'Plan Name' => 'Small', 'Plan Price' => 10, 'Is Trial Plan?' => false })
36
+
37
+ subject.plan_viewed(:request => request, :plan => small_plan)
38
+ end
39
+
40
+ it "records a Signed Up event through the KISSmetrics JS API for #account_created" do
41
+ Snogmetrics::KissmetricsApi.should_receive(:new).
42
+ with('abc123', session, false)
43
+
44
+ javascript_client.should_receive(:record).
45
+ with('Signed Up', { 'Plan Name' => 'Small', 'Plan Price' => 10, 'Is Trial Plan?' => false })
46
+
47
+ subject.account_created(:request => request, :account => account)
48
+ end
49
+
50
+ it "records an Upgraded event through the KISSmetrics JS API for #plan_upgraded" do
51
+ Snogmetrics::KissmetricsApi.should_receive(:new).
52
+ with('abc123', session, false)
53
+
54
+ javascript_client.should_receive(:record).
55
+ with('Upgraded', { 'Previous Plan Name' => 'Small', 'Plan Name' => 'Large', 'Plan Price' => 20, 'Is Trial Plan?' => false })
56
+
57
+ subject.plan_upgraded(:request => request, :account => account, :from_plan => small_plan, :to_plan => large_plan)
58
+ end
59
+
60
+ it "records a Downgraded event through the KISSmetrics JS API for #plan_downgraded" do
61
+ Snogmetrics::KissmetricsApi.should_receive(:new).
62
+ with('abc123', session, false)
63
+
64
+ javascript_client.should_receive(:record).
65
+ with('Downgraded', { 'Previous Plan Name' => 'Large', 'Plan Name' => 'Small', 'Plan Price' => 10, 'Is Trial Plan?' => false })
66
+
67
+ subject.plan_downgraded(:request => request, :account => account, :from_plan => large_plan, :to_plan => small_plan)
68
+ end
69
+
70
+ it "records a Billed event through the KISSmetrics HTTP API for #billed" do
71
+ Kissmetrics::HttpClient.should_receive(:new).
72
+ with('abc123')
73
+
74
+ account_identity = "account-#{account.id}"
75
+
76
+ http_client.should_receive(:record).
77
+ with(account_identity, 'Billed', { 'Billing Description' => 'Monthly billing for Small plan', 'Billing Amount' => 10 })
78
+
79
+ subject.billed(:request => request, :account => account)
80
+ end
81
+
82
+ it "records a Canceled event through the KISSmetrics JS API for #canceled" do
83
+ Snogmetrics::KissmetricsApi.should_receive(:new).
84
+ with('abc123', session, false)
85
+
86
+ javascript_client.should_receive(:record).
87
+ with('Canceled', { 'Plan Name' => 'Small', 'Plan Price' => 10, 'Is Trial Plan?' => false })
88
+
89
+ subject.canceled(:request => request, :account => account)
90
+ end
91
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'saucy-kiss'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+ config.include RailsEnvHelpers
12
+ end
@@ -0,0 +1,18 @@
1
+ require 'active_support'
2
+ require 'active_support/hash_with_indifferent_access'
3
+
4
+ class Rails
5
+ def self.env
6
+ ActiveSupport::StringInquirer.new($rails_env.to_s || "test")
7
+ end
8
+ end
9
+
10
+ module RailsEnvHelpers
11
+ def with_rails_env(new_env)
12
+ old_env = $rails_env
13
+ $rails_env = new_env
14
+ yield
15
+ ensure
16
+ $rails_env = old_env
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: saucy-kiss
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Jason Morrison
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-06-27 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: rspec
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 2.3.0
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: yard
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: 0.6.0
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: bundler
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 1.0.0
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: jeweler
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: 1.6.2
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: rcov
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ type: :development
70
+ version_requirements: *id005
71
+ - !ruby/object:Gem::Dependency
72
+ name: activesupport
73
+ prerelease: false
74
+ requirement: &id006 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ version: "3.0"
80
+ type: :development
81
+ version_requirements: *id006
82
+ - !ruby/object:Gem::Dependency
83
+ name: kissmetrics
84
+ prerelease: false
85
+ requirement: &id007 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ~>
89
+ - !ruby/object:Gem::Version
90
+ version: 1.0.1
91
+ type: :runtime
92
+ version_requirements: *id007
93
+ - !ruby/object:Gem::Dependency
94
+ name: snogmetrics
95
+ prerelease: false
96
+ requirement: &id008 !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: 0.1.7
102
+ type: :runtime
103
+ version_requirements: *id008
104
+ - !ruby/object:Gem::Dependency
105
+ name: saucy
106
+ prerelease: false
107
+ requirement: &id009 !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ~>
111
+ - !ruby/object:Gem::Version
112
+ version: 0.9.0
113
+ type: :runtime
114
+ version_requirements: *id009
115
+ description: Record Saucy SaaS events to KISSmetrics
116
+ email: jason.p.morrison@gmail.com
117
+ executables: []
118
+
119
+ extensions: []
120
+
121
+ extra_rdoc_files:
122
+ - LICENSE.txt
123
+ - README.rdoc
124
+ files:
125
+ - lib/saucy/kiss/controller_identity_filter.rb
126
+ - lib/saucy/kiss/observer.rb
127
+ - lib/saucy-kiss.rb
128
+ - spec/saucy/kiss/controller_identity_filter_spec.rb
129
+ - spec/saucy/kiss/observer_spec.rb
130
+ - spec/spec_helper.rb
131
+ - spec/support/rails_env.rb
132
+ - Gemfile
133
+ - LICENSE.txt
134
+ - README.rdoc
135
+ - CHANGELOG.md
136
+ - Rakefile
137
+ has_rdoc: true
138
+ homepage: http://github.com/jasonm/saucy-kiss
139
+ licenses:
140
+ - MIT
141
+ post_install_message:
142
+ rdoc_options: []
143
+
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ none: false
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: "0"
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: "0"
158
+ requirements: []
159
+
160
+ rubyforge_project:
161
+ rubygems_version: 1.6.2
162
+ signing_key:
163
+ specification_version: 3
164
+ summary: Record Saucy SaaS events to KISSmetrics
165
+ test_files: []
166
+