Skip to content

Commit

Permalink
Merge cc81476 into 2b1d68b
Browse files Browse the repository at this point in the history
  • Loading branch information
pawels-optimizely committed Oct 7, 2020
2 parents 2b1d68b + cc81476 commit 38628a2
Show file tree
Hide file tree
Showing 14 changed files with 273 additions and 43 deletions.
4 changes: 2 additions & 2 deletions optimizely/event/event_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ def _create_visitor(cls, event, logger):
"""

if isinstance(event, user_event.ImpressionEvent):
decision = payload.Decision(event.experiment.layerId, event.experiment.id, event.variation.id,)

metadata = payload.Metadata(event.flag_key, event.flag_type, event.variation.key)
decision = payload.Decision(event.experiment.layerId, event.experiment.id, event.variation.id, metadata)
snapshot_event = payload.SnapshotEvent(
event.experiment.layerId, event.uuid, cls.ACTIVATE_EVENT_KEY, event.timestamp,
)
Expand Down
12 changes: 11 additions & 1 deletion optimizely/event/payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,20 @@ def get_event_params(self):
class Decision(object):
""" Class respresenting Decision. """

def __init__(self, campaign_id, experiment_id, variation_id):
def __init__(self, campaign_id, experiment_id, variation_id, metadata):
self.campaign_id = campaign_id
self.experiment_id = experiment_id
self.variation_id = variation_id
self.metadata = metadata


class Metadata(object):
""" Class respresenting Metadata. """

def __init__(self, flag_key, flag_type, variation_key):
self.flag_key = flag_key
self.flag_type = flag_type
self.variation_key = variation_key


class Snapshot(object):
Expand Down
5 changes: 4 additions & 1 deletion optimizely/event/user_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,14 @@ class ImpressionEvent(UserEvent):
""" Class representing Impression Event. """

def __init__(
self, event_context, user_id, experiment, visitor_attributes, variation, bot_filtering=None,
self, event_context, user_id, experiment, visitor_attributes, variation, flag_key, flag_type,
bot_filtering=None,
):
super(ImpressionEvent, self).__init__(event_context, user_id, visitor_attributes, bot_filtering)
self.experiment = experiment
self.variation = variation
self.flag_key = flag_key
self.flag_type = flag_type


class ConversionEvent(UserEvent):
Expand Down
16 changes: 12 additions & 4 deletions optimizely/event/user_event_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,24 @@

from . import event_factory
from . import user_event
from optimizely.helpers import enums


class UserEventFactory(object):
""" UserEventFactory builds impression and conversion events from a given UserEvent. """

@classmethod
def create_impression_event(
cls, project_config, activated_experiment, variation_id, user_id, user_attributes,
cls, project_config, activated_experiment, variation_id, flag_key, flag_type, user_id, user_attributes
):
""" Create impression Event to be sent to the logging endpoint.
Args:
project_config: Instance of ProjectConfig.
experiment: Experiment for which impression needs to be recorded.
variation_id: ID for variation which would be presented to user.
flag_key: key for a feature flag or experiment
flag_type: type for the source
user_id: ID for user.
attributes: Dict representing user attributes and values which need to be recorded.
Expand All @@ -36,12 +39,15 @@ def create_impression_event(
- activated_experiment is None.
"""

if not activated_experiment:
if not activated_experiment and flag_type is not enums.DecisionSources.ROLLOUT:
return None

experiment_key = activated_experiment.key
variation = project_config.get_variation_from_id(experiment_key, variation_id)
variation, experiment_key = None, None
if activated_experiment:
experiment_key = activated_experiment.key

if variation_id and experiment_key:
variation = project_config.get_variation_from_id(experiment_key, variation_id)
event_context = user_event.EventContext(
project_config.account_id, project_config.project_id, project_config.revision, project_config.anonymize_ip,
)
Expand All @@ -52,6 +58,8 @@ def create_impression_event(
activated_experiment,
event_factory.EventFactory.build_attribute_list(user_attributes, project_config),
variation,
flag_key,
flag_type,
project_config.get_bot_filtering_value(),
)

Expand Down
1 change: 1 addition & 0 deletions optimizely/helpers/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ class DecisionNotificationTypes(object):


class DecisionSources(object):
EXPERIMENT = 'experiment'
FEATURE_TEST = 'feature-test'
ROLLOUT = 'rollout'

Expand Down
21 changes: 16 additions & 5 deletions optimizely/optimizely.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,19 +160,21 @@ def _validate_user_inputs(self, attributes=None, event_tags=None):

return True

def _send_impression_event(self, project_config, experiment, variation, user_id, attributes):
def _send_impression_event(self, project_config, experiment, variation, flag_key, flag_type, user_id, attributes):
""" Helper method to send impression event.
Args:
project_config: Instance of ProjectConfig.
experiment: Experiment for which impression event is being sent.
variation: Variation picked for user for the given experiment.
flag_key: key for a feature flag or experiment
flag_type: type for the source
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, user_id, attributes
project_config, experiment, variation_id, flag_key, flag_type, user_id, attributes
)

