@@ -32,6 +32,12 @@ def __init__(self, logger, user_profile_service):
3232 self .logger = logger
3333 self .user_profile_service = user_profile_service
3434
35+ # Map of user IDs to another map of experiments to variations.
36+ # This contains all the forced variations set by the user
37+ # by calling set_forced_variation (it is not the same as the
38+ # whitelisting forcedVariations data structure).
39+ self .forced_variation_map = {}
40+
3541 def _get_bucketing_id (self , user_id , attributes ):
3642 """ Helper method to determine bucketing ID for the user.
3743
@@ -54,8 +60,114 @@ def _get_bucketing_id(self, user_id, attributes):
5460
5561 return user_id
5662
57- def get_forced_variation (self , project_config , experiment , user_id ):
58- """ Determine if a user is forced into a variation for the given experiment and return that variation.
63+ def set_forced_variation (self , project_config , experiment_key , user_id , variation_key ):
64+ """ Sets users to a map of experiments to forced variations.
65+
66+ Args:
67+ project_config: Instance of ProjectConfig.
68+ experiment_key: Key for experiment.
69+ user_id: The user ID.
70+ variation_key: Key for variation. If None, then clear the existing experiment-to-variation mapping.
71+
72+ Returns:
73+ A boolean value that indicates if the set completed successfully.
74+ """
75+ experiment = project_config .get_experiment_from_key (experiment_key )
76+ if not experiment :
77+ # The invalid experiment key will be logged inside this call.
78+ return False
79+
80+ experiment_id = experiment .id
81+ if variation_key is None :
82+ if user_id in self .forced_variation_map :
83+ experiment_to_variation_map = self .forced_variation_map .get (user_id )
84+ if experiment_id in experiment_to_variation_map :
85+ del (self .forced_variation_map [user_id ][experiment_id ])
86+ self .logger .debug ('Variation mapped to experiment "%s" has been removed for user "%s".' % (
87+ experiment_key ,
88+ user_id
89+ ))
90+ else :
91+ self .logger .debug ('Nothing to remove. Variation mapped to experiment "%s" for user "%s" does not exist.' % (
92+ experiment_key ,
93+ user_id
94+ ))
95+ else :
96+ self .logger .debug ('Nothing to remove. User "%s" does not exist in the forced variation map.' % user_id )
97+ return True
98+
99+ if not validator .is_non_empty_string (variation_key ):
100+ self .logger .debug ('Variation key is invalid.' )
101+ return False
102+
103+ forced_variation = project_config .get_variation_from_key (experiment_key , variation_key )
104+ if not forced_variation :
105+ # The invalid variation key will be logged inside this call.
106+ return False
107+
108+ variation_id = forced_variation .id
109+
110+ if user_id not in self .forced_variation_map :
111+ self .forced_variation_map [user_id ] = {experiment_id : variation_id }
112+ else :
113+ self .forced_variation_map [user_id ][experiment_id ] = variation_id
114+
115+ self .logger .debug ('Set variation "%s" for experiment "%s" and user "%s" in the forced variation map.' % (
116+ variation_id ,
117+ experiment_id ,
118+ user_id
119+ ))
120+ return True
121+
122+ def get_forced_variation (self , project_config , experiment_key , user_id ):
123+ """ Gets the forced variation key for the given user and experiment.
124+
125+ Args:
126+ project_config: Instance of ProjectConfig.
127+ experiment_key: Key for experiment.
128+ user_id: The user ID.
129+
130+ Returns:
131+ The variation which the given user and experiment should be forced into.
132+ """
133+
134+ if user_id not in self .forced_variation_map :
135+ self .logger .debug ('User "%s" is not in the forced variation map.' % user_id )
136+ return None
137+
138+ experiment = project_config .get_experiment_from_key (experiment_key )
139+ if not experiment :
140+ # The invalid experiment key will be logged inside this call.
141+ return None
142+
143+ experiment_to_variation_map = self .forced_variation_map .get (user_id )
144+
145+ if not experiment_to_variation_map :
146+ self .logger .debug ('No experiment "%s" mapped to user "%s" in the forced variation map.' % (
147+ experiment_key ,
148+ user_id
149+ ))
150+ return None
151+
152+ variation_id = experiment_to_variation_map .get (experiment .id )
153+ if variation_id is None :
154+ self .logger .debug (
155+ 'No variation mapped to experiment "%s" in the forced variation map.' % experiment_key
156+ )
157+ return None
158+
159+ variation = project_config .get_variation_from_id (experiment_key , variation_id )
160+
161+ self .logger .debug ('Variation "%s" is mapped to experiment "%s" and user "%s" in the forced variation map' % (
162+ variation .key ,
163+ experiment_key ,
164+ user_id
165+ ))
166+ return variation
167+
168+ def get_whitelisted_variation (self , project_config , experiment , user_id ):
169+ """ Determine if a user is forced into a variation (through whitelisting)
170+ for the given experiment and return that variation.
59171
60172 Args:
61173 project_config: Instance of ProjectConfig.
@@ -129,12 +241,12 @@ def get_variation(self, project_config, experiment, user_id, attributes, ignore_
129241 return None
130242
131243 # Check if the user is forced into a variation
132- variation = project_config .get_forced_variation (experiment .key , user_id )
244+ variation = self .get_forced_variation (project_config , experiment .key , user_id )
133245 if variation :
134246 return variation
135247
136248 # Check to see if user is white-listed for a certain variation
137- variation = self .get_forced_variation (project_config , experiment , user_id )
249+ variation = self .get_whitelisted_variation (project_config , experiment , user_id )
138250 if variation :
139251 return variation
140252
0 commit comments