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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4494d298c8907ddb04588f1430d93ff2e83dd1978df0cfcb419df63db0cf302a
4
- data.tar.gz: ab8105ad6c2dc8c8fc6992f5a312b240dcda3f13624fa3258fc769685ba9bdf3
3
+ metadata.gz: e0f6d46482500c45c759f6b2965ae13a76aea2b3b0d18c069e31055fe985146f
4
+ data.tar.gz: 7527ed13e8306bc4f9b53fd1add1a12c478c261be30b0b0dc50d16e146f88f10
5
5
  SHA512:
6
- metadata.gz: 97a95326b32122f9d81292881c139cd0902f98c92dded7015d8ab7483c83b322f1e668f999da981a59e7c686523c74bf2ece6443e3dab26307cf44470f6a0cb9
7
- data.tar.gz: 703316787c03c1e30eec766fda2f2247d357b8ad4e8c9b99765421a7d0fc119f998ba2cbb342044e229ad8067672a174a2f31f8cf62ce9206f545ba005fc1a11
6
+ metadata.gz: fa5105d3d057f9e949dca0fa46a47c5d701e8dabec37f1d64619969827a580994245a6a83b5fbce399be06201f29d8758c2829e1d76b066bee8a4e8ea62fc892
7
+ data.tar.gz: c6d29aeb9721ad09de063c21afd529495ff8c7eb3422886010124e85fc654fe711efeeb35d5b1a873f4f0ca4d56c064a119069f698f080a203daf797309ad8a9
data/.gitignore CHANGED
@@ -55,3 +55,6 @@ build-iPhoneSimulator/
55
55
 
56
56
  # Used by RuboCop. Remote config files pulled in from inherit_from directive.
57
57
  # .rubocop-https?--*
58
+
59
+ # IDE
60
+ .vscode/
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 'byebug'
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.0)
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
- faraday (1.4.1)
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
- log_request = Promoted::Ruby::Client.prepare_for_logging(input)
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
- `fullInsertion` - 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
+ `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.from` to be passed in for the number of items deep that the page is.
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
- fullInsertion: to_insertions(products)
133
+ full_insertion: to_insertions(products)
96
134
  }
97
135
 
98
- log_request = Promoted::Ruby::Client.prepare_for_logging(request_input)
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\":{\"from\":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}]}"
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
- "fullInsertion"=>to_insertions(products)
181
+ "full_insertion"=>to_insertions(products)
143
182
  }
144
183
  ```
145
184
 
@@ -155,7 +194,7 @@ input = {
155
194
  }
156
195
  }
157
196
  },
158
- "fullInsertion"=>[
197
+ "full_insertion"=>[
159
198
  {
160
199
  "contentId"=>"123",
161
200
  "properties"=>{
data/bin/setup CHANGED
@@ -3,6 +3,6 @@ set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
- bundle install
6
+ bundle install --without development test
7
7
 
8
8
  # Do any other automated setup that you need to do here
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.
@@ -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
- class Error < StandardError; end
9
- attr_accessor :options
10
- BASE_URL = "http://wh12.lvh.me:3000"
11
- DELIVERY_ENDPOINT = "#{BASE_URL}/deliver"
12
- LOGGING_ENDPOINT = "#{BASE_URL}/log_request"
13
-
14
- def self.send_request payload, endpoint=nil
15
- endpoint ||= BASE_URL
16
- response = Faraday.post(endpoint) do |req|
17
- req.headers['Content-Type'] = 'application/json'
18
- req.body = payload.to_json
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
- def deliver payload={}
24
- # TODO
25
- end
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
- def self.prepare_for_logging args
34
- options.set_request_params(args)
35
- if options.perform_checks
36
- Promoted::Ruby::Client::Settings.check_that_log_ids_not_set(args)
37
- pre_delivery_fillin_fields
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
- def self.pre_delivery_fillin_fields
43
- if !options.timing[:client_log_timestamp].present?
44
- options.client_log_timestamp = Time.now.to_i
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
- def self.options
49
- @options ||= Options.new
50
- end
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
- def self.promoted_client_impl params={}
53
- # Dummy implementation
54
- # TODO will implement it in details
55
- perform_checks = params[:perform_checks] || true
56
- only_Log = params[:only_Log] || false
57
- uuid = params[:uuid]
58
- now_millis = params[:now_millis] || Time.now.to_i
59
- delivery_timeout_millis = params[:delivery_timeout_millis] || DEFAULT_DELIVERY_TIMEOUT_MILLIS
60
- metrics_timeout_millis = params[:metrics_timeout_millis] || DEFAULT_METRICS_TIMEOUT_MILLIS
61
- should_apply_treatment = params[:should_apply_treatment] || false
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/options"
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 fullInsertion.'
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"
@@ -0,0 +1,13 @@
1
+ module Promoted
2
+ module Ruby
3
+ module Client
4
+ class Sampler
5
+ def sample_random? (threshold)
6
+ true if threshold >= 1.0
7
+ false if threshold <= 0.0
8
+ threshold >= rand()
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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("request", "request_id")
8
- raise RequestInsertionError if options_hash["insertion"]
7
+ raise RequestError if options_hash.dig(:request, :request_id)
8
+ raise RequestInsertionError if options_hash[:insertion]
9
9
 
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"]
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
@@ -1,7 +1,7 @@
1
1
  module Promoted
2
2
  module Ruby
3
3
  module Client
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
6
6
  end
7
7
  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 = ["danbosnichill"]
10
- spec.email = ["dhill@promoted.ai"]
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.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
- - danbosnichill
7
+ - scottmcmaster
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-06-10 00:00:00.000000000 Z
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
- - dhill@promoted.ai
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/options.rb
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'