promoted-ruby-client 0.1.17 → 0.1.21

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: c2cabc9fa3074e1eed3fd795eaa180a0e47ee18b8eceacb8c3516a347670a5d7
4
- data.tar.gz: 8c32e19fe9227fe327ec3995e384477e087ed7d5b4ed48bb5dd0b6a78fdf7ac2
3
+ metadata.gz: 526e2104c5f4658b67b971a1a2f19639ea383ddcee8556dd496c906c7aed8d86
4
+ data.tar.gz: f3e6afce46a2995b8497f8426d7fcccda89e9a0c533d270a55dd72598c6a0287
5
5
  SHA512:
6
- metadata.gz: daa95d7fa296498252bd833106fddd6f08c21c1ce3fcc3aa8afa2e79d30d04e914bdc978cafa13d436b5364d91d9467f568d210d86d5d948a0500be3de7b2c8a
7
- data.tar.gz: 757d9829c35ab58512093f0940f1f5d02aef11b23f94523a09878eeb82b717f047e83936b867aa60f34fa8ce9da2aca45d1be8ecf8bb9cad6b4c8de5b047b723
6
+ metadata.gz: c3b5e39490f8ceac8c261cfd6a7b3c9264980b98306bf7ac520fcc4f4855600329f43fc1eee55a22f4c8968c1d44eaa1ae47fa1ed737d61fbce6b8e5064eabb8
7
+ data.tar.gz: 94fe93237603bc0be26b54a5a7ecbd39f45065d2e878610e240079a946ecde5697b39894a6c7b2fd2817b51fdc60462d5edc66c279be3038e79618e14c9e6deb
@@ -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.17)
4
+ promoted-ruby-client (0.1.21)
5
5
  concurrent-ruby (~> 1)
6
6
  faraday (>= 0.9.0)
7
7
  faraday_middleware (>= 0.9.0)
data/README.md CHANGED
@@ -49,6 +49,7 @@ Name | Type | Description
49
49
  ```:perform_checks``` | Boolean | Whether or not to perform detailed input validation, defaults to true but may be disabled for performance
50
50
  ```:logger``` | Ruby Logger-compatible logger | Defaults to nil (no logging). Example: ```Logger.new(STDERR, :progname => 'promotedai')```
51
51
  ```:shadow_traffic_delivery_percent``` | Number between 0 and 1 | % of ```prepare_for_logging``` traffic that gets directed to Delivery API as "shadow traffic". Defaults to 0 (no shadow traffic).
52
+ ```:send_shadow_traffic_for_control``` | Boolean | If true, the ```deliver``` method will send shadow traffic for users in the CONTROL arm of an experiment. Defaults to true.
52
53
  ```:default_request_headers``` | Hash | Additional headers to send on the request beyond ```x-api-key```. Defaults to {}
53
54
  ```: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.
54
55
  ```: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.
@@ -94,7 +95,73 @@ Field Name | Type | Optional? | Description
94
95
  ```:request_id``` | String | Yes | Generated by the SDK when needed (*do not set*)
95
96
  ```:content_id``` | String | No | Identifier for the content to be shown, must be set.
96
97
  ```:properties``` | Properties | Yes | Any additional custom properties to associate. For v1 integrations, it is fine not to fill in all the properties.
98
+ ---
99
+ ### Size
100
+ User's screen dimensions.
101
+ Field Name | Type | Optional? | Description
102
+ ---------- | ---- | --------- | -----------
103
+ ```:width``` | Integer | No | Screen width
104
+ ```:height``` | Integer | No | Screen height
105
+ ---
97
106
 
