amplitude-api 0.0.8 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+
5
+ class AmplitudeAPI
6
+ # AmplitudeAPI::Config
7
+ class Config
8
+ include Singleton
9
+
10
+ attr_accessor :api_key, :secret_key, :whitelist, :time_formatter,
11
+ :event_properties_formatter, :user_properties_formatter
12
+
13
+ def initialize
14
+ self.class.defaults.each { |k, v| send("#{k}=", v) }
15
+ end
16
+
17
+ class << self
18
+ def base_properties
19
+ %i[event_type event_properties user_properties user_id device_id]
20
+ end
21
+
22
+ def revenue_properties
23
+ %i[revenue_type product_id revenue price quantity]
24
+ end
25
+
26
+ def optional_properties
27
+ %i[
28
+ time
29
+ ip platform country insert_id
30
+ groups app_version os_name os_version
31
+ device_brand device_manufacturer device_model
32
+ carrier region city dma language
33
+ location_lat location_lng
34
+ idfa idfv adid android_id
35
+ event_id session_id
36
+ ]
37
+ end
38
+
39
+ def defaults
40
+ {
41
+ api_key: nil,
42
+ secret_key: nil,
43
+ whitelist: base_properties + revenue_properties + optional_properties,
44
+ time_formatter: ->(time) { time ? time.to_i * 1_000 : nil },
45
+ event_properties_formatter: ->(props) { props || {} },
46
+ user_properties_formatter: ->(props) { props || {} }
47
+ }
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,66 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This class is 115 lines long. It's on the limit, it should be refactored before
4
+ # including more code.
5
+ #
6
+ # rubocop:disable Metrics/ClassLength
1
7
  class AmplitudeAPI
2
8
  # AmplitudeAPI::Event
3
9
  class Event
4
- # @!attribute [ rw ] user_id
5
- # @return [ String ] the user_id to be sent to Amplitude
6
- attr_accessor :user_id
7
- # @!attribute [ rw ] event_type
8
- # @return [ String ] the event_type to be sent to Amplitude
9
- attr_accessor :event_type
10
- # @!attribute [ rw ] event_properties
11
- # @return [ String ] the event_properties to be attached to the Amplitude Event
12
- attr_accessor :event_properties
13
- # @!attribute [ rw ] user_properties
14
- # @return [ String ] the user_properties to be passed for the user
15
- attr_accessor :user_properties
16
- # @!attribute [ rw ] time
17
- # @return [ Time ] Time that the event occurred (defaults to now)
18
- attr_accessor :time
19
- # @!attribute [ rw ] ip
20
- # @return [ String ] IP address of the user
21
- attr_accessor :ip
22
-
23
- # @!attribute [ rw ] insert_id
24
- # @return [ String ] the unique identifier to be sent to Amplitude
25
- attr_accessor :insert_id
26
-
27
- # @!attribute [ rw ] price
28
- # @return [ String ] (required for revenue data) price of the item purchased
29
- attr_accessor :price
30
-
31
- # @!attribute [ rw ] quantity
32
- # @return [ String ] (required for revenue data, defaults to 1 if not specified) quantity of the item purchased
33
- attr_accessor :quantity
34
-
35
- # @!attribute [ rw ] product_id
36
- # @return [ String ] an identifier for the product. (Note: you must send a price and quantity with this field)
37
- attr_accessor :product_id
38
-
39
- # @!attribute [ rw ] revenue_type
40
- # @return [ String ] type of revenue. (Note: you must send a price and quantity with this field)
41
- attr_accessor :revenue_type
10
+ AmplitudeAPI::Config.instance.whitelist.each do |attribute|
11
+ instance_eval("attr_accessor :#{attribute}", __FILE__, __LINE__)
12
+ end
42
13
 
43
14
  # Create a new Event
44
15
  #
