Skip to content

Commit

Permalink
fix(bucketingAlgo): same user in multiple similar campaigns should ge…
Browse files Browse the repository at this point in the history
…t different variations
  • Loading branch information
rohitesh-wingify committed Oct 13, 2023
1 parent 6b99696 commit 7b81015
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 6 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ 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.62.0] - 2023-10-13

### Fixed

- When a same user becomes part of multiple similar campaigns they should get different variations

## [1.60.0] - 2023-09-28

### Added
Expand Down
13 changes: 10 additions & 3 deletions src/Core/Bucketer.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public static function getBucketVariationId($campaign, $variationName)
* @param bool $disableLogs optional: disable logs if True
* @return array|null
*/
public static function getBucket($userId, $campaign, $is_new_bucketing_enabled, $disableLogs = false)
public static function getBucket($userId, $campaign, $is_new_bucketing_enabled, $is_new_bucketing_v2_enabled = false, $accountId=null, $disableLogs = false)
{
// if bucketing to be done - check first if user is part of campaign
list($bucketVal, $hashValue) = self::getBucketVal($userId, $campaign, $is_new_bucketing_enabled, $disableLogs);
Expand All @@ -88,20 +88,27 @@ public static function getBucket($userId, $campaign, $is_new_bucketing_enabled,
}

// based on bucketing algo flag, determine bucket value
if(!$is_new_bucketing_enabled || (isset($campaign["isOB"]) && $campaign["isOB"])){
if((!$is_new_bucketing_enabled && !$is_new_bucketing_v2_enabled)|| ($is_new_bucketing_enabled && isset($campaign["isOB"]) && $campaign["isOB"])){
// old algo
$multiplier = self::getMultiplier($campaign['percentTraffic'], $disableLogs);
list($bucketVal, $hashValue) = self::getBucketVal($userId, $campaign, $is_new_bucketing_enabled, $disableLogs);

// log for type of algo
LoggerService::log(Logger::INFO, 'Using Old Algo!');
} else {
} else if (($is_new_bucketing_enabled && !isset($campaign["isOB"]) && !$is_new_bucketing_v2_enabled) || ($is_new_bucketing_v2_enabled && isset($campaign["isOBv2"]) && $campaign["isOBv2"])){
// new algo
$multiplier = 1;
list($bucketVal, $hashValue) = self::getBucketVal($userId, null, $is_new_bucketing_enabled, $disableLogs);

// log for type of algo
LoggerService::log(Logger::INFO, 'Using New Algo!');
} else {
// new v2 algo
$multiplier = 1;
list($bucketVal, $hashValue) = self::getBucketVal($userId = strval($accountId) . "_" . strval($userId), $campaign, $is_new_bucketing_enabled = true, $disableLogs);

// log for type of algo
LoggerService::log(Logger::INFO, 'Using New V2 Algo!');
}

$rangeForVariations = self::getRangeForVariations($bucketVal, $multiplier);
Expand Down
25 changes: 23 additions & 2 deletions src/Core/VariationDecider.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ public function fetchVariationData($userStorageObj, $campaign, $userId, $options

$isCampaignPartOfGroup = $this->settings && CampaignUtil::isPartOfGroup($this->settings, $campaign["id"]);
$campaignKey = $campaign['key'];
if($this->settings!=null){
$accountId = $this->settings["accountId"];
}
$decision['isUserWhitelisted'] = false;
$decision['fromUserStorageService'] = false;

Expand All @@ -119,6 +122,13 @@ public function fetchVariationData($userStorageObj, $campaign, $userId, $options
$is_new_bucketing_enabled = false;
}

# get new bucketing V2 enabled flag from settings
if ($this->settings!=null && isset($this->settings["isNBv2"]) && $this->settings["isNBv2"]) {
$is_new_bucketing_v2_enabled = true;
} else {
$is_new_bucketing_v2_enabled = false;
}

// VWO generated UUID based on passed UserId and Account ID
if (isset($this->accountId)) {
$decision['vwoUserId'] = UuidUtil::get($userId, $this->accountId);
Expand Down Expand Up @@ -208,7 +218,7 @@ public function fetchVariationData($userStorageObj, $campaign, $userId, $options
self::CLASSNAME
);
if ($winnerCampaign && $winnerCampaign["id"] == $campaign["id"]) {
$bucketInfo = Bucketer::getBucket($userId, $campaign, $is_new_bucketing_enabled);
$bucketInfo = Bucketer::getBucket($userId, $campaign, $is_new_bucketing_enabled, $is_new_bucketing_v2_enabled, $accountId);
if ($bucketInfo == null) {
return $bucketInfo;
} else {
Expand Down Expand Up @@ -657,6 +667,10 @@ private static function checkCampaignNotActivated($apiName, $userStorageObj, $us
private function getVariationIfPreSegmentationApplied($isPreSegmentation, $campaign, $userId, $userStorageObj = null, $goalIdentifier = '')
{
$bucketInfo = null;
$accountId = null;
if($this->settings!=null){
$accountId = $this->settings["accountId"];
}
//check for pre-segmentation if applied
if ($isPreSegmentation == false) {
LoggerService::log(
Expand All @@ -679,7 +693,14 @@ private function getVariationIfPreSegmentationApplied($isPreSegmentation, $campa
$is_new_bucketing_enabled = false;
}

$bucketInfo = Bucketer::getBucket($userId, $campaign, $is_new_bucketing_enabled);
# get new bucketing V2 enabled flag from settings
if ($this->settings!=null && isset($this->settings["isNBv2"]) && $this->settings["isNBv2"]) {
$is_new_bucketing_v2_enabled = true;
} else {
$is_new_bucketing_v2_enabled = false;
}

$bucketInfo = Bucketer::getBucket($userId, $campaign, $is_new_bucketing_enabled, $is_new_bucketing_v2_enabled, $accountId);
LoggerService::log(
Logger::INFO,
'USER_VARIATION_ALLOCATION_STATUS',
Expand Down
2 changes: 1 addition & 1 deletion src/Utils/ImpressionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ImpressionBuilder
/**
* sdk version for api hit
*/
const SDK_VERSION = '1.60.0';
const SDK_VERSION = '1.62.0';
/**
* sdk langauge for api hit
*/
Expand Down
34 changes: 34 additions & 0 deletions tests/Core/BucketerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,38 @@ public function testWithIsNBAndNewBucketingLogicWithoutIsOBAndSeedFlag()
$this->assertEquals($expected[$userId], $result);
}
}

public function testSameUserMultipleCampaignsWithIsNBv2AndWithoutIsOBv2()
{
// initializations
$settings = new SettingsFileBucketing();
$settingsFile = $settings->setting_with_isNBv2_and_without_isOBv2_and_without_seed_flag;
$vwoInstance = TestUtil::instantiateSdk($settingsFile, ['isDevelopmentMode' => 1]);
$campaigns = $settingsFile['campaigns'];

//expected result array
$expected = $this->variationResults->results['BUCKET_ALGO_WITHOUT_SEED_FLAG_WITH_isNBV2_WITHOUT_isOBV2'];

foreach($campaigns as $i => $campaign){
$result[] = $vwoInstance->activate($campaign['key'], 'Ashley');
}
$this->assertEquals($expected, $result);
}

public function testSameUserMultipleCampaignsWithIsNBv2AndIsOBv2()
{
// initializations
$settings = new SettingsFileBucketing();
$settingsFile = $settings->setting_with_isNBv2_and_with_isOBv2_and_without_seed_flag;
$vwoInstance = TestUtil::instantiateSdk($settingsFile, ['isDevelopmentMode' => 1]);
$campaigns = $settingsFile['campaigns'];

//expected result array
$expected = $this->variationResults->results['BUCKET_ALGO_WITHOUT_SEED_FLAG_WITH_isNBV2_WITH_isOBV2'];

foreach($campaigns as $i => $campaign){
$result[] = $vwoInstance->activate($campaign['key'], 'Ashley');
}
$this->assertEquals($expected, $result);
}
}
201 changes: 201 additions & 0 deletions tests/assets/SettingsFileBucketing.php
Original file line number Diff line number Diff line change
Expand Up @@ -218,4 +218,205 @@ class SettingsFileBucketing
'accountId' => 123456,
'version' => 1
];

var $setting_with_isNBv2_and_without_isOBv2_and_without_seed_flag = [
'sdkKey' => 'loremipsum123456',
'isNBv2' => true,
'campaigns' => [
[
'goals' => [
[
'identifier' => 'CUSTOM',
'id' => 213,
'type' => 'CUSTOM_GOAL'
]
],
'variations' => [
[
'id' => 1,
'name' => 'Control',
'changes' => [],
'weight' => 50
],
[
'id' => 2,
'name' => 'Variation-1',
'changes' => [],
'weight' => 50
]
],
'id' => 230,
'percentTraffic' => 100,
'key' => 'testKey1',
'name' => 'bucket_algo_without_seed_with_isNB_and_without_isOB',
'status' => 'RUNNING',
'type' => 'VISUAL_AB',
'segments' => [],
],
[
'goals' => [
[
'identifier' => 'CUSTOM',
'id' => 213,
'type' => 'CUSTOM_GOAL'
]
],
'variations' => [
[
'id' => 1,
'name' => 'Control',
'changes' => [],
'weight' => 50
],
[
'id' => 2,
'name' => 'Variation-1',
'changes' => [],
'weight' => 50
]
],
'id' => 231,
'percentTraffic' => 100,
'key' => 'testKey2',
'name' => 'bucket_algo_without_seed_with_isNB_and_without_isOB_2',
'status' => 'RUNNING',
'type' => 'VISUAL_AB',
'segments' => [],
],
[
'goals' => [
[
'identifier' => 'CUSTOM',
'id' => 213,
'type' => 'CUSTOM_GOAL'
]
],
'variations' => [
[
'id' => 1,
'name' => 'Control',
'changes' => [],
'weight' => 50
],
[
'id' => 2,
'name' => 'Variation-1',
'changes' => [],
'weight' => 50
]
],
'id' => 232,
'percentTraffic' => 100,
'key' => 'testKey3',
'name' => 'bucket_algo_without_seed_with_isNB_and_without_isOB_3',
'status' => 'RUNNING',
'type' => 'VISUAL_AB',
'segments' => [],
]
],
'accountId' => 123456,
'version' => 1
];

var $setting_with_isNBv2_and_with_isOBv2_and_without_seed_flag = [
'sdkKey' => 'loremipsum123456',
'isNBv2' => true,
'campaigns' => [
[
'goals' => [
[
'identifier' => 'CUSTOM',
'id' => 213,
'type' => 'CUSTOM_GOAL'
]
],
'variations' => [
[
'id' => 1,
'name' => 'Control',
'changes' => [],
'weight' => 50
],
[
'id' => 2,
'name' => 'Variation-1',
'changes' => [],
'weight' => 50
]
],
'id' => 230,
'isOBv2' => true,
'percentTraffic' => 100,
'key' => 'testKey1',
'name' => 'bucket_algo_without_seed_with_isNB_and_without_isOB',
'status' => 'RUNNING',
'type' => 'VISUAL_AB',
'segments' => [],
],
[
'goals' => [
[
'identifier' => 'CUSTOM',
'id' => 213,
'type' => 'CUSTOM_GOAL'
]
],
'variations' => [
[
'id' => 1,
'name' => 'Control',
'changes' => [],
'weight' => 50
],
[
'id' => 2,
'name' => 'Variation-1',
'changes' => [],
'weight' => 50
]
],
'id' => 231,
'isOBv2' => true,
'percentTraffic' => 100,
'key' => 'testKey2',
'name' => 'bucket_algo_without_seed_with_isNB_and_without_isOB_2',
'status' => 'RUNNING',
'type' => 'VISUAL_AB',
'segments' => [],
],
[
'goals' => [
[
'identifier' => 'CUSTOM',
'id' => 213,
'type' => 'CUSTOM_GOAL'
]
],
'variations' => [
[
'id' => 1,
'name' => 'Control',
'changes' => [],
'weight' => 50
],
[
'id' => 2,
'name' => 'Variation-1',
'changes' => [],
'weight' => 50
]
],
'id' => 232,
'isOBv2' => true,
'percentTraffic' => 100,
'key' => 'testKey3',
'name' => 'bucket_algo_without_seed_with_isNB_and_without_isOB_3',
'status' => 'RUNNING',
'type' => 'VISUAL_AB',
'segments' => [],
]
],
'accountId' => 123456,
'version' => 1
];
}
9 changes: 9 additions & 0 deletions tests/assets/VariationResults.php
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,16 @@ class VariationResults
'Xin' => 'Control' ,
'You' => 'Variation-1' ,
'Zeba' => 'Control'
],

'BUCKET_ALGO_WITHOUT_SEED_FLAG_WITH_isNBV2_WITHOUT_isOBV2' => [
'Control','Variation-1','Control'
],

'BUCKET_ALGO_WITHOUT_SEED_FLAG_WITH_isNBV2_WITH_isOBV2' => [
'Control','Control','Control'
]


];
}

0 comments on commit 7b81015

Please sign in to comment.