self.event_processor.process(user_event)
Expand Down Expand Up @@ -422,7 +424,8 @@ 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, user_id, attributes)
self._send_impression_event(project_config, experiment, variation, experiment.key,
enums.DecisionSources.EXPERIMENT, user_id, attributes)

return variation.key

Expand Down Expand Up @@ -573,6 +576,13 @@ def is_feature_enabled(self, feature_key, user_id, attributes=None):
source_info = {}
decision = self.decision_service.get_variation_for_feature(project_config, feature, user_id, attributes)
is_source_experiment = decision.source == enums.DecisionSources.FEATURE_TEST
is_source_rollout = decision.source == enums.DecisionSources.ROLLOUT

if is_source_rollout and project_config.get_send_flag_decisions_value():
self._send_impression_event(
project_config, decision.experiment, decision.variation, feature.key,
decision.source, user_id, attributes
)

if decision.variation:
if decision.variation.featureEnabled is True:
Expand All @@ -584,7 +594,8 @@ def is_feature_enabled(self, feature_key, user_id, attributes=None):
'variation_key': decision.variation.key,
}
self._send_impression_event(
project_config, decision.experiment, decision.variation, user_id, attributes,
project_config, decision.experiment, decision.variation, feature.key,
decision.source, user_id, attributes
)

if feature_enabled:
Expand Down
10 changes: 10 additions & 0 deletions optimizely/project_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def __init__(self, datafile, logger, error_handler):
self.feature_flags = config.get('featureFlags', [])
self.rollouts = config.get('rollouts', [])
self.anonymize_ip = config.get('anonymizeIP', False)
self.send_flag_decisions = config.get('sendFlagDecisions', False)
self.bot_filtering = config.get('botFiltering', None)

# Utility maps for quick lookup
Expand Down Expand Up @@ -514,6 +515,15 @@ def get_anonymize_ip_value(self):

return self.anonymize_ip

def get_send_flag_decisions_value(self):
""" Gets the Send Flag Decisions value.
Returns:
A boolean value that indicates if we should send flag decisions.
"""

return self.send_flag_decisions

