Skip to content

Commit

Permalink
Bot Filtering Changes (#110)
Browse files Browse the repository at this point in the history
  • Loading branch information
oakbani authored and aliabbasrizvi committed Jun 27, 2018
1 parent a6dccf4 commit 1509d06
Show file tree
Hide file tree
Showing 9 changed files with 352 additions and 65 deletions.
13 changes: 7 additions & 6 deletions src/Optimizely/DecisionService/DecisionService.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Optimizely\Entity\FeatureFlag;
use Optimizely\Entity\Rollout;
use Optimizely\Entity\Variation;
use Optimizely\Enums\ControlAttributes;
use Optimizely\Logger\LoggerInterface;
use Optimizely\ProjectConfig;
use Optimizely\UserProfile\Decision;
Expand All @@ -31,9 +32,6 @@
use Optimizely\UserProfile\UserProfileUtils;
use Optimizely\Utils\Validator;

// Reserved attribute for bucketing ID.
define("RESERVED_ATTRIBUTE_KEY_BUCKETING_ID", "\$opt_bucketing_id");

/**
* Optimizely's decision service that determines which variation of an experiment the user will be allocated to.
*
Expand Down Expand Up @@ -98,8 +96,10 @@ private function getBucketingId($userId, $userAttributes)

// If the bucketing ID key is defined in userAttributes, then use that in
// place of the userID for the murmur hash key
if (!empty($userAttributes[RESERVED_ATTRIBUTE_KEY_BUCKETING_ID])) {
$bucketingId = $userAttributes[RESERVED_ATTRIBUTE_KEY_BUCKETING_ID];
$bucketingIdKey = ControlAttributes::BUCKETING_ID;

if (!empty($userAttributes[$bucketingIdKey])) {
$bucketingId = $userAttributes[$bucketingIdKey];
$this->_logger->log(Logger::DEBUG, sprintf('Setting the bucketing ID to "%s".', $bucketingId));
}

Expand Down Expand Up @@ -318,7 +318,8 @@ public function getVariationForFeatureRollout(FeatureFlag $featureFlag, $userId,
if (!Validator::isUserInExperiment($this->_projectConfig, $experiment, $userAttributes)) {
$this->_logger->log(
Logger::DEBUG,
sprintf("User '%s' did not meet the audience conditions to be in rollout rule '%s'.", $userId, $experiment->getKey()));
sprintf("User '%s' did not meet the audience conditions to be in rollout rule '%s'.", $userId, $experiment->getKey())
);
return null;
}

Expand Down
25 changes: 25 additions & 0 deletions src/Optimizely/Enums/ControlAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php
/**
* Copyright 2018, 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\Enums;

class ControlAttributes
{
const BOT_FILTERING = "\$opt_bot_filtering";
const BUCKETING_ID = "\$opt_bucketing_id";
const USER_AGENT = "\$opt_user_agent";
}
46 changes: 23 additions & 23 deletions src/Optimizely/Event/Builder/EventBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@

require 'Params.php';

use Optimizely\Entity\Attribute;
use Optimizely\Entity\Experiment;
use Optimizely\Enums\ControlAttributes;
use Optimizely\Event\LogEvent;
use Optimizely\ProjectConfig;
use Optimizely\Utils\EventTagUtils;
use Optimizely\Utils\GeneratorUtils;

define("RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY", "optimizely_bucketing_id");

class EventBuilder
{
/**
Expand Down Expand Up @@ -63,6 +63,7 @@ class EventBuilder

/**
* Event Builder constructor to set logger
*
* @param $logger LoggerInterface
*/
public function __construct($logger)
Expand Down Expand Up @@ -97,38 +98,37 @@ private function getCommonParams($config, $userId, $attributes)
ANONYMIZE_IP => $config->getAnonymizeIP()
];

if (is_null($attributes)) {
return $commonParams;
}

foreach ($attributes as $attributeKey => $attributeValue) {
$feature = [];
// Do not discard attribute if value is zero or false
if (!is_null($attributeValue)) {
// check for reserved attributes
if (strcmp($attributeKey, RESERVED_ATTRIBUTE_KEY_BUCKETING_ID) == 0) {
$feature = [
ENTITY_ID => RESERVED_ATTRIBUTE_KEY_BUCKETING_ID,
KEY => RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY,
TYPE => CUSTOM_ATTRIBUTE_FEATURE_TYPE,
VALUE => $attributeValue
];
} else {
if(!is_null($attributes)) {
foreach ($attributes as $attributeKey => $attributeValue) {
$feature = [];
// Do not discard attribute if value is zero or false
if (!is_null($attributeValue)) {
$attributeEntity = $config->getAttribute($attributeKey);
if (!is_null($attributeEntity->getKey())) {
if ($attributeEntity instanceof Attribute) {
$feature = [
ENTITY_ID => $attributeEntity->getId(),
KEY => $attributeKey,
TYPE => CUSTOM_ATTRIBUTE_FEATURE_TYPE,
VALUE => $attributeValue
];

$commonParams[VISITORS][0][ATTRIBUTES][] = $feature;
}
}
}
}

if (!empty($feature)) {
$commonParams[VISITORS][0][ATTRIBUTES][] = $feature;
}
// Append Bot Filtering attribute
$botFilteringValue = $config->getBotFiltering();
if (is_bool($botFilteringValue)) {
$feature = [
ENTITY_ID => ControlAttributes::BOT_FILTERING,
KEY => ControlAttributes::BOT_FILTERING,
TYPE => CUSTOM_ATTRIBUTE_FEATURE_TYPE,
VALUE => $botFilteringValue
];

$commonParams[VISITORS][0][ATTRIBUTES][] = $feature;
}

return $commonParams;
Expand Down
39 changes: 36 additions & 3 deletions src/Optimizely/ProjectConfig.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php
/**
* Copyright 2016, Optimizely
* Copyright 2016, 2018, Optimizely
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -27,6 +27,7 @@
use Optimizely\Entity\Group;
use Optimizely\Entity\Rollout;
use Optimizely\Entity\Variation;
use Optimizely\Enums\ControlAttributes;
use Optimizely\ErrorHandler\ErrorHandlerInterface;
use Optimizely\Exceptions\InvalidAttributeException;
use Optimizely\Exceptions\InvalidAudienceException;
Expand All @@ -48,6 +49,8 @@
*/
class ProjectConfig
{
const RESERVED_ATTRIBUTE_PREFIX = '$opt_';

/**
* @var string Version of the datafile.
*/
Expand All @@ -69,6 +72,12 @@ class ProjectConfig
*/
private $_anonymizeIP;

/**
* @var boolean denotes if Optimizely should perform
* bot filtering on your dispatched events.
*/
private $_botFiltering;

/**
* @var string Revision of the datafile.
*/
Expand Down Expand Up @@ -183,6 +192,7 @@ public function __construct($datafile, $logger, $errorHandler)
$this->_accountId = $config['accountId'];
$this->_projectId = $config['projectId'];
$this->_anonymizeIP = isset($config['anonymizeIP'])? $config['anonymizeIP'] : false;
$this->_botFiltering = isset($config['botFiltering'])? $config['botFiltering'] : null;
$this->_revision = $config['revision'];
$this->_forcedVariationMap = [];

Expand Down Expand Up @@ -293,6 +303,15 @@ public function getAnonymizeIP()
return $this->_anonymizeIP;
}

/**
* @return boolean Flag denoting if Optimizely should perform
* bot filtering on your dispatched events.
*/
public function getBotFiltering()
{
return $this->_botFiltering;
}

/**
* @return string String representing revision of the datafile.
*/
Expand Down Expand Up @@ -431,17 +450,31 @@ public function getAudience($audienceId)
* @param $attributeKey string Key of the attribute.
*
* @return Attribute Entity corresponding to the key.
* Dummy entity is returned if key is invalid.
* Null is returned if key is invalid.
*/
public function getAttribute($attributeKey)
{
$hasReservedPrefix = strpos($attributeKey, self::RESERVED_ATTRIBUTE_PREFIX) === 0;

if (isset($this->_attributeKeyMap[$attributeKey])) {
if ($hasReservedPrefix) {
$this->_logger->log(
Logger::WARNING,
sprintf('Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.', $attributeKey, self::RESERVED_ATTRIBUTE_PREFIX)
);
}

return $this->_attributeKeyMap[$attributeKey];
}

if ($hasReservedPrefix) {
return new Attribute($attributeKey, $attributeKey);
}

$this->_logger->log(Logger::ERROR, sprintf('Attribute key "%s" is not in datafile.', $attributeKey));
$this->_errorHandler->handleError(new InvalidAttributeException('Provided attribute is not in datafile.'));
return new Attribute();

return null;
}

/**
Expand Down
4 changes: 2 additions & 2 deletions tests/DecisionServiceTests/DecisionServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -561,12 +561,12 @@ public function testGetVariationWithBucketingId()
'device_type' => 'iPhone',
'company' => 'Optimizely',
'location' => 'San Francisco',
RESERVED_ATTRIBUTE_KEY_BUCKETING_ID => $this->testBucketingIdVariation
'$opt_bucketing_id' => $this->testBucketingIdVariation
];

$invalidUserAttributesWithBucketingId = [
'company' => 'Optimizely',
RESERVED_ATTRIBUTE_KEY_BUCKETING_ID => $this->testBucketingIdControl
'$opt_bucketing_id' => $this->testBucketingIdControl
];

$optlyObject = new Optimizely(DATAFILE, new ValidEventDispatcher(), $this->loggerMock);
Expand Down
Loading

0 comments on commit 1509d06

Please sign in to comment.