promoted-ruby-client 0.1.17 → 0.1.21

Sign up to get free protection for your applications and to get access to all the features.
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