amplitude-api 0.1.0 → 0.3.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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class AmplitudeAPI
2
- VERSION = '0.1.0'.freeze
4
+ VERSION = "0.3.0"
3
5
  end
data/lib/amplitude_api.rb CHANGED
@@ -1,18 +1,20 @@
1
- require 'json'
2
- require 'typhoeus'
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "faraday"
3
5
 
4
6
  # AmplitudeAPI
5
7
  class AmplitudeAPI
6
- require_relative 'amplitude_api/config'
7
- require_relative 'amplitude_api/event'
8
- require_relative 'amplitude_api/identification'
8
+ require_relative "amplitude_api/config"
9
+ require_relative "amplitude_api/event"
10
+ require_relative "amplitude_api/identification"
9
11
 
10
- TRACK_URI_STRING = 'https://api.amplitude.com/httpapi'.freeze
11
- IDENTIFY_URI_STRING = 'https://api.amplitude.com/identify'.freeze
12
- SEGMENTATION_URI_STRING = 'https://amplitude.com/api/2/events/segmentation'.freeze
13
- DELETION_URI_STRING = 'https://amplitude.com/api/2/deletions/users'.freeze
12
+ TRACK_URI_STRING = "https://api.amplitude.com/2/httpapi"
13
+ IDENTIFY_URI_STRING = "https://api.amplitude.com/identify"
14
+ SEGMENTATION_URI_STRING = "https://amplitude.com/api/2/events/segmentation"
15
+ DELETION_URI_STRING = "https://amplitude.com/api/2/deletions/users"
14
16
 
15
- USER_WITH_NO_ACCOUNT = "user who doesn't have an account".freeze
17
+ USER_WITH_NO_ACCOUNT = "user who doesn't have an account"
16
18
 
17
19
  class << self
18
20
  def config
@@ -38,12 +40,12 @@ class AmplitudeAPI
38
40
  # @param [ String ] event_name a string that describes the event, e.g. "clicked on Home"
39
41
  # @param [ String ] user a string or integer that uniquely identifies a user.
40
42
  # @param [ String ] device a string that uniquely identifies the device.
41
- # @param [ Hash ] event_properties a hash that is serialized to JSON,
43
+ # @option options [ Hash ] event_properties a hash that is serialized to JSON,
42
44
  # and can contain any other property to be stored on the Event
43
- # @param [ Hash ] user_properties a hash that is serialized to JSON,
45
+ # @option options [ Hash ] user_properties a hash that is serialized to JSON,
44
46
  # and contains user properties to be associated with the user
45
47
  #
46
- # @return [ Typhoeus::Response ]
48
+ # @return [ Faraday::Response ]
47
49
  def send_event(event_name, user, device, options = {})
