Commit a9cdf830 by Daniel Friedman

Add learner analytics UI scaffolding

AN-6199
parent f0459481
...@@ -301,6 +301,16 @@ class CoursePerformanceAnswerDistributionPage(CoursePage): ...@@ -301,6 +301,16 @@ class CoursePerformanceAnswerDistributionPage(CoursePage):
self.browser.title.startswith('Performance: Problem Submissions') self.browser.title.startswith('Performance: Problem Submissions')
class CourseLearnersPage(CoursePage):
def __init__(self, browser, course_id=None):
super(CourseLearnersPage, self).__init__(browser, course_id)
self.page_url += '/learners/'
def is_browser_on_page(self):
return super(CourseLearnersPage, self).is_browser_on_page() \
and self.browser.title.startswith('Learners')
class ErrorPage(DashboardPage): class ErrorPage(DashboardPage):
error_code = None error_code = None
error_title = None error_title = None
......
from bok_choy.web_app_test import WebAppTest
from acceptance_tests.mixins import CoursePageTestsMixin
from acceptance_tests.pages import CourseLearnersPage
class CourseLearnersTests(CoursePageTestsMixin, WebAppTest):
def setUp(self):
super(CourseLearnersTests, self).setUp()
self.page = CourseLearnersPage(self.browser)
def _test_data_update_message(self):
# Don't test the update message for now, since it won't exist
# until the SPA adds it to the page in AN-6205.
pass
def _get_data_update_message(self):
# Don't test the update message for now, since it won't exist
# until the SPA adds it to the page in AN-6205.
return ''
{% extends "courses/base-course.html" %}
{% load rjs %}
{% comment %}
View of individual learners within a course.
{% endcomment %}
{% block javascript %}
{{ block.super }}
<script src="{% static_rjs 'js/learners-main.js' %}"></script>
{% endblock javascript %}
{% block child_content %}
<div class="learners-app-container">
<p>TODO: put the app here!</p>
</div>
{% endblock %}
...@@ -4,7 +4,7 @@ from django.conf import settings ...@@ -4,7 +4,7 @@ from django.conf import settings
from django.conf.urls import url, patterns, include from django.conf.urls import url, patterns, include
from courses import views from courses import views
from courses.views import enrollment, engagement, performance, csv from courses.views import enrollment, engagement, performance, csv, learners
CONTENT_ID_PATTERN = r'(?P<content_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))' CONTENT_ID_PATTERN = r'(?P<content_id>(?:i4x://?[^/]+/[^/]+/[^/]+/[^@]+(?:@[^/]+)?)|(?:[^/]+))'
PROBLEM_PART_ID_PATTERN = CONTENT_ID_PATTERN.replace('content_id', 'problem_part_id') PROBLEM_PART_ID_PATTERN = CONTENT_ID_PATTERN.replace('content_id', 'problem_part_id')
...@@ -101,6 +101,11 @@ CSV_URLS = patterns( ...@@ -101,6 +101,11 @@ CSV_URLS = patterns(
name='performance_answer_distribution'), name='performance_answer_distribution'),
) )
LEARNER_URLS = patterns(
'',
url(r'^$', learners.LearnersView.as_view(), name='learners'),
)
COURSE_URLS = patterns( COURSE_URLS = patterns(
'', '',
# Course homepage. This should be the entry point for other applications linking to the course. # Course homepage. This should be the entry point for other applications linking to the course.
...@@ -109,6 +114,7 @@ COURSE_URLS = patterns( ...@@ -109,6 +114,7 @@ COURSE_URLS = patterns(
url(r'^engagement/', include(ENGAGEMENT_URLS, namespace='engagement')), url(r'^engagement/', include(ENGAGEMENT_URLS, namespace='engagement')),
url(r'^performance/', include(PERFORMANCE_URLS, namespace='performance')), url(r'^performance/', include(PERFORMANCE_URLS, namespace='performance')),
url(r'^csv/', include(CSV_URLS, namespace='csv')), url(r'^csv/', include(CSV_URLS, namespace='csv')),
url(r'^learners/', include(LEARNER_URLS, namespace='learners')),
) )
urlpatterns = patterns( urlpatterns = patterns(
......
...@@ -298,6 +298,12 @@ class CourseNavBarMixin(object): ...@@ -298,6 +298,12 @@ class CourseNavBarMixin(object):
'view': 'courses:performance:graded_content', 'view': 'courses:performance:graded_content',
'icon': 'fa-check-square-o', 'icon': 'fa-check-square-o',
'switch': 'enable_course_api', 'switch': 'enable_course_api',
},
{
'name': 'learners',
'label': _('Learners'),
'view': 'courses:learners:learners',
'icon': 'fa-users',
} }
] ]
...@@ -516,6 +522,32 @@ class CourseHome(CourseTemplateWithNavView): ...@@ -516,6 +522,32 @@ class CourseHome(CourseTemplateWithNavView):
] ]
}) })
items.append({
'name': _('Learners'),
'icon': 'fa-users',
'heading': _('What are individual learners doing?'),
'items': [
{
'title': _('Who has been active recently?'),
'view': 'courses:learners:learners', # TODO: map this to the actual action in AN-6205
# TODO: what would the breadcrumbs be?
'breadcrumbs': [_('TODO: what is this?')]
},
{
'title': _('Who is most engaged in the discussions?'),
'view': 'courses:learners:learners', # TODO: map this to the actual action in AN-6205
# TODO: what would the breadcrumbs be?
'breadcrumbs': [_('TODO: what is this?')]
},
{
'title': _("Who hasn't watched videos recently?"),
'view': 'courses:learners:learners', # TODO: map this to the actual action in AN-6205
# TODO: what would the breadcrumbs be?
'breadcrumbs': [_('TODO: what is this?')]
}
]
})
return items return items
# pylint: disable=redefined-variable-type # pylint: disable=redefined-variable-type
......
from django.utils.translation import ugettext_lazy as _
from courses.views import CourseTemplateWithNavView
class LearnersView(CourseTemplateWithNavView):
template_name = 'courses/learners.html'
active_primary_nav_item = 'learners'
page_title = _('Learners')
def get_context_data(self, **kwargs):
context = super(LearnersView, self).get_context_data(**kwargs)
context['page_data'] = self.get_page_data(context)
return context
...@@ -30,7 +30,8 @@ require.config({ ...@@ -30,7 +30,8 @@ require.config({
'cldr-data': 'bower_components/cldr-data', 'cldr-data': 'bower_components/cldr-data',
globalize: 'bower_components/globalize/dist/globalize', globalize: 'bower_components/globalize/dist/globalize',
globalization: 'js/utils/globalization', globalization: 'js/utils/globalization',
collapsible: 'bower_components/edx-ui-toolkit/components/views/collapsible-view' collapsible: 'bower_components/edx-ui-toolkit/components/views/collapsible-view',
marionette: 'bower_components/marionette/lib/backbone.marionette.min'
}, },
wrapShim: true, wrapShim: true,
shim: { shim: {
......
require([], function () {
'use strict';
});
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
"font-awesome": "~4.2.0", "font-awesome": "~4.2.0",
"natural-sort": "overset/javascript-natural-sort#dbf4ca259b327a488bd1d7897fd46d80c414a7e0", "natural-sort": "overset/javascript-natural-sort#dbf4ca259b327a488bd1d7897fd46d80c414a7e0",
"cldr-data": "26.0.3", "cldr-data": "26.0.3",
"edx-ui-toolkit": "edx/edx-ui-toolkit#1e025d169f28632cf903274f3ef8aaf6e2fd6825" "edx-ui-toolkit": "edx/edx-ui-toolkit#1e025d169f28632cf903274f3ef8aaf6e2fd6825",
"marionette": "~2.4.4"
} }
} }
...@@ -67,6 +67,10 @@ ...@@ -67,6 +67,10 @@
{ {
name: 'js/performance-answer-distribution-main', name: 'js/performance-answer-distribution-main',
exclude: ['js/common'] exclude: ['js/common']
},
{
name: 'js/learners-main',
exclude: ['js/common']
} }
] ]
}) })
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