diff --git a/optimizely/event/event_factory.py b/optimizely/event/event_factory.py index c8062ca4..54155358 100644 --- a/optimizely/event/event_factory.py +++ b/optimizely/event/event_factory.py @@ -99,7 +99,7 @@ def _create_visitor(cls, event, logger): experiment_layerId = event.experiment.layerId experiment_id = event.experiment.id - metadata = payload.Metadata(event.flag_key, event.rule_key, event.rule_type, variation_key) + metadata = payload.Metadata(event.flag_key, event.rule_key, event.rule_type, variation_key, event.enabled) decision = payload.Decision(experiment_layerId, experiment_id, variation_id, metadata) snapshot_event = payload.SnapshotEvent( experiment_layerId, event.uuid, cls.ACTIVATE_EVENT_KEY, event.timestamp, diff --git a/optimizely/event/payload.py b/optimizely/event/payload.py index 53b24b9e..b7e51a24 100644 --- a/optimizely/event/payload.py +++ b/optimizely/event/payload.py @@ -71,11 +71,12 @@ def __init__(self, campaign_id, experiment_id, variation_id, metadata): class Metadata(object): """ Class respresenting Metadata. """ - def __init__(self, flag_key, rule_key, rule_type, variation_key): + def __init__(self, flag_key, rule_key, rule_type, variation_key, enabled): self.flag_key = flag_key self.rule_key = rule_key self.rule_type = rule_type self.variation_key = variation_key + self.enabled = enabled class Snapshot(object): diff --git a/optimizely/event/user_event.py b/optimizely/event/user_event.py index 57b2c2e5..0c4e021a 100644 --- a/optimizely/event/user_event.py +++ b/optimizely/event/user_event.py @@ -41,8 +41,8 @@ class ImpressionEvent(UserEvent): """ Class representing Impression Event. """ def __init__( - self, event_context, user_id, experiment, visitor_attributes, variation, flag_key, rule_key, rule_type, - bot_filtering=None, + self, event_context, user_id, experiment, visitor_attributes, variation, flag_key, + rule_key, rule_type, enabled, bot_filtering=None ): super(ImpressionEvent, self).__init__(event_context, user_id, visitor_attributes, bot_filtering) self.experiment = experiment @@ -50,6 +50,7 @@ def __init__( self.flag_key = flag_key self.rule_key = rule_key self.rule_type = rule_type + self.enabled = enabled class ConversionEvent(UserEvent): diff --git a/optimizely/event/user_event_factory.py b/optimizely/event/user_event_factory.py index 002bee17..94ca8638 100644 --- a/optimizely/event/user_event_factory.py +++ b/optimizely/event/user_event_factory.py @@ -21,7 +21,8 @@ class UserEventFactory(object): @classmethod def create_impression_event( - cls, project_config, activated_experiment, variation_id, flag_key, rule_key, rule_type, user_id, user_attributes + cls, project_config, activated_experiment, variation_id, flag_key, rule_key, rule_type, + enabled, user_id, user_attributes ): """ Create impression Event to be sent to the logging endpoint. @@ -32,6 +33,7 @@ def create_impression_event( flag_key: key for a feature flag. rule_key: key for an experiment. rule_type: type for the source. + enabled: boolean representing if feature is enabled user_id: ID for user. attributes: Dict representing user attributes and values which need to be recorded. @@ -62,6 +64,7 @@ def create_impression_event( flag_key, rule_key, rule_type, + enabled, project_config.get_bot_filtering_value(), ) diff --git a/optimizely/optimizely.py b/optimizely/optimizely.py index 400db190..74bde6a2 100644 --- a/optimizely/optimizely.py +++ b/optimizely/optimizely.py @@ -160,8 +160,8 @@ def _validate_user_inputs(self, attributes=None, event_tags=None): return True - def _send_impression_event(self, project_config, experiment, variation, flag_key, rule_key, rule_type, user_id, - attributes): + def _send_impression_event(self, project_config, experiment, variation, flag_key, rule_key, rule_type, enabled, + user_id, attributes): """ Helper method to send impression event. Args: @@ -171,12 +171,13 @@ def _send_impression_event(self, project_config, experiment, variation, flag_key flag_key: key for a feature flag. rule_key: key for an experiment. rule_type: type for the source. + enabled: boolean representing if feature is enabled user_id: ID for user. attributes: Dict representing user attributes and values which need to be recorded. """ variation_id = variation.id if variation is not None else None user_event = user_event_factory.UserEventFactory.create_impression_event( - project_config, experiment, variation_id, flag_key, rule_key, rule_type, user_id, attributes + project_config, experiment, variation_id, flag_key, rule_key, rule_type, enabled, user_id, attributes ) self.event_processor.process(user_event) @@ -427,7 +428,7 @@ def activate(self, experiment_key, user_id, attributes=None): # Create and dispatch impression event self.logger.info('Activating user "%s" in experiment "%s".' % (user_id, experiment.key)) self._send_impression_event(project_config, experiment, variation, '', experiment.key, - enums.DecisionSources.EXPERIMENT, user_id, attributes) + enums.DecisionSources.EXPERIMENT, True, user_id, attributes) return variation.key @@ -580,25 +581,26 @@ def is_feature_enabled(self, feature_key, user_id, attributes=None): is_source_experiment = decision.source == enums.DecisionSources.FEATURE_TEST is_source_rollout = decision.source == enums.DecisionSources.ROLLOUT + if decision.variation: + if decision.variation.featureEnabled is True: + feature_enabled = True + if (is_source_rollout or not decision.variation) and project_config.get_send_flag_decisions_value(): self._send_impression_event( project_config, decision.experiment, decision.variation, feature.key, decision.experiment.key if - decision.experiment else '', decision.source, user_id, attributes + decision.experiment else '', decision.source, feature_enabled, user_id, attributes ) - if decision.variation: - if decision.variation.featureEnabled is True: - feature_enabled = True - # Send event if Decision came from an experiment. - if is_source_experiment: - source_info = { - 'experiment_key': decision.experiment.key, - 'variation_key': decision.variation.key, - } - self._send_impression_event( - project_config, decision.experiment, decision.variation, feature.key, decision.experiment.key, - decision.source, user_id, attributes - ) + # Send event if Decision came from an experiment. + if is_source_experiment and decision.variation: + source_info = { + 'experiment_key': decision.experiment.key, + 'variation_key': decision.variation.key, + } + self._send_impression_event( + project_config, decision.experiment, decision.variation, feature.key, decision.experiment.key, + decision.source, feature_enabled, user_id, attributes + ) if feature_enabled: self.logger.info('Feature "%s" is enabled for user "%s".' % (feature_key, user_id)) diff --git a/tests/test_event_factory.py b/tests/test_event_factory.py index 93e5db7c..2e8a6192 100644 --- a/tests/test_event_factory.py +++ b/tests/test_event_factory.py @@ -78,7 +78,8 @@ def test_create_impression_event(self): 'metadata': {'flag_key': 'flag_key', 'rule_key': 'rule_key', 'rule_type': 'experiment', - 'variation_key': 'variation'}} + 'variation_key': 'variation', + 'enabled': False}} ], 'events': [ { @@ -109,6 +110,7 @@ def test_create_impression_event(self): 'flag_key', 'rule_key', 'experiment', + False, 'test_user', None, ) @@ -139,7 +141,8 @@ def test_create_impression_event__with_attributes(self): 'metadata': {'flag_key': 'flag_key', 'rule_key': 'rule_key', 'rule_type': 'experiment', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': True}, } ], 'events': [ @@ -171,6 +174,7 @@ def test_create_impression_event__with_attributes(self): 'flag_key', 'rule_key', 'experiment', + True, 'test_user', {'test_attribute': 'test_value'}, ) @@ -199,7 +203,8 @@ def test_create_impression_event_when_attribute_is_not_in_datafile(self): 'metadata': {'flag_key': 'flag_key', 'rule_key': 'rule_key', 'rule_type': 'experiment', - 'variation_key': 'variation'} + 'variation_key': 'variation', + 'enabled': True} } ], 'events': [ @@ -231,6 +236,7 @@ def test_create_impression_event_when_attribute_is_not_in_datafile(self): 'flag_key', 'rule_key', 'experiment', + True, 'test_user', {'do_you_know_me': 'test_value'}, ) @@ -350,7 +356,8 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_enabled( 'metadata': {'flag_key': 'flag_key', 'rule_key': 'rule_key', 'rule_type': 'experiment', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': False}, } ], 'events': [ @@ -384,6 +391,7 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_enabled( 'flag_key', 'rule_key', 'experiment', + False, 'test_user', {'$opt_user_agent': 'Edge'}, ) @@ -420,7 +428,8 @@ def test_create_impression_event__with_empty_attributes_when_bot_filtering_is_en 'metadata': {'flag_key': 'flag_key', 'rule_key': 'rule_key', 'rule_type': 'experiment', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': False}, } ], 'events': [ @@ -454,6 +463,7 @@ def test_create_impression_event__with_empty_attributes_when_bot_filtering_is_en 'flag_key', 'rule_key', 'experiment', + False, 'test_user', None, ) @@ -496,7 +506,8 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_disabled 'metadata': {'flag_key': 'flag_key', 'rule_key': 'rule_key', 'rule_type': 'experiment', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': True}, } ], 'events': [ @@ -530,6 +541,7 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_disabled 'flag_key', 'rule_key', 'experiment', + True, 'test_user', {'$opt_user_agent': 'Chrome'}, ) diff --git a/tests/test_event_payload.py b/tests/test_event_payload.py index ae168d8e..fdbf1cbf 100644 --- a/tests/test_event_payload.py +++ b/tests/test_event_payload.py @@ -34,7 +34,8 @@ def test_impression_event_equals_serialized_payload(self): 'metadata': {'flag_key': 'flag_key', 'rule_key': 'rule_key', 'rule_type': 'experiment', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': False}, } ], 'events': [ @@ -59,7 +60,7 @@ def test_impression_event_equals_serialized_payload(self): batch = payload.EventBatch('12001', '111001', '42', 'python-sdk', version.__version__, False, True) visitor_attr = payload.VisitorAttribute('111094', 'test_attribute', 'custom', 'test_value') event = payload.SnapshotEvent('111182', 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', 'campaign_activated', 42123,) - metadata = payload.Metadata('flag_key', 'rule_key', 'experiment', 'variation') + metadata = payload.Metadata('flag_key', 'rule_key', 'experiment', 'variation', False) event_decision = payload.Decision('111182', '111127', '111129', metadata) snapshots = payload.Snapshot([event], [event_decision]) diff --git a/tests/test_optimizely.py b/tests/test_optimizely.py index 4e3b9cfe..92952556 100644 --- a/tests/test_optimizely.py +++ b/tests/test_optimizely.py @@ -325,7 +325,8 @@ def test_activate(self): 'metadata': {'flag_key': '', 'rule_key': 'test_experiment', 'rule_type': 'experiment', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': True}, } ], 'events': [ @@ -703,7 +704,8 @@ def test_activate__with_attributes__audience_match(self): 'metadata': {'flag_key': '', 'rule_key': 'test_experiment', 'rule_type': 'experiment', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': True}, } ], 'events': [ @@ -785,7 +787,8 @@ def test_activate__with_attributes_of_different_types(self): 'metadata': {'flag_key': '', 'rule_key': 'test_experiment', 'rule_type': 'experiment', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': True}, } ], 'events': [ @@ -981,7 +984,8 @@ def test_activate__with_attributes__audience_match__forced_bucketing(self): 'metadata': {'flag_key': '', 'rule_key': 'test_experiment', 'rule_type': 'experiment', - 'variation_key': 'control'}, + 'variation_key': 'control', + 'enabled': True}, } ], 'events': [ @@ -1056,7 +1060,8 @@ def test_activate__with_attributes__audience_match__bucketing_id_provided(self): 'metadata': {'flag_key': '', 'rule_key': 'test_experiment', 'rule_type': 'experiment', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': True}, } ], 'events': [ @@ -2004,7 +2009,8 @@ def test_is_feature_enabled__returns_true_for_feature_experiment_if_feature_enab 'metadata': {'flag_key': 'test_feature_in_experiment', 'rule_key': 'test_experiment', 'rule_type': 'feature-test', - 'variation_key': 'variation'}} + 'variation_key': 'variation', + 'enabled': True}} ], 'events': [ { @@ -2102,7 +2108,8 @@ def test_is_feature_enabled__returns_false_for_feature_experiment_if_feature_dis 'metadata': {'flag_key': 'test_feature_in_experiment', 'rule_key': 'test_experiment', 'rule_type': 'feature-test', - 'variation_key': 'control'}} + 'variation_key': 'control', + 'enabled': False}} ], 'events': [ { @@ -2248,7 +2255,8 @@ def test_is_feature_enabled__returns_true_for_feature_rollout_if_feature_enabled 'metadata': {'flag_key': 'test_feature_in_experiment', 'rule_key': 'test_experiment', 'rule_type': 'rollout', - 'variation_key': 'variation'}, + 'variation_key': 'variation', + 'enabled': True}, } ], 'events': [ diff --git a/tests/test_user_event_factory.py b/tests/test_user_event_factory.py index 4e8c2845..e723a823 100644 --- a/tests/test_user_event_factory.py +++ b/tests/test_user_event_factory.py @@ -29,7 +29,7 @@ def test_impression_event(self): user_id = 'test_user' impression_event = UserEventFactory.create_impression_event(project_config, experiment, '111128', 'flag_key', - 'rule_key', 'rule_type', user_id, None) + 'rule_key', 'rule_type', True, user_id, None) self.assertEqual(self.project_config.project_id, impression_event.event_context.project_id) self.assertEqual(self.project_config.revision, impression_event.event_context.revision) @@ -51,7 +51,7 @@ def test_impression_event__with_attributes(self): user_attributes = {'test_attribute': 'test_value', 'boolean_key': True} impression_event = UserEventFactory.create_impression_event( - project_config, experiment, '111128', 'flag_key', 'rule_key', 'rule_type', user_id, user_attributes + project_config, experiment, '111128', 'flag_key', 'rule_key', 'rule_type', True, user_id, user_attributes ) expected_attrs = EventFactory.build_attribute_list(user_attributes, project_config)