Skip to content

Commit a5771c7

Browse files
committed
feat: decision listener added for is_feature_enabled
1 parent f772ecc commit a5771c7

File tree

5 files changed

+225
-40
lines changed

5 files changed

+225
-40
lines changed

optimizely/decision_service.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@
2222
from .user_profile import UserProfile
2323

2424
Decision = namedtuple('Decision', 'experiment variation source')
25-
DECISION_SOURCE_EXPERIMENT = 'experiment'
26-
DECISION_SOURCE_ROLLOUT = 'rollout'
25+
DECISION_SOURCE_EXPERIMENT = 'EXPERIMENT'
26+
DECISION_SOURCE_ROLLOUT = 'ROLLOUT'
2727

2828

2929
class DecisionService(object):
@@ -296,6 +296,7 @@ def get_variation_for_feature(self, feature, user_id, attributes=None):
296296
variation.key,
297297
experiment.key
298298
))
299+
return Decision(experiment, variation, DECISION_SOURCE_EXPERIMENT)
299300
else:
300301
self.logger.error(enums.Errors.INVALID_GROUP_ID_ERROR.format('_get_variation_for_feature'))
301302

@@ -312,10 +313,11 @@ def get_variation_for_feature(self, feature, user_id, attributes=None):
312313
variation.key,
313314
experiment.key
314315
))
316+
return Decision(experiment, variation, DECISION_SOURCE_EXPERIMENT)
315317

316318
# Next check if user is part of a rollout
317-
if not variation and feature.rolloutId:
319+
if feature.rolloutId:
318320
rollout = self.config.get_rollout_from_id(feature.rolloutId)
319321
return self.get_variation_for_rollout(rollout, user_id, attributes)
320-
321-
return Decision(experiment, variation, DECISION_SOURCE_EXPERIMENT)
322+
else:
323+
return Decision(None, None, DECISION_SOURCE_ROLLOUT)

optimizely/helpers/enums.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,14 @@ class NotificationTypes(object):
8989
Experiment experiment, str user_id, dict attributes (can be None), Variation variation, Event event
9090
TRACK notification listener has the following parameters:
9191
str event_key, str user_id, dict attributes (can be None), event_tags (can be None), Event event
92+
DECISION notification listener has the following parameters:
93+
str type, str user_id, dict attributes, dict decision_info
9294
"""
9395
ACTIVATE = "ACTIVATE:experiment, user_id, attributes, variation, event"
96+
DECISION = "DECISION:type, user_id, attributes, decision_info"
9497
TRACK = "TRACK:event_key, user_id, attributes, event_tags, event"
98+
99+
100+
class DecisionInfoTypes(object):
101+
EXPERIMENT = "experiment"
102+
FEATURE = "feature"

optimizely/optimizely.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -384,21 +384,43 @@ def is_feature_enabled(self, feature_key, user_id, attributes=None):
384384
if not feature:
385385
return False
386386

387+
experiment_key = None
388+
feature_enabled = False
389+
variation_key = None
387390
decision = self.decision_service.get_variation_for_feature(feature, user_id, attributes)
391+
is_source_experiment = decision.source == decision_service.DECISION_SOURCE_EXPERIMENT
392+
388393
if decision.variation:
394+
feature_enabled = decision.variation.featureEnabled
389395
# Send event if Decision came from an experiment.
390-
if decision.source == decision_service.DECISION_SOURCE_EXPERIMENT:
396+
if is_source_experiment:
397+
experiment_key = decision.experiment.key
398+
variation_key = decision.variation.key
391399
self._send_impression_event(decision.experiment,
392400
decision.variation,
393401
user_id,
394402
attributes)
395403

396-
if decision.variation.featureEnabled:
397-
self.logger.info('Feature "%s" is enabled for user "%s".' % (feature_key, user_id))
398-
return True
399-
400-
self.logger.info('Feature "%s" is not enabled for user "%s".' % (feature_key, user_id))
401-
return False
404+
if feature_enabled:
405+
self.logger.info('Feature "%s" is enabled for user "%s".' % (feature_key, user_id))
406+
else:
407+
self.logger.info('Feature "%s" is not enabled for user "%s".' % (feature_key, user_id))
408+
409+
self.notification_center.send_notifications(
410+
enums.NotificationTypes.DECISION,
411+
enums.DecisionInfoTypes.FEATURE,
412+
user_id,
413+
attributes or {},
414+
{
415+
'feature_key': feature_key,
416+
'feature_enabled': feature_enabled,
417+
'source': decision.source,
418+
'source_experiment_key': experiment_key,
419+
'source_variation_key': variation_key
420+
}
421+
)
422+
423+
return feature_enabled
402424

403425
def get_enabled_features(self, user_id, attributes=None):
404426
""" Returns the list of features that are enabled for the user.