def get_bot_filtering_value(self):
""" Gets the bot filtering value.
Expand Down
1 change: 1 addition & 0 deletions tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def setUp(self, config_dict='config_dict'):
'projectId': '111111',
'version': '4',
'botFiltering': True,
'sendFlagDecisions': True,
'events': [{'key': 'test_event', 'experimentIds': ['111127'], 'id': '111095'}],
'experiments': [
{
Expand Down
14 changes: 14 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,20 @@ def test_get_bot_filtering(self):
self.config_dict_with_features['botFiltering'], project_config.get_bot_filtering_value(),
)

def test_get_send_flag_decisions(self):
""" Test that send_flag_decisions is retrieved correctly when using get_send_flag_decisions_value. """

# Assert send_flag_decisions is None when not provided in data file
self.assertTrue('sendFlagDecisions' not in self.config_dict)
self.assertFalse(self.project_config.get_send_flag_decisions_value())

# Assert send_flag_decisions is retrieved as provided in the data file
opt_obj = optimizely.Optimizely(json.dumps(self.config_dict_with_features))
project_config = opt_obj.config_manager.get_config()
self.assertEqual(
self.config_dict_with_features['sendFlagDecisions'], project_config.get_send_flag_decisions_value(),
)

def test_get_experiment_from_key__valid_key(self):
""" Test that experiment is retrieved correctly for valid experiment key. """

Expand Down
55 changes: 48 additions & 7 deletions tests/test_event_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ def test_create_impression_event(self):
'snapshots': [
{
'decisions': [
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'}
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
'metadata': {'flag_key': 'flag_key',
'flag_type': 'experiment',
'variation_key': 'variation'}}
],
'events': [
{
Expand Down Expand Up @@ -102,6 +105,8 @@ def test_create_impression_event(self):
self.project_config,
self.project_config.get_experiment_from_key('test_experiment'),
'111129',
'flag_key',
'experiment',
'test_user',
None,
)
Expand All @@ -128,7 +133,11 @@ def test_create_impression_event__with_attributes(self):
'snapshots': [
{
'decisions': [
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'}
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
'metadata': {'flag_key': 'flag_key',
'flag_type': 'experiment',
'variation_key': 'variation'},
}
],
'events': [
{
Expand Down Expand Up @@ -156,6 +165,8 @@ def test_create_impression_event__with_attributes(self):
self.project_config,
self.project_config.get_experiment_from_key('test_experiment'),
'111129',
'flag_key',
'experiment',
'test_user',
{'test_attribute': 'test_value'},
)
Expand All @@ -180,7 +191,11 @@ def test_create_impression_event_when_attribute_is_not_in_datafile(self):
'snapshots': [
{
'decisions': [
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'}
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
'metadata': {'flag_key': 'flag_key',
'flag_type': 'experiment',
'variation_key': 'variation'}
}
],
'events': [
{
Expand Down Expand Up @@ -208,6 +223,8 @@ def test_create_impression_event_when_attribute_is_not_in_datafile(self):
self.project_config,
self.project_config.get_experiment_from_key('test_experiment'),
'111129',
'flag_key',
'experiment',
'test_user',
{'do_you_know_me': 'test_value'},
)
Expand Down Expand Up @@ -235,7 +252,11 @@ def test_create_impression_event_calls_is_attribute_valid(self):
'snapshots': [
{
'decisions': [
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'}
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
'metadata': {'flag_key': 'flag_key',
'flag_type': 'experiment',
'variation_key': 'variation'},
}
],
'events': [
{
Expand Down Expand Up @@ -280,6 +301,8 @@ def side_effect(*args, **kwargs):
self.project_config,
self.project_config.get_experiment_from_key('test_experiment'),
'111129',
'flag_key',
'experiment',
'test_user',
attributes,
)
Expand Down Expand Up @@ -317,7 +340,11 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_enabled(
'snapshots': [
{
'decisions': [
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'}
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
'metadata': {'flag_key': 'flag_key',
'flag_type': 'experiment',
'variation_key': 'variation'},
}
],
'events': [
{
Expand Down Expand Up @@ -347,6 +374,8 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_enabled(
self.project_config,
self.project_config.get_experiment_from_key('test_experiment'),
'111129',
'flag_key',
'experiment',
'test_user',
{'$opt_user_agent': 'Edge'},
)
Expand Down Expand Up @@ -379,7 +408,11 @@ def test_create_impression_event__with_empty_attributes_when_bot_filtering_is_en
'snapshots': [
{
'decisions': [
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'}
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
'metadata': {'flag_key': 'flag_key',
'flag_type': 'experiment',
'variation_key': 'variation'},
}
],
'events': [
{
Expand Down Expand Up @@ -409,6 +442,8 @@ def test_create_impression_event__with_empty_attributes_when_bot_filtering_is_en
self.project_config,
self.project_config.get_experiment_from_key('test_experiment'),
'111129',
'flag_key',
'experiment',
'test_user',
None,
)
Expand Down Expand Up @@ -447,7 +482,11 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_disabled
'snapshots': [
{
'decisions': [
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'}
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
'metadata': {'flag_key': 'flag_key',
'flag_type': 'experiment',
'variation_key': 'variation'},
}
],
'events': [
{
Expand Down Expand Up @@ -477,6 +516,8 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_disabled
self.project_config,
self.project_config.get_experiment_from_key('test_experiment'),
'111129',
'flag_key',
'experiment',
'test_user',
{'$opt_user_agent': 'Chrome'},
)
Expand Down
9 changes: 7 additions & 2 deletions tests/test_event_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ def test_impression_event_equals_serialized_payload(self):
'snapshots': [
{
'decisions': [
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182'}
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
'metadata': {'flag_key': 'flag_key',
'flag_type': 'experiment',
'variation_key': 'variation'},
}
],
'events': [
{
Expand All @@ -54,7 +58,8 @@ 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,)
event_decision = payload.Decision('111182', '111127', '111129')
metadata = payload.Metadata('flag_key', 'experiment', 'variation')
event_decision = payload.Decision('111182', '111127', '111129', metadata)

snapshots = payload.Snapshot([event], [event_decision])
user = payload.Visitor([snapshots], [visitor_attr], 'test_user')
Expand Down
Loading

0 comments on commit 38628a2

Please sign in to comment.