Commit c124a330 by Renzo Lucioni

Merge pull request #829 from edx/renzo/ab-testing

Split Testing
parents 82af8d4b ac0e65ab
...@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes, ...@@ -5,6 +5,8 @@ These are notable changes in edx-platform. This is a rolling list of changes,
in roughly chronological order, most recent first. Add your entries at or near in roughly chronological order, most recent first. Add your entries at or near
the top. Include a label indicating the component affected. the top. Include a label indicating the component affected.
LMS: Add split testing functionality for internal use.
LMS: Improved accessibility of parts of forum navigation sidebar. LMS: Improved accessibility of parts of forum navigation sidebar.
LMS: enhanced accessibility labeling and aria support for the discussion forum new post dropdown as well as response and comment area labeling. LMS: enhanced accessibility labeling and aria support for the discussion forum new post dropdown as well as response and comment area labeling.
......
...@@ -728,6 +728,7 @@ def submission_history(request, course_id, student_username, location): ...@@ -728,6 +728,7 @@ def submission_history(request, course_id, student_username, location):
Right now this only works for problems because that's all Right now this only works for problems because that's all
StudentModuleHistory records. StudentModuleHistory records.
""" """
course = get_course_with_access(request.user, course_id, 'load') course = get_course_with_access(request.user, course_id, 'load')
staff_access = has_access(request.user, course, 'staff') staff_access = has_access(request.user, course, 'staff')
......
...@@ -80,7 +80,7 @@ MITX_FEATURES = { ...@@ -80,7 +80,7 @@ MITX_FEATURES = {
'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard) 'ENABLE_PSYCHOMETRICS': False, # real-time psychometrics (eg item response theory analysis in instructor dashboard)
'ENABLE_DJANGO_ADMIN_SITE': False, # set true to enable django's admin site, even on prod (e.g. for course ops) 'ENABLE_DJANGO_ADMIN_SITE': True, # set true to enable django's admin site, even on prod (e.g. for course ops)
'ENABLE_SQL_TRACKING_LOGS': False, 'ENABLE_SQL_TRACKING_LOGS': False,
'ENABLE_LMS_MIGRATION': False, 'ENABLE_LMS_MIGRATION': False,
'ENABLE_MANUAL_GIT_RELOAD': False, 'ENABLE_MANUAL_GIT_RELOAD': False,
...@@ -523,6 +523,14 @@ MOCK_STAFF_GRADING = False ...@@ -523,6 +523,14 @@ MOCK_STAFF_GRADING = False
################################# Jasmine ################################### ################################# Jasmine ###################################
JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee'
################################# Waffle ###################################
# Name prepended to cookies set by Waffle
WAFFLE_COOKIE = "waffle_flag_%s"
# Two weeks (in sec)
WAFFLE_MAX_AGE = 1209600
################################# Middleware ################################### ################################# Middleware ###################################
# List of finder classes that know how to find static files in # List of finder classes that know how to find static files in
# various locations. # various locations.
...@@ -570,6 +578,9 @@ MIDDLEWARE_CLASSES = ( ...@@ -570,6 +578,9 @@ MIDDLEWARE_CLASSES = (
# catches any uncaught RateLimitExceptions and returns a 403 instead of a 500 # catches any uncaught RateLimitExceptions and returns a 403 instead of a 500
'ratelimitbackend.middleware.RateLimitMiddleware', 'ratelimitbackend.middleware.RateLimitMiddleware',
# For A/B testing
'waffle.middleware.WaffleMiddleware',
) )
############################### Pipeline ####################################### ############################### Pipeline #######################################
...@@ -832,6 +843,9 @@ INSTALLED_APPS = ( ...@@ -832,6 +843,9 @@ INSTALLED_APPS = (
# Foldit integration # Foldit integration
'foldit', 'foldit',
# For A/B testing
'waffle',
# For testing # For testing
'django.contrib.admin', # only used in DEBUG mode 'django.contrib.admin', # only used in DEBUG mode
'django_nose', 'django_nose',
......
...@@ -255,7 +255,7 @@ ANALYTICS_API_KEY = "" ...@@ -255,7 +255,7 @@ ANALYTICS_API_KEY = ""
##### segment-io ###### ##### segment-io ######
# If there's an environment variable set, grab it and turn on segment io # If there's an environment variable set, grab it and turn on Segment.io
SEGMENT_IO_LMS_KEY = os.environ.get('SEGMENT_IO_LMS_KEY') SEGMENT_IO_LMS_KEY = os.environ.get('SEGMENT_IO_LMS_KEY')
if SEGMENT_IO_LMS_KEY: if SEGMENT_IO_LMS_KEY:
MITX_FEATURES['SEGMENT_IO_LMS'] = True MITX_FEATURES['SEGMENT_IO_LMS'] = True
......
...@@ -23,6 +23,17 @@ nav.course-material { ...@@ -23,6 +23,17 @@ nav.course-material {
list-style: none; list-style: none;
margin-right: 6px; margin-right: 6px;
&.prominent {
margin-right: 16px;
background: rgba(255, 255, 255, .5);
border-radius: 3px;
}
&.prominent + li {
padding-left: 15px;
border-left: 1px solid #333;
}
a { a {
border-radius: 3px; border-radius: 3px;
color: #555; color: #555;
......
...@@ -13,19 +13,24 @@ def url_class(is_active): ...@@ -13,19 +13,24 @@ def url_class(is_active):
%> %>
<%! from courseware.tabs import get_course_tabs %> <%! from courseware.tabs import get_course_tabs %>
<%! from django.utils.translation import ugettext as _ %> <%! from django.utils.translation import ugettext as _ %>
<% import waffle %>
<nav class="${active_page} course-material"> <nav class="${active_page} course-material">
<div class="inner-wrapper"> <div class="inner-wrapper">
<ol class="course-tabs"> <ol class="course-tabs">
% for tab in get_course_tabs(user, course, active_page): % for tab in get_course_tabs(user, course, active_page, request):
% if waffle.flag_is_active(request, 'visual_treatment') or waffle.flag_is_active(request, 'merge_course_tabs'):
<li class="${"prominent" if tab.name in ("Courseware", "Course Content") else ""}">
% else:
<li> <li>
% endif
<a href="${tab.link | h}" class="${url_class(tab.is_active)}"> <a href="${tab.link | h}" class="${url_class(tab.is_active)}">
${tab.name | h} ${tab.name | h}
% if tab.is_active == True: % if tab.is_active == True:
<span class="sr">, current location</span> <span class="sr">, current location</span>
%endif %endif
% if tab.has_img == True: % if tab.has_img == True:
<img src="${tab.img}"/> <img src="${tab.img}"/>
%endif %endif
</a> </a>
</li> </li>
......
<%! from django.utils.translation import ugettext as _ %> <%!
from django.utils.translation import ugettext as _
import waffle
%>
<h2>${chapter_module.display_name_with_default}</h2> <h2>${chapter_module.display_name_with_default}</h2>
<p>${_("You were most recently in {section_link}. If you\'re done with that, choose another section on the left.").format( <p>${_("You were most recently in {section_link}. If you\'re done with that, choose another section on the left.").format(
...@@ -7,3 +11,31 @@ ...@@ -7,3 +11,31 @@
section_name=prev_section.display_name_with_default, section_name=prev_section.display_name_with_default,
) )
)}</p> )}</p>
% if waffle.flag_is_active(request, 'merge_course_tabs'):
<%! from courseware.courses import get_course_info_section %>
<section class="container">
<div class="info-wrapper">
% if user.is_authenticated():
<section class="updates">
<h1>${_("Course Updates &amp; News")}</h1>
${get_course_info_section(request, course, 'updates')}
</section>
<section aria-label="${_('Handout Navigation')}" class="handouts">
<h1>${course.info_sidebar_name}</h1>
${get_course_info_section(request, course, 'handouts')}
</section>
% else:
<section class="updates">
<h1>${_("Course Updates &amp; News")}</h1>
${get_course_info_section(request, course, 'guest_updates')}
</section>
<section aria-label="${_('Handout Navigation')}" class="handouts">
<h1>${_("Course Handouts")}</h1>
${get_course_info_section(request, course, 'guest_handouts')}
</section>
% endif
</div>
</section>
% endif
...@@ -5,8 +5,11 @@ ...@@ -5,8 +5,11 @@
from courseware.courses import course_image_url, get_course_about_section from courseware.courses import course_image_url, get_course_about_section
from courseware.access import has_access from courseware.access import has_access
from certificates.models import CertificateStatuses from certificates.models import CertificateStatuses
from xmodule.modulestore import MONGO_MODULESTORE_TYPE from xmodule.modulestore import MONGO_MODULESTORE_TYPE
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
import waffle
%> %>
<%inherit file="main.html" /> <%inherit file="main.html" />
...@@ -163,7 +166,10 @@ ...@@ -163,7 +166,10 @@
<li class="course-item"> <li class="course-item">
<article class="course ${enrollment.mode}"> <article class="course ${enrollment.mode}">
<% <%
course_target = reverse('info', args=[course.id]) if waffle.flag_is_active(request, 'merge_course_tabs'):
course_target = reverse('courseware', args=[course.id])
else:
course_target = reverse('info', args=[course.id])
%> %>
% if course.id in show_courseware_links_for: % if course.id in show_courseware_links_for:
......
% if settings.MITX_FEATURES.get('SEGMENT_IO_LMS'): % if settings.MITX_FEATURES.get('SEGMENT_IO_LMS'):
<!-- begin Segment.io --> <!-- begin Segment.io -->
<%! from django.core.urlresolvers import reverse %>
<%! import waffle %>
<% active_flags = " + ".join(waffle.get_flags(request)) %>
<script type="text/javascript"> <script type="text/javascript">
var analytics=analytics||[];analytics.load=function(e){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=("https:"===document.location.protocol?"https://":"http://")+"d2dq2ahtl5zl1z.cloudfront.net/analytics.js/v1/"+e+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);var r=function(e){return function(){analytics.push([e].concat(Array.prototype.slice.call(arguments,0)))}},i=["identify","track","trackLink","trackForm","trackClick","trackSubmit","pageview","ab","alias","ready"];for(var s=0;s<i.length;s++)analytics[i[s]]=r(i[s])}; var analytics=analytics||[];analytics.load=function(e){var t=document.createElement("script");t.type="text/javascript",t.async=!0,t.src=("https:"===document.location.protocol?"https://":"http://")+"d2dq2ahtl5zl1z.cloudfront.net/analytics.js/v1/"+e+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);var r=function(e){return function(){analytics.push([e].concat(Array.prototype.slice.call(arguments,0)))}},i=["identify","track","trackLink","trackForm","trackClick","trackSubmit","pageview","ab","alias","ready"];for(var s=0;s<i.length;s++)analytics[i[s]]=r(i[s])};
analytics.load("${ settings.SEGMENT_IO_LMS_KEY }"); analytics.load("${ settings.SEGMENT_IO_LMS_KEY }");
% if user.is_authenticated(): % if user.is_authenticated():
analytics.identify("${ user.id }", {
email : "${ user.email }", analytics.identify("${ user.id }", {
username : "${ user.username }" email : "${ user.email }",
}); username : "${ user.username }",
"Active Flags" : "${ active_flags }",
});
% endif % endif
</script> </script>
......
...@@ -59,6 +59,7 @@ urlpatterns = ('', # nopep8 ...@@ -59,6 +59,7 @@ urlpatterns = ('', # nopep8
url(r'^user_api/', include('user_api.urls')), url(r'^user_api/', include('user_api.urls')),
url(r'^', include('waffle.urls')),
) )
# if settings.MITX_FEATURES.get("MULTIPLE_ENROLLMENT_ROLES"): # if settings.MITX_FEATURES.get("MULTIPLE_ENROLLMENT_ROLES"):
......
...@@ -18,3 +18,4 @@ ...@@ -18,3 +18,4 @@
-e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail -e git+https://github.com/edx/codejail.git@0a1b468#egg=codejail
-e git+https://github.com/edx/diff-cover.git@v0.2.3#egg=diff_cover -e git+https://github.com/edx/diff-cover.git@v0.2.3#egg=diff_cover
-e git+https://github.com/edx/js-test-tool.git@v0.0.7#egg=js_test_tool -e git+https://github.com/edx/js-test-tool.git@v0.0.7#egg=js_test_tool
-e git+https://github.com/edx/django-waffle.git@823a102e48#egg=django-waffle
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment