promoted-ruby-client 2.0.2 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +6 -8
- data/README.md +17 -19
- data/dev.md +2 -2
- data/lib/promoted/ruby/client/constants.rb +0 -3
- data/lib/promoted/ruby/client/errors.rb +0 -12
- data/lib/promoted/ruby/client/pager.rb +13 -13
- data/lib/promoted/ruby/client/request_builder.rb +1 -1
- data/lib/promoted/ruby/client/validator.rb +1 -1
- data/lib/promoted/ruby/client/version.rb +1 -1
- data/lib/promoted/ruby/client.rb +7 -16
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4d336aef96b116acbe2e611279e9ccefe5efce88063d5c530fb669b32383d10
|
4
|
+
data.tar.gz: 6df512d099b5a5b9414b2672847585db2118825b87c178b923da26ba2cb610e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7375f08d88303d6de8eefaf3a4b7e2b80333f15e8b709781c4884aee54d84c9d64b6bdc834c4a5c53f1f426a04a5aed9752cc7ceba78d6015f100d7ee6096e0c
|
7
|
+
data.tar.gz: 2cdff05db0a989331ca56e0f18716f53e635ea7d000d685275693b21445a313763e2001827b201ec2863173eb52f54744b50177f824ae7a0d44a85f975105e5e
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
promoted-ruby-client (
|
4
|
+
promoted-ruby-client (4.0.0)
|
5
5
|
concurrent-ruby (~> 1)
|
6
6
|
faraday (>= 0.9.0)
|
7
7
|
faraday_middleware (>= 0.9.0)
|
@@ -52,13 +52,12 @@ GEM
|
|
52
52
|
rexml
|
53
53
|
kramdown-parser-gfm (1.1.0)
|
54
54
|
kramdown (~> 2.0)
|
55
|
-
language_server-protocol (3.17.0.3)
|
56
55
|
multipart-post (2.3.0)
|
57
56
|
net-http-persistent (4.0.2)
|
58
57
|
connection_pool (~> 2.2)
|
59
|
-
nokogiri (1.15.
|
58
|
+
nokogiri (1.15.4-arm64-darwin)
|
60
59
|
racc (~> 1.4)
|
61
|
-
nokogiri (1.15.
|
60
|
+
nokogiri (1.15.4-x86_64-linux)
|
62
61
|
racc (~> 1.4)
|
63
62
|
parallel (1.23.0)
|
64
63
|
parser (3.2.2.3)
|
@@ -85,11 +84,10 @@ GEM
|
|
85
84
|
diff-lcs (>= 1.2.0, < 2.0)
|
86
85
|
rspec-support (~> 3.12.0)
|
87
86
|
rspec-support (3.12.1)
|
88
|
-
rubocop (1.
|
87
|
+
rubocop (1.50.2)
|
89
88
|
json (~> 2.3)
|
90
|
-
language_server-protocol (>= 3.17.0)
|
91
89
|
parallel (~> 1.10)
|
92
|
-
parser (>= 3.2.
|
90
|
+
parser (>= 3.2.0.0)
|
93
91
|
rainbow (>= 2.2.2, < 4.0)
|
94
92
|
regexp_parser (>= 1.8, < 3.0)
|
95
93
|
rexml (>= 3.2.5, < 4.0)
|
@@ -142,4 +140,4 @@ DEPENDENCIES
|
|
142
140
|
solargraph
|
143
141
|
|
144
142
|
BUNDLED WITH
|
145
|
-
2.4.
|
143
|
+
2.4.19
|
data/README.md
CHANGED
@@ -20,7 +20,7 @@ HTTP client for calling Promoted.
|
|
20
20
|
### [Net::HTTP::Persistent](https://github.com/drbrain/net-http-persistent)
|
21
21
|
Faraday binding (provides connection pool support)
|
22
22
|
### [Concurrent Ruby](https://github.com/ruby-concurrency/concurrent-ruby)
|
23
|
-
Provides a thread pool for making shadow traffic requests to Delivery API in the background on a subset of calls to ```
|
23
|
+
Provides a thread pool for making shadow traffic requests to Delivery API in the background on a subset of calls to ```deliver```
|
24
24
|
## Creating a Client
|
25
25
|
```rb
|
26
26
|
client = Promoted::Ruby::Client::PromotedClient.new
|
@@ -48,7 +48,7 @@ Name | Type | Description
|
|
48
48
|
```:metrics_timeout_millis``` | Number | Timeout on the Metrics API call. Defaults to 3000.
|
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
|
-
```:shadow_traffic_delivery_percent``` | Number between 0 and 1 | % of ```
|
51
|
+
```:shadow_traffic_delivery_percent``` | Number between 0 and 1 | % of ```deliver``` traffic that gets directed to Delivery API as "shadow traffic". Defaults to 0 (no shadow traffic).
|
52
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.
|
53
53
|
```:default_request_headers``` | Hash | Additional headers to send on the request beyond ```x-api-key```. Defaults to {}
|
54
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.
|
@@ -63,7 +63,7 @@ Basic information about the request user.
|
|
63
63
|
Field Name | Type | Optional? | Description
|
64
64
|
---------- | ---- | --------- | -----------
|
65
65
|
```:user_id``` | String | Yes | The platform user id, cleared from Promoted logs.
|
66
|
-
```:
|
66
|
+
```:anon_user_id``` | String | Yes | A different user id (presumably a UUID) disconnected from the platform user id, good for working with unauthenticated users or implementing right-to-be-forgotten.
|
67
67
|
```:is_internal_user``` | Boolean | Yes | If this user is a test user or not, defaults to false.
|
68
68
|
|
69
69
|
---
|
@@ -169,7 +169,14 @@ Field Name | Type | Optional? | Description
|
|
169
169
|
```:browser``` | Browser | Yes | Browser information
|
170
170
|
---
|
171
171
|
### Paging
|
172
|
-
|
172
|
+
Paging parameters.
|
173
|
+
|
174
|
+
```DeliveryRequest.retrieval_insertion_offset``` also impacts paging. That field indicate the offset of the retrieved insertions that are passed into ```Request.insertion```. See [detailed documentation on paging and ```retrieval_insertion_offset```](https://docs.promoted.ai/docs/ranking-requests#sending-even-more-request-insertions).
|
175
|
+
|
176
|
+
Field Name | Type | Optional? | Description
|
177
|
+
---------- | ---- | --------- | -----------
|
178
|
+
```:offset``` | Integer | Yes | The 0-based, starting index for the response page. This should be the global position.
|
179
|
+
```:size``` | Integer | Yes | The number of items to return in a response page.
|
173
180
|
---
|
174
181
|
### Request
|
175
182
|
A request for content insertions.
|
@@ -179,7 +186,7 @@ Field Name | Type | Optional? | Description
|
|
179
186
|
```:request_id``` | String | Yes | Generated by the SDK when needed (*do not set*)
|
180
187
|
```: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)).
|
181
188
|
```:properties``` | Properties | Yes | Any additional custom properties to associate.
|
182
|
-
```:paging``` | Paging | Yes | Paging parameters
|
189
|
+
```:paging``` | Paging | Yes | Paging parameters
|
183
190
|
```:device``` | Device | Yes | Device information (as available)
|
184
191
|
---
|
185
192
|
|
@@ -190,15 +197,12 @@ Field Name | Type | Optional? | Description
|
|
190
197
|
```:experiment``` | CohortMembership | Yes | A cohort to evaluation in experimentation.
|
191
198
|
```:request``` | Request | No | The underlying request for content.
|
192
199
|
```:only_log``` | Boolean | Yes | Defaults to false. Set to true to override whether Delivery API is called for this request.
|
200
|
+
```:retrieval_insertion_offset``` | Integer | Yes | The index of the retrieved insertions set on ```Request.insertion``` list. If just sending the top-N from your retrieval, this is ```0```. If this is the next batch (e.g. ```500``` to ```999```), then the value is ```500```. This can be used to send multiple groups of retrieved insertions. This interacts with the ```Paging``` fields. [More detailed documentation on paging](https://docs.promoted.ai/docs/ranking-requests#sending-even-more-request-insertions).
|
193
201
|
---
|
194
202
|
|
195
203
|
### LogRequest
|
196
204
|
|
197
|
-
|
198
|
-
Field Name | Type | Optional? | Description
|
199
|
-
---------- | ---- | --------- | -----------
|
200
|
-
```:request``` | Request | No | The underlying request for content to log.
|
201
|
-
```:insertion``` | [] of Insertion | No | The insertions, which are either the original request insertions or the insertions resulting from a call to ```deliver``` if such call occurred.
|
205
|
+
A log object that is sent as a RPC request to Promoted's Metrics API log endpoint. This is outputted from the ```deliver``` SDK call. Callers need to either input it into the ```send_log_request``` method or send it to the Metrics API directly. Clients should avoid manipulating this object directly.
|
202
206
|
---
|
203
207
|
|
204
208
|
### ClientResponse
|
@@ -214,18 +218,12 @@ Field Name | Type | Optional? | Description
|
|
214
218
|
### PromotedClient
|
215
219
|
Method | Input | Output | Description
|
216
220
|
------ | ----- | ------ | -----------
|
217
|
-
```prepare_for_logging``` | MetricsRequest | LogRequest | Builds a request suitable for logging locally and/or to Promoted, either via a subsequent call to ```send_log_request``` in the SDK client or by using this structure to make the call yourself. Optionally, based on client configuration may send a random subset of requests to Delivery API as shadow traffic for integration purposes.
|
218
221
|
```send_log_request``` | LogRequest | n/a | Forwards a LogRequest to Promoted using an HTTP client.
|
219
|
-
```deliver``` | DeliveryRequest | ClientResponse |
|
222
|
+
```deliver``` | DeliveryRequest | ClientResponse | Depending on flags, either (1) makes a request (subject to experimentation) to Delivery API for insertions, which are then returned along with a LogRequest or (2) implements SDK-side paging and prepares log records that can be logged to Promoted using ```send_log_request``` or by using this structure to make the call yourself. Optionally, based on client configuration may send a random subset of requests to Delivery API as shadow traffic for integration purposes.
|
220
223
|
```close``` | n/a | n/a | Closes down the client at shutdown, currently this is just to drain the thread pool that handles shadow traffic.
|
221
224
|
---
|
222
225
|
|
223
226
|
## Metrics API
|
224
|
-
### Pagination
|
225
|
-
|
226
|
-
The `prepare_for_logging` call assumes the client has already handled pagination. It needs a `Request.paging.offset` to be passed in for the number of items deep that the page is.
|
227
|
-
TODO: Needs more details.
|
228
|
-
|
229
227
|
### Expected flow for Metrics logging
|
230
228
|
|
231
229
|
```rb
|
@@ -270,7 +268,7 @@ insertions = products.map { |product|
|
|
270
268
|
# Form a MetricsRequest
|
271
269
|
metrics_request = {
|
272
270
|
:request => {
|
273
|
-
:user_info => { :user_id => "912", :
|
271
|
+
:user_info => { :user_id => "912", :anon_user_id => "912191"},
|
274
272
|
:use_case => "FEED",
|
275
273
|
:paging => {
|
276
274
|
:offset => 0,
|
@@ -306,7 +304,7 @@ client.send_log_request(client_response[:log_request]) if client_response[:log_r
|
|
306
304
|
# Form a DeliveryRequest
|
307
305
|
delivery_request = {
|
308
306
|
:request => {
|
309
|
-
:user_info => { :user_id => "912", :
|
307
|
+
:user_info => { :user_id => "912", :anon_user_id => "912191"},
|
310
308
|
:use_case => "FEED",
|
311
309
|
:paging => {
|
312
310
|
:offset => 0,
|
data/dev.md
CHANGED
@@ -10,5 +10,5 @@ bundle exec rspec
|
|
10
10
|
2. Get credentials for deployment from 1password.
|
11
11
|
3. Modify `promoted-ruby-client.gemspec`'s push block.
|
12
12
|
4. Run `gem build promoted-ruby-client.gemspec` to generate `gem`.
|
13
|
-
5. Run
|
14
|
-
6.
|
13
|
+
5. Run `bundle exec rspec`. This updates `Gemfile.lock`.
|
14
|
+
6. Run (using new output) `gem push promoted-ruby-client-4.0.0.gem`
|
@@ -14,9 +14,6 @@ module Promoted
|
|
14
14
|
'SELLER_CONTENT'=> 'SELLER_CONTENT',
|
15
15
|
'DISCOVER'=> 'DISCOVER'}
|
16
16
|
|
17
|
-
INSERTION_PAGING_TYPE = {'UNPAGED' => 'UNPAGED',
|
18
|
-
'PRE_PAGED' => 'PRE_PAGED'}
|
19
|
-
|
20
17
|
COHORT_ARM = {'UNKNOWN_GROUP' => 'UNKNOWN_GROUP',
|
21
18
|
'CONTROL' => 'CONTROL',
|
22
19
|
'TREATMENT' => 'TREATMENT',
|
@@ -1,18 +1,6 @@
|
|
1
1
|
module Promoted
|
2
2
|
module Ruby
|
3
3
|
module Client
|
4
|
-
class ShadowTrafficInsertionPageType < StandardError
|
5
|
-
def message
|
6
|
-
'Insertions must be unpaged when shadow traffic is on'
|
7
|
-
end
|
8
|
-
end
|
9
|
-
|
10
|
-
class DeliveryInsertionPageType < StandardError
|
11
|
-
def message
|
12
|
-
'Delivery insertions must be unpaged'
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
4
|
class EndpointError < StandardError
|
17
5
|
attr_reader :cause
|
18
6
|
def initialize(cause)
|
@@ -11,15 +11,21 @@ module Promoted
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class Pager
|
14
|
-
def validate_paging(insertions, paging)
|
15
|
-
if paging && paging[:offset]
|
16
|
-
|
14
|
+
def validate_paging(insertions, retrieval_insertion_offset, paging)
|
15
|
+
if paging && paging[:offset]
|
16
|
+
offset = [0, paging[:offset]].max
|
17
|
+
if offset >= insertions.length
|
18
|
+
raise InvalidPagingError.new("Invalid page offset (insertion size #{insertions.length}, offset #{offset})", [])
|
19
|
+
end
|
20
|
+
if offset < retrieval_insertion_offset
|
21
|
+
raise InvalidPagingError.new("Invalid page offset (retrieval_insertion_offset #{retrieval_insertion_offset}, offset #{offset})", [])
|
22
|
+
end
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
20
|
-
def apply_paging(insertions,
|
26
|
+
def apply_paging(insertions, retrieval_insertion_offset, paging = nil)
|
21
27
|
begin
|
22
|
-
validate_paging(insertions, paging)
|
28
|
+
validate_paging(insertions, retrieval_insertion_offset, paging)
|
23
29
|
rescue InvalidPagingError => err
|
24
30
|
# This is invalid input, stop it before it goes to the server.
|
25
31
|
return err.default_insertions_page
|
@@ -33,14 +39,8 @@ module Promoted
|
|
33
39
|
end
|
34
40
|
|
35
41
|
offset = [0, paging[:offset]].max
|
36
|
-
|
37
|
-
index = offset
|
38
|
-
if insertion_page_type == Promoted::Ruby::Client::INSERTION_PAGING_TYPE['PRE_PAGED']
|
39
|
-
# When insertions are pre-paged, we don't use offset to
|
40
|
-
# window into the provided insertions, although we do use it when
|
41
|
-
# assigning positions.
|
42
|
-
index = 0
|
43
|
-
end
|
42
|
+
retrieval_insertion_offset = [0, retrieval_insertion_offset].max
|
43
|
+
index = [0, offset - retrieval_insertion_offset].max
|
44
44
|
|
45
45
|
size = paging[:size]
|
46
46
|
if size <= 0
|
@@ -27,7 +27,7 @@ module Promoted
|
|
27
27
|
@view_id = request[:view_id]
|
28
28
|
@use_case = Promoted::Ruby::Client::USE_CASES[request[:use_case]] || Promoted::Ruby::Client::USE_CASES['UNKNOWN_USE_CASE']
|
29
29
|
@insertion = request[:insertion] || []
|
30
|
-
@user_info = request[:user_info] || { :user_id => nil, :
|
30
|
+
@user_info = request[:user_info] || { :user_id => nil, :anon_user_id => nil}
|
31
31
|
@timing = request[:timing] || { :client_log_timestamp => (Time.now.to_f * 1000).to_i }
|
32
32
|
|
33
33
|
# If the user didn't create a client request id, we do it for them.
|
data/lib/promoted/ruby/client.rb
CHANGED
@@ -54,6 +54,7 @@ module Promoted
|
|
54
54
|
|
55
55
|
@sampler = Sampler.new
|
56
56
|
@pager = Pager.new
|
57
|
+
@retrieval_insertion_offset = params[:retrieval_insertion_offset] || 0
|
57
58
|
|
58
59
|
# HTTP Client creation
|
59
60
|
@delivery_endpoint = params[:delivery_endpoint] || DEFAULT_DELIVERY_ENDPOINT
|
@@ -119,7 +120,7 @@ module Promoted
|
|
119
120
|
# Respect the enabled state
|
120
121
|
if !@enabled
|
121
122
|
return {
|
122
|
-
insertion: @pager.apply_paging(args[:request][:insertion],
|
123
|
+
insertion: @pager.apply_paging(args[:request][:insertion], @retrieval_insertion_offset, args[:request][:paging])
|
123
124
|
# No log request returned when disabled
|
124
125
|
}
|
125
126
|
end
|
@@ -134,16 +135,6 @@ module Promoted
|
|
134
135
|
# perform_checks raises errors.
|
135
136
|
if @perform_checks
|
136
137
|
perform_common_checks!(args)
|
137
|
-
if !only_log && args[:insertion_page_type] == Promoted::Ruby::Client::INSERTION_PAGING_TYPE['PRE_PAGED'] then
|
138
|
-
err = DeliveryInsertionPageType.new
|
139
|
-
@logger.error(err) if @logger
|
140
|
-
raise err
|
141
|
-
end
|
142
|
-
|
143
|
-
if should_send_shadow_traffic && args[:insertion_page_type] != Promoted::Ruby::Client::INSERTION_PAGING_TYPE['UNPAGED'] then
|
144
|
-
should_send_shadow_traffic = false
|
145
|
-
@logger.error(ShadowTrafficInsertionPageType.new) if @logger
|
146
|
-
end
|
147
138
|
end
|
148
139
|
|
149
140
|
delivery_request_builder.ensure_client_timestamp
|
@@ -161,7 +152,7 @@ module Promoted
|
|
161
152
|
end
|
162
153
|
|
163
154
|
begin
|
164
|
-
@pager.validate_paging(delivery_request_builder.insertion, delivery_request_builder.request[:paging])
|
155
|
+
@pager.validate_paging(delivery_request_builder.insertion, @retrieval_insertion_offset, delivery_request_builder.request[:paging])
|
165
156
|
rescue InvalidPagingError => err
|
166
157
|
# Invalid input, log and do SDK-side delivery.
|
167
158
|
@logger.warn(err) if @logger
|
@@ -234,7 +225,7 @@ module Promoted
|
|
234
225
|
end
|
235
226
|
|
236
227
|
##
|
237
|
-
# Sends a log request
|
228
|
+
# Sends a log request to the metrics endpoint.
|
238
229
|
def send_log_request log_request_params, headers={}
|
239
230
|
begin
|
240
231
|
send_request(log_request_params, @metrics_endpoint, @metrics_timeout_millis, @metrics_api_key, headers)
|
@@ -249,7 +240,7 @@ module Promoted
|
|
249
240
|
##
|
250
241
|
# Creates response insertions for SDK-side delivery, when we don't get response insertions from Delivery API.
|
251
242
|
def build_sdk_response_insertions delivery_request_builder
|
252
|
-
response_insertions = @pager.apply_paging(delivery_request_builder.insertion,
|
243
|
+
response_insertions = @pager.apply_paging(delivery_request_builder.insertion, @retrieval_insertion_offset, delivery_request_builder.request[:paging])
|
253
244
|
delivery_request_builder.add_missing_insertion_ids! response_insertions
|
254
245
|
return response_insertions
|
255
246
|
end
|
@@ -317,7 +308,7 @@ module Promoted
|
|
317
308
|
|
318
309
|
# Delivers shadow traffic from the given metrics args.
|
319
310
|
# Assumes that the args have already been normalized since this
|
320
|
-
# method should only be called from inside
|
311
|
+
# method should only be called from inside deliver.
|
321
312
|
def deliver_shadow_traffic args, headers
|
322
313
|
delivery_request_builder = RequestBuilder.new
|
323
314
|
delivery_request_builder.set_request_params args
|
@@ -326,7 +317,7 @@ module Promoted
|
|
326
317
|
delivery_request_params[:client_info][:traffic_type] = Promoted::Ruby::Client::TRAFFIC_TYPE['SHADOW']
|
327
318
|
|
328
319
|
begin
|
329
|
-
@pager.validate_paging(delivery_request_builder.insertion, delivery_request_builder.request[:paging])
|
320
|
+
@pager.validate_paging(delivery_request_builder.insertion, @retrieval_insertion_offset, delivery_request_builder.request[:paging])
|
330
321
|
rescue InvalidPagingError => err
|
331
322
|
# Invalid input, log and skip.
|
332
323
|
@logger.warn("Shadow traffic call failed with invalid paging #{err}") if @logger
|
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:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- scottmcmaster
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-09-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|