Skip to content

Commit 0e25622

Browse files
update: add cmab_uuid parameter to impression events
1 parent c2b3d96 commit 0e25622

File tree

8 files changed

+286
-14
lines changed

8 files changed

+286
-14
lines changed

optimizely/event/event_factory.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ def _create_visitor(cls, event: Optional[user_event.UserEvent], logger: Logger)
123123
experiment_layerId = event.experiment.layerId
124124
experiment_id = event.experiment.id
125125

126-
metadata = payload.Metadata(event.flag_key, event.rule_key, event.rule_type, variation_key, event.enabled)
126+
metadata = payload.Metadata(event.flag_key, event.rule_key,
127+
event.rule_type, variation_key,
128+
event.enabled, event.cmab_uuid)
127129
decision = payload.Decision(experiment_layerId, experiment_id, variation_id, metadata)
128130
snapshot_event = payload.SnapshotEvent(
129131
experiment_layerId, event.uuid, cls.ACTIVATE_EVENT_KEY, event.timestamp,

optimizely/event/payload.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,15 @@ def __init__(self, campaign_id: str, experiment_id: str, variation_id: str, meta
8181
class Metadata:
8282
""" Class respresenting Metadata. """
8383

84-
def __init__(self, flag_key: str, rule_key: str, rule_type: str, variation_key: str, enabled: bool):
84+
def __init__(self, flag_key: str, rule_key: str, rule_type: str,
85+
variation_key: str, enabled: bool, cmab_uuid: Optional[str] = None):
8586
self.flag_key = flag_key
8687
self.rule_key = rule_key
8788
self.rule_type = rule_type
8889
self.variation_key = variation_key
8990
self.enabled = enabled
91+
if cmab_uuid:
92+
self.cmab_uuid = cmab_uuid
9093

9194

9295
class Snapshot:

optimizely/event/user_event.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ def __init__(
7070
rule_key: str,
7171
rule_type: str,
7272
enabled: bool,
73-
bot_filtering: Optional[bool] = None
73+
bot_filtering: Optional[bool] = None,
74+
cmab_uuid: Optional[str] = None
7475
):
7576
super().__init__(event_context, user_id, visitor_attributes, bot_filtering)
7677
self.experiment = experiment
@@ -79,6 +80,7 @@ def __init__(
7980
self.rule_key = rule_key
8081
self.rule_type = rule_type
8182
self.enabled = enabled
83+
self.cmab_uuid = cmab_uuid
8284

8385

8486
class ConversionEvent(UserEvent):

optimizely/event/user_event_factory.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ def create_impression_event(
4040
rule_type: str,
4141
enabled: bool,
4242
user_id: str,
43-
user_attributes: Optional[UserAttributes]
43+
user_attributes: Optional[UserAttributes],
44+
cmab_uuid: Optional[str]
4445
) -> Optional[user_event.ImpressionEvent]:
4546
""" Create impression Event to be sent to the logging endpoint.
4647
@@ -90,6 +91,7 @@ def create_impression_event(
9091
rule_type,
9192
enabled,
9293
project_config.get_bot_filtering_value(),
94+
cmab_uuid,
9395
)
9496

9597
@classmethod

optimizely/optimizely.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from __future__ import annotations
1515

1616
from typing import TYPE_CHECKING, Any, Optional
17+
from unittest import mock
1718

1819
from . import decision_service
1920
from . import entities
@@ -260,7 +261,7 @@ def _validate_user_inputs(
260261
def _send_impression_event(
261262
self, project_config: project_config.ProjectConfig, experiment: Optional[entities.Experiment],
262263
variation: Optional[entities.Variation], flag_key: str, rule_key: str, rule_type: str,
263-
enabled: bool, user_id: str, attributes: Optional[UserAttributes]
264+
enabled: bool, user_id: str, attributes: Optional[UserAttributes], cmab_uuid: Optional[str]
264265
) -> None:
265266
""" Helper method to send impression event.
266267
@@ -280,7 +281,9 @@ def _send_impression_event(
280281

281282
variation_id = variation.id if variation is not None else None
282283
user_event = user_event_factory.UserEventFactory.create_impression_event(
283-
project_config, experiment, variation_id, flag_key, rule_key, rule_type, enabled, user_id, attributes
284+
project_config, experiment, variation_id,
285+
flag_key, rule_key, rule_type,
286+
enabled, user_id, attributes, cmab_uuid
284287
)
285288

286289
if user_event is None:
@@ -550,7 +553,7 @@ def activate(self, experiment_key: str, user_id: str, attributes: Optional[UserA
550553
# Create and dispatch impression event
551554
self.logger.info(f'Activating user "{user_id}" in experiment "{experiment.key}".')
552555
self._send_impression_event(project_config, experiment, variation, '', experiment.key,
553-
enums.DecisionSources.EXPERIMENT, True, user_id, attributes)
556+
enums.DecisionSources.EXPERIMENT, True, user_id, attributes, None)
554557

555558
return variation.key
556559

@@ -718,7 +721,9 @@ def is_feature_enabled(self, feature_key: str, user_id: str, attributes: Optiona
718721

719722
user_context = OptimizelyUserContext(self, self.logger, user_id, attributes, False)
720723

721-
decision = self.decision_service.get_variation_for_feature(project_config, feature, user_context)['decision']
724+
decision_result = self.decision_service.get_variation_for_feature(project_config, feature, user_context)
725+
decision = decision_result['decision']
726+
cmab_uuid = decision_result['decision'].cmab_uuid
722727
is_source_experiment = decision.source == enums.DecisionSources.FEATURE_TEST
723728
is_source_rollout = decision.source == enums.DecisionSources.ROLLOUT
724729

@@ -729,7 +734,7 @@ def is_feature_enabled(self, feature_key: str, user_id: str, attributes: Optiona
729734
if (is_source_rollout or not decision.variation) and project_config.get_send_flag_decisions_value():
730735
self._send_impression_event(
731736
project_config, decision.experiment, decision.variation, feature.key, decision.experiment.key if
732-
decision.experiment else '', str(decision.source), feature_enabled, user_id, attributes
737+
decision.experiment else '', str(decision.source), feature_enabled, user_id, attributes, cmab_uuid
733738
)
734739

735740
# Send event if Decision came from an experiment.
@@ -740,7 +745,7 @@ def is_feature_enabled(self, feature_key: str, user_id: str, attributes: Optiona
740745
}
741746
self._send_impression_event(
742747
project_config, decision.experiment, decision.variation, feature.key, decision.experiment.key,
743-
str(decision.source), feature_enabled, user_id, attributes
748+
str(decision.source), feature_enabled, user_id, attributes, cmab_uuid
744749
)
745750

746751
if feature_enabled:
@@ -1193,7 +1198,9 @@ def _create_optimizely_decision(
11931198
flag_decision.variation,
11941199
flag_key, rule_key or '',
11951200
str(decision_source), feature_enabled,
1196-
user_id, attributes)
1201+
user_id, attributes,
1202+
flag_decision.cmab_uuid
1203+
)
11971204

11981205
decision_event_dispatched = True
11991206

tests/test_event_factory.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ def test_create_impression_event(self):
113113
False,
114114
'test_user',
115115
None,
116+
None
116117
)
117118

118119
log_event = EventFactory.create_log_event(event_obj, self.logger)
@@ -177,6 +178,7 @@ def test_create_impression_event__with_attributes(self):
177178
True,
178179
'test_user',
179180
{'test_attribute': 'test_value'},
181+
None
180182
)
181183

182184
log_event = EventFactory.create_log_event(event_obj, self.logger)
@@ -239,6 +241,7 @@ def test_create_impression_event_when_attribute_is_not_in_datafile(self):
239241
True,
240242
'test_user',
241243
{'do_you_know_me': 'test_value'},
244+
None
242245
)
243246

244247
log_event = EventFactory.create_log_event(event_obj, self.logger)
@@ -394,6 +397,7 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_enabled(
394397
False,
395398
'test_user',
396399
{'$opt_user_agent': 'Edge'},
400+
None
397401
)
398402

399403
log_event = EventFactory.create_log_event(event_obj, self.logger)
@@ -466,6 +470,7 @@ def test_create_impression_event__with_empty_attributes_when_bot_filtering_is_en
466470
False,
467471
'test_user',
468472
None,
473+
None
469474
)
470475

471476
log_event = EventFactory.create_log_event(event_obj, self.logger)
@@ -544,6 +549,7 @@ def test_create_impression_event__with_user_agent_when_bot_filtering_is_disabled
544549
True,
545550
'test_user',
546551
{'$opt_user_agent': 'Chrome'},
552+
None
547553
)
548554

549555
log_event = EventFactory.create_log_event(event_obj, self.logger)
@@ -920,3 +926,136 @@ def test_create_conversion_event__when_event_is_used_in_multiple_experiments(sel
920926
self._validate_event_object(
921927
log_event, EventFactory.EVENT_ENDPOINT, expected_params, EventFactory.HTTP_VERB, EventFactory.HTTP_HEADERS,
922928
)
929+
930+
def test_create_impression_event_with_cmab_uuid(self):
931+
""" Test that create_impression_event creates LogEvent object with CMAB UUID in metadata. """
932+
933+
expected_params = {
934+
'account_id': '12001',
935+
'project_id': '111001',
936+
'visitors': [
937+
{
938+
'visitor_id': 'test_user',
939+
'attributes': [],
940+
'snapshots': [
941+
{
942+
'decisions': [
943+
{'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
944+
'metadata': {'flag_key': '',
945+
'rule_key': 'rule_key',
946+
'rule_type': 'experiment',
947+
'variation_key': 'variation',
948+
'enabled': False,
949+
'cmab_uuid': 'test-cmab-uuid-123'
950+
}
951+
}
952+
],
953+
'events': [
954+
{
955+
'timestamp': 42123,
956+
'entity_id': '111182',
957+
'uuid': 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c',
958+
'key': 'campaign_activated',
959+
}
960+
],
961+
}
962+
],
963+
}
964+
],
965+
'client_name': 'python-sdk',
966+
'client_version': version.__version__,
967+
'enrich_decisions': True,
968+
'anonymize_ip': False,
969+
'revision': '42',
970+
}
971+
972+
with mock.patch('time.time', return_value=42.123), mock.patch(
973+
'uuid.uuid4', return_value='a68cf1ad-0393-4e18-af87-efe8f01a7c9c'
974+
):
975+
event_obj = UserEventFactory.create_impression_event(
976+
self.project_config,
977+
self.project_config.get_experiment_from_key('test_experiment'),
978+
'111129',
979+
'',
980+
'rule_key',
981+
'experiment',
982+
False,
983+
'test_user',
984+
None,
985+
'test-cmab-uuid-123' # cmab_uuid parameter
986+
)
987+
988+
log_event = EventFactory.create_log_event(event_obj, self.logger)
989+
990+
self._validate_event_object(
991+
log_event, EventFactory.EVENT_ENDPOINT, expected_params, EventFactory.HTTP_VERB, EventFactory.HTTP_HEADERS,
992+
)
993+
994+
def test_create_impression_event_without_cmab_uuid(self):
995+
""" Test that create_impression_event creates LogEvent object without CMAB UUID when not provided. """
996+
997+
expected_params = {
998+
'account_id': '12001',
999+
'project_id': '111001',
1000+
'visitors': [
1001+
{
1002+
'visitor_id': 'test_user',
1003+
'attributes': [],
1004+
'snapshots': [
1005+
{
1006+
'decisions': [
1007+
{
1008+
'variation_id': '111129', 'experiment_id': '111127', 'campaign_id': '111182',
1009+
'metadata': {
1010+
'flag_key': '',
1011+
'rule_key': 'rule_key',
1012+
'rule_type': 'experiment',
1013+
'variation_key': 'variation',
1014+
'enabled': False
1015+
}
1016+
}
1017+
],
1018+
'events': [
1019+
{
1020+
'timestamp': 42123,
1021+
'entity_id': '111182',
1022+
'uuid': 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c',
1023+
'key': 'campaign_activated',
1024+
}
1025+
],
1026+
}
1027+
],
1028+
}
1029+
],
1030+
'client_name': 'python-sdk',
1031+
'client_version': version.__version__,
1032+
'enrich_decisions': True,
1033+
'anonymize_ip': False,
1034+
'revision': '42',
1035+
}
1036+
1037+
with mock.patch('time.time', return_value=42.123), mock.patch(
1038+
'uuid.uuid4', return_value='a68cf1ad-0393-4e18-af87-efe8f01a7c9c'
1039+
):
1040+
event_obj = UserEventFactory.create_impression_event(
1041+
self.project_config,
1042+
self.project_config.get_experiment_from_key('test_experiment'),
1043+
'111129',
1044+
'',
1045+
'rule_key',
1046+
'experiment',
1047+
False,
1048+
'test_user',
1049+
None,
1050+
None # No cmab_uuid
1051+
)
1052+
1053+
log_event = EventFactory.create_log_event(event_obj, self.logger)
1054+
1055+
# Verify no cmab_uuid in metadata
1056+
metadata = log_event.params['visitors'][0]['snapshots'][0]['decisions'][0]['metadata']
1057+
self.assertNotIn('cmab_uuid', metadata)
1058+
1059+
self._validate_event_object(
1060+
log_event, EventFactory.EVENT_ENDPOINT, expected_params, EventFactory.HTTP_VERB, EventFactory.HTTP_HEADERS,
1061+
)

tests/test_user_context.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,8 @@ def test_decide__feature_test(self):
297297
'feature-test',
298298
expected.enabled,
299299
'test_user',
300-
{'browser': 'chrome'}
300+
{'browser': 'chrome'},
301+
None
301302
)
302303

303304
def test_decide__feature_test__send_flag_decision_false(self):

0 commit comments

Comments
 (0)