Skip to content

Commit e4d949a

Browse files
rashidspaliabbasrizvi
authored andcommitted
feat(DecisionListener): Adds feature decision listener. (#169)
1 parent 1f34b2a commit e4d949a

File tree

5 files changed

+220
-41
lines changed

5 files changed

+220
-41
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: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,10 @@ class NotificationTypes(object):
9393
DecisionInfoTypes type, str user_id, dict attributes (can be None), dict decision_info
9494
"""
9595
ACTIVATE = "ACTIVATE:experiment, user_id, attributes, variation, event"
96+
DECISION = "DECISION:type, user_id, attributes, decision_info"
9697
TRACK = "TRACK:event_key, user_id, attributes, event_tags, event"
97-
DECISION = "DECISON:type, user_id, attributes, decision_info"
9898

9999

100100
class DecisionInfoTypes(object):
101-
EXPERIMENT = "experiment"
101+
EXPERIMENT = "experiment"
102+
FEATURE = "feature"

optimizely/optimizely.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -409,21 +409,44 @@ def is_feature_enabled(self, feature_key, user_id, attributes=None):
409409
if not feature:
410410
return False
411411

412+
experiment_key = None
413+
feature_enabled = False
414+
variation_key = None
412415
decision = self.decision_service.get_variation_for_feature(feature, user_id, attributes)
416+
is_source_experiment = decision.source == decision_service.DECISION_SOURCE_EXPERIMENT
417+
413418
if decision.variation:
419+
if decision.variation.featureEnabled is True:
420+
feature_enabled = True
414421
# Send event if Decision came from an experiment.
415-
if decision.source == decision_service.DECISION_SOURCE_EXPERIMENT:
422+
if is_source_experiment:
423+
experiment_key = decision.experiment.key
424+
variation_key = decision.variation.key
416425
self._send_impression_event(decision.experiment,
417426
decision.variation,
418427
user_id,
419428
attributes)
420429

421-
if decision.variation.featureEnabled:
422-
self.logger.info('Feature "%s" is enabled for user "%s".' % (feature_key, user_id))
423-
return True
430+
if feature_enabled:
431+
self.logger.info('Feature "%s" is enabled for user "%s".' % (feature_key, user_id))
432+
else:
433+
self.logger.info('Feature "%s" is not enabled for user "%s".' % (feature_key, user_id))
434+
435+
self.notification_center.send_notifications(
436+
enums.NotificationTypes.DECISION,
437+
enums.DecisionInfoTypes.FEATURE,
438+
user_id,
439+
attributes or {},
440+
{
441+
'feature_key': feature_key,
442+
'feature_enabled': feature_enabled,
443+
'source': decision.source,
444+
'source_experiment_key': experiment_key,
445+
'source_variation_key': variation_key
446+
}
447+
)
424448

425-
self.logger.info('Feature "%s" is not enabled for user "%s".' % (feature_key, user_id))
426-
return False
449+
return feature_enabled
427450

428451
def get_enabled_features(self, user_id, attributes=None):
429452
""" 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)