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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c6ac96f572587bcc605eb0bd622d0238e3fa2fe7ee55f479afdb913320a5e10
4
- data.tar.gz: d89fc05b6e9701034bfb020fda1a6779e1d3cce4352115d89a3eaaba258dee3a
3
+ metadata.gz: 121200a7e00beda3ecf9643712df3aab9794f95719f9f8d65903b9484bf9857d
4
+ data.tar.gz: 901e657b2391b76d53d1b50d743469033f50f3679c1919d48c5644621e8c7866
5
5
  SHA512:
6
- metadata.gz: 13e6c6245b1b4a277309ab8c18e1797ea324e8569292fdf0727af313f4266f770c19c84218c18cceea05c8ee44cd11926bafa8c50ec3e1f7bc9df77aafc68477
7
- data.tar.gz: f206b67e03cfe7221858606f270185ceb90c690b29b04eb17ca6385a1d935de38bad6a7c6fe605a35fd871a95f55c7259d2dcd3af554bcfee46db0d0cfccd18f
6
+ metadata.gz: 54fd3b49e89e049fe39c45afaa10171fc38cf24df7e402e46f922e22e0b12ed6f570f48b0f273b14a7c6a731b98c0d17493ddc4c4636b30582e8a115b0323b92
7
+ data.tar.gz: 195b628e077cbb154423aa8156fb16410582885f72ce292ea444c25abc1ca523656bdb65f5632088d593f4579a06f78cbfcd19a17da813095bdd144ad4b91ad9
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- promoted-ruby-client (0.1.3)
4
+ promoted-ruby-client (0.1.4)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
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
- This version of the library only supports preparing objects for logging. TODO - support Delivery API.
5
+ More information at [http://www.promoted.ai](http://www.promoted.ai)
6
6
 
7
- ## Expected pseudo-code flow for Metrics logging
7
+ ## Installation
8
+ ```gem 'promoted-ruby-client'```
8
9
 
9
- This example is for the integration where we do not want to modify the list of items to include `insertionId`. TODO - add this example too.
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
- # Done async
19
- def log_request items
20
- client = Promoted::Ruby::Client::PromotedClient.new
21
- log_request = client.prepare_for_logging(input)
22
- # Send JSON to Metrics API.
23
- log_to_promoted_event_api(log_request)
24
- end
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
- ## Naming details
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
- `full_insertion` - for `prepare_for_logging`, this is the current page of `Insertion`s with full `Insertion.properties` filled with the item details. For v1 integrations, it is fine to not fill in the full properties.
54
+ ## Data Types
30
55
 
31
- ## Pagination
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
- 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.
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
- ## Example to run the client
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
- Install our Ruby client.
38
- `promoted-ruby-client (0.1.3)`
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
- Or
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
- 1. Clone the repo on your local machine
43
- 2. `cd promoted-ruby-client`
44
- 3. `bundle`
45
- 4. `irb -Ilib -rpromoted/ruby/client`
127
+ ### LogRequest
46
128
 
47
- A console will launch with the library loaded. Here is example code to use.
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
- # Converts the Products to a list of Insertions.
72
- def to_insertions products
73
- @to_insertions = []
74
- products.each_with_index do |product, index|
75
- @to_insertions << {
76
- content_id: product[:id],
77
- properties: {
78
- struct: {
79
- product: product.reject { |k, v| [:id].include? k }
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
- end
84
- @to_insertions
85
- end
197
+ }
198
+ }
86
199
 
87
- request_input = {
88
- request: {
89
- user_info: { user_id: "912", log_user_id: "912191"},
90
- use_case: "FEED",
91
- paging: {
92
- offset: 10,
93
- size: 5
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: true
209
+ :properties => {
210
+ :struct => {
211
+ :active => true
98
212
  }
99
213
  }
100
214
  },
101
- full_insertion: to_insertions(products)
215
+ :full_insertion => insertions
102
216
  }
103
217
 
104
- client = Promoted::Ruby::Client::PromotedClient.new
105
- log_request = client.prepare_for_logging(request_input)
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
- request_input = {
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
- `log_request.to_json` returns a result that looks like the following
142
- ```
143
- => "{\"user_info\":{\"user_id\":\"912\",\"log_user_id\":\"912191\"},\"timing\":{\"client_log_timestamp\":1623306198},\"request\":[{\"user_info\":{\"user_id\":\"912\",\"log_user_id\":\"912191\"},\"use_case\":\"FEED\",\"paging\":{\"offset\":10,\"size\":10},\"properties\":{\"struct\":{\"active\":true}}}],\"insertion\":[{\"content_id\":\"123\",\"properties\":{\"struct\":{\"product\":{\"type\":\"SHOE\",\"name\":\"Blue shoe\",\"total_sales\":1000}}},\"user_info\":{\"user_id\":\"912\",\"log_user_id\":\"912191\"},\"timing\":{\"client_log_timestamp\":1623306198},\"insertion_id\":\"a87e1b57-a574-424f-8af6-10e0250aa7ab\",\"request_id\":\"54ff4884-2192-4180-8c72-a805a436980f\",\"position\":10},{\"content_id\":\"124\",\"properties\":{\"struct\":{\"product\":{\"type\":\"SHIRT\",\"name\":\"Green shirt\",\"total_sales\":800}}},\"user_info\":{\"user_id\":\"912\",\"log_user_id\":\"912191\"},\"timing\":{\"client_log_timestamp\":1623306198},\"insertion_id\":\"4495f72a-8101-4cb8-94ce-4db76839b8b6\",\"request_id\":\"54ff4884-2192-4180-8c72-a805a436980f\",\"position\":11},{\"content_id\":\"125\",\"properties\":{\"struct\":{\"product\":{\"type\":\"DRESS\",\"name\":\"Red dress\",\"total_sales\":1200}}},\"user_info\":{\"user_id\":\"912\",\"log_user_id\":\"912191\"},\"timing\":{\"client_log_timestamp\":1623306198},\"insertion_id\":\"d1e4f3f6-1783-4059-8fab-fdf2ba343cdf\",\"request_id\":\"54ff4884-2192-4180-8c72-a805a436980f\",\"position\":12}]}"
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
- ## Other input syntaxes
236
+ ## Delivery API
147
237
 
148
- The client should also work if Hash rocket too.
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
- input = {
172
- "request"=>{
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
- Or inlined full request.
186
- ```
187
- input = {
188
- "request"=>{
189
- "user_info"=>{"user_id"=> "912", "log_user_id"=> "912191"},
190
- "use_case"=>"FEED",
191
- "properties"=>{
192
- "struct"=>{
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
- "contentId"=>"125",
224
- "properties"=>{
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.3.gem`
8
+ 5. Run (using new output) `gem push promoted-ruby-client-0.1.4.gem`
9
9
  6. Update README with new version.
@@ -6,18 +6,22 @@ module Promoted
6
6
  module Ruby
7
7
  module Client
8
8
 
9
- DEFAULT_DELIVERY_TIMEOUT_MILLIS = 3000
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
- @default_request_headers['x-api-key'] = params[:api_key] || ''
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
- def send_request payload, endpoint, timeout_millis, headers=[], send_async=false
70
- use_headers = @default_request_headers.merge headers
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
- if response != nil && response[:insertion] != nil
116
- response_insertions = delivery_request_builder.fill_details_from_response(response[:insertion])
117
- insertions_from_promoted = true;
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
- raise ShadowTrafficInsertionPageType
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
@@ -1,7 +1,7 @@
1
1
  module Promoted
2
2
  module Ruby
3
3
  module Client
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.4"
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.3
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-29 00:00:00.000000000 Z
11
+ date: 2021-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler