Skip to content

[FSSDK-11459] Ruby - Add SDK Multi-Region Support for Data Hosting #365

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0165dee
[FSSDK-11459] Ruby - Add SDK Multi-Region Support for Data Hosting
esrakartalOpt Jun 30, 2025
73769f1
Fix lint issues
esrakartalOpt Jun 30, 2025
f9156d0
Fix lint
esrakartalOpt Jun 30, 2025
a5e6faf
Fix test
esrakartalOpt Jun 30, 2025
9f47706
Add with region
esrakartalOpt Jun 30, 2025
e776ead
Fix lint
esrakartalOpt Jun 30, 2025
884c972
Fix lint issue
esrakartalOpt Jun 30, 2025
6d0245e
Correct the Region default value
esrakartalOpt Jun 30, 2025
e1ae8b5
Fix failed tests
esrakartalOpt Jun 30, 2025
c8a47b1
Fix lint
esrakartalOpt Jun 30, 2025
93e7a11
Fix test cases
esrakartalOpt Jun 30, 2025
06295e6
Fix event builder
esrakartalOpt Jun 30, 2025
04e3a7e
Fix the issue
esrakartalOpt Jun 30, 2025
474d01d
Fix typo
esrakartalOpt Jun 30, 2025
ae26ced
Correct the event name
esrakartalOpt Jun 30, 2025
80d0c16
Fix errors
esrakartalOpt Jun 30, 2025
fd72f77
Fix errors
esrakartalOpt Jun 30, 2025
4dea3ec
Fix test cases
esrakartalOpt Jun 30, 2025
904d0bd
Fix test cases
esrakartalOpt Jun 30, 2025
7f98564
Implement copilot review comments
esrakartalOpt Jun 30, 2025
56bc8cd
Fix lint
esrakartalOpt Jun 30, 2025
d9ddb68
Implement changes and add new tests
esrakartalOpt Jul 21, 2025
dbdd9b1
Merge branch 'master' of https://github.com/optimizely/ruby-sdk into …
esrakartalOpt Jul 21, 2025
226b7f3
Fix lint issue
esrakartalOpt Jul 21, 2025
1b067c1
Remove unnecessary region params
esrakartalOpt Jul 21, 2025
68fd5d8
Remove region from expected param
esrakartalOpt Jul 22, 2025
1380142
Remove region from impression expected
esrakartalOpt Jul 22, 2025
c9f4a17
Add region to builder
esrakartalOpt Jul 22, 2025
b5aab91
Add region to as_json
esrakartalOpt Jul 22, 2025
331c0b1
Implement
esrakartalOpt Jul 22, 2025
c6f0725
Update region EU
esrakartalOpt Jul 22, 2025
6469629
Fix test
esrakartalOpt Jul 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/optimizely/config/datafile_project_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class DatafileProjectConfig < ProjectConfig
:group_id_map, :rollout_id_map, :rollout_experiment_id_map, :variation_id_map,
:variation_id_to_variable_usage_map, :variation_key_map, :variation_id_map_by_experiment_id,
:variation_key_map_by_experiment_id, :flag_variation_map, :integration_key_map, :integrations,
:public_key_for_odp, :host_for_odp, :all_segments
:public_key_for_odp, :host_for_odp, :all_segments, :region
# Boolean - denotes if Optimizely should remove the last block of visitors' IP address before storing event data
attr_reader :anonymize_ip

Expand Down Expand Up @@ -69,6 +69,10 @@ def initialize(datafile, logger, error_handler)
@rollouts = config.fetch('rollouts', [])
@send_flag_decisions = config.fetch('sendFlagDecisions', false)
@integrations = config.fetch('integrations', [])
@region = config.fetch('region', 'US')

# Default to US region if not specified
@region = 'US' if @region.nil? || @region.empty?

# Json type is represented in datafile as a subtype of string for the sake of backwards compatibility.
# Converting it to a first-class json type while creating Project Config
Expand Down
7 changes: 5 additions & 2 deletions lib/optimizely/event/entity/event_context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ def initialize(
anonymize_ip:,
revision:,
client_name:,
client_version:
client_version:,
region:
)
@account_id = account_id
@project_id = project_id
@anonymize_ip = anonymize_ip
@revision = revision
@client_name = client_name
@client_version = client_version
@region = region
end

def as_json
Expand All @@ -43,7 +45,8 @@ def as_json
anonymize_ip: @anonymize_ip,
revision: @revision,
client_name: @client_name,
client_version: @client_version
client_version: @client_version,
region: @region
}
end
end
Expand Down
10 changes: 8 additions & 2 deletions lib/optimizely/event/event_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ class EventFactory
# EventFactory builds LogEvent objects from a given user_event.
class << self
CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'
ENDPOINT = 'https://logx.optimizely.com/v1/events'
ENDPOINTS = {
US: 'https://logx.optimizely.com/v1/events',
EU: 'https://eu.logx.optimizely.com/v1/events'
}.freeze
POST_HEADERS = {'Content-Type' => 'application/json'}.freeze
ACTIVATE_EVENT_KEY = 'campaign_activated'

Expand Down Expand Up @@ -67,7 +70,10 @@ def create_log_event(user_events, logger)

