Class: VWO::Core::Bucketer

Inherits:
Object
  • Object
show all
Includes:
VWO::CONSTANTS, Enums, Utils::Validations
Defined in:
lib/vwo/core/bucketer.rb

Constant Summary collapse

U_MAX_32_BIT =

Took reference from StackOverflow(stackoverflow.com/) to: convert signed to unsigned integer in python from StackOverflow Author - Duncan (stackoverflow.com/users/107660/duncan) Source - stackoverflow.com/a/20766900/2494535

0xFFFFFFFF
MAX_HASH_VALUE =
2**32
FILE =
FileNameEnum::BUCKETER

Constants included from VWO::CONSTANTS

VWO::CONSTANTS::API_VERSION, VWO::CONSTANTS::DEFAULT_EVENTS_PER_REQUEST, VWO::CONSTANTS::DEFAULT_REQUEST_TIME_INTERVAL, VWO::CONSTANTS::GOAL_TYPES, VWO::CONSTANTS::HTTPS_PROTOCOL, VWO::CONSTANTS::HTTP_PROTOCOL, VWO::CONSTANTS::LIBRARY_PATH, VWO::CONSTANTS::MAX_EVENTS_PER_REQUEST, VWO::CONSTANTS::MAX_RANGE, VWO::CONSTANTS::MAX_TRAFFIC_PERCENT, VWO::CONSTANTS::MAX_TRAFFIC_VALUE, VWO::CONSTANTS::MIN_EVENTS_PER_REQUEST, VWO::CONSTANTS::MIN_REQUEST_TIME_INTERVAL, VWO::CONSTANTS::PLATFORM, VWO::CONSTANTS::RUBY_VARIABLE_TYPES, VWO::CONSTANTS::SDK_NAME, VWO::CONSTANTS::SDK_VERSION, VWO::CONSTANTS::SEED_VALUE, VWO::CONSTANTS::STATUS_RUNNING, VWO::CONSTANTS::URL_NAMESPACE, VWO::CONSTANTS::VWO_DELIMITER

Instance Method Summary collapse

Methods included from Utils::Validations

#invalid_config_log, #valid_basic_data_type?, #valid_batch_event_settings, #valid_boolean?, #valid_config_log, #valid_goal?, #valid_hash?, #valid_number?, #valid_settings_file?, #valid_string?, #valid_value?, #validate_sdk_config?

Constructor Details

#initializeBucketer

Returns a new instance of Bucketer.



37
38
39
# File 'lib/vwo/core/bucketer.rb', line 37

def initialize
  @logger = VWO::Utils::Logger
end

Instance Method Details

#bucket_user_to_variation(user_id, campaign, disable_logs = false) ⇒ Object

Validates the User ID and Generates Variation into which the User is bucketed to

@param :user_id The unique ID assigned to User @param :campaign The Campaign of which User is a part of @param :disable_logs if true, do not log log-message

@return[Hash|nil} Variation data into which user is bucketed to

or nil if not


88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/vwo/core/bucketer.rb', line 88

def bucket_user_to_variation(user_id, campaign, disable_logs = false)
  unless valid_value?(user_id)
    @logger.log(
      LogLevelEnum::ERROR,
      'USER_ID_INVALID',
      {
        '{file}' => FILE,
        '{userId}' => user_id
      }
    )
    return
  end

  return unless campaign

  isNBv2 = VWO::Utils::GetAccountFlags.get_instance.get_isNbv2_flag
  isNB = VWO::Utils::GetAccountFlags.get_instance.get_isNB_flag
   = VWO::Utils::GetAccountFlags.get_instance.

  user_id_for_hash_value = user_id
  multiplier = MAX_TRAFFIC_VALUE.to_f / campaign['percentTraffic'] / 100

  if ((!isNB && !isNBv2) || (isNB && campaign['isOB'])) && campaign['percentTraffic']
    # Old bucketing logic if feature flag is OFF or
    # Feature flag is ON and campaign is old i.e. created before feature flag was turned ON
    user_id_for_hash_value = "#{campaign['id']}_#{user_id}" if campaign['isBucketingSeedEnabled']
    multiplier = MAX_TRAFFIC_VALUE.to_f / campaign['percentTraffic'] / 100
  elsif ((isNB && !campaign['isOB'] && !isNBv2) || (isNBv2 && campaign['isOBv2']))
    # New bucketing logic if feature flag is ON and campaign is new i.e. created after feature flag was turned ON
    user_id_for_hash_value = user_id
    multiplier = 1
  else
    # new bucketing V2 Logic
    user_id_for_hash_value = "#{campaign['id']}_#{}_#{user_id}"
    multiplier = 1
  end

  hash_value = MurmurHash3::V32.str_hash(user_id_for_hash_value, SEED_VALUE) & U_MAX_32_BIT
  bucket_value = get_bucket_value(
    hash_value,
    MAX_TRAFFIC_VALUE,
    multiplier
  )

  @logger.log(
    LogLevelEnum::DEBUG,
    'USER_CAMPAIGN_BUCKET_VALUES',
    {
      '{file}' => FILE,
      '{campaignKey}' => campaign['key'],
      '{userId}' => user_id,
      '{percentTraffic}' => campaign['percentTraffic'],
      '{hashValue}' => hash_value,
      '{bucketValue}' => bucket_value
    },
    disable_logs
  )

  get_variation(campaign['variations'], bucket_value)
