promoted-ruby-client 0.1.15 → 0.1.19

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99ee2c76dc1547104c6e40f5b05c1b7897fb609108d3009c686236ef5646f33b
4
- data.tar.gz: ff7dc6ed3901dac8112564687353c4942352b044d7b158ce2e9bd554843d777a
3
+ metadata.gz: 6b8a39c8af4d21ae9987db29f0041e57d54de87da119b799e89091069a0bfbc2
4
+ data.tar.gz: 7b3932706882a896f330b0bd0a3d05671111e80b97fac4403e355ae30e537750
5
5
  SHA512:
6
- metadata.gz: f7d4ab79296c610db92af6ba93d86023f254fef6d59753e991df0915b028b303a505e11cdce623345f1bc78414f30ecd72f8c237e3f2eb87a93f7c4b331ae690
7
- data.tar.gz: b612ddec96289bffe5b6eb2e9dff2b58f103d2008d53e8ff91069f2b6b13d24a179a25fbbe99315ea29b69cf227ad862da1eeb6204a05a288ab6a6caa0f23c3b
6
+ metadata.gz: 3146dc2f31afa84fa2b332b1cdb5ca145e7aa51140ae14503a8ca8a4aebc96bb59c4313ee10884b85ba819c930f40f9dd88e7d88454a39a9748b42f3e7d827e5
7
+ data.tar.gz: 903cb0dca7f83acfa605db3be223833ffebe78d0e530b1e98599ed2b8b15e2b5babffb1e40704789bab45f42f3d8372a46e2263733428da2e9fea90860003cb0
@@ -0,0 +1,16 @@
1
+ name: Pull-Requests Check
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ Test:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - uses: ruby/setup-ruby@v1
11
+ with:
12
+ ruby-version: 2.5.1
13
+ bundler-cache: true
14
+
15
+ - name: Build and test with rspec
16
+ run: bundle exec rspec spec
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- promoted-ruby-client (0.1.15)
4
+ promoted-ruby-client (0.1.19)
5
5
  concurrent-ruby (~> 1)
6
6
  faraday (>= 0.9.0)
7
7
  faraday_middleware (>= 0.9.0)
