promoted-ruby-client 0.1.15 → 0.1.19
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/.github/workflows/push-pull-requests_check.yml +16 -0
- data/Gemfile.lock +1 -1
- data/README.md +71 -5
- data/dev.md +1 -1
- data/lib/promoted/ruby/client.rb +62 -18
- data/lib/promoted/ruby/client/constants.rb +7 -0
- data/lib/promoted/ruby/client/errors.rb +6 -0
- data/lib/promoted/ruby/client/faraday_http_client.rb +10 -2
- data/lib/promoted/ruby/client/pager.rb +17 -7
- data/lib/promoted/ruby/client/request_builder.rb +50 -17
- data/lib/promoted/ruby/client/util.rb +26 -3
- data/lib/promoted/ruby/client/version.rb +1 -1
- 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: 6b8a39c8af4d21ae9987db29f0041e57d54de87da119b799e89091069a0bfbc2
         | 
| 4 | 
            +
              data.tar.gz: 7b3932706882a896f330b0bd0a3d05671111e80b97fac4403e355ae30e537750
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 3146dc2f31afa84fa2b332b1cdb5ca145e7aa51140ae14503a8ca8a4aebc96bb59c4313ee10884b85ba819c930f40f9dd88e7d88454a39a9748b42f3e7d827e5
         | 
| 7 | 
            +
              data.tar.gz: 903cb0dca7f83acfa605db3be223833ffebe78d0e530b1e98599ed2b8b15e2b5babffb1e40704789bab45f42f3d8372a46e2263733428da2e9fea90860003cb0
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            name: Pull-Requests Check
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            on: [push, pull_request]
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            jobs:
         | 
| 6 | 
            +
              Test:
         | 
| 7 | 
            +
                runs-on: ubuntu-latest
         | 
| 8 | 
            +
                steps:
         | 
| 9 | 
            +
                - uses: actions/checkout@v2
         | 
| 10 | 
            +
                - uses: ruby/setup-ruby@v1
         | 
| 11 | 
            +
                  with:
         | 
| 12 | 
            +
                    ruby-version: 2.5.1
         | 
| 13 | 
            +
                    bundler-cache: true
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                - name: Build and test with rspec
         | 
