Skip to content

Commit 12a0ae3

Browse files
authored
feat: Support for Sending Flag Decisions (#215)
1 parent 9aef7ba commit 12a0ae3

File tree

10 files changed

+221
-32
lines changed

10 files changed

+221
-32
lines changed

src/Optimizely/Config/DatafileProjectConfig.php

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,13 @@ class DatafileProjectConfig implements ProjectConfigInterface
190190
*/
191191
private $_experimentFeatureMap;
192192

193+
/**
194+
* Boolean indicating if flag decisions should be sent to server or not
195+
*
196+
* @return boolean
197+
*/
198+
private $_sendFlagDecisions;
199+
193200
/**
194201
* DatafileProjectConfig constructor to load and set project configuration data.
195202
*
@@ -216,6 +223,7 @@ public function __construct($datafile, $logger, $errorHandler)
216223
$this->_anonymizeIP = isset($config['anonymizeIP'])? $config['anonymizeIP'] : false;
217224
$this->_botFiltering = isset($config['botFiltering'])? $config['botFiltering'] : null;
218225
$this->_revision = $config['revision'];
226+
$this->_sendFlagDecisions = isset($config['sendFlagDecisions']) ? $config['sendFlagDecisions'] : false;
219227

220228
$groups = $config['groups'] ?: [];
221229
$experiments = $config['experiments'] ?: [];
@@ -256,6 +264,12 @@ public function __construct($datafile, $logger, $errorHandler)
256264
$this->_experimentKeyMap = $this->_experimentKeyMap + $experimentsInGroup;
257265
}
258266

267+
foreach ($this->_rollouts as $rollout) {
268+
foreach ($rollout->getExperiments() as $experiment) {
269+
$this->_experimentKeyMap[$experiment->getKey()] = $experiment;
270+
}
271+
}
272+
259273
$this->_variationKeyMap = [];
260274
$this->_variationIdMap = [];
261275
$this->_experimentIdMap = [];
@@ -425,7 +439,16 @@ public function getFeatureFlags()
425439
*/
426440
public function getAllExperiments()
427441
{
428-
return array_values($this->_experimentKeyMap);
442+
// Exclude rollout experiments
443+
$rolloutExperimentIds = [];
444+
foreach ($this->_rollouts as $rollout) {
445+
foreach ($rollout->getExperiments() as $experiment) {
446+
$rolloutExperimentIds[] = $experiment->getId();
447+
}
448+
}
449+
return array_filter(array_values($this->_experimentKeyMap), function ($experiment) use ($rolloutExperimentIds) {
450+
return !in_array($experiment->getId(), $rolloutExperimentIds);
451+
});
429452
}
430453

431454
/**
@@ -678,4 +701,14 @@ public function isFeatureExperiment($experimentId)
678701
{
679702
return array_key_exists($experimentId, $this->_experimentFeatureMap);
680703
}
704+
705+
/**
706+
* Returns if flag decisions should be sent to server or not
707+
*
708+
* @return boolean
709+
*/
710+
public function getSendFlagDecisions()
711+
{
712+
return $this->_sendFlagDecisions;
713+
}
681714
}

src/Optimizely/Config/ProjectConfigInterface.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,4 +166,11 @@ public function isFeatureExperiment($experimentId);
166166
* @return string A string value that contains datafile contents.
167167
*/
168168
public function toDatafile();
169+
170+
/**
171+
* Returns if flag decisions should be sent to server or not
172+
*
173+
* @return boolean
174+
*/
175+
public function getSendFlagDecisions();
169176
}