end

#get_bucket_value(hash_value, max_value, multiplier = 1) ⇒ Object

Generates Bucket Value of the User by hashing the User ID by murmurHash And scaling it down.

@param :hash_value HashValue generated after hashing @param :max_value The value up-to which hashValue needs to be scaled @param :multiplier @return Bucket Value of the User



209
210
211
212
213
# File 'lib/vwo/core/bucketer.rb', line 209

def get_bucket_value(hash_value, max_value, multiplier = 1)
  ratio = hash_value.to_f / MAX_HASH_VALUE
  multiplied_value = (max_value * ratio + 1) * multiplier
  multiplied_value.to_i
end

#get_bucket_value_for_user(user_id, campaign = {}, group_id = nil, disable_logs = false) ⇒ Object

Validates the User ID and generates Bucket Value of the User by hashing the userId by murmurHash and scaling it down.

@param :user_id The unique ID assigned to User @param :campaign Campaign data @return The bucket Value allotted to User

(between 1 to $this->$MAX_TRAFFIC_PERCENT)


169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/vwo/core/bucketer.rb', line 169

def get_bucket_value_for_user(user_id, campaign = {}, group_id = nil, disable_logs = false)
  user_id_for_hash_value = user_id
  if group_id
    user_id_for_hash_value = "#{group_id}_#{user_id}"
  elsif campaign['isBucketingSeedEnabled']
    user_id_for_hash_value = "#{campaign['id']}_#{user_id}"
  end

  isNBv2 = VWO::Utils::GetAccountFlags.get_instance.get_isNbv2_flag
  isNB = VWO::Utils::GetAccountFlags.get_instance.get_isNB_flag

  if isNBv2 || isNB || campaign['isBucketingSeedEnabled'] 
    user_id_for_hash_value = "#{campaign['id']}_#{user_id}"
  end

  hash_value = MurmurHash3::V32.str_hash(user_id_for_hash_value, SEED_VALUE) & U_MAX_32_BIT
  bucket_value = get_bucket_value(hash_value, MAX_TRAFFIC_PERCENT)

  @logger.log(
    LogLevelEnum::DEBUG,
    'USER_HASH_BUCKET_VALUE',
    {
      '{file}' => FILE,
      '{hashValue}' => hash_value,
      '{userId}' => user_id,
      '{bucketValue}' => bucket_value
    },
    disable_logs
  )
  bucket_value
end

#get_campaign_using_range(range_for_campaigns, campaigns) ⇒ Object

Returns a campaign by checking the Start and End Bucket Allocations of each campaign.

@param :range_for_campaigns the bucket value of the user @param :campaigns The bucket Value of the user @return



221
222
223
224
225
226
227
# File 'lib/vwo/core/bucketer.rb', line 221

def get_campaign_using_range(range_for_campaigns, campaigns)
  range_for_campaigns *= 100
  campaigns.each do |campaign|
    return campaign if campaign['max_range'] && campaign['max_range'] >= range_for_campaigns && campaign['min_range'] <= range_for_campaigns
  end
  nil
end

#get_variation(variations, bucket_value) ⇒ Object

Returns the Variation by checking the Start and End Bucket Allocations of each Variation

@param :campaign Which contains the variations @param :bucket_value The bucket Value of the user @return Variation data allotted to the user or None if not



156
157
158
159
160
# File 'lib/vwo/core/bucketer.rb', line 156

def get_variation(variations, bucket_value)
  variations.find do |variation|
    (variation['start_variation_allocation']..variation['end_variation_allocation']).cover?(bucket_value)
  end
end

#user_part_of_campaign?(user_id, campaign, disable_logs = false) ⇒ Boolean

Calculate if this user should become part of the campaign or not @param :user_id The unique ID assigned to a user @param :campaign For getting traffic allotted to the campaign @param :disable_logs if true, do not log log-message @return If User is a part of Campaign or not

Returns:

  • (Boolean)


47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/vwo/core/bucketer.rb', line 47

def user_part_of_campaign?(user_id, campaign, disable_logs = false)
  unless valid_value?(user_id)
    @logger.log(
      LogLevelEnum::ERROR,
      'USER_ID_INVALID',
      {
        '{file}' => FILE,
        '{userId}' => user_id
      }
    )
    return false
  end

  return false if campaign.nil?

  traffic_allocation = campaign['percentTraffic']
  value_assigned_to_user = get_bucket_value_for_user(user_id, campaign, nil, disable_logs)
  is_user_part = (value_assigned_to_user != 0) && value_assigned_to_user <= traffic_allocation
  @logger.log(
    LogLevelEnum::INFO,
    'USER_CAMPAIGN_ELIGIBILITY',
    {
      '{file}' => FILE,
      '{userId}' => user_id,
      '{status}' => is_user_part ? 'eligible' : 'not eligible',
      '{campaignKey}' => campaign['key']
    },
    disable_logs
  )
  is_user_part
end