def participate_in_hippos(self): # Multiple conversions test return ab_test("hippos", conversion_name=["hippos_binary", "hippos_counting"], conversion_type=[ConversionTypes.Binary, ConversionTypes.Counting])
def get_alternative_for_user(user_data, current_user=False): """ Returns the experiment alternative for the specified user, or the current logged in user. If the user is the logged in user, will opt in for an experiment, as well. Will not affect experiments if not the current user. """ # We're interested in analyzing the effects of different struggling # models on users. A more accurate model would imply that the user # can get help earlier on. This varies drastically for those with # and without coaches, so it is useful to separate the population out. if user_data.coaches: exp_name = 'Struggling model (w/coach)' else: exp_name = 'Struggling model (no coach)' # If it's not the current user, then it must be an admin or coach # viewing a dashboard. Don't affect the actual experiment as only the # actions of the user affect her participation in the experiment. if current_user: return ab_test(exp_name, StrugglingExperiment._ab_test_alternatives, StrugglingExperiment._conversion_names, StrugglingExperiment._conversion_types) return find_alternative_for_user(exp_name, user_data)
def participate_in_doppleganger_on_new_instance(self): """Simulate participating in a new experiment on a "new" instance. This test works by loading memcache with a copy of all gae/bingo experiments before the doppleganger test exists. After the doppleganger test has been created once, all future calls to this function simulate being run on machines that haven't yet cleared their instance cache and loaded the newly created doppleganger yet. We do this by replacing the instance cache'd state of BingoCache with the deep copy that we made before doppleganger was created. A correctly functioning test will still only create one copy of the experiment even though multiple clients attempted to create a new experiment. """ # First, make a deep copy of the current state of bingo's experiments bingo_clone = memcache.get("bingo_clone") if not bingo_clone: # Set the clone by copying the current bingo cache state memcache.set("bingo_clone", copy.deepcopy(BingoCache.get())) else: # Set the current bingo cache state to the cloned state gae_bingo.instance_cache.set(BingoCache.CACHE_KEY, bingo_clone) return ab_test("doppleganger")
def get(self): user_data = models.UserData.current() or models.UserData.pre_phantom() user_exercise_graph = models.UserExerciseGraph.get(user_data) sees_new_review = ab_test( 'Review Mode UI', conversion_name=ViewExercise._review_conversion_names, conversion_type=ViewExercise._review_conversion_types) show_review_drawer = (sees_new_review and not user_exercise_graph.has_completed_review()) template_values = { 'graph_dict_data': exercise_graph_dict_json(user_data), 'user_data': user_data, 'expanded_all_exercises': user_data.expanded_all_exercises, 'map_coords': knowledgemap.deserializeMapCoords(user_data.map_coords), 'selected_nav_link': 'practice', 'show_review_drawer': show_review_drawer, } if show_review_drawer: template_values['review_statement'] = ab_test( 'review_statement_of_fact', [ 'Fortify your knowledge', 'Attain mastery', 'Review exercises', 'Reinforce your learning', 'Consolidate what you know', "Master what you've learned", 'How much can you recall?', "Let's review", 'Refresh your memory', ]) template_values['review_call_to_action'] = ab_test( 'review_call_to_action', [ 'Start Reviews', 'Start now', 'Go go go!', "Let's go!", "I'll do it", "Let's do this!", ]) self.render_jinja2_template('viewexercises.html', template_values)
def get(self): user_data = models.UserData.current() or models.UserData.pre_phantom() user_exercise_graph = models.UserExerciseGraph.get(user_data) sees_new_review = ab_test('Review Mode UI', conversion_name=ViewExercise._review_conversion_names, conversion_type=ViewExercise._review_conversion_types) show_review_drawer = (sees_new_review and not user_exercise_graph.has_completed_review()) template_values = { 'graph_dict_data': exercise_graph_dict_json(user_data), 'user_data': user_data, 'expanded_all_exercises': user_data.expanded_all_exercises, 'map_coords': knowledgemap.deserializeMapCoords(user_data.map_coords), 'selected_nav_link': 'practice', 'show_review_drawer': show_review_drawer, } if show_review_drawer: template_values['review_statement'] = ab_test( 'review_statement_of_fact', [ 'Fortify your knowledge', 'Attain mastery', 'Review exercises', 'Reinforce your learning', 'Consolidate what you know', "Master what you've learned", 'How much can you recall?', "Let's review", 'Refresh your memory', ] ) template_values['review_call_to_action'] = ab_test( 'review_call_to_action', [ 'Start Reviews', 'Start now', 'Go go go!', "Let's go!", "I'll do it", "Let's do this!", ] ) self.render_jinja2_template('viewexercises.html', template_values)
def add_conversions(self): return ab_test("hippos", conversion_name=["hippos_binary", "hippos_counting", "rhinos_counting"], conversion_type=[ConversionTypes.Binary, ConversionTypes.Counting, ConversionTypes.Counting])
def get(self): import models code_url = "https://foursquare.com/oauth2/authenticate?client_id=%s&response_type=code&redirect_uri=%s" % (CLIENT_ID,REDIRECT_URI) self.context["fslogin"] = code_url self.context["page"] = "home" self.context["justRegistered"] = self.session.pop("justRegistered", default=None) logging.info("-----------------------") logging.info("getting justRegistered value") logging.info(self.context["justRegistered"]) tasks=[] partnerTasks=[] user = None #---------------------------------------------- # get user if we have one if self.session.has_key("userKey"): user_identifier = self.session["userKey"] user = UserProfile.get_by_key_name(user_identifier) #---------------------------------------------- # redirect to settings to complete registration if we need to if user is not None: if user.status==0: return self.redirect('/settings') else: tasks = user.getActiveTasks() if user.partner: partnerTasks = user.partner.getActiveTasks() # for partnerTask in partnerTasks: # tasks.append(partnerTask) else: self.context["bannerHeader"] = True from gae_bingo.gae_bingo import ab_test self.context["showshare"] = ab_test("showshare", conversion_name="registered") if "inviteCouple" in self.session: thisInvite = EmailInvite.get_by_key_name(self.session["inviteCouple"]) if thisInvite is not None: logging.info("definining firstname here") self.context["inviteCouple"] = thisInvite self.context["inviter"] = thisInvite.inviter self.context["tasks"] = tasks self.context["partnerTasks"] = partnerTasks self.context["user"] = user self.render('index.html')
def get_alternative_for_user(user_data, current_user=False): """ Returns the experiment alternative for the specified user, or the current logged in user. If the user is the logged in user, will opt in for an experiment, as well. Will not affect experiments if not the current user. """ exp_name = "Suggested activity on profile page" # If it's not the current user, then it must be an admin or coach # viewing a dashboard. Don't affect the actual experiment as only the # actions of the user affect her participation in the experiment. if current_user: return ab_test(exp_name, SuggestedActivityExperiment._ab_test_alternatives, SuggestedActivityExperiment._conversion_names, SuggestedActivityExperiment._conversion_types) return find_alternative_for_user(exp_name, user_data)
def participate_in_crocodiles(self): # Weighted test return ab_test("crocodiles", {"a": 100, "b": 200, "c": 400})
def participate_in_skunks(self): # Too many alternatives return ab_test("skunks", ["a", "b", "c", "d", "e"])
def participate_in_chimpanzees(self): # Multiple conversions test return ab_test("chimpanzees", conversion_name=["chimps_conversion_1", "chimps_conversion_2"])
def participate_in_gorillas(self): return ab_test("gorillas", ["a", "b", "c"])
def participate_in_monkeys(self): return ab_test("monkeys")
def ab_test(): """gaebingo.ab_test() wrapper""" return ab_test(InteractiveTranscriptExperiment.NAME, InteractiveTranscriptExperiment._ab_test_alternatives, InteractiveTranscriptExperiment._conversion_names, InteractiveTranscriptExperiment._conversion_types)
def get_render_type(): return ab_test("Homepage Restructuring 2", HomepageRestructuringExperiment._ab_test_alternatives, HomepageRestructuringExperiment._conversion_names, HomepageRestructuringExperiment._conversion_types)
def get(self): version_number = None if user_models.UserData.current() and user_models.UserData.current().developer: version_number = self.request_string('version', default=None) thumbnail_link_sets = new_and_noteworthy_link_sets() # If all else fails, just show the TED talk on the homepage marquee_video = { "youtube_id": "gM95HHI4gLk", "href": "/video?v=%s" % "gM95HHI4gLk", "thumb_urls": video_models.Video.youtube_thumbnail_urls("gM95HHI4gLk"), "title": "Salman Khan talk at TED 2011", "key": "", } if len(thumbnail_link_sets) > 1: day = datetime.datetime.now().day # Switch up the first 4 New & Noteworthy videos on a daily basis current_link_set_offset = day % len(thumbnail_link_sets) # Switch up the marquee video on a daily basis marquee_videos = [] for thumbnail_link_set in thumbnail_link_sets: marquee_videos += filter(lambda item: item["marquee"], thumbnail_link_set) if marquee_videos: marquee_video = marquee_videos[day % len(marquee_videos)] marquee_video["selected"] = True if len(thumbnail_link_sets[current_link_set_offset]) < ITEMS_PER_SET: # If the current offset lands on a set of videos that isn't a full set, just start # again at the first set, which is most likely full. current_link_set_offset = 0 thumbnail_link_sets = thumbnail_link_sets[current_link_set_offset:] + thumbnail_link_sets[:current_link_set_offset] default = topic_models.TopicVersion.get_default_version() if App.is_dev_server and default is None: library_content = "<h1>Content not initialized. <a href=\"/devadmin/content?autoupdate=1\">Click here</a> to autoupdate from khanacademy.org." elif version_number: layer_cache.disable() library_content = library.library_content_html(version_number=int(version_number)) elif not self.is_mobile_capable(): # Only running ajax version of homepage for non-mobile clients library_content = library.library_content_html(ajax = True) else: library_content = library.library_content_html() from gae_bingo.gae_bingo import ab_test, create_redirect_url donate_button_test = ab_test("hp_donate_button", {"button":1, "text":99}, conversion_name=['hp_donate_button_click', 'hp_donate_button_paypal']) donate_redirect_url = create_redirect_url("/donate", "hp_donate_button_click") template_values = { 'marquee_video': marquee_video, 'thumbnail_link_sets': thumbnail_link_sets, 'library_content': library_content, 'DVD_list': DVD_list, 'is_mobile_allowed': True, 'approx_vid_count': video_models.Video.approx_count(), 'link_heat': self.request_bool("heat", default=False), 'version_number': version_number, 'donate_button_test': donate_button_test, 'donate_redirect_url': donate_redirect_url } self.render_jinja2_template('homepage.html', template_values) layer_cache.enable()
def participate_in_baboons(self): # Multiple conversions test return ab_test("baboons")
def add_global_template_values(self, template_values): template_values['App'] = App template_values['None'] = None if not template_values.has_key('user_data'): user_data = user_models.UserData.current() template_values['user_data'] = user_data user_data = template_values['user_data'] template_values['username'] = user_data.nickname if user_data else "" template_values['viewer_profile_root'] = user_data.profile_root if user_data else "/profile/nouser/" template_values['points'] = user_data.points if user_data else 0 template_values['logged_in'] = not user_data.is_phantom if user_data else False template_values['http_host'] = os.environ["HTTP_HOST"] # Always insert a post-login request before our continue url template_values['continue'] = util.create_post_login_url(template_values.get('continue') or self.request.uri) template_values['login_url'] = ('%s&direct=1' % util.create_login_url(template_values['continue'])) template_values['logout_url'] = util.create_logout_url(self.request.uri) template_values['is_mobile'] = False template_values['is_mobile_capable'] = False template_values['is_ipad'] = False if self.is_mobile_capable(): template_values['is_mobile_capable'] = True template_values['is_ipad'] = self.is_ipad() if 'is_mobile_allowed' in template_values and template_values['is_mobile_allowed']: template_values['is_mobile'] = self.is_mobile() # overridable hide_analytics querystring that defaults to true in dev # mode but false for prod. hide_analytics = self.request_bool("hide_analytics", App.is_dev_server) template_values['hide_analytics'] = hide_analytics # client-side error logging template_values['include_errorception'] = gandalf('errorception') # Analytics template_values['mixpanel_enabled'] = gandalf('mixpanel_enabled') if False: # Enable for testing only template_values['mixpanel_test'] = "70acc4fce4511b89477ac005639cfee1" template_values['mixpanel_enabled'] = True template_values['hide_analytics'] = False if template_values['mixpanel_enabled']: template_values['mixpanel_id'] = gae_bingo.identity.identity() if not template_values['hide_analytics']: superprops_list = user_models.UserData.get_analytics_properties(user_data) # Create a superprops dict for MixPanel with a version number # Bump the version number if changes are made to the client-side analytics # code and we want to be able to filter by version. template_values['mixpanel_superprops'] = dict(superprops_list) # Copy over first 4 per-user properties for GA (5th is reserved for Bingo) template_values['ga_custom_vars'] = superprops_list[0:4] if user_data: user_goals = goals.models.GoalList.get_current_goals(user_data) goals_data = [g.get_visible_data() for g in user_goals] if goals_data: template_values['global_goals'] = jsonify(goals_data) # Disable topic browser in the header on mobile devices template_values['watch_topic_browser_enabled'] = not self.is_mobile_capable() # Begin topic pages A/B test if template_values['mixpanel_enabled']: show_topic_pages = ab_test("Show topic pages", ["show", "hide"], ["topic_pages_view_page", "topic_pages_started_video", "topic_pages_completed_video"]) analytics_bingo = {"name": "Bingo: Topic pages", "value": show_topic_pages} template_values['analytics_bingo'] = analytics_bingo else: show_topic_pages = "hide" template_values['show_topic_pages'] = (show_topic_pages == "show") # End topic pages A/B test return template_values
def get(self): user_data = models.UserData.current() or models.UserData.pre_phantom() exid = self.request_string("exid", default="addition_1") exercise = models.Exercise.get_by_name(exid) if not exercise: raise MissingExerciseException("Missing exercise w/ exid '%s'" % exid) user_exercise = user_data.get_or_insert_exercise(exercise) # Cache this so we don't have to worry about future lookups user_exercise.exercise_model = exercise user_exercise._user_data = user_data user_exercise.summative = exercise.summative # Temporarily work around in-app memory caching bug exercise.user_exercise = None problem_number = self.request_int('problem_number', default=(user_exercise.total_done + 1)) user_data_student = self.request_user_data( "student_email") or user_data if user_data_student.key_email != user_data.key_email and not user_data_student.is_visible_to( user_data): user_data_student = user_data viewing_other = user_data_student.key_email != user_data.key_email # Can't view your own problems ahead of schedule if not viewing_other and problem_number > user_exercise.total_done + 1: problem_number = user_exercise.total_done + 1 # When viewing another student's problem or a problem out-of-order, show read-only view read_only = viewing_other or problem_number != ( user_exercise.total_done + 1) exercise_template_html = exercise_template() exercise_body_html, exercise_inline_script, exercise_inline_style, data_require, sha1 = exercise_contents( exercise) user_exercise.exercise_model.sha1 = sha1 user_exercise.exercise_model.related_videos = map( lambda exercise_video: exercise_video.video, user_exercise.exercise_model.related_videos_fetch()) for video in user_exercise.exercise_model.related_videos: video.id = video.key().id() renderable = True if read_only: # Override current problem number and user being inspected # so proper exercise content will be generated user_exercise.total_done = problem_number - 1 user_exercise.user = user_data_student.user user_exercise.read_only = True if not self.request_bool("renderable", True): # We cannot render old problems that were created in the v1 exercise framework. renderable = False query = models.ProblemLog.all() query.filter("user = "******"exercise = ", exid) # adding this ordering to ensure that query is served by an existing index. # could be ok if we remove this query.order('time_done') problem_logs = query.fetch(500) problem_log = None for p in problem_logs: if p.problem_number == problem_number: problem_log = p break user_activity = [] previous_time = 0 if not problem_log or not hasattr(problem_log, "hint_after_attempt_list"): renderable = False else: # Don't include incomplete information problem_log.hint_after_attempt_list = filter( lambda x: x != -1, problem_log.hint_after_attempt_list) while len(problem_log.hint_after_attempt_list ) and problem_log.hint_after_attempt_list[0] == 0: user_activity.append([ "hint-activity", "0", max( 0, problem_log.hint_time_taken_list[0] - previous_time) ]) previous_time = problem_log.hint_time_taken_list[0] problem_log.hint_after_attempt_list.pop(0) problem_log.hint_time_taken_list.pop(0) # For each attempt, add it to the list and then add any hints # that came after it for i in range(0, len(problem_log.attempts)): user_activity.append([ "correct-activity" if problem_log.correct else "incorrect-activity", unicode(problem_log.attempts[i] if problem_log. attempts[i] else 0), max(0, problem_log.time_taken_attempts[i] - previous_time) ]) previous_time = 0 # Here i is 0-indexed but problems are numbered starting at 1 while len( problem_log.hint_after_attempt_list ) and problem_log.hint_after_attempt_list[0] == i + 1: user_activity.append([ "hint-activity", "0", max( 0, problem_log.hint_time_taken_list[0] - previous_time) ]) previous_time = problem_log.hint_time_taken_list[0] # easiest to just pop these instead of maintaining # another index into this list problem_log.hint_after_attempt_list.pop(0) problem_log.hint_time_taken_list.pop(0) user_exercise.user_activity = user_activity if problem_log.count_hints is not None: user_exercise.count_hints = problem_log.count_hints is_webos = self.is_webos() browser_disabled = is_webos or self.is_older_ie() renderable = renderable and not browser_disabled url_pattern = "/exercises?exid=%s&student_email=%s&problem_number=%d" user_exercise.previous_problem_url = url_pattern % \ (exid, user_data_student.key_email , problem_number-1) user_exercise.next_problem_url = url_pattern % \ (exid, user_data_student.key_email , problem_number+1) user_exercise_json = jsonify.jsonify(user_exercise) template_values = { 'exercise': exercise, 'user_exercise_json': user_exercise_json, 'exercise_body_html': exercise_body_html, 'exercise_template_html': exercise_template_html, 'exercise_inline_script': exercise_inline_script, 'exercise_inline_style': exercise_inline_style, 'data_require': data_require, 'read_only': read_only, 'selected_nav_link': 'practice', 'browser_disabled': browser_disabled, 'is_webos': is_webos, 'renderable': renderable, 'issue_labels': ('Component-Code,Exercise-%s,Problem-%s' % (exid, problem_number)), 'alternate_hints_treatment': ab_test('Hints or Show Solution', ViewExercise._hints_ab_test_alternatives, ViewExercise._hints_conversion_names, ViewExercise._hints_conversion_types) } self.render_jinja2_template("exercise_template.html", template_values)
def get(self, exid=None): # TODO(david): Is there some webapp2 magic that will allow me not to # repeat this URL string in main.py? review_mode = self.request.path == "/review" if not exid and not review_mode: self.redirect("/exercise/%s" % self.request_string("exid", default="addition_1")) return user_data = models.UserData.current() or models.UserData.pre_phantom() user_exercise_graph = models.UserExerciseGraph.get(user_data) if review_mode: # Take the first review exercise if available exid = (user_exercise_graph.review_exercise_names() or user_exercise_graph.proficient_exercise_names() or ["addition_1"])[0] reviews_left_count = user_exercise_graph.reviews_left_count() exercise = models.Exercise.get_by_name(exid) if not exercise: raise MissingExerciseException("Missing exercise w/ exid '%s'" % exid) user_exercise = user_data.get_or_insert_exercise(exercise) # Cache these so we don't have to worry about future lookups user_exercise.exercise_model = exercise user_exercise._user_data = user_data user_exercise._user_exercise_graph = user_exercise_graph user_exercise.summative = exercise.summative # Temporarily work around in-app memory caching bug exercise.user_exercise = None problem_number = self.request_int('problem_number', default=(user_exercise.total_done + 1)) user_data_student = self.request_student_user_data(legacy=True) or user_data if user_data_student.key_email != user_data.key_email and not user_data_student.is_visible_to(user_data): user_data_student = user_data viewing_other = user_data_student.key_email != user_data.key_email # Can't view your own problems ahead of schedule if not viewing_other and problem_number > user_exercise.total_done + 1: problem_number = user_exercise.total_done + 1 # When viewing another student's problem or a problem out-of-order, show read-only view read_only = viewing_other or problem_number != (user_exercise.total_done + 1) exercise_template_html = exercise_template() exercise_body_html, exercise_inline_script, exercise_inline_style, data_require, sha1 = exercise_contents(exercise) user_exercise.exercise_model.sha1 = sha1 user_exercise.exercise_model.related_videos = map(lambda exercise_video: exercise_video.video, user_exercise.exercise_model.related_videos_fetch()) for video in user_exercise.exercise_model.related_videos: video.id = video.key().id() renderable = True if read_only: # Override current problem number and user being inspected # so proper exercise content will be generated user_exercise.total_done = problem_number - 1 user_exercise.user = user_data_student.user user_exercise.read_only = True if not self.request_bool("renderable", True): # We cannot render old problems that were created in the v1 exercise framework. renderable = False query = models.ProblemLog.all() query.filter("user = "******"exercise = ", exid) # adding this ordering to ensure that query is served by an existing index. # could be ok if we remove this query.order('time_done') problem_logs = query.fetch(500) problem_log = None for p in problem_logs: if p.problem_number == problem_number: problem_log = p break user_activity = [] previous_time = 0 if not problem_log or not hasattr(problem_log, "hint_after_attempt_list"): renderable = False else: # Don't include incomplete information problem_log.hint_after_attempt_list = filter(lambda x: x != -1, problem_log.hint_after_attempt_list) while len(problem_log.hint_after_attempt_list) and problem_log.hint_after_attempt_list[0] == 0: user_activity.append([ "hint-activity", "0", max(0, problem_log.hint_time_taken_list[0] - previous_time) ]) previous_time = problem_log.hint_time_taken_list[0] problem_log.hint_after_attempt_list.pop(0) problem_log.hint_time_taken_list.pop(0) # For each attempt, add it to the list and then add any hints # that came after it for i in range(0, len(problem_log.attempts)): user_activity.append([ "correct-activity" if problem_log.correct else "incorrect-activity", unicode(problem_log.attempts[i] if problem_log.attempts[i] else 0), max(0, problem_log.time_taken_attempts[i] - previous_time) ]) previous_time = 0 # Here i is 0-indexed but problems are numbered starting at 1 while (len(problem_log.hint_after_attempt_list) and problem_log.hint_after_attempt_list[0] == i + 1): user_activity.append([ "hint-activity", "0", max(0, problem_log.hint_time_taken_list[0] - previous_time) ]) previous_time = problem_log.hint_time_taken_list[0] # easiest to just pop these instead of maintaining # another index into this list problem_log.hint_after_attempt_list.pop(0) problem_log.hint_time_taken_list.pop(0) user_exercise.user_activity = user_activity if problem_log.count_hints is not None: user_exercise.count_hints = problem_log.count_hints user_exercise.current = problem_log.sha1 == sha1 else: # Not read_only suggested_exercise_names = user_exercise_graph.suggested_exercise_names() if exercise.name in suggested_exercise_names: bingo('suggested_activity_visit_suggested_exercise') is_webos = self.is_webos() browser_disabled = is_webos or self.is_older_ie() renderable = renderable and not browser_disabled url_pattern = "/exercise/%s?student_email=%s&problem_number=%d" user_exercise.previous_problem_url = url_pattern % \ (exid, user_data_student.key_email, problem_number - 1) user_exercise.next_problem_url = url_pattern % \ (exid, user_data_student.key_email, problem_number + 1) user_exercise_json = jsonify.jsonify(user_exercise) template_values = { 'exercise': exercise, 'user_exercise_json': user_exercise_json, 'exercise_body_html': exercise_body_html, 'exercise_template_html': exercise_template_html, 'exercise_inline_script': exercise_inline_script, 'exercise_inline_style': exercise_inline_style, 'data_require': data_require, 'read_only': read_only, 'selected_nav_link': 'practice', 'browser_disabled': browser_disabled, 'is_webos': is_webos, 'renderable': renderable, 'issue_labels': ('Component-Code,Exercise-%s,Problem-%s' % (exid, problem_number)), 'alternate_hints_treatment': ab_test('Hints or Show Solution Dec 10', ViewExercise._hints_ab_test_alternatives, ViewExercise._hints_conversion_names, ViewExercise._hints_conversion_types, 'Hints or Show Solution Nov 5'), 'reviews_left_count': reviews_left_count if review_mode else "null", } self.render_jinja2_template("exercise_template.html", template_values)
def get(self): if ab_test("new button design"): self.response.write('Hello world! <a href="/click">Click Me!</a>') else: self.response.write('Hello world! <a href="/click">Click Here!</a>')