promoted-ruby-client 0.1.0 → 0.1.1
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/.gitignore +3 -0
- data/Gemfile +13 -1
- data/Gemfile.lock +82 -2
- data/README.md +47 -8
- data/bin/setup +1 -1
- data/dev.md +9 -0
- data/lib/promoted/ruby/client.rb +217 -48
- data/lib/promoted/ruby/client/constants.rb +37 -0
- data/lib/promoted/ruby/client/errors.rb +7 -1
- data/lib/promoted/ruby/client/extensions.rb +41 -0
- data/lib/promoted/ruby/client/faraday_http_client.rb +31 -0
- data/lib/promoted/ruby/client/request_builder.rb +164 -0
- data/lib/promoted/ruby/client/sampler.rb +13 -0
- data/lib/promoted/ruby/client/settings.rb +5 -5
- data/lib/promoted/ruby/client/util.rb +17 -0
- data/lib/promoted/ruby/client/version.rb +1 -1
- data/promoted-ruby-client.gemspec +2 -2
- metadata +11 -5
- data/lib/promoted/ruby/client/options.rb +0 -206
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0f6d46482500c45c759f6b2965ae13a76aea2b3b0d18c069e31055fe985146f
|
4
|
+
data.tar.gz: 7527ed13e8306bc4f9b53fd1add1a12c478c261be30b0b0dc50d16e146f88f10
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fa5105d3d057f9e949dca0fa46a47c5d701e8dabec37f1d64619969827a580994245a6a83b5fbce399be06201f29d8758c2829e1d76b066bee8a4e8ea62fc892
|
7
|
+
data.tar.gz: c6d29aeb9721ad09de063c21afd529495ff8c7eb3422886010124e85fc654fe711efeeb35d5b1a873f4f0ca4d56c064a119069f698f080a203daf797309ad8a9
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -6,4 +6,16 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
|
6
6
|
gemspec
|
7
7
|
|
8
8
|
gem 'faraday', '~> 1.4.1'
|
9
|
-
gem '
|
9
|
+
gem 'faraday_middleware'
|
10
|
+
gem 'faraday-net_http'
|
11
|
+
gem 'concurrent-ruby', require: 'concurrent'
|
12
|
+
gem 'byebug'
|
13
|
+
|
14
|
+
group :development do
|
15
|
+
gem 'ruby-debug-ide', group: :development
|
16
|
+
gem 'debase', '>= 0.2.5.beta2', group: :development
|
17
|
+
gem 'jaro_winkler', group: :development
|
18
|
+
gem 'solargraph', group: :development
|
19
|
+
end
|
20
|
+
|
21
|
+
gem 'simplecov', require: false, group: :test
|
data/Gemfile.lock
CHANGED
@@ -1,24 +1,57 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
promoted-ruby-client (0.1.
|
4
|
+
promoted-ruby-client (0.1.1)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
8
8
|
specs:
|
9
|
+
ast (2.4.2)
|
10
|
+
backport (1.2.0)
|
11
|
+
benchmark (0.1.1)
|
9
12
|
byebug (11.1.3)
|
13
|
+
concurrent-ruby (1.1.9)
|
14
|
+
debase (0.2.5.beta2)
|
15
|
+
debase-ruby_core_source (>= 0.10.12)
|
16
|
+
debase-ruby_core_source (0.10.12)
|
10
17
|
diff-lcs (1.4.4)
|
11
|
-
|
18
|
+
docile (1.4.0)
|
19
|
+
e2mmap (0.1.0)
|
20
|
+
faraday (1.4.3)
|
21
|
+
faraday-em_http (~> 1.0)
|
22
|
+
faraday-em_synchrony (~> 1.0)
|
12
23
|
faraday-excon (~> 1.1)
|
13
24
|
faraday-net_http (~> 1.0)
|
14
25
|
faraday-net_http_persistent (~> 1.1)
|
15
26
|
multipart-post (>= 1.2, < 3)
|
16
27
|
ruby2_keywords (>= 0.0.4)
|
28
|
+
faraday-em_http (1.0.0)
|
29
|
+
faraday-em_synchrony (1.0.0)
|
17
30
|
faraday-excon (1.1.0)
|
18
31
|
faraday-net_http (1.0.1)
|
19
32
|
faraday-net_http_persistent (1.1.0)
|
33
|
+
faraday_middleware (1.0.0)
|
34
|
+
faraday (~> 1.0)
|
35
|
+
jaro_winkler (1.5.4)
|
36
|
+
kramdown (2.3.1)
|
37
|
+
rexml
|
38
|
+
kramdown-parser-gfm (1.1.0)
|
39
|
+
kramdown (~> 2.0)
|
40
|
+
mini_portile2 (2.5.3)
|
20
41
|
multipart-post (2.1.1)
|
42
|
+
nokogiri (1.11.7)
|
43
|
+
mini_portile2 (~> 2.5.0)
|
44
|
+
racc (~> 1.4)
|
45
|
+
parallel (1.20.1)
|
46
|
+
parser (3.0.1.1)
|
47
|
+
ast (~> 2.4.1)
|
48
|
+
racc (1.5.2)
|
49
|
+
rainbow (3.0.0)
|
21
50
|
rake (10.5.0)
|
51
|
+
regexp_parser (2.1.1)
|
52
|
+
reverse_markdown (2.0.0)
|
53
|
+
nokogiri
|
54
|
+
rexml (3.2.5)
|
22
55
|
rspec (3.10.0)
|
23
56
|
rspec-core (~> 3.10.0)
|
24
57
|
rspec-expectations (~> 3.10.0)
|
@@ -32,7 +65,46 @@ GEM
|
|
32
65
|
diff-lcs (>= 1.2.0, < 2.0)
|
33
66
|
rspec-support (~> 3.10.0)
|
34
67
|
rspec-support (3.10.2)
|
68
|
+
rubocop (1.17.0)
|
69
|
+
parallel (~> 1.10)
|
70
|
+
parser (>= 3.0.0.0)
|
71
|
+
rainbow (>= 2.2.2, < 4.0)
|
72
|
+
regexp_parser (>= 1.8, < 3.0)
|
73
|
+
rexml
|
74
|
+
rubocop-ast (>= 1.7.0, < 2.0)
|
75
|
+
ruby-progressbar (~> 1.7)
|
76
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
77
|
+
rubocop-ast (1.7.0)
|
78
|
+
parser (>= 3.0.1.1)
|
79
|
+
ruby-debug-ide (0.7.2)
|
80
|
+
rake (>= 0.8.1)
|
81
|
+
ruby-progressbar (1.11.0)
|
35
82
|
ruby2_keywords (0.0.4)
|
83
|
+
simplecov (0.21.2)
|
84
|
+
docile (~> 1.1)
|
85
|
+
simplecov-html (~> 0.11)
|
86
|
+
simplecov_json_formatter (~> 0.1)
|
87
|
+
simplecov-html (0.12.3)
|
88
|
+
simplecov_json_formatter (0.1.3)
|
89
|
+
solargraph (0.42.3)
|
90
|
+
backport (~> 1.2)
|
91
|
+
benchmark
|
92
|
+
bundler (>= 1.17.2)
|
93
|
+
diff-lcs (~> 1.4)
|
94
|
+
e2mmap
|
95
|
+
jaro_winkler (~> 1.5)
|
96
|
+
kramdown (~> 2.3)
|
97
|
+
kramdown-parser-gfm (~> 1.1)
|
98
|
+
parser (~> 3.0)
|
99
|
+
reverse_markdown (>= 1.0.5, < 3)
|
100
|
+
rubocop (>= 0.52)
|
101
|
+
thor (~> 1.0)
|
102
|
+
tilt (~> 2.0)
|
103
|
+
yard (~> 0.9, >= 0.9.24)
|
104
|
+
thor (1.1.0)
|
105
|
+
tilt (2.0.10)
|
106
|
+
unicode-display_width (2.0.0)
|
107
|
+
yard (0.9.26)
|
36
108
|
|
37
109
|
PLATFORMS
|
38
110
|
ruby
|
@@ -40,10 +112,18 @@ PLATFORMS
|
|
40
112
|
DEPENDENCIES
|
41
113
|
bundler (~> 1.17)
|
42
114
|
byebug
|
115
|
+
concurrent-ruby
|
116
|
+
debase (>= 0.2.5.beta2)
|
43
117
|
faraday (~> 1.4.1)
|
118
|
+
faraday-net_http
|
119
|
+
faraday_middleware
|
120
|
+
jaro_winkler
|
44
121
|
promoted-ruby-client!
|
45
122
|
rake (~> 10.0)
|
46
123
|
rspec (~> 3.0)
|
124
|
+
ruby-debug-ide
|
125
|
+
simplecov
|
126
|
+
solargraph
|
47
127
|
|
48
128
|
BUNDLED WITH
|
49
129
|
1.17.2
|
data/README.md
CHANGED
@@ -17,7 +17,8 @@ end
|
|
17
17
|
|
18
18
|
# Done async
|
19
19
|
def log_request items
|
20
|
-
|
20
|
+
client = Promoted::Ruby::Client::PromotedClient.new
|
21
|
+
log_request = client.prepare_for_logging(input)
|
21
22
|
# Send JSON to Metrics API.
|
22
23
|
log_to_promoted_event_api(log_request)
|
23
24
|
end
|
@@ -25,14 +26,19 @@ end
|
|
25
26
|
|
26
27
|
## Naming details
|
27
28
|
|
28
|
-
`
|
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.
|
29
30
|
|
30
31
|
## Pagination
|
31
32
|
|
32
|
-
The `prepare_for_logging` call assumes the client has already handled pagination. It needs a `Request.paging.
|
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.
|
33
34
|
|
34
35
|
## Example to run the client
|
35
36
|
|
37
|
+
Install our Ruby client.
|
38
|
+
`promoted-ruby-client (0.1.1)`
|
39
|
+
|
40
|
+
Or
|
41
|
+
|
36
42
|
1. Clone the repo on your local machine
|
37
43
|
2. `cd promoted-ruby-client`
|
38
44
|
3. `bundle`
|
@@ -79,6 +85,38 @@ def to_insertions products
|
|
79
85
|
end
|
80
86
|
|
81
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
|
94
|
+
},
|
95
|
+
properties: {
|
96
|
+
struct: {
|
97
|
+
active: true
|
98
|
+
}
|
99
|
+
}
|
100
|
+
},
|
101
|
+
full_insertion: to_insertions(products)
|
102
|
+
}
|
103
|
+
|
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
|
113
|
+
to_compact_metrics_insertion_func = Proc.new do |insertion|
|
114
|
+
insertion.delete(:properties)
|
115
|
+
insertion
|
116
|
+
end
|
117
|
+
|
118
|
+
request_input = {
|
119
|
+
to_compact_metrics_insertion_func: to_compact_metrics_insertion_func,
|
82
120
|
request: {
|
83
121
|
user_info: { user_id: "912", log_user_id: "912191"},
|
84
122
|
use_case: "FEED",
|
@@ -92,16 +130,17 @@ request_input = {
|
|
92
130
|
}
|
93
131
|
}
|
94
132
|
},
|
95
|
-
|
133
|
+
full_insertion: to_insertions(products)
|
96
134
|
}
|
97
135
|
|
98
|
-
|
136
|
+
client = Promoted::Ruby::Client::PromotedClient.new
|
137
|
+
log_request = client.prepare_for_logging(request_input)
|
99
138
|
log_request.to_json
|
100
139
|
```
|
101
140
|
|
102
141
|
`log_request.to_json` returns a result that looks like the following
|
103
142
|
```
|
104
|
-
=> "{\"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\":{\"
|
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}]}"
|
105
144
|
```
|
106
145
|
|
107
146
|
## Other input syntaxes
|
@@ -139,7 +178,7 @@ input = {
|
|
139
178
|
}
|
140
179
|
}
|
141
180
|
},
|
142
|
-
"
|
181
|
+
"full_insertion"=>to_insertions(products)
|
143
182
|
}
|
144
183
|
```
|
145
184
|
|
@@ -155,7 +194,7 @@ input = {
|
|
155
194
|
}
|
156
195
|
}
|
157
196
|
},
|
158
|
-
"
|
197
|
+
"full_insertion"=>[
|
159
198
|
{
|
160
199
|
"contentId"=>"123",
|
161
200
|
"properties"=>{
|
data/bin/setup
CHANGED
data/dev.md
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
|
2
|
+
## Deploy
|
3
|
+
|
4
|
+
1. Update version number.
|
5
|
+
2. Get credentials for deployment from 1password.
|
6
|
+
3. Modify `promoted-ruby-client.gemspec`'s push block.
|
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.1.gem`
|
9
|
+
6. Update README with new version.
|
data/lib/promoted/ruby/client.rb
CHANGED
@@ -1,71 +1,240 @@
|
|
1
|
+
require "concurrent-ruby"
|
2
|
+
require "promoted/ruby/client/faraday_http_client"
|
1
3
|
require "promoted/ruby/client/version"
|
2
|
-
require 'faraday'
|
3
|
-
require 'json'
|
4
4
|
|
5
5
|
module Promoted
|
6
6
|
module Ruby
|
7
7
|
module Client
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
|
9
|
+
DEFAULT_DELIVERY_TIMEOUT_MILLIS = 3000
|
10
|
+
DEFAULT_METRICS_TIMEOUT_MILLIS = 3000
|
11
|
+
DEFAULT_DELIVERY_ENDPOINT = "http://delivery.example.com"
|
12
|
+
DEFAULT_METRICS_ENDPOINT = "http://metrics.example.com"
|
13
|
+
|
14
|
+
class PromotedClient
|
15
|
+
|
16
|
+
class Error < StandardError; end
|
17
|
+
|
18
|
+
attr_reader :perform_checks, :default_only_log, :delivery_timeout_millis, :metrics_timeout_millis, :should_apply_treatment_func,
|
19
|
+
:default_request_headers
|
20
|
+
|
21
|
+
def initialize (params={})
|
22
|
+
@perform_checks = true
|
23
|
+
if params[:perform_checks] != nil
|
24
|
+
@perform_checks = params[:perform_checks]
|
25
|
+
end
|
26
|
+
|
27
|
+
@default_request_headers = params[:default_request_headers] || {}
|
28
|
+
@default_request_headers['x-api-key'] = params[:api_key] || ''
|
29
|
+
|
30
|
+
@default_only_log = params[:default_only_log] || false
|
31
|
+
@should_apply_treatment_func = params[:should_apply_treatment_func]
|
32
|
+
|
33
|
+
@shadow_traffic_delivery_percent = params[:shadow_traffic_delivery_percent] || 0.0
|
34
|
+
raise ArgumentError.new("Invalid shadow_traffic_delivery_percent, must be between 0 and 1") if @shadow_traffic_delivery_percent < 0 || @shadow_traffic_delivery_percent > 1.0
|
35
|
+
|
36
|
+
@sampler = Sampler.new
|
37
|
+
|
38
|
+
# HTTP Client creation
|
39
|
+
@delivery_endpoint = params[:delivery_endpoint] || DEFAULT_DELIVERY_ENDPOINT
|
40
|
+
raise ArgumentError.new("delivery_endpoint is required") if @delivery_endpoint.strip.empty?
|
41
|
+
|
42
|
+
@metrics_endpoint = params[:metrics_endpoint] || DEFAULT_METRICS_ENDPOINT
|
43
|
+
raise ArgumentError.new("metrics_endpoint is required") if @metrics_endpoint.strip.empty?
|
44
|
+
|
45
|
+
@delivery_timeout_millis = params[:delivery_timeout_millis] || DEFAULT_DELIVERY_TIMEOUT_MILLIS
|
46
|
+
@metrics_timeout_millis = params[:metrics_timeout_millis] || DEFAULT_METRICS_TIMEOUT_MILLIS
|
47
|
+
|
48
|
+
@http_client = FaradayHTTPClient.new
|
49
|
+
@pool = Concurrent::CachedThreadPool.new
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
@pool.shutdown
|
54
|
+
@pool.wait_for_termination
|
19
55
|
end
|
20
|
-
response
|
21
|
-
end
|
22
56
|
|
23
|
-
|
24
|
-
|
25
|
-
|
57
|
+
def send_request payload, endpoint, timeout_millis, headers=[], send_async=false
|
58
|
+
use_headers = @default_request_headers.merge headers
|
59
|
+
|
60
|
+
if send_async
|
61
|
+
@pool.post do
|
62
|
+
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
63
|
+
end
|
64
|
+
else
|
65
|
+
@http_client.send(endpoint, timeout_millis, payload, use_headers)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def deliver args, headers={}
|
70
|
+
args = Promoted::Ruby::Client::Util.translate_args(args)
|
71
|
+
|
72
|
+
delivery_request_builder = RequestBuilder.new
|
73
|
+
delivery_request_builder.set_request_params(args)
|
74
|
+
|
75
|
+
if perform_checks?
|
76
|
+
Promoted::Ruby::Client::Settings.check_that_log_ids_not_set!(args)
|
77
|
+
end
|
78
|
+
|
79
|
+
pre_delivery_fillin_fields delivery_request_builder
|
80
|
+
|
81
|
+
response_insertions = []
|
82
|
+
cohort_membership_to_log = nil
|
83
|
+
insertions_from_promoted = false
|
84
|
+
|
85
|
+
only_log = delivery_request_builder.only_log != nil ? delivery_request_builder.only_log : @default_only_log
|
86
|
+
if !only_log
|
87
|
+
cohort_membership_to_log = delivery_request_builder.new_cohort_membership_to_log
|
88
|
+
end
|
89
|
+
|
90
|
+
if should_apply_treatment(cohort_membership_to_log)
|
91
|
+
delivery_request_params = delivery_request_builder.delivery_request_params
|
92
|
+
|
93
|
+
# Call Delivery API
|
94
|
+
response = send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, headers)
|
95
|
+
|
96
|
+
response_insertions = delivery_request_builder.fill_details_from_response(response[:insertion])
|
97
|
+
insertions_from_promoted = true;
|
98
|
+
end
|
99
|
+
|
100
|
+
request_to_log = nil
|
101
|
+
if !insertions_from_promoted
|
102
|
+
request_to_log = delivery_request_builder.request
|
103
|
+
size = delivery_request_builder.request.dig(:paging, :size)
|
104
|
+
response_insertions = size != nil ? delivery_request_builder.full_insertion[0..size] : delivery_request_builder.full_insertion
|
105
|
+
end
|
106
|
+
|
107
|
+
if request_to_log
|
108
|
+
request_to_log[:request_id] = SecureRandom.uuid if not request_to_log[:request_id]
|
109
|
+
add_missing_ids_on_insertions! request_to_log, response_insertions
|
110
|
+
end
|
111
|
+
|
112
|
+
log_req = nil
|
113
|
+
# We only return a log request if there's a request or cohort to log.
|
114
|
+
if request_to_log || cohort_membership_to_log
|
115
|
+
log_request_builder = RequestBuilder.new
|
116
|
+
log_request = {
|
117
|
+
:full_insertion => response_insertions,
|
118
|
+
:experiment => cohort_membership_to_log,
|
119
|
+
:request => request_to_log
|
120
|
+
}
|
121
|
+
log_request_builder.set_request_params(log_request)
|
122
|
+
|
123
|
+
# We can't count on these being set already since request_to_log may be nil.
|
124
|
+
log_request_builder.platform_id = delivery_request_builder.platform_id
|
125
|
+
log_request_builder.timing = delivery_request_builder.timing
|
126
|
+
log_request_builder.user_info = delivery_request_builder.user_info
|
127
|
+
pre_delivery_fillin_fields log_request_builder
|
26
128
|
|
27
|
-
def self.log_request args={}, options={}
|
28
|
-
endpoint = options[:endpoint]
|
29
|
-
payload = prepare_for_logging(args)
|
30
|
-
send_request(payload, endpoint)
|
31
|
-
end
|
32
129
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
130
|
+
# On a successful delivery request, we don't log the insertions
|
131
|
+
# or the request since they are logged on the server-side.
|
132
|
+
log_req = log_request_builder.log_request_params(
|
133
|
+
include_insertions: !insertions_from_promoted,
|
134
|
+
include_request: !insertions_from_promoted)
|
135
|
+
end
|
136
|
+
|
137
|
+
client_response = {
|
138
|
+
insertion: response_insertions,
|
139
|
+
log_request: log_req
|
140
|
+
}
|
141
|
+
return client_response
|
38
142
|
end
|
39
|
-
options.log_request_params
|
40
|
-
end
|
41
143
|
|
42
|
-
|
43
|
-
|
44
|
-
|
144
|
+
def add_missing_ids_on_insertions! request, insertions
|
145
|
+
insertions.each do |insertion|
|
146
|
+
insertion[:insertion_id] = SecureRandom.uuid if not insertion[:insertion_id]
|
147
|
+
insertion[:session_id] = request[:session_id] if request[:session_id]
|
148
|
+
insertion[:request_id] = request[:request_id] if request[:request_id]
|
149
|
+
end
|
45
150
|
end
|
46
|
-
end
|
47
151
|
|
48
|
-
|
49
|
-
|
50
|
-
|
152
|
+
def perform_checks?
|
153
|
+
@perform_checks
|
154
|
+
end
|
155
|
+
|
156
|
+
# Sends a log request (previously created by a call to prepare_for_logging) to the metrics endpoint.
|
157
|
+
def send_log_request log_request_params, headers={}
|
158
|
+
send_request(log_request_params, @metrics_endpoint, @metrics_timeout_millis, headers)
|
159
|
+
end
|
160
|
+
|
161
|
+
def should_send_as_shadow_traffic?
|
162
|
+
@sampler.sample_random?(@shadow_traffic_delivery_percent)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Delivers shadow traffic from the given metrics args.
|
166
|
+
# Assumes that the args have already been normalized since this
|
167
|
+
# method should only be called from inside prepare_for_logging.
|
168
|
+
def deliver_shadow_traffic args, headers
|
169
|
+
delivery_request_builder = RequestBuilder.new
|
170
|
+
delivery_request_builder.set_request_params args
|
171
|
+
|
172
|
+
delivery_request_params = delivery_request_builder.delivery_request_params(should_compact: false)
|
173
|
+
delivery_request_params[:client_info][:traffic_type] = Promoted::Ruby::Client::TRAFFIC_TYPE['SHADOW']
|
174
|
+
|
175
|
+
# Call Delivery API async (fire and forget)
|
176
|
+
send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, headers, true)
|
177
|
+
end
|
178
|
+
|
179
|
+
def prepare_for_logging args, headers={}
|
180
|
+
args = Promoted::Ruby::Client::Util.translate_args(args)
|
181
|
+
|
182
|
+
log_request_builder = RequestBuilder.new
|
183
|
+
|
184
|
+
# Note: This method expects as JSON (string keys) but internally, RequestBuilder
|
185
|
+
# transforms and works with symbol keys.
|
186
|
+
log_request_builder.set_request_params(args)
|
187
|
+
if perform_checks?
|
188
|
+
Promoted::Ruby::Client::Settings.check_that_log_ids_not_set!(args)
|
189
|
+
|
190
|
+
if @shadow_traffic_delivery_percent > 0 && args[:insertion_page_type] != Promoted::Ruby::Client::INSERTION_PAGING_TYPE['UNPAGED'] then
|
191
|
+
raise ShadowTrafficInsertionPageType
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
pre_delivery_fillin_fields log_request_builder
|
196
|
+
|
197
|
+
if should_send_as_shadow_traffic?
|
198
|
+
deliver_shadow_traffic args, headers
|
199
|
+
end
|
51
200
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
201
|
+
log_request_builder.log_request_params
|
202
|
+
end
|
203
|
+
|
204
|
+
def should_apply_treatment(cohort_membership)
|
205
|
+
if @should_apply_treatment_func != nil
|
206
|
+
@should_apply_treatment_func
|
207
|
+
else
|
208
|
+
return true if !cohort_membership
|
209
|
+
return true if !cohort_membership[:arm]
|
210
|
+
return cohort_membership[:arm] != Promoted::Ruby::Client::COHORT_ARM['CONTROL']
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
# TODO: This probably just goes better in the RequestBuilder class.
|
215
|
+
def pre_delivery_fillin_fields(log_request_builder)
|
216
|
+
if log_request_builder.timing[:client_log_timestamp].nil?
|
217
|
+
log_request_builder.timing[:client_log_timestamp] = Time.now.to_i
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
# A common compact method implementation.
|
222
|
+
def self.copy_and_remove_properties
|
223
|
+
Proc.new do |insertion|
|
224
|
+
insertion = Hash[insertion]
|
225
|
+
insertion.delete(:properties)
|
226
|
+
insertion
|
227
|
+
end
|
228
|
+
end
|
62
229
|
end
|
63
230
|
end
|
64
231
|
end
|
65
232
|
end
|
66
233
|
|
67
234
|
# dependent /libs
|
68
|
-
require "promoted/ruby/client/
|
235
|
+
require "promoted/ruby/client/request_builder"
|
236
|
+
require "promoted/ruby/client/sampler"
|
69
237
|
require "promoted/ruby/client/settings"
|
238
|
+
require "promoted/ruby/client/util"
|
70
239
|
require 'byebug'
|
71
240
|
require 'securerandom'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Promoted
|
2
|
+
module Ruby
|
3
|
+
module Client
|
4
|
+
USE_CASES = {'UNKNOWN_USE_CASE'=> 'UNKNOWN_USE_CASE',
|
5
|
+
'CUSTOM'=> 'CUSTOM',
|
6
|
+
'SEARCH'=> 'SEARCH',
|
7
|
+
'SEARCH_SUGGESTIONS'=> 'SEARCH_SUGGESTIONS',
|
8
|
+
'FEED'=> 'FEED',
|
9
|
+
'RELATED_CONTENT'=> 'RELATED_CONTENT',
|
10
|
+
'CLOSE_UP'=> 'CLOSE_UP',
|
11
|
+
'CATEGORY_CONTENT'=> 'CATEGORY_CONTENT',
|
12
|
+
'MY_CONTENT'=> 'MY_CONTENT',
|
13
|
+
'MY_SAVED_CONTENT'=> 'MY_SAVED_CONTENT',
|
14
|
+
'SELLER_CONTENT'=> 'SELLER_CONTENT',
|
15
|
+
'DISCOVER'=> 'DISCOVER'}
|
16
|
+
|
17
|
+
INSERTION_PAGING_TYPE = {'UNPAGED' => 'UNPAGED',
|
18
|
+
'PRE_PAGED' => 'PRE_PAGED'}
|
19
|
+
|
20
|
+
COHORT_ARM = {'UNKNOWN_GROUP' => 'UNKNOWN_GROUP',
|
21
|
+
'CONTROL' => 'CONTROL',
|
22
|
+
'TREATMENT' => 'TREATMENT',
|
23
|
+
'TREATMENT1' => 'TREATMENT1',
|
24
|
+
'TREATMENT2' => 'TREATMENT2',
|
25
|
+
'TREATMENT3' => 'TREATMENT3'}
|
26
|
+
|
27
|
+
TRAFFIC_TYPE = {'UNKNOWN_TRAFFIC_TYPE' => 'UNKNOWN_TRAFFIC_TYPE',
|
28
|
+
'PRODUCTION' => 'PRODUCTION',
|
29
|
+
'REPLAY' => 'REPLAY',
|
30
|
+
'SHADOW' => 'SHADOW'}
|
31
|
+
|
32
|
+
CLIENT_TYPE = {'UNKNOWN_REQUEST_CLIENT' => 'UNKNOWN_REQUEST_CLIENT',
|
33
|
+
'PLATFORM_SERVER' => 'PLATFORM_SERVER',
|
34
|
+
'PLATFORM_CLIENT' => 'PLATFORM_CLIENT'}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -9,7 +9,7 @@ module Promoted
|
|
9
9
|
|
10
10
|
class RequestInsertionError < StandardError
|
11
11
|
def message
|
12
|
-
'Do not set Request.insertion. Set
|
12
|
+
'Do not set Request.insertion. Set full_insertion.'
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -30,6 +30,12 @@ module Promoted
|
|
30
30
|
'Insertion.contentId should be set'
|
31
31
|
end
|
32
32
|
end
|
33
|
+
|
34
|
+
class ShadowTrafficInsertionPageType < StandardError
|
35
|
+
def message
|
36
|
+
'Insertions must be unpaged when shadow traffic is on'
|
37
|
+
end
|
38
|
+
end
|
33
39
|
end
|
34
40
|
end
|
35
41
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class String
|
2
|
+
# Ruby mutation methods have the expectation to return self if a mutation occurred, nil otherwise. (see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-gsub-21)
|
3
|
+
def to_underscore!
|
4
|
+
gsub!(/(.)([A-Z])/,'\1_\2')
|
5
|
+
downcase!
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_underscore
|
9
|
+
dup.tap { |s| s.to_underscore! }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Hash
|
14
|
+
def clean!
|
15
|
+
self.delete_if do |key, val|
|
16
|
+
if block_given?
|
17
|
+
yield(key,val)
|
18
|
+
else
|
19
|
+
# checks for empty/blank values
|
20
|
+
nil_value = val.nil?
|
21
|
+
falsy = val === false
|
22
|
+
is_empty = val.empty? if val.respond_to?('empty?')
|
23
|
+
is_empty_string = val.strip.empty? if val.is_a?(String) && val.respond_to?('empty?')
|
24
|
+
|
25
|
+
# Were any of the checks true
|
26
|
+
nil_value || falsy || is_empty || is_empty_string
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
self.each do |key, val|
|
31
|
+
if self[key].is_a?(Hash) && self[key].respond_to?('clean!')
|
32
|
+
if block_given?
|
33
|
+
self[key] = self[key].clean!(&Proc.new)
|
34
|
+
else
|
35
|
+
self[key] = self[key].clean!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
return self
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday_middleware'
|
3
|
+
|
4
|
+
module Promoted
|
5
|
+
module Ruby
|
6
|
+
module Client
|
7
|
+
class FaradayHTTPClient
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@conn = Faraday.new do |f|
|
11
|
+
f.request :json
|
12
|
+
f.request :retry, max: 3
|
13
|
+
f.adapter :net_http
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def send(endpoint, timeout_millis, request, additional_headers={})
|
18
|
+
response = @conn.post(endpoint) do |req|
|
19
|
+
req.headers = req.headers.merge(additional_headers) if additional_headers
|
20
|
+
req.headers['Content-Type'] = req.headers['Content-Type'] || 'application/json'
|
21
|
+
req.options.timeout = timeout_millis / 1000
|
22
|
+
req.body = request.to_json
|
23
|
+
end
|
24
|
+
|
25
|
+
# TODO: Check response code, rescue on ParserError, etc.
|
26
|
+
JSON.parse(response.body, :symbolize_names => true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Promoted
|
2
|
+
module Ruby
|
3
|
+
module Client
|
4
|
+
class RequestBuilder
|
5
|
+
attr_reader :session_id, :only_log, :experiment, :client_info,
|
6
|
+
:view_id, :insertion, :to_compact_delivery_insertion_func,
|
7
|
+
:request_id, :full_insertion, :use_case, :request, :to_compact_metrics_insertion_func
|
8
|
+
|
9
|
+
attr_accessor :timing, :user_info, :platform_id
|
10
|
+
|
11
|
+
def initialize;end
|
12
|
+
|
13
|
+
# Populates request parameters from the given arguments, presumed to be a hash of symbols.
|
14
|
+
def set_request_params args = {}
|
15
|
+
@request = args[:request] || {}
|
16
|
+
@experiment = args[:experiment]
|
17
|
+
@session_id = request[:session_id]
|
18
|
+
@platform_id = request[:platform_id]
|
19
|
+
@client_info = request[:client_info] || {}
|
20
|
+
@view_id = request[:view_id]
|
21
|
+
@use_case = Promoted::Ruby::Client::USE_CASES[request[:use_case]] || Promoted::Ruby::Client::USE_CASES['UNKNOWN_USE_CASE']
|
22
|
+
@full_insertion = args[:full_insertion]
|
23
|
+
@request_id = SecureRandom.uuid
|
24
|
+
@user_info = request[:user_info] || { :user_id => nil, :log_user_id => nil}
|
25
|
+
@timing = request[:timing] || { :client_log_timestamp => Time.now.to_i }
|
26
|
+
@only_log = request[:only_log]
|
27
|
+
@to_compact_metrics_insertion_func = args[:to_compact_metrics_insertion_func]
|
28
|
+
@to_compact_delivery_insertion_func = args[:to_compact_delivery_insertion_func]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Only used in delivery
|
32
|
+
def new_cohort_membership_to_log
|
33
|
+
return nil unless @experiment
|
34
|
+
if !@experiment[:platform_id] && @platform_id
|
35
|
+
@experiment[:platform_id] = @platform_id
|
36
|
+
end
|
37
|
+
if !@experiment[:user_info] && @user_info
|
38
|
+
@experiment[:user_info] = @user_info
|
39
|
+
end
|
40
|
+
if !@experiment[:timing] && @timing
|
41
|
+
@experiment[:timing] = @timing
|
42
|
+
end
|
43
|
+
return @experiment
|
44
|
+
end
|
45
|
+
|
46
|
+
# Only used in delivery
|
47
|
+
def delivery_request_params(should_compact: true)
|
48
|
+
params = {
|
49
|
+
user_info: user_info,
|
50
|
+
timing: timing,
|
51
|
+
cohort_membership: @experiment,
|
52
|
+
client_info: @client_info.merge({ :client_type => Promoted::Ruby::Client::CLIENT_TYPE['PLATFORM_SERVER'] })
|
53
|
+
}
|
54
|
+
params[:request] = request
|
55
|
+
params[:insertion] = should_compact ? compact_delivery_insertions : full_insertion
|
56
|
+
|
57
|
+
params.clean!
|
58
|
+
end
|
59
|
+
|
60
|
+
# Only used in delivery
|
61
|
+
# Maps the response insertions to the full insertions and re-insert the properties bag
|
62
|
+
# to the responses.
|
63
|
+
def fill_details_from_response response_insertions
|
64
|
+
props = @full_insertion.each_with_object({}) do |insertion, hash|
|
65
|
+
hash[insertion[:content_id]] = insertion[:properties]
|
66
|
+
end
|
67
|
+
|
68
|
+
filled_in_copy = []
|
69
|
+
response_insertions.each do |resp_insertion|
|
70
|
+
copied_insertion = resp_insertion.clone
|
71
|
+
if copied_insertion.has_key?(:content_id) && props.has_key?(copied_insertion[:content_id])
|
72
|
+
copied_insertion[:properties] = props[resp_insertion[:content_id]]
|
73
|
+
end
|
74
|
+
filled_in_copy << copied_insertion
|
75
|
+
end
|
76
|
+
|
77
|
+
filled_in_copy
|
78
|
+
end
|
79
|
+
|
80
|
+
def validate_request_params
|
81
|
+
# TODO
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_compact_metrics_insertion_func
|
85
|
+
@to_compact_metrics_insertion_func
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_compact_delivery_insertion_func
|
89
|
+
@to_compact_delivery_insertion_func
|
90
|
+
end
|
91
|
+
|
92
|
+
# A list of the response Insertions. This client expects lists to be truncated
|
93
|
+
# already to request.paging.size. If not truncated, this client will truncate
|
94
|
+
# the list.
|
95
|
+
def insertion
|
96
|
+
@insertion
|
97
|
+
end
|
98
|
+
|
99
|
+
# A way to turn off logging. Defaults to true.
|
100
|
+
def enabled?
|
101
|
+
@enabled
|
102
|
+
end
|
103
|
+
|
104
|
+
# Default values to use on DeliveryRequests.
|
105
|
+
def default_request_values
|
106
|
+
@default_request_values
|
107
|
+
end
|
108
|
+
|
109
|
+
def log_request_params(include_insertions: true, include_request: true)
|
110
|
+
params = {
|
111
|
+
user_info: user_info,
|
112
|
+
timing: timing,
|
113
|
+
cohort_membership: @experiment,
|
114
|
+
client_info: @client_info
|
115
|
+
}
|
116
|
+
params[:request] = [request] if include_request
|
117
|
+
params[:insertion] = compact_metrics_insertions if include_insertions
|
118
|
+
|
119
|
+
params.clean!
|
120
|
+
end
|
121
|
+
|
122
|
+
def compact_delivery_insertions
|
123
|
+
if !@to_compact_delivery_insertion_func
|
124
|
+
full_insertion
|
125
|
+
else
|
126
|
+
full_insertion.map {|insertion| @to_compact_delivery_insertion_func.call(insertion) }
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# TODO: This looks overly complicated.
|
131
|
+
def compact_metrics_insertions
|
132
|
+
@insertion = [] # insertion should be set according to the compact insertion
|
133
|
+
paging = request[:paging] || {}
|
134
|
+
size = paging[:size] ? paging[:size].to_i : 0
|
135
|
+
if size <= 0
|
136
|
+
size = full_insertion.length()
|
137
|
+
end
|
138
|
+
offset = paging[:offset] ? paging[:offset].to_i : 0
|
139
|
+
|
140
|
+
full_insertion.each_with_index do |insertion_obj, index|
|
141
|
+
# TODO - this does not look performant.
|
142
|
+
break if @insertion.length() >= size
|
143
|
+
|
144
|
+
insertion_obj = insertion_obj.transform_keys{ |key| key.to_s.to_underscore.to_sym }
|
145
|
+
insertion_obj = Hash[insertion_obj]
|
146
|
+
insertion_obj[:user_info] = user_info
|
147
|
+
insertion_obj[:timing] = timing
|
148
|
+
insertion_obj[:insertion_id] = SecureRandom.uuid # generate random UUID
|
149
|
+
insertion_obj[:request_id] = request_id
|
150
|
+
insertion_obj[:position] = offset + index
|
151
|
+
insertion_obj = @to_compact_metrics_insertion_func.call(insertion_obj) if @to_compact_metrics_insertion_func
|
152
|
+
@insertion << insertion_obj.clean!
|
153
|
+
end
|
154
|
+
@insertion
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
require 'securerandom'
|
163
|
+
require "promoted/ruby/client/constants"
|
164
|
+
require "promoted/ruby/client/extensions"
|
@@ -4,12 +4,12 @@ module Promoted
|
|
4
4
|
class Settings
|
5
5
|
|
6
6
|
def self.check_that_log_ids_not_set! options_hash
|
7
|
-
raise RequestError if options_hash.dig(
|
8
|
-
raise RequestInsertionError if options_hash[
|
7
|
+
raise RequestError if options_hash.dig(:request, :request_id)
|
8
|
+
raise RequestInsertionError if options_hash[:insertion]
|
9
9
|
|
10
|
-
options_hash[
|
11
|
-
raise InsertionRequestIdError if insertion_hash[
|
12
|
-
raise InsertionIdError if insertion_hash[
|
10
|
+
options_hash[:full_insertion].each do |insertion_hash|
|
11
|
+
raise InsertionRequestIdError if insertion_hash[:request_id]
|
12
|
+
raise InsertionIdError if insertion_hash[:insertion_id]
|
13
13
|
end
|
14
14
|
true
|
15
15
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Promoted
|
2
|
+
module Ruby
|
3
|
+
module Client
|
4
|
+
module Util
|
5
|
+
def self.translate_args(args)
|
6
|
+
sym_hash = {}
|
7
|
+
args.each do |k, v|
|
8
|
+
sym_hash[k.to_s.to_underscore.to_sym] = v.is_a?(Hash) ? translate_args(v) : v
|
9
|
+
end
|
10
|
+
sym_hash
|
11
|
+
rescue => e
|
12
|
+
raise 'Unable to parse args. Please pass correct arguments. Must be JSON'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -6,8 +6,8 @@ require "promoted/ruby/client/version"
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "promoted-ruby-client"
|
8
8
|
spec.version = Promoted::Ruby::Client::VERSION
|
9
|
-
spec.authors = ["
|
10
|
-
spec.email = ["
|
9
|
+
spec.authors = ["scottmcmaster"]
|
10
|
+
spec.email = ["scott@promoted.ai"]
|
11
11
|
|
12
12
|
spec.summary = 'A Ruby Client to contact Promoted APIs.'
|
13
13
|
spec.description = 'This is primarily intended to be used when logging Requests and Insertions on a backend server.'
|
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.1
|
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-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -55,7 +55,7 @@ dependencies:
|
|
55
55
|
description: This is primarily intended to be used when logging Requests and Insertions
|
56
56
|
on a backend server.
|
57
57
|
email:
|
58
|
-
-
|
58
|
+
- scott@promoted.ai
|
59
59
|
executables: []
|
60
60
|
extensions: []
|
61
61
|
extra_rdoc_files:
|
@@ -69,10 +69,16 @@ files:
|
|
69
69
|
- Rakefile
|
70
70
|
- bin/console
|
71
71
|
- bin/setup
|
72
|
+
- dev.md
|
72
73
|
- lib/promoted/ruby/client.rb
|
74
|
+
- lib/promoted/ruby/client/constants.rb
|
73
75
|
- lib/promoted/ruby/client/errors.rb
|
74
|
-
- lib/promoted/ruby/client/
|
76
|
+
- lib/promoted/ruby/client/extensions.rb
|
77
|
+
- lib/promoted/ruby/client/faraday_http_client.rb
|
78
|
+
- lib/promoted/ruby/client/request_builder.rb
|
79
|
+
- lib/promoted/ruby/client/sampler.rb
|
75
80
|
- lib/promoted/ruby/client/settings.rb
|
81
|
+
- lib/promoted/ruby/client/util.rb
|
76
82
|
- lib/promoted/ruby/client/version.rb
|
77
83
|
- promoted-ruby-client.gemspec
|
78
84
|
homepage: https://github.com/promotedai/promoted-ruby-client
|
@@ -1,206 +0,0 @@
|
|
1
|
-
module Promoted
|
2
|
-
module Ruby
|
3
|
-
module Client
|
4
|
-
DELIVERY_TIMEOUT_MILLIS = 30000
|
5
|
-
DEFAULT_METRICS_TIMEOUT_MILLIS = 250
|
6
|
-
|
7
|
-
class Options
|
8
|
-
attr_accessor :delivery_timeout_millis, :session_id, :perform_checks,
|
9
|
-
:uuid, :metrics_timeout_millis, :now_millis, :should_apply_treatment,
|
10
|
-
:view_id, :user_id, :insertion, :client_log_timestamp,
|
11
|
-
:request_id, :full_insertion, :use_case, :request
|
12
|
-
|
13
|
-
def initialize()
|
14
|
-
# TODO
|
15
|
-
end
|
16
|
-
|
17
|
-
def set_request_params args = {}
|
18
|
-
args = translate_args(args)
|
19
|
-
@request = args[:request]
|
20
|
-
@delivery_timeout_millis = args[:delivery_timeout_millis] || DELIVERY_TIMEOUT_MILLIS
|
21
|
-
@session_id = args[:session_id]
|
22
|
-
@user_id = args[:user_id]
|
23
|
-
@log_user_id = args[:log_user_id]
|
24
|
-
@view_id = args[:view_id]
|
25
|
-
@perform_checks = args[:perform_checks] || false
|
26
|
-
@only_Log = args[:only_Log] || false
|
27
|
-
@uuid = args[:uuid]
|
28
|
-
@use_case = args[:use_case] || 'FEED'
|
29
|
-
@now_millis = args[:now_millis] || Time.now.to_i
|
30
|
-
@metrics_timeout_millis = args[:metrics_timeout_millis] || DEFAULT_METRICS_TIMEOUT_MILLIS
|
31
|
-
@should_apply_treatment = args[:should_apply_treatment] || false
|
32
|
-
@full_insertion = args[:full_insertion]
|
33
|
-
@insertion = args[:insertion] || []
|
34
|
-
@client_log_timestamp = args[:client_log_timestamp] || Time.now.to_i
|
35
|
-
@request_id = SecureRandom.uuid
|
36
|
-
end
|
37
|
-
|
38
|
-
def translate_args(args)
|
39
|
-
args.transform_keys(&:to_s).transform_keys(&:to_underscore).transform_keys(&:to_sym)
|
40
|
-
rescue => e
|
41
|
-
raise 'Unable to parse args. Please pass correct arguments. Must be JSON'
|
42
|
-
end
|
43
|
-
|
44
|
-
def validate_request_params
|
45
|
-
# TODO
|
46
|
-
end
|
47
|
-
|
48
|
-
def request
|
49
|
-
@request
|
50
|
-
end
|
51
|
-
|
52
|
-
def client_log_timestamp
|
53
|
-
@client_log_timestamp
|
54
|
-
end
|
55
|
-
|
56
|
-
def view_id
|
57
|
-
@view_id
|
58
|
-
end
|
59
|
-
|
60
|
-
def user_id
|
61
|
-
return @user_id if @user_id
|
62
|
-
@user_id = request.dig(:user_info, :user_id)
|
63
|
-
@user_id ||= request.dig('user_info', 'user_id')
|
64
|
-
@user_id
|
65
|
-
end
|
66
|
-
|
67
|
-
def session_id
|
68
|
-
@session_id
|
69
|
-
end
|
70
|
-
|
71
|
-
# A list of the response Insertions. This client expects lists to be truncated
|
72
|
-
# already to request.paging.size. If not truncated, this client will truncate
|
73
|
-
# the list.
|
74
|
-
def insertion
|
75
|
-
@insertion
|
76
|
-
end
|
77
|
-
|
78
|
-
def log_user_id
|
79
|
-
return @log_user_id if @log_user_id
|
80
|
-
@log_user_id = request.dig(:user_info, :log_user_id)
|
81
|
-
@log_user_id ||= request.dig('user_info', 'log_user_id')
|
82
|
-
@log_user_id
|
83
|
-
end
|
84
|
-
|
85
|
-
# A way to turn off logging. Defaults to true.
|
86
|
-
def enabled?
|
87
|
-
@enabled
|
88
|
-
end
|
89
|
-
|
90
|
-
# Performs extra dev checks. Safer but slower. Defaults to true.
|
91
|
-
def perform_checks?
|
92
|
-
@perform_checks
|
93
|
-
end
|
94
|
-
|
95
|
-
# Default values to use on DeliveryRequests.
|
96
|
-
def default_request_values
|
97
|
-
@default_request_values
|
98
|
-
end
|
99
|
-
|
100
|
-
# Required as a dependency so clients can load reduce dependency on multiple
|
101
|
-
# uuid libraries.
|
102
|
-
def uuid
|
103
|
-
@uuid
|
104
|
-
end
|
105
|
-
|
106
|
-
# Defaults to 250ms
|
107
|
-
def delivery_timeout_millis
|
108
|
-
@delivery_timeout_millis
|
109
|
-
end
|
110
|
-
|
111
|
-
# Defaults to 3000ms
|
112
|
-
def metrics_timeout_millis
|
113
|
-
@metrics_timeout_millis
|
114
|
-
end
|
115
|
-
|
116
|
-
# For testing. Allows for easy mocking of the clock.
|
117
|
-
def now_millis
|
118
|
-
@now_millis
|
119
|
-
end
|
120
|
-
|
121
|
-
def only_Log
|
122
|
-
@only_Log
|
123
|
-
end
|
124
|
-
|
125
|
-
def full_insertion
|
126
|
-
@full_insertion
|
127
|
-
end
|
128
|
-
|
129
|
-
def user_info
|
130
|
-
{
|
131
|
-
user_id: user_id,
|
132
|
-
log_user_id: log_user_id
|
133
|
-
}
|
134
|
-
end
|
135
|
-
|
136
|
-
def timing
|
137
|
-
@timing = {
|
138
|
-
client_log_timestamp: client_log_timestamp
|
139
|
-
}
|
140
|
-
end
|
141
|
-
|
142
|
-
def request_id
|
143
|
-
@request_id
|
144
|
-
end
|
145
|
-
|
146
|
-
def log_request_params
|
147
|
-
{
|
148
|
-
user_info: user_info,
|
149
|
-
timing: timing,
|
150
|
-
request: [request],
|
151
|
-
insertion: compact_insertions
|
152
|
-
}
|
153
|
-
end
|
154
|
-
|
155
|
-
def request_params include_insertion: true
|
156
|
-
@request_params = {
|
157
|
-
user_info: user_info,
|
158
|
-
timing: timing,
|
159
|
-
request_id: request_id,
|
160
|
-
view_id: view_id,
|
161
|
-
session_id: session_id,
|
162
|
-
insertion: compact_insertions
|
163
|
-
}
|
164
|
-
@request_params.merge!({insertion: compact_insertions}) if include_insertion
|
165
|
-
@request_params
|
166
|
-
end
|
167
|
-
|
168
|
-
def compact_insertions
|
169
|
-
@compact_insertions = []
|
170
|
-
insertions_to_compact = full_insertion
|
171
|
-
paging = request[:paging] || {}
|
172
|
-
size = paging[:size]
|
173
|
-
unless size.nil? || size == 0
|
174
|
-
insertions_to_compact = insertions_to_compact[0..size-1]
|
175
|
-
end
|
176
|
-
from = paging[:from].to_i
|
177
|
-
insertions_to_compact.each_with_index do |insertion_obj, index|
|
178
|
-
# TODO - this does not look performant.
|
179
|
-
insertion_obj = insertion_obj.transform_keys{ |key| key.to_s.to_underscore.to_sym }
|
180
|
-
insertion_obj[:user_info] = user_info
|
181
|
-
insertion_obj[:timing] = timing
|
182
|
-
insertion_obj[:insertion_id] = SecureRandom.uuid # generate random UUID
|
183
|
-
insertion_obj[:request_id] = request_id
|
184
|
-
insertion_obj[:position] = from + index
|
185
|
-
@compact_insertions << insertion_obj
|
186
|
-
end
|
187
|
-
@compact_insertions
|
188
|
-
end
|
189
|
-
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
class String
|
196
|
-
# Ruby mutation methods have the expectation to return self if a mutation occurred, nil otherwise. (see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-gsub-21)
|
197
|
-
def to_underscore!
|
198
|
-
gsub!(/(.)([A-Z])/,'\1_\2')
|
199
|
-
downcase!
|
200
|
-
end
|
201
|
-
|
202
|
-
def to_underscore
|
203
|
-
dup.tap { |s| s.to_underscore! }
|
204
|
-
end
|
205
|
-
end
|
206
|
-
require 'securerandom'
|