45
- # @param [ String ] user_id a user_id to associate with the event
46
- # @param [ String ] event_type a name for the event
47
- # @param [ Hash ] event_properties various properties to attach to the event
48
- # @param [ Time ] Time that the event occurred (defaults to now)
49
- # @param [ Double ] price (optional, but required for revenue data) price of the item purchased
50
- # @param [ Integer ] quantity (optional, but required for revenue data) quantity of the item purchased
51
- # @param [ String ] product_id (optional) an identifier for the product.
52
- # @param [ String ] revenue_type (optional) type of revenue
53
- # @param [ String ] IP address of the user
54
- # @param [ String ] insert_id a unique identifier for the event
55
- def initialize(options = {})
56
- self.user_id = options.fetch(:user_id, '')
57
- self.event_type = options.fetch(:event_type, '')
58
- self.event_properties = options.fetch(:event_properties, {})
59
- self.user_properties = options.fetch(:user_properties, {})
60
- self.time = options[:time]
61
- self.ip = options.fetch(:ip, '')
62
- self.insert_id = options[:insert_id]
63
- validate_revenue_arguments(options)
16
+ # See (Amplitude HTTP API Documentation)[https://developers.amplitude.com/docs/http-api-v2]
17
+ # for a list of valid parameters and their types.
18
+ def initialize(attributes = {})
19
+ attributes.each do |k, v|
20
+ send("#{k}=", v) if respond_to?("#{k}=")
21
+ end
22
+ validate_arguments
23
+ @extra_properties = []
24
+ end
25
+
26
+ def method_missing(method_name, *args)
27
+ super if block_given?
28
+
29
+ property_name = method_name.to_s.delete_suffix("=")
30
+
31
+ @extra_properties << property_name
32
+
33
+ create_setter property_name
34
+ create_getter property_name
35
+
36
+ send("#{property_name}=".to_sym, *args)
37
+ end
38
+
39
+ def create_setter(attribute_name)
40
+ self.class.send(:define_method, "#{attribute_name}=".to_sym) do |value|
41
+ instance_variable_set("@" + attribute_name.to_s, value)
42
+ end
43
+ end
44
+
45
+ def create_getter(attribute_name)
46
+ self.class.send(:define_method, attribute_name.to_sym) do
47
+ instance_variable_get("@" + attribute_name.to_s)
48
+ end
49
+ end
50
+
51
+ def respond_to_missing?(method_name, *args)
52
+ @extra_properties.include?(method_name) || @extra_properties.include?("#{method_name}=") || super
64
53
  end
65
54
 
66
55
  def user_id=(value)
@@ -76,43 +65,97 @@ class AmplitudeAPI
76
65
  #
77
66
  # Used for serialization and comparison
78
67
  def to_hash
79
- serialized_event = {}
80
- serialized_event[:event_type] = event_type
81
- serialized_event[:user_id] = user_id
82
- serialized_event[:event_properties] = event_properties
83
- serialized_event[:user_properties] = user_properties
84
- serialized_event[:time] = formatted_time if time
85
- serialized_event[:ip] = ip if ip
86
- serialized_event[:insert_id] = insert_id if insert_id
68
+ event = {
69
+ event_type: event_type,
70
+ event_properties: formatted_event_properties,
71
+ user_properties: formatted_user_properties
72
+ }
73
+ event[:user_id] = user_id if user_id
74
+ event[:device_id] = device_id if device_id
75
+ event.merge(optional_properties).merge(revenue_hash).merge(extra_properties)
76
+ end
77
+ alias to_h to_hash
78
+
79
+ # @return [ Hash ] Optional properties
80
+ #
81
+ # Returns optional properties (belong to the API but are optional)
82
+ def optional_properties
83
+ AmplitudeAPI::Config.optional_properties.map do |prop|
84
+ val = prop == :time ? formatted_time : send(prop)
85
+ val ? [prop, val] : nil
86
+ end.compact.to_h
87
+ end
87
88
 
88
- serialized_event.merge(revenue_hash)
89
+ # @return [ Hash ] Extra properties
90
+ #
91
+ # Returns optional properties (not belong to the API, are assigned by the user)
92
+ # This way, if the API is updated with new properties, the gem will be able
93
+ # to work with the new specification until the code is modified
94
+ def extra_properties
95
+ @extra_properties.map do |prop|
96
+ val = send(prop)
97
+ val ? [prop.to_sym, val] : nil
98
+ end.compact.to_h
99
+ end
100
+
101
+ # @return [ true, false ]
102
+ #
103
+ # Returns true if the event type matches one reserved by Amplitude API.
104
+ def reserved_event?(type)
105
+ ["[Amplitude] Start Session",
106
+ "[Amplitude] End Session",
107
+ "[Amplitude] Revenue",
108
+ "[Amplitude] Revenue (Verified)",
109
+ "[Amplitude] Revenue (Unverified)",
110
+ "[Amplitude] Merged User"].include?(type)
89
111
  end