107
+ ### Screen
108
+ State of the screen including scaling.
109
+ Field Name | Type | Optional? | Description
110
+ ---------- | ---- | --------- | -----------
111
+ ```:size``` | Size | Yes | Screen size
112
+ ```:scale``` | Float | Yes | Current screen scaling factor
113
+ ---
114
+
115
+ ### ClientHints
116
+ Alternative to user-agent strings. See https://raw.githubusercontent.com/snowplow/iglu-central/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0
117
+ Field Name | Type | Optional? | Description
118
+ ---------- | ---- | --------- | -----------
119
+ ```:is_mobile``` | Boolean | Yes | Mobile flag
120
+ ```:brand``` | Array of ClientBrandHint | Yes |
121
+ ```:architecture``` | String | Yes |
122
+ ```:model``` | String | Yes |
123
+ ```:platform``` | String | Yes |
124
+ ```:platform_version``` | String | Yes |
125
+ ```:ua_full_version``` | String | Yes |
126
+
127
+ ---
128
+ ### ClientBrandHint
129
+ See https://raw.githubusercontent.com/snowplow/iglu-central/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0
130
+ Field Name | Type | Optional? | Description
131
+ ---------- | ---- | --------- | -----------
132
+ ```:brand``` | String | Yes | Mobile flag
133
+ ```:version``` | String | Yes |
134
+
135
+ ---
136
+ ### Location
137
+ Information about the user's location.
138
+ Field Name | Type | Optional? | Description
139
+ ---------- | ---- | --------- | -----------
140
+ ```:latitude``` | Float | No | Location latitude
141
+ ```:longitude``` | Float | No | Location longitude
142
+ ```:accuracy_in_meters``` | Integer | Yes | Location accuracy if available
143
+ ---
144
+
145
+ ### Browser
146
+ Information about the user's browser.
147
+ Field Name | Type | Optional? | Description
148
+ ---------- | ---- | --------- | -----------
149
+ ```:user_agent``` | String | Yes | Browser user agent string
150
+ ```:viewport_size``` | Size | Yes | Size of the browser viewport
151
+ ```:client_hints``` | ClientHints | Yes | HTTP client hints structure
152
+ ---
153
+ ### Device
154
+ Information about the user's device.
155
+ Field Name | Type | Optional? | Description
156
+ ---------- | ---- | --------- | -----------
157
+ ```:device_type``` | one of (`UNKNOWN_DEVICE_TYPE`, `DESKTOP`, `MOBILE`, `TABLET`) | Yes | Type of device
158
+ ```:brand``` | String | Yes | "Apple, "google", Samsung", etc.
159
+ ```:manufacturer``` | String | Yes | "Apple", "HTC", Motorola", "HUAWEI", etc.
160
+ ```:identifier``` | String | Yes | Android: android.os.Build.MODEL; iOS: iPhoneXX,YY, etc.
161
+ ```:screen``` | Screen | Yes | Screen dimensions
162
+ ```:ip_address``` | String | Yes | Originating IP address
163
+ ```:location``` | Location | Yes | Location information
164
+ ```:browser``` | Browser | Yes | Browser information
98
165
  ---
99
166
  ### Paging
100
167
  #### TODO
@@ -108,6 +175,7 @@ Field Name | Type | Optional? | Description
108
175
  ```: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)).
109
176
  ```:properties``` | Properties | Yes | Any additional custom properties to associate.
110
177
  ```:paging``` | Paging | Yes | Paging parameters (see TODO)
178
+ ```:device``` | Device | Yes | Device information (as available)
111
179
  ---
112
180
  ### MetricsRequest
113
181
  Input to ```prepare_for_logging```
@@ -142,6 +210,8 @@ Field Name | Type | Optional? | Description
142
210
  ---------- | ---- | --------- | -----------
143
211
  ```: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).
144
212
  ```: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.
213
+ ```:client_request_id``` | String | Yes | Client-generated request id sent to Delivery API and may be useful for logging and debugging.
214
+ ```:execution_server``` | one of 'API' or 'SDK' | Yes | Indicates if response insertions on a delivery request came from the API or the SDK.
145
215
  ---
146
216
 
147
217
  ### PromotedClient
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.17.gem`
7
+ 5. Run (using new output) `gem push promoted-ruby-client-0.1.21.gem`
8
8
  6. Update README with new version.
@@ -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
@@ -11,13 +11,13 @@ module Promoted
11
11
  end
12
12
 
13
13
  class Pager
14
- def validate_paging (insertions, paging)
14
+ def validate_paging(insertions, paging)
15
15
  if paging && paging[:offset] && paging[:offset] >= insertions.length
16
16
  raise InvalidPagingError.new("Invalid page offset (insertion size #{insertions.length}, offset #{paging[:offset]})", [])
17
17
  end
18
18
  end
19
19
 
20
- def apply_paging (insertions, insertion_page_type, paging = nil)
20
+ def apply_paging(insertions, insertion_page_type, paging = nil)
21
21
  begin
22
22
  validate_paging(insertions, paging)
23
23
  rescue InvalidPagingError => err
@@ -47,7 +47,7 @@ module Promoted
47
47
  size = insertions.length
48
48
  end
49
49
 
50
- final_insertion_size = [size, insertions.length].min
50
+ final_insertion_size = [size, insertions.length - index].min
51
51
  insertion_page = Array.new(final_insertion_size)
