promoted-ruby-client 0.1.19 → 0.1.23
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 +13 -2
- data/dev.md +1 -1
- data/lib/promoted/ruby/client/pager.rb +4 -4
- data/lib/promoted/ruby/client/request_builder.rb +30 -9
- data/lib/promoted/ruby/client/validator.rb +17 -2
- data/lib/promoted/ruby/client/version.rb +1 -1
- data/lib/promoted/ruby/client.rb +28 -12
- data/promoted-ruby-client.gemspec +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 280c1b17bece86732743246173338746e1ab9d414b9515bc9fc77c8c598ae319
|
4
|
+
data.tar.gz: 1fed11fd16de69732e6f61d1d1e33b31c812151413a34d881b8dd7aace712daa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 997ca8c047a62a08b2f5e008184719c3e3f1f1dac45cfbccf41c4f451b8fad3f9a8aab7737e148a6281b4a9600e8c2ff6b8bfe9a269aba417ab7bf21f735fcd3
|
7
|
+
data.tar.gz: 274d3ce469128a6fa7da5fc127b65e9a3e1cc2327b322af5719182f06a37ada713806458c754d2efed6d6a21ad7ab945890541d5e3d1247844b66fa6b3a77a16
|
data/Gemfile.lock
CHANGED
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.
|
@@ -62,6 +63,7 @@ Field Name | Type | Optional? | Description
|
|
62
63
|
---------- | ---- | --------- | -----------
|
63
64
|
```:user_id``` | String | Yes | The platform user id, cleared from Promoted logs.
|
64
65
|
```: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.
|
66
|
+
```:is_internal_user``` | Boolean | Yes | If this user is a test user or not, defaults to false.
|
65
67
|
|
66
68
|
---
|
67
69
|
### CohortMembership
|
@@ -93,6 +95,8 @@ Field Name | Type | Optional? | Description
|
|
93
95
|
```:insertion_id``` | String | Yes | Generated by the SDK (*do not set*)
|
94
96
|
```:request_id``` | String | Yes | Generated by the SDK when needed (*do not set*)
|
95
97
|
```:content_id``` | String | No | Identifier for the content to be shown, must be set.
|
98
|
+
```:retrieval_rank``` | Number | Yes | Optional original ranking of this content item.
|
99
|
+
```:retrieval_score``` | Number | Yes | Optional original quality score of this content item.
|
96
100
|
```:properties``` | Properties | Yes | Any additional custom properties to associate. For v1 integrations, it is fine not to fill in all the properties.
|
97
101
|
---
|
98
102
|
### Size
|
@@ -103,6 +107,14 @@ Field Name | Type | Optional? | Description
|
|
103
107
|
```:height``` | Integer | No | Screen height
|
104
108
|
---
|
105
109
|
|
110
|
+
### Screen
|
111
|
+
State of the screen including scaling.
|
112
|
+
Field Name | Type | Optional? | Description
|
113
|
+
---------- | ---- | --------- | -----------
|
114
|
+
```:size``` | Size | Yes | Screen size
|
115
|
+
```:scale``` | Float | Yes | Current screen scaling factor
|
116
|
+
---
|
117
|
+
|
106
118
|
### ClientHints
|
107
119
|
Alternative to user-agent strings. See https://raw.githubusercontent.com/snowplow/iglu-central/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0
|
108
120
|
Field Name | Type | Optional? | Description
|
@@ -131,7 +143,6 @@ Field Name | Type | Optional? | Description
|
|
131
143
|
```:latitude``` | Float | No | Location latitude
|
132
144
|
```:longitude``` | Float | No | Location longitude
|
133
145
|
```:accuracy_in_meters``` | Integer | Yes | Location accuracy if available
|
134
|
-
```:client_hints``` | ClientHints | Yes | HTTP client hints structure
|
135
146
|
---
|
136
147
|
|
137
148
|
### Browser
|
@@ -140,7 +151,7 @@ Field Name | Type | Optional? | Description
|
|
140
151
|
---------- | ---- | --------- | -----------
|
141
152
|
```:user_agent``` | String | Yes | Browser user agent string
|
142
153
|
```:viewport_size``` | Size | Yes | Size of the browser viewport
|
143
|
-
```:
|
154
|
+
```:client_hints``` | ClientHints | Yes | HTTP client hints structure
|
144
155
|
---
|
145
156
|
### Device
|
146
157
|
Information about the user's device.
|
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.
|
7
|
+
5. Run (using new output) `gem push promoted-ruby-client-0.1.23.gem`
|
8
8
|
6. Update README with new version.
|
@@ -11,13 +11,13 @@ module Promoted
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class Pager
|
14
|
-
def validate_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
|
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
|
-
|
66
|
+
end
|
67
67
|
end
|
@@ -57,7 +57,7 @@ module Promoted
|
|
57
57
|
params = {
|
58
58
|
user_info: user_info,
|
59
59
|
timing: timing,
|
60
|
-
client_info:
|
60
|
+
client_info: merge_client_info_defaults,
|
61
61
|
device: @device,
|
62
62
|
platform_id: @platform_id,
|
63
63
|
view_id: @view_id,
|
@@ -78,7 +78,7 @@ module Promoted
|
|
78
78
|
# to the responses.
|
79
79
|
def fill_details_from_response response_insertions
|
80
80
|
if !response_insertions then
|
81
|
-
response_insertions =
|
81
|
+
response_insertions = []
|
82
82
|
end
|
83
83
|
|
84
84
|
props = @full_insertion.each_with_object({}) do |insertion, hash|
|
@@ -104,8 +104,13 @@ module Promoted
|
|
104
104
|
params = {
|
105
105
|
user_info: user_info,
|
106
106
|
timing: timing,
|
107
|
-
client_info:
|
108
|
-
device: @device
|
107
|
+
client_info: merge_client_info_defaults,
|
108
|
+
device: @device,
|
109
|
+
delivery_log: [{
|
110
|
+
execution: {
|
111
|
+
execution_server: Promoted::Ruby::Client::EXECUTION_SERVER['SDK']
|
112
|
+
}
|
113
|
+
}]
|
109
114
|
}
|
110
115
|
|
111
116
|
if @experiment
|
@@ -115,12 +120,16 @@ module Promoted
|
|
115
120
|
# Log request allows for multiple requests but here we only send one.
|
116
121
|
if include_request
|
117
122
|
request[:request_id] = request[:request_id] || @id_generator.newID
|
118
|
-
|
123
|
+
# Set request on delivery log.
|
124
|
+
params[:delivery_log][0][:request] = request
|
119
125
|
end
|
120
126
|
|
121
127
|
if include_insertions
|
122
|
-
|
123
|
-
|
128
|
+
# Add a response containing compacted insertions to delivery log.
|
129
|
+
params[:delivery_log][0][:response] = {
|
130
|
+
insertion: compact_metrics_properties
|
131
|
+
}
|
132
|
+
add_missing_ids_on_insertions! request, params[:delivery_log][0][:response][:insertion]
|
124
133
|
end
|
125
134
|
|
126
135
|
params.clean!
|
@@ -178,7 +187,6 @@ module Promoted
|
|
178
187
|
insertion_obj = Hash[insertion_obj]
|
179
188
|
insertion_obj[:user_info] = user_info
|
180
189
|
insertion_obj[:timing] = timing
|
181
|
-
insertion_obj[:insertion_id] = @id_generator.newID
|
182
190
|
insertion_obj[:request_id] = request_id
|
183
191
|
insertion_obj[:position] = offset + index
|
184
192
|
insertion_obj = compact_one_insertion(insertion_obj, @to_compact_metrics_properties_func)
|
@@ -187,18 +195,31 @@ module Promoted
|
|
187
195
|
@insertion
|
188
196
|
end
|
189
197
|
|
198
|
+
def add_missing_insertion_ids! insertions
|
199
|
+
insertions.each do |insertion|
|
200
|
+
insertion[:insertion_id] = @id_generator.newID if not insertion[:insertion_id]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
190
204
|
def client_request_id
|
191
205
|
request[:client_request_id]
|
192
206
|
end
|
193
207
|
|
194
208
|
private
|
195
209
|
|
210
|
+
def merge_client_info_defaults
|
211
|
+
return @client_info.merge({
|
212
|
+
:client_type => Promoted::Ruby::Client::CLIENT_TYPE['PLATFORM_SERVER'],
|
213
|
+
:traffic_type => Promoted::Ruby::Client::TRAFFIC_TYPE['PRODUCTION']
|
214
|
+
})
|
215
|
+
end
|
216
|
+
|
196
217
|
def add_missing_ids_on_insertions! request, insertions
|
197
218
|
insertions.each do |insertion|
|
198
|
-
insertion[:insertion_id] = @id_generator.newID if not insertion[:insertion_id]
|
199
219
|
insertion[:session_id] = request[:session_id] if request[:session_id]
|
200
220
|
insertion[:request_id] = request[:request_id] if request[:request_id]
|
201
221
|
end
|
222
|
+
add_missing_insertion_ids! insertions
|
202
223
|
end
|
203
224
|
|
204
225
|
# A list of the response Insertions. This client expects lists to be truncated
|
@@ -14,6 +14,10 @@ module Promoted
|
|
14
14
|
{
|
15
15
|
:name => :log_user_id,
|
16
16
|
:type => String
|
17
|
+
},
|
18
|
+
{
|
19
|
+
:name => :is_internal_user,
|
20
|
+
:type => [TrueClass, FalseClass]
|
17
21
|
}
|
18
22
|
]
|
19
23
|
)
|
@@ -55,6 +59,14 @@ module Promoted
|
|
55
59
|
{
|
56
60
|
:name => :delivery_score,
|
57
61
|
:type => Integer
|
62
|
+
},
|
63
|
+
{
|
64
|
+
:name => :retrieval_rank,
|
65
|
+
:type => Integer
|
66
|
+
},
|
67
|
+
{
|
68
|
+
:name => :retrieval_score,
|
69
|
+
:type => Float
|
58
70
|
}
|
59
71
|
]
|
60
72
|
)
|
@@ -133,7 +145,6 @@ module Promoted
|
|
133
145
|
req[:full_insertion].each do |insertion_hash|
|
134
146
|
raise ValidationError.new("Insertion.requestId should not be set") if insertion_hash[:request_id]
|
135
147
|
raise ValidationError.new("'Insertion.insertionId should not be set") if insertion_hash[:insertion_id]
|
136
|
-
raise ValidationError.new("Insertion.deliveryScore should not be set") if insertion_hash[:delivery_score]
|
137
148
|
end
|
138
149
|
end
|
139
150
|
|
@@ -147,7 +158,11 @@ module Promoted
|
|
147
158
|
|
148
159
|
# If a field is provided as non-nil, it should be of the correct type.
|
149
160
|
if field[:type] && obj.has_key?(field[:name]) && obj[field[:name]] != nil then
|
150
|
-
|
161
|
+
if field[:type].is_a?(Array) then
|
162
|
+
raise ValidationError.new(field[:name].to_s + " should be one of " + field[:type].to_s) if !field[:type].include?(obj[field[:name]].class)
|
163
|
+
else
|
164
|
+
raise ValidationError.new(field[:name].to_s + " should be a " + field[:type].to_s) if !obj[field[:name]].is_a?(field[:type])
|
165
|
+
end
|
151
166
|
end
|
152
167
|
}
|
153
168
|
end
|
data/lib/promoted/ruby/client.rb
CHANGED
@@ -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
|
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
|
-
|
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,17 +177,20 @@ module Promoted
|
|
172
177
|
deliver_err = true
|
173
178
|
@logger.error("Error calling delivery: " + err.message) if @logger
|
174
179
|
end
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
183
191
|
if !insertions_from_delivery then
|
184
192
|
request_to_log = delivery_request_builder.request
|
185
|
-
response_insertions =
|
193
|
+
response_insertions = build_sdk_response_insertions(delivery_request_builder)
|
186
194
|
end
|
187
195
|
|
188
196
|
log_req = nil
|
@@ -266,6 +274,14 @@ module Promoted
|
|
266
274
|
|
267
275
|
private
|
268
276
|
|
277
|
+
##
|
278
|
+
# Creates response insertions for SDK-side delivery, when we don't get response insertions from Delivery API.
|
279
|
+
def build_sdk_response_insertions delivery_request_builder
|
280
|
+
response_insertions = @pager.apply_paging(delivery_request_builder.full_insertion, Promoted::Ruby::Client::INSERTION_PAGING_TYPE['UNPAGED'], delivery_request_builder.request[:paging])
|
281
|
+
delivery_request_builder.add_missing_insertion_ids! response_insertions
|
282
|
+
return response_insertions
|
283
|
+
end
|
284
|
+
|
269
285
|
def do_warmup
|
270
286
|
if !@delivery_endpoint
|
271
287
|
# Warmup only supported when delivery is enabled.
|
@@ -318,7 +334,7 @@ module Promoted
|
|
318
334
|
|
319
335
|
ellapsed_time = Time.now - start_time
|
320
336
|
@logger.debug("Sync send_request completed in #{ellapsed_time.to_f * 1000} ms") if @logger
|
321
|
-
|
337
|
+
end
|
322
338
|
|
323
339
|
return resp
|
324
340
|
end
|
@@ -357,7 +373,7 @@ module Promoted
|
|
357
373
|
|
358
374
|
if !@async_shadow_traffic
|
359
375
|
ellapsed_time = Time.now - start_time
|
360
|
-
insertions = response
|
376
|
+
insertions = response && response[:insertion] || []
|
361
377
|
@logger.info("Shadow traffic call completed in #{ellapsed_time.to_f * 1000} ms with #{insertions.length} insertions") if @logger
|
362
378
|
end
|
363
379
|
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.
|
4
|
+
version: 0.1.23
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- scottmcmaster
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -150,6 +150,7 @@ homepage: https://github.com/promotedai/promoted-ruby-client
|
|
150
150
|
licenses:
|
151
151
|
- MIT
|
152
152
|
metadata:
|
153
|
+
allowed_push_host: https://rubygems.org/
|
153
154
|
homepage_uri: https://github.com/promotedai/promoted-ruby-client
|
154
155
|
source_code_uri: https://github.com/promotedai/promoted-ruby-client
|
155
156
|
changelog_uri: https://github.com/promotedai/promoted-ruby-client/blob/master/CHANGELOG.md
|
@@ -168,7 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
168
169
|
- !ruby/object:Gem::Version
|
169
170
|
version: '0'
|
170
171
|
requirements: []
|
171
|
-
rubygems_version: 3.
|
172
|
+
rubygems_version: 3.0.3
|
172
173
|
signing_key:
|
173
174
|
specification_version: 4
|
174
175
|
summary: A Ruby Client to contact Promoted APIs.
|