Skip to content

Commit

Permalink
feat(sdk): send POST call for events-enabled acc and multiple custom …
Browse files Browse the repository at this point in the history
…dimensions push support
  • Loading branch information
Abbas-khaliq authored and softvar committed Dec 23, 2021
1 parent 1bf473f commit 217f3e0
Show file tree
Hide file tree
Showing 11 changed files with 622 additions and 41 deletions.
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,45 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.25.0] - 2021-12-23

### Added

- Support for pushing multiple custom dimensions at once. Earlier, you had to call push API multiple times for tracking multiple custom dimensions as follows:

```ruby
vwo_client_instance.push('browser', 'chrome', user_id)
vwo_client_instance.push('price', '20', user_id)
```

Now, you can pass an hash

```ruby
custom_dimension_map = {
browser: 'chrome',
price: '20'
}

vwo_client_instance.push(custom_dimension_map, user_id)
```

Multiple asynchronous tracking calls would be initiated in this case.

### Changed

- If Events Architecture is enabled for your VWO account, all the tracking calls being initiated from SDK would now be `POST` instead of `GET` and there would be single endpoint i.e. `/events/t`. This is done in order to bring events support and building advanced capabilities in future.

- For events architecture accounts, tracking same goal across multiple campaigns will not send multiple tracking calls. Instead, one single `POST` call would be made to track the same goal across multiple different campaigns running on the same environment.

- Multiple custom dimension can be pushed via `push` API. For events architecture enabled account, only one single asynchronous call would be made to track multiple custom dimensions.
```ruby
custom_dimension_map = {
browser: 'chrome',
price: '20'
}
vwo_client_instance.push(custom_dimension_map, user_id)
```

## [1.24.1] - 2021-12-09