52
52
  0.upto(final_insertion_size - 1) {|i|
53
53
  insertion = insertions[index]
@@ -63,5 +63,5 @@ module Promoted
63
63
  end
64
64
  end
65
65
  end
66
- end
66
+ end
67
67
  end
@@ -2,7 +2,7 @@ module Promoted
2
2
  module Ruby
3
3
  module Client
4
4
  class RequestBuilder
5
- attr_reader :session_id, :only_log, :experiment, :client_info,
5
+ attr_reader :session_id, :only_log, :experiment, :client_info, :device,
6
6
  :view_id, :insertion, :to_compact_delivery_properties_func,
7
7
  :request_id, :full_insertion, :use_case, :request, :to_compact_metrics_properties_func
8
8
 
@@ -24,6 +24,7 @@ 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]
@@ -56,7 +57,8 @@ module Promoted
56
57
  params = {
57
58
  user_info: user_info,
58
59
  timing: timing,
59
- client_info: @client_info.merge({ :client_type => Promoted::Ruby::Client::CLIENT_TYPE['PLATFORM_SERVER'] }),
60
+ client_info: merge_client_info_defaults,
61
+ device: @device,
60
62
  platform_id: @platform_id,
61
63
  view_id: @view_id,
62
64
  session_id: @session_id,
@@ -64,7 +66,7 @@ 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
71
  params[:insertion] = insertions_with_compact_props(@to_compact_delivery_properties_func)
70
72
 
@@ -76,7 +78,7 @@ module Promoted
76
78
  # to the responses.
77
79
  def fill_details_from_response response_insertions
78
80
  if !response_insertions then
79
- response_insertions = full_insertion
81
+ response_insertions = []
80
82
  end
81
83
 
82
84
  props = @full_insertion.each_with_object({}) do |insertion, hash|
@@ -102,7 +104,8 @@ module Promoted
102
104
  params = {
103
105
  user_info: user_info,
104
106
  timing: timing,
105
- client_info: @client_info
107
+ client_info: merge_client_info_defaults,
108
+ device: @device
106
109
  }
107
110
 
108
111
  if @experiment
@@ -128,7 +131,12 @@ module Promoted
128
131
 
129
132
  # Only need a copy if there are properties to compact.
130
133
  compact_insertion = insertion.dup
131
- compact_insertion[:properties] = compact_func.call(insertion[:properties])
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)
132
140
  compact_insertion.clean!
133
141
  return compact_insertion
134
142
  end
@@ -179,8 +187,19 @@ module Promoted
179
187
  @insertion
180
188
  end
181
189
 
190
+ def client_request_id
191
+ request[:client_request_id]
192
+ end
193
+
182
194
  private
183
195
 
196
+ def merge_client_info_defaults
197
+ return @client_info.merge({
198
+ :client_type => Promoted::Ruby::Client::CLIENT_TYPE['PLATFORM_SERVER'],
199
+ :traffic_type => Promoted::Ruby::Client::TRAFFIC_TYPE['PRODUCTION']
200
+ })
201
+ end
202
+
184
203
  def add_missing_ids_on_insertions! request, insertions
185
204
  insertions.each do |insertion|
186
205
  insertion[:insertion_id] = @id_generator.newID if not insertion[:insertion_id]
@@ -1,7 +1,7 @@
1
1
  module Promoted
2
2
  module Ruby
3
3
  module Client
4
- VERSION = "0.1.17"
4
+ VERSION = "0.1.21"
5
5
  end
6
6
  end
7
7
  end
@@ -19,7 +19,8 @@ module Promoted
19
19
  class Error < StandardError; end
20
20
 
21
21
  attr_reader :perform_checks, :default_only_log, :delivery_timeout_millis, :metrics_timeout_millis, :should_apply_treatment_func,
22
- :default_request_headers, :http_client, :logger, :shadow_traffic_delivery_percent, :async_shadow_traffic
22
+ :default_request_headers, :http_client, :logger, :shadow_traffic_delivery_percent, :async_shadow_traffic,
23
+ :send_shadow_traffic_for_control
23
24
 
24
25
  attr_accessor :request_logging_on, :enabled
25
26
 
@@ -39,7 +40,7 @@ module Promoted
39
40
 
40
41
  ##
41
42
  # Create and configure a new Promoted client.
42
- def initialize (params={})
43
+ def initialize(params={})
43
44
  @perform_checks = true
44
45
  if params[:perform_checks] != nil
45
46
  @perform_checks = params[:perform_checks]
@@ -79,6 +80,11 @@ module Promoted
79
80
  @async_shadow_traffic = params[:async_shadow_traffic] || false
80
81
  end
81
82
 
83
+ @send_shadow_traffic_for_control = true
84
+ if params[:send_shadow_traffic_for_control] != nil
85
+ @send_shadow_traffic_for_control = params[:send_shadow_traffic_for_control] || false
86
+ end
87
+
82
88
  @pool = nil
83
89
  if @async_shadow_traffic
84
90
  # Thread pool to process delivery of shadow traffic. Will silently drop excess requests beyond the queue
