The project is in a healthy, maintained state
This is primarily intended to be used when logging Requests and Insertions on a backend server.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
 Dependencies

Development

~> 2.2, >= 2.2.24
~> 10.0
~> 3.0

Runtime

 Project Readme

promoted-ruby-client

Ruby client designed for calling Promoted's Delivery and Metrics API.

More information at http://www.promoted.ai

Installation

gem 'promoted-ruby-client'

Local Development

  1. Clone or fork the repo on your local machine
  2. cd promoted-ruby-client
  3. bundle
  4. To test interactively: irb -Ilib -rpromoted/ruby/client

Dependencies

Faraday

HTTP client for calling Promoted.

Net::HTTP::Persistent

Faraday binding (provides connection pool support)

Concurrent Ruby

Provides a thread pool for making shadow traffic requests to Delivery API in the background on a subset of calls to prepare_for_logging

Creating a Client

client = Promoted::Ruby::Client::PromotedClient.new

This client will suffice for building log requests. To send actually send traffing to the API, some configuration is required.

client = Promoted::Ruby::Client::PromotedClient.new({
  :metrics_endpoint => "https://<get this from Promoted>",
  :delivery_endpoint => "https://<get this from Promoted>",
  :metrics_api_key => "<get this from Promoted>",
  :delivery_api_key => "<get this from Promoted>"
})

Client Configuration Parameters

Name Type Description
:delivery_endpoint String POST URL for the Promoted Delivery API (get this from Promoted)
:metrics_endpoint String POST URL for the Promoted Metrics API (get this from Promoted)
:metrics_api_key String Used as the x-api-key header on Metrics API requests to Promoted (get this value from Promoted)
:delivery_api_key String Used as the x-api-key header on Delivery API requests to Promoted (get this value from Promoted)
:delivery_timeout_millis Number Timeout on the Delivery API call. Defaults to 3000.
:metrics_timeout_millis Number Timeout on the Metrics API call. Defaults to 3000.
:perform_checks Boolean Whether or not to perform detailed input validation, defaults to true but may be disabled for performance
:logger Ruby Logger-compatible logger Defaults to nil (no logging). Example: Logger.new(STDERR, :progname => 'promotedai')
:shadow_traffic_delivery_percent Number between 0 and 1 % of prepare_for_logging traffic that gets directed to Delivery API as "shadow traffic". Defaults to 0 (no shadow traffic).
:send_shadow_traffic_for_control Boolean If true, the deliver method will send shadow traffic for users in the CONTROL arm of an experiment. Defaults to true.
:default_request_headers Hash Additional headers to send on the request beyond x-api-key. Defaults to {}
: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.
: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.
: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.
:max_request_insertions Number Maximum number of request insertions that will be passed to Delivery API on a single request (any more will be truncated by the SDK). Defaults to 1000.

Data Types

UserInfo

Basic information about the request user.

Field Name Type Optional? Description
:user_id String Yes The platform user id, cleared from Promoted logs.
:log_user_id String Yes A different user id (presumably a UUID) disconnected from the platform user id, good for working with unauthenticated users or implementing right-to-be-forgotten.
:is_internal_user Boolean Yes If this user is a test user or not, defaults to false.

CohortMembership

Useful fields for experimentation during the delivery phase.

Field Name Type Optional? Description
:user_info UserInfo Yes The user info structure.
:arm String Yes 'CONTROL' or one of the TREATMENT values (see constants.rb).

Properties

Properties bag. Has the structure:

  :struct => {
    :product => {
      "id": "product3",
      "title": "Product 3",
      "url": "www.mymarket.com/p/3"
      # other key-value pairs...
    }
  }

Insertion

Content being served at a certain position.

Field Name Type Optional? Description
:user_info UserInfo Yes The user info structure.
:insertion_id String Yes Generated by the SDK (do not set)
:request_id String Yes Generated by the SDK when needed (do not set)
:content_id String No Identifier for the content to be shown, must be set.
:retrieval_rank Number Yes Optional original ranking of this content item.
:retrieval_score Number Yes Optional original quality score of this content item.
:properties Properties Yes Any additional custom properties to associate. For v1 integrations, it is fine not to fill in all the properties.

Size

User's screen dimensions.

Field Name Type Optional? Description
:width Integer No Screen width
:height Integer No Screen height

Screen

State of the screen including scaling.

Field Name Type Optional? Description
:size Size Yes Screen size
:scale Float Yes Current screen scaling factor

ClientHints

Alternative to user-agent strings. See https://raw.githubusercontent.com/snowplow/iglu-central/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0

Field Name Type Optional? Description
:is_mobile Boolean Yes Mobile flag
:brand Array of ClientBrandHint Yes
:architecture String Yes
:model String Yes
:platform String Yes
:platform_version String Yes
:ua_full_version String Yes

ClientBrandHint

See https://raw.githubusercontent.com/snowplow/iglu-central/master/schemas/org.ietf/http_client_hints/jsonschema/1-0-0

Field Name Type Optional? Description
:brand String Yes Mobile flag
:version String Yes

Location

Information about the user's location.

Field Name Type Optional? Description
:latitude Float No Location latitude
:longitude Float No Location longitude
:accuracy_in_meters Integer Yes Location accuracy if available

Browser

Information about the user's browser.

Field Name Type Optional? Description
:user_agent String Yes Browser user agent string
:viewport_size Size Yes Size of the browser viewport
:client_hints ClientHints Yes HTTP client hints structure
referrer String Yes Request referrer

Device

