Skip to content

Commit 4df4f53

Browse files
Duplicate experiment Key issue with multi feature flag (#347)
* Changing lookup of experiment in decision service to use experiment ID instead of key to resolve bug * [BUGFIX] - Update to bucketer and added proper ID map to project_config. * Switch to generate experiments_key_map from experiment_id_map rather than the other way around. * Updated with unit tests. * Change line break before operator to avoid W504 Flake8 issue. * Flake8 changes to follow best practice and ignore the W504
1 parent c52c3a4 commit 4df4f53

File tree

5 files changed

+81
-12
lines changed

5 files changed

+81
-12
lines changed

.flake8

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
[flake8]
22
# E722 - do not use bare 'except'
3-
ignore = E722
3+
# W504 - Either W503 (Line break after Operand) or W503 (
4+
# Line break before operand needs to be ignored for line lengths
5+
# greater than max-line-length. Best practice shows W504
6+
ignore = E722, W504
47
exclude = optimizely/lib/pymmh3.py,*virtualenv*
58
max-line-length = 120

optimizely/bucketer.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def bucket(self, project_config, experiment, user_id, bucketing_id):
142142
variation_id = self.find_bucket(project_config, bucketing_id,
143143
experiment.id, experiment.trafficAllocation)
144144
if variation_id:
145-
variation = project_config.get_variation_from_id(experiment.key, variation_id)
145+
variation = project_config.get_variation_from_id_by_experiment_id(experiment.id, variation_id)
146146
return variation, decide_reasons
147147

148148
else:

optimizely/decision_service.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ def get_variation_for_rollout(self, project_config, rollout, user_id, attributes
342342
if rollout and len(rollout.experiments) > 0:
343343
for idx in range(len(rollout.experiments) - 1):
344344
logging_key = str(idx + 1)
345-
rollout_rule = project_config.get_experiment_from_key(rollout.experiments[idx].get('key'))
345+
rollout_rule = project_config.get_experiment_from_id(rollout.experiments[idx].get('id'))
346346

347347
# Check if user meets audience conditions for targeting rule
348348
audience_conditions = rollout_rule.get_audience_conditions_or_ids()
@@ -387,7 +387,7 @@ def get_variation_for_rollout(self, project_config, rollout, user_id, attributes
387387
break
388388

389389
# Evaluate last rule i.e. "Everyone Else" rule
390-
everyone_else_rule = project_config.get_experiment_from_key(rollout.experiments[-1].get('key'))
390+
everyone_else_rule = project_config.get_experiment_from_id(rollout.experiments[-1].get('id'))
391391
audience_conditions = everyone_else_rule.get_audience_conditions_or_ids()
392392
audience_eval, audience_reasons = audience_helper.does_user_meet_audience_conditions(
393393
project_config,

optimizely/project_config.py

+49-8
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def __init__(self, datafile, logger, error_handler):
6666

6767
# Utility maps for quick lookup
6868
self.group_id_map = self._generate_key_map(self.groups, 'id', entities.Group)
69-
self.experiment_key_map = self._generate_key_map(self.experiments, 'key', entities.Experiment)
69+
self.experiment_id_map = self._generate_key_map(self.experiments, 'id', entities.Experiment)
7070
self.event_key_map = self._generate_key_map(self.events, 'key', entities.Event)
7171
self.attribute_key_map = self._generate_key_map(self.attributes, 'key', entities.Attribute)
7272

@@ -82,27 +82,36 @@ def __init__(self, datafile, logger, error_handler):
8282
self.rollout_id_map = self._generate_key_map(self.rollouts, 'id', entities.Layer)
8383
for layer in self.rollout_id_map.values():
8484
for experiment in layer.experiments:
85-
self.experiment_key_map[experiment['key']] = entities.Experiment(**experiment)
85+
self.experiment_id_map[experiment['id']] = entities.Experiment(**experiment)
8686

8787
self.audience_id_map = self._deserialize_audience(self.audience_id_map)
8888
for group in self.group_id_map.values():
89-
experiments_in_group_key_map = self._generate_key_map(group.experiments, 'key', entities.Experiment)
90-
for experiment in experiments_in_group_key_map.values():
89+
experiments_in_group_id_map = self._generate_key_map(group.experiments, 'id', entities.Experiment)
90+
for experiment in experiments_in_group_id_map.values():
9191
experiment.__dict__.update({'groupId': group.id, 'groupPolicy': group.policy})
92-
self.experiment_key_map.update(experiments_in_group_key_map)
92+
self.experiment_id_map.update(experiments_in_group_id_map)
9393

94-
self.experiment_id_map = {}
94+
self.experiment_key_map = {}
9595
self.variation_key_map = {}
9696
self.variation_id_map = {}
9797
self.variation_variable_usage_map = {}
98-
for experiment in self.experiment_key_map.values():
99-
self.experiment_id_map[experiment.id] = experiment
98+
self.variation_id_map_by_experiment_id = {}
99+
self.variation_key_map_by_experiment_id = {}
100+
101+
for experiment in self.experiment_id_map.values():
102+
self.experiment_key_map[experiment.key] = experiment
100103
self.variation_key_map[experiment.key] = self._generate_key_map(
101104
experiment.variations, 'key', entities.Variation
102105
)
106+
103107
self.variation_id_map[experiment.key] = {}
108+
self.variation_id_map_by_experiment_id[experiment.id] = {}
109+
self.variation_key_map_by_experiment_id[experiment.id] = {}
110+
104111
for variation in self.variation_key_map.get(experiment.key).values():
105112
self.variation_id_map[experiment.key][variation.id] = variation
113+
self.variation_id_map_by_experiment_id[experiment.id][variation.id] = variation
114+
self.variation_key_map_by_experiment_id[experiment.id][variation.key] = variation
106115
self.variation_variable_usage_map[variation.id] = self._generate_key_map(
107116
variation.variables, 'id', entities.Variation.VariableUsage
108117
)
@@ -537,3 +546,35 @@ def is_feature_experiment(self, experiment_id):
537546
"""
538547

539548
return experiment_id in self.experiment_feature_map
549+
550+
def get_variation_from_id_by_experiment_id(self, experiment_id, variation_id):
551+
""" Gets variation from variation id and specific experiment id
552+
553+
Returns:
554+
The variation for the experiment id and variation id
555+
or empty dict if not found
556+
"""
557+
if (experiment_id in self.variation_id_map_by_experiment_id and
558+
variation_id in self.variation_id_map_by_experiment_id[experiment_id]):
559+
return self.variation_id_map_by_experiment_id[experiment_id][variation_id]
560+
561+
self.logger.error('Variation with id "%s" not defined in the datafile for experiment "%s".',
562+
variation_id, experiment_id)
563+
564+
return {}
565+
566+
def get_variation_from_key_by_experiment_id(self, experiment_id, variation_key):
567+
""" Gets variation from variation key and specific experiment id
568+
569+
Returns:
570+
The variation for the experiment id and variation key
571+
or empty dict if not found
572+
"""
573+
if (experiment_id in self.variation_key_map_by_experiment_id and
574+
variation_key in self.variation_key_map_by_experiment_id[experiment_id]):
575+
return self.variation_key_map_by_experiment_id[experiment_id][variation_key]
576+
577+
self.logger.error('Variation with key "%s" not defined in the datafile for experiment "%s".',
578+
variation_key, experiment_id)
579+
580+
return {}

tests/test_config.py

+25
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def test_init(self):
4141
self.config_dict['groups'][0]['trafficAllocation'],
4242
)
4343
}
44+
4445
expected_experiment_key_map = {
4546
'test_experiment': entities.Experiment(
4647
'111127',
@@ -1213,3 +1214,27 @@ def test_is_feature_experiment(self):
12131214

12141215
self.assertStrictFalse(project_config.is_feature_experiment(experiment.id))
12151216
self.assertStrictTrue(project_config.is_feature_experiment(feature_experiment.id))
1217+
1218+
def test_get_variation_from_id_by_experiment_id(self):
1219+
1220+
opt_obj = optimizely.Optimizely(json.dumps(self.config_dict))
1221+
project_config = opt_obj.config_manager.get_config()
1222+
1223+
experiment_id = '111127'
1224+
variation_id = '111128'
1225+
1226+
variation = project_config.get_variation_from_id_by_experiment_id(experiment_id, variation_id)
1227+
1228+
self.assertIsInstance(variation, entities.Variation)
1229+
1230+
def test_get_variation_from_key_by_experiment_id(self):
1231+
1232+
opt_obj = optimizely.Optimizely(json.dumps(self.config_dict))
1233+
project_config = opt_obj.config_manager.get_config()
1234+
1235+
experiment_id = '111127'
1236+
variation_key = 'control'
1237+
1238+
variation = project_config.get_variation_from_key_by_experiment_id(experiment_id, variation_key)
1239+
1240+
self.assertIsInstance(variation, entities.Variation)

0 commit comments

Comments
 (0)