@@ -161,9 +167,8 @@ module Promoted
161
167
  cohort_membership_to_log = delivery_request_builder.new_cohort_membership_to_log
162
168
 
163
169
  if should_apply_treatment(cohort_membership_to_log)
164
- delivery_request_params = delivery_request_builder.delivery_request_params
165
-
166
- # Call Delivery API
170
+ # Call Delivery API to get insertions to use
171
+ delivery_request_params = delivery_request_builder.delivery_request_params
167
172
  begin
168
173
  response = send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, @delivery_api_key, headers)
169
174
  rescue StandardError => err
@@ -172,11 +177,14 @@ module Promoted
172
177
  deliver_err = true
173
178
  @logger.error("Error calling delivery: " + err.message) if @logger
174
179
  end
175
-
176
- insertions_from_delivery = (response != nil && !deliver_err);
177
- response_insertions = delivery_request_builder.fill_details_from_response(
178
- response ? response[:insertion] : [])
180
+ elsif @send_shadow_traffic_for_control
181
+ # Call Delivery API to send shadow traffic. This will create the request params with traffic type set.
182
+ deliver_shadow_traffic args, headers
179
183
  end
184
+
185
+ insertions_from_delivery = (response != nil && !deliver_err);
186
+ response_insertions = delivery_request_builder.fill_details_from_response(
187
+ response && response[:insertion] || [])
180
188
  end
181
189
 
182
190
  request_to_log = nil
@@ -210,7 +218,9 @@ module Promoted
210
218
 
211
219
  client_response = {
212
220
  insertion: response_insertions,
213
- log_request: log_req
221
+ log_request: log_req,
222
+ execution_server: insertions_from_delivery ? Promoted::Ruby::Client::EXECUTION_SERVER['API'] : Promoted::Ruby::Client::EXECUTION_SERVER['SDK'],
223
+ client_request_id: delivery_request_builder.client_request_id
214
224
  }
215
225
  return client_response
216
226
  end
@@ -316,7 +326,7 @@ module Promoted
316
326
 
317
327
  ellapsed_time = Time.now - start_time
318
328
  @logger.debug("Sync send_request completed in #{ellapsed_time.to_f * 1000} ms") if @logger
319
- end
329
+ end
320
330
 
321
331
  return resp
322
332
  end
@@ -335,6 +345,14 @@ module Promoted
335
345
  delivery_request_params = delivery_request_builder.delivery_request_params
336
346
  delivery_request_params[:client_info][:traffic_type] = Promoted::Ruby::Client::TRAFFIC_TYPE['SHADOW']
337
347
 
348
+ begin
349
+ @pager.validate_paging(delivery_request_builder.full_insertion, delivery_request_builder.request[:paging])
350
+ rescue InvalidPagingError => err
351
+ # Invalid input, log and skip.
352
+ @logger.warn("Shadow traffic call failed with invalid paging #{err}") if @logger
353
+ return
354
+ end
355
+
338
356
  # Call Delivery API and log/ignore errors.
339
357
  start_time = Time.now
340
358
  response = nil
@@ -347,7 +365,7 @@ module Promoted
347
365
 
348
366
  if !@async_shadow_traffic
349
367
  ellapsed_time = Time.now - start_time
350
- insertions = response ? response[:insertion] : []
368
+ insertions = response && response[:insertion] || []
351
369
  @logger.info("Shadow traffic call completed in #{ellapsed_time.to_f * 1000} ms with #{insertions.length} insertions") if @logger
352
370
  end
353
371
  end
@@ -14,6 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = 'https://github.com/promotedai/promoted-ruby-client'
15
15
  spec.license = 'MIT'
16
16
 
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org/"
17
18
  spec.metadata["homepage_uri"] = spec.homepage
18
19
  spec.metadata["source_code_uri"] = "https://github.com/promotedai/promoted-ruby-client"
19
20
  spec.metadata["changelog_uri"] = "https://github.com/promotedai/promoted-ruby-client/blob/master/CHANGELOG.md"
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.17
4
+ version: 0.1.21
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-29 00:00:00.000000000 Z
11
+ date: 2021-09-02 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
@@ -149,6 +150,7 @@ homepage: https://github.com/promotedai/promoted-ruby-client
149
150
  licenses:
150
151
  - MIT
151
152
  metadata:
153
+ allowed_push_host: https://rubygems.org/
152
154
  homepage_uri: https://github.com/promotedai/promoted-ruby-client
153
155
  source_code_uri: https://github.com/promotedai/promoted-ruby-client
154
156
  changelog_uri: https://github.com/promotedai/promoted-ruby-client/blob/master/CHANGELOG.md