src/Optimizely/DecisionService/FeatureDecision.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class FeatureDecision
2020
{
2121
const DECISION_SOURCE_FEATURE_TEST = 'feature-test';
2222
const DECISION_SOURCE_ROLLOUT = 'rollout';
23+
const DECITION_SOURCE_EXPERIMENT = 'experiment';
2324

2425
/**
2526
* The experiment in this decision.

src/Optimizely/Event/Builder/EventBuilder.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,14 +143,21 @@ private function getCommonParams($config, $userId, $attributes)
143143
*
144144
* @return array Hash representing parameters particular to impression event.
145145
*/
146-
private function getImpressionParams(Experiment $experiment, $variationId)
146+
private function getImpressionParams(Experiment $experiment, $variation, $flagKey, $ruleKey, $ruleType)
147147
{
148+
$variationKey = $variation->getKey() ? $variation->getKey() : '';
148149
$impressionParams = [
149150
DECISIONS => [
150151
[
151152
CAMPAIGN_ID => $experiment->getLayerId(),
152153
EXPERIMENT_ID => $experiment->getId(),
153-
VARIATION_ID => $variationId
154+
VARIATION_ID => $variation->getId(),
155+
METADATA => [
156+
FLAG_KEY => $flagKey,
157+
RULE_KEY => $ruleKey,
158+
RULE_TYPE => $ruleType,
159+
VARIATION_KEY => $variationKey
160+
],
154161
]
155162
],
156163

@@ -221,13 +228,13 @@ private function getConversionParams($eventEntity, $eventTags)
221228
*
222229
* @return LogEvent Event object to be sent to dispatcher.
223230
*/
224-
public function createImpressionEvent($config, $experimentKey, $variationKey, $userId, $attributes)
231+
public function createImpressionEvent($config, $experimentKey, $variationKey, $flagKey, $ruleKey, $ruleType, $userId, $attributes)
225232
{
226233
$eventParams = $this->getCommonParams($config, $userId, $attributes);
227234

228235
$experiment = $config->getExperimentFromKey($experimentKey);
229236
$variation = $config->getVariationFromKey($experimentKey, $variationKey);
230-
$impressionParams = $this->getImpressionParams($experiment, $variation->getId());
237+
$impressionParams = $this->getImpressionParams($experiment, $variation, $flagKey, $ruleKey, $ruleType);
231238

232239
$eventParams[VISITORS][0][SNAPSHOTS][] = $impressionParams;
233240

src/Optimizely/Event/Builder/Params.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,8 @@
3939
define('VARIATION_ID', 'variation_id');
4040
define('VISITOR_ID', 'visitor_id');
4141
define('VISITORS', 'visitors');
42+
define('METADATA', 'metadata');
43+
define('FLAG_KEY', 'flag_key');
44+
define('RULE_KEY', 'rule_key');
45+
define('RULE_TYPE', 'rule_type');
46+
define('VARIATION_KEY', 'variation_key');

src/Optimizely/Optimizely.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,10 @@ private function validateUserInputs($attributes, $eventTags = null)
195195
* @param array Associative array of user attributes
196196
* @param DatafileProjectConfig DatafileProjectConfig instance
197197
*/
198-
protected function sendImpressionEvent($config, $experimentKey, $variationKey, $userId, $attributes)
198+
protected function sendImpressionEvent($config, $experimentKey, $variationKey, $flagKey, $ruleKey, $ruleType, $userId, $attributes)
199199
{
200200
$impressionEvent = $this->_eventBuilder
201-
->createImpressionEvent($config, $experimentKey, $variationKey, $userId, $attributes);
201+
->createImpressionEvent($config, $experimentKey, $variationKey, $flagKey, $ruleKey, $ruleType, $userId, $attributes);
202202
$this->_logger->log(Logger::INFO, sprintf('Activating user "%s" in experiment "%s".', $userId, $experimentKey));
203203
$this->_logger->log(
204204
Logger::DEBUG,
@@ -274,7 +274,7 @@ public function activate($experimentKey, $userId, $attributes = null)
274274
return null;
275275
}
276276

277-
$this->sendImpressionEvent($config, $experimentKey, $variationKey, $userId, $attributes);
277+
$this->sendImpressionEvent($config, $experimentKey, $variationKey, '', $experimentKey, FeatureDecision::DECITION_SOURCE_EXPERIMENT, $userId, $attributes);
278278

279279
return $variationKey;
280280
}
@@ -554,18 +554,22 @@ public function isFeatureEnabled($featureFlagKey, $userId, $attributes = null)
554554
$featureEnabled = false;
555555
$decision = $this->_decisionService->getVariationForFeature($config, $featureFlag, $userId, $attributes);
556556
$variation = $decision->getVariation();
557+
558+
if ($config->getSendFlagDecisions() && ($decision->getSource() == FeatureDecision::DECISION_SOURCE_ROLLOUT || !$variation)) {
559+
$ruleKey = $decision->getExperiment() ? $decision->getExperiment()->getKey() : '';
560+
$this->sendImpressionEvent($config, $ruleKey, $variation ? $variation->getKey() : '', $featureFlagKey, $ruleKey, $decision->getSource(), $userId, $attributes);
561+
}
562+
557563
if ($variation) {
558-
$experiment = $decision->getExperiment();
564+
$experimentKey = $decision->getExperiment()->getKey();
559565
$featureEnabled = $variation->getFeatureEnabled();
560566
if ($decision->getSource() == FeatureDecision::DECISION_SOURCE_FEATURE_TEST) {
561-
$experimentKey = $experiment->getKey();
562-
$variationKey = $variation->getKey();
563567
$sourceInfo = (object) array(
564568
'experimentKey'=> $experimentKey,
565-
'variationKey'=> $variationKey
569+
'variationKey'=> $variation->getKey()
566570
);
567571

568-
$this->sendImpressionEvent($config, $experimentKey, $variationKey, $userId, $attributes);
572+
$this->sendImpressionEvent($config, $experimentKey, $variation->getKey(), $featureFlagKey, $experimentKey, $decision->getSource(), $userId, $attributes);
569573
} else {
570574
$this->_logger->log(Logger::INFO, "The user '{$userId}' is not being experimented on Feature Flag '{$featureFlagKey}'.");
571575
}

tests/ConfigTests/DatafileProjectConfigTest.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,12 @@ public function testInit()
116116
'test_experiment_double_feature' => $this->config->getExperimentFromKey('test_experiment_double_feature'),
117117
'test_experiment_integer_feature' => $this->config->getExperimentFromKey('test_experiment_integer_feature'),
118118
'test_experiment_2' => $this->config->getExperimentFromKey('test_experiment_2'),
119-
'test_experiment_json_feature' => $this->config->getExperimentFromKey('test_experiment_json_feature')
119+
'test_experiment_json_feature' => $this->config->getExperimentFromKey('test_experiment_json_feature'),
120+
'rollout_1_exp_1' => $this->config->getExperimentFromKey('rollout_1_exp_1'),
121+
'rollout_1_exp_2' => $this->config->getExperimentFromKey('rollout_1_exp_2'),
122+
'rollout_1_exp_3' => $this->config->getExperimentFromKey('rollout_1_exp_3'),
123+
'rollout_2_exp_1' => $this->config->getExperimentFromKey('rollout_2_exp_1'),
124+
'rollout_2_exp_2' => $this->config->getExperimentFromKey('rollout_2_exp_2'),
120125
],
121126
$experimentKeyMap->getValue($this->config)
122127
);
@@ -135,7 +140,12 @@ public function testInit()
135140
'122238' => $this->config->getExperimentFromId('122238'),
136141
'122241' => $this->config->getExperimentFromId('122241'),
137142
'111133' => $this->config->getExperimentFromId('111133'),
138-
'122245' => $this->config->getExperimentFromId('122245')
143+
'122245' => $this->config->getExperimentFromId('122245'),
144+
'177770' => $this->config->getExperimentFromId('177770'),
145+
'177772' => $this->config->getExperimentFromId('177772'),
146+
'177776' => $this->config->getExperimentFromId('177776'),
147+
'177774' => $this->config->getExperimentFromId('177774'),
148+
'177779' => $this->config->getExperimentFromId('177779'),
139149
],
140150
$experimentIdMap->getValue($this->config)
141151
);

tests/EventTests/EventBuilderTest.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,13 @@ public function setUp()
7676
$decisions = array('decisions' => [[
7777
'campaign_id'=> '7719770039',
7878
'experiment_id'=> '7716830082',
79-
'variation_id'=> '7721010009'
79+
'variation_id'=> '7721010009',
80+
'metadata'=> [
81+
'flag_key' => 'test_experiment',
82+
'rule_key' => 'test_experiment',
83+
'rule_type' => 'experiment',
84+
'variation_key'=> 'variation'
85+
]
8086
]]
8187
);
8288
$this->expectedImpressionEventParams = $this->expectedEventParams;
@@ -137,6 +143,9 @@ public function testCreateImpressionEventNoAttributesNoValue()
137143
$this->config,
138144
'test_experiment',
139145
'variation',
146+
'test_experiment',
147+
'test_experiment',
148+
'experiment',
140149
$this->testUserId,
141150
null
142151
);
@@ -193,6 +202,9 @@ public function testCreateImpressionEventWithAttributesNoValue()
193202
$this->config,
194203
'test_experiment',
195204
'variation',
205+
'test_experiment',
206+
'test_experiment',
207+
'experiment',
196208
$this->testUserId,
197209
$userAttributes
198210
);
@@ -230,6 +242,9 @@ public function testCreateImpressionEventWithFalseAttributesNoValue()
230242
$this->config,
231243
'test_experiment',
232244
'variation',
245+
'test_experiment',
246+
'test_experiment',
247+
'experiment',
233248
$this->testUserId,
234249
$userAttributes
235250
);
@@ -268,6 +283,9 @@ public function testCreateImpressionEventWithZeroAttributesNoValue()
268283
$this->config,
269284
'test_experiment',
270285
'variation',
286+
'test_experiment',
287+
'test_experiment',
288+
'experiment',
271289
$this->testUserId,
272290
$userAttributes
273291
);
@@ -296,6 +314,9 @@ public function testCreateImpressionEventWithInvalidAttributesNoValue()
296314
$this->config,
297315
'test_experiment',
298316
'variation',
317+
'test_experiment',
318+
'test_experiment',
319+
'experiment',
299320
$this->testUserId,
300321
$userAttributes
301322
);
@@ -333,6 +354,9 @@ public function testCreateImpressionEventWithUserAgentWhenBotFilteringIsEnabled(
333354
$this->config,
334355
'test_experiment',
335356
'variation',
357+
'test_experiment',
358+
'test_experiment',
359+
'experiment',
336360
$this->testUserId,
337361
$userAttributes
338362
);
@@ -377,6 +401,9 @@ public function testCreateImpressionEventWithInvalidAttributeTypes()
377401
$this->config,
378402
'test_experiment',
379403
'variation',
404+
'test_experiment',
405+
'test_experiment',
406+
'experiment',
380407
$this->testUserId,
381408
$userAttributes
382409
);
@@ -424,6 +451,9 @@ public function testCreateImpressionEventWithUserAgentWhenBotFilteringIsDisabled
424451
$configMock,
425452
'test_experiment',
426453
'variation',
454+
'test_experiment',
455+
'test_experiment',
456+
'experiment',
427457
$this->testUserId,
428458
$userAttributes
429459
);
@@ -467,6 +497,9 @@ public function testCreateImpressionEventWithUserAgentWhenBotFilteringIsNull()
467497
$configMock,
468498
'test_experiment',
469499
'variation',
500+
'test_experiment',
501+
'test_experiment',
502+
'experiment',
470503
$this->testUserId,
471504
$userAttributes
472505
);
@@ -793,6 +826,9 @@ public function testCreateImpressionEventWithBucketingIDAttribute()
793826
$this->config,
794827
'test_experiment',
795828
'variation',
829+
'test_experiment',
830+
'test_experiment',
831+
'experiment',
796832
$this->testUserId,
797833
$userAttributes
798834
);

0 commit comments

Comments
 (0)