48
50
  event = AmplitudeAPI::Event.new(
49
51
  user_id: user,
@@ -68,10 +70,10 @@ class AmplitudeAPI
68
70
  def track_body(*events)
69
71
  event_body = events.flatten.map(&:to_hash)
70
72
 
71
- {
73
+ JSON.generate(
72
74
  api_key: api_key,
73
- event: JSON.generate(event_body)
74
- }
75
+ events: event_body
76
+ )
75
77
  end
76
78
 
77
79
  # @overload track(event)
@@ -80,11 +82,11 @@ class AmplitudeAPI
80
82
  # @overload track([events])
81
83
  # @param [ Array<AmplitudeAPI::Event> ] Send an array of events in a single request to Amplitude
82
84
  #
83
- # @return [ Typhoeus::Response ]
85
+ # @return [ Faraday::Response ]
84
86
  #
85
87
  # Send one or more Events to the Amplitude API
86
88
  def track(*events)
87
- Typhoeus.post(TRACK_URI_STRING, body: track_body(events))
89
+ Faraday.post(TRACK_URI_STRING, track_body(events), "Content-Type" => "application/json")
88
90
  end
89
91
 
90
92
  # ==== Identification related methods
@@ -123,40 +125,40 @@ class AmplitudeAPI
123
125
  # @overload identify([identifications])
124
126
  # @param [ Array<AmplitudeAPI::Identify> ] Send an array of identifications in a single request to Amplitude
125
127
  #
126
- # @return [ Typhoeus::Response ]
128
+ # @return [ Faraday::Response ]
127
129
  #
128
130
  # Send one or more Identifications to the Amplitude Identify API
129
131
  def identify(*identifications)
130
- Typhoeus.post(IDENTIFY_URI_STRING, body: identify_body(identifications))
132
+ Faraday.post(IDENTIFY_URI_STRING, identify_body(identifications))
131
133
  end
132
134
 
133
135
  # ==== Event Segmentation related methods
134
136
 
135
137
  # Get metrics for an event with segmentation.
136
138
  #
137
- # @param [ Hash ] e a hash that defines event.
139
+ # @param [ Hash ] event a hash that defines event.
138
140
  # @param [ Time ] start_time a start time.
139
141
  # @param [ Time ] end_time a end time.
140
- # @param [ String ] m a string that defines aggregate function.
142
+ # @option options [ String ] m a string that defines aggregate function.
141
143
  # For non-property metrics: "uniques", "totals", "pct_dau", or "average" (default: "uniques").
142
144
  # For property metrics: "histogram", "sums", or "value_avg"
143
145
  # (note: a valid "group_by" value is required in parameter e).
144
- # @param [ Integer ] i an integer that defines segmentation interval.
146
+ # @option options [ Integer ] i an integer that defines segmentation interval.
145
147
  # Set to -300000, -3600000, 1, 7, or 30 for realtime, hourly, daily, weekly,
146
148
  # and monthly counts, respectively (default: 1). Realtime segmentation is capped at 2 days,
147
149
  # hourly segmentation is capped at 7 days, and daily at 365 days.
148
- # @param [ Array ] s an array that defines segment definitions.
149
- # @param [ String ] g a string that defines property to group by.
150
- # @param [ Integer ] limit an integer that defines number of Group By values
150
+ # @option options [ Array ] s an array that defines segment definitions.
151
+ # @option options [ String ] g a string that defines property to group by.
152
+ # @option options [ Integer ] limit an integer that defines number of Group By values
151
153
  # returned (default: 100). The maximum limit is 1000.
152
154
  #
153
- # @return [ Typhoeus::Response ]
155
+ # @return [ Faraday::Response ]
154
156
  def segmentation(event, start_time, end_time, **options)
155
- Typhoeus.get SEGMENTATION_URI_STRING, userpwd: "#{api_key}:#{secret_key}", params: {
157
+ Faraday.get SEGMENTATION_URI_STRING, userpwd: "#{api_key}:#{secret_key}", params: {
156
158
  e: event.to_json,
157
159
  m: options[:m],
158
- start: start_time.strftime('%Y%m%d'),
159
- end: end_time.strftime('%Y%m%d'),
160
+ start: start_time.strftime("%Y%m%d"),
161
+ end: end_time.strftime("%Y%m%d"),
160
162
  i: options[:i],
161
163
  s: (options[:s] || []).map(&:to_json),
162
164
  g: options[:g],
@@ -164,27 +166,48 @@ class AmplitudeAPI
164
166
  }.delete_if { |_, value| value.nil? }
165
167
  end
166
168
 
167
- # ==== GDPR compliance methods
168
-
169
- # Delete a user from amplitude when they request it to comply with GDPR
169
+ # Delete a user from amplitude
170
+ #
170
171
  # You must pass in either an array of user_ids or an array of amplitude_ids
171
172
  #
172
- # @param [ user_ids ] (optional) the user_ids to delete
173
+ # @param [ Array<String> ] (optional) the user_ids to delete
173
174
  # based on your database
174
- # @param [ amplitude_ids ] (optional) the amplitude_ids to delete
175
+ # @param [ Array<Integer> ] (optional) the amplitude_ids to delete
175
176
  # based on the amplitude database
176
- # @param [ requester ] the email address of the person who
177
+ # @param [ String ] requester the email address of the person who
177
178
  # is requesting the deletion, optional but useful for reporting
178
- #
179
- # @return [ Typhoeus::Response ]
180
- def delete(user_ids: nil, amplitude_ids: nil, requester: nil)
181
- Typhoeus.post DELETION_URI_STRING,
182
- userpwd: "#{api_key}:#{config.secret_key}",
183
- body: {
184
- amplitude_ids: amplitude_ids,
185
- user_ids: user_ids,
186
- requester: requester
187
- }.delete_if { |_, value| value.nil? }
179
+ # @param [ Boolean ] (optional) ignore any invalid user IDs(users that do no
180
+ # exist in the project) that were passed in
181
+ # @param [ Boolean ] (optional) delete from the entire org rather than just
182
+ # this project.
183
+ # @return [ Faraday::Response ]
184
+ def delete(user_ids: nil, amplitude_ids: nil, requester: nil, ignore_invalid_id: nil, delete_from_org: nil)
185
+ user_ids = Array(user_ids)
186
+ amplitude_ids = Array(amplitude_ids)
187
+
188
+ faraday = Faraday.new do |conn|
189
+ conn.basic_auth config.api_key, config.secret_key
190
+ end
191
+
192
+ faraday.post(
193
+ DELETION_URI_STRING,
194
+ delete_body(user_ids, amplitude_ids, requester, ignore_invalid_id, delete_from_org),
195
+ "Content-Type" => "application/json"
196
+ )
197
+ end
198
+
199
+ private
200
+
201
+ def delete_body(user_ids, amplitude_ids, requester, ignore_invalid_id, delete_from_org)
202
+ body = {
203
+ amplitude_ids: amplitude_ids,
204
+ user_ids: user_ids,
205
+ requester: requester
206
+ }.delete_if { |_, value| value.nil? || value.empty? }
207
+
208
+ body[:ignore_invalid_id] = ignore_invalid_id.to_s if ignore_invalid_id
209
+ body[:delete_from_org] = delete_from_org.to_s if delete_from_org
210
+ JSON.generate(body)
188
211
  end
189
212
  end
190
213
  end
data/readme.md CHANGED
@@ -19,7 +19,7 @@ AmplitudeAPI.config.api_key = "abcdef123456"
19
19
 
20
20
 
21
21
  event = AmplitudeAPI::Event.new({
22
- user_id: "123",
22
+ user_id: "12345",
23
23
  event_type: "clicked on home",
24
24
  time: Time.now,
25
25
  insert_id: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
@@ -31,7 +31,7 @@ event = AmplitudeAPI::Event.new({
31
31
  AmplitudeAPI.track(event)
32
32
  ```
33
33
 
34
- ## GDPR Compliance
34
+ ## User Privacy APIs
35
35
 
36
36
  The following code snippet will delete a user from amplitude
37
37
 
@@ -42,12 +42,13 @@ AmplitudeAPI.config.api_key = "abcdef123456"
42
42
  # Configure your Amplitude Secret Key
43
43
  AmplitudeAPI.config.secret_key = "secretMcSecret"
44
44
 
45
- AmplitudeAPI.delete(user_ids: [233],
46
- requester: "privacy@mycompany.com"
45
+ AmplitudeAPI.delete(user_ids: ["12345"],
46
+ requester: "privacy@example.com"
47
47
  )
48
48
  ```
49
49
 
50
- Currently, we are using this in Rails and using ActiveJob to dispatch events asynchronously. I plan on moving background/asynchronous support into this gem.
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.
51
52
 
52
53
  ## What's Next
53
54
 
@@ -55,7 +56,7 @@ Currently, we are using this in Rails and using ActiveJob to dispatch events asy
55
56
  * Configurable default account to use when no `user_id` present
56
57
 
57
58
  ## Other useful resources
58
- * [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)
59
60
  * [Segment.io Amplitude integration](https://segment.com/docs/integrations/amplitude/)
60
61
 
61
62
  ## Contributing
@@ -1,277 +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 'attributes' do
44
- it 'accepts string attributes' do
44
+ describe "init" do
45
+ context "attributes" do
46
+ it "accepts string attributes" do
45
47
  time = Time.at(1_451_606_400_000 / 1_000)
46
48
  event = described_class.new(
47
- 'user_id' => 123,
48
- 'device_id' => 'abcd',
49
- 'event_type' => 'sausage',
50
- 'event_properties' => { 'a' => 'b' },
51
- 'user_properties' => { 'c' => 'd' },
52
- 'time' => time,
53
- 'ip' => '127.0.0.1',
54
- 'platform' => 'Web',
55
- 'country' => 'United States',
56
- 'insert_id' => 'bestId'
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"
57
59
  )
58
60
 
59
- expect(event.to_hash).to eq(event_type: 'sausage',
61
+ expect(event.to_hash).to eq(event_type: "sausage",
60
62
  user_id: 123,
61
- device_id: 'abcd',
62
- event_properties: { 'a' => 'b' },
63
- user_properties: { 'c' => 'd' },
63
+ device_id: "abcd",
64
+ event_properties: { "a" => "b" },
65
+ user_properties: { "c" => "d" },
64
66
  time: 1_451_606_400_000,
65
- ip: '127.0.0.1',
66
- platform: 'Web',
67
- country: 'United States',
68
- insert_id: 'bestId')
67
+ ip: "127.0.0.1",
68
+ platform: "Web",
69
+ country: "United States",
70
+ insert_id: "bestId")
69
71
  end
70
72
 
71
- it 'accepts symbol attributes' do
73
+ it "accepts symbol attributes" do
72
74
  time = Time.at(1_451_606_400_000 / 1_000)
73
75
  event = described_class.new(
74
76
  user_id: 123,
75
- device_id: 'abcd',
76
- event_type: 'sausage',
77
- event_properties: { 'a' => 'b' },
78
- user_properties: { 'c' => 'd' },
77
+ device_id: "abcd",
78
+ event_type: "sausage",
79
+ event_properties: { "a" => "b" },
80
+ user_properties: { "c" => "d" },
79
81
  time: time,
80
- ip: '127.0.0.1',
81
- platform: 'Web',
82
- country: 'United States',
83
- insert_id: 'bestId'
82
+ ip: "127.0.0.1",
83
+ platform: "Web",
84
+ country: "United States",
85
+ insert_id: "bestId"
84
86
  )
85
87
 
86
- expect(event.to_hash).to eq(event_type: 'sausage',
88
+ expect(event.to_hash).to eq(event_type: "sausage",
87
89
  user_id: 123,
88
- device_id: 'abcd',
89
- event_properties: { 'a' => 'b' },
90
- user_properties: { 'c' => 'd' },
90
+ device_id: "abcd",
91
+ event_properties: { "a" => "b" },
92
+ user_properties: { "c" => "d" },
91
93
  time: 1_451_606_400_000,
92
- ip: '127.0.0.1',
93
- platform: 'Web',
94
- country: 'United States',
95
- insert_id: 'bestId')
94
+ ip: "127.0.0.1",
95
+ platform: "Web",
96
+ country: "United States",
97
+ insert_id: "bestId")
96
98
  end
97
99
  end
98
100
 
99
- context 'the user does not send in a price' do
100
- it 'raises an error if the user sends in a product_id' do
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
101
103
  expect do
102
104
  described_class.new(
103
105
  user_id: 123,
104
- event_type: 'bad event',
105
- product_id: 'hopscotch.4lyfe'
106
+ event_type: "bad event",
107
+ product_id: "hopscotch.4lyfe"
106
108
  )
107
- end.to raise_error(ArgumentError)
109
+ end.to raise_error ArgumentError, /You must provide a price or a revenue/
110
+
111
+ expect do
112
+ described_class.new(
113
+ user_id: 123,
114
+ event_type: "bad event",
115
+ revenue_type: "whatever"
116
+ )
117
+ end.to raise_error ArgumentError, /You must provide a price or a revenue/
108
118
  end
109
119
 
110
- 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
111
121
  expect do
112
122
  described_class.new(
113
123
  user_id: 123,
114
- event_type: 'bad event',
115
- revenue_type: 'tax return'
124
+ event_type: "bad event",
125
+ product_id: "hopscotch.4lyfe",
126
+ price: 10.2
116
127
  )
117
- 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
118
158
  end
119
159
  end
120
160
  end
121
161
 
122
- describe '#to_hash' do
123
- it 'includes the event type' do
162
+ describe "#to_hash" do
163
+ it "includes the event type" do
124
164
  event = described_class.new(
125
165
  user_id: 123,
126
- event_type: 'clicked on home'
166
+ event_type: "clicked on home"
127
167
  )
128
- expect(event.to_hash[:event_type]).to eq('clicked on home')
168
+ expect(event.to_hash[:event_type]).to eq("clicked on home")
129
169
  end
130
170
 
131
- it 'includes arbitrary properties' do
171
+ it "includes arbitrary properties" do
132
172
  event = described_class.new(
133
173
  user_id: 123,
134
- event_type: 'clicked on home',
174
+ event_type: "clicked on home",
135
175
  event_properties: { abc: :def }
136
176
  )
137
177
  expect(event.to_hash[:event_properties]).to eq(abc: :def)
138
178
  end
139
179
 
140
- describe 'time' do
141
- it 'includes a time for the event' do
180
+ describe "time" do
181
+ it "includes a time for the event" do
142
182
  time = Time.at(1_451_606_400_000 / 1_000)
143
183
  event = described_class.new(
144
184
  user_id: 123,
145
- event_type: 'clicked on home',
185
+ event_type: "clicked on home",
146
186
  time: time
147
187
  )
148
188
  expect(event.to_hash[:time]).to eq(1_451_606_400_000)
149
189
  end
150
190
 
151
- it 'does not include time if it is not set' do
191
+ it "does not include time if it is not set" do
152
192
  event = described_class.new(
153
193
  user_id: 123,
154
- event_type: 'clicked on home'
194
+ event_type: "clicked on home"
155
195
  )
156
196
  expect(event.to_hash).not_to have_key(:time)
157
197
  end
158
198
  end
159
199
 
160
- describe 'insert_id' do
161
- it 'includes an insert_id for the event' do
200
+ describe "insert_id" do
201
+ it "includes an insert_id for the event" do
162
202
  event = described_class.new(
163
203
  user_id: 123,
164
- event_type: 'clicked on home',
165
- insert_id: 'foo-bar'
204
+ event_type: "clicked on home",
205
+ insert_id: "foo-bar"
166
206
  )
167
- expect(event.to_hash[:insert_id]).to eq('foo-bar')
207
+ expect(event.to_hash[:insert_id]).to eq("foo-bar")
168
208
  end
169
209
 
170
- 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
171
211
  event = described_class.new(
172
212
  user_id: 123,
173
- event_type: 'clicked on home'
213
+ event_type: "clicked on home"
174
214
  )
175
215
  expect(event.to_hash).not_to have_key(:insert_id)
176
216
  end
177
217
  end
178
218
 
179
- describe 'platform' do
180
- it 'includes the platform for the event' do
219
+ describe "platform" do
220
+ it "includes the platform for the event" do
181
221
  event = described_class.new(
182
222
  user_id: 123,
183
- event_type: 'clicked on home',
184
- platform: 'Web'
223
+ event_type: "clicked on home",
224
+ platform: "Web"
185
225
  )
186
- expect(event.to_hash[:platform]).to eq('Web')
226
+ expect(event.to_hash[:platform]).to eq("Web")
187
227
  end
188
228
 
189
- it 'does not include the platform if it is not set' do
229
+ it "does not include the platform if it is not set" do
190
230
  event = described_class.new(
191
231
  user_id: 123,
192
- event_type: 'clicked on home'
232
+ event_type: "clicked on home"
193
233
  )
194
234
  expect(event.to_hash).not_to have_key(:platform)
195
235
  end
196
236
  end
197
237
 
198
- describe 'country' do
199
- it 'includes the country for the event' do
238
+ describe "country" do
239
+ it "includes the country for the event" do
200
240
  event = described_class.new(
201
241
  user_id: 123,
202
- event_type: 'clicked on home',
203
- country: 'United States'
242
+ event_type: "clicked on home",
243
+ country: "United States"
204
244
  )
205
- expect(event.to_hash[:country]).to eq('United States')
245
+ expect(event.to_hash[:country]).to eq("United States")
206
246
  end
207
247
 
208
- it 'does not include the country if it is not set' do
248
+ it "does not include the country if it is not set" do
209
249
  event = described_class.new(
210
250
  user_id: 123,
211
- event_type: 'clicked on home'
251
+ event_type: "clicked on home"
212
252
  )
213
253
  expect(event.to_hash).not_to have_key(:country)
214
254
  end
215
255
  end
216
256
 
217
- describe 'revenue params' do
218
- it 'includes the price if it is set' do
257
+ describe "revenue params" do
258
+ it "includes the price if it is set" do
219
259
  price = 100_000.99
220
260
  event = described_class.new(
221
261
  user_id: 123,
222
- event_type: 'clicked on home',
262
+ event_type: "clicked on home",
223
263
  price: price
224
264
  )
225
265
  expect(event.to_hash[:price]).to eq(price)
226
266
  end
227
267
 
228
- it 'sets the quantity to 1 if the price is set and the quantity is not' do
229
- price = 100_000.99
268
+ it "includes the quantity if it is set" do
269
+ quantity = 100
230
270
  event = described_class.new(
231
271
  user_id: 123,
232
- event_type: 'clicked on home',
233
- price: price
272
+ event_type: "clicked on home",
273
+ quantity: quantity,
274
+ price: 10.99
234
275
  )
235
- expect(event.to_hash[:quantity]).to eq(1)
276
+ expect(event.to_hash[:quantity]).to eq(quantity)
236
277
  end
237
278
 
238
- it 'includes the quantity if it is set' do
239
- quantity = 100
279
+ it "includes the revenue if it is set" do
280
+ revenue = 100
240
281
  event = described_class.new(
241
282
  user_id: 123,
242
- event_type: 'clicked on home',
243
- quantity: quantity,
244
- price: 10.99
283
+ event_type: "clicked on home",
284
+ quantity: 456,
285
+ revenue: revenue
245
286
  )
246
- expect(event.to_hash[:quantity]).to eq(quantity)
287
+ expect(event.to_hash[:revenue]).to eq(revenue)
247
288
  end
248
289
 
249
- it 'includes the productID if set' do
250
- product_id = 'hopscotch.subscriptions.rule'
290
+ it "includes the productID if set" do
291
+ product_id = "hopscotch.subscriptions.rule"
251
292
  event = described_class.new(
252
293
  user_id: 123,
253
- event_type: 'clicked on home',
294
+ event_type: "clicked on home",
254
295
  price: 199.99,
255
296
  product_id: product_id
256
297
  )
257
298
  expect(event.to_hash[:productId]).to eq(product_id)
258
299
  end
259
300
 
260
- it 'includes the revenueType if set' do
261
- revenue_type = 'income'
301
+ it "includes the revenueType if set" do
302
+ revenue_type = "income"
262
303
  event = described_class.new(
263
304
  user_id: 123,
264
- event_type: 'clicked on home',
305
+ event_type: "clicked on home",
265
306
  price: 199.99,
266
307
  revenue_type: revenue_type
267
308
  )
268
309
  expect(event.to_hash[:revenueType]).to eq(revenue_type)
269
310
  end
270
311
 
271
- 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
272
313
  event = described_class.new(
273
314
  user_id: 123,
274
- event_type: 'clicked on home'
315
+ event_type: "clicked on home"
275
316
  )
276
317
  expect(event.to_hash).not_to have_key(:quantity)
277
318
  expect(event.to_hash).not_to have_key(:revenueType)
@@ -280,4 +321,44 @@ describe AmplitudeAPI::Event do
280
321
  end
281
322
  end
282
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
283
364
  end