leanplum_api 3.1.0 → 4.0.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.
- checksums.yaml +4 -4
- data/README.md +63 -32
- data/lib/leanplum_api/api.rb +107 -156
- data/lib/leanplum_api/configuration.rb +9 -1
- data/lib/leanplum_api/connection.rb +2 -2
- data/lib/leanplum_api/data_export_api.rb +78 -0
- data/lib/leanplum_api/faraday_middleware/response_validation.rb +23 -19
- data/lib/leanplum_api/version.rb +1 -1
- data/spec/api_spec.rb +142 -98
- data/spec/configuration_spec.rb +2 -2
- data/spec/data_export_api_spec.rb +57 -0
- data/spec/fixtures/vcr/delete_user.yml +129 -0
- data/spec/fixtures/vcr/export_data.yml +5 -5
- data/spec/fixtures/vcr/export_data_dates.yml +6 -6
- data/spec/fixtures/vcr/export_user.yml +7 -7
- data/spec/fixtures/vcr/export_users.yml +44 -0
- data/spec/fixtures/vcr/get_ab_test.yml +5 -5
- data/spec/fixtures/vcr/get_ab_tests.yml +5 -5
- data/spec/fixtures/vcr/get_export_results.yml +5 -5
- data/spec/fixtures/vcr/get_messages.yml +5 -5
- data/spec/fixtures/vcr/get_vars.yml +5 -5
- data/spec/fixtures/vcr/missing_message.yml +4 -4
- data/spec/fixtures/vcr/reset_anomalous_user.yml +6 -6
- data/spec/fixtures/vcr/set_device_attributes.yml +46 -0
- data/spec/fixtures/vcr/set_user_attributes.yml +7 -7
- data/spec/fixtures/vcr/set_user_attributes_with_devices.yml +46 -0
- data/spec/fixtures/vcr/set_user_attributes_with_devices_and_events.yml +46 -0
- data/spec/fixtures/vcr/set_user_attributes_with_events.yml +46 -0
- data/spec/fixtures/vcr/track_events.yml +8 -8
- data/spec/fixtures/vcr/track_events_and_attributes.yml +9 -9
- data/spec/fixtures/vcr/track_events_anomaly_overrider.yml +20 -19
- data/spec/fixtures/vcr/track_offline_events.yml +8 -8
- data/spec/http_spec.rb +6 -5
- data/spec/spec_helper.rb +11 -8
- metadata +40 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba21ebe73260c66ae36b3c901abfe6824d238b26
|
4
|
+
data.tar.gz: '009e58d6f0537a717bd1a66ddf6f39d5601ab328'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0e56f3fea79360b0820803e7edb1d153f19691d7adf644699feba9c7abe00edd58593852c8e62988dd202f3471ad7818ed8de9c55225adf6fb10b657c4e420c5
|
7
|
+
data.tar.gz: 9404b64c448032f32cde14db3082cdb4fc5fa4e2b4d7e27de1ede0ea5f4dc653fd4a8bdf8d3c01b9983df5a75687deb62c922c6d4d6fae05b2035f3f518e88ae
|
data/README.md
CHANGED
@@ -8,11 +8,11 @@ Leanplum calls it a REST API but it is not very RESTful.
|
|
8
8
|
|
9
9
|
Leanplum also likes to change and break stuff in their API without changing the version number, so buyer beware.
|
10
10
|
|
11
|
-
The gem uses the
|
11
|
+
The gem uses the `multi` method with a POST for all event tracking and user attribute updating requests. Check Leanplum's docs for more information on `multi`.
|
12
12
|
|
13
13
|
Tested with Leanplum API version 1.0.6 - which is actually totally meaningless because the version is always 1.0.6, even when they make major revisions to how the API works.
|
14
14
|
|
15
|
-
`required_ruby_version` is set to
|
15
|
+
`required_ruby_version` is set to 2.0 but this code has only been tested with Ruby 2.1.5 and up!
|
16
16
|
|
17
17
|
## Configuration
|
18
18
|
|
@@ -44,6 +44,9 @@ LeanplumApi.configure do |config|
|
|
44
44
|
# Set this to true to send events and user attributes to the test environment.
|
45
45
|
# Defaults to false. See "Debugging" below for more info.
|
46
46
|
config.developer_mode = true
|
47
|
+
|
48
|
+
# Override validations for Leanplum responses. True by default and you should probably leave it that way.
|
49
|
+
config.validate_response = true
|
47
50
|
end
|
48
51
|
```
|
49
52
|
|
@@ -54,9 +57,16 @@ end
|
|
54
57
|
```ruby
|
55
58
|
api = LeanplumApi::API.new
|
56
59
|
|
60
|
+
# Setting device attributes requires a device_id.
|
61
|
+
device_attributes = {
|
62
|
+
device_id: 'big_boss_belt_buckler',
|
63
|
+
device_model: 'four_vogues'
|
64
|
+
}
|
65
|
+
api.set_device_attributes(device_attributes)
|
66
|
+
|
57
67
|
# You must provide either :user_id or :device_id for requests involving
|
58
68
|
# attribute updates or event tracking.
|
59
|
-
|
69
|
+
user_attributes = {
|
60
70
|
user_id: 12345,
|
61
71
|
first_name: 'Mike',
|
62
72
|
last_name: 'Jones',
|
@@ -64,23 +74,26 @@ attribute_hash = {
|
|
64
74
|
email: 'still_tippin@test.com',
|
65
75
|
birthday: Date.today # Dates/times will be converted to ISO8601 format
|
66
76
|
}
|
67
|
-
api.set_user_attributes(
|
68
|
-
|
69
|
-
# In 2017, Leanplum implemented the ability to set various first and last timestamps for event
|
70
|
-
# counts for that event in their API
|
71
|
-
#
|
72
|
-
|
77
|
+
api.set_user_attributes(user_attributes)
|
78
|
+
|
79
|
+
# In 2017, Leanplum implemented the ability to set various first and last timestamps for event
|
80
|
+
# occurrences, as well as counts for that event in their setUserAttributes API.
|
81
|
+
# They also added the ability to set devices for that user.
|
82
|
+
# This is what it would look like to push data about an event that happened 5 times between
|
83
|
+
# 2015-02-01 and today along with a set of devices for that user.
|
84
|
+
user_attributes = {
|
73
85
|
user_id: 12345,
|
86
|
+
devices: [device_attributes],
|
74
87
|
events: {
|
75
88
|
my_event_name: {
|
76
89
|
count: 5,
|
77
90
|
value: 'woodgrain',
|
78
|
-
firstTime: '2015-02-01'.to_time,
|
79
|
-
lastTime: Time.now.utc
|
91
|
+
firstTime: '2015-02-01'.to_time, # Dates/times will be converted to epoch seconds
|
92
|
+
lastTime: Time.now.utc # Dates/times will be converted to epoch seconds
|
80
93
|
}
|
81
94
|
}
|
82
95
|
}
|
83
|
-
api.set_user_attributes(
|
96
|
+
api.set_user_attributes(user_attributes)
|
84
97
|
|
85
98
|
# You must also provide the :event property for event tracking.
|
86
99
|
## :info is an optional property for an extra string.
|
@@ -89,63 +102,79 @@ api.set_user_attributes(attribute_hash)
|
|
89
102
|
event = {
|
90
103
|
user_id: 12345,
|
91
104
|
event: 'purchase',
|
105
|
+
time: Time.now.utc, # Event timestamps will be converted to epoch seconds
|
92
106
|
info: 'reallybigpurchase',
|
93
|
-
time: Time.now.utc, # Event timestamps will be converted to epoch seconds by the gem.
|
94
107
|
some_event_property: 'boss_hog_on_candy'
|
95
108
|
}
|
96
109
|
api.track_events(event)
|
97
110
|
# Events tracked like that will be made part of a session; for independent events use :allow_offline
|
98
|
-
# Ed. note 2017-09-12 - looks like Leanplum changed their API and everything is considered offline now
|
111
|
+
# Ed. note 2017-09-12 - looks like Leanplum changed their API and everything is considered offline now.
|
99
112
|
api.track_events(event, allow_offline: true)
|
100
113
|
|
101
|
-
# You can also track events and
|
102
|
-
api.track_multi(
|
114
|
+
# You can also track events, user attributes, and device attributes at the same time. Magic!
|
115
|
+
api.track_multi(
|
116
|
+
events: event,
|
117
|
+
user_attributes: user_attributes,
|
118
|
+
device_attributes: device_attributes,
|
119
|
+
options: { force_anomalous_override: true }
|
120
|
+
)
|
103
121
|
|
104
122
|
# If your event is sufficiently far in the past, leanplum will mark your user as "Anomalous"
|
105
|
-
# To force a reset of this flag, either call the method directly
|
123
|
+
# To force a reset of this flag, either call the method directly.
|
106
124
|
api.reset_anomalous_users([12345, 23456])
|
107
|
-
# Or use the :force_anomalous_override option when calling track_events or track_multi
|
125
|
+
# Or use the :force_anomalous_override option when calling track_events or track_multi.
|
108
126
|
api.track_events(event, force_anomalous_override: true)
|
109
127
|
```
|
110
128
|
|
111
129
|
### API based data export:
|
112
130
|
|
113
131
|
```ruby
|
114
|
-
|
115
|
-
|
116
|
-
|
132
|
+
data_export_api = LeanplumApi::DataExportAPI.new
|
133
|
+
|
134
|
+
# Bulk data export
|
135
|
+
job_id = data_export_api.export_data(start_time, end_time)
|
136
|
+
response = data_export_api.wait_for_export_job(job_id)
|
137
|
+
|
138
|
+
# User export
|
139
|
+
job_id = data_export_api.export_users(ab_test_id, segment)
|
140
|
+
response = data_export_api.wait_for_export_job(job_id)
|
117
141
|
```
|
118
142
|
|
119
143
|
**Note well that Leanplum now officially recommends use of the automated S3 export instead of API based export.** According to a Leanplum engineer these two data export methodologies are completely independent data paths and in our experience we have found API based data export to be missing 10-15% of the data that is eventually returned by the automated export.
|
120
144
|
|
121
145
|
### Other Available Methods
|
122
|
-
|
123
|
-
|
146
|
+
These are mostly simple wrappers around Leanplum's API methods. See their documentation for details.
|
147
|
+
|
148
|
+
* `api.export_user(user_id)`
|
149
|
+
* `api.user_attributes(user_id)` (gives you the attributes section of `exportUser`)
|
150
|
+
* `api.user_events(user_id)` (gives you the events section of `exportUser`)
|
124
151
|
* `api.get_ab_tests(only_recent)`
|
125
152
|
* `api.get_ab_test(ab_test_id)`
|
126
153
|
* `api.get_messages(only_recent)`
|
127
154
|
* `api.get_message(message_id)`
|
128
155
|
* `api.get_variant(variant_id)`
|
129
156
|
* `api.get_vars(user_id)`
|
157
|
+
* `api.delete_user(user_id)`
|
158
|
+
|
130
159
|
|
131
160
|
## Specs
|
132
161
|
|
133
162
|
`bundle exec rspec` should work fine at running existing specs.
|
134
163
|
|
135
|
-
To write _new_ specs (or regenerate one of [VCR](https://github.com/vcr/vcr)'s YAML files), you must set the `LEANPLUM_PRODUCTION_KEY`, `LEANPLUM_APP_ID`, `LEANPLUM_CONTENT_READ_ONLY_KEY`, `LEANPLUM_DEVELOPMENT_KEY`, and `LEANPLUM_DATA_EXPORT_KEY` environment variables (preferably to some development only keys)
|
136
|
-
|
137
|
-
> BE AWARE THAT IF YOU WRITE A NEW SPEC OR DELETE A VCR FILE, IT'S POSSIBLE THAT REAL DATA WILL BE WRITTEN TO THE `LEANPLUM_APP_ID` YOU CONFIGURE! Certainly a real request will be made to rebuild the VCR file, and while specs run with ```devMode=true```, it's usually a good idea to create a fake app for testing/running specs against.
|
164
|
+
To write _new_ specs (or regenerate one of [VCR](https://github.com/vcr/vcr)'s YAML files), you must set the `LEANPLUM_PRODUCTION_KEY`, `LEANPLUM_APP_ID`, `LEANPLUM_CONTENT_READ_ONLY_KEY`, `LEANPLUM_DEVELOPMENT_KEY`, and `LEANPLUM_DATA_EXPORT_KEY` environment variables (preferably to some development only keys). The easiest way to do this is to create a `.env` file based on the [.env.example](.env.example) file in the repo and then fill in the blanks; the `dotenv` gem will handle loading them into the environment when you run `bundle exec rspec`.
|
138
165
|
|
139
166
|
```bash
|
140
|
-
|
141
|
-
|
142
|
-
export LEANPLUM_DATA_EXPORT_KEY=data_something_3238mmmX
|
143
|
-
export LEANPLUM_CONTENT_READ_ONLY_KEY=sometingsome23xx9
|
144
|
-
export LEANPLUM_DEVELOPMENT_KEY=sometingsome23xx923n23i
|
145
|
-
|
167
|
+
cp .env.example .env
|
168
|
+
vi .env # open in your favorite text editor; edit it and fill in the various keys
|
146
169
|
bundle exec rspec
|
147
170
|
```
|
148
171
|
|
172
|
+
> BE AWARE THAT IF YOU WRITE A NEW SPEC OR DELETE A VCR FILE, IT'S POSSIBLE THAT REAL DATA WILL BE WRITTEN TO THE `LEANPLUM_APP_ID` YOU CONFIGURE! Certainly a real request will be made to rebuild the VCR file, and while specs run with ```devMode=true```, it's usually a good idea to create a fake app for testing/running specs against.
|
173
|
+
|
174
|
+
VCR will create fixture data based on your requests, masking your actual keys so that it's safe to commit the file.
|
175
|
+
|
176
|
+
One gotcha is that the `Timecop` gem is used to freeze the specs at a particular point in time. You may need to update the `Timecop` config in [spec_helper.rb](spec/spec_helper.rb) and regenerate all the fixtures should you want to record any new interactions. If you see warnings about "Device skew" in the fixtures, this is how you fix it.
|
177
|
+
|
149
178
|
## Debugging
|
150
179
|
|
151
180
|
The `LEANPLUM_API_DEBUG` environment variable will trigger full printouts of Faraday's debug output to STDERR and to the configured logger.
|
@@ -164,6 +193,8 @@ LeanplumApi.configure do |config|
|
|
164
193
|
end
|
165
194
|
```
|
166
195
|
|
196
|
+
This also works when running specs.
|
197
|
+
|
167
198
|
### Developer Mode
|
168
199
|
|
169
200
|
You can also configure "developer mode". This will use the `devMode=true` parameter on some requests, which seems to sends them to a separate queue which might not count towards Leanplum's usage billing.
|
data/lib/leanplum_api/api.rb
CHANGED
@@ -1,146 +1,51 @@
|
|
1
1
|
module LeanplumApi
|
2
2
|
class API
|
3
|
-
|
3
|
+
# API Command Constants
|
4
|
+
SET_USER_ATTRIBUTES = 'setUserAttributes'.freeze
|
5
|
+
SET_DEVICE_ATTRIBUTES = 'setDeviceAttributes'.freeze
|
6
|
+
TRACK = 'track'.freeze
|
4
7
|
|
5
|
-
|
8
|
+
# Data export related constants
|
9
|
+
EXPORT_PENDING = 'PENDING'.freeze
|
10
|
+
EXPORT_RUNNING = 'RUNNING'.freeze
|
11
|
+
EXPORT_FINISHED = 'FINISHED'.freeze
|
6
12
|
|
7
|
-
|
8
|
-
EXPORT_RUNNING = 'RUNNING'
|
9
|
-
EXPORT_FINISHED = 'FINISHED'
|
10
|
-
|
11
|
-
def initialize(options = {})
|
13
|
+
def initialize
|
12
14
|
fail 'LeanplumApi not configured yet!' unless LeanplumApi.configuration
|
13
15
|
end
|
14
16
|
|
15
17
|
def set_user_attributes(user_attributes, options = {})
|
16
|
-
track_multi(
|
18
|
+
track_multi(user_attributes: user_attributes, options: options)
|
17
19
|
end
|
18
20
|
|
19
|
-
def
|
20
|
-
track_multi(
|
21
|
-
end
|
22
|
-
|
23
|
-
# This method is for tracking events and/or updating user attributes at the same time, batched together like
|
24
|
-
# leanplum recommends.
|
25
|
-
# Set force_anomalous_override: true to catch warnings from leanplum about anomalous events and force them to not
|
26
|
-
# be considered anomalous
|
27
|
-
def track_multi(events = nil, user_attributes = nil, options = {})
|
28
|
-
request_data = Array.wrap(user_attributes).map { |h| build_user_attributes_hash(h) } +
|
29
|
-
Array.wrap(events).map { |h| build_event_attributes_hash(h, options) }
|
30
|
-
response = production_connection.multi(request_data).body['response']
|
31
|
-
|
32
|
-
if options[:force_anomalous_override]
|
33
|
-
user_ids_to_reset = []
|
34
|
-
response.each_with_index do |indicator, i|
|
35
|
-
# Leanplum's engineering team likes to break their API and or change stuff without warning (often)
|
36
|
-
# and has no idea what "versioning" actually means, so we just reset everyone all the time.
|
37
|
-
# This condition should be:
|
38
|
-
# if indicator['warning'] && indicator['warning']['message'] =~ /Past event detected/i
|
39
|
-
#
|
40
|
-
# but it has to be:
|
41
|
-
if indicator['warning']
|
42
|
-
# Leanplum does not return their warnings in order!!! So we just have
|
43
|
-
# to reset everyone who had any events. This is what the code should be:
|
44
|
-
# user_ids_to_reset << request_data[i]['userId']
|
45
|
-
|
46
|
-
# This is what it has to be:
|
47
|
-
user_ids_to_reset = events.map { |e| e[:user_id] }.uniq
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
unless user_ids_to_reset.empty?
|
52
|
-
LeanplumApi.configuration.logger.debug("Resetting anomalous user ids: #{user_ids_to_reset}")
|
53
|
-
reset_anomalous_users(user_ids_to_reset)
|
54
|
-
end
|
55
|
-
end
|
21
|
+
def set_device_attributes(device_attributes, options = {})
|
22
|
+
track_multi(device_attributes: device_attributes, options: options)
|
56
23
|
end
|
57
24
|
|
58
|
-
|
59
|
-
|
60
|
-
# leads to sort of unprocessed information that can be incomplete.
|
61
|
-
# They recommend using the automatic export to S3 if possible.
|
62
|
-
def export_data(start_time, end_time = nil)
|
63
|
-
LeanplumApi.configuration.logger.warn("You should probably use the direct S3 export instead of exportData")
|
64
|
-
fail "Start time #{start_time} after end time #{end_time}" if end_time && start_time > end_time
|
65
|
-
LeanplumApi.configuration.logger.info("Requesting data export from #{start_time} to #{end_time}...")
|
66
|
-
|
67
|
-
# Because of open questions about how startTime and endTime work (or don't work, as the case may be), we
|
68
|
-
# only want to pass the dates unless start and end times are specifically requested.
|
69
|
-
params = { action: 'exportData', startDate: start_time.strftime('%Y%m%d') }
|
70
|
-
params[:startTime] = start_time.strftime('%s') if start_time.is_a?(DateTime) || start_time.is_a?(Time)
|
71
|
-
if end_time
|
72
|
-
params[:endDate] = end_time.strftime('%Y%m%d')
|
73
|
-
params[:endTime] = end_time.strftime('%s') if end_time.is_a?(DateTime) || end_time.is_a?(Time)
|
74
|
-
end
|
75
|
-
|
76
|
-
# Handle optional S3 export params
|
77
|
-
if LeanplumApi.configuration.s3_bucket_name
|
78
|
-
fail 's3_bucket_name set but s3_access_id not configured!' unless LeanplumApi.configuration.s3_access_id
|
79
|
-
fail 's3_bucket_name set but s3_access_key not configured!' unless LeanplumApi.configuration.s3_access_key
|
80
|
-
|
81
|
-
params.merge!(
|
82
|
-
s3BucketName: LeanplumApi.configuration.s3_bucket_name,
|
83
|
-
s3AccessId: LeanplumApi.configuration.s3_access_id,
|
84
|
-
s3AccessKey: LeanplumApi.configuration.s3_access_key
|
85
|
-
)
|
86
|
-
params.merge!(s3ObjectPrefix: LeanplumApi.configuration.s3_object_prefix) if LeanplumApi.configuration.s3_object_prefix
|
87
|
-
end
|
88
|
-
|
89
|
-
data_export_connection.get(params).body['response'].first['jobId']
|
25
|
+
def track_events(events, options = {})
|
26
|
+
track_multi(events: events, options: options)
|
90
27
|
end
|
91
28
|
|
92
|
-
#
|
93
|
-
#
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
29
|
+
# This method is for tracking events and/or updating user and/or device attributes
|
30
|
+
# at the same time, batched together like leanplum recommends.
|
31
|
+
# Set the :force_anomalous_override option to catch warnings from leanplum
|
32
|
+
# about anomalous events and force them to not be considered anomalous.
|
33
|
+
def track_multi(events: nil, user_attributes: nil, device_attributes: nil, options: {})
|
34
|
+
events = Array.wrap(events)
|
98
35
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
if response['state'] == EXPORT_FINISHED
|
103
|
-
LeanplumApi.configuration.logger.info("Export finished.")
|
104
|
-
LeanplumApi.configuration.logger.debug(" Response: #{response}")
|
105
|
-
{
|
106
|
-
files: response['files'],
|
107
|
-
number_of_sessions: response['numSessions'],
|
108
|
-
number_of_bytes: response['numBytes'],
|
109
|
-
state: response['state'],
|
110
|
-
s3_copy_status: response['s3CopyStatus']
|
111
|
-
}
|
112
|
-
else
|
113
|
-
{ state: response['state'] }
|
114
|
-
end
|
115
|
-
end
|
36
|
+
request_data = events.map { |h| build_event_attributes_hash(h.dup, options) } +
|
37
|
+
Array.wrap(user_attributes).map { |h| build_user_attributes_hash(h.dup) } +
|
38
|
+
Array.wrap(device_attributes).map { |h| build_device_attributes_hash(h.dup) }
|
116
39
|
|
117
|
-
|
118
|
-
|
119
|
-
LeanplumApi.configuration.logger.debug("Polling job #{job_id}: #{get_export_results(job_id)}")
|
120
|
-
sleep(polling_interval)
|
121
|
-
end
|
122
|
-
get_export_results(job_id)
|
123
|
-
end
|
40
|
+
response = production_connection.multi(request_data)
|
41
|
+
force_anomalous_override(response, events) if options[:force_anomalous_override]
|
124
42
|
|
125
|
-
|
126
|
-
def wait_for_job(job_id, polling_interval = 60)
|
127
|
-
wait_for_export_job(job_id, polling_interval)
|
43
|
+
response
|
128
44
|
end
|
129
|
-
deprecate :wait_for_job, 'wait_for_export_job', 2018, 6
|
130
45
|
|
131
46
|
def user_attributes(user_id)
|
132
|
-
|
133
|
-
|
134
|
-
if v == 'True'
|
135
|
-
attrs[k] = true
|
136
|
-
elsif v == 'False'
|
137
|
-
attrs[k] = false
|
138
|
-
else
|
139
|
-
attrs[k] = v
|
140
|
-
end
|
141
|
-
|
142
|
-
attrs
|
143
|
-
end
|
47
|
+
# Leanplum returns strings instead of booleans
|
48
|
+
Hash[export_user(user_id)['userAttributes'].map { |k, v| [k, v.to_s =~ /\Atrue|false\z/i ? eval(v.downcase) : v] }]
|
144
49
|
end
|
145
50
|
|
146
51
|
def user_events(user_id)
|
@@ -148,102 +53,114 @@ module LeanplumApi
|
|
148
53
|
end
|
149
54
|
|
150
55
|
def export_user(user_id)
|
151
|
-
data_export_connection.get(action: 'exportUser', userId: user_id).
|
56
|
+
response = data_export_connection.get(action: 'exportUser', userId: user_id).first
|
57
|
+
fail ResourceNotFoundError, "User #{user_id} not found" unless response['events'] || response['userAttributes']
|
58
|
+
response
|
152
59
|
end
|
153
60
|
|
154
61
|
def get_ab_tests(only_recent = false)
|
155
|
-
content_read_only_connection.get(action: 'getAbTests', recent: only_recent).
|
62
|
+
content_read_only_connection.get(action: 'getAbTests', recent: only_recent).first['abTests']
|
156
63
|
end
|
157
64
|
|
158
65
|
def get_ab_test(ab_test_id)
|
159
|
-
content_read_only_connection.get(action: 'getAbTest', id: ab_test_id).
|
66
|
+
content_read_only_connection.get(action: 'getAbTest', id: ab_test_id).first['abTest']
|
160
67
|
end
|
161
68
|
|
162
69
|
def get_variant(variant_id)
|
163
|
-
content_read_only_connection.get(action: 'getVariant', id: variant_id).
|
70
|
+
content_read_only_connection.get(action: 'getVariant', id: variant_id).first['variant']
|
164
71
|
end
|
165
72
|
|
166
73
|
def get_messages(only_recent = false)
|
167
|
-
content_read_only_connection.get(action: 'getMessages', recent: only_recent).
|
74
|
+
content_read_only_connection.get(action: 'getMessages', recent: only_recent).first['messages']
|
168
75
|
end
|
169
76
|
|
170
77
|
def get_message(message_id)
|
171
|
-
content_read_only_connection.get(action: 'getMessage', id: message_id).
|
78
|
+
content_read_only_connection.get(action: 'getMessage', id: message_id).first['message']
|
172
79
|
end
|
173
80
|
|
174
81
|
def get_vars(user_id)
|
175
|
-
production_connection.get(action: 'getVars', userId: user_id).
|
82
|
+
production_connection.get(action: 'getVars', userId: user_id).first['vars']
|
83
|
+
end
|
84
|
+
|
85
|
+
def delete_user(user_id)
|
86
|
+
development_connection.get(action: 'deleteUser', userId: user_id).first['vars']
|
176
87
|
end
|
177
88
|
|
178
|
-
# If you pass old events OR users with old date attributes (
|
179
|
-
# them 'anomalous' and exclude them from your data set.
|
180
|
-
# Calling this method after you pass old events will fix that for all events for the specified user_id
|
181
|
-
# For some reason this API feature requires the developer key
|
89
|
+
# If you pass old events OR users with old date attributes (e.g. create_date for an old user), Leanplum
|
90
|
+
# wil mark them 'anomalous' and exclude them from your data set.
|
91
|
+
# Calling this method after you pass old events will fix that for all events for the specified user_id.
|
182
92
|
def reset_anomalous_users(user_ids)
|
183
93
|
user_ids = Array.wrap(user_ids)
|
184
|
-
request_data = user_ids.map { |user_id| { action:
|
94
|
+
request_data = user_ids.map { |user_id| { action: SET_USER_ATTRIBUTES, resetAnomalies: true, userId: user_id } }
|
185
95
|
development_connection.multi(request_data)
|
186
96
|
end
|
187
97
|
|
188
98
|
private
|
189
99
|
|
190
100
|
def production_connection
|
191
|
-
fail
|
101
|
+
fail 'production_key not configured!' unless LeanplumApi.configuration.production_key
|
192
102
|
@production ||= Connection.new(LeanplumApi.configuration.production_key)
|
193
103
|
end
|
194
104
|
|
195
105
|
# Only instantiated for data export endpoint calls
|
196
106
|
def data_export_connection
|
197
|
-
fail
|
107
|
+
fail 'data_export_key not configured!' unless LeanplumApi.configuration.data_export_key
|
198
108
|
@data_export ||= Connection.new(LeanplumApi.configuration.data_export_key)
|
199
109
|
end
|
200
110
|
|
201
111
|
# Only instantiated for ContentReadOnly calls (AB tests)
|
202
112
|
def content_read_only_connection
|
203
|
-
fail
|
113
|
+
fail 'content_read_only_key not configured!' unless LeanplumApi.configuration.content_read_only_key
|
204
114
|
@content_read_only ||= Connection.new(LeanplumApi.configuration.content_read_only_key)
|
205
115
|
end
|
206
116
|
|
207
117
|
def development_connection
|
208
|
-
fail
|
118
|
+
fail 'development_key not configured!' unless LeanplumApi.configuration.development_key
|
209
119
|
@development ||= Connection.new(LeanplumApi.configuration.development_key)
|
210
120
|
end
|
211
121
|
|
212
122
|
# Deletes the user_id and device_id key/value pairs from the hash parameter.
|
213
123
|
def extract_user_id_or_device_id_hash!(hash)
|
214
|
-
user_id = hash.delete(:user_id)
|
215
|
-
device_id = hash.delete(:device_id)
|
124
|
+
user_id = hash.delete(:user_id) || hash.delete(:userId)
|
125
|
+
device_id = hash.delete(:device_id) || hash.delete(:deviceId)
|
216
126
|
fail "No device_id or user_id in hash #{hash}" unless user_id || device_id
|
217
127
|
|
218
128
|
user_id ? { userId: user_id } : { deviceId: device_id }
|
219
129
|
end
|
220
130
|
|
221
|
-
#
|
222
|
-
#
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
131
|
+
# build a user attributes hash
|
132
|
+
# @param [Hash] user_hash user attributes to set into LP user
|
133
|
+
def build_user_attributes_hash(user_hash)
|
134
|
+
user_attr_hash = extract_user_id_or_device_id_hash!(user_hash)
|
135
|
+
user_attr_hash[:action] = SET_USER_ATTRIBUTES
|
136
|
+
user_attr_hash[:devices] = user_hash.delete(:devices) if user_hash.key?(:devices)
|
227
137
|
|
228
|
-
if
|
229
|
-
events
|
230
|
-
|
231
|
-
end
|
138
|
+
if user_hash.key?(:events)
|
139
|
+
user_attr_hash[:events] = user_hash.delete(:events)
|
140
|
+
user_attr_hash[:events].each { |k, v| user_attr_hash[:events][k] = fix_seconds_since_epoch(v) }
|
232
141
|
end
|
233
142
|
|
234
|
-
|
235
|
-
|
236
|
-
|
143
|
+
user_attr_hash[:userAttributes] = fix_iso8601(user_hash)
|
144
|
+
user_attr_hash
|
145
|
+
end
|
146
|
+
|
147
|
+
# build a user attributes hash
|
148
|
+
# @param [Hash] device_hash device attributes to set into LP device
|
149
|
+
def build_device_attributes_hash(device_hash)
|
150
|
+
device_hash = fix_iso8601(device_hash)
|
151
|
+
extract_user_id_or_device_id_hash!(device_hash).merge(
|
152
|
+
action: SET_DEVICE_ATTRIBUTES,
|
153
|
+
deviceAttributes: device_hash
|
154
|
+
)
|
237
155
|
end
|
238
156
|
|
239
157
|
# Events have a :user_id or :device id, a name (:event) and an optional time (:time)
|
240
158
|
# Use the :allow_offline option to send events without creating a new session
|
241
159
|
def build_event_attributes_hash(event_hash, options = {})
|
242
|
-
event_hash = HashWithIndifferentAccess.new(event_hash)
|
243
160
|
event_name = event_hash.delete(:event)
|
244
161
|
fail ":event key not present in #{event_hash}" unless event_name
|
245
162
|
|
246
|
-
event = { action:
|
163
|
+
event = { action: TRACK, event: event_name }.merge(extract_user_id_or_device_id_hash!(event_hash))
|
247
164
|
event.merge!(time: event_hash.delete(:time).strftime('%s').to_i) if event_hash[:time]
|
248
165
|
event.merge!(info: event_hash.delete(:info)) if event_hash[:info]
|
249
166
|
event.merge!(allowOffline: true) if options[:allow_offline]
|
@@ -251,6 +168,40 @@ module LeanplumApi
|
|
251
168
|
event_hash.keys.size > 0 ? event.merge(params: event_hash.symbolize_keys ) : event
|
252
169
|
end
|
253
170
|
|
171
|
+
# Leanplum's engineering team likes to break their API and or change stuff without warning (often)
|
172
|
+
# and has no idea what "versioning" actually means, so we just reset everyone on any type of warning.
|
173
|
+
def force_anomalous_override(responses, events)
|
174
|
+
user_ids_to_reset = []
|
175
|
+
|
176
|
+
responses.each_with_index do |indicator, i|
|
177
|
+
# This condition should be:
|
178
|
+
# if indicator['warning'] && indicator['warning']['message'] =~ /Past event detected/i
|
179
|
+
# but it has to be:
|
180
|
+
if indicator['warning']
|
181
|
+
# Leanplum does not return their warnings in order!!! So we just have
|
182
|
+
# to reset everyone who had any events. This is what the code should be:
|
183
|
+
# user_ids_to_reset << request_data[i]['userId']
|
184
|
+
|
185
|
+
# This is what it has to be:
|
186
|
+
user_ids_to_reset = events.map { |e| e[:user_id] }.uniq
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
unless user_ids_to_reset.empty?
|
191
|
+
LeanplumApi.configuration.logger.debug("Resetting anomalous user ids: #{user_ids_to_reset}")
|
192
|
+
reset_anomalous_users(user_ids_to_reset)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
# As of 2015-10 Leanplum supports ISO8601 date & time strings as user attributes.
|
197
|
+
def fix_iso8601(attr_hash)
|
198
|
+
Hash[attr_hash.map { |k, v| [k, (is_date_or_time?(v) ? v.iso8601 : v)] }]
|
199
|
+
end
|
200
|
+
|
201
|
+
def fix_seconds_since_epoch(attr_hash)
|
202
|
+
Hash[attr_hash.map { |k, v| [k, (is_date_or_time?(v) ? v.strftime('%s').to_i : v)] }]
|
203
|
+
end
|
204
|
+
|
254
205
|
def is_date_or_time?(obj)
|
255
206
|
obj.is_a?(Date) || obj.is_a?(Time) || obj.is_a?(DateTime)
|
256
207
|
end
|