promoted-ruby-client 0.1.3 → 0.1.4
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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +205 -169
- data/dev.md +1 -1
- data/lib/promoted/ruby/client.rb +59 -31
- data/lib/promoted/ruby/client/faraday_http_client.rb +1 -1
- data/lib/promoted/ruby/client/request_builder.rb +4 -0
- data/lib/promoted/ruby/client/version.rb +1 -1
- 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: 121200a7e00beda3ecf9643712df3aab9794f95719f9f8d65903b9484bf9857d
|
4
|
+
data.tar.gz: 901e657b2391b76d53d1b50d743469033f50f3679c1919d48c5644621e8c7866
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54fd3b49e89e049fe39c45afaa10171fc38cf24df7e402e46f922e22e0b12ed6f570f48b0f273b14a7c6a731b98c0d17493ddc4c4636b30582e8a115b0323b92
|
7
|
+
data.tar.gz: 195b628e077cbb154423aa8156fb16410582885f72ce292ea444c25abc1ca523656bdb65f5632088d593f4579a06f78cbfcd19a17da813095bdd144ad4b91ad9
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,51 +2,166 @@
|
|
2
2
|
|
3
3
|
Ruby client designed for calling Promoted's Delivery and Metrics API.
|
4
4
|
|
5
|
-
|
5
|
+
More information at [http://www.promoted.ai](http://www.promoted.ai)
|
6
6
|
|
7
|
-
##
|
7
|
+
## Installation
|
8
|
+
```gem 'promoted-ruby-client'```
|
8
9
|
|
9
|
-
|
10
|
+
## Local Development
|
11
|
+
1. Clone or fork the repo on your local machine
|
12
|
+
2. `cd promoted-ruby-client`
|
13
|
+
3. `bundle`
|
14
|
+
4. To test interactively: `irb -Ilib -rpromoted/ruby/client`
|
10
15
|
|
16
|
+
## Dependencies
|
17
|
+
|
18
|
+
### [Faraday](https://github.com/lostisland/faraday)
|
19
|
+
HTTP client for calling Promoted.
|
20
|
+
### [Concurrent Ruby](https://github.com/ruby-concurrency/concurrent-ruby)
|
21
|
+
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
|
+
## Creating a Client
|
23
|
+
```rb
|
24
|
+
client = Promoted::Ruby::Client::PromotedClient.new
|
11
25
|
```
|
12
|
-
def get_items args
|
13
|
-
items = retrieve_items((args))
|
14
|
-
async_log_request(items)
|
15
|
-
return items
|
16
|
-
end
|
17
26
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
27
|
+
This client will suffice for building log requests. To send actually send traffing to the API, some configuration is required.
|
28
|
+
|
29
|
+
```rb
|
30
|
+
client = Promoted::Ruby::Client::PromotedClient.new({
|
31
|
+
:metrics_endpoint = "https://<get this from Promoted>",
|
32
|
+
:delivery_endpoint = "https://<get this from Promoted>",
|
33
|
+
:metrics_api_key = "<get this from Promoted>",
|
34
|
+
:delivery_api_key = "<get this from Promoted>"
|
35
|
+
})
|
25
36
|
```
|
26
37
|
|
27
|
-
|
38
|
+
### Client Configuration Parameters
|
39
|
+
Name | Type | Description
|
40
|
+
---- | ---- | -----------
|
41
|
+
```:delivery_endpoint``` | String | POST URL for the Promoted Delivery API (get this from Promoted)
|
42
|
+
```:metrics_endpoint``` | String | POST URL for the Promoted Metrics API (get this from Promoted)
|
43
|
+
```:metrics_api_key``` | String | Used as the ```x-api-key``` header on Metrics API requests to Promoted (get this value from Promoted)
|
44
|
+
```:delivery_api_key``` | String | Used as the ```x-api-key``` header on Delivery API requests to Promoted (get this value from Promoted)
|
45
|
+
```:delivery_timeout_millis``` | Number | Timeout on the Delivery API call. Defaults to 3000.
|
46
|
+
```:metrics_timeout_millis``` | Number | Timeout on the Metrics API call. Defaults to 3000.
|
47
|
+
```:perform_checks``` | Boolean | Whether or not to perform detailed input validation, defaults to true but may be disabled for performance
|
48
|
+
```:logger``` | Ruby Logger-compatible logger | Defaults to nil (no logging). Example: ```Logger.new(STDERR, :progname => 'promotedai')```
|
49
|
+
```: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).
|
50
|
+
```:default_request_headers``` | Hash | Additional headers to send on the request beyond ```x-api-key```. Defaults to {}
|
51
|
+
```: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
|
+
```: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.
|
28
53
|
|
29
|
-
|
54
|
+
## Data Types
|
30
55
|
|
31
|
-
|
56
|
+
### UserInfo
|
57
|
+
Basic information about the request user.
|
58
|
+
Field Name | Type | Optional? | Description
|
59
|
+
---------- | ---- | --------- | -----------
|
60
|
+
```:user_id``` | String | Yes | The platform user id, cleared from Promoted logs.
|
61
|
+
```:log_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.
|
32
62
|
|
33
|
-
|
63
|
+
---
|
64
|
+
### CohortMembership
|
65
|
+
Useful fields for experimentation during the delivery phase.
|
66
|
+
Field Name | Type | Optional? | Description
|
67
|
+
---------- | ---- | --------- | -----------
|
68
|
+
```:user_info``` | UserInfo | Yes | The user info structure.
|
69
|
+
```:arm``` | String | Yes | 'CONTROL' or one of the TREATMENT values (see [constants.rb](https://github.com/promotedai/promoted-ruby-client/blob/main/lib/promoted/ruby/client/constants.rb)).
|
70
|
+
---
|
71
|
+
### Properties
|
72
|
+
Properties bag. Has the structure:
|
34
73
|
|
35
|
-
|
74
|
+
```rb
|
75
|
+
:struct => {
|
76
|
+
:product => {
|
77
|
+
"id": "product3",
|
78
|
+
"title": "Product 3",
|
79
|
+
"url": "www.mymarket.com/p/3"
|
80
|
+
# other key-value pairs...
|
81
|
+
}
|
82
|
+
}
|
83
|
+
```
|
84
|
+
---
|
85
|
+
### Insertion
|
86
|
+
Content being served at a certain position.
|
87
|
+
Field Name | Type | Optional? | Description
|
88
|
+
---------- | ---- | --------- | -----------
|
89
|
+
```:user_info``` | UserInfo | Yes | The user info structure.
|
90
|
+
```:insertion_id``` | String | Yes | Generated by the SDK (*do not set*)
|
91
|
+
```:request_id``` | String | Yes | Generated by the SDK (*do not set*)
|
92
|
+
```:content_id``` | String | No | Identifier for the content to be shown, must be set.
|
93
|
+
```:properties``` | Properties | Yes | Any additional custom properties to associate. For v1 integrations, it is fine not to fill in all the properties.
|
36
94
|
|
37
|
-
|
38
|
-
|
95
|
+
---
|
96
|
+
### Paging
|
97
|
+
#### TODO
|
98
|
+
---
|
99
|
+
### Request
|
100
|
+
A request for content insertions.
|
101
|
+
Field Name | Type | Optional? | Description
|
102
|
+
---------- | ---- | --------- | -----------
|
103
|
+
```:user_info``` | UserInfo | Yes | The user info structure.
|
104
|
+
```:request_id``` | String | Yes | Generated by the SDK (*do not set*)
|
105
|
+
```: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
|
+
```:properties``` | Properties | Yes | Any additional custom properties to associate.
|
107
|
+
```:paging``` | Paging | Yes | Paging parameters (see TODO)
|
108
|
+
---
|
109
|
+
### MetricsRequest
|
110
|
+
Input to ```prepare_for_logging```
|
111
|
+
Field Name | Type | Optional? | Description
|
112
|
+
---------- | ---- | --------- | -----------
|
113
|
+
```:request``` | Request | No | The underlying request for content.
|
114
|
+
```:full_insertion``` | [] of Insertion | No | The proposed list of insertions.
|
115
|
+
---
|
39
116
|
|
40
|
-
|
117
|
+
### DeliveryRequest
|
118
|
+
Input to ```deliver```
|
119
|
+
Field Name | Type | Optional? | Description
|
120
|
+
---------- | ---- | --------- | -----------
|
121
|
+
```:experiment``` | CohortMembership | Yes | A cohort to evaluation in experimentation.
|
122
|
+
```:request``` | Request | No | The underlying request for content.
|
123
|
+
```:full_insertion``` | [] of Insertion | No | The proposed list of insertions with all metadata, will be compacted before forwarding to Promoted.
|
124
|
+
```:only_log``` | Boolean | Yes | Defaults to false. Set to true to override whether Delivery API is called for this request.
|
125
|
+
---
|
41
126
|
|
42
|
-
|
43
|
-
2. `cd promoted-ruby-client`
|
44
|
-
3. `bundle`
|
45
|
-
4. `irb -Ilib -rpromoted/ruby/client`
|
127
|
+
### LogRequest
|
46
128
|
|
47
|
-
|
129
|
+
Output of ```prepare_for_logging``` as well as an ouput of an SDK call to ```deliver```, input to ```send_log_request``` to log to Promoted
|
130
|
+
Field Name | Type | Optional? | Description
|
131
|
+
---------- | ---- | --------- | -----------
|
132
|
+
```:request``` | Request | No | The underlying request for content to log.
|
133
|
+
```: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.
|
134
|
+
---
|
135
|
+
|
136
|
+
### ClientResponse
|
137
|
+
Output of ```deliver```, includes the insertions as well as a suitable ```LogRequest``` for forwarding to Metrics API.
|
138
|
+
Field Name | Type | Optional? | Description
|
139
|
+
---------- | ---- | --------- | -----------
|
140
|
+
```: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
|
+
```: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.
|
142
|
+
---
|
143
|
+
|
144
|
+
### PromotedClient
|
145
|
+
Method | Input | Output | Description
|
146
|
+
------ | ----- | ------ | -----------
|
147
|
+
```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.
|
148
|
+
```send_log_request``` | LogRequest | n/a | Forwards a LogRequest to Promoted using an HTTP client.
|
149
|
+
```deliver``` | DeliveryRequest | ClientResponse | Makes a request (subject to experimentation) to Delivery API for insertions, which are then returned along with a LogRequest.
|
150
|
+
```close``` | n/a | n/a | Closes down the client at shutdown, currently this is just to drain the thread pool that handles shadow traffic.
|
151
|
+
---
|
152
|
+
|
153
|
+
## Metrics API
|
154
|
+
### Pagination
|
155
|
+
|
156
|
+
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.
|
157
|
+
TODO: Needs more details.
|
158
|
+
|
159
|
+
### Expected flow for Metrics logging
|
160
|
+
|
161
|
+
```rb
|
162
|
+
# Retrieve a list of content (i.e. products)
|
163
|
+
# products = fetch_my_marketplace_products()
|
48
164
|
|
49
|
-
```
|
50
165
|
products = [
|
51
166
|
{
|
52
167
|
id: "123",
|
@@ -68,170 +183,91 @@ products = [
|
|
68
183
|
}
|
69
184
|
]
|
70
185
|
|
71
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
}
|
186
|
+
# Transform them into an [] of Insertions.
|
187
|
+
insertions = products.map { |product|
|
188
|
+
{
|
189
|
+
:content_id => product[:id],
|
190
|
+
:properties => {
|
191
|
+
:struct => {
|
192
|
+
:type => product[:type],
|
193
|
+
:name => product[:name]
|
194
|
+
# etc
|
81
195
|
}
|
82
196
|
}
|
83
|
-
|
84
|
-
|
85
|
-
end
|
197
|
+
}
|
198
|
+
}
|
86
199
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
200
|
+
# Form a MetricsRequest
|
201
|
+
metrics_request = {
|
202
|
+
:request => {
|
203
|
+
:user_info => { :user_id => "912", :log_user_id => "912191"},
|
204
|
+
:use_case => "FEED",
|
205
|
+
:paging => {
|
206
|
+
:offset => 0,
|
207
|
+
:size => 5
|
94
208
|
},
|
95
|
-
properties
|
96
|
-
struct
|
97
|
-
active
|
209
|
+
:properties => {
|
210
|
+
:struct => {
|
211
|
+
:active => true
|
98
212
|
}
|
99
213
|
}
|
100
214
|
},
|
101
|
-
full_insertion
|
215
|
+
:full_insertion => insertions
|
102
216
|
}
|
103
217
|
|
104
|
-
|
105
|
-
|
106
|
-
log_request.to_json
|
107
|
-
```
|
108
|
-
|
109
|
-
## Pass a custom function for compact insertion
|
110
|
-
```
|
111
|
-
# define a function using proc and it should use one argument.
|
112
|
-
# Example
|
218
|
+
# 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.
|
113
220
|
to_compact_metrics_insertion_func = Proc.new do |insertion|
|
114
221
|
insertion.delete(:properties)
|
115
222
|
insertion
|
116
223
|
end
|
224
|
+
# metrics_request[:to_compact_metrics_insertion_func] = to_compact_metrics_insertion
|
117
225
|
|
118
|
-
|
119
|
-
to_compact_metrics_insertion_func: to_compact_metrics_insertion_func,
|
120
|
-
request: {
|
121
|
-
user_info: { user_id: "912", log_user_id: "912191"},
|
122
|
-
use_case: "FEED",
|
123
|
-
paging: {
|
124
|
-
from: 10,
|
125
|
-
size: 5
|
126
|
-
},
|
127
|
-
properties: {
|
128
|
-
struct: {
|
129
|
-
active: true
|
130
|
-
}
|
131
|
-
}
|
132
|
-
},
|
133
|
-
full_insertion: to_insertions(products)
|
134
|
-
}
|
135
|
-
|
226
|
+
# Create a client
|
136
227
|
client = Promoted::Ruby::Client::PromotedClient.new
|
137
|
-
log_request = client.prepare_for_logging(request_input)
|
138
|
-
log_request.to_json
|
139
|
-
```
|
140
228
|
|
141
|
-
|
142
|
-
|
143
|
-
|
229
|
+
# Build a log request
|
230
|
+
log_request = client.prepare_for_logging(metrics_request)
|
231
|
+
|
232
|
+
# Log (assuming you have configured your client with a :metrics_endpoint)
|
233
|
+
client.send_log_request(log_request)
|
144
234
|
```
|
145
235
|
|
146
|
-
##
|
236
|
+
## Delivery API
|
147
237
|
|
148
|
-
|
149
|
-
```
|
150
|
-
products = [
|
151
|
-
{
|
152
|
-
"id"=>"123",
|
153
|
-
"type"=>"SHOE",
|
154
|
-
"name"=>"Blue shoe",
|
155
|
-
"totalSales"=>1000
|
156
|
-
},
|
157
|
-
{
|
158
|
-
"id"=>"124",
|
159
|
-
"type"=>"SHIRT",
|
160
|
-
"name"=>"Green shirt",
|
161
|
-
"totalSales"=>800
|
162
|
-
},
|
163
|
-
{
|
164
|
-
"id"=>"125",
|
165
|
-
"type"=>"DRESS",
|
166
|
-
"name"=>"Red dress",
|
167
|
-
"totalSales"=>1200
|
168
|
-
}
|
169
|
-
]
|
238
|
+
### Expected flow for Delivery
|
170
239
|
|
171
|
-
|
172
|
-
|
173
|
-
"user_info"=>{"user_id"=> "912", "log_user_id"=> "912191"},
|
174
|
-
"use_case"=>"FEED",
|
175
|
-
"properties"=>{
|
176
|
-
"struct"=>{
|
177
|
-
"active"=>true
|
178
|
-
}
|
179
|
-
}
|
180
|
-
},
|
181
|
-
"full_insertion"=>to_insertions(products)
|
182
|
-
}
|
183
|
-
```
|
240
|
+
```rb
|
241
|
+
# (continuing from the above example for Metrics)
|
184
242
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
"active"=>true
|
194
|
-
}
|
195
|
-
}
|
196
|
-
},
|
197
|
-
"full_insertion"=>[
|
198
|
-
{
|
199
|
-
"contentId"=>"123",
|
200
|
-
"properties"=>{
|
201
|
-
"struct"=>{
|
202
|
-
"product"=>{
|
203
|
-
"type"=>"SHOE",
|
204
|
-
"name"=>"Blue shoe",
|
205
|
-
"totalSales"=>1000
|
206
|
-
}
|
207
|
-
}
|
208
|
-
}
|
209
|
-
},
|
210
|
-
{
|
211
|
-
"contentId"=>"124",
|
212
|
-
"properties"=>{
|
213
|
-
"struct"=>{
|
214
|
-
"product"=>{
|
215
|
-
"type"=>"SHIRT",
|
216
|
-
"name"=>"Green shirt",
|
217
|
-
"totalSales"=>800
|
218
|
-
}
|
219
|
-
}
|
220
|
-
}
|
243
|
+
# Form a DeliveryRequest
|
244
|
+
delivery_request = {
|
245
|
+
:request => {
|
246
|
+
:user_info => { :user_id => "912", :log_user_id => "912191"},
|
247
|
+
:use_case => "FEED",
|
248
|
+
:paging => {
|
249
|
+
:offset => 0,
|
250
|
+
:size => 5
|
221
251
|
},
|
222
|
-
{
|
223
|
-
|
224
|
-
|
225
|
-
"struct"=>{
|
226
|
-
"product"=>{
|
227
|
-
"type"=>"DRESS",
|
228
|
-
"name"=>"Red dress",
|
229
|
-
"totalSales"=>1200
|
230
|
-
}
|
231
|
-
}
|
252
|
+
:properties => {
|
253
|
+
:struct => {
|
254
|
+
:active => true
|
232
255
|
}
|
233
256
|
}
|
234
|
-
|
257
|
+
},
|
258
|
+
:full_insertion => insertions,
|
259
|
+
:only_log => false
|
235
260
|
}
|
261
|
+
|
262
|
+
# Request insertions from Delivery API
|
263
|
+
client_response = client.deliver(delivery_request)
|
264
|
+
|
265
|
+
# Use the resulting insertions
|
266
|
+
client_response[:insertion]
|
267
|
+
|
268
|
+
# Log if a log request was provided (if not, deliver was called successfully
|
269
|
+
# and Promoted logged on the server-side).)
|
270
|
+
client.send_log_request(client_response[:log_request]) if client_response[:log_request]
|
236
271
|
```
|
237
272
|
|
273
|
+
### TODO Experimentation example
|
data/dev.md
CHANGED
@@ -5,5 +5,5 @@
|
|
5
5
|
2. Get credentials for deployment from 1password.
|
6
6
|
3. Modify `promoted-ruby-client.gemspec`'s push block.
|
7
7
|
4. Run `gem build promoted-ruby-client.gemspec` to generate `gem`.
|
8
|
-
5. Run (using new output) `gem push promoted-ruby-client-0.1.
|
8
|
+
5. Run (using new output) `gem push promoted-ruby-client-0.1.4.gem`
|
9
9
|
6. Update README with new version.
|
data/lib/promoted/ruby/client.rb
CHANGED
@@ -6,18 +6,22 @@ module Promoted
|
|
6
6
|
module Ruby
|
7
7
|
module Client
|
8
8
|
|
9
|
-
DEFAULT_DELIVERY_TIMEOUT_MILLIS =
|
9
|
+
DEFAULT_DELIVERY_TIMEOUT_MILLIS = 250
|
10
10
|
DEFAULT_METRICS_TIMEOUT_MILLIS = 3000
|
11
11
|
DEFAULT_DELIVERY_ENDPOINT = "http://delivery.example.com"
|
12
12
|
DEFAULT_METRICS_ENDPOINT = "http://metrics.example.com"
|
13
13
|
|
14
|
+
##
|
15
|
+
# Client for working with Promoted's Metrics and Delivery APIs.
|
16
|
+
# See {Github}[https://github.com/promotedai/promoted-ruby-client] for more info.
|
14
17
|
class PromotedClient
|
15
18
|
|
16
19
|
class Error < StandardError; end
|
17
20
|
|
18
21
|
attr_reader :perform_checks, :default_only_log, :delivery_timeout_millis, :metrics_timeout_millis, :should_apply_treatment_func,
|
19
|
-
:default_request_headers
|
22
|
+
:default_request_headers, :http_client
|
20
23
|
|
24
|
+
##
|
21
25
|
# A common compact method implementation.
|
22
26
|
def self.copy_and_remove_properties
|
23
27
|
Proc.new do |insertion|
|
@@ -27,6 +31,8 @@ module Promoted
|
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
34
|
+
##
|
35
|
+
# Create and configure a new Promoted client.
|
30
36
|
def initialize (params={})
|
31
37
|
@perform_checks = true
|
32
38
|
if params[:perform_checks] != nil
|
@@ -36,7 +42,8 @@ module Promoted
|
|
36
42
|
@logger = params[:logger] # Example: Logger.new(STDERR, :progname => "promotedai")
|
37
43
|
|
38
44
|
@default_request_headers = params[:default_request_headers] || {}
|
39
|
-
@
|
45
|
+
@metrics_api_key = params[:metrics_api_key] || ''
|
46
|
+
@delivery_api_key = params[:delivery_api_key] || ''
|
40
47
|
|
41
48
|
@default_only_log = params[:default_only_log] || false
|
42
49
|
@should_apply_treatment_func = params[:should_apply_treatment_func]
|
@@ -57,31 +64,27 @@ module Promoted
|
|
57
64
|
@metrics_timeout_millis = params[:metrics_timeout_millis] || DEFAULT_METRICS_TIMEOUT_MILLIS
|
58
65
|
|
59
66
|
@http_client = FaradayHTTPClient.new
|
60
|
-
@pool = Concurrent::CachedThreadPool.new
|
61
67
|
@validator = Promoted::Ruby::Client::Validator.new
|
68
|
+
|
69
|
+
# Thread pool to process delivery of shadow traffic. Will silently drop excess requests beyond the queue
|
70
|
+
# size, and silently eat errors on the background threads.
|
71
|
+
@pool = Concurrent::ThreadPoolExecutor.new(
|
72
|
+
min_threads: 0,
|
73
|
+
max_threads: 10,
|
74
|
+
max_queue: 100,
|
75
|
+
fallback_policy: :discard
|
76
|
+
)
|
62
77
|
end
|
63
78
|
|
79
|
+
##
|
80
|
+
# Politely shut down a Promoted client.
|
64
81
|
def close
|
65
82
|
@pool.shutdown
|
66
83
|
@pool.wait_for_termination
|
67
84
|
end
|
68
85
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
if send_async
|
73
|
-
@pool.post do
|
74
|
-
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
75
|
-
end
|
76
|
-
else
|
77
|
-
begin
|
78
|
-
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
79
|
-
rescue Faraday::Error => err
|
80
|
-
raise EndpointError.new(err)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
86
|
+
##
|
87
|
+
# Make a delivery request.
|
85
88
|
def deliver args, headers={}
|
86
89
|
args = Promoted::Ruby::Client::Util.translate_args(args)
|
87
90
|
|
@@ -97,6 +100,7 @@ module Promoted
|
|
97
100
|
insertions_from_promoted = false
|
98
101
|
|
99
102
|
only_log = delivery_request_builder.only_log != nil ? delivery_request_builder.only_log : @default_only_log
|
103
|
+
deliver_err = false
|
100
104
|
if !only_log
|
101
105
|
cohort_membership_to_log = delivery_request_builder.new_cohort_membership_to_log
|
102
106
|
|
@@ -105,22 +109,22 @@ module Promoted
|
|
105
109
|
|
106
110
|
# Call Delivery API
|
107
111
|
begin
|
108
|
-
response = send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, headers)
|
112
|
+
response = send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, @delivery_api_key, headers)
|
109
113
|
rescue StandardError => err
|
110
114
|
# Currently we don't propagate errors to the SDK caller, but rather default to returning
|
111
115
|
# the request insertions.
|
116
|
+
deliver_err = true
|
112
117
|
@logger.error("Error calling delivery: " + err.message) if @logger
|
113
118
|
end
|
114
119
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
end
|
120
|
+
insertions_from_promoted = (response != nil && !deliver_err);
|
121
|
+
response_insertions = delivery_request_builder.fill_details_from_response(
|
122
|
+
response ? response[:insertion] : nil)
|
119
123
|
end
|
120
124
|
end
|
121
125
|
|
122
126
|
request_to_log = nil
|
123
|
-
if !insertions_from_promoted
|
127
|
+
if !insertions_from_promoted then
|
124
128
|
request_to_log = delivery_request_builder.request
|
125
129
|
size = delivery_request_builder.request.dig(:paging, :size)
|
126
130
|
response_insertions = size != nil ? delivery_request_builder.full_insertion[0..size] : delivery_request_builder.full_insertion
|
@@ -163,6 +167,9 @@ module Promoted
|
|
163
167
|
return client_response
|
164
168
|
end
|
165
169
|
|
170
|
+
##
|
171
|
+
# Generate a log request for a subsequent call to send_log_request
|
172
|
+
# or for logging via alternative means.
|
166
173
|
def prepare_for_logging args, headers={}
|
167
174
|
args = Promoted::Ruby::Client::Util.translate_args(args)
|
168
175
|
|
@@ -171,27 +178,30 @@ module Promoted
|
|
171
178
|
# Note: This method expects as JSON (string keys) but internally, RequestBuilder
|
172
179
|
# transforms and works with symbol keys.
|
173
180
|
log_request_builder.set_request_params(args)
|
181
|
+
shadow_traffic_err = false
|
174
182
|
if @perform_checks
|
175
183
|
perform_common_checks! args
|
176
184
|
|
177
185
|
if @shadow_traffic_delivery_percent > 0 && args[:insertion_page_type] != Promoted::Ruby::Client::INSERTION_PAGING_TYPE['UNPAGED'] then
|
178
|
-
|
186
|
+
shadow_traffic_err = true
|
187
|
+
@logger.error(ShadowTrafficInsertionPageType.new) if @logger
|
179
188
|
end
|
180
189
|
end
|
181
190
|
|
182
191
|
pre_delivery_fillin_fields log_request_builder
|
183
192
|
|
184
|
-
if should_send_as_shadow_traffic?
|
193
|
+
if !shadow_traffic_err && should_send_as_shadow_traffic?
|
185
194
|
deliver_shadow_traffic args, headers
|
186
195
|
end
|
187
196
|
|
188
197
|
log_request_builder.log_request_params
|
189
198
|
end
|
190
199
|
|
200
|
+
##
|
191
201
|
# Sends a log request (previously created by a call to prepare_for_logging) to the metrics endpoint.
|
192
202
|
def send_log_request log_request_params, headers={}
|
193
203
|
begin
|
194
|
-
send_request(log_request_params, @metrics_endpoint, @metrics_timeout_millis, headers)
|
204
|
+
send_request(log_request_params, @metrics_endpoint, @metrics_timeout_millis, @metrics_api_key, headers)
|
195
205
|
rescue StandardError => err
|
196
206
|
# Currently we don't propagate errors to the SDK caller.
|
197
207
|
@logger.error("Error from metrics: " + err.message) if @logger
|
@@ -200,6 +210,24 @@ module Promoted
|
|
200
210
|
|
201
211
|
private
|
202
212
|
|
213
|
+
def send_request payload, endpoint, timeout_millis, api_key, headers={}, send_async=false
|
214
|
+
headers["x-api-key"] = api_key
|
215
|
+
use_headers = @default_request_headers.merge headers
|
216
|
+
|
217
|
+
if send_async
|
218
|
+
@pool.post do
|
219
|
+
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
220
|
+
end
|
221
|
+
else
|
222
|
+
begin
|
223
|
+
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
224
|
+
rescue Faraday::Error => err
|
225
|
+
raise EndpointError.new(err)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
|
203
231
|
def add_missing_ids_on_insertions! request, insertions
|
204
232
|
insertions.each do |insertion|
|
205
233
|
insertion[:insertion_id] = SecureRandom.uuid if not insertion[:insertion_id]
|
@@ -223,7 +251,7 @@ module Promoted
|
|
223
251
|
delivery_request_params[:client_info][:traffic_type] = Promoted::Ruby::Client::TRAFFIC_TYPE['SHADOW']
|
224
252
|
|
225
253
|
# Call Delivery API async (fire and forget)
|
226
|
-
send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, headers, true)
|
254
|
+
send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, @delivery_api_key, headers, true)
|
227
255
|
end
|
228
256
|
|
229
257
|
def perform_common_checks!(req)
|
@@ -238,7 +266,7 @@ module Promoted
|
|
238
266
|
|
239
267
|
def should_apply_treatment(cohort_membership)
|
240
268
|
if @should_apply_treatment_func != nil
|
241
|
-
@should_apply_treatment_func
|
269
|
+
@should_apply_treatment_func.call(cohort_membership)
|
242
270
|
else
|
243
271
|
return true if !cohort_membership
|
244
272
|
return true if !cohort_membership[:arm]
|
@@ -19,7 +19,7 @@ module Promoted
|
|
19
19
|
response = @conn.post(endpoint) do |req|
|
20
20
|
req.headers = req.headers.merge(additional_headers) if additional_headers
|
21
21
|
req.headers['Content-Type'] = req.headers['Content-Type'] || 'application/json'
|
22
|
-
req.options.timeout = timeout_millis / 1000
|
22
|
+
req.options.timeout = timeout_millis.to_f / 1000
|
23
23
|
req.body = request.to_json
|
24
24
|
end
|
25
25
|
|
@@ -61,6 +61,10 @@ module Promoted
|
|
61
61
|
# Maps the response insertions to the full insertions and re-insert the properties bag
|
62
62
|
# to the responses.
|
63
63
|
def fill_details_from_response response_insertions
|
64
|
+
if !response_insertions then
|
65
|
+
response_insertions = full_insertion
|
66
|
+
end
|
67
|
+
|
64
68
|
props = @full_insertion.each_with_object({}) do |insertion, hash|
|
65
69
|
hash[insertion[:content_id]] = insertion[:properties]
|
66
70
|
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.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- scottmcmaster
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-06-
|
11
|
+
date: 2021-06-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|