Information about the user's device.

Field Name Type Optional? Description
:device_type one of (UNKNOWN_DEVICE_TYPE, DESKTOP, MOBILE, TABLET) Yes Type of device
:brand String Yes "Apple, "google", Samsung", etc.
:manufacturer String Yes "Apple", "HTC", Motorola", "HUAWEI", etc.
:identifier String Yes Android: android.os.Build.MODEL; iOS: iPhoneXX,YY, etc.
:screen Screen Yes Screen dimensions
:ip_address String Yes Originating IP address
:location Location Yes Location information
:browser Browser Yes Browser information

Paging

TODO


Request

A request for content insertions.

Field Name Type Optional? Description
:user_info UserInfo Yes The user info structure.
:request_id String Yes Generated by the SDK when needed (do not set)
:use_case String Yes One of the use case values, i.e. 'FEED' (see constants.rb).
:properties Properties Yes Any additional custom properties to associate.
:paging Paging Yes Paging parameters (see TODO)
:device Device Yes Device information (as available)

MetricsRequest

Input to prepare_for_logging

Field Name Type Optional? Description
:request Request No The underlying request for content.
:full_insertion [] of Insertion No The proposed list of insertions.

DeliveryRequest

Input to deliver

Field Name Type Optional? Description
:experiment CohortMembership Yes A cohort to evaluation in experimentation.
:request Request No The underlying request for content.
:full_insertion [] of Insertion No The proposed list of insertions with all metadata, will be compacted before forwarding to Promoted.
:only_log Boolean Yes Defaults to false. Set to true to override whether Delivery API is called for this request.

LogRequest

Output of prepare_for_logging as well as an ouput of an SDK call to deliver, input to send_log_request to log to Promoted

Field Name Type Optional? Description
:request Request No The underlying request for content to log.
:insertion [] of Insertion No The insertions, which are either the original request insertions or the insertions resulting from a call to deliver if such call occurred.

ClientResponse

Output of deliver, includes the insertions as well as a suitable LogRequest for forwarding to Metrics API.

Field Name Type Optional? Description
: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).
: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.
:client_request_id String Yes Client-generated request id sent to Delivery API and may be useful for logging and debugging.
:execution_server one of 'API' or 'SDK' Yes Indicates if response insertions on a delivery request came from the API or the SDK.

PromotedClient

Method Input Output Description
prepare_for_logging MetricsRequest LogRequest Builds a request suitable for logging locally and/or to Promoted, either via a subsequent call to send_log_request in the SDK client or by using this structure to make the call yourself. Optionally, based on client configuration may send a random subset of requests to Delivery API as shadow traffic for integration purposes.
send_log_request LogRequest n/a Forwards a LogRequest to Promoted using an HTTP client.
deliver DeliveryRequest ClientResponse Makes a request (subject to experimentation) to Delivery API for insertions, which are then returned along with a LogRequest.
close n/a n/a Closes down the client at shutdown, currently this is just to drain the thread pool that handles shadow traffic.

Metrics API

Pagination

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. TODO: Needs more details.

Expected flow for Metrics logging

# Retrieve a list of content (i.e. products)
# products = fetch_my_marketplace_products()

products = [
  {
    id: "123",
    type: "SHOE",
    name: "Blue shoe",
    total_sales: 1000
  },
  {
    id: "124",
    type: "SHIRT",
    name: "Green shirt",
    total_sales: 800
  },
  {
    id: "125",
    type: "DRESS",
    name: "Red dress",
    total_sales: 1200
  }
]

# Transform them into an [] of Insertions.
insertions = products.map { |product| 
  {
    :content_id => product[:id],
    :properties => {
      :struct => {
        :type => product[:type],
        :name => product[:name]
        # etc
      }
    }
  }
}

# Form a MetricsRequest
metrics_request = {
  :request => {
    :user_info => { :user_id => "912", :log_user_id => "912191"},
    :use_case => "FEED",
    :paging => {
      :offset => 0,
      :size => 5
    },
    :properties => {
      :struct => {
        :active => true
      }
    }
  },
  :full_insertion => insertions
}

# OPTIONAL: You can pass a custom function to "compact" insertions before metrics logging.
# Note that the PromotedClient has a class method helper, remove_all_properties, that does
# an implementation of this, returning nil to remove ALL properties.
to_compact_metrics_properties_func = Proc.new do |properties|
  properties[:struct].delete(:active)
  properties
end
# metrics_request[:to_compact_metrics_properties_func] = to_compact_metrics_properties_func

# Create a client
client = Promoted::Ruby::Client::PromotedClient.new

# Build a log request
log_request = client.prepare_for_logging(metrics_request)

# Log (assuming you have configured your client with a :metrics_endpoint)
client.send_log_request(log_request)

Delivery API

Expected flow for Delivery

# (continuing from the above example for Metrics)

# Form a DeliveryRequest
delivery_request = {
  :request => {
    :user_info => { :user_id => "912", :log_user_id => "912191"},
    :use_case => "FEED",
    :paging => {
      :offset => 0,
      :size => 5
    },
    :properties => {
      :struct => {
        :active => true
      }
    }
  },
  :full_insertion => insertions,
  :only_log => false
}

# Request insertions from Delivery API
client_response = client.deliver(delivery_request)

# Use the resulting insertions
client_response[:insertion]

# Log if a log request was provided (if not, deliver was called successfully
# and Promoted logged on the server-side).)
client.send_log_request(client_response[:log_request]) if client_response[:log_request]

TODO Experimentation example