data/README.md CHANGED
@@ -17,6 +17,8 @@ More information at [http://www.promoted.ai](http://www.promoted.ai)
17
17
 
18
18
  ### [Faraday](https://github.com/lostisland/faraday)
19
19
  HTTP client for calling Promoted.
20
+ ### [Net::HTTP::Persistent](https://github.com/drbrain/net-http-persistent)
21
+ Faraday binding (provides connection pool support)
20
22
  ### [Concurrent Ruby](https://github.com/ruby-concurrency/concurrent-ruby)
21
23
  Provides a thread pool for making shadow traffic requests to Delivery API in the background on a subset of calls to ```prepare_for_logging```
22
24
  ## Creating a Client
@@ -50,6 +52,7 @@ Name | Type | Description
50
52
  ```:default_request_headers``` | Hash | Additional headers to send on the request beyond ```x-api-key```. Defaults to {}
51
53
  ```:default_only_log``` | Boolean | If true, the ```deliver``` method will not direct traffic to Delivery API but rather return a request suitable for logging. Defaults to false.
52
54
  ```:should_apply_treatment_func``` | Proc | Called during delivery, accepts an experiment and returns a Boolean indicating whether the request should be considered part of the control group (false) or in the experiment (true). If nil, the default behavior of checking the experiement ```:arm``` is applied.
55
+ ```:warmup``` | Boolean | If true, the client will prime the `Net::HTTP::Persistent` connection pool on construction; this can make the first few calls to Promoted complete faster. Defaults to false.
53
56
 
54
57
  ## Data Types
55
58
 
@@ -91,7 +94,66 @@ Field Name | Type | Optional? | Description
91
94
  ```:request_id``` | String | Yes | Generated by the SDK when needed (*do not set*)
92
95
  ```:content_id``` | String | No | Identifier for the content to be shown, must be set.
93
96
  ```:properties``` | Properties | Yes | Any additional custom properties to associate. For v1 integrations, it is fine not to fill in all the properties.
97
+ ---
98
+ ### Size
99
+ User's screen dimensions.
100
+ Field Name | Type | Optional? | Description
101
+ ---------- | ---- | --------- | -----------
102
+ ```:width``` | Integer | No | Screen width
103
+ ```:height``` | Integer | No | Screen height
104
+ ---
105
+
106
+ ### ClientHints
107
+ Alternative to user-agent strings. See https://raw.githubusercontent.com/snowplow/iglu-central/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0
108
+ Field Name | Type | Optional? | Description
109
+ ---------- | ---- | --------- | -----------
110
+ ```:is_mobile``` | Boolean | Yes | Mobile flag
111
+ ```:brand``` | Array of ClientBrandHint | Yes |
112
+ ```:architecture``` | String | Yes |
113
+ ```:model``` | String | Yes |
114
+ ```:platform``` | String | Yes |
115
+ ```:platform_version``` | String | Yes |
116
+ ```:ua_full_version``` | String | Yes |
117
+
118
+ ---
119
+ ### ClientBrandHint
120
+ See https://raw.githubusercontent.com/snowplow/iglu-central/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0
121
+ Field Name | Type | Optional? | Description
122
+ ---------- | ---- | --------- | -----------
123
+ ```:brand``` | String | Yes | Mobile flag
124
+ ```:version``` | String | Yes |
94
125
 
126
+ ---
127
+ ### Location
128
+ Information about the user's location.
129
+ Field Name | Type | Optional? | Description
130
+ ---------- | ---- | --------- | -----------
131
+ ```:latitude``` | Float | No | Location latitude
132
+ ```:longitude``` | Float | No | Location longitude
133
+ ```:accuracy_in_meters``` | Integer | Yes | Location accuracy if available
134
+ ```:client_hints``` | ClientHints | Yes | HTTP client hints structure
135
+ ---
136
+
137
+ ### Browser
138
+ Information about the user's browser.
139
+ Field Name | Type | Optional? | Description
140
+ ---------- | ---- | --------- | -----------
141
+ ```:user_agent``` | String | Yes | Browser user agent string
142
+ ```:viewport_size``` | Size | Yes | Size of the browser viewport
143
+ ```:
144
+ ---
145
+ ### Device
146
+ Information about the user's device.
147
+ Field Name | Type | Optional? | Description
148
+ ---------- | ---- | --------- | -----------
149
+ ```:device_type``` | one of (`UNKNOWN_DEVICE_TYPE`, `DESKTOP`, `MOBILE`, `TABLET`) | Yes | Type of device
150
+ ```:brand``` | String | Yes | "Apple, "google", Samsung", etc.
151
+ ```:manufacturer``` | String | Yes | "Apple", "HTC", Motorola", "HUAWEI", etc.
152
+ ```:identifier``` | String | Yes | Android: android.os.Build.MODEL; iOS: iPhoneXX,YY, etc.
153
+ ```:screen``` | Screen | Yes | Screen dimensions
154
+ ```:ip_address``` | String | Yes | Originating IP address
155
+ ```:location``` | Location | Yes | Location information
156
+ ```:browser``` | Browser | Yes | Browser information
95
157
  ---
96
158
  ### Paging
97
159
  #### TODO
@@ -105,6 +167,7 @@ Field Name | Type | Optional? | Description
105
167
  ```:use_case``` | String | Yes | One of the use case values, i.e. 'FEED' (see [constants.rb](https://github.com/promotedai/promoted-ruby-client/blob/main/lib/promoted/ruby/client/constants.rb)).
106
168
  ```:properties``` | Properties | Yes | Any additional custom properties to associate.
107
169
  ```:paging``` | Paging | Yes | Paging parameters (see TODO)
170
+ ```:device``` | Device | Yes | Device information (as available)
108
171
  ---
109
172
  ### MetricsRequest
110
173
  Input to ```prepare_for_logging```
@@ -139,6 +202,8 @@ Field Name | Type | Optional? | Description
139
202
  ---------- | ---- | --------- | -----------
140
203
  ```:insertion``` | [] of Insertion | No | The insertions, which are from Delivery API (when ```deliver``` was called, i.e. we weren't either only-log or part of an experiment) or the input insertions (when the other conditions don't hold).
141
204
  ```:log_request``` | LogRequest | Yes | A message suitable for logging to Metrics API via ```send_log_request```. If the call to ```deliver``` was made (i.e. the request was not part of the CONTROL arm of an experiment or marked to only log), ```:log_request``` will not be set, as you can assume logging was performed on the server-side by Promoted.
205
+ ```:client_request_id``` | String | Yes | Client-generated request id sent to Delivery API and may be useful for logging and debugging.
206
+ ```:execution_server``` | one of 'API' or 'SDK' | Yes | Indicates if response insertions on a delivery request came from the API or the SDK.
142
207
  ---
143
208
 
144
209
  ### PromotedClient
@@ -216,12 +281,13 @@ metrics_request = {
216
281
  }
217
282
 
218
283
  # OPTIONAL: You can pass a custom function to "compact" insertions before metrics logging.
219
- # Note that the PromotedClient has a class method helper, copy_and_remove_properties, that does just this.
220
- to_compact_metrics_insertion_func = Proc.new do |insertion|
221
- insertion.delete(:properties)
222
- insertion
284
+ # Note that the PromotedClient has a class method helper, remove_all_properties, that does
285
+ # an implementation of this, returning nil to remove ALL properties.
286
+ to_compact_metrics_properties_func = Proc.new do |properties|
287
+ properties[:struct].delete(:active)
288
+ properties
223
289
  end
224
- # metrics_request[:to_compact_metrics_insertion_func] = to_compact_metrics_insertion
290
+ # metrics_request[:to_compact_metrics_properties_func] = to_compact_metrics_properties_func
225
291
 
226
292
  # Create a client
227
293
  client = Promoted::Ruby::Client::PromotedClient.new
data/dev.md CHANGED
@@ -4,5 +4,5 @@
4
4
  2. Get credentials for deployment from 1password.
5
5
  3. Modify `promoted-ruby-client.gemspec`'s push block.
6
6
  4. Run `gem build promoted-ruby-client.gemspec` to generate `gem`.
7
- 5. Run (using new output) `gem push promoted-ruby-client-0.1.15.gem`
7
+ 5. Run (using new output) `gem push promoted-ruby-client-0.1.19.gem`
8
8
  6. Update README with new version.
@@ -30,12 +30,10 @@ module Promoted
30
30
  end
31
31
 
32
32
  ##
33
- # A common compact method implementation.
34
- def self.copy_and_remove_properties
35
- Proc.new do |insertion|
36
- insertion = Hash[insertion]
37
- insertion.delete(:properties)
38
- insertion
33
+ # A common compact properties method implementation.
34
+ def self.remove_all_properties
35
+ Proc.new do |properties|
36
+ nil
39
37
  end
40
38
  end
41
39
 
@@ -73,7 +71,7 @@ module Promoted
73
71
  @delivery_timeout_millis = params[:delivery_timeout_millis] || DEFAULT_DELIVERY_TIMEOUT_MILLIS
74
72
  @metrics_timeout_millis = params[:metrics_timeout_millis] || DEFAULT_METRICS_TIMEOUT_MILLIS
75
73
 
76
- @http_client = FaradayHTTPClient.new
74
+ @http_client = FaradayHTTPClient.new(@logger)
77
75
  @validator = Promoted::Ruby::Client::Validator.new
78
76
 
79
77
  @async_shadow_traffic = true
@@ -97,6 +95,10 @@ module Promoted
97
95
  if params[:enabled] != nil
98
96
  @enabled = params[:enabled] || false
99
97
  end
98
+
99
+ if params[:warmup]
100
+ do_warmup
101
+ end
100
102
  end
101
103
 
102
104
  ##
@@ -109,9 +111,9 @@ module Promoted
109
111
  end
110
112
 
111
113
  ##
112
- # Make a delivery request.
114
+ # Make a delivery request. If @perform_checks is set, input validation will occur and possibly raise errors.
113
115
  def deliver args, headers={}
114
- args = Promoted::Ruby::Client::Util.translate_args(args)
116
+ args = Promoted::Ruby::Client::Util.translate_hash(args)
115
117
 
116
118
  # Respect the enabled state
117
119
  if !@enabled
@@ -124,7 +126,16 @@ module Promoted
124
126
  delivery_request_builder = RequestBuilder.new
125
127
  delivery_request_builder.set_request_params(args)
126
128
 
127
- perform_common_checks!(args) if @perform_checks
129
+ # perform_checks raises errors.
130
+ if @perform_checks
131
+ perform_common_checks!(args)
132
+
133
+ if args[:insertion_page_type] == Promoted::Ruby::Client::INSERTION_PAGING_TYPE['PRE_PAGED'] then
134
+ err = DeliveryInsertionPageType.new
135
+ @logger.error(err) if @logger
136
+ raise err
137
+ end
138
+ end
128
139
 
129
140
  delivery_request_builder.ensure_client_timestamp
130
141
 
@@ -135,10 +146,15 @@ module Promoted
135
146
  only_log = delivery_request_builder.only_log != nil ? delivery_request_builder.only_log : @default_only_log
136
147
  deliver_err = false
137
148
 
138
- if !@pager.validate_paging(delivery_request_builder.full_insertion, delivery_request_builder.request[:paging])
149
+ begin
150
+ @pager.validate_paging(delivery_request_builder.full_insertion, delivery_request_builder.request[:paging])
151
+ rescue InvalidPagingError => err
139
152
  # Invalid input, log and do SDK-side delivery.
140
- @logger.warn("Invalid paging parameters") if @logger
141
- only_log = true
153
+ @logger.warn(err) if @logger
154
+ return {
155
+ insertion: err.default_insertions_page
156
+ # No log request returned when no response insertions due to invalid paging
157
+ }
142
158
  end
143
159
 
144
160
  if !only_log
@@ -194,7 +210,9 @@ module Promoted
194
210
 
195
211
  client_response = {
196
212
  insertion: response_insertions,
197
- log_request: log_req
213
+ log_request: log_req,
214
+ execution_server: insertions_from_delivery ? Promoted::Ruby::Client::EXECUTION_SERVER['API'] : Promoted::Ruby::Client::EXECUTION_SERVER['SDK'],
215
+ client_request_id: delivery_request_builder.client_request_id
198
216
  }
199
217
  return client_response
200
218
  end
@@ -203,7 +221,7 @@ module Promoted
203
221
  # Generate a log request for a subsequent call to send_log_request
204
222
  # or for logging via alternative means.
205
223
  def prepare_for_logging args, headers={}
206
- args = Promoted::Ruby::Client::Util.translate_args(args)
224
+ args = Promoted::Ruby::Client::Util.translate_hash(args)
207
225
 
208
226
  if !@enabled
209
227
  return {
@@ -248,6 +266,22 @@ module Promoted
248
266
 
249
267
  private
250
268
 
269
+ def do_warmup
270
+ if !@delivery_endpoint
271
+ # Warmup only supported when delivery is enabled.
272
+ return
273
+ end
274
+
275
+ warmup_url = @delivery_endpoint.reverse.sub("/deliver".reverse, "/healthz".reverse).reverse
276
+ @logger.info("Warming up at #{warmup_url}") if @logger
277
+ 1.upto(20) do
278
+ resp = @http_client.get(warmup_url)
279
+ if resp != "ok"
280
+ @logger.warn("Got a failure warming up") if @logger
281
+ end
282
+ end
283
+ end
284
+
251
285
  def send_request payload, endpoint, timeout_millis, api_key, headers={}, send_async=false
252
286
  resp = nil
253
287
 
@@ -300,13 +334,22 @@ module Promoted
300
334
  delivery_request_builder = RequestBuilder.new
301
335
  delivery_request_builder.set_request_params args
302
336
 
303
- delivery_request_params = delivery_request_builder.delivery_request_params(should_compact: false)
337
+ delivery_request_params = delivery_request_builder.delivery_request_params
304
338
  delivery_request_params[:client_info][:traffic_type] = Promoted::Ruby::Client::TRAFFIC_TYPE['SHADOW']
305
339
 
340
+ begin
341
+ @pager.validate_paging(delivery_request_builder.full_insertion, delivery_request_builder.request[:paging])
342
+ rescue InvalidPagingError => err
343
+ # Invalid input, log and skip.
344
+ @logger.warn("Shadow traffic call failed with invalid paging #{err}") if @logger
345
+ return
346
+ end
347
+
306
348
  # Call Delivery API and log/ignore errors.
307
349
  start_time = Time.now
350
+ response = nil
308
351
  begin
309
- send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, @delivery_api_key, headers, @async_shadow_traffic)
352
+ response = send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, @delivery_api_key, headers, @async_shadow_traffic)
310
353
  rescue StandardError => err
311
354
  @logger.warn("Shadow traffic call failed with #{err}") if @logger
312
355
  return
@@ -314,7 +357,8 @@ module Promoted
314
357
 
315
358
  if !@async_shadow_traffic
316
359
  ellapsed_time = Time.now - start_time
317
- @logger.info("Shadow traffic call completed in #{ellapsed_time.to_f * 1000} ms") if @logger
360
+ insertions = response ? response[:insertion] : []
361
+ @logger.info("Shadow traffic call completed in #{ellapsed_time.to_f * 1000} ms with #{insertions.length} insertions") if @logger
318
362
  end
319
363
  end
320
364
 
@@ -32,6 +32,13 @@ module Promoted
32
32
  CLIENT_TYPE = {'UNKNOWN_REQUEST_CLIENT' => 'UNKNOWN_REQUEST_CLIENT',
33
33
  'PLATFORM_SERVER' => 'PLATFORM_SERVER',
34
34
  'PLATFORM_CLIENT' => 'PLATFORM_CLIENT'}
35
+
36
+ EXECUTION_SERVER = {'API' => 'API', 'SDK' => 'SDK'}
37
+
38
+ DEVICE_TYPE = {'UNKNOWN_DEVICE_TYPE' => 'UNKNOWN_DEVICE_TYPE',
39
+ 'DESKTOP' => 'DESKTOP',
40
+ 'MOBILE' => 'MOBILE',
41
+ 'TABLET' => 'TABLET'}
35
42
  end
36
43
  end
37
44
  end
@@ -7,6 +7,12 @@ module Promoted
7
7
  end
8
8
  end
9
9
 
10
+ class DeliveryInsertionPageType < StandardError
11
+ def message
12
+ 'Delivery insertions must be unpaged'
13
+ end
14
+ end
15
+
10
16
  class EndpointError < StandardError
11
17
  attr_reader :cause
12
18
  def initialize(cause)
@@ -1,15 +1,19 @@
1
1
  require 'faraday'
2
2
  require 'faraday_middleware'
3
+ require 'promoted/ruby/client/util'
3
4
 
4
5
  module Promoted
5
6
  module Ruby
6
7
  module Client
7
8
  class FaradayHTTPClient
8
9
 
9
- def initialize
10
+ def initialize(logger = nil)
10
11
  @conn = Faraday.new do |f|
11
12
  f.request :json
12
13
  f.request :retry, max: 3
14
+ if logger
15
+ f.response :logger, logger, { headers: false, bodies: true, log_level: :debug }
16
+ end
13
17
  f.use Faraday::Response::RaiseError # raises on 4xx and 5xx responses
14
18
  f.adapter :net_http_persistent
15
19
  end
@@ -25,11 +29,15 @@ module Promoted
25
29
 
26
30
  norm_headers = response.headers.transform_keys(&:downcase)
27
31
  if norm_headers["content-type"] != nil && norm_headers["content-type"].start_with?("application/json")
28
- JSON.parse(response.body, :symbolize_names => true)
32
+ Promoted::Ruby::Client::Util.translate_hash(JSON.parse(response.body))
29
33
  else
30
34
  response.body
31
35
  end
32
36
  end
37
+
38
+ def get(endpoint)
39
+ @conn.get(endpoint).body
40
+ end
33
41
  end
34
42
  end
35
43
  end
@@ -1,18 +1,28 @@
1
1
  module Promoted
2
2
  module Ruby
3
3
  module Client
4
+ class InvalidPagingError < StandardError
5
+ attr_reader :default_insertions_page
6
+
7
+ def initialize(message, default_insertions_page)
8
+ super(message)
9
+ @default_insertions_page = default_insertions_page
10
+ end
11
+ end
12
+
4
13
  class Pager
5
14
  def validate_paging (insertions, paging)
6
- if paging && paging[:offset]
7
- return paging[:offset] < insertions.length
15
+ if paging && paging[:offset] && paging[:offset] >= insertions.length
16
+ raise InvalidPagingError.new("Invalid page offset (insertion size #{insertions.length}, offset #{paging[:offset]})", [])
8
17
  end
9
- return true
10
18
  end
11
19
 
12
20
  def apply_paging (insertions, insertion_page_type, paging = nil)
13
- # This is invalid input, stop it before it goes to the server.
14
- if !validate_paging(insertions, paging)
15
- return []
21
+ begin
22
+ validate_paging(insertions, paging)
23
+ rescue InvalidPagingError => err
24
+ # This is invalid input, stop it before it goes to the server.
25
+ return err.default_insertions_page
16
26
  end
17
27
 
18
28
  if !paging
@@ -54,4 +64,4 @@ module Promoted
54
64
  end
55
65
  end
56
66
  end
57
- end
67
+ end
@@ -2,9 +2,9 @@ module Promoted
2
2
  module Ruby
3
3
  module Client
4
4
  class RequestBuilder
5
- attr_reader :session_id, :only_log, :experiment, :client_info,
6
- :view_id, :insertion, :to_compact_delivery_insertion_func,
7
- :request_id, :full_insertion, :use_case, :request, :to_compact_metrics_insertion_func
5
+ attr_reader :session_id, :only_log, :experiment, :client_info, :device,
6
+ :view_id, :insertion, :to_compact_delivery_properties_func,
7
+ :request_id, :full_insertion, :use_case, :request, :to_compact_metrics_properties_func
8
8
 
9
9
  attr_accessor :timing, :user_info, :platform_id
10
10
 
@@ -24,13 +24,14 @@ module Promoted
24
24
  @session_id = request[:session_id]
25
25
  @platform_id = request[:platform_id]
26
26
  @client_info = request[:client_info] || {}
27
+ @device = request[:device] || {}
27
28
  @view_id = request[:view_id]
28
29
  @use_case = Promoted::Ruby::Client::USE_CASES[request[:use_case]] || Promoted::Ruby::Client::USE_CASES['UNKNOWN_USE_CASE']
29
30
  @full_insertion = args[:full_insertion]
30
31
  @user_info = request[:user_info] || { :user_id => nil, :log_user_id => nil}
31
32
  @timing = request[:timing] || { :client_log_timestamp => Time.now.to_i }
32
- @to_compact_metrics_insertion_func = args[:to_compact_metrics_insertion_func]
33
- @to_compact_delivery_insertion_func = args[:to_compact_delivery_insertion_func]
33
+ @to_compact_metrics_properties_func = args[:to_compact_metrics_properties_func]
34
+ @to_compact_delivery_properties_func = args[:to_compact_delivery_properties_func]
34
35
 
35
36
  # If the user didn't create a client request id, we do it for them.
36
37
  request[:client_request_id] = request[:client_request_id] || @id_generator.newID
@@ -52,11 +53,12 @@ module Promoted
52
53
  end
53
54
 
54
55
  # Only used in delivery
55
- def delivery_request_params(should_compact: true)
56
+ def delivery_request_params
56
57
  params = {
57
58
  user_info: user_info,
58
59
  timing: timing,
59
60
  client_info: @client_info.merge({ :client_type => Promoted::Ruby::Client::CLIENT_TYPE['PLATFORM_SERVER'] }),
61
+ device: @device,
60
62
  platform_id: @platform_id,
61
63
  view_id: @view_id,
62
64
  session_id: @session_id,
@@ -64,9 +66,9 @@ module Promoted
64
66
  search_query: request[:search_query],
65
67
  properties: request[:properties],
66
68
  paging: request[:paging],
67
- client_request_id: request[:client_request_id]
69
+ client_request_id: client_request_id
68
70
  }
69
- params[:insertion] = should_compact ? compact_delivery_insertions : full_insertion
71
+ params[:insertion] = insertions_with_compact_props(@to_compact_delivery_properties_func)
70
72
 
71
73
  params.clean!
72
74
  end
@@ -80,7 +82,10 @@ module Promoted
80
82
  end
81
83
 
82
84
  props = @full_insertion.each_with_object({}) do |insertion, hash|
83
- hash[insertion[:content_id]] = insertion[:properties]
85
+ if insertion.has_key?(:properties)
86
+ # Don't add nil properties to response insertions.
87
+ hash[insertion[:content_id]] = insertion[:properties]
88
+ end
84
89
  end
85
90
 
86
91
  filled_in_copy = []
@@ -99,10 +104,14 @@ module Promoted
99
104
  params = {
100
105
  user_info: user_info,
101
106
  timing: timing,
102
- cohort_membership: @experiment,
103
- client_info: @client_info
107
+ client_info: @client_info,
108
+ device: @device
104
109
  }
105
110
 
111
+ if @experiment
112
+ params[:cohort_membership] = [@experiment]
113
+ end
114
+
106
115
  # Log request allows for multiple requests but here we only send one.
107
116
  if include_request
108
117
  request[:request_id] = request[:request_id] || @id_generator.newID
@@ -110,18 +119,38 @@ module Promoted
110
119
  end
111
120
 
112
121
  if include_insertions
113
- params[:insertion] = compact_metrics_insertions if include_insertions
122
+ params[:insertion] = compact_metrics_properties if include_insertions
114
123
  add_missing_ids_on_insertions! request, params[:insertion]
115
124
  end
116
125
 
117
126
  params.clean!
118
127
  end
119
128
 
120
- def compact_delivery_insertions
121
- if !@to_compact_delivery_insertion_func
129
+ def compact_one_insertion(insertion, compact_func)
130
+ return insertion if (!compact_func || !insertion[:properties])
131
+
132
+ # Only need a copy if there are properties to compact.
133
+ compact_insertion = insertion.dup
134
+
135
+ # Let the custom function work with a deep copy of the properties.
136
+ # There's really no way to work with a shallow copy and still be able
137
+ # to restore the correct insertion properties after a call to delivery.
138
+ new_props = Marshal.load(Marshal.dump(insertion[:properties]))
139
+ compact_insertion[:properties] = compact_func.call(new_props)
140
+ compact_insertion.clean!
141
+ return compact_insertion
142
+ end
143
+
144
+ def insertions_with_compact_props(compact_func)
145
+ if !compact_func
146
+ # Nothing to do, avoid copying the whole array.
122
147
  full_insertion
123
148
  else
124
- full_insertion.map {|insertion| @to_compact_delivery_insertion_func.call(insertion) }
149
+ compact_insertions = Array.new(full_insertion.length)
150
+ full_insertion.each_with_index {|insertion, index|
151
+ compact_insertions[index] = compact_one_insertion(insertion, compact_func)
152
+ }
153
+ compact_insertions
125
154
  end
126
155
  end
127
156
 
@@ -132,7 +161,7 @@ module Promoted
132
161
  end
133
162
 
134
163
  # TODO: This looks overly complicated.
135
- def compact_metrics_insertions
164
+ def compact_metrics_properties
136
165
  @insertion = [] # insertion should be set according to the compact insertion
137
166
  paging = request[:paging] || {}
138
167
  size = paging[:size] ? paging[:size].to_i : 0
@@ -152,12 +181,16 @@ module Promoted
152
181
  insertion_obj[:insertion_id] = @id_generator.newID
153
182
  insertion_obj[:request_id] = request_id
154
183
  insertion_obj[:position] = offset + index
155
- insertion_obj = @to_compact_metrics_insertion_func.call(insertion_obj) if @to_compact_metrics_insertion_func
184
+ insertion_obj = compact_one_insertion(insertion_obj, @to_compact_metrics_properties_func)
156
185
  @insertion << insertion_obj.clean!
157
186
  end
158
187
  @insertion
159
188
  end
160
189
 
190
+ def client_request_id
191
+ request[:client_request_id]
192
+ end
193
+
161
194
  private
162
195
 
163
196
  def add_missing_ids_on_insertions! request, insertions
@@ -2,16 +2,39 @@ module Promoted
2
2
  module Ruby
3
3
  module Client
4
4
  module Util
5
- def self.translate_args(args)
5
+ def self.translate_array(arr)
6
+ sym_arr = Array.new(arr.length)
7
+ arr.each_with_index do |v, i|
8
+ new_v = v
9
+ case v
10
+ when Hash
11
+ new_v = translate_hash(v)
12
+ when Array
13
+ new_v = translate_array(v)
14
+ end
15
+ sym_arr[i] = new_v
16
+ end
17
+ sym_arr
18
+ end
19
+
20
+ def self.translate_hash(args)
6
21
  sym_hash = {}
7
22
  args.each do |k, v|
8
- sym_hash[k.to_s.to_underscore.to_sym] = v.is_a?(Hash) ? translate_args(v) : v
23
+ new_key = k.to_s.to_underscore.to_sym
24
+ case v
25
+ when Hash
26
+ sym_hash[new_key] = translate_hash(v)
27
+ when Array
28
+ sym_hash[new_key] = translate_array(v)
29
+ else
30
+ sym_hash[new_key] = v
31
+ end
9
32
  end
10
33
  sym_hash
11
34
  rescue => e
12
35
  raise 'Unable to parse args. Please pass correct arguments. Must be JSON'
13
36
  end
14
- end
37
+ end
15
38
  end
16
39
  end
17
40
  end
@@ -1,7 +1,7 @@
1
1
  module Promoted
2
2
  module Ruby
3
3
  module Client
4
- VERSION = "0.1.15"
4
+ VERSION = "0.1.19"
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: promoted-ruby-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.15
4
+ version: 0.1.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - scottmcmaster
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-24 00:00:00.000000000 Z
11
+ date: 2021-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -123,6 +123,7 @@ extensions: []
123
123
  extra_rdoc_files:
124
124
  - README.md
125
125
  files:
126
+ - ".github/workflows/push-pull-requests_check.yml"
126
127
  - ".gitignore"
127
128
  - Gemfile
128
129
  - Gemfile.lock
@@ -167,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
167
168
  - !ruby/object:Gem::Version
168
169
  version: '0'
169
170
  requirements: []
170
- rubygems_version: 3.0.3
171
+ rubygems_version: 3.2.24
171
172
  signing_key:
172
173
  specification_version: 4
173
174
  summary: A Ruby Client to contact Promoted APIs.