Skip to content

Commit a85e7ba

Browse files
fix(track): Send decisions for all experiments using an event when using track (#136)
1 parent 210e9c6 commit a85e7ba

File tree

4 files changed

+234
-26
lines changed

4 files changed

+234
-26
lines changed

.travis.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ language: python
22
python:
33
- "2.7"
44
- "3.4"
5-
- "3.5"
5+
- "3.5.5"
66
- "3.6"
77
- "pypy"
88
- "pypy3"

optimizely/event_builder.py

+22-22
Original file line numberDiff line numberDiff line change
@@ -243,40 +243,41 @@ def _get_required_params_for_conversion(self, event_key, event_tags, decisions):
243243
Returns:
244244
Dict consisting of the decisions and events info for conversion event.
245245
"""
246+
snapshot = {}
247+
snapshot[self.EventParams.DECISIONS] = []
246248

247249
for experiment_id, variation_id in decisions:
248-
snapshot = {}
250+
249251
experiment = self.config.get_experiment_from_id(experiment_id)
250252

251253
if variation_id:
252-
snapshot[self.EventParams.DECISIONS] = [{
254+
snapshot[self.EventParams.DECISIONS].append({
253255
self.EventParams.EXPERIMENT_ID: experiment_id,
254256
self.EventParams.VARIATION_ID: variation_id,
255257
self.EventParams.CAMPAIGN_ID: experiment.layerId
256-
}]
258+
})
257259

258-
event_dict = {
259-
self.EventParams.EVENT_ID: self.config.get_event(event_key).id,
260-
self.EventParams.TIME: self._get_time(),
261-
self.EventParams.KEY: event_key,
262-
self.EventParams.UUID: str(uuid.uuid4())
263-
}
264-
265-
if event_tags:
266-
revenue_value = event_tag_utils.get_revenue_value(event_tags)
267-
if revenue_value is not None:
268-
event_dict[event_tag_utils.REVENUE_METRIC_TYPE] = revenue_value
260+
event_dict = {
261+
self.EventParams.EVENT_ID: self.config.get_event(event_key).id,
262+
self.EventParams.TIME: self._get_time(),
263+
self.EventParams.KEY: event_key,
264+
self.EventParams.UUID: str(uuid.uuid4())
265+
}
269266

270-
numeric_value = event_tag_utils.get_numeric_value(event_tags, self.config.logger)
271-
if numeric_value is not None:
272-
event_dict[event_tag_utils.NUMERIC_METRIC_TYPE] = numeric_value
267+
if event_tags:
268+
revenue_value = event_tag_utils.get_revenue_value(event_tags)
269+
if revenue_value is not None:
270+
event_dict[event_tag_utils.REVENUE_METRIC_TYPE] = revenue_value
273271

274-
if len(event_tags) > 0:
275-
event_dict[self.EventParams.TAGS] = event_tags
272+
numeric_value = event_tag_utils.get_numeric_value(event_tags, self.config.logger)
273+
if numeric_value is not None:
274+
event_dict[event_tag_utils.NUMERIC_METRIC_TYPE] = numeric_value
276275

277-
snapshot[self.EventParams.EVENTS] = [event_dict]
276+
if len(event_tags) > 0:
277+
event_dict[self.EventParams.TAGS] = event_tags
278278

279-
return snapshot
279+
snapshot[self.EventParams.EVENTS] = [event_dict]
280+
return snapshot
280281

281282
def create_impression_event(self, experiment, variation_id, user_id, attributes):
282283
""" Create impression Event to be sent to the logging endpoint.
@@ -319,7 +320,6 @@ def create_conversion_event(self, event_key, user_id, attributes, event_tags, de
319320
conversion_params = self._get_required_params_for_conversion(event_key, event_tags, decisions)
320321

321322
params[self.EventParams.USERS][0][self.EventParams.SNAPSHOTS].append(conversion_params)
322-
323323
return Event(self.EVENTS_URL,
324324
params,
325325
http_verb=self.HTTP_VERB,

tests/base.py

+149-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
class BaseTest(unittest.TestCase):
2121

22-
def setUp(self):
22+
def setUp(self, config_dict='config_dict'):
2323
self.config_dict = {
2424
'revision': '42',
2525
'version': '2',
@@ -375,5 +375,152 @@ def setUp(self):
375375
}]
376376
}
377377

378-
self.optimizely = optimizely.Optimizely(json.dumps(self.config_dict))
378+
self.config_dict_with_multiple_experiments = {
379+
'revision': '42',
380+
'version': '2',
381+
'events': [{
382+
'key': 'test_event',
383+
'experimentIds': ['111127', '111130'],
384+
'id': '111095'
385+
}, {
386+
'key': 'Total Revenue',
387+
'experimentIds': ['111127'],
388+
'id': '111096'
389+
}],
390+
'experiments': [{
391+
'key': 'test_experiment',
392+
'status': 'Running',
393+
'forcedVariations': {
394+
'user_1': 'control',
395+
'user_2': 'control'
396+
},
397+
'layerId': '111182',
398+
'audienceIds': ['11154'],
399+
'trafficAllocation': [{
400+
'entityId': '111128',
401+
'endOfRange': 4000
402+
}, {
403+
'entityId': '',
404+
'endOfRange': 5000
405+
}, {
406+
'entityId': '111129',
407+
'endOfRange': 9000
408+
}],
409+
'id': '111127',
410+
'variations': [{
411+
'key': 'control',
412+
'id': '111128'
413+
}, {
414+
'key': 'variation',
415+
'id': '111129'
416+
}]
417+
}, {
418+
'key': 'test_experiment_2',
419+
'status': 'Running',
420+
'forcedVariations': {
421+
'user_1': 'control',
422+
'user_2': 'control'
423+
},
424+
'layerId': '111182',
425+
'audienceIds': ['11154'],
426+
'trafficAllocation': [{
427+
'entityId': '111131',
428+
'endOfRange': 4000
429+
}, {
430+
'entityId': '',
431+
'endOfRange': 5000
432+
}, {
433+
'entityId': '111132',
434+
'endOfRange': 9000
435+
}],
436+
'id': '111130',
437+
'variations': [{
438+
'key': 'control',
439+
'id': '111133'
440+
}, {
441+
'key': 'variation',
442+
'id': '111134'
443+
}]
444+
}],
445+
'groups': [{
446+
'id': '19228',
447+
'policy': 'random',
448+
'experiments': [{
449+
'id': '32222',
450+
'key': 'group_exp_1',
451+
'status': 'Running',
452+
'audienceIds': [],
453+
'layerId': '111183',
454+
'variations': [{
455+
'key': 'group_exp_1_control',
456+
'id': '28901'
457+
}, {
458+
'key': 'group_exp_1_variation',
459+
'id': '28902'
460+
}],
461+
'forcedVariations': {
462+
'user_1': 'group_exp_1_control',
463+
'user_2': 'group_exp_1_control'
464+
},
465+
'trafficAllocation': [{
466+
'entityId': '28901',
467+
'endOfRange': 3000
468+
}, {
469+
'entityId': '28902',
470+
'endOfRange': 9000
471+
}]
472+
}, {
473+
'id': '32223',
474+
'key': 'group_exp_2',
475+
'status': 'Running',
476+
'audienceIds': [],
477+
'layerId': '111184',
478+
'variations': [{
479+
'key': 'group_exp_2_control',
480+
'id': '28905'
481+
}, {
482+
'key': 'group_exp_2_variation',
483+
'id': '28906'
484+
}],
485+
'forcedVariations': {
486+
'user_1': 'group_exp_2_control',
487+
'user_2': 'group_exp_2_control'
488+
},
489+
'trafficAllocation': [{
490+
'entityId': '28905',
491+
'endOfRange': 8000
492+
}, {
493+
'entityId': '28906',
494+
'endOfRange': 10000
495+
}]
496+
}],
497+
'trafficAllocation': [{
498+
'entityId': '32222',
499+
"endOfRange": 3000
500+
}, {
501+
'entityId': '32223',
502+
'endOfRange': 7500
503+
}]
504+
}],
505+
'accountId': '12001',
506+
'attributes': [{
507+
'key': 'test_attribute',
508+
'id': '111094'
509+
}],
510+
'audiences': [{
511+
'name': 'Test attribute users 1',
512+
'conditions': '["and", ["or", ["or", '
513+
'{"name": "test_attribute", "type": "custom_attribute", "value": "test_value_1"}]]]',
514+
'id': '11154'
515+
}, {
516+
'name': 'Test attribute users 2',
517+
'conditions': '["and", ["or", ["or", '
518+
'{"name": "test_attribute", "type": "custom_attribute", "value": "test_value_2"}]]]',
519+
'id': '11159'
520+
}],
521+
'projectId': '111001'
522+
}
523+
524+
config = getattr(self, config_dict)
525+
self.optimizely = optimizely.Optimizely(json.dumps(config))
379526
self.project_config = self.optimizely.config

tests/test_event_builder.py

+62-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def test_init(self):
4242
class EventBuilderTest(base.BaseTest):
4343

4444
def setUp(self):
45-
base.BaseTest.setUp(self)
45+
base.BaseTest.setUp(self, 'config_dict_with_multiple_experiments')
4646
self.event_builder = self.optimizely.event_builder
4747

4848
def _validate_event_object(self, event_obj, expected_url, expected_params, expected_verb, expected_headers):
@@ -655,3 +655,64 @@ def test_create_conversion_event__with_invalid_event_tags(self):
655655
expected_params,
656656
event_builder.EventBuilder.HTTP_VERB,
657657
event_builder.EventBuilder.HTTP_HEADERS)
658+
659+
def test_create_conversion_event__when_event_is_used_in_multiple_experiments(self):
660+
""" Test that create_conversion_event creates Event object with
661+
right params when multiple experiments use the same event. """
662+
663+
expected_params = {
664+
'client_version': version.__version__,
665+
'project_id': '111001',
666+
'visitors': [{
667+
'attributes': [{
668+
'entity_id': '111094',
669+
'type': 'custom',
670+
'value': 'test_value',
671+
'key': 'test_attribute'
672+
}],
673+
'visitor_id': 'test_user',
674+
'snapshots': [{
675+
'decisions': [{
676+
'variation_id': '111129',
677+
'experiment_id': '111127',
678+
'campaign_id': '111182'
679+
}, {
680+
'experiment_id': '111130',
681+
'variation_id': '111131',
682+
'campaign_id': '111182'
683+
}],
684+
'events': [{
685+
'uuid': 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c',
686+
'tags': {
687+
'non-revenue': 'abc',
688+
'revenue': 4200,
689+
'value': 1.234
690+
},
691+
'timestamp': 42123,
692+
'revenue': 4200,
693+
'value': 1.234,
694+
'key': 'test_event',
695+
'entity_id': '111095'
696+
}]
697+
}]
698+
}],
699+
'account_id': '12001',
700+
'client_name': 'python-sdk',
701+
'anonymize_ip': False,
702+
'revision': '42'
703+
}
704+
705+
with mock.patch('time.time', return_value=42.123), \
706+
mock.patch('uuid.uuid4', return_value='a68cf1ad-0393-4e18-af87-efe8f01a7c9c'):
707+
event_obj = self.event_builder.create_conversion_event(
708+
'test_event',
709+
'test_user',
710+
{'test_attribute': 'test_value'},
711+
{'revenue': 4200, 'value': 1.234, 'non-revenue': 'abc'},
712+
[('111127', '111129'), ('111130', '111131')]
713+
)
714+
self._validate_event_object(event_obj,
715+
event_builder.EventBuilder.EVENTS_URL,
716+
expected_params,
717+
event_builder.EventBuilder.HTTP_VERB,
718+
event_builder.EventBuilder.HTTP_HEADERS)

0 commit comments

Comments
 (0)