diff --git a/src/Optimizely/Config/DatafileProjectConfig.php b/src/Optimizely/Config/DatafileProjectConfig.php index 5f071b5b..9663a924 100644 --- a/src/Optimizely/Config/DatafileProjectConfig.php +++ b/src/Optimizely/Config/DatafileProjectConfig.php @@ -93,6 +93,16 @@ class DatafileProjectConfig implements ProjectConfigInterface */ private $datafile; + /** + * @var string environmentKey of the config. + */ + private $environmentKey; + + /** + * @var string sdkKey of the config. + */ + private $sdkKey; + /** * @var string Revision of the datafile. */ @@ -172,6 +182,34 @@ class DatafileProjectConfig implements ProjectConfigInterface */ private $_rollouts; + /** + * list of Attributes that will be parsed from the datafile + * + * @var [Attribute] + */ + private $attributes; + + /** + * list of Audiences that will be parsed from the datafile + * + * @var [Audience] + */ + private $audiences; + + /** + * list of Events that will be parsed from the datafile + * + * @var [Event] + */ + private $events; + + /** + * list of Typed Audiences that will be parsed from the datafile + * + * @var [Audience] + */ + private $typedAudiences; + /** * internal mapping of feature keys to feature flag models. * @@ -222,6 +260,8 @@ public function __construct($datafile, $logger, $errorHandler) $this->_logger = $logger; $this->_errorHandler = $errorHandler; $this->_version = $config['version']; + $this->environmentKey = isset($config['environmentKey']) ? $config['environmentKey'] : ''; + $this->sdkKey = isset($config['sdkKey']) ? $config['sdkKey'] : ''; if (!in_array($this->_version, $supportedVersions)) { throw new InvalidDatafileVersionException( "This version of the PHP SDK does not support the given datafile version: {$this->_version}." @@ -237,10 +277,10 @@ public function __construct($datafile, $logger, $errorHandler) $groups = $config['groups'] ?: []; $experiments = $config['experiments'] ?: []; - $events = $config['events'] ?: []; - $attributes = $config['attributes'] ?: []; - $audiences = $config['audiences'] ?: []; - $typedAudiences = isset($config['typedAudiences']) ? $config['typedAudiences']: []; + $this->attributes = isset($config['attributes']) ? $config['attributes'] : []; + $this->audiences = isset($config['audiences']) ? $config['audiences'] : []; + $this->events = isset($config['events']) ? $config['events'] : []; + $this->typedAudiences = isset($config['typedAudiences']) ? $config['typedAudiences'] : []; $rollouts = isset($config['rollouts']) ? $config['rollouts'] : []; $featureFlags = isset($config['featureFlags']) ? $config['featureFlags']: []; @@ -258,10 +298,10 @@ public function __construct($datafile, $logger, $errorHandler) $this->_groupIdMap = ConfigParser::generateMap($groups, 'id', Group::class); $this->_experimentIdMap = ConfigParser::generateMap($experiments, 'id', Experiment::class); - $this->_eventKeyMap = ConfigParser::generateMap($events, 'key', Event::class); - $this->_attributeKeyMap = ConfigParser::generateMap($attributes, 'key', Attribute::class); - $typedAudienceIdMap = ConfigParser::generateMap($typedAudiences, 'id', Audience::class); - $this->_audienceIdMap = ConfigParser::generateMap($audiences, 'id', Audience::class); + $this->_eventKeyMap = ConfigParser::generateMap($this->events, 'key', Event::class); + $this->_attributeKeyMap = ConfigParser::generateMap($this->attributes, 'key', Attribute::class); + $typedAudienceIdMap = ConfigParser::generateMap($this->typedAudiences, 'id', Audience::class); + $this->_audienceIdMap = ConfigParser::generateMap($this->audiences, 'id', Audience::class); $this->_rollouts = ConfigParser::generateMap($rollouts, null, Rollout::class); $this->_featureFlags = ConfigParser::generateMap($featureFlags, null, FeatureFlag::class); @@ -449,6 +489,22 @@ public function getRevision() return $this->_revision; } + /** + * @return string Config environmentKey. + */ + public function getEnvironmentKey() + { + return $this->environmentKey; + } + + /** + * @return string Config sdkKey. + */ + public function getSdkKey() + { + return $this->sdkKey; + } + /** * @return array List of feature flags parsed from the datafile */ @@ -457,6 +513,38 @@ public function getFeatureFlags() return $this->_featureFlags; } + /** + * @return array List of attributes parsed from the datafile + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * @return array List of audiences parsed from the datafile + */ + public function getAudiences() + { + return $this->audiences; + } + + /** + * @return array List of events parsed from the datafile + */ + public function getEvents() + { + return $this->events; + } + + /** + * @return array List of typed audiences parsed from the datafile + */ + public function getTypedAudiences() + { + return $this->typedAudiences; + } + /** * @return array List of all experiments (including group experiments) * parsed from the datafile @@ -470,7 +558,7 @@ public function getAllExperiments() $rolloutExperimentIds[] = $experiment->getId(); } } - return array_filter(array_values($this->_experimentKeyMap), function ($experiment) use ($rolloutExperimentIds) { + return array_filter(array_values($this->_experimentIdMap), function ($experiment) use ($rolloutExperimentIds) { return !in_array($experiment->getId(), $rolloutExperimentIds); }); } diff --git a/src/Optimizely/Config/ProjectConfigInterface.php b/src/Optimizely/Config/ProjectConfigInterface.php index 53a6d6c2..887e706c 100644 --- a/src/Optimizely/Config/ProjectConfigInterface.php +++ b/src/Optimizely/Config/ProjectConfigInterface.php @@ -51,6 +51,37 @@ public function getBotFiltering(); */ public function getRevision(); + /** + * @return string String representing environment key of the datafile. + */ + public function getEnvironmentKey(); + + /** + * @return string String representing sdkkey of the datafile. + */ + public function getSdkKey(); + + /** + * @return array List of attributes parsed from the datafile + */ + public function getAttributes(); + + /** + * @return array List of audiences parsed from the datafile + */ + public function getAudiences(); + + /** + * @return array List of events parsed from the datafile + */ + public function getEvents(); + + /** + * @return array List of typed audiences parsed from the datafile + */ + public function getTypedAudiences(); + + /** * @return array List of feature flags parsed from the datafile */ diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyAttribute.php b/src/Optimizely/OptimizelyConfig/OptimizelyAttribute.php new file mode 100644 index 00000000..d3eee9b9 --- /dev/null +++ b/src/Optimizely/OptimizelyConfig/OptimizelyAttribute.php @@ -0,0 +1,60 @@ +id = $id; + $this->key = $key; + } + + /** + * @return string attribute id. + */ + public function getId() + { + return $this->id; + } + + /** + * @return string attribute key. + */ + public function getKey() + { + return $this->key; + } + + /** + * @return string JSON representation of the object. + */ + public function jsonSerialize() + { + return get_object_vars($this); + } +} diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyAudience.php b/src/Optimizely/OptimizelyConfig/OptimizelyAudience.php new file mode 100644 index 00000000..e331871a --- /dev/null +++ b/src/Optimizely/OptimizelyConfig/OptimizelyAudience.php @@ -0,0 +1,75 @@ +id = $id; + $this->name = $name; + $this->conditions = $conditions; + } + + /** + * @return string audience id. + */ + public function getId() + { + return $this->id; + } + + /** + * @return string audience name. + */ + public function getName() + { + return $this->name; + } + + /** + * @return string audience conditions. + */ + public function getConditions() + { + return $this->conditions; + } + + /** + * @return string JSON representation of the object. + */ + public function jsonSerialize() + { + return get_object_vars($this); + } +} diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php index fdd5736c..26093779 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php @@ -1,6 +1,6 @@ associative array */ @@ -37,20 +51,62 @@ class OptimizelyConfig implements \JsonSerializable */ private $featuresMap; + /** + * Array of attributes as OptimizelyAttribute. + * + * @var [OptimizelyAttribute] + */ + private $attributes; + + /** + * Array of audiences as OptimizelyAudience. + * + * @var [OptimizelyAudience] + */ + private $audiences; + + /** + * Array of events as OptimizelyEvent. + * + * @var [OptimizelyEvent] + */ + private $events; + /** * @var string Contents of datafile. */ private $datafile; - public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile = null) + public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile = null, $environmentKey = '', $sdkKey = '', $attributes = [], $audiences = [], $events = []) { + $this->environmentKey = $environmentKey; + $this->sdkKey = $sdkKey; $this->revision = $revision; $this->experimentsMap = $experimentsMap; $this->featuresMap = $featuresMap; + $this->attributes = $attributes; + $this->audiences = $audiences; + $this->events = $events; $this->datafile = $datafile; } + /** + * @return string Config environmentKey. + */ + public function getEnvironmentKey() + { + return $this->environmentKey; + } + + /** + * @return string Config sdkKey. + */ + public function getSdkKey() + { + return $this->sdkKey; + } + /** * @return string Config revision. */ @@ -83,6 +139,30 @@ public function getFeaturesMap() return $this->featuresMap; } + /** + * @return array Attributes as OptimizelyAttribute. + */ + public function getAttributes() + { + return $this->attributes; + } + + /** + * @return array Audiences as OptimizelyAudience. + */ + public function getAudiences() + { + return $this->audiences; + } + + /** + * @return array Events as OptimizelyEvent. + */ + public function getEvents() + { + return $this->events; + } + /** * @return string JSON representation of the object. */ diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php index eef1829a..d2dadb7c 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php @@ -1,6 +1,6 @@ featureFlags = $projectConfig->getFeatureFlags(); $this->revision = $projectConfig->getRevision(); $this->datafile = $projectConfig->toDatafile(); + $this->environmentKey = $projectConfig->getEnvironmentKey(); + $this->sdkKey = $projectConfig->getSdkKey(); + $this->projectConfig = $projectConfig; $this->createLookupMaps(); } @@ -80,15 +93,107 @@ public function getConfig() { $experimentsMaps = $this->getExperimentsMaps(); $featuresMap = $this->getFeaturesMap($experimentsMaps[1]); + $attributes = $this->getConfigAttributes(); + $audiences = $this->getConfigAudiences(); + $events = $this->getConfigEvents(); return new OptimizelyConfig( $this->revision, + # This experimentsMap is for experiments of legacy projects only. + # For flag projects, experiment keys are not guaranteed to be unique + # across multiple flags, so this map may not include all experiments + # when keys conflict. Use experimentRules and deliveryRules instead. $experimentsMaps[0], $featuresMap, - $this->datafile + $this->datafile, + $this->environmentKey, + $this->sdkKey, + $attributes, + $audiences, + $events ); } - + + /** + * Generates array of attributes as OptimizelyAttribute. + * + * @return array of OptimizelyAttributes. + */ + protected function getConfigAttributes() + { + $attributeArray = []; + $attributes = $this->projectConfig->getAttributes(); + foreach ($attributes as $attr) { + $optlyAttr = new OptimizelyAttribute( + $attr['id'], + $attr['key'] + ); + array_push($attributeArray, $optlyAttr); + } + return $attributeArray; + } + + + /** + * Generates array of events as OptimizelyEvents. + * + * @return array of OptimizelyEvents. + */ + protected function getConfigEvents() + { + $eventsArray = []; + $events = $this->projectConfig->getEvents(); + foreach ($events as $event) { + $optlyEvent = new OptimizelyEvent( + $event['id'], + $event['key'], + $event['experimentIds'] + ); + $eventsArray[] = $optlyEvent; + } + return $eventsArray; + } + + /** + * Generates array of audiences giving typed audiences high priority as OptimizelyAudience. + * + * @return array of OptimizelyEvents. + */ + protected function getConfigAudiences() + { + $allAudiences = []; + $typedAudienceIds = []; + $audiences = $this->projectConfig->getAudiences(); + $typedAudiences = $this->projectConfig->getTypedAudiences(); + $audiencesArray = $typedAudiences; + foreach ($audiencesArray as $key => $typedAudience) { + $typedAudienceIds[] = $typedAudience['id']; + $id = $typedAudience['id']; + if ($id != '$opt_dummy_audience') { + $optlyAudience = new OptimizelyAudience( + $id, + $typedAudience['name'], + json_encode($typedAudience['conditions']) + ); + array_push($allAudiences, $optlyAudience); + } + } + foreach ($audiences as $naudience) { + if (!in_array($naudience['id'], $typedAudienceIds, true)) { + $id = $naudience['id']; + if ($id != '$opt_dummy_audience') { + $optlyAudience = new OptimizelyAudience( + $id, + $naudience['name'], + $naudience['conditions'] + ); + array_push($allAudiences, $optlyAudience); + } + } + } + return $allAudiences; + } + /** * Generates lookup maps to avoid redundant iteration while creating OptimizelyConfig. */ @@ -103,7 +208,11 @@ protected function createLookupMaps() foreach ($feature->getExperimentIds() as $expId) { $this->experimentIdFeatureMap[$expId] = $feature; } - + $rolloutID = $feature->getRolloutId(); + $rollout = $this->projectConfig->getRolloutFromId($rolloutID); + foreach ($rollout->getExperiments() as $exp) { + $this->experimentIdFeatureMap[$exp->getId()] = $feature; + } # Populate featKeyOptlyVariableKeyVariableMap and featKeyOptlyVariableIdVariableMap $variablesKeyMap = []; $variablesIdMap = []; @@ -207,6 +316,75 @@ protected function getVariationsMap(Experiment $experiment) return $variationsMap; } + /** + * Converts array of audience conditions to serialized audiences. + * + * for examples: + * 1. Input: ["or", "1", "2"] + * Output: "us" OR "female" + * 2. Input: ["not", "1"] + * Output: "NOT "us" + * 3. Input: ["or", "1"] + * Output: "us" + * 4. Input: ["and", ["or", "1", ["and", "2", "3"]], ["and", "11", ["or", "12", "13"]]] + * Output: "("us" OR ("female" AND "adult")) AND ("fr" AND ("male" OR "kid"))" + * + * @param array audience conditions . + * + * @return string of experiment audience conditions. + */ + protected function getSerializedAudiences(array $audienceConditions) + { + $operators = ['and', 'or', 'not']; + $finalAudiences = ''; + if ($audienceConditions == null) { + return $finalAudiences; + } + $cond = ''; + foreach ($audienceConditions as $var) { + $subAudience = ''; + // Checks if item is list of conditions means if it is sub audience + if (is_array($var)) { + $subAudience = '(' . $this->getSerializedAudiences($var) . ')'; + } elseif (in_array($var, $operators, true)) { + $cond = strtoupper(strval($var)); + } else { + // Checks if item is audience id + $itemStr = strval($var); + $audience = $this->projectConfig->getAudience($itemStr); + $name = $audience == null ? $itemStr : $audience->getName(); + // if audience condition is "NOT" then add "NOT" at start. Otherwise check if there is already audience id in finalAudiences then append condition between finalAudiences and item + if ($finalAudiences !== '' || $cond == "NOT") { + if ($finalAudiences !== '') { + $finalAudiences .= ' '; + } + if ($cond == '') { + $cond = 'OR'; + } + $finalAudiences .= $cond . ' ' . '"' . $name . '"'; + } else { + $finalAudiences = '"' . $name . '"'; + } + } + // Checks if sub audience is empty or not + if ($subAudience !== '') { + if ($finalAudiences !== '' || $cond == "NOT") { + if ($finalAudiences !== '') { + $finalAudiences .= ' '; + } + if ($cond == '') { + $cond = 'OR'; + } + $finalAudiences = $finalAudiences . $cond . ' ' . $subAudience; + } else { + $finalAudiences = $finalAudiences . $subAudience; + } + } + } + return $finalAudiences; + } + + /** * Generates OptimizelyExperiment Key and ID Maps. * Returns an array with @@ -223,11 +401,16 @@ protected function getExperimentsMaps() foreach ($this->experiments as $exp) { $expId = $exp->getId(); $expKey = $exp->getKey(); - + $audiences = ''; + if ($exp->getAudienceConditions() != null) { + $audienceConditions = $exp->getAudienceConditions(); + $audiences = $this->getSerializedAudiences($audienceConditions); + } $optExp = new OptimizelyExperiment( $expId, $expKey, - $this->getVariationsMap($exp) + $this->getVariationsMap($exp), + $audiences ); $experimentsKeyMap[$expKey] = $optExp; @@ -237,6 +420,39 @@ protected function getExperimentsMaps() return [$experimentsKeyMap, $experimentsIdMap]; } + /** + * Generates array of delivery rules for optimizelyFeature. + * + * @param string feature rollout id. + * + * @return array of optimizelyExperiments as delivery rules. + */ + protected function getDeliveryRules($rollout_id) + { + $deliveryRules = []; + $rollout = $this->projectConfig->getRolloutFromId($rollout_id); + $experiments = $rollout->getExperiments(); + foreach ($experiments as $exp) { + $expId = $exp->getId(); + $expKey = $exp->getKey(); + $audiences = ''; + if ($exp->getAudienceConditions() != null) { + $audienceConditions = $exp->getAudienceConditions(); + $audiences = $this->getSerializedAudiences($audienceConditions); + } + $optExp = new OptimizelyExperiment( + $expId, + $expKey, + $this->getVariationsMap($exp), + $audiences + ); + array_push($deliveryRules, $optExp); + } + + return $deliveryRules; + } + + /** * Generates Features map for the project config. * @@ -251,10 +467,15 @@ protected function getFeaturesMap(array $experimentsIdMap) foreach ($this->featureFlags as $feature) { $featureKey = $feature->getKey(); $experimentsMap = []; - + $experimentRules = []; + $deliveryRules = []; + if ($feature->getRolloutId() != null) { + $deliveryRules = $this->getDeliveryRules($feature->getRolloutId()); + } foreach ($feature->getExperimentIds() as $expId) { $optExp = $experimentsIdMap[$expId]; $experimentsMap[$optExp->getKey()] = $optExp; + array_push($experimentRules, $optExp); } $variablesMap = $this->featKeyOptlyVariableKeyVariableMap[$featureKey]; @@ -262,8 +483,11 @@ protected function getFeaturesMap(array $experimentsIdMap) $optFeature = new OptimizelyFeature( $feature->getId(), $featureKey, + # This experimentsMap is deprecated. Use experimentRules and deliveryRules instead. $experimentsMap, - $variablesMap + $variablesMap, + $experimentRules, + $deliveryRules ); $featuresMap[$featureKey] = $optFeature; diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyEvent.php b/src/Optimizely/OptimizelyConfig/OptimizelyEvent.php new file mode 100644 index 00000000..8a3a90de --- /dev/null +++ b/src/Optimizely/OptimizelyConfig/OptimizelyEvent.php @@ -0,0 +1,75 @@ +id = $id; + $this->key = $key; + $this->experimentIds = $experimentIds; + } + + /** + * @return string event ID. + */ + public function getId() + { + return $this->id; + } + + /** + * @return string event Key. + */ + public function getKey() + { + return $this->key; + } + + /** + * @return array experimentIds representing event experiment ids. + */ + public function getExperimentIds() + { + return $this->experimentIds; + } + + /** + * @return string JSON representation of the object. + */ + public function jsonSerialize() + { + return get_object_vars($this); + } +} diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyExperiment.php b/src/Optimizely/OptimizelyConfig/OptimizelyExperiment.php index ed7b32b5..71bd9959 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyExperiment.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyExperiment.php @@ -1,6 +1,6 @@ id = $id; $this->key = $key; + $this->audiences = $audiences; $this->variationsMap = $variationsMap; } @@ -58,6 +64,14 @@ public function getKey() return $this->key; } + /** + * @return string Experiment audiences. + */ + public function getExperimentAudiences() + { + return $this->audiences; + } + /** * @return array Map of Variation Keys to OptimizelyVariations. */ diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyFeature.php b/src/Optimizely/OptimizelyConfig/OptimizelyFeature.php index 63f1afec..7b605064 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyFeature.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyFeature.php @@ -1,6 +1,6 @@ id = $id; $this->key = $key; + $this->experimentRules = $experimentRules; + $this->deliveryRules = $deliveryRules; $this->experimentsMap = $experimentsMap; $this->variablesMap = $variablesMap; } @@ -66,11 +82,28 @@ public function getKey() return $this->key; } + /** + * @return array array of feature Experiments as OptimizelyExperiments. + */ + public function getExperimentRules() + { + return $this->experimentRules; + } + + /** + * @return array array of Rollout Experiments of feature as OptimizelyExperiments. + */ + public function getDeliveryRules() + { + return $this->deliveryRules; + } + /** * @return array Map of Experiment Keys to OptimizelyExperiments. */ public function getExperimentsMap() { + # This experimentsMap is deprecated. Use experimentRules and deliveryRules instead. return $this->experimentsMap; } diff --git a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php index 0a1d5ad8..5bbaaf23 100644 --- a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php @@ -1,6 +1,6 @@ featExpVariationMap); - - + new OptimizelyExperiment("17279300791", "feat_experiment", $this->featExpVariationMap, ''); + + // creating optimizely Experiment for delivery rules + $boolDeliveryFeatVariable = new OptimizelyVariable('17252790456', 'boolean_var', 'boolean', 'false'); + $intDeliveryFeatVariable = new OptimizelyVariable('17258820367', 'integer_var', 'integer', 1); + $doubleDeliveryFeatVariable = new OptimizelyVariable('17260550714', 'double_var', 'double', 0.5); + $strDeliveryFeatVariable = new OptimizelyVariable('17290540010', 'string_var', 'string', 'i am default value'); + $jsonDeliveryFeatVariable = new OptimizelyVariable('17260550458', 'json_var', 'json', "{\"text\": \"default value\"}"); + + $this->deliveryDefaultVariableKeyMap = []; + $this->deliveryDefaultVariableKeyMap['boolean_var'] = $boolDeliveryFeatVariable; + $this->deliveryDefaultVariableKeyMap['integer_var'] = $intDeliveryFeatVariable; + $this->deliveryDefaultVariableKeyMap['double_var'] = $doubleDeliveryFeatVariable; + $this->deliveryDefaultVariableKeyMap['string_var'] = $strDeliveryFeatVariable; + $this->deliveryDefaultVariableKeyMap['json_var'] = $jsonDeliveryFeatVariable; + $this->deliveryExpVariationMap = []; + $this->deliveryExpVariationMap['17285550838'] = + new OptimizelyVariation('17285550838', '17285550838', true, $this->deliveryDefaultVariableKeyMap); + + $del_Experiment = + new OptimizelyExperiment("17268110732", "17268110732", $this->deliveryExpVariationMap, ''); // create feature $experimentsMap = ['feat_experiment' => $featExperiment]; + $experiment_rules = [$featExperiment]; + $deliver_rules = [$del_Experiment]; $this->feature = new OptimizelyFeature( '17266500726', 'test_feature', $experimentsMap, - $this->expectedDefaultVariableKeyMap + $this->expectedDefaultVariableKeyMap, + $experiment_rules, + $deliver_rules ); // create ab experiment and variations @@ -100,7 +125,7 @@ public function setUp() $variationsMap['variation_a'] = $variationA; $variationsMap['variation_b'] = $variationB; - $abExperiment = new OptimizelyExperiment('17301270474', 'ab_experiment', $variationsMap); + $abExperiment = new OptimizelyExperiment('17301270474', 'ab_experiment', $variationsMap, ''); // create group_ab_experiment and variations $variationA = new OptimizelyVariation('17287500312', 'variation_a', null, []); @@ -110,7 +135,7 @@ public function setUp() $variationsMap['variation_b'] = $variationB; $groupExperiment = - new OptimizelyExperiment('17258450439', 'group_ab_experiment', $variationsMap); + new OptimizelyExperiment('17258450439', 'group_ab_experiment', $variationsMap, ''); // create experiment key map $this->expectedExpKeyMap = []; @@ -172,10 +197,58 @@ public function testGetVariationsMap() $getVariationsMap = self::getMethod("getVariationsMap"); $response = $getVariationsMap->invokeArgs($this->optConfigService, array($featExp)); - $this->assertEquals($this->featExpVariationMap, $response); } + public function testGetOptimizelyConfigWithDuplicateExperimentKeys() + { + $this->datafile = DATAFILE_FOR_DUPLICATE_EXP_KEYS; + $this->projectConfig = new DatafileProjectConfig( + $this->datafile, + new NoOpLogger(), + new NoOpErrorHandler() + ); + $this->optConfigService = new OptimizelyConfigService($this->projectConfig); + $optimizelyConfig = $this->optConfigService->getConfig(); + $this->assertEquals(Count($optimizelyConfig->getExperimentsMap()), 1); + $experimentRulesFlag1 = $optimizelyConfig->getFeaturesMap()['flag1']->getExperimentRules(); // 9300000007569 + $experimentRulesFlag2 = $optimizelyConfig->getFeaturesMap()['flag2']->getExperimentRules(); // 9300000007573 + foreach ($experimentRulesFlag1 as $experimentRule) { + if ($experimentRule->getKey() == 'targeted_delivery') { + $this->assertEquals($experimentRule->getId(), '9300000007569'); + } + } + + foreach ($experimentRulesFlag2 as $experimentRule) { + if ($experimentRule->getKey() == 'targeted_delivery') { + $this->assertEquals($experimentRule->getId(), '9300000007573'); + } + } + } + + public function testGetOptimizelyConfigWithDuplicateRuleKeys() + { + $this->datafile = DATAFILE_FOR_DUPLICATE_RUL_KEYS; + $this->projectConfig = new DatafileProjectConfig( + $this->datafile, + new NoOpLogger(), + new NoOpErrorHandler() + ); + $this->optConfigService = new OptimizelyConfigService($this->projectConfig); + + $optimizelyConfig = $this->optConfigService->getConfig(); + $this->assertEquals(Count($optimizelyConfig->getExperimentsMap()), 0); + $rolloutFlag1 = $optimizelyConfig->getFeaturesMap()["flag_1"]->getDeliveryRules()[0]; // 9300000004977 + $rolloutFlag2 = $optimizelyConfig->getFeaturesMap()["flag_2"]->getDeliveryRules()[0]; // 9300000004979 + $rolloutFlag3 = $optimizelyConfig->getFeaturesMap()["flag_3"]->getDeliveryRules()[0]; // 9300000004981 + $this->assertEquals($rolloutFlag1->getId(), "9300000004977"); + $this->assertEquals($rolloutFlag1->getKey(), "targeted_delivery"); + $this->assertEquals($rolloutFlag2->getId(), "9300000004979"); + $this->assertEquals($rolloutFlag2->getKey(), "targeted_delivery"); + $this->assertEquals($rolloutFlag3->getId(), "9300000004981"); + $this->assertEquals($rolloutFlag3->getKey(), "targeted_delivery"); + } + public function testGetExperimentsMaps() { $getExperimentsMap = self::getMethod("getExperimentsMaps"); @@ -197,6 +270,114 @@ public function testGetFeaturesMap() ); $this->assertEquals(['test_feature' => $this->feature], $response); + foreach ($response as $feature) { + $this->assertInstanceof(OptimizelyFeature::class, $feature); + $experiment_rules = $feature->getExperimentRules(); + $deliver_rules = $feature->getDeliveryRules(); + if (!empty($experiment_rules)) { + foreach ($experiment_rules as $exp) { + $this->assertInstanceof(OptimizelyExperiment::class, $exp); + } + } + if (!empty($deliver_rules)) { + foreach ($deliver_rules as $del) { + $this->assertInstanceof(OptimizelyExperiment::class, $del); + } + } + } + } + + public function testgetExperimentAudiences() + { + $audienceConditions = [ + array("or", "3468206642"), + array('or', '3468206642', '3988293899'), + array('or', '3468206642', '3988293899', '3988293898'), + array("not", "3468206642"), + array('and', '3468206642', '3988293899'), + array("and", "3468206642"), + array('3468206642', '3988293899'), + array("3468206642"), + array('and', array('or', '3468206642', '3988293899'), '3988293898'), + array('and', 'and'), + array(), + array('not', array('and', '3468206642', '3988293899')), + array( + "and", array("or", "3468206642", array( + 'and', '3468206642', '3988293899' + )), + array( + "and", "3468206642", + array('or', '3468206642', '3988293899') + ) + ), + array('or', '1', '100000') + ]; + + $expectedAudienceOutputs = [ + '"' . "exactString" . '"', + '"' . "exactString" . '"' . ' ' . 'OR' . ' ' . '"' . '$$dummyExists' . '"', + '"' . "exactString" . '"' . ' ' . 'OR' . ' ' . '"' . '$$dummyExists' . '"' . ' ' . 'OR' . ' ' . '"' . '$$dummySubstringString' . '"', + 'NOT' . ' ' . '"' . "exactString" . '"', + '"' . "exactString" . '"' . ' ' . 'AND' . ' ' . '"' . '$$dummyExists' . '"', + '"' . "exactString" . '"', + '"' . "exactString" . '"' . ' ' . 'OR' . ' ' . '"' . '$$dummyExists' . '"', + '"' . "exactString" . '"', + '(' . '"' . 'exactString' . '"' . ' ' . 'OR' . ' ' . '"' . '$$dummyExists' . '"' . ')' + . ' ' . 'AND' . ' ' . '"' . '$$dummySubstringString' . '"', + '', + '', + 'NOT' . ' ' . '(' . '"' . "exactString" . '"' . ' ' . 'AND' . ' ' . '"' . '$$dummyExists' . '"' . ')', + + '(' . '"' . "exactString" . '"' . ' ' . 'OR' . ' ' . + '(' . '"' . "exactString" . '"' . ' ' . 'AND' . ' ' . '"' . '$$dummyExists' . '"' . ')' . ')' + . ' ' . 'AND' . ' ' . '(' . '"' . "exactString" . '"' . ' ' . 'AND' . ' ' . '(' . + '"' . "exactString" . '"' . ' ' . 'OR' . ' ' . '"' . '$$dummyExists' . '"' . ')' . ')', + '"'. '1'. '"' . ' '. 'OR' . ' ' . '"' . '100000' . '"' + ]; + + for ($testNo = 0; $testNo < count($audienceConditions); $testNo++) { + $getExperimentAudiences = self::getMethod("getSerializedAudiences"); + $response = $getExperimentAudiences->invokeArgs( + $this->optConfigService, + array($audienceConditions[$testNo]) + ); + $this->assertEquals($expectedAudienceOutputs[$testNo], $response); + } + } + + public function testgetConfigAttributes() + { + $getConfigAttributes = self::getMethod("getConfigAttributes"); + $response = $getConfigAttributes->invokeArgs($this->optConfigService, array()); + if (!empty($response)) { + foreach ($response as $attr) { + $this->assertInstanceof(OptimizelyAttribute::class, $attr); + } + } + } + + public function testgetConfigAudiences() + { + $getConfigAudiences = self::getMethod("getConfigAudiences"); + $response = $getConfigAudiences->invokeArgs($this->optConfigService, array()); + if (!empty($response)) { + foreach ($response as $attr) { + $this->assertInstanceof(OptimizelyAudience::class, $attr); + } + } + } + + + public function testgetConfigEvents() + { + $getConfigEvents = self::getMethod("getConfigEvents"); + $response = $getConfigEvents->invokeArgs($this->optConfigService, array()); + if (!empty($response)) { + foreach ($response as $event) { + $this->assertInstanceof(OptimizelyEvent::class, $event); + } + } } public function testGetConfig() @@ -220,11 +401,14 @@ public function testJsonEncodeofOptimizelyConfig() $response = $this->optConfigService->getConfig(); $expectedJSON = '{ + "environmentKey":"", + "sdkKey":"", "revision": "16", "experimentsMap": { "ab_experiment": { "id": "17301270474", "key": "ab_experiment", + "audiences":"", "variationsMap": { "variation_a": { "id": "17277380360", @@ -245,6 +429,7 @@ public function testJsonEncodeofOptimizelyConfig() "feat_experiment": { "id": "17279300791", "key": "feat_experiment", + "audiences":"", "variationsMap": { "variation_a": { "id": "17289540366", @@ -325,6 +510,7 @@ public function testJsonEncodeofOptimizelyConfig() "group_ab_experiment": { "id": "17258450439", "key": "group_ab_experiment", + "audiences":"", "variationsMap": { "variation_a": { "id": "17287500312", @@ -347,10 +533,14 @@ public function testJsonEncodeofOptimizelyConfig() "test_feature": { "id": "17266500726", "key": "test_feature", + "experimentRules":[{"id":"17279300791","key":"feat_experiment","audiences":"","variationsMap":{"variation_a":{"id":"17289540366","key":"variation_a","featureEnabled":true,"variablesMap":{"boolean_var":{"id":"17252790456","key":"boolean_var","type":"boolean","value":"true"},"integer_var":{"id":"17258820367","key":"integer_var","type":"integer","value":"5"},"double_var":{"id":"17260550714","key":"double_var","type":"double","value":"5.5"},"string_var":{"id":"17290540010","key":"string_var","type":"string","value":"i am variable value"},"json_var":{"id":"17260550458","key":"json_var","type":"json","value":"{\"text\": \"variable value\"}"}}},"variation_b":{"id":"17304990114","key":"variation_b","featureEnabled":false,"variablesMap":{"boolean_var":{"id":"17252790456","key":"boolean_var","type":"boolean","value":"false"},"integer_var":{"id":"17258820367","key":"integer_var","type":"integer","value":"1"},"double_var":{"id":"17260550714","key":"double_var","type":"double","value":"0.5"},"string_var":{"id":"17290540010","key":"string_var","type":"string","value":"i am default value"},"json_var":{"id":"17260550458","key":"json_var","type":"json","value":"{\"text\": \"default value\"}"}}}}}], + + "deliveryRules":[{"id":"17268110732","key":"17268110732","audiences":"","variationsMap":{"17285550838":{"id":"17285550838","key":"17285550838","featureEnabled":true,"variablesMap":{"boolean_var":{"id":"17252790456","key":"boolean_var","type":"boolean","value":"false"},"integer_var":{"id":"17258820367","key":"integer_var","type":"integer","value":"1"},"double_var":{"id":"17260550714","key":"double_var","type":"double","value":"0.5"},"string_var":{"id":"17290540010","key":"string_var","type":"string","value":"i am default value"},"json_var":{"id":"17260550458","key":"json_var","type":"json","value":"{\"text\": \"default value\"}"}}}}}], "experimentsMap": { "feat_experiment": { "id": "17279300791", "key": "feat_experiment", + "audiences":"", "variationsMap": { "variation_a": { "id": "17289540366", @@ -466,8 +656,12 @@ public function testJsonEncodeofOptimizelyConfig() }'; $optimizelyConfig = json_decode($expectedJSON, true); - $optimizelyConfig['datafile'] = DATAFILE_FOR_OPTIMIZELY_CONFIG; - + $optimizelyConfig["attributes"] = [["id" => "111094", "key" => "test_attribute"]]; + $json_encoded = '[{"id":"3468206642","name":"exactString","conditions":"[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]"},{"id":"3988293898","name":"$$dummySubstringString","conditions":"{ \"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\" }"},{"id":"3988293899","name":"$$dummyExists","conditions":"{ \"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\" }"}]'; + $converted_audiences = json_decode($json_encoded, true); + $optimizelyConfig["audiences"] = $converted_audiences; + $optimizelyConfig["events"] = [["id" => "111095", "key" => "test_event", "experimentIds" => ["111127"]]]; + $optimizelyConfig['datafile'] = DATAFILE_FOR_OPTIMIZELY_CONFIG; $this->assertEquals(json_encode($optimizelyConfig), json_encode($response)); } diff --git a/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php b/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php index 3551af3f..995d2364 100644 --- a/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php @@ -37,11 +37,18 @@ public function testOptimizelyConfigEntity() $this->assertEquals("20", $optConfig->getRevision()); $this->assertEquals(["a" => "apple"], $optConfig->getExperimentsMap()); $this->assertEquals(["o" => "orange"], $optConfig->getFeaturesMap()); + $this->assertEquals("", $optConfig->getEnvironmentKey()); + $this->assertEquals("", $optConfig->getSdkKey()); $expectedJson = '{ + "environmentKey":"", + "sdkKey":"", "revision": "20", "experimentsMap" : {"a": "apple"}, "featuresMap": {"o": "orange"}, + "attributes":[], + "audiences":[], + "events":[], "datafile": null }'; @@ -55,16 +62,19 @@ public function testOptimizelyExperimentEntity() $optExp = new OptimizelyExperiment( "id", "key", - ["a" => "apple"] + ["a" => "apple"], + '' ); $this->assertEquals("id", $optExp->getId()); $this->assertEquals("key", $optExp->getKey()); $this->assertEquals(["a" => "apple"], $optExp->getVariationsMap()); + $this->assertEquals('', $optExp->getExperimentAudiences()); $expectedJson = '{ "id": "id", "key" : "key", + "audiences":"", "variationsMap": {"a": "apple"} }'; @@ -79,17 +89,23 @@ public function testOptimizelyFeatureEntity() "id", "key", ["a" => "apple"], - ["o" => "orange"] + ["o" => "orange"], + [], + [] ); $this->assertEquals("id", $optFeature->getId()); $this->assertEquals("key", $optFeature->getKey()); $this->assertEquals(["a" => "apple"], $optFeature->getExperimentsMap()); $this->assertEquals(["o" => "orange"], $optFeature->getVariablesMap()); + $this->assertEquals([], $optFeature->getExperimentRules()); + $this->assertEquals([], $optFeature->getDeliveryRules()); $expectedJson = '{ "id": "id", "key" : "key", + "experimentRules":[], + "deliveryRules":[], "experimentsMap": {"a": "apple"}, "variablesMap": {"o": "orange"} }'; diff --git a/tests/TestData.php b/tests/TestData.php index 0d668702..10c9ed83 100644 --- a/tests/TestData.php +++ b/tests/TestData.php @@ -145,93 +145,93 @@ "id": "122230", "forcedVariations": { - }, - "trafficAllocation": [ - { - "entityId": "122231", - "endOfRange": 2500 - }, - { - "entityId": "122232", - "endOfRange": 5000 - }, - { - "entityId": "122233", - "endOfRange": 7500 }, - { - "entityId": "122234", - "endOfRange": 10000 - } - ], - "variations": [ - { - "id": "122231", - "key": "Fred", - "variables": [ + "trafficAllocation": [ { - "id": "155560", - "value": "F" + "entityId": "122231", + "endOfRange": 2500 }, { - "id": "155561", - "value": "red" - } - ], - "featureEnabled": true - }, - { - "id": "122232", - "key": "Feorge", - "variables": [ + "entityId": "122232", + "endOfRange": 5000 + }, { - "id": "155560", - "value": "F" + "entityId": "122233", + "endOfRange": 7500 }, { - "id": "155561", - "value": "eorge" + "entityId": "122234", + "endOfRange": 10000 } - ], - "featureEnabled": true - }, - { - "id": "122233", - "key": "Gred", - "variables": [ + ], + "variations": [ { - "id": "155560", - "value": "G" + "id": "122231", + "key": "Fred", + "variables": [ + { + "id": "155560", + "value": "F" + }, + { + "id": "155561", + "value": "red" + } + ], + "featureEnabled": true }, { - "id": "155561", - "value": "red" - } - ], - "featureEnabled": true - }, - { - "id": "122234", - "key": "George", - "variables": [ + "id": "122232", + "key": "Feorge", + "variables": [ + { + "id": "155560", + "value": "F" + }, + { + "id": "155561", + "value": "eorge" + } + ], + "featureEnabled": true + }, { - "id": "155560", - "value": "G" + "id": "122233", + "key": "Gred", + "variables": [ + { + "id": "155560", + "value": "G" + }, + { + "id": "155561", + "value": "red" + } + ], + "featureEnabled": true }, { - "id": "155561", - "value": "eorge" + "id": "122234", + "key": "George", + "variables": [ + { + "id": "155560", + "value": "G" + }, + { + "id": "155561", + "value": "eorge" + } + ], + "featureEnabled": true } - ], - "featureEnabled": true - } - ] - }, - { - "key": "test_experiment_with_feature_rollout", - "status": "Running", - "layerId": "5", - "audienceIds": [ + ] + }, + { + "key": "test_experiment_with_feature_rollout", + "status": "Running", + "layerId": "5", + "audienceIds": [ ], "id": "122235", @@ -1575,7 +1575,23 @@ "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", "id": "$opt_dummy_audience", "name": "Optimizely-Generated Audience for Backwards Compatibility" + }, + { + "id": "3468206642", + "name": "exactString", + "conditions": "[\"and\", [\"or\", [\"or\", {\"name\": \"house\", \"type\": \"custom_attribute\", \"value\": \"Gryffindor\"}]]]" + }, + { + "id": "3988293898", + "name": "$$dummySubstringString", + "conditions": "{ \"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\" }" + }, + { + "id": "3988293899", + "name": "$$dummyExists", + "conditions": "{ \"type\": \"custom_attribute\", \"name\": \"$opt_dummy_attribute\", \"value\": \"impossible_value\" }" } + ], "groups": [ { @@ -1693,15 +1709,318 @@ } ], "attributes": [ - - ], - "botFiltering": false, - "accountId": "8272261422", - "events": [ - + {"key": "test_attribute", "id": "111094"} + ], + "botFiltering": false, + "accountId": "8272261422", + "events": [ + {"key": "test_event", "experimentIds": ["111127"], "id": "111095"} + ], + "revision": "16" + }' +); + +define( + 'DATAFILE_FOR_DUPLICATE_EXP_KEYS', + '{ + "version": "4", + "rollouts": [], + "typedAudiences": [ + { + "id": "20415611520", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "value": true, + "type": "custom_attribute", + "name": "hiddenLiveEnabled", + "match": "exact" + } + ] + ] + ], + "name": "test1" + }, + { + "id": "20406066925", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "value": false, + "type": "custom_attribute", + "name": "hiddenLiveEnabled", + "match": "exact" + } + ] + ] + ], + "name": "test2" + } + ], + "anonymizeIP": true, + "projectId": "20430981610", + "variables": [], + "featureFlags": [ + { + "experimentIds": ["9300000007569"], + "rolloutId": "", + "variables": [], + "id": "3045", + "key": "flag1" + }, + { + "experimentIds": ["9300000007573"], + "rolloutId": "", + "variables": [], + "id": "3046", + "key": "flag2" + } ], - "revision": "16" - }' + "experiments": [ + { + "status": "Running", + "audienceConditions": ["or", "20415611520"], + "audienceIds": ["20415611520"], + "variations": [ + { + "variables": [], + "id": "8045", + "key": "variation1", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "key": "targeted_delivery", + "layerId": "9300000007569", + "trafficAllocation": [{ "entityId": "8045", "endOfRange": 10000 }], + "id": "9300000007569" + }, + { + "status": "Running", + "audienceConditions": ["or", "20406066925"], + "audienceIds": ["20406066925"], + "variations": [ + { + "variables": [], + "id": "8048", + "key": "variation2", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "key": "targeted_delivery", + "layerId": "9300000007573", + "trafficAllocation": [{ "entityId": "8048", "endOfRange": 10000 }], + "id": "9300000007573" + } + ], + "audiences": [ + { + "id": "20415611520", + "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + "name": "test1" + }, + { + "id": "20406066925", + "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + "name": "test2" + }, + { + "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + "id": "$opt_dummy_audience", + "name": "Optimizely-Generated Audience for Backwards Compatibility" + } + ], + "groups": [], + "attributes": [{ "id": "20408641883", "key": "hiddenLiveEnabled" }], + "botFiltering": false, + "accountId": "17882702980", + "events": [], + "revision": "25", + "sendFlagDecisions": true + }' +); + +define( + 'DATAFILE_FOR_DUPLICATE_RUL_KEYS', + '{ + "version": "4", + "rollouts": [ + { + "experiments": [ + { + "status": "Running", + "audienceConditions": [], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "5452", + "key": "on", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "key": "targeted_delivery", + "layerId": "9300000004981", + "trafficAllocation": [{ "entityId": "5452", "endOfRange": 10000 }], + "id": "9300000004981" + }, + { + "status": "Running", + "audienceConditions": [], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "5451", + "key": "off", + "featureEnabled": false + } + ], + "forcedVariations": {}, + "key": "default-rollout-2029-20301771717", + "layerId": "default-layer-rollout-2029-20301771717", + "trafficAllocation": [{ "entityId": "5451", "endOfRange": 10000 }], + "id": "default-rollout-2029-20301771717" + } + ], + "id": "rollout-2029-20301771717" + }, + { + "experiments": [ + { + "status": "Running", + "audienceConditions": [], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "5450", + "key": "on", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "key": "targeted_delivery", + "layerId": "9300000004979", + "trafficAllocation": [{ "entityId": "5450", "endOfRange": 10000 }], + "id": "9300000004979" + }, + { + "status": "Running", + "audienceConditions": [], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "5449", + "key": "off", + "featureEnabled": false + } + ], + "forcedVariations": {}, + "key": "default-rollout-2028-20301771717", + "layerId": "default-layer-rollout-2028-20301771717", + "trafficAllocation": [{ "entityId": "5449", "endOfRange": 10000 }], + "id": "default-rollout-2028-20301771717" + } + ], + "id": "rollout-2028-20301771717" + }, + { + "experiments": [ + { + "status": "Running", + "audienceConditions": [], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "5448", + "key": "on", + "featureEnabled": true + } + ], + "forcedVariations": {}, + "key": "targeted_delivery", + "layerId": "9300000004977", + "trafficAllocation": [{ "entityId": "5448", "endOfRange": 10000 }], + "id": "9300000004977" + }, + { + "status": "Running", + "audienceConditions": [], + "audienceIds": [], + "variations": [ + { + "variables": [], + "id": "5447", + "key": "off", + "featureEnabled": false + } + ], + "forcedVariations": {}, + "key": "default-rollout-2027-20301771717", + "layerId": "default-layer-rollout-2027-20301771717", + "trafficAllocation": [{ "entityId": "5447", "endOfRange": 10000 }], + "id": "default-rollout-2027-20301771717" + } + ], + "id": "rollout-2027-20301771717" + } + ], + "typedAudiences": [], + "anonymizeIP": true, + "projectId": "20286295225", + "variables": [], + "featureFlags": [ + { + "experimentIds": [], + "rolloutId": "rollout-2029-20301771717", + "variables": [], + "id": "2029", + "key": "flag_3" + }, + { + "experimentIds": [], + "rolloutId": "rollout-2028-20301771717", + "variables": [], + "id": "2028", + "key": "flag_2" + }, + { + "experimentIds": [], + "rolloutId": "rollout-2027-20301771717", + "variables": [], + "id": "2027", + "key": "flag_1" + } + ], + "experiments": [], + "audiences": [ + { + "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + "id": "$opt_dummy_audience", + "name": "Optimizely-Generated Audience for Backwards Compatibility" + } + ], + "groups": [], + "attributes": [], + "botFiltering": false, + "accountId": "19947277778", + "events": [], + "revision": "11", + "sendFlagDecisions": true + }' ); /**
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: