Skip to content

Commit 1509d06

Browse files
oakbanialiabbasrizvi
authored andcommitted
Bot Filtering Changes (#110)
1 parent a6dccf4 commit 1509d06

File tree

9 files changed

+352
-65
lines changed

9 files changed

+352
-65
lines changed

src/Optimizely/DecisionService/DecisionService.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use Optimizely\Entity\FeatureFlag;
2424
use Optimizely\Entity\Rollout;
2525
use Optimizely\Entity\Variation;
26+
use Optimizely\Enums\ControlAttributes;
2627
use Optimizely\Logger\LoggerInterface;
2728
use Optimizely\ProjectConfig;
2829
use Optimizely\UserProfile\Decision;
@@ -31,9 +32,6 @@
3132
use Optimizely\UserProfile\UserProfileUtils;
3233
use Optimizely\Utils\Validator;
3334

34-
// Reserved attribute for bucketing ID.
35-
define("RESERVED_ATTRIBUTE_KEY_BUCKETING_ID", "\$opt_bucketing_id");
36-
3735
/**
3836
* Optimizely's decision service that determines which variation of an experiment the user will be allocated to.
3937
*
@@ -98,8 +96,10 @@ private function getBucketingId($userId, $userAttributes)
9896

9997
// If the bucketing ID key is defined in userAttributes, then use that in
10098
// place of the userID for the murmur hash key
101-
if (!empty($userAttributes[RESERVED_ATTRIBUTE_KEY_BUCKETING_ID])) {
102-
$bucketingId = $userAttributes[RESERVED_ATTRIBUTE_KEY_BUCKETING_ID];
99+
$bucketingIdKey = ControlAttributes::BUCKETING_ID;
100+
101+
if (!empty($userAttributes[$bucketingIdKey])) {
102+
$bucketingId = $userAttributes[$bucketingIdKey];
103103
$this->_logger->log(Logger::DEBUG, sprintf('Setting the bucketing ID to "%s".', $bucketingId));
104104
}
105105

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* Copyright 2018, Optimizely
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace Optimizely\Enums;
19+
20+
class ControlAttributes
21+
{
22+
const BOT_FILTERING = "\$opt_bot_filtering";
23+
const BUCKETING_ID = "\$opt_bucketing_id";
24+
const USER_AGENT = "\$opt_user_agent";
25+
}

src/Optimizely/Event/Builder/EventBuilder.php

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@
1919

2020
require 'Params.php';
2121

22+
use Optimizely\Entity\Attribute;
2223
use Optimizely\Entity\Experiment;
24+
use Optimizely\Enums\ControlAttributes;
2325
use Optimizely\Event\LogEvent;
2426
use Optimizely\ProjectConfig;
2527
use Optimizely\Utils\EventTagUtils;
2628
use Optimizely\Utils\GeneratorUtils;
2729

28-
define("RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY", "optimizely_bucketing_id");
29-
3030
class EventBuilder
3131
{
3232
/**
@@ -63,6 +63,7 @@ class EventBuilder
6363

6464
/**
6565
* Event Builder constructor to set logger
66+
*
6667
* @param $logger LoggerInterface
6768
*/
6869
public function __construct($logger)
@@ -97,38 +98,37 @@ private function getCommonParams($config, $userId, $attributes)
9798
ANONYMIZE_IP => $config->getAnonymizeIP()
9899
];
99100

