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
- Clone or fork the repo on your local machine
cd promoted-ruby-client
bundle
- 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 deliver
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 deliver 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.
:anon_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.
:ignore_usage
| Boolean | Yes | If you want to ignore usage from this user, 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
Paging parameters.
DeliveryRequest.retrieval_insertion_offset
also impacts paging. That field indicate the offset of the retrieved insertions that are passed into Request.insertion
. See detailed documentation on paging and retrieval_insertion_offset
.
Field Name | Type | Optional? | Description |
---|---|---|---|
:offset |
Integer | Yes | The 0-based, starting index for the response page. This should be the global position. |
:size |
Integer | Yes | The number of items to return in a response page. |
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
:device
| Device | Yes | Device information (as available)
:disable_personalization
| Boolean | Yes | If true, disables personalized inputs into Delivery algorithm.
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.
:only_log
| Boolean | Yes | Defaults to false. Set to true to override whether Delivery API is called for this request.
:retrieval_insertion_offset
| Integer | Yes | The index of the retrieved insertions set on Request.insertion
list. If just sending the top-N from your retrieval, this is 0
. If this is the next batch (e.g. 500
to 999
), then the value is 500
. This can be used to send multiple groups of retrieved insertions. This interacts with the Paging
fields. More detailed documentation on paging.
LogRequest
A log object that is sent as a RPC request to Promoted's Metrics API log endpoint. This is outputted from the deliver
SDK call. Callers need to either input it into the send_log_request
method or send it to the Metrics API directly. Clients should avoid manipulating this object directly.
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 paged 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 |
---|---|---|---|
send_log_request |
LogRequest | n/a | Forwards a LogRequest to Promoted using an HTTP client. |
deliver |
DeliveryRequest | ClientResponse | Depending on flags, either (1) makes a request (subject to experimentation) to Delivery API for insertions, which are then returned along with a LogRequest or (2) implements SDK-side paging and prepares log records that can be logged to Promoted using send_log_request 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. |
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
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", :anon_user_id => "912191"},
:use_case => "FEED",
:paging => {
:offset => 0,
:size => 5
},
:properties => {
:struct => {
:active => true
}
},
:insertion => insertions
},
:only_log => true,
}
# Create a client
client = Promoted::Ruby::Client::PromotedClient.new
# Build a log request
client_response = client.deliver(metrics_request)
# Log (assuming you have configured your client with a :metrics_endpoint)
client.send_log_request(client_response[:log_request]) if client_response[: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", :anon_user_id => "912191"},
:use_case => "FEED",
:paging => {
:offset => 0,
:size => 5
},
:properties => {
:struct => {
:active => true
}
},
: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]