Skip to content

Commit

Permalink
Merge 4562445 into 5f73a01
Browse files Browse the repository at this point in the history
  • Loading branch information
oakbani committed Jan 20, 2021
2 parents 5f73a01 + 4562445 commit f31ddaa
Show file tree
Hide file tree
Showing 18 changed files with 3,077 additions and 398 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ php:
- '7.0'
- '7.1'
- '7.2'
- '7.3'
- '7.3.24' # revert it back to 7.3 once 7.3.25 latest build gets fixed.
install: "composer install"
script:
- mkdir -p build/logs
Expand Down
72 changes: 41 additions & 31 deletions src/Optimizely/Bucketer.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Copyright 2016-2020, Optimizely
* Copyright 2016-2021, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -110,23 +110,26 @@ protected function generateBucketValue($bucketingKey)
* @param $parentId mixed ID representing Experiment or Group.
* @param $trafficAllocations array Traffic allocations for variation or experiment.
*
* @return string ID representing experiment or variation.
* @return [ string, array ] ID representing experiment or variation and array of log messages representing decision making.
*/
private function findBucket($bucketingId, $userId, $parentId, $trafficAllocations)
{
$decideReasons = [];
// Generate the bucketing key based on combination of user ID and experiment ID or group ID.
$bucketingKey = $bucketingId.$parentId;
$bucketingNumber = $this->generateBucketValue($bucketingKey);
$this->_logger->log(Logger::DEBUG, sprintf('Assigned bucket %s to user "%s" with bucketing ID "%s".', $bucketingNumber, $userId, $bucketingId));
$message = sprintf('Assigned bucket %s to user "%s" with bucketing ID "%s".', $bucketingNumber, $userId, $bucketingId);
$this->_logger->log(Logger::DEBUG, $message);
$decideReasons[] = $message;

foreach ($trafficAllocations as $trafficAllocation) {
$currentEnd = $trafficAllocation->getEndOfRange();
if ($bucketingNumber < $currentEnd) {
return $trafficAllocation->getEntityId();
return [$trafficAllocation->getEntityId(), $decideReasons];
}
}

return null;
return [null, $decideReasons];
}

/**
Expand All @@ -137,12 +140,14 @@ private function findBucket($bucketingId, $userId, $parentId, $trafficAllocation
* @param $bucketingId string A customer-assigned value used to create the key for the murmur hash.
* @param $userId string User identifier.
*
* @return Variation Variation which will be shown to the user.
* @return [ Variation, array ] Variation which will be shown to the user and array of log messages representing decision making.
*/
public function bucket(ProjectConfigInterface $config, Experiment $experiment, $bucketingId, $userId)
{
$decideReasons = [];

if (is_null($experiment->getKey())) {
return null;
return [ null, $decideReasons ];
}

// Determine if experiment is in a mutually exclusive group.
Expand All @@ -151,47 +156,52 @@ public function bucket(ProjectConfigInterface $config, Experiment $experiment, $
$group = $config->getGroup($experiment->getGroupId());

if (is_null($group->getId())) {
return null;
return [ null, $decideReasons ];
}

$userExperimentId = $this->findBucket($bucketingId, $userId, $group->getId(), $group->getTrafficAllocation());
list($userExperimentId, $reasons) = $this->findBucket($bucketingId, $userId, $group->getId(), $group->getTrafficAllocation());
$decideReasons = array_merge($decideReasons, $reasons);

if (empty($userExperimentId)) {
$this->_logger->log(Logger::INFO, sprintf('User "%s" is in no experiment.', $userId));
return null;
$message = sprintf('User "%s" is in no experiment.', $userId);
$this->_logger->log(Logger::INFO, $message);
$decideReasons[] = $message;
return [ null, $decideReasons ];
}

if ($userExperimentId != $experiment->getId()) {
$this->_logger->log(
Logger::INFO,
sprintf(
'User "%s" is not in experiment %s of group %s.',
$userId,
$experiment->getKey(),
$experiment->getGroupId()
)
);
return null;
}

$this->_logger->log(
Logger::INFO,
sprintf(
'User "%s" is in experiment %s of group %s.',
$message = sprintf(
'User "%s" is not in experiment %s of group %s.',
$userId,
$experiment->getKey(),
$experiment->getGroupId()
)
);

$this->_logger->log(Logger::INFO, $message);
$decideReasons[] = $message;
return [ null, $decideReasons ];
}

$message = sprintf(
'User "%s" is in experiment %s of group %s.',
$userId,
$experiment->getKey(),
$experiment->getGroupId()
);

$this->_logger->log(Logger::INFO, $message);
$decideReasons[] = $message;
}

// Bucket user if not in whitelist and in group (if any).
$variationId = $this->findBucket($bucketingId, $userId, $experiment->getId(), $experiment->getTrafficAllocation());
list($variationId, $reasons) = $this->findBucket($bucketingId, $userId, $experiment->getId(), $experiment->getTrafficAllocation());
$decideReasons = array_merge($decideReasons, $reasons);
if (!empty($variationId)) {
$variation = $config->getVariationFromId($experiment->getKey(), $variationId);

return $variation;
return [ $variation, $decideReasons ];
}

return null;
return [ null, $decideReasons ];
}
}
27 changes: 27 additions & 0 deletions src/Optimizely/Decide/OptimizelyDecideOption.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* Copyright 2021, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Optimizely\Decide;