100-
if (is_null($attributes)) {
101-
return $commonParams;
102-
}
103-
104-
foreach ($attributes as $attributeKey => $attributeValue) {
105-
$feature = [];
106-
// Do not discard attribute if value is zero or false
107-
if (!is_null($attributeValue)) {
108-
// check for reserved attributes
109-
if (strcmp($attributeKey, RESERVED_ATTRIBUTE_KEY_BUCKETING_ID) == 0) {
110-
$feature = [
111-
ENTITY_ID => RESERVED_ATTRIBUTE_KEY_BUCKETING_ID,
112-
KEY => RESERVED_ATTRIBUTE_KEY_BUCKETING_ID_EVENT_PARAM_KEY,
113-
TYPE => CUSTOM_ATTRIBUTE_FEATURE_TYPE,
114-
VALUE => $attributeValue
115-
];
116-
} else {
101+
if(!is_null($attributes)) {
102+
foreach ($attributes as $attributeKey => $attributeValue) {
103+
$feature = [];
104+
// Do not discard attribute if value is zero or false
105+
if (!is_null($attributeValue)) {
117106
$attributeEntity = $config->getAttribute($attributeKey);
118-
if (!is_null($attributeEntity->getKey())) {
107+
if ($attributeEntity instanceof Attribute) {
119108
$feature = [
120109
ENTITY_ID => $attributeEntity->getId(),
121110
KEY => $attributeKey,
122111
TYPE => CUSTOM_ATTRIBUTE_FEATURE_TYPE,
123112
VALUE => $attributeValue
124113
];
114+
115+
$commonParams[VISITORS][0][ATTRIBUTES][] = $feature;
125116
}
126117
}
127118
}
119+
}
128120

129-
if (!empty($feature)) {
130-
$commonParams[VISITORS][0][ATTRIBUTES][] = $feature;
131-
}
121+
// Append Bot Filtering attribute
122+
$botFilteringValue = $config->getBotFiltering();
123+
if (is_bool($botFilteringValue)) {
124+
$feature = [
125+
ENTITY_ID => ControlAttributes::BOT_FILTERING,
126+
KEY => ControlAttributes::BOT_FILTERING,
127+
TYPE => CUSTOM_ATTRIBUTE_FEATURE_TYPE,
128+
VALUE => $botFilteringValue
129+
];
130+
131+
$commonParams[VISITORS][0][ATTRIBUTES][] = $feature;
132132
}
133133

134134
return $commonParams;

src/Optimizely/ProjectConfig.php

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?php
22
/**
3-
* Copyright 2016, Optimizely
3+
* Copyright 2016, 2018, Optimizely
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License");
66
* you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@
2727
use Optimizely\Entity\Group;
2828
use Optimizely\Entity\Rollout;
2929
use Optimizely\Entity\Variation;
30+
use Optimizely\Enums\ControlAttributes;
3031
use Optimizely\ErrorHandler\ErrorHandlerInterface;
3132
use Optimizely\Exceptions\InvalidAttributeException;
3233
use Optimizely\Exceptions\InvalidAudienceException;
@@ -48,6 +49,8 @@
4849
*/
4950
class ProjectConfig
5051
{
52+
const RESERVED_ATTRIBUTE_PREFIX = '$opt_';
53+
5154
/**
5255
* @var string Version of the datafile.
5356
*/
@@ -69,6 +72,12 @@ class ProjectConfig
6972
*/
7073
private $_anonymizeIP;
7174

75+
/**
76+
* @var boolean denotes if Optimizely should perform
77+
* bot filtering on your dispatched events.
78+
*/
79+
private $_botFiltering;
80+
7281
/**
7382
* @var string Revision of the datafile.
7483
*/
@@ -183,6 +192,7 @@ public function __construct($datafile, $logger, $errorHandler)
183192
$this->_accountId = $config['accountId'];
184193
$this->_projectId = $config['projectId'];
185194
$this->_anonymizeIP = isset($config['anonymizeIP'])? $config['anonymizeIP'] : false;
195+
$this->_botFiltering = isset($config['botFiltering'])? $config['botFiltering'] : null;
186196
$this->_revision = $config['revision'];
187197
$this->_forcedVariationMap = [];
188198

@@ -293,6 +303,15 @@ public function getAnonymizeIP()
293303
return $this->_anonymizeIP;
294304
}
295305

306+
/**
307+
* @return boolean Flag denoting if Optimizely should perform
308+
* bot filtering on your dispatched events.
309+
*/
310+
public function getBotFiltering()
311+
{
312+
return $this->_botFiltering;
313+
}
314+
296315
/**
297316
* @return string String representing revision of the datafile.
298317
*/
@@ -431,17 +450,31 @@ public function getAudience($audienceId)
431450
* @param $attributeKey string Key of the attribute.
432451
*
433452
* @return Attribute Entity corresponding to the key.
434-
* Dummy entity is returned if key is invalid.
453+
* Null is returned if key is invalid.
435454
*/
436455
public function getAttribute($attributeKey)
437456
{
457+
$hasReservedPrefix = strpos($attributeKey, self::RESERVED_ATTRIBUTE_PREFIX) === 0;
458+
438459
if (isset($this->_attributeKeyMap[$attributeKey])) {
460+
if ($hasReservedPrefix) {
461+
$this->_logger->log(
462+
Logger::WARNING,
463+
sprintf('Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.', $attributeKey, self::RESERVED_ATTRIBUTE_PREFIX)
464+
);
465+
}
466+
439467
return $this->_attributeKeyMap[$attributeKey];
440468
}
441469

470+
if ($hasReservedPrefix) {
471+
return new Attribute($attributeKey, $attributeKey);
472+
}
473+
442474
$this->_logger->log(Logger::ERROR, sprintf('Attribute key "%s" is not in datafile.', $attributeKey));
443475
$this->_errorHandler->handleError(new InvalidAttributeException('Provided attribute is not in datafile.'));
444-
return new Attribute();
476+
477+
return null;
445478
}
446479

447480
/**

tests/DecisionServiceTests/DecisionServiceTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -561,12 +561,12 @@ public function testGetVariationWithBucketingId()
561561
'device_type' => 'iPhone',
562562
'company' => 'Optimizely',
563563
'location' => 'San Francisco',
564-
RESERVED_ATTRIBUTE_KEY_BUCKETING_ID => $this->testBucketingIdVariation
564+
'$opt_bucketing_id' => $this->testBucketingIdVariation
565565
];
566566

567567
$invalidUserAttributesWithBucketingId = [
568568
'company' => 'Optimizely',
569-
RESERVED_ATTRIBUTE_KEY_BUCKETING_ID => $this->testBucketingIdControl
569+
'$opt_bucketing_id' => $this->testBucketingIdControl
570570
];
571571

572572
$optlyObject = new Optimizely(DATAFILE, new ValidEventDispatcher(), $this->loggerMock);

0 commit comments

Comments
 (0)