### Changed
Expand Down
124 changes: 84 additions & 40 deletions lib/vwo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,10 @@ def activate(campaign_key, user_id, options = {})
user_id
)
@batch_events_queue.enqueue(impression)
elsif is_event_arch_enabled
properties = get_events_base_properties(@settings_file, EventEnum::VWO_VARIATION_SHOWN, @usage_stats.usage_stats)
payload = get_track_user_payload_data(@settings_file, user_id, EventEnum::VWO_VARIATION_SHOWN, campaign['id'], variation['id'])
@event_dispatcher.dispatch_event_arch_post(properties, payload)
else
# Variation found, dispatch it to server
impression = create_impression(
Expand Down Expand Up @@ -592,6 +596,8 @@ def track(campaign_key, user_id, goal_identifier, options = {})
return nil
end

metric_map = {}
revenue_props = []
result = {}
campaigns.each do |campaign|
begin
Expand Down Expand Up @@ -687,6 +693,11 @@ def track(campaign_key, user_id, goal_identifier, options = {})
revenue_value
)
@batch_events_queue.enqueue(impression)
elsif is_event_arch_enabled
metric_map[campaign['id']] = goal['id']
if goal['type'] == GoalTypes::REVENUE && !(revenue_props.include? goal['revenueProp'])
revenue_props << goal['revenueProp']
end
else
impression = create_impression(
@settings_file,
Expand Down Expand Up @@ -737,6 +748,12 @@ def track(campaign_key, user_id, goal_identifier, options = {})
end
end

if is_event_arch_enabled
properties = get_events_base_properties(@settings_file, goal_identifier)
payload = get_track_goal_payload_data(@settings_file, user_id, goal_identifier, revenue_value, metric_map, revenue_props)
@event_dispatcher.dispatch_event_arch_post(properties, payload)
end

if result.length() == 0
return nil
end
Expand Down Expand Up @@ -861,6 +878,10 @@ def feature_enabled?(campaign_key, user_id, options = {})
user_id
)
@batch_events_queue.enqueue(impression)
elsif is_event_arch_enabled
properties = get_events_base_properties(@settings_file, EventEnum::VWO_VARIATION_SHOWN, @usage_stats.usage_stats)
payload = get_track_user_payload_data(@settings_file, user_id, EventEnum::VWO_VARIATION_SHOWN, campaign['id'], variation['id'])
@event_dispatcher.dispatch_event_arch_post(properties, payload)
else
impression = create_impression(
@settings_file,
Expand Down Expand Up @@ -1110,12 +1131,12 @@ def get_feature_variable_value(campaign_key, variable_key, user_id, options = {}
# This API method: Makes a call to our server to store the tag_values
# 1. Validates the arguments being passed
# 2. Send a call to our server
# @param[String] :tag_key key name of the tag
# @param[String] :tag_value Value of the tag
# @param[String|Hash] :tag_key key name of the tag OR tagKey/tagValue pair(custom dimension map)
# @param[String] :tag_value Value of the tag OR userId if TagKey is hash
# @param[String] :user_id ID of the user for which value should be stored
# @return true if call is made successfully, else false

def push(tag_key, tag_value, user_id)
def push(tag_key, tag_value, user_id = nil)
unless @is_instance_valid
@logger.log(
LogLevelEnum::ERROR,
Expand All @@ -1128,7 +1149,16 @@ def push(tag_key, tag_value, user_id)
return
end

unless valid_string?(tag_key) && valid_string?(tag_value) && valid_string?(user_id)
# Argument reshuffling.
custom_dimension_map = {}
if user_id.nil? || tag_key.is_a?(Hash)
custom_dimension_map = convert_to_symbol_hash(tag_key)
user_id = tag_value
else
custom_dimension_map[tag_key.to_sym] = tag_value
end

unless (valid_string?(tag_key) || valid_hash?(tag_key)) && valid_string?(tag_value) && valid_string?(user_id)
@logger.log(
LogLevelEnum::ERROR,
format(
Expand All @@ -1140,51 +1170,61 @@ def push(tag_key, tag_value, user_id)
return false
end

if tag_key.length > PushApi::TAG_KEY_LENGTH
@logger.log(
LogLevelEnum::ERROR,
format(
LogMessageEnum::ErrorMessages::TAG_KEY_LENGTH_EXCEEDED,
file: FILE,
user_id: user_id,
tag_key: tag_key,
api_name: ApiMethods::PUSH
custom_dimension_map.each do |tag_key, tag_value|
if tag_key.length > PushApi::TAG_KEY_LENGTH
@logger.log(
LogLevelEnum::ERROR,
format(
LogMessageEnum::ErrorMessages::TAG_KEY_LENGTH_EXCEEDED,
file: FILE,
user_id: user_id,
tag_key: tag_key,
api_name: ApiMethods::PUSH
)
)
)
return false
end
return false
end

if tag_value.length > PushApi::TAG_VALUE_LENGTH
@logger.log(
LogLevelEnum::ERROR,
format(
LogMessageEnum::ErrorMessages::TAG_VALUE_LENGTH_EXCEEDED,
file: FILE,
user_id: user_id,
tag_value: tag_value,
api_name: ApiMethods::PUSH
if tag_value.length > PushApi::TAG_VALUE_LENGTH
@logger.log(
LogLevelEnum::ERROR,
format(
LogMessageEnum::ErrorMessages::TAG_VALUE_LENGTH_EXCEEDED,
file: FILE,
user_id: user_id,
tag_value: tag_value,
api_name: ApiMethods::PUSH
)
)
)
return false
return false
end
end

if defined?(@batch_events)
impression = get_batch_event_url_params(@settings_file, tag_key, tag_value, user_id)
@batch_events_queue.enqueue(impression)
custom_dimension_map.each do |tag_key, tag_value|
impression = get_batch_event_url_params(@settings_file, tag_key, tag_value, user_id)
@batch_events_queue.enqueue(impression)
end
elsif is_event_arch_enabled
properties = get_events_base_properties(@settings_file, EventEnum::VWO_SYNC_VISITOR_PROP)
payload = get_push_payload_data(@settings_file, user_id, EventEnum::VWO_SYNC_VISITOR_PROP, custom_dimension_map)
@event_dispatcher.dispatch_event_arch_post(properties, payload)
else
impression = get_url_params(@settings_file, tag_key, tag_value, user_id, @sdk_key)
@event_dispatcher.dispatch(impression)
custom_dimension_map.each do |tag_key, tag_value|
impression = get_url_params(@settings_file, tag_key, tag_value, user_id, @sdk_key)
@event_dispatcher.dispatch(impression)

@logger.log(
LogLevelEnum::INFO,
format(
LogMessageEnum::InfoMessages::MAIN_KEYS_FOR_PUSH_API,
file: FILE,
u: impression['u'],
account_id: impression['account_id'],
tags: impression['tags']
@logger.log(
LogLevelEnum::INFO,
format(
LogMessageEnum::InfoMessages::MAIN_KEYS_FOR_PUSH_API,
file: FILE,
u: impression['u'],
account_id: impression['account_id'],
tags: impression['tags']
)
)
)
end
end
true
rescue StandardError => e
Expand Down Expand Up @@ -1253,4 +1293,8 @@ def get_goal_type_to_track(options)
end
goal_type_to_track
end

def is_event_arch_enabled
return @settings_file.key?('isEventArchEnabled') && @settings_file['isEventArchEnabled']
end
end
8 changes: 7 additions & 1 deletion lib/vwo/constants.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module CONSTANTS
HTTP_PROTOCOL = 'http://'
HTTPS_PROTOCOL = 'https://'
URL_NAMESPACE = '6ba7b811-9dad-11d1-80b4-00c04fd430c8'
SDK_VERSION = '1.24.1'
SDK_VERSION = '1.25.0'
SDK_NAME = 'ruby'
VWO_DELIMITER = '_vwo_'
MAX_EVENTS_PER_REQUEST = 5000
Expand All @@ -44,6 +44,7 @@ module ENDPOINTS
TRACK_GOAL = '/server-side/track-goal'
PUSH = '/server-side/push'
BATCH_EVENTS = '/server-side/batch-events'
EVENTS = '/events/t'
end

module EVENTS
Expand Down Expand Up @@ -113,5 +114,10 @@ module CampaignTypes
FEATURE_TEST = 'FEATURE_TEST'
FEATURE_ROLLOUT = 'FEATURE_ROLLOUT'
end

module EventEnum
VWO_VARIATION_SHOWN = 'vwo_variationShown'
VWO_SYNC_VISITOR_PROP = 'vwo_syncVisitorProp'
end
end
end
4 changes: 4 additions & 0 deletions lib/vwo/enums.rb
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ module DebugMessages
BEFORE_FLUSHING = '(%<file>s): Flushing events queue %<manually>s having %<length>s events %<timer>s queue summary: %<queue_metadata>s'
EVENT_BATCHING_INSUFFICIENT = '(%<file>s): %<key>s not provided, assigning default value'
GOT_ELIGIBLE_CAMPAIGNS = "(%<file>s): Campaigns:%<eligible_campaigns_key>s are eligible, %<ineligible_campaigns_log_text>s are ineligible from the Group:%<group_name>s for the User ID:%<user_id>s"
IMPRESSION_FOR_EVENT_ARCH_TRACK_USER = "(%<file>s): impression built for vwo_variationShown event for account ID:%<a>s, user ID:%<u>s, and campaign ID:%<c>s"
IMPRESSION_FOR_EVENT_ARCH_TRACK_GOAL = "(%<file>s): impression built for %<goal_identifier>s event for account ID:%<a>s, user ID:%<u>s, and campaign ID:%<c>s"
IMPRESSION_FOR_EVENT_ARCH_PUSH = "(%<file>s): impression built for visitor property:%<property>s for account ID:%<a>s and user ID:%<u>s"
end

# Info Messages
Expand All @@ -128,6 +131,7 @@ module InfoMessages
AUDIENCE_CONDITION_NOT_MET = '(%<file>s): userId:%<user_id>s does not become part of campaign because of not meeting audience conditions'
GOT_VARIATION_FOR_USER = '(%<file>s): userId:%<user_id>s for campaign:%<campaign_key>s got variationName:%<variation_name>s'
USER_GOT_NO_VARIATION = '(%<file>s): userId:%<user_id>s for campaign:%<campaign_key>s did not allot any variation'
IMPRESSION_SUCCESS_FOR_EVENT_ARCH = '(%<file>s): Impression for %<event>s - %<url>s was successfully received by VWO for account ID:%<a>s'
IMPRESSION_SUCCESS = '(%<file>s): Impression event - %<end_point>s was successfully received by VWO having main keys: accountId:%<account_id>s campaignId:%<campaign_id>s and variationId:%<variation_id>s'
MAIN_KEYS_FOR_IMPRESSION = '(%<file>s): Having main keys: accountId:%<account_id>s campaignId:%<campaign_id>s and variationId:%<variation_id>s}'
MAIN_KEYS_FOR_PUSH_API = '(%<file>s): Having main keys: accountId:%<account_id>s u:%<u>s and tags:%<tags>s}'
Expand Down
3 changes: 3 additions & 0 deletions lib/vwo/schemas/settings_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ module Schema
accountId: {
type: %w[number string]
},
isEventArchEnabled: {
type: ['boolean']
},
campaigns: {
if: {
type: 'array'
Expand Down
34 changes: 34 additions & 0 deletions lib/vwo/services/event_dispatcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
require_relative '../logger'
require_relative '../enums'
require_relative '../utils/request'
require_relative '../constants'

class VWO
module Services
class EventDispatcher
include VWO::Enums
include VWO::CONSTANTS

EXCLUDE_KEYS = ['url'].freeze

Expand Down Expand Up @@ -63,6 +65,38 @@ def dispatch(impression)
)
false
end

def dispatch_event_arch_post(params, post_data)
return true if @is_development_mode

url = HTTPS_PROTOCOL + ENDPOINTS::BASE_URL + ENDPOINTS::EVENTS
resp = VWO::Utils::Request.event_post(url, params, post_data, SDK_NAME)
if resp.code == '200'
@logger.log(
LogLevelEnum::INFO,
format(
LogMessageEnum::InfoMessages::IMPRESSION_SUCCESS_FOR_EVENT_ARCH,
file: FileNameEnum::EventDispatcher,
event: 'visitor property:' + JSON.generate(post_data[:d][:visitor][:props]),
url: url,
a: params[:a]
)
)
true
else
@logger.log(
LogLevelEnum::ERROR,
format(LogMessageEnum::ErrorMessages::IMPRESSION_FAILED, file: FileNameEnum::EventDispatcher, end_point: url)
)
false
end
rescue StandardError
@logger.log(
LogLevelEnum::ERROR,
format(LogMessageEnum::ErrorMessages::IMPRESSION_FAILED, file: FileNameEnum::EventDispatcher, end_point: url)
)
false
end
end
end
end
5 changes: 5 additions & 0 deletions lib/vwo/utils/function.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ def get_current_unix_timestamp
Time.now.to_i
end

# @return[Integer]
def get_current_unix_timestamp_in_millis
(Time.now.to_f * 1000).to_i
end

# @return[any, any]
def get_key_value(obj)
[obj.keys[0], obj.values[0]]
Expand Down
Loading

0 comments on commit 217f3e0

Please sign in to comment.