leanplum_api 1.3.1
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 +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
|