class OptimizelyDecideOption
{
const DISABLE_DECISION_EVENT = 'DISABLE_DECISION_EVENT';
const ENABLED_FLAGS_ONLY = 'ENABLED_FLAGS_ONLY';
const IGNORE_USER_PROFILE_SERVICE = 'IGNORE_USER_PROFILE_SERVICE';
const INCLUDE_REASONS = 'INCLUDE_REASONS';
const EXCLUDE_VARIABLES = 'EXCLUDE_VARIABLES';
}
88 changes: 88 additions & 0 deletions src/Optimizely/Decide/OptimizelyDecision.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php
/**
* Copyright 2021, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Optimizely\Decide;

class OptimizelyDecision implements \JsonSerializable
{
private $variationKey;
private $enabled;
private $variables;
private $ruleKey;
private $flagKey;
private $userContext;
private $reasons;


public function __construct(
$variationKey = null,
$enabled = null,
$variables = null,
$ruleKey = null,
$flagKey = null,
$userContext = null,
$reasons = null
) {
$this->variationKey = $variationKey;
$this->enabled = $enabled === null ? false : $enabled;
$this->variables = $variables === null ? [] : $variables;
$this->ruleKey = $ruleKey;
$this->flagKey = $flagKey;
$this->userContext = $userContext;
$this->reasons = $reasons === null ? [] : $reasons;
}

public function getVariationKey()
{
return $this->variationKey;
}

public function getEnabled()
{
return $this->enabled;
}

public function getVariables()
{
return $this->variables;
}

public function getRuleKey()
{
return $this->ruleKey;
}

public function getFlagKey()
{
return $this->flagKey;
}

public function getUserContext()
{
return $this->userContext;
}

public function getReasons()
{
return $this->reasons;
}

public function jsonSerialize()
{
return get_object_vars($this);
}
}
25 changes: 25 additions & 0 deletions src/Optimizely/Decide/OptimizelyDecisionMessage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
/**
* Copyright 2021, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Optimizely\Decide;

class OptimizelyDecisionMessage
{
const SDK_NOT_READY = 'Optimizely SDK not configured properly yet.';
const FLAG_KEY_INVALID = 'No flag was found for key "%s".';
const VARIABLE_VALUE_INVALID = 'Variable value for key "%s" is invalid or wrong type.';
}

0 comments on commit f31ddaa

Please sign in to comment.