Skip to content

Commit 12593dd

Browse files
Merge pull request #84 from optimizely/alda/1.5.0
Alda/1.5.0
2 parents 9f5a688 + 962dec7 commit 12593dd

15 files changed

+1291
-398
lines changed

CHANGELOG

+6
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 1.5.0
2+
December 13, 2017
3+
4+
* Implemented IP anonymization.
5+
* Implemented bucketing IDs.
6+
* Implemented Notification Listeners.
17
-------------------------------------------------------------------------------
28
## 1.4.0
39
October 3, 2017

lib/optimizely.rb

+12
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
require_relative 'optimizely/helpers/validator'
2525
require_relative 'optimizely/helpers/variable_type'
2626
require_relative 'optimizely/logger'
27+
require_relative 'optimizely/notification_center'
2728
require_relative 'optimizely/project_config'
2829

2930
module Optimizely
@@ -38,6 +39,7 @@ class Project
3839
attr_reader :event_builder
3940
attr_reader :event_dispatcher
4041
attr_reader :logger
42+
attr_reader :notification_center
4143

4244
def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = nil, skip_json_validation = false, user_profile_service = nil)
4345
# Constructor for Projects.
@@ -83,6 +85,7 @@ def initialize(datafile, event_dispatcher = nil, logger = nil, error_handler = n
8385

8486
@decision_service = DecisionService.new(@config, @user_profile_service)
8587
@event_builder = EventBuilder.new(@config)
88+
@notification_center = NotificationCenter.new(@logger, @error_handler)
8689
end
8790

8891
def activate(experiment_key, user_id, attributes = nil)
@@ -231,6 +234,10 @@ def track(event_key, user_id, attributes = nil, event_tags = nil)
231234
rescue => e
232235
@logger.log(Logger::ERROR, "Unable to dispatch conversion event. Error: #{e}")
233236
end
237+
@notification_center.send_notifications(
238+
NotificationCenter::NOTIFICATION_TYPES[:TRACK],
239+
event_key, user_id, attributes, event_tags, conversion_event
240+
)
234241
end
235242

236243
def is_feature_enabled(feature_flag_key, user_id, attributes = nil)
@@ -512,6 +519,11 @@ def send_impression(experiment, variation_key, user_id, attributes = nil)
512519
rescue => e
513520
@logger.log(Logger::ERROR, "Unable to dispatch impression event. Error: #{e}")
514521
end
522+
variation = @config.get_variation_from_id(experiment_key, variation_id)
523+
@notification_center.send_notifications(
524+
NotificationCenter::NOTIFICATION_TYPES[:ACTIVATE],
525+
experiment,user_id, attributes, variation, impression_event
526+
)
515527
end
516528
end
517529
end

lib/optimizely/bucketer.rb

+18-16
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ module Optimizely
2020
class Bucketer
2121
# Optimizely bucketing algorithm that evenly distributes visitors.
2222

23-
BUCKETING_ID_TEMPLATE = '%{user_id}%{entity_id}'
23+
BUCKETING_ID_TEMPLATE = '%{bucketing_id}%{entity_id}'
2424
HASH_SEED = 1
2525
MAX_HASH_VALUE = 2**32
2626
MAX_TRAFFIC_VALUE = 10_000
@@ -35,13 +35,15 @@ def initialize(config)
3535
@config = config
3636
end
3737

38-
def bucket(experiment, user_id)
38+
def bucket(experiment, bucketing_id, user_id)
3939
# Determines ID of variation to be shown for a given experiment key and user ID.
4040
#
4141
# experiment - Experiment for which visitor is to be bucketed.
42+
# bucketing_id - String A customer-assigned value used to generate the bucketing key
4243
# user_id - String ID for user.
4344
#
4445
# Returns variation in which visitor with ID user_id has been placed. Nil if no variation.
46+
return nil if experiment.nil?
4547

4648
# check if experiment is in a group; if so, check if user is bucketed into specified experiment
4749
experiment_id = experiment['id']
@@ -51,7 +53,7 @@ def bucket(experiment, user_id)
5153
group = @config.group_key_map.fetch(group_id)
5254
if Helpers::Group.random_policy?(group)
5355
traffic_allocations = group.fetch('trafficAllocation')
54-
bucketed_experiment_id = find_bucket(user_id, group_id, traffic_allocations)
56+
bucketed_experiment_id = find_bucket(bucketing_id, user_id, group_id, traffic_allocations)
5557
# return if the user is not bucketed into any experiment
5658
unless bucketed_experiment_id
5759
@config.logger.log(Logger::INFO, "User '#{user_id}' is in no experiment.")
@@ -76,7 +78,7 @@ def bucket(experiment, user_id)
7678
end
7779

7880
traffic_allocations = experiment['trafficAllocation']
79-
variation_id = find_bucket(user_id, experiment_id, traffic_allocations)
81+
variation_id = find_bucket(bucketing_id, user_id, experiment_id, traffic_allocations)
8082
if variation_id && variation_id != ''
8183
variation = @config.get_variation_from_id(experiment_key, variation_id)
8284
variation_key = variation ? variation['key'] : nil
@@ -96,18 +98,18 @@ def bucket(experiment, user_id)
9698
nil
9799
end
98100

99-
def find_bucket(user_id, parent_id, traffic_allocations)
101+
def find_bucket(bucketing_id, user_id, parent_id, traffic_allocations)
100102
# Helper function to find the matching entity ID for a given bucketing value in a list of traffic allocations.
101103
#
104+
# bucketing_id - String A customer-assigned value user to generate bucketing key
102105
# user_id - String ID for user
103106
# parent_id - String entity ID to use for bucketing ID
104107
# traffic_allocations - Array of traffic allocations
105108
#
106109
# Returns entity ID corresponding to the provided bucket value or nil if no match is found.
107-
108-
bucketing_id = sprintf(BUCKETING_ID_TEMPLATE, user_id: user_id, entity_id: parent_id)
109-
bucket_value = generate_bucket_value(bucketing_id)
110-
@config.logger.log(Logger::DEBUG, "Assigned bucket #{bucket_value} to user '#{user_id}'.")
110+
bucketing_key = sprintf(BUCKETING_ID_TEMPLATE, bucketing_id: bucketing_id, entity_id: parent_id)
111+
bucket_value = generate_bucket_value(bucketing_key)
112+
@config.logger.log(Logger::DEBUG, "Assigned bucket #{bucket_value} to user '#{user_id}' with bucketing ID: '#{bucketing_id}'.")
111113

112114
traffic_allocations.each do |traffic_allocation|
113115
current_end_of_range = traffic_allocation['endOfRange']
@@ -122,25 +124,25 @@ def find_bucket(user_id, parent_id, traffic_allocations)
122124

123125
private
124126

125-
def generate_bucket_value(bucketing_id)
127+
def generate_bucket_value(bucketing_key)
126128
# Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE).
127129
#
128-
# bucketing_id - String ID for bucketing.
130+
# bucketing_key - String - Value used to generate bucket value
129131
#
130-
# Returns bucket value corresponding to the provided bucketing ID.
132+
# Returns bucket value corresponding to the provided bucketing key.
131133

132-
ratio = (generate_unsigned_hash_code_32_bit(bucketing_id)).to_f / MAX_HASH_VALUE
134+
ratio = (generate_unsigned_hash_code_32_bit(bucketing_key)).to_f / MAX_HASH_VALUE
133135
(ratio * MAX_TRAFFIC_VALUE).to_i
134136
end
135137

136-
def generate_unsigned_hash_code_32_bit(bucketing_id)
138+
def generate_unsigned_hash_code_32_bit(bucketing_key)
137139
# Helper function to retreive hash code
138140
#
139-
# bucketing_id - String ID for bucketing.
141+
# bucketing_key - String - Value used for the key of the murmur hash
140142
#
141143
# Returns hash code which is a 32 bit unsigned integer.
142144

143-
MurmurHash3::V32.str_hash(bucketing_id, @bucket_seed) & UNSIGNED_MAX_32_BIT_VALUE
145+
MurmurHash3::V32.str_hash(bucketing_key, @bucket_seed) & UNSIGNED_MAX_32_BIT_VALUE
144146
end
145147
end
146148
end

0 commit comments

Comments
 (0)