| 16 | 
            +
                  run: bundle exec rspec spec
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -17,6 +17,8 @@ More information at [http://www.promoted.ai](http://www.promoted.ai) | |
| 17 17 |  | 
| 18 18 | 
             
            ### [Faraday](https://github.com/lostisland/faraday)
         | 
| 19 19 | 
             
            HTTP client for calling Promoted.
         | 
| 20 | 
            +
            ### [Net::HTTP::Persistent](https://github.com/drbrain/net-http-persistent)
         | 
| 21 | 
            +
            Faraday binding (provides connection pool support)
         | 
| 20 22 | 
             
            ### [Concurrent Ruby](https://github.com/ruby-concurrency/concurrent-ruby)
         | 
| 21 23 | 
             
            Provides a thread pool for making shadow traffic requests to Delivery API in the background on a subset of calls to ```prepare_for_logging```
         | 
| 22 24 | 
             
            ## Creating a Client
         | 
| @@ -50,6 +52,7 @@ Name | Type | Description | |
| 50 52 | 
             
            ```:default_request_headers``` | Hash | Additional headers to send on the request beyond ```x-api-key```. Defaults to {}
         | 
| 51 53 | 
             
            ```:default_only_log``` | Boolean | If true, the ```deliver``` method will not direct traffic to Delivery API but rather return a request suitable for logging. Defaults to false.
         | 
| 52 54 | 
             
            ```: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.
         | 
| 55 | 
            +
            ```:warmup``` | Boolean | If true, the client will prime the `Net::HTTP::Persistent` connection pool on construction; this can make the first few calls to Promoted complete faster. Defaults to false.
         | 
| 53 56 |  | 
| 54 57 | 
             
            ## Data Types
         | 
| 55 58 |  | 
| @@ -91,7 +94,66 @@ Field Name | Type | Optional? | Description | |
| 91 94 | 
             
            ```:request_id``` | String | Yes | Generated by the SDK when needed (*do not set*)
         | 
| 92 95 | 
             
            ```:content_id``` | String | No | Identifier for the content to be shown, must be set.
         | 
| 93 96 | 
             
            ```:properties``` | Properties | Yes | Any additional custom properties to associate. For v1 integrations, it is fine not to fill in all the properties.
         | 
| 97 | 
            +
            ---
         | 
| 98 | 
            +
            ### Size
         | 
| 99 | 
            +
            User's screen dimensions.
         | 
| 100 | 
            +
            Field Name | Type | Optional? | Description
         | 
| 101 | 
            +
            ---------- | ---- | --------- | -----------
         | 
| 102 | 
            +
            ```:width``` | Integer | No | Screen width
         | 
| 103 | 
            +
            ```:height``` | Integer | No | Screen height
         | 
| 104 | 
            +
            ---
         | 
| 105 | 
            +
             | 
| 106 | 
            +
            ### ClientHints
         | 
| 107 | 
            +
            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 | 
            +
            Field Name | Type | Optional? | Description
         | 
| 109 | 
            +
            ---------- | ---- | --------- | -----------
         | 
| 110 | 
            +
            ```:is_mobile``` | Boolean | Yes | Mobile flag
         | 
| 111 | 
            +
            ```:brand``` | Array of ClientBrandHint | Yes |
         | 
| 112 | 
            +
            ```:architecture``` | String | Yes |
         | 
| 113 | 
            +
            ```:model``` | String | Yes |
         | 
| 114 | 
            +
            ```:platform``` | String | Yes |
         | 
| 115 | 
            +
            ```:platform_version``` | String | Yes |
         | 
| 116 | 
            +
            ```:ua_full_version``` | String | Yes |
         | 
| 117 | 
            +
             | 
| 118 | 
            +
            ---
         | 
| 119 | 
            +
            ### ClientBrandHint
         | 
| 120 | 
            +
            See https://raw.githubusercontent.com/snowplow/iglu-central/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0
         | 
| 121 | 
            +
            Field Name | Type | Optional? | Description
         | 
| 122 | 
            +
            ---------- | ---- | --------- | -----------
         | 
| 123 | 
            +
            ```:brand``` | String | Yes | Mobile flag
         | 
| 124 | 
            +
            ```:version``` | String | Yes |
         | 
| 94 125 |  | 
| 126 | 
            +
            ---
         | 
| 127 | 
            +
            ### Location
         | 
| 128 | 
            +
            Information about the user's location.
         | 
| 129 | 
            +
            Field Name | Type | Optional? | Description
         | 
| 130 | 
            +
            ---------- | ---- | --------- | -----------
         | 
| 131 | 
            +
            ```:latitude``` | Float | No | Location latitude
         | 
| 132 | 
            +
            ```:longitude``` | Float | No | Location longitude
         | 
| 133 | 
            +
            ```:accuracy_in_meters``` | Integer | Yes | Location accuracy if available
         | 
| 134 | 
            +
            ```:client_hints``` | ClientHints | Yes | HTTP client hints structure
         | 
| 135 | 
            +
            ---
         | 
| 136 | 
            +
             | 
| 137 | 
            +
            ### Browser
         | 
| 138 | 
            +
            Information about the user's browser.
         | 
| 139 | 
            +
            Field Name | Type | Optional? | Description
         | 
| 140 | 
            +
            ---------- | ---- | --------- | -----------
         | 
| 141 | 
            +
            ```:user_agent``` | String | Yes | Browser user agent string
         | 
| 142 | 
            +
            ```:viewport_size``` | Size | Yes | Size of the browser viewport
         | 
| 143 | 
            +
            ```:
         | 
| 144 | 
            +
            ---
         | 
| 145 | 
            +
            ### Device
         | 
| 146 | 
            +
            Information about the user's device.
         | 
| 147 | 
            +
            Field Name | Type | Optional? | Description
         | 
| 148 | 
            +
            ---------- | ---- | --------- | -----------
         | 
| 149 | 
            +
            ```:device_type``` | one of (`UNKNOWN_DEVICE_TYPE`, `DESKTOP`, `MOBILE`, `TABLET`) | Yes | Type of device
         | 
| 150 | 
            +
            ```:brand``` | String | Yes | "Apple, "google", Samsung", etc.
         | 
| 151 | 
            +
            ```:manufacturer``` | String | Yes | "Apple", "HTC", Motorola", "HUAWEI", etc.
         | 
| 152 | 
            +
            ```:identifier``` | String | Yes | Android: android.os.Build.MODEL; iOS: iPhoneXX,YY, etc.
         | 
| 153 | 
            +
            ```:screen``` | Screen | Yes | Screen dimensions
         | 
| 154 | 
            +
            ```:ip_address``` | String | Yes | Originating IP address
         | 
| 155 | 
            +
            ```:location``` | Location | Yes | Location information
         | 
| 156 | 
            +
            ```:browser``` | Browser | Yes | Browser information
         | 
| 95 157 | 
             
            ---
         | 
| 96 158 | 
             
            ### Paging
         | 
| 97 159 | 
             
            #### TODO
         | 
| @@ -105,6 +167,7 @@ Field Name | Type | Optional? | Description | |
| 105 167 | 
             
            ```:use_case``` | String | Yes | One of the use case values, i.e. 'FEED' (see [constants.rb](https://github.com/promotedai/promoted-ruby-client/blob/main/lib/promoted/ruby/client/constants.rb)).
         | 
| 106 168 | 
             
            ```:properties``` | Properties | Yes | Any additional custom properties to associate.
         | 
| 107 169 | 
             
            ```:paging``` | Paging | Yes | Paging parameters (see TODO)
         | 
| 170 | 
            +
            ```:device``` | Device | Yes | Device information (as available)
         | 
| 108 171 | 
             
            ---
         | 
| 109 172 | 
             
            ### MetricsRequest
         | 
| 110 173 | 
             
            Input to ```prepare_for_logging```
         | 
| @@ -139,6 +202,8 @@ Field Name | Type | Optional? | Description | |
| 139 202 | 
             
            ---------- | ---- | --------- | -----------
         | 
| 140 203 | 
             
            ```:insertion``` | [] of Insertion | No | The insertions, which are from Delivery API (when ```deliver``` was called, i.e. we weren't either only-log or part of an experiment) or the input insertions (when the other conditions don't hold).
         | 
| 141 204 | 
             
            ```:log_request``` | LogRequest | Yes | A message suitable for logging to Metrics API via ```send_log_request```. If the call to ```deliver``` was made (i.e. the request was not part of the CONTROL arm of an experiment or marked to only log), ```:log_request``` will not be set, as you can assume logging was performed on the server-side by Promoted.
         | 
| 205 | 
            +
            ```:client_request_id``` | String | Yes | Client-generated request id sent to Delivery API and may be useful for logging and debugging.
         | 
| 206 | 
            +
            ```:execution_server``` | one of 'API' or 'SDK' | Yes | Indicates if response insertions on a delivery request came from the API or the SDK.
         | 
| 142 207 | 
             
            ---
         | 
| 143 208 |  | 
| 144 209 | 
             
            ### PromotedClient
         | 
| @@ -216,12 +281,13 @@ metrics_request = { | |
| 216 281 | 
             
            }
         | 
| 217 282 |  | 
| 218 283 | 
             
            # OPTIONAL: You can pass a custom function to "compact" insertions before metrics logging.
         | 
| 219 | 
            -
            # Note that the PromotedClient has a class method helper,  | 
| 220 | 
            -
             | 
| 221 | 
            -
             | 
| 222 | 
            -
               | 
| 284 | 
            +
            # Note that the PromotedClient has a class method helper, remove_all_properties, that does
         | 
| 285 | 
            +
            # an implementation of this, returning nil to remove ALL properties.
         | 
| 286 | 
            +
            to_compact_metrics_properties_func = Proc.new do |properties|
         | 
| 287 | 
            +
              properties[:struct].delete(:active)
         | 
| 288 | 
            +
              properties
         | 
| 223 289 | 
             
            end
         | 
| 224 | 
            -
            # metrics_request[: | 
| 290 | 
            +
            # metrics_request[:to_compact_metrics_properties_func] = to_compact_metrics_properties_func
         | 
| 225 291 |  | 
| 226 292 | 
             
            # Create a client
         | 
| 227 293 | 
             
            client = Promoted::Ruby::Client::PromotedClient.new
         | 
    
        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.19.gem`
         | 
| 8 8 | 
             
            6. Update README with new version.
         | 
    
        data/lib/promoted/ruby/client.rb
    CHANGED
    
    | @@ -30,12 +30,10 @@ module Promoted | |
| 30 30 | 
             
                    end
         | 
| 31 31 |  | 
| 32 32 | 
             
                    ##            
         | 
| 33 | 
            -
                    # A common compact method implementation.
         | 
| 34 | 
            -
                    def self. | 
| 35 | 
            -
                      Proc.new do | | 
| 36 | 
            -
                         | 
| 37 | 
            -
                        insertion.delete(:properties)
         | 
| 38 | 
            -
                        insertion
         | 
| 33 | 
            +
                    # A common compact properties method implementation.
         | 
| 34 | 
            +
                    def self.remove_all_properties
         | 
| 35 | 
            +
                      Proc.new do |properties|
         | 
| 36 | 
            +
                        nil
         | 
| 39 37 | 
             
                      end
         | 
| 40 38 | 
             
                    end
         | 
| 41 39 |  | 
| @@ -73,7 +71,7 @@ module Promoted | |
| 73 71 | 
             
                      @delivery_timeout_millis = params[:delivery_timeout_millis] || DEFAULT_DELIVERY_TIMEOUT_MILLIS
         | 
| 74 72 | 
             
                      @metrics_timeout_millis  = params[:metrics_timeout_millis] || DEFAULT_METRICS_TIMEOUT_MILLIS
         | 
| 75 73 |  | 
| 76 | 
            -
                      @http_client = FaradayHTTPClient.new
         | 
| 74 | 
            +
                      @http_client = FaradayHTTPClient.new(@logger)
         | 
| 77 75 | 
             
                      @validator = Promoted::Ruby::Client::Validator.new
         | 
| 78 76 |  | 
| 79 77 | 
             
                      @async_shadow_traffic = true
         | 
| @@ -97,6 +95,10 @@ module Promoted | |
| 97 95 | 
             
                      if params[:enabled] != nil
         | 
| 98 96 | 
             
                        @enabled = params[:enabled] || false
         | 
| 99 97 | 
             
                      end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                      if params[:warmup]
         | 
| 100 | 
            +
                        do_warmup
         | 
| 101 | 
            +
                      end
         | 
| 100 102 | 
             
                    end
         | 
| 101 103 |  | 
| 102 104 | 
             
                    ##
         | 
| @@ -109,9 +111,9 @@ module Promoted | |
| 109 111 | 
             
                    end
         | 
| 110 112 |  | 
| 111 113 | 
             
                    ##
         | 
| 112 | 
            -
                    # Make a delivery request.
         | 
| 114 | 
            +
                    # Make a delivery request. If @perform_checks is set, input validation will occur and possibly raise errors.
         | 
| 113 115 | 
             
                    def deliver args, headers={}
         | 
| 114 | 
            -
                      args = Promoted::Ruby::Client::Util. | 
| 116 | 
            +
                      args = Promoted::Ruby::Client::Util.translate_hash(args)
         | 
| 115 117 |  | 
| 116 118 | 
             
                      # Respect the enabled state
         | 
| 117 119 | 
             
                      if !@enabled
         | 
| @@ -124,7 +126,16 @@ module Promoted | |
| 124 126 | 
             
                      delivery_request_builder = RequestBuilder.new
         | 
| 125 127 | 
             
                      delivery_request_builder.set_request_params(args)
         | 
| 126 128 |  | 
| 127 | 
            -
                       | 
| 129 | 
            +
                      # perform_checks raises errors.
         | 
| 130 | 
            +
                      if @perform_checks
         | 
| 131 | 
            +
                        perform_common_checks!(args)
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                        if args[:insertion_page_type] == Promoted::Ruby::Client::INSERTION_PAGING_TYPE['PRE_PAGED'] then
         | 
| 134 | 
            +
                          err = DeliveryInsertionPageType.new
         | 
| 135 | 
            +
                          @logger.error(err) if @logger
         | 
| 136 | 
            +
                          raise err
         | 
| 137 | 
            +
                        end
         | 
| 138 | 
            +
                      end
         | 
| 128 139 |  | 
| 129 140 | 
             
                      delivery_request_builder.ensure_client_timestamp
         | 
| 130 141 |  | 
| @@ -135,10 +146,15 @@ module Promoted | |
| 135 146 | 
             
                      only_log = delivery_request_builder.only_log != nil ? delivery_request_builder.only_log : @default_only_log
         | 
| 136 147 | 
             
                      deliver_err = false
         | 
| 137 148 |  | 
| 138 | 
            -
                       | 
| 149 | 
            +
                      begin
         | 
| 150 | 
            +
                        @pager.validate_paging(delivery_request_builder.full_insertion, delivery_request_builder.request[:paging])
         | 
| 151 | 
            +
                      rescue InvalidPagingError => err
         | 
| 139 152 | 
             
                        # Invalid input, log and do SDK-side delivery.
         | 
| 140 | 
            -
                        @logger.warn( | 
| 141 | 
            -
                         | 
| 153 | 
            +
                        @logger.warn(err) if @logger
         | 
| 154 | 
            +
                        return {
         | 
| 155 | 
            +
                          insertion: err.default_insertions_page
         | 
| 156 | 
            +
                          # No log request returned when no response insertions due to invalid paging
         | 
| 157 | 
            +
                        }
         | 
| 142 158 | 
             
                      end
         | 
| 143 159 |  | 
| 144 160 | 
             
                      if !only_log
         | 
| @@ -194,7 +210,9 @@ module Promoted | |
| 194 210 |  | 
| 195 211 | 
             
                      client_response = {
         | 
| 196 212 | 
             
                        insertion: response_insertions,
         | 
| 197 | 
            -
                        log_request: log_req
         | 
| 213 | 
            +
                        log_request: log_req,
         | 
| 214 | 
            +
                        execution_server: insertions_from_delivery ? Promoted::Ruby::Client::EXECUTION_SERVER['API'] : Promoted::Ruby::Client::EXECUTION_SERVER['SDK'],
         | 
| 215 | 
            +
                        client_request_id: delivery_request_builder.client_request_id
         | 
| 198 216 | 
             
                      }
         | 
| 199 217 | 
             
                      return client_response
         | 
| 200 218 | 
             
                    end
         | 
| @@ -203,7 +221,7 @@ module Promoted | |
| 203 221 | 
             
                    # Generate a log request for a subsequent call to send_log_request
         | 
| 204 222 | 
             
                    # or for logging via alternative means.
         | 
| 205 223 | 
             
                    def prepare_for_logging args, headers={}
         | 
| 206 | 
            -
                      args = Promoted::Ruby::Client::Util. | 
| 224 | 
            +
                      args = Promoted::Ruby::Client::Util.translate_hash(args)
         | 
| 207 225 |  | 
| 208 226 | 
             
                      if !@enabled
         | 
| 209 227 | 
             
                        return {
         | 
| @@ -248,6 +266,22 @@ module Promoted | |
| 248 266 |  | 
| 249 267 | 
             
                    private
         | 
| 250 268 |  | 
| 269 | 
            +
                    def do_warmup
         | 
| 270 | 
            +
                      if !@delivery_endpoint
         | 
| 271 | 
            +
                        # Warmup only supported when delivery is enabled.
         | 
| 272 | 
            +
                        return
         | 
| 273 | 
            +
                      end
         | 
| 274 | 
            +
             | 
| 275 | 
            +
                      warmup_url = @delivery_endpoint.reverse.sub("/deliver".reverse, "/healthz".reverse).reverse
         | 
| 276 | 
            +
                      @logger.info("Warming up at #{warmup_url}") if @logger
         | 
| 277 | 
            +
                      1.upto(20) do
         | 
| 278 | 
            +
                        resp = @http_client.get(warmup_url)
         | 
| 279 | 
            +
                        if resp != "ok"
         | 
| 280 | 
            +
                          @logger.warn("Got a failure warming up") if @logger
         | 
| 281 | 
            +
                        end
         | 
| 282 | 
            +
                      end
         | 
| 283 | 
            +
                    end
         | 
| 284 | 
            +
             | 
| 251 285 | 
             
                    def send_request payload, endpoint, timeout_millis, api_key, headers={}, send_async=false
         | 
| 252 286 | 
             
                      resp = nil
         | 
| 253 287 |  | 
| @@ -300,13 +334,22 @@ module Promoted | |
| 300 334 | 
             
                      delivery_request_builder = RequestBuilder.new
         | 
| 301 335 | 
             
                      delivery_request_builder.set_request_params args
         | 
| 302 336 |  | 
| 303 | 
            -
                      delivery_request_params = delivery_request_builder.delivery_request_params | 
| 337 | 
            +
                      delivery_request_params = delivery_request_builder.delivery_request_params
         | 
| 304 338 | 
             
                      delivery_request_params[:client_info][:traffic_type] = Promoted::Ruby::Client::TRAFFIC_TYPE['SHADOW']
         | 
| 305 339 |  | 
| 340 | 
            +
                      begin
         | 
| 341 | 
            +
                        @pager.validate_paging(delivery_request_builder.full_insertion, delivery_request_builder.request[:paging])
         | 
| 342 | 
            +
                      rescue InvalidPagingError => err
         | 
| 343 | 
            +
                        # Invalid input, log and skip.
         | 
| 344 | 
            +
                        @logger.warn("Shadow traffic call failed with invalid paging #{err}") if @logger
         | 
| 345 | 
            +
                        return
         | 
| 346 | 
            +
                      end
         | 
| 347 | 
            +
             | 
| 306 348 | 
             
                      # Call Delivery API and log/ignore errors.
         | 
| 307 349 | 
             
                      start_time = Time.now
         | 
| 350 | 
            +
                      response = nil
         | 
| 308 351 | 
             
                      begin
         | 
| 309 | 
            -
                        send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, @delivery_api_key, headers, @async_shadow_traffic)
         | 
| 352 | 
            +
                        response = send_request(delivery_request_params, @delivery_endpoint, @delivery_timeout_millis, @delivery_api_key, headers, @async_shadow_traffic)
         | 
| 310 353 | 
             
                      rescue StandardError => err
         | 
| 311 354 | 
             
                        @logger.warn("Shadow traffic call failed with #{err}") if @logger
         | 
| 312 355 | 
             
                        return
         | 
| @@ -314,7 +357,8 @@ module Promoted | |
| 314 357 |  | 
| 315 358 | 
             
                      if !@async_shadow_traffic
         | 
| 316 359 | 
             
                        ellapsed_time = Time.now - start_time
         | 
| 317 | 
            -
                         | 
| 360 | 
            +
                        insertions = response ? response[:insertion] : []
         | 
| 361 | 
            +
                        @logger.info("Shadow traffic call completed in #{ellapsed_time.to_f * 1000} ms with #{insertions.length} insertions") if @logger
         | 
| 318 362 | 
             
                      end
         | 
| 319 363 | 
             
                    end
         | 
| 320 364 |  | 
| @@ -32,6 +32,13 @@ module Promoted | |
| 32 32 | 
             
                  CLIENT_TYPE = {'UNKNOWN_REQUEST_CLIENT' => 'UNKNOWN_REQUEST_CLIENT',
         | 
| 33 33 | 
             
                                 'PLATFORM_SERVER' => 'PLATFORM_SERVER',
         | 
| 34 34 | 
             
                                 'PLATFORM_CLIENT' => 'PLATFORM_CLIENT'}
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  EXECUTION_SERVER = {'API' => 'API', 'SDK' => 'SDK'}
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  DEVICE_TYPE = {'UNKNOWN_DEVICE_TYPE' => 'UNKNOWN_DEVICE_TYPE',
         | 
| 39 | 
            +
                                 'DESKTOP' => 'DESKTOP',
         | 
| 40 | 
            +
                                 'MOBILE' => 'MOBILE',
         | 
| 41 | 
            +
                                 'TABLET' => 'TABLET'}
         | 
| 35 42 | 
             
                end
         | 
| 36 43 | 
             
              end
         | 
| 37 44 | 
             
            end
         | 
| @@ -1,15 +1,19 @@ | |
| 1 1 | 
             
            require 'faraday'
         | 
| 2 2 | 
             
            require 'faraday_middleware'
         | 
| 3 | 
            +
            require 'promoted/ruby/client/util'
         | 
| 3 4 |  | 
| 4 5 | 
             
            module Promoted
         | 
| 5 6 | 
             
                module Ruby
         | 
| 6 7 | 
             
                  module Client
         | 
| 7 8 | 
             
                    class FaradayHTTPClient
         | 
| 8 9 |  | 
| 9 | 
            -
                        def initialize
         | 
| 10 | 
            +
                        def initialize(logger = nil)
         | 
| 10 11 | 
             
                            @conn = Faraday.new do |f|
         | 
| 11 12 | 
             
                                f.request :json
         | 
| 12 13 | 
             
                                f.request :retry, max: 3
         | 
| 14 | 
            +
                                if logger
         | 
| 15 | 
            +
                                  f.response :logger, logger, { headers: false, bodies: true, log_level: :debug }
         | 
| 16 | 
            +
                                end
         | 
| 13 17 | 
             
                                f.use Faraday::Response::RaiseError # raises on 4xx and 5xx responses
         | 
| 14 18 | 
             
                                f.adapter :net_http_persistent
         | 
| 15 19 | 
             
                            end
         | 
| @@ -25,11 +29,15 @@ module Promoted | |
| 25 29 |  | 
| 26 30 | 
             
                              norm_headers = response.headers.transform_keys(&:downcase)
         | 
| 27 31 | 
             
                              if norm_headers["content-type"] != nil && norm_headers["content-type"].start_with?("application/json")
         | 
| 28 | 
            -
                                JSON.parse(response.body | 
| 32 | 
            +
                                Promoted::Ruby::Client::Util.translate_hash(JSON.parse(response.body))
         | 
| 29 33 | 
             
                              else
         | 
| 30 34 | 
             
                                response.body
         | 
| 31 35 | 
             
                              end
         | 
| 32 36 | 
             
                        end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                        def get(endpoint)
         | 
| 39 | 
            +
                          @conn.get(endpoint).body
         | 
| 40 | 
            +
                        end
         | 
| 33 41 | 
             
                    end
         | 
| 34 42 | 
             
                  end
         | 
| 35 43 | 
             
                end
         | 
| @@ -1,18 +1,28 @@ | |
| 1 1 | 
             
            module Promoted
         | 
| 2 2 | 
             
                module Ruby
         | 
| 3 3 | 
             
                  module Client
         | 
| 4 | 
            +
                    class InvalidPagingError < StandardError
         | 
| 5 | 
            +
                      attr_reader :default_insertions_page
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                      def initialize(message, default_insertions_page)
         | 
| 8 | 
            +
                        super(message)
         | 
| 9 | 
            +
                        @default_insertions_page = default_insertions_page
         | 
| 10 | 
            +
                      end
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                    
         | 
| 4 13 | 
             
                    class Pager
         | 
| 5 14 | 
             
                        def validate_paging (insertions, paging)
         | 
| 6 | 
            -
                          if paging && paging[:offset]
         | 
| 7 | 
            -
                             | 
| 15 | 
            +
                          if paging && paging[:offset] && paging[:offset] >= insertions.length
         | 
| 16 | 
            +
                            raise InvalidPagingError.new("Invalid page offset (insertion size #{insertions.length}, offset #{paging[:offset]})", [])
         | 
| 8 17 | 
             
                          end
         | 
| 9 | 
            -
                          return true          
         | 
| 10 18 | 
             
                        end
         | 
| 11 19 |  | 
| 12 20 | 
             
                        def apply_paging (insertions, insertion_page_type, paging = nil)
         | 
| 13 | 
            -
                           | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 21 | 
            +
                          begin
         | 
| 22 | 
            +
                            validate_paging(insertions, paging)
         | 
| 23 | 
            +
                          rescue InvalidPagingError => err
         | 
| 24 | 
            +
                            # This is invalid input, stop it before it goes to the server.
         | 
| 25 | 
            +
                            return err.default_insertions_page
         | 
| 16 26 | 
             
                          end
         | 
| 17 27 |  | 
| 18 28 | 
             
                          if !paging
         | 
| @@ -54,4 +64,4 @@ module Promoted | |
| 54 64 | 
             
                    end
         | 
| 55 65 | 
             
                  end
         | 
| 56 66 | 
             
               end
         | 
| 57 | 
            -
            end
         | 
| 67 | 
            +
            end
         | 
| @@ -2,9 +2,9 @@ module Promoted | |
| 2 2 | 
             
              module Ruby
         | 
| 3 3 | 
             
                module Client
         | 
| 4 4 | 
             
                  class RequestBuilder
         | 
| 5 | 
            -
                    attr_reader   :session_id, :only_log, :experiment, :client_info,
         | 
| 6 | 
            -
                                  :view_id, :insertion, : | 
| 7 | 
            -
                                  :request_id, :full_insertion, :use_case, :request, : | 
| 5 | 
            +
                    attr_reader   :session_id, :only_log, :experiment, :client_info, :device,
         | 
| 6 | 
            +
                                  :view_id, :insertion, :to_compact_delivery_properties_func,
         | 
| 7 | 
            +
                                  :request_id, :full_insertion, :use_case, :request, :to_compact_metrics_properties_func
         | 
| 8 8 |  | 
| 9 9 | 
             
                    attr_accessor :timing, :user_info, :platform_id
         | 
| 10 10 |  | 
| @@ -24,13 +24,14 @@ module Promoted | |
| 24 24 | 
             
                      @session_id              = request[:session_id]
         | 
| 25 25 | 
             
                      @platform_id             = request[:platform_id]
         | 
| 26 26 | 
             
                      @client_info             = request[:client_info] || {}
         | 
| 27 | 
            +
                      @device                  = request[:device] || {}
         | 
| 27 28 | 
             
                      @view_id                 = request[:view_id]
         | 
| 28 29 | 
             
                      @use_case                = Promoted::Ruby::Client::USE_CASES[request[:use_case]] || Promoted::Ruby::Client::USE_CASES['UNKNOWN_USE_CASE']
         | 
| 29 30 | 
             
                      @full_insertion          = args[:full_insertion]
         | 
| 30 31 | 
             
                      @user_info               = request[:user_info] || { :user_id => nil, :log_user_id => nil}
         | 
| 31 32 | 
             
                      @timing                  = request[:timing] || { :client_log_timestamp => Time.now.to_i }
         | 
| 32 | 
            -
                      @ | 
| 33 | 
            -
                      @ | 
| 33 | 
            +
                      @to_compact_metrics_properties_func       = args[:to_compact_metrics_properties_func]
         | 
| 34 | 
            +
                      @to_compact_delivery_properties_func      = args[:to_compact_delivery_properties_func]
         | 
| 34 35 |  | 
| 35 36 | 
             
                      # If the user didn't create a client request id, we do it for them.
         | 
| 36 37 | 
             
                      request[:client_request_id] = request[:client_request_id] || @id_generator.newID
         | 
| @@ -52,11 +53,12 @@ module Promoted | |
| 52 53 | 
             
                    end
         | 
| 53 54 |  | 
| 54 55 | 
             
                    # Only used in delivery
         | 
| 55 | 
            -
                    def delivery_request_params | 
| 56 | 
            +
                    def delivery_request_params
         | 
| 56 57 | 
             
                      params = {
         | 
| 57 58 | 
             
                        user_info: user_info,
         | 
| 58 59 | 
             
                        timing: timing,
         | 
| 59 60 | 
             
                        client_info: @client_info.merge({ :client_type => Promoted::Ruby::Client::CLIENT_TYPE['PLATFORM_SERVER'] }),
         | 
| 61 | 
            +
                        device: @device,
         | 
| 60 62 | 
             
                        platform_id: @platform_id,
         | 
| 61 63 | 
             
                        view_id: @view_id,
         | 
| 62 64 | 
             
                        session_id: @session_id,
         | 
| @@ -64,9 +66,9 @@ module Promoted | |
| 64 66 | 
             
                        search_query: request[:search_query],
         | 
| 65 67 | 
             
                        properties: request[:properties],
         | 
| 66 68 | 
             
                        paging: request[:paging],
         | 
| 67 | 
            -
                        client_request_id:  | 
| 69 | 
            +
                        client_request_id: client_request_id
         | 
| 68 70 | 
             
                      }
         | 
| 69 | 
            -
                      params[:insertion] =  | 
| 71 | 
            +
                      params[:insertion] = insertions_with_compact_props(@to_compact_delivery_properties_func)
         | 
| 70 72 |  | 
| 71 73 | 
             
                      params.clean!
         | 
| 72 74 | 
             
                    end
         | 
| @@ -80,7 +82,10 @@ module Promoted | |
| 80 82 | 
             
                      end
         | 
| 81 83 |  | 
| 82 84 | 
             
                      props = @full_insertion.each_with_object({}) do |insertion, hash|
         | 
| 83 | 
            -
                         | 
| 85 | 
            +
                        if insertion.has_key?(:properties)
         | 
| 86 | 
            +
                          # Don't add nil properties to response insertions.
         | 
| 87 | 
            +
                          hash[insertion[:content_id]] = insertion[:properties]
         | 
| 88 | 
            +
                        end
         | 
| 84 89 | 
             
                      end
         | 
| 85 90 |  | 
| 86 91 | 
             
                      filled_in_copy = []
         | 
| @@ -99,10 +104,14 @@ module Promoted | |
| 99 104 | 
             
                      params = {
         | 
| 100 105 | 
             
                        user_info: user_info,
         | 
| 101 106 | 
             
                        timing: timing,
         | 
| 102 | 
            -
                         | 
| 103 | 
            -
                         | 
| 107 | 
            +
                        client_info: @client_info,
         | 
| 108 | 
            +
                        device: @device
         | 
| 104 109 | 
             
                      }
         | 
| 105 110 |  | 
| 111 | 
            +
                      if @experiment
         | 
| 112 | 
            +
                        params[:cohort_membership] = [@experiment]
         | 
| 113 | 
            +
                      end
         | 
| 114 | 
            +
             | 
| 106 115 | 
             
                      # Log request allows for multiple requests but here we only send one.
         | 
| 107 116 | 
             
                      if include_request
         | 
| 108 117 | 
             
                        request[:request_id] = request[:request_id] || @id_generator.newID
         | 
| @@ -110,18 +119,38 @@ module Promoted | |
| 110 119 | 
             
                      end
         | 
| 111 120 |  | 
| 112 121 | 
             
                      if include_insertions
         | 
| 113 | 
            -
                        params[:insertion] =  | 
| 122 | 
            +
                        params[:insertion] = compact_metrics_properties if include_insertions
         | 
| 114 123 | 
             
                        add_missing_ids_on_insertions! request, params[:insertion]
         | 
| 115 124 | 
             
                      end
         | 
| 116 125 |  | 
| 117 126 | 
             
                      params.clean!
         | 
| 118 127 | 
             
                    end
         | 
| 119 128 |  | 
| 120 | 
            -
                    def  | 
| 121 | 
            -
                      if  | 
| 129 | 
            +
                    def compact_one_insertion(insertion, compact_func)
         | 
| 130 | 
            +
                      return insertion if (!compact_func || !insertion[:properties])
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                      # Only need a copy if there are properties to compact.
         | 
| 133 | 
            +
                      compact_insertion = insertion.dup
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                      # Let the custom function work with a deep copy of the properties.
         | 
| 136 | 
            +
                      # There's really no way to work with a shallow copy and still be able
         | 
| 137 | 
            +
                      # to restore the correct insertion properties after a call to delivery.
         | 
| 138 | 
            +
                      new_props =  Marshal.load(Marshal.dump(insertion[:properties]))
         | 
| 139 | 
            +
                      compact_insertion[:properties] = compact_func.call(new_props)
         | 
| 140 | 
            +
                      compact_insertion.clean!
         | 
| 141 | 
            +
                      return compact_insertion
         | 
| 142 | 
            +
                    end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                    def insertions_with_compact_props(compact_func)
         | 
| 145 | 
            +
                      if !compact_func
         | 
| 146 | 
            +
                        # Nothing to do, avoid copying the whole array.
         | 
| 122 147 | 
             
                        full_insertion
         | 
| 123 148 | 
             
                      else
         | 
| 124 | 
            -
                         | 
| 149 | 
            +
                        compact_insertions = Array.new(full_insertion.length)
         | 
| 150 | 
            +
                        full_insertion.each_with_index {|insertion, index|
         | 
| 151 | 
            +
                          compact_insertions[index] = compact_one_insertion(insertion, compact_func)
         | 
| 152 | 
            +
                        }
         | 
| 153 | 
            +
                        compact_insertions
         | 
| 125 154 | 
             
                      end
         | 
| 126 155 | 
             
                    end
         | 
| 127 156 |  | 
| @@ -132,7 +161,7 @@ module Promoted | |
| 132 161 | 
             
                    end
         | 
| 133 162 |  | 
| 134 163 | 
             
                    # TODO: This looks overly complicated.
         | 
| 135 | 
            -
                    def  | 
| 164 | 
            +
                    def compact_metrics_properties
         | 
| 136 165 | 
             
                      @insertion            = [] # insertion should be set according to the compact insertion
         | 
| 137 166 | 
             
                      paging                = request[:paging] || {}
         | 
| 138 167 | 
             
                      size                  = paging[:size] ? paging[:size].to_i : 0
         | 
| @@ -152,12 +181,16 @@ module Promoted | |
| 152 181 | 
             
                        insertion_obj[:insertion_id] = @id_generator.newID
         | 
| 153 182 | 
             
                        insertion_obj[:request_id]   = request_id
         | 
| 154 183 | 
             
                        insertion_obj[:position]     = offset + index
         | 
| 155 | 
            -
                        insertion_obj                =  | 
| 184 | 
            +
                        insertion_obj                = compact_one_insertion(insertion_obj, @to_compact_metrics_properties_func)
         | 
| 156 185 | 
             
                        @insertion << insertion_obj.clean!
         | 
| 157 186 | 
             
                      end
         | 
| 158 187 | 
             
                      @insertion
         | 
| 159 188 | 
             
                    end
         | 
| 160 189 |  | 
| 190 | 
            +
                    def client_request_id
         | 
| 191 | 
            +
                      request[:client_request_id]
         | 
| 192 | 
            +
                    end
         | 
| 193 | 
            +
             | 
| 161 194 | 
             
                    private
         | 
| 162 195 |  | 
| 163 196 | 
             
                    def add_missing_ids_on_insertions! request, insertions
         | 
| @@ -2,16 +2,39 @@ module Promoted | |
| 2 2 | 
             
                module Ruby
         | 
| 3 3 | 
             
                  module Client
         | 
| 4 4 | 
             
                    module Util
         | 
| 5 | 
            -
                        def self. | 
| 5 | 
            +
                        def self.translate_array(arr)
         | 
| 6 | 
            +
                          sym_arr = Array.new(arr.length)
         | 
| 7 | 
            +
                          arr.each_with_index do |v, i|
         | 
| 8 | 
            +
                            new_v = v
         | 
| 9 | 
            +
                            case v
         | 
| 10 | 
            +
                            when Hash
         | 
| 11 | 
            +
                              new_v = translate_hash(v)
         | 
| 12 | 
            +
                            when Array
         | 
| 13 | 
            +
                              new_v = translate_array(v)
         | 
| 14 | 
            +
                            end
         | 
| 15 | 
            +
                            sym_arr[i] = new_v
         | 
| 16 | 
            +
                          end
         | 
| 17 | 
            +
                          sym_arr
         | 
| 18 | 
            +
                        end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                        def self.translate_hash(args)
         | 
| 6 21 | 
             
                          sym_hash = {}
         | 
| 7 22 | 
             
                          args.each do |k, v|
         | 
| 8 | 
            -
                             | 
| 23 | 
            +
                            new_key = k.to_s.to_underscore.to_sym
         | 
| 24 | 
            +
                            case v
         | 
| 25 | 
            +
                            when Hash
         | 
| 26 | 
            +
                              sym_hash[new_key] = translate_hash(v)
         | 
| 27 | 
            +
                            when Array
         | 
| 28 | 
            +
                              sym_hash[new_key] = translate_array(v)
         | 
| 29 | 
            +
                            else
         | 
| 30 | 
            +
                              sym_hash[new_key] = v
         | 
| 31 | 
            +
                            end
         | 
| 9 32 | 
             
                          end
         | 
| 10 33 | 
             
                          sym_hash
         | 
| 11 34 | 
             
                          rescue => e
         | 
| 12 35 | 
             
                            raise 'Unable to parse args. Please pass correct arguments. Must be JSON'
         | 
| 13 36 | 
             
                          end      
         | 
| 14 | 
            -
             | 
| 37 | 
            +
                        end
         | 
| 15 38 | 
             
                  end
         | 
| 16 39 | 
             
              end
         | 
| 17 40 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: promoted-ruby-client
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.19
         | 
| 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-08-11 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: faraday
         | 
| @@ -123,6 +123,7 @@ extensions: [] | |
| 123 123 | 
             
            extra_rdoc_files:
         | 
| 124 124 | 
             
            - README.md
         | 
| 125 125 | 
             
            files:
         | 
| 126 | 
            +
            - ".github/workflows/push-pull-requests_check.yml"
         | 
| 126 127 | 
             
            - ".gitignore"
         | 
| 127 128 | 
             
            - Gemfile
         | 
| 128 129 | 
             
            - Gemfile.lock
         | 
| @@ -167,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 167 168 | 
             
                - !ruby/object:Gem::Version
         | 
| 168 169 | 
             
                  version: '0'
         | 
| 169 170 | 
             
            requirements: []
         | 
| 170 | 
            -
            rubygems_version: 3. | 
| 171 | 
            +
            rubygems_version: 3.2.24
         | 
| 171 172 | 
             
            signing_key: 
         | 
| 172 173 | 
             
            specification_version: 4
         | 
| 173 174 | 
             
            summary: A Ruby Client to contact Promoted APIs.
         |