tests/test_decision_service.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ def test_get_variation_for_feature__returns_none_for_user_not_in_group(self):
637637
with mock.patch('optimizely.decision_service.DecisionService.get_experiment_in_group',
638638
return_value=None) as mock_get_experiment_in_group, \
639639
mock.patch('optimizely.decision_service.DecisionService.get_variation') as mock_decision:
640-
self.assertEqual(decision_service.Decision(None, None, decision_service.DECISION_SOURCE_EXPERIMENT),
640+
self.assertEqual(decision_service.Decision(None, None, decision_service.DECISION_SOURCE_ROLLOUT),
641641
self.decision_service.get_variation_for_feature(feature, 'test_user'))
642642

643643
mock_get_experiment_in_group.assert_called_once_with(self.project_config.get_group('19228'), 'test_user')
@@ -647,12 +647,11 @@ def test_get_variation_for_feature__returns_none_for_user_not_in_experiment(self
647647
""" Test that get_variation_for_feature returns None for user not in the associated experiment. """
648648

649649
feature = self.project_config.get_feature_from_key('test_feature_in_experiment')
650-
expected_experiment = self.project_config.get_experiment_from_key('test_experiment')
651650

652651
with mock.patch('optimizely.decision_service.DecisionService.get_variation', return_value=None) as mock_decision:
653-
self.assertEqual(decision_service.Decision(expected_experiment,
652+
self.assertEqual(decision_service.Decision(None,
654653
None,
655-
decision_service.DECISION_SOURCE_EXPERIMENT),
654+
decision_service.DECISION_SOURCE_ROLLOUT),
656655
self.decision_service.get_variation_for_feature(feature, 'test_user'))
657656

658657
mock_decision.assert_called_once_with(
@@ -667,7 +666,7 @@ def test_get_variation_for_feature__returns_none_for_invalid_group_id(self):
667666

668667
with self.mock_decision_logger as mock_decision_logging:
669668
self.assertEqual(
670-
decision_service.Decision(None, None, decision_service.DECISION_SOURCE_EXPERIMENT),
669+
decision_service.Decision(None, None, decision_service.DECISION_SOURCE_ROLLOUT),
671670
self.decision_service.get_variation_for_feature(feature, 'test_user')
672671
)
673672
mock_decision_logging.error.assert_called_once_with(
@@ -679,13 +678,12 @@ def test_get_variation_for_feature__returns_none_for_user_in_group_experiment_no
679678
not targeting a feature, then None is returned. """
680679

681680
feature = self.project_config.get_feature_from_key('test_feature_in_group')
682-
expected_experiment = self.project_config.get_experiment_from_key('group_exp_2')
683681

684682
with mock.patch('optimizely.decision_service.DecisionService.get_experiment_in_group',
685683
return_value=self.project_config.get_experiment_from_key('group_exp_2')) as mock_decision:
686-
self.assertEqual(decision_service.Decision(expected_experiment,
684+
self.assertEqual(decision_service.Decision(None,
687685
None,
688-
decision_service.DECISION_SOURCE_EXPERIMENT),
686+
decision_service.DECISION_SOURCE_ROLLOUT),
689687
self.decision_service.get_variation_for_feature(feature, 'test_user'))
690688

691689
mock_decision.assert_called_once_with(self.project_config.get_group('19228'), 'test_user')

0 commit comments

Comments
 (0)