builder.with_visitors(visitors)
event_batch = builder.build
Event.new(:post, ENDPOINT, event_batch.as_json, POST_HEADERS)

endpoint = ENDPOINTS[user_context[:region].to_s.upcase.to_sym] || ENDPOINTS[:US]

Event.new(:post, endpoint, event_batch.as_json, POST_HEADERS)
end

def build_attribute_list(user_attributes, project_config)
Expand Down
2 changes: 2 additions & 0 deletions lib/optimizely/event/user_event_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ def self.create_impression_event(project_config, experiment, variation_id, metad
#
# Returns Event encapsulating the impression event.
event_context = Optimizely::EventContext.new(
region: project_config.region,
account_id: project_config.account_id,
project_id: project_config.project_id,
anonymize_ip: project_config.anonymize_ip,
Expand Down Expand Up @@ -67,6 +68,7 @@ def self.create_conversion_event(project_config, event, user_id, user_attributes
# Returns Event encapsulating the conversion event.

event_context = Optimizely::EventContext.new(
region: project_config.region,
account_id: project_config.account_id,
project_id: project_config.project_id,
anonymize_ip: project_config.anonymize_ip,
Expand Down
18 changes: 14 additions & 4 deletions lib/optimizely/event_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,17 @@ def get_common_params(project_config, user_id, attributes)
revision: project_config.revision,
client_name: CLIENT_ENGINE,
enrich_decisions: true,
client_version: VERSION
client_version: VERSION,
region: project_config.region || 'US'
}
end
end

class EventBuilder < BaseEventBuilder
ENDPOINT = 'https://logx.optimizely.com/v1/events'
ENDPOINTS = {
US: 'https://logx.optimizely.com/v1/events',
EU: 'https://eu.logx.optimizely.com/v1/events'
}.freeze
POST_HEADERS = {'Content-Type' => 'application/json'}.freeze
ACTIVATE_EVENT_KEY = 'campaign_activated'

Expand All @@ -122,11 +126,14 @@ def create_impression_event(project_config, experiment, variation_id, user_id, a
#
# Returns +Event+ encapsulating the impression event.

region = project_config.region || 'US'
event_params = get_common_params(project_config, user_id, attributes)
impression_params = get_impression_params(project_config, experiment, variation_id)
event_params[:visitors][0][:snapshots].push(impression_params)

Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
endpoint = ENDPOINTS[region.to_s.upcase.to_sym]

Event.new(:post, endpoint, event_params, POST_HEADERS)
end

def create_conversion_event(project_config, event, user_id, attributes, event_tags)
Expand All @@ -140,11 +147,14 @@ def create_conversion_event(project_config, event, user_id, attributes, event_ta
#
# Returns +Event+ encapsulating the conversion event.

region = project_config.region || 'US'
event_params = get_common_params(project_config, user_id, attributes)
conversion_params = get_conversion_params(event, event_tags)
event_params[:visitors][0][:snapshots] = [conversion_params]

Event.new(:post, ENDPOINT, event_params, POST_HEADERS)
endpoint = ENDPOINTS[region.to_s.upcase.to_sym]

Event.new(:post, endpoint, event_params, POST_HEADERS)
end

private
Expand Down
2 changes: 2 additions & 0 deletions lib/optimizely/project_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def host_for_odp; end

def all_segments; end

def region; end
Copy link
Preview

Copilot AI Jun 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The region method is currently a stub and returns nothing. It should return the instance variable, e.g., def region; @region; end.

Copilot uses AI. Check for mistakes.


def experiment_running?(experiment); end

def get_experiment_from_key(experiment_key); end
Expand Down
18 changes: 18 additions & 0 deletions spec/config/datafile_project_config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
expect(project_config.sdk_key).to eq(config_body['sdkKey'])
expect(project_config.environment_key).to eq(config_body['environmentKey'])
expect(project_config.send_flag_decisions).to eq(config_body['sendFlagDecisions'])
expect(project_config.region).to eq(config_body['region'])

expected_attribute_key_map = {
'browser_type' => config_body['attributes'][0],
Expand Down Expand Up @@ -756,6 +757,23 @@
expect(project_config.rollout_experiment_id_map).to eq(expected_rollout_experiment_id_map)
end

it 'should use US region when no region is specified in datafile' do
project_config = Optimizely::DatafileProjectConfig.new(config_body_JSON, logger, error_handler)
expect(project_config.region).to eq('US')
end

it 'should parse region specified in datafile correctly' do
project_config_us = Optimizely::DatafileProjectConfig.new(config_body_JSON, logger, error_handler)
expect(project_config_us.region).to eq('US')

config_body_eu = config_body.dup
config_body_eu['region'] = 'EU'
config_body_json = JSON.dump(config_body_eu)
project_config_eu = Optimizely::DatafileProjectConfig.new(config_body_json, logger, error_handler)

expect(project_config_eu.region).to eq('EU')
end

it 'should initialize properties correctly upon creating project with typed audience dict' do
project_config = Optimizely::DatafileProjectConfig.new(JSON.dump(OptimizelySpec::CONFIG_DICT_WITH_TYPED_AUDIENCES), logger, error_handler)
config_body = OptimizelySpec::CONFIG_DICT_WITH_TYPED_AUDIENCES
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy