leanplum_api 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +133 -0
- data/Rakefile +11 -0
- data/lib/leanplum_api.rb +15 -0
- data/lib/leanplum_api/api.rb +251 -0
- data/lib/leanplum_api/configuration.rb +45 -0
- data/lib/leanplum_api/content_read_only.rb +15 -0
- data/lib/leanplum_api/data_export.rb +15 -0
- data/lib/leanplum_api/development.rb +14 -0
- data/lib/leanplum_api/exception.rb +4 -0
- data/lib/leanplum_api/http.rb +68 -0
- data/lib/leanplum_api/logger.rb +23 -0
- data/lib/leanplum_api/version.rb +3 -0
- data/spec/api_spec.rb +230 -0
- data/spec/configuration_spec.rb +26 -0
- data/spec/fixtures/vcr/export_data.yml +40 -0
- data/spec/fixtures/vcr/export_data_dates.yml +40 -0
- data/spec/fixtures/vcr/export_user.yml +40 -0
- data/spec/fixtures/vcr/get_ab_test.yml +40 -0
- data/spec/fixtures/vcr/get_ab_tests.yml +40 -0
- data/spec/fixtures/vcr/get_export_results.yml +40 -0
- data/spec/fixtures/vcr/get_messages.yml +41 -0
- data/spec/fixtures/vcr/get_vars.yml +40 -0
- data/spec/fixtures/vcr/reset_anomalous_user.yml +43 -0
- data/spec/fixtures/vcr/set_user_attributes.yml +43 -0
- data/spec/fixtures/vcr/track_events.yml +45 -0
- data/spec/fixtures/vcr/track_events_and_attributes.yml +46 -0
- data/spec/fixtures/vcr/track_events_anomaly_overrider.yml +86 -0
- data/spec/http_spec.rb +23 -0
- data/spec/spec_helper.rb +45 -0
- metadata +211 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eaef8b007ad46754ae7107b22f86db959f8805c9
|
4
|
+
data.tar.gz: 503030e7e4f4a85640e42aa2e2d60da7911bad5d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3d9b7d5ad103922bf4888953aaad2e5130b98c2136a8b18a0b02ba30e9d55d6d2d041403eddad4799457c51290b42491570a42a9a68bb63315b3843130cf0814
|
7
|
+
data.tar.gz: dc14c91eb847b399af97a187bed901f948f88a34462539a2489d72fd587c07a885537e3b3b2f34dac4e8bad3dd4795f0a918ccbd0395e3d99bd64046f876efa7
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Lumos Labs, Inc.
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# leanplum_api
|
2
|
+
|
3
|
+
Gem for the Leanplum API.
|
4
|
+
|
5
|
+
## Notes
|
6
|
+
|
7
|
+
Leanplum calls it a REST API but it is not very RESTful.
|
8
|
+
|
9
|
+
The gem uses the ```multi``` method with a POST for all requests except data export. Check Leanplum's docs for more information on ```multi```.
|
10
|
+
|
11
|
+
Tested with Leanplum API version 1.0.6.
|
12
|
+
|
13
|
+
required_ruby_version is set to 1.9 but this code has only been tested with Ruby 2.1.5!
|
14
|
+
|
15
|
+
## Configuration
|
16
|
+
|
17
|
+
You need to obtain (at a minimum) the PRODUCTION and APP_ID from Leanplum. You may also want to configure the DATA_EXPORT_KEY, CONTENT_READ_ONLY_KEY, and DEVELOPMENT_KEY if you plan on calling methods that require those keys. Then you can setup the gem for use in your application like so:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
require 'leanplum_api'
|
21
|
+
|
22
|
+
LeanplumApi.configure do |config|
|
23
|
+
config.production_key = 'MY_CLIENT_KEY'
|
24
|
+
config.app_id = 'MY_APP_ID'
|
25
|
+
config.data_export_key = 'MY_DATA_KEY' # Optional; necessary only if you want to call data export methods.
|
26
|
+
config.content_read_only_key = 'MY_CONTENT_KEY' # Optional; necessary for retrieving AB test info
|
27
|
+
config.development_key = 'MY_CONTENT_KEY' # Optional; needed for resetting anomalous events
|
28
|
+
|
29
|
+
# Optional configuration variables
|
30
|
+
config.log_path = '/log/path' # Defaults to 'log/'
|
31
|
+
attr_accessor :timeout_seconds # Defaults to 600
|
32
|
+
config.api_version # Defaults to 1.0.6
|
33
|
+
attr_accessor :developer_mode # Defaults to false
|
34
|
+
|
35
|
+
# S3 export required options
|
36
|
+
config.s3_bucket_name = 'my_bucket'
|
37
|
+
config.s3_access_id = 'access_id'
|
38
|
+
config.s3_access_key = 'access_key'
|
39
|
+
|
40
|
+
# Set this to true to send events and user attributes to the test environment
|
41
|
+
# Defaults to false. See "Debugging" below for more info.
|
42
|
+
config.developer_mode = true
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
## Usage
|
47
|
+
|
48
|
+
Tracking events and user attributes:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
api = LeanplumApi::API.new
|
52
|
+
|
53
|
+
# You must provide either :user_id or :device_id for requests involving
|
54
|
+
# attribute updates or event tracking.
|
55
|
+
attribute_hash = {
|
56
|
+
user_id: 12345,
|
57
|
+
first_name: 'Mike',
|
58
|
+
last_name: 'Jones',
|
59
|
+
gender: 'm',
|
60
|
+
birthday: Date.today, # Dates and times in user attributes will be formatted as strings; Leanplum doesn't support date or time types
|
61
|
+
email: 'still_tippin@test.com'
|
62
|
+
}
|
63
|
+
api.set_user_attributes(attribute_hash)
|
64
|
+
|
65
|
+
# You must also provide the :event property for event tracking
|
66
|
+
event = {
|
67
|
+
user_id: 12345,
|
68
|
+
event: 'purchase',
|
69
|
+
time: Time.now.utc, # Event timestamps will be converted to epoch seconds by the gem.
|
70
|
+
params: {
|
71
|
+
'some_event_property' => 'boss_hog_on_candy'
|
72
|
+
}
|
73
|
+
}
|
74
|
+
api.track_events(event)
|
75
|
+
|
76
|
+
# You can also track events and user attributes at the same time
|
77
|
+
api.track_multi(event, attribute_hash)
|
78
|
+
```
|
79
|
+
|
80
|
+
Data export:
|
81
|
+
```ruby
|
82
|
+
api = LeanplumApi::API.new
|
83
|
+
job_id = api.export_data(start_time, end_time)
|
84
|
+
response = wait_for_job(job_id)
|
85
|
+
```
|
86
|
+
|
87
|
+
## Logging
|
88
|
+
|
89
|
+
When you instantiate a ```LeanplumApi::API``` object, you can pass a ```Logger``` object to redirect the logging as you see fit.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
api = LeanplumApi::API.new(logger: Logger.new('/path/to/my/log_file.log))
|
93
|
+
```
|
94
|
+
|
95
|
+
Alternatively, you can configure a log_path in the configure block.
|
96
|
+
```ruby
|
97
|
+
LeanplumApi.configure do |config|
|
98
|
+
config.log_path = '/path/to/my/logs'
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
And logs will be sent to ```/path/to/my/logs/{PID}_leanplum_{timestamp}.log```
|
103
|
+
|
104
|
+
The default log_path is ```log/```
|
105
|
+
|
106
|
+
## Tests
|
107
|
+
|
108
|
+
To run tests, 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) to something and then run rspec.
|
109
|
+
Because of the nature of VCR/Webmock, you can set them to anything (including invalid keys) as long as you are not changing anything substantive or writing new specs. If you want to make substantive changes/add new specs, VCR will need to be able to generate fixture data so you will need to use a real set of Leanplum keys.
|
110
|
+
|
111
|
+
> 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.
|
112
|
+
|
113
|
+
```bash
|
114
|
+
export LEANPLUM_PRODUCTION_KEY=dev_somethingsomeg123456
|
115
|
+
export LEANPLUM_APP_ID=app_somethingsomething2039410238
|
116
|
+
export LEANPLUM_DATA_EXPORT_KEY=data_something_3238mmmX
|
117
|
+
export LEANPLUM_CONTENT_READ_ONLY_KEY=sometingsome23xx9
|
118
|
+
export LEANPLUM_DEVELOPMENT_KEY=sometingsome23xx923n23i
|
119
|
+
|
120
|
+
bundle exec rspec
|
121
|
+
```
|
122
|
+
|
123
|
+
## Debugging
|
124
|
+
|
125
|
+
The LEANPLUM_API_DEBUG environment variable will trigger full printouts of Faraday's debug output to STDERR and to the configured logger.
|
126
|
+
|
127
|
+
```bash
|
128
|
+
cd /my/app
|
129
|
+
export LEANPLUM_API_DEBUG=true
|
130
|
+
bundle exec rails whatever
|
131
|
+
```
|
132
|
+
|
133
|
+
You can also configure "developer mode". This will use the "devMode=true" parameter on all requests, which sends them to a separate queue (and probably means actions logged as development tests don't count towards your bill).
|
data/Rakefile
ADDED
data/lib/leanplum_api.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'active_support/all'
|
3
|
+
|
4
|
+
require 'leanplum_api/api'
|
5
|
+
require 'leanplum_api/configuration'
|
6
|
+
require 'leanplum_api/content_read_only'
|
7
|
+
require 'leanplum_api/data_export'
|
8
|
+
require 'leanplum_api/development'
|
9
|
+
require 'leanplum_api/exception'
|
10
|
+
require 'leanplum_api/http'
|
11
|
+
require 'leanplum_api/logger'
|
12
|
+
require 'leanplum_api/version'
|
13
|
+
|
14
|
+
module LeanplumApi
|
15
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
module LeanplumApi
|
2
|
+
class API
|
3
|
+
EXPORT_PENDING = 'PENDING'
|
4
|
+
EXPORT_RUNNING = 'RUNNING'
|
5
|
+
EXPORT_FINISHED = 'FINISHED'
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
fail 'LeanplumApi not configured yet!' unless LeanplumApi.configuration
|
9
|
+
|
10
|
+
@logger = options[:logger] || LeanplumApiLogger.new(File.join(LeanplumApi.configuration.log_path, "#{$$}_leanplum_#{Time.now.utc.strftime('%Y-%m-%d_%H:%M:%S')}.log"))
|
11
|
+
@http = LeanplumApi::HTTP.new(logger: @logger)
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_user_attributes(user_attributes, options = {})
|
15
|
+
track_multi(nil, user_attributes, options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def track_events(events, options = {})
|
19
|
+
track_multi(events, nil, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
# This method is for tracking events and/or updating user attributes at the same time, batched together like leanplum
|
23
|
+
# recommends.
|
24
|
+
# Set the :force_anomalous_override to catch warnings from leanplum about anomalous events and force them to not
|
25
|
+
# be considered anomalous
|
26
|
+
def track_multi(events = nil, user_attributes = nil, options = {})
|
27
|
+
events = arrayify(events)
|
28
|
+
user_attributes = arrayify(user_attributes)
|
29
|
+
|
30
|
+
request_data = user_attributes.map { |h| build_user_attributes_hash(h) } + events.map { |h| build_event_attributes_hash(h) }
|
31
|
+
response = @http.post(request_data)
|
32
|
+
validate_response(events + user_attributes, response)
|
33
|
+
|
34
|
+
if options[:force_anomalous_override]
|
35
|
+
user_ids_to_reset = []
|
36
|
+
response.body['response'].each_with_index do |indicator, i|
|
37
|
+
if indicator['warning'] && indicator['warning']['message'] =~ /Anomaly detected/i
|
38
|
+
user_ids_to_reset << (events + user_attributes)[i][:user_id]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
reset_anomalous_users(user_ids_to_reset)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns the jobId
|
46
|
+
# Leanplum has confirmed that using startTime and endTime, especially trying to be relatively up to the minute,
|
47
|
+
# leads to sort of unprocessed information that can be incomplete.
|
48
|
+
# They recommend using the automatic export to S3 if possible.
|
49
|
+
def export_data(start_time, end_time = nil)
|
50
|
+
fail "Start time #{start_time} after end time #{end_time}" if end_time && start_time > end_time
|
51
|
+
@logger.info("Requesting data export from #{start_time} to #{end_time}...")
|
52
|
+
|
53
|
+
# Because of open questions about how startTime and endTime work (or don't work, as the case may be), we
|
54
|
+
# only want to pass the dates unless start and end times are specifically requested.
|
55
|
+
params = { action: 'exportData', startDate: start_time.strftime('%Y%m%d') }
|
56
|
+
params[:startTime] = start_time.strftime('%s') if start_time.is_a?(DateTime) || start_time.is_a?(Time)
|
57
|
+
if end_time
|
58
|
+
params[:endDate] = end_time.strftime('%Y%m%d')
|
59
|
+
params[:endTime] = end_time.strftime('%s') if end_time.is_a?(DateTime) || end_time.is_a?(Time)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Handle optional S3 export params
|
63
|
+
if LeanplumApi.configuration.s3_bucket_name
|
64
|
+
fail 's3_bucket_name set but s3_access_id not configured!' unless LeanplumApi.configuration.s3_access_id
|
65
|
+
fail 's3_bucket_name set but s3_access_key not configured!' unless LeanplumApi.configuration.s3_access_key
|
66
|
+
|
67
|
+
params.merge!(
|
68
|
+
s3BucketName: LeanplumApi.configuration.s3_bucket_name,
|
69
|
+
s3AccessId: LeanplumApi.configuration.s3_access_id,
|
70
|
+
s3AccessKey: LeanplumApi.configuration.s3_access_key
|
71
|
+
)
|
72
|
+
params.merge!(s3ObjectPrefix: LeanplumApi.configuration.s3_object_prefix) if LeanplumApi.configuration.s3_object_prefix
|
73
|
+
end
|
74
|
+
|
75
|
+
response = data_export_connection.get(params).body['response'].first
|
76
|
+
if response['success'] == true
|
77
|
+
response['jobId']
|
78
|
+
else
|
79
|
+
fail "No success message! Response: #{response}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# See leanplum docs.
|
84
|
+
# The segment syntax is identical to that produced by the "Insert Value" feature on the dashboard.
|
85
|
+
# Examples: 'Country = "US"', '{Country = “US”} and {App version = 1}'.
|
86
|
+
def export_users(segment, ab_test_id)
|
87
|
+
data_export_connection.get(action: 'exportUsers', segment: segment, ab_test_id: ab_test_id)
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_export_results(job_id)
|
91
|
+
response = data_export_connection.get(action: 'getExportResults', jobId: job_id).body['response'].first
|
92
|
+
if response['state'] == EXPORT_FINISHED
|
93
|
+
@logger.info("Export finished.")
|
94
|
+
@logger.debug(" Response: #{response}")
|
95
|
+
{
|
96
|
+
files: response['files'],
|
97
|
+
number_of_sessions: response['numSessions'],
|
98
|
+
number_of_bytes: response['numBytes'],
|
99
|
+
state: response['state'],
|
100
|
+
s3_copy_status: response['s3CopyStatus']
|
101
|
+
}
|
102
|
+
else
|
103
|
+
{ state: response['state'] }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def wait_for_job(job_id, polling_interval = 60)
|
108
|
+
while get_export_results(job_id)[:state] != EXPORT_FINISHED
|
109
|
+
@logger.debug("Polling job #{job_id}: #{get_export_results(job_id)}")
|
110
|
+
sleep(polling_interval)
|
111
|
+
end
|
112
|
+
get_export_results(job_id)
|
113
|
+
end
|
114
|
+
|
115
|
+
def export_user(user_id)
|
116
|
+
data_export_connection.get(action: 'exportUser', userId: user_id).body['response'].first['userAttributes']
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_ab_tests(only_recent = false)
|
120
|
+
content_read_only_connection.get(action: 'getAbTests', recent: only_recent).body['response'].first['abTests']
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_ab_test(ab_test_id)
|
124
|
+
content_read_only_connection.get(action: 'getAbTest', id: ab_test_id).body['response'].first['abTest']
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_variant(variant_id)
|
128
|
+
content_read_only_connection.get(action: 'getVariant', id: variant_id).body['response'].first['variant']
|
129
|
+
end
|
130
|
+
|
131
|
+
def get_messages(only_recent = false)
|
132
|
+
content_read_only_connection.get(action: 'getMessages', recent: only_recent).body['response'].first['messages']
|
133
|
+
end
|
134
|
+
|
135
|
+
def get_message(message_id)
|
136
|
+
content_read_only_connection.get(action: 'getMessage', id: message_id).body['response'].first['message']
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_vars(user_id)
|
140
|
+
@http.get(action: 'getVars', userId: user_id).body['response'].first['vars']
|
141
|
+
end
|
142
|
+
|
143
|
+
# If you pass old events OR users with old date attributes (i.e. create_date for an old users), leanplum will mark them 'anomalous'
|
144
|
+
# and exclude them from your data set.
|
145
|
+
# Calling this method after you pass old events will fix that for all events for the specified user_id
|
146
|
+
# For some reason this API feature requires the developer key
|
147
|
+
def reset_anomalous_users(user_ids)
|
148
|
+
user_ids = arrayify(user_ids)
|
149
|
+
request_data = user_ids.map { |user_id| { 'action' => 'setUserAttributes', 'resetAnomalies' => true, 'userId' => user_id } }
|
150
|
+
response = development_connection.post(request_data)
|
151
|
+
validate_response(request_data, response)
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
# Only instantiated for data export endpoint calls
|
157
|
+
def data_export_connection
|
158
|
+
@data_export ||= LeanplumApi::DataExport.new(logger: @logger)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Only instantiated for ContentReadOnly calls (AB tests)
|
162
|
+
def content_read_only_connection
|
163
|
+
@content_read_only ||= LeanplumApi::ContentReadOnly.new(logger: @logger)
|
164
|
+
end
|
165
|
+
|
166
|
+
def development_connection
|
167
|
+
@development ||= LeanplumApi::Development.new(logger: @logger)
|
168
|
+
end
|
169
|
+
|
170
|
+
def extract_user_id_or_device_id_hash(hash)
|
171
|
+
user_id = hash['user_id'] || hash[:user_id]
|
172
|
+
device_id = hash['device_id'] || hash[:device_id]
|
173
|
+
fail "No device_id or user_id in hash #{hash}" unless user_id || device_id
|
174
|
+
|
175
|
+
user_id ? { 'userId' => user_id } : { 'deviceId' => device_id }
|
176
|
+
end
|
177
|
+
|
178
|
+
# Action can be any command that takes a userAttributes param. "start" (a session) is the other command that most
|
179
|
+
# obviously takes userAttributes.
|
180
|
+
def build_user_attributes_hash(user_hash, action = 'setUserAttributes')
|
181
|
+
extract_user_id_or_device_id_hash(user_hash).merge(
|
182
|
+
'action' => action,
|
183
|
+
'userAttributes' => turn_date_and_time_values_to_strings(user_hash).reject { |k,v| k.to_s =~ /^(user_id|device_id)$/ }
|
184
|
+
)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Events have a :user_id or :device id, a name (:event) and an optional time (:time)
|
188
|
+
def build_event_attributes_hash(event_hash)
|
189
|
+
fail "No event name provided in #{event_hash}" unless event_hash[:event] || event_hash['event']
|
190
|
+
|
191
|
+
time = event_hash[:time] || event_hash['time']
|
192
|
+
time_hash = time ? { 'time' => time.strftime('%s') } : {}
|
193
|
+
|
194
|
+
event = extract_user_id_or_device_id_hash(event_hash).merge(time_hash).merge(
|
195
|
+
'action' => 'track',
|
196
|
+
'event' => event_hash[:event] || event_hash['event']
|
197
|
+
)
|
198
|
+
event_params = event_hash.reject { |k,v| k.to_s =~ /^(user_id|device_id|event|time)$/ }
|
199
|
+
if event_params.keys.size > 0
|
200
|
+
event.merge('params' => event_params )
|
201
|
+
else
|
202
|
+
event
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Leanplum does not support dates and times as of 2015-08-11
|
207
|
+
def turn_date_and_time_values_to_strings(hash)
|
208
|
+
new_hash = {}
|
209
|
+
hash.each do |k,v|
|
210
|
+
if v.is_a?(Time) || v.is_a?(DateTime)
|
211
|
+
new_hash[k] = v.strftime('%Y-%m-%d %H:%M:%S')
|
212
|
+
elsif v.is_a?(Date)
|
213
|
+
new_hash[k] = v.strftime('%Y-%m-%d')
|
214
|
+
else
|
215
|
+
new_hash[k] = v
|
216
|
+
end
|
217
|
+
end
|
218
|
+
new_hash
|
219
|
+
end
|
220
|
+
|
221
|
+
# In case leanplum decides your events are too old, they will send a warning.
|
222
|
+
# Right now we aren't responding to this directly.
|
223
|
+
# '{"response":[{"success":true,"warning":{"message":"Anomaly detected: time skew. User will be excluded from analytics."}}]}'
|
224
|
+
def validate_response(input, response)
|
225
|
+
success_indicators = response.body['response']
|
226
|
+
if success_indicators.size != input.size
|
227
|
+
fail "Attempted to update #{input.size} records but only received confirmation for #{success_indicators.size}!"
|
228
|
+
end
|
229
|
+
|
230
|
+
failure_indices = []
|
231
|
+
success_indicators.each_with_index do |s,i|
|
232
|
+
if s['success'].to_s != 'true'
|
233
|
+
@logger.error("Unsuccessful attempt to update at position #{i}: #{input[i]}")
|
234
|
+
failure_indices << i
|
235
|
+
else
|
236
|
+
@logger.debug("Successfully updated position #{i}: #{input[i]}")
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
fail LeanplumValidationException.new('Failed to update') if failure_indices.size > 0
|
241
|
+
end
|
242
|
+
|
243
|
+
def arrayify(x)
|
244
|
+
if x && !x.is_a?(Array)
|
245
|
+
[x]
|
246
|
+
else
|
247
|
+
x || []
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|