90
112
 
91
113
  # @return [ true, false ]
92
114
  #
93
115
  # Compares +to_hash+ for equality
94
116
  def ==(other)
95
- if other.respond_to?(:to_hash)
96
- to_hash == other.to_hash
97
- else
98
- false
99
- end
117
+ return false unless other.respond_to?(:to_h)
118
+
119
+ to_h == other.to_h
100
120
  end
101
121
 
102
122
  private
103
123
 
104
124
  def formatted_time
105
- time.to_i * 1_000
125
+ Config.instance.time_formatter.call(time)
126
+ end
127
+
128
+ def formatted_event_properties
129
+ Config.instance.event_properties_formatter.call(event_properties)
130
+ end
131
+
132
+ def formatted_user_properties
133
+ Config.instance.user_properties_formatter.call(user_properties)
106
134
  end
107
135
 
108
- def validate_revenue_arguments(options)
109
- self.price = options[:price]
110
- self.quantity = options[:quantity] || 1 if price
111
- self.product_id = options[:product_id]
112
- self.revenue_type = options[:revenue_type]
113
- return if price
114
- raise ArgumentError, 'You must provide a price in order to use the product_id' if product_id
115
- raise ArgumentError, 'You must provide a price in order to use the revenue_type' if revenue_type
136
+ def validate_arguments
137
+ validate_required_arguments
138
+ validate_revenue_arguments
139
+ end
140
+
141
+ def validate_required_arguments
142
+ raise ArgumentError, "You must provide user_id or device_id (or both)" unless user_id || device_id
143
+ raise ArgumentError, "You must provide event_type" unless event_type
144
+ raise ArgumentError, "Invalid event_type - cannot match a reserved event name" if reserved_event?(event_type)
145
+ end
146
+
147
+ def validate_revenue_arguments
148
+ return true if !revenue_type && !product_id
149
+ return true if revenue || price
150
+
151
+ raise ArgumentError, revenue_error_message
152
+ end
153
+
154
+ def revenue_error_message
155
+ error_field = "product_id" if product_id
156
+ error_field = "revenue_type" if revenue_type
157
+
158
+ "You must provide a price or a revenue in order to use the field #{error_field}"
116
159
  end
117
160
 
118
161
  def revenue_hash
@@ -121,7 +164,13 @@ class AmplitudeAPI
121
164
  revenue_hash[:revenueType] = revenue_type if revenue_type
122
165
  revenue_hash[:quantity] = quantity if quantity
123
166
  revenue_hash[:price] = price if price
167
+ revenue_hash[:revenue] = revenue if revenue
124
168
  revenue_hash
125
169
  end
170
+
171
+ def getopt(options, key, default = nil)
172
+ options.fetch(key.to_sym, options.fetch(key.to_s, default))
173
+ end
126
174
  end
127
175
  end
176
+ # rubocop:enable Metrics/ClassLength
@@ -1,9 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AmplitudeAPI
2
4
  # AmplitudeAPI::Identification
3
5
  class Identification
4
6
  # @!attribute [ rw ] user_id
5
7
  # @return [ String ] the user_id to be sent to Amplitude
6
- attr_accessor :user_id
8
+ attr_reader :user_id
9
+ # @!attribute [ rw ] device_id
10
+ # @return [ String ] the device_id to be sent to Amplitude
11
+ attr_accessor :device_id
7
12
  # @!attribute [ rw ] user_properties
8
13
  # @return [ String ] the user_properties to be attached to the Amplitude Identify
9
14
  attr_accessor :user_properties
@@ -11,9 +16,11 @@ class AmplitudeAPI
11
16
  # Create a new Identification
12
17
  #
13
18
  # @param [ String ] user_id a user_id to associate with the identification
19
+ # @param [ String ] device_id a device_id to associate with the identification
14
20
  # @param [ Hash ] user_properties various properties to attach to the user identification
15
- def initialize(user_id: '', user_properties: {})
21
+ def initialize(user_id: "", device_id: nil, user_properties: {})
16
22
  self.user_id = user_id
23
+ self.device_id = device_id if device_id
17
24
  self.user_properties = user_properties
18
25
  end
19
26
 
