Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(DecisionListener): Adds feature decision listener. #169

Merged
merged 5 commits into from
Apr 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions optimizely/decision_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
from .user_profile import UserProfile

Decision = namedtuple('Decision', 'experiment variation source')
DECISION_SOURCE_EXPERIMENT = 'experiment'
DECISION_SOURCE_ROLLOUT = 'rollout'
DECISION_SOURCE_EXPERIMENT = 'EXPERIMENT'
DECISION_SOURCE_ROLLOUT = 'ROLLOUT'


class DecisionService(object):
Expand Down Expand Up @@ -296,6 +296,7 @@ def get_variation_for_feature(self, feature, user_id, attributes=None):
variation.key,
experiment.key
))
return Decision(experiment, variation, DECISION_SOURCE_EXPERIMENT)
else:
self.logger.error(enums.Errors.INVALID_GROUP_ID_ERROR.format('_get_variation_for_feature'))

Expand All @@ -312,10 +313,11 @@ def get_variation_for_feature(self, feature, user_id, attributes=None):
variation.key,
experiment.key
))
return Decision(experiment, variation, DECISION_SOURCE_EXPERIMENT)

# Next check if user is part of a rollout
if not variation and feature.rolloutId:
if feature.rolloutId:
rollout = self.config.get_rollout_from_id(feature.rolloutId)
return self.get_variation_for_rollout(rollout, user_id, attributes)

return Decision(experiment, variation, DECISION_SOURCE_EXPERIMENT)
else:
return Decision(None, None, DECISION_SOURCE_ROLLOUT)
5 changes: 3 additions & 2 deletions optimizely/helpers/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,10 @@ class NotificationTypes(object):
DecisionInfoTypes type, str user_id, dict attributes (can be None), dict decision_info
"""
ACTIVATE = "ACTIVATE:experiment, user_id, attributes, variation, event"
DECISION = "DECISION:type, user_id, attributes, decision_info"
TRACK = "TRACK:event_key, user_id, attributes, event_tags, event"
DECISION = "DECISON:type, user_id, attributes, decision_info"


class DecisionInfoTypes(object):
EXPERIMENT = "experiment"
EXPERIMENT = "experiment"
FEATURE = "feature"
35 changes: 29 additions & 6 deletions optimizely/optimizely.py
Original file line number Diff line number Diff line change
Expand Up @@ -409,21 +409,44 @@ def is_feature_enabled(self, feature_key, user_id, attributes=None):
if not feature:
return False

experiment_key = None
feature_enabled = False
variation_key = None
decision = self.decision_service.get_variation_for_feature(feature, user_id, attributes)
is_source_experiment = decision.source == decision_service.DECISION_SOURCE_EXPERIMENT

if decision.variation:
if decision.variation.featureEnabled is True:
feature_enabled = True
# Send event if Decision came from an experiment.
if decision.source == decision_service.DECISION_SOURCE_EXPERIMENT:
if is_source_experiment:
experiment_key = decision.experiment.key
variation_key = decision.variation.key
self._send_impression_event(decision.experiment,
decision.variation,
user_id,
attributes)

if decision.variation.featureEnabled:
self.logger.info('Feature "%s" is enabled for user "%s".' % (feature_key, user_id))
return True
if feature_enabled:
self.logger.info('Feature "%s" is enabled for user "%s".' % (feature_key, user_id))
else:
self.logger.info('Feature "%s" is not enabled for user "%s".' % (feature_key, user_id))

self.notification_center.send_notifications(
enums.NotificationTypes.DECISION,
enums.DecisionInfoTypes.FEATURE,
user_id,
attributes or {},
{
'feature_key': feature_key,
'feature_enabled': feature_enabled,
'source': decision.source,
'source_experiment_key': experiment_key,
'source_variation_key': variation_key
}
)

self.logger.info('Feature "%s" is not enabled for user "%s".' % (feature_key, user_id))
return False
return feature_enabled

def get_enabled_features(self, user_id, attributes=None):
""" Returns the list of features that are enabled for the user.
Expand Down
14 changes: 6 additions & 8 deletions tests/test_decision_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ def test_get_variation_for_feature__returns_none_for_user_not_in_group(self):
with mock.patch('optimizely.decision_service.DecisionService.get_experiment_in_group',
return_value=None) as mock_get_experiment_in_group, \
mock.patch('optimizely.decision_service.DecisionService.get_variation') as mock_decision:
self.assertEqual(decision_service.Decision(None, None, decision_service.DECISION_SOURCE_EXPERIMENT),
self.assertEqual(decision_service.Decision(None, None, decision_service.DECISION_SOURCE_ROLLOUT),
self.decision_service.get_variation_for_feature(feature, 'test_user'))

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

feature = self.project_config.get_feature_from_key('test_feature_in_experiment')
expected_experiment = self.project_config.get_experiment_from_key('test_experiment')

with mock.patch('optimizely.decision_service.DecisionService.get_variation', return_value=None) as mock_decision:
self.assertEqual(decision_service.Decision(expected_experiment,
self.assertEqual(decision_service.Decision(None,
None,
decision_service.DECISION_SOURCE_EXPERIMENT),
decision_service.DECISION_SOURCE_ROLLOUT),
self.decision_service.get_variation_for_feature(feature, 'test_user'))

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

with self.mock_decision_logger as mock_decision_logging:
self.assertEqual(
decision_service.Decision(None, None, decision_service.DECISION_SOURCE_EXPERIMENT),
decision_service.Decision(None, None, decision_service.DECISION_SOURCE_ROLLOUT),
self.decision_service.get_variation_for_feature(feature, 'test_user')
)
mock_decision_logging.error.assert_called_once_with(
Expand All @@ -679,13 +678,12 @@ def test_get_variation_for_feature__returns_none_for_user_in_group_experiment_no
not targeting a feature, then None is returned. """

feature = self.project_config.get_feature_from_key('test_feature_in_group')
expected_experiment = self.project_config.get_experiment_from_key('group_exp_2')

with mock.patch('optimizely.decision_service.DecisionService.get_experiment_in_group',
return_value=self.project_config.get_experiment_from_key('group_exp_2')) as mock_decision:
self.assertEqual(decision_service.Decision(expected_experiment,
self.assertEqual(decision_service.Decision(None,
None,
decision_service.DECISION_SOURCE_EXPERIMENT),
decision_service.DECISION_SOURCE_ROLLOUT),
self.decision_service.get_variation_for_feature(feature, 'test_user'))

mock_decision.assert_called_once_with(self.project_config.get_group('19228'), 'test_user')
Expand Down
Loading