@@ -33,7 +40,7 @@ class AmplitudeAPI
33
40
  {
34
41
  user_id: user_id,
35
42
  user_properties: user_properties
36
- }
43
+ }.tap { |hsh| hsh[:device_id] = device_id if device_id }
37
44
  end
38
45
 
39
46
  # @return [ true, false ]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AmplitudeAPI
2
- VERSION = '0.0.8'.freeze
4
+ VERSION = "0.3.0"
3
5
  end
data/readme.md CHANGED
@@ -15,10 +15,11 @@ The following code snippet will immediately track an event to the Amplitude API.
15
15
 
16
16
  ```ruby
17
17
  # Configure your Amplitude API key
18
- AmplitudeAPI.api_key = "abcdef123456"
18
+ AmplitudeAPI.config.api_key = "abcdef123456"
19
+
19
20
 
20
21
  event = AmplitudeAPI::Event.new({
21
- user_id: "123",
22
+ user_id: "12345",
22
23
  event_type: "clicked on home",
23
24
  time: Time.now,
24
25
  insert_id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
@@ -30,16 +31,32 @@ event = AmplitudeAPI::Event.new({
30
31
  AmplitudeAPI.track(event)
31
32
  ```
32
33
 
33
- Currently, we are using this in Rails and using ActiveJob to dispatch events asynchronously. I plan on moving background/asynchronous support into this gem.
34
+ ## User Privacy APIs
35
+
36
+ The following code snippet will delete a user from amplitude
37
+
38
+ ```ruby
39
+ # Configure your Amplitude API key
40
+ AmplitudeAPI.config.api_key = "abcdef123456"
41
+
42
+ # Configure your Amplitude Secret Key
43
+ AmplitudeAPI.config.secret_key = "secretMcSecret"
44
+
45
+ AmplitudeAPI.delete(user_ids: ["12345"],
46
+ requester: "privacy@example.com"
47
+ )
48
+ ```
49
+
50
+ Currently, we are using this in Rails and using ActiveJob to dispatch events asynchronously. I plan on moving
51
+ background/asynchronous support into this gem.
34
52
 
35
53
  ## What's Next
36
54
 
37
55
  * Thread support for background dispatching in bulk
38
- * `device_id` support as an alternative to `user_id`
39
56
  * Configurable default account to use when no `user_id` present
40
57
 
41
58
  ## Other useful resources
42
- * [Amplitude HTTP Api Documentation](https://amplitude.zendesk.com/hc/en-us/articles/204771828)
59
+ * [Amplitude HTTP API V2 Api Documentation](https://developers.amplitude.com/docs/http-api-v2)
43
60
  * [Segment.io Amplitude integration](https://segment.com/docs/integrations/amplitude/)
44
61
 
45
62
  ## Contributing
@@ -1,183 +1,318 @@
1
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
2
4
 
3
5
  describe AmplitudeAPI::Event do
4
6
  user = Struct.new(:id)
5
7
 
6
- context 'with a user object' do
7
- describe '#body' do
8
+ context "with a user object" do
9
+ describe "#body" do
8
10
  it "populates with the user's id" do
9
11
  event = described_class.new(
10
12
  user_id: user.new(123),
11
- event_type: 'clicked on home'
13
+ event_type: "clicked on home"
12
14
  )
13
15
  expect(event.to_hash[:user_id]).to eq(123)
14
16
  end
15
17
  end
16
18
  end
17
19
 
18
- context 'with a user id' do
19
- describe '#body' do
20
+ context "with a user id" do
21
+ describe "#body" do
20
22
  it "populates with the user's id" do
21
23
  event = described_class.new(
22
24
  user_id: 123,
23
- event_type: 'clicked on home'
25
+ event_type: "clicked on home"
24
26
  )
25
27
  expect(event.to_hash[:user_id]).to eq(123)
26
28
  end
27
29
  end
28
30
  end
29
31
 
30
- context 'without a user' do
31
- describe '#body' do
32
- it 'populates with the unknown user' do
32
+ context "without a user" do
33
+ describe "#body" do
34
+ it "populates with the unknown user" do
33
35
  event = described_class.new(
34
36
  user_id: nil,
35
- event_type: 'clicked on home'
37
+ event_type: "clicked on home"
36
38
  )
37
39
  expect(event.to_hash[:user_id]).to eq(AmplitudeAPI::USER_WITH_NO_ACCOUNT)
38
40
  end
39
41
  end
40
42
  end
41
43
 
42
- describe 'init' do
43
- context 'the user does not send in a price' do
44
- it 'raises an error if the user sends in a product_id' do
44
+ describe "init" do
45
+ context "attributes" do
46
+ it "accepts string attributes" do
47
+ time = Time.at(1_451_606_400_000 / 1_000)
48
+ event = described_class.new(
49
+ "user_id" => 123,
50
+ "device_id" => "abcd",
51
+ "event_type" => "sausage",
52
+ "event_properties" => { "a" => "b" },
53
+ "user_properties" => { "c" => "d" },
54
+ "time" => time,
55
+ "ip" => "127.0.0.1",
56
+ "platform" => "Web",
57
+ "country" => "United States",
58
+ "insert_id" => "bestId"
59
+ )
60
+
61
+ expect(event.to_hash).to eq(event_type: "sausage",
62
+ user_id: 123,
63
+ device_id: "abcd",
64
+ event_properties: { "a" => "b" },
65
+ user_properties: { "c" => "d" },
66
+ time: 1_451_606_400_000,
67
+ ip: "127.0.0.1",
68
+ platform: "Web",
69
+ country: "United States",
70
+ insert_id: "bestId")
71
+ end
72
+
73
+ it "accepts symbol attributes" do
74
+ time = Time.at(1_451_606_400_000 / 1_000)
75
+ event = described_class.new(
76
+ user_id: 123,
77
+ device_id: "abcd",
78
+ event_type: "sausage",
79
+ event_properties: { "a" => "b" },
80
+ user_properties: { "c" => "d" },
81
+ time: time,
82
+ ip: "127.0.0.1",
83
+ platform: "Web",
84
+ country: "United States",
85
+ insert_id: "bestId"
86
+ )
87
+
88
+ expect(event.to_hash).to eq(event_type: "sausage",
89
+ user_id: 123,
90
+ device_id: "abcd",
91
+ event_properties: { "a" => "b" },
92
+ user_properties: { "c" => "d" },
93
+ time: 1_451_606_400_000,
94
+ ip: "127.0.0.1",
95
+ platform: "Web",
96
+ country: "United States",
97
+ insert_id: "bestId")
98
+ end
99
+ end
100
+
101
+ context "the user sends a revenue_type or a product_id" do
102
+ it "raises an error if there is not a price neither a revenue" do
103
+ expect do
104
+ described_class.new(
105
+ user_id: 123,
106
+ event_type: "bad event",
107
+ product_id: "hopscotch.4lyfe"
108
+ )
109
+ end.to raise_error ArgumentError, /You must provide a price or a revenue/
110
+
45
111
  expect do
46
112
  described_class.new(
47
113
  user_id: 123,
48
- event_type: 'bad event',
49
- product_id: 'hopscotch.4lyfe'
114
+ event_type: "bad event",
115
+ revenue_type: "whatever"
50
116
  )
51
- end.to raise_error(ArgumentError)
117
+ end.to raise_error ArgumentError, /You must provide a price or a revenue/
52
118
  end
53
119
 
54
- it 'raises an error if the user sends in a revenue_type' do
120
+ it "does not raise an error if there is a price" do
55
121
  expect do
56
122
  described_class.new(
57
123
  user_id: 123,
58
- event_type: 'bad event',
59
- revenue_type: 'tax return'
124
+ event_type: "bad event",
125
+ product_id: "hopscotch.4lyfe",
126
+ price: 10.2
60
127
  )
61
- end.to raise_error(ArgumentError)
128
+ end.not_to raise_error
129
+
130
+ expect do
131
+ described_class.new(
132
+ user_id: 123,
133
+ event_type: "bad event",
134
+ revenue_type: "whatever",
135
+ price: 10.2
136
+ )
137
+ end.not_to raise_error
138
+ end
139
+
140
+ it "does not raise an error if there is a revenue" do
141
+ expect do
142
+ described_class.new(
143
+ user_id: 123,
144
+ event_type: "bad event",
145
+ product_id: "hopscotch.4lyfe",
146
+ revenue: 100.1
147
+ )
148
+ end.not_to raise_error
149
+
150
+ expect do
151
+ described_class.new(
152
+ user_id: 123,
153
+ event_type: "bad event",
154
+ revenue_type: "whatever",
155
+ revenue: 100.1
156
+ )
157
+ end.not_to raise_error
62
158
  end
63
159
  end
64
160
  end
65
161
 
66
- describe '#body' do
67
- it 'includes the event type' do
162
+ describe "#to_hash" do
163
+ it "includes the event type" do
68
164
  event = described_class.new(
69
165
  user_id: 123,
70
- event_type: 'clicked on home'
166
+ event_type: "clicked on home"
71
167
  )
72
- expect(event.to_hash[:event_type]).to eq('clicked on home')
168
+ expect(event.to_hash[:event_type]).to eq("clicked on home")
73
169
  end
74
170
 
75
- it 'includes arbitrary properties' do
171
+ it "includes arbitrary properties" do
76
172
  event = described_class.new(
77
173
  user_id: 123,
78
- event_type: 'clicked on home',
174
+ event_type: "clicked on home",
79
175
  event_properties: { abc: :def }
80
176
  )
81
177
  expect(event.to_hash[:event_properties]).to eq(abc: :def)
82
178
  end
83
179
 
84
- describe 'time' do
85
- it 'includes a time for the event' do
86
- time = Time.parse('2016-01-01 00:00:00 -0000')
180
+ describe "time" do
181
+ it "includes a time for the event" do
182
+ time = Time.at(1_451_606_400_000 / 1_000)
87
183
  event = described_class.new(
88
184
  user_id: 123,
89
- event_type: 'clicked on home',
185
+ event_type: "clicked on home",
90
186
  time: time
91
187
  )
92
188
  expect(event.to_hash[:time]).to eq(1_451_606_400_000)
93
189
  end
94
190
 
95
- it 'does not include time if it is not set' do
191
+ it "does not include time if it is not set" do
96
192
  event = described_class.new(
97
193
  user_id: 123,
98
- event_type: 'clicked on home'
194
+ event_type: "clicked on home"
99
195
  )
100
196
  expect(event.to_hash).not_to have_key(:time)
101
197
  end
102
198
  end
103
199
 
104
- describe 'insert_id' do
105
- it 'includes an insert_id for the event' do
200
+ describe "insert_id" do
201
+ it "includes an insert_id for the event" do
106
202
  event = described_class.new(
107
203
  user_id: 123,
108
- event_type: 'clicked on home',
109
- insert_id: 'foo-bar'
204
+ event_type: "clicked on home",
205
+ insert_id: "foo-bar"
110
206
  )
111
- expect(event.to_hash[:insert_id]).to eq('foo-bar')
207
+ expect(event.to_hash[:insert_id]).to eq("foo-bar")
112
208
  end
113
209
 
114
- it 'does not include insert_id if it is not set' do
210
+ it "does not include insert_id if it is not set" do
115
211
  event = described_class.new(
116
212
  user_id: 123,
117
- event_type: 'clicked on home'
213
+ event_type: "clicked on home"
118
214
  )
119
215
  expect(event.to_hash).not_to have_key(:insert_id)
120
216
  end
121
217
  end
122
218
 
123
- describe 'revenue params' do
124
- it 'includes the price if it is set' do
125
- price = 100_000.99
219
+ describe "platform" do
220
+ it "includes the platform for the event" do
126
221
  event = described_class.new(
127
222
  user_id: 123,
128
- event_type: 'clicked on home',
129
- price: price
223
+ event_type: "clicked on home",
224
+ platform: "Web"
130
225
  )
131
- expect(event.to_hash[:price]).to eq(price)
226
+ expect(event.to_hash[:platform]).to eq("Web")
132
227
  end
133
228
 
134
- it 'sets the quantity to 1 if the price is set and the quantity is not' do
229
+ it "does not include the platform if it is not set" do
230
+ event = described_class.new(
231
+ user_id: 123,
232
+ event_type: "clicked on home"
233
+ )
234
+ expect(event.to_hash).not_to have_key(:platform)
235
+ end
236
+ end
237
+
238
+ describe "country" do
239
+ it "includes the country for the event" do
240
+ event = described_class.new(
241
+ user_id: 123,
242
+ event_type: "clicked on home",
243
+ country: "United States"
244
+ )
245
+ expect(event.to_hash[:country]).to eq("United States")
246
+ end
247
+
248
+ it "does not include the country if it is not set" do
249
+ event = described_class.new(
250
+ user_id: 123,
251
+ event_type: "clicked on home"
252
+ )
253
+ expect(event.to_hash).not_to have_key(:country)
254
+ end
255
+ end
256
+
257
+ describe "revenue params" do
258
+ it "includes the price if it is set" do
135
259
  price = 100_000.99
136
260
  event = described_class.new(
137
261
  user_id: 123,
138
- event_type: 'clicked on home',
262
+ event_type: "clicked on home",
139
263
  price: price
140
264
  )
141
- expect(event.to_hash[:quantity]).to eq(1)
265
+ expect(event.to_hash[:price]).to eq(price)
142
266
  end
143
267
 
144
- it 'includes the quantity if it is set' do
268
+ it "includes the quantity if it is set" do
145
269
  quantity = 100
146
270
  event = described_class.new(
147
271
  user_id: 123,
148
- event_type: 'clicked on home',
272
+ event_type: "clicked on home",
149
273
  quantity: quantity,
150
274
  price: 10.99
151
275
  )
152
276
  expect(event.to_hash[:quantity]).to eq(quantity)
153
277
  end
154
278
 
155
- it 'includes the productID if set' do
156
- product_id = 'hopscotch.subscriptions.rule'
279
+ it "includes the revenue if it is set" do
280
+ revenue = 100
281
+ event = described_class.new(
282
+ user_id: 123,
283
+ event_type: "clicked on home",
284
+ quantity: 456,
285
+ revenue: revenue
286
+ )
287
+ expect(event.to_hash[:revenue]).to eq(revenue)
288
+ end
289
+
290
+ it "includes the productID if set" do
291
+ product_id = "hopscotch.subscriptions.rule"
157
292
  event = described_class.new(
158
293
  user_id: 123,
159
- event_type: 'clicked on home',
294
+ event_type: "clicked on home",
160
295
  price: 199.99,
161
296
  product_id: product_id
162
297
  )
163
298
  expect(event.to_hash[:productId]).to eq(product_id)
164
299
  end
165
300
 
166
- it 'includes the revenueType if set' do
167
- revenue_type = 'income'
301
+ it "includes the revenueType if set" do
302
+ revenue_type = "income"
168
303
  event = described_class.new(
169
304
  user_id: 123,
170
- event_type: 'clicked on home',
305
+ event_type: "clicked on home",
171
306
  price: 199.99,
172
307
  revenue_type: revenue_type
173
308
  )
174
309
  expect(event.to_hash[:revenueType]).to eq(revenue_type)
175
310
  end
176
311
 
177
- it 'does not include revenue params if they are not set' do
312
+ it "does not include revenue params if they are not set" do
178
313
  event = described_class.new(
179
314
  user_id: 123,
180
- event_type: 'clicked on home'
315
+ event_type: "clicked on home"
181
316
  )
182
317
  expect(event.to_hash).not_to have_key(:quantity)
183
318
  expect(event.to_hash).not_to have_key(:revenueType)
@@ -186,4 +321,44 @@ describe AmplitudeAPI::Event do
186
321
  end
187
322
  end
188
323
  end
324
+
325
+ describe "arbitrary properties" do
326
+ # We need to create a class for each test because the methods we are calling
327
+ # in this test group are modifying the class
328
+ let(:klass) { Class.new described_class }
329
+
330
+ let(:event) {
331
+ klass.new(
332
+ user_id: 123,
333
+ event_type: "bad event"
334
+ )
335
+ }
336
+
337
+ it "creates arbitrary properties when assigning values" do
338
+ event.arbitrary_property = "arbitrary value"
339
+
340
+ expect(event.arbitrary_property).to eq "arbitrary value"
341
+ end
342
+
343
+ it "responds_to? arbitrary properties" do
344
+ event.arbitrary_property = "arbitrary value"
345
+
346
+ expect(event.respond_to?(:arbitrary_property)).to be true
347
+ expect(event.respond_to?(:arbitrary_property=)).to be true
348
+ end
349
+
350
+ it "do not accepts blocks when assigning values to create properties" do
351
+ expect do
352
+ event.arbitrary_property { puts "whatever" }
353
+ end.to raise_error NoMethodError
354
+ end
355
+
356
+ it "includes arbitrary properties in the generated hash" do
357
+ event.arbitrary_property = "arbitrary value"
358
+
359
+ hash = event.to_hash
360
+
361
+ expect(hash).to include(arbitrary_property: "arbitrary value")
362
+ end
363
+ end
189
364
  end