diff --git a/cms/envs/bok_choy.env.json b/cms/envs/bok_choy.env.json
index 3d73b109cb..d4a648394e 100644
--- a/cms/envs/bok_choy.env.json
+++ b/cms/envs/bok_choy.env.json
@@ -70,7 +70,6 @@
"CUSTOM_COURSE_URLS": true
},
"ENABLE_DISCUSSION_SERVICE": true,
- "ENABLE_INSTRUCTOR_ANALYTICS": true,
"ENABLE_S3_GRADE_DOWNLOADS": true,
"ENTRANCE_EXAMS": true,
"MILESTONES_APP": true,
diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py
index 757eb83aa2..e5d7ec3aec 100644
--- a/lms/djangoapps/instructor/tests/test_api.py
+++ b/lms/djangoapps/instructor/tests/test_api.py
@@ -61,8 +61,7 @@ import instructor_task.api
import instructor.views.api
from instructor.views.api import require_finance_admin
from instructor.tests.utils import FakeContentTask, FakeEmail, FakeEmailInfo
-from instructor.views.api import generate_unique_password
-from instructor.views.api import _split_input_list, common_exceptions_400
+from instructor.views.api import _split_input_list, common_exceptions_400, generate_unique_password
from instructor_task.api_helper import AlreadyRunningError
from openedx.core.djangoapps.course_groups.cohorts import set_course_cohort_settings
@@ -208,14 +207,12 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
{'identifiers': 'foo@example.org', 'action': 'enroll'}),
('get_grading_config', {}),
('get_students_features', {}),
- ('get_distribution', {}),
('get_student_progress_url', {'unique_student_identifier': self.user.username}),
('reset_student_attempts',
{'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.user.email}),
('update_forum_role_membership',
{'unique_student_identifier': self.user.email, 'rolename': 'Moderator', 'action': 'allow'}),
('list_forum_members', {'rolename': FORUM_ROLE_COMMUNITY_TA}),
- ('proxy_legacy_analytics', {'aname': 'ProblemGradeDistribution'}),
('send_email', {'send_to': 'staff', 'subject': 'test', 'message': 'asdf'}),
('list_instructor_tasks', {}),
('list_background_email_tasks', {}),
@@ -291,7 +288,7 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
for endpoint, args in self.staff_level_endpoints:
# TODO: make these work
- if endpoint in ['update_forum_role_membership', 'proxy_legacy_analytics', 'list_forum_members']:
+ if endpoint in ['update_forum_role_membership', 'list_forum_members']:
continue
self._access_endpoint(
endpoint,
@@ -320,7 +317,7 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
for endpoint, args in self.staff_level_endpoints:
# TODO: make these work
- if endpoint in ['update_forum_role_membership', 'proxy_legacy_analytics']:
+ if endpoint in ['update_forum_role_membership']:
continue
self._access_endpoint(
endpoint,
@@ -1978,7 +1975,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
percentage_discount=10, created_by=self.instructor, is_active=True)
self.coupon.save()
- #create testing invoice 1
+ # Create testing invoice 1
self.sale_invoice_1 = Invoice.objects.create(
total_amount=1234.32, company_name='Test1', company_contact_name='TestName', company_contact_email='Test@company.com',
recipient_name='Testw', recipient_email='test1@test.com', customer_reference_number='2Fwe23S',
@@ -2205,7 +2202,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
)
course_registration_code.save()
- #create test invoice 2
+ # Create test invoice 2
sale_invoice_2 = Invoice.objects.create(
total_amount=1234.32, company_name='Test1', company_contact_name='TestName', company_contact_email='Test@company.com',
recipient_name='Testw_2', recipient_email='test2@test.com', customer_reference_number='2Fwe23S',
@@ -2602,46 +2599,6 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
" report when it is complete.".format(report_type=report_type)
self.assertIn(already_running_status, response.content)
- def test_get_distribution_no_feature(self):
- """
- Test that get_distribution lists available features
- when supplied no feature parameter.
- """
- url = reverse('get_distribution', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
- res_json = json.loads(response.content)
- self.assertEqual(type(res_json['available_features']), list)
-
- url = reverse('get_distribution', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url + u'?feature=')
- self.assertEqual(response.status_code, 200)
- res_json = json.loads(response.content)
- self.assertEqual(type(res_json['available_features']), list)
-
- def test_get_distribution_unavailable_feature(self):
- """
- Test that get_distribution fails gracefully with
- an unavailable feature.
- """
- url = reverse('get_distribution', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url, {'feature': 'robot-not-a-real-feature'})
- self.assertEqual(response.status_code, 400)
-
- def test_get_distribution_gender(self):
- """
- Test that get_distribution fails gracefully with
- an unavailable feature.
- """
- url = reverse('get_distribution', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url, {'feature': 'gender'})
- self.assertEqual(response.status_code, 200)
- res_json = json.loads(response.content)
- self.assertEqual(res_json['feature_results']['data']['m'], 6)
- self.assertEqual(res_json['feature_results']['choices_display_names']['m'], 'Male')
- self.assertEqual(res_json['feature_results']['data']['no_data'], 0)
- self.assertEqual(res_json['feature_results']['choices_display_names']['no_data'], 'No Data')
-
def test_get_student_progress_url(self):
""" Test that progress_url is in the successful response. """
url = reverse('get_student_progress_url', kwargs={'course_id': self.course.id.to_deprecated_string()})
@@ -3459,155 +3416,6 @@ class TestInstructorEmailContentList(ModuleStoreTestCase, LoginEnrollmentTestCas
self.assertDictEqual(expected_info, returned_info)
-@attr('shard_1')
-@ddt.ddt
-@override_settings(ANALYTICS_SERVER_URL="http://robotanalyticsserver.netbot:900/")
-@override_settings(ANALYTICS_API_KEY="robot_api_key")
-class TestInstructorAPIAnalyticsProxy(ModuleStoreTestCase, LoginEnrollmentTestCase):
- """
- Test instructor analytics proxy endpoint.
- """
-
- class FakeProxyResponse(object):
- """ Fake successful requests response object. """
-
- def __init__(self):
- self.status_code = requests.status_codes.codes.OK
- self.content = '{"test_content": "robot test content"}'
-
- class FakeBadProxyResponse(object):
- """ Fake strange-failed requests response object. """
-
- def __init__(self):
- self.status_code = 'notok.'
- self.content = '{"test_content": "robot test content"}'
-
- def setUp(self):
- super(TestInstructorAPIAnalyticsProxy, self).setUp()
-
- self.course = CourseFactory.create()
- self.instructor = InstructorFactory(course_key=self.course.id)
- self.client.login(username=self.instructor.username, password='test')
-
- @ddt.data((ModuleStoreEnum.Type.mongo, False), (ModuleStoreEnum.Type.split, True))
- @ddt.unpack
- @patch.object(instructor.views.api.requests, 'get')
- def test_analytics_proxy_url(self, store_type, assert_wo_encoding, act):
- """ Test legacy analytics proxy url generation. """
- with modulestore().default_store(store_type):
- course = CourseFactory.create()
- instructor_local = InstructorFactory(course_key=course.id)
- self.client.login(username=instructor_local.username, password='test')
-
- act.return_value = self.FakeProxyResponse()
-
- url = reverse('proxy_legacy_analytics', kwargs={'course_id': course.id.to_deprecated_string()})
- response = self.client.get(url, {
- 'aname': 'ProblemGradeDistribution'
- })
- self.assertEqual(response.status_code, 200)
-
- # Make request URL pattern - everything but course id.
- url_pattern = "{url}get?aname={aname}&course_id={course_id}&apikey={api_key}".format(
- url="http://robotanalyticsserver.netbot:900/",
- aname="ProblemGradeDistribution",
- course_id="{course_id!s}",
- api_key="robot_api_key",
- )
-
- if assert_wo_encoding:
- # Format url with no URL-encoding of parameters.
- assert_url = url_pattern.format(course_id=course.id.to_deprecated_string())
- with self.assertRaises(AssertionError):
- act.assert_called_once_with(assert_url)
-
- # Format url *with* URL-encoding of parameters.
- expected_url = url_pattern.format(course_id=quote(course.id.to_deprecated_string()))
- act.assert_called_once_with(expected_url)
-
- @override_settings(ANALYTICS_SERVER_URL="")
- @patch.object(instructor.views.api.requests, 'get')
- def test_analytics_proxy_server_url(self, act):
- """
- Test legacy analytics when empty server url.
- """
- act.return_value = self.FakeProxyResponse()
-
- url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url, {
- 'aname': 'ProblemGradeDistribution'
- })
- self.assertEqual(response.status_code, 501)
-
- @override_settings(ANALYTICS_API_KEY="")
- @patch.object(instructor.views.api.requests, 'get')
- def test_analytics_proxy_api_key(self, act):
- """
- Test legacy analytics when empty server API key.
- """
- act.return_value = self.FakeProxyResponse()
-
- url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url, {
- 'aname': 'ProblemGradeDistribution'
- })
- self.assertEqual(response.status_code, 501)
-
- @override_settings(ANALYTICS_SERVER_URL="")
- @override_settings(ANALYTICS_API_KEY="")
- @patch.object(instructor.views.api.requests, 'get')
- def test_analytics_proxy_empty_url_and_api_key(self, act):
- """
- Test legacy analytics when empty server url & API key.
- """
- act.return_value = self.FakeProxyResponse()
-
- url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url, {
- 'aname': 'ProblemGradeDistribution'
- })
- self.assertEqual(response.status_code, 501)
-
- @patch.object(instructor.views.api.requests, 'get')
- def test_analytics_proxy(self, act):
- """
- Test legacy analytics content proxyin, actg.
- """
- act.return_value = self.FakeProxyResponse()
-
- url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url, {
- 'aname': 'ProblemGradeDistribution'
- })
- self.assertEqual(response.status_code, 200)
-
- # check response
- self.assertTrue(act.called)
- expected_res = {'test_content': "robot test content"}
- self.assertEqual(json.loads(response.content), expected_res)
-
- @patch.object(instructor.views.api.requests, 'get')
- def test_analytics_proxy_reqfailed(self, act):
- """ Test proxy when server reponds with failure. """
- act.return_value = self.FakeBadProxyResponse()
-
- url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url, {
- 'aname': 'ProblemGradeDistribution'
- })
- self.assertEqual(response.status_code, 500)
-
- @patch.object(instructor.views.api.requests, 'get')
- def test_analytics_proxy_missing_param(self, act):
- """ Test proxy when missing the aname query parameter. """
- act.return_value = self.FakeProxyResponse()
-
- url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id.to_deprecated_string()})
- response = self.client.get(url, {})
- self.assertEqual(response.status_code, 400)
- self.assertFalse(act.called)
-
-
@attr('shard_1')
class TestInstructorAPIHelpers(TestCase):
""" Test helpers for instructor.api """
diff --git a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py
index 2fad82fdd6..3694308b45 100644
--- a/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py
+++ b/lms/djangoapps/instructor/tests/views/test_instructor_dashboard.py
@@ -54,11 +54,11 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
return 'Enrollment data is now available in Example .'.format(unicode(self.course.id))
- def get_dashboard_demographic_message(self):
+ def get_dashboard_analytics_message(self):
"""
Returns expected dashboard demographic message with link to Insights.
"""
- return 'Demographic data is now available in Example .'.format(unicode(self.course.id))
def test_instructor_tab(self):
@@ -157,38 +157,28 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase):
expected_message = self.get_dashboard_enrollment_message()
self.assertTrue(expected_message in response.content)
- @patch.dict(settings.FEATURES, {'DISPLAY_ANALYTICS_DEMOGRAPHICS': True})
@override_settings(ANALYTICS_DASHBOARD_URL='')
@override_settings(ANALYTICS_DASHBOARD_NAME='')
- def test_show_dashboard_demographic_data(self):
+ def test_dashboard_analytics_tab_not_shown(self):
"""
- Test enrollment demographic data is shown.
+ Test dashboard analytics tab isn't shown if insights isn't configured.
"""
response = self.client.get(self.url)
- # demographic information displayed
- self.assertTrue('data-feature="year_of_birth"' in response.content)
- self.assertTrue('data-feature="gender"' in response.content)
- self.assertTrue('data-feature="level_of_education"' in response.content)
+ analytics_section = '
Analytics '
+ self.assertFalse(analytics_section in response.content)
- # dashboard link hidden
- self.assertFalse(self.get_dashboard_demographic_message() in response.content)
-
- @patch.dict(settings.FEATURES, {'DISPLAY_ANALYTICS_DEMOGRAPHICS': False})
@override_settings(ANALYTICS_DASHBOARD_URL='http://example.com')
@override_settings(ANALYTICS_DASHBOARD_NAME='Example')
- def test_show_dashboard_demographic_message(self):
+ def test_dashboard_analytics_points_at_insights(self):
"""
- Test enrollment demographic dashboard message is shown and data is hidden.
+ Test analytics dashboard message is shown
"""
response = self.client.get(self.url)
-
- # demographics are hidden
- self.assertFalse('data-feature="year_of_birth"' in response.content)
- self.assertFalse('data-feature="gender"' in response.content)
- self.assertFalse('data-feature="level_of_education"' in response.content)
+ analytics_section = 'Analytics '
+ self.assertTrue(analytics_section in response.content)
# link to dashboard shown
- expected_message = self.get_dashboard_demographic_message()
+ expected_message = self.get_dashboard_analytics_message()
self.assertTrue(expected_message in response.content)
def add_course_to_user_cart(self, cart, course_key):
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index 432f399655..6027525c94 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -1645,56 +1645,6 @@ def get_anon_ids(request, course_id): # pylint: disable=unused-argument
return csv_response(course_id.to_deprecated_string().replace('/', '-') + '-anon-ids.csv', header, rows)
-@ensure_csrf_cookie
-@cache_control(no_cache=True, no_store=True, must_revalidate=True)
-@require_level('staff')
-def get_distribution(request, course_id):
- """
- Respond with json of the distribution of students over selected features which have choices.
-
- Ask for a feature through the `feature` query parameter.
- If no `feature` is supplied, will return response with an
- empty response['feature_results'] object.
- A list of available will be available in the response['available_features']
- """
- course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
- feature = request.GET.get('feature')
- # alternate notations of None
- if feature in (None, 'null', ''):
- feature = None
- else:
- feature = str(feature)
-
- available_features = instructor_analytics.distributions.AVAILABLE_PROFILE_FEATURES
- # allow None so that requests for no feature can list available features
- if feature not in available_features + (None,):
- return HttpResponseBadRequest(strip_tags(
- "feature '{}' not available.".format(feature)
- ))
-
- response_payload = {
- 'course_id': course_id.to_deprecated_string(),
- 'queried_feature': feature,
- 'available_features': available_features,
- 'feature_display_names': instructor_analytics.distributions.DISPLAY_NAMES,
- }
-
- p_dist = None
- if feature is not None:
- p_dist = instructor_analytics.distributions.profile_distribution(course_id, feature)
- response_payload['feature_results'] = {
- 'feature': p_dist.feature,
- 'feature_display_name': p_dist.feature_display_name,
- 'data': p_dist.data,
- 'type': p_dist.type,
- }
-
- if p_dist.type == 'EASY_CHOICE':
- response_payload['feature_results']['choices_display_names'] = p_dist.choices_display_names
-
- return JsonResponse(response_payload)
-
-
@ensure_csrf_cookie
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
@common_exceptions_400
@@ -2361,62 +2311,6 @@ def update_forum_role_membership(request, course_id):
return JsonResponse(response_payload)
-@ensure_csrf_cookie
-@cache_control(no_cache=True, no_store=True, must_revalidate=True)
-@require_level('staff')
-@require_query_params(
- aname="name of analytic to query",
-)
-@common_exceptions_400
-def proxy_legacy_analytics(request, course_id):
- """
- Proxies to the analytics cron job server.
-
- `aname` is a query parameter specifying which analytic to query.
- """
- course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id)
- analytics_name = request.GET.get('aname')
-
- # abort if misconfigured
- if not (hasattr(settings, 'ANALYTICS_SERVER_URL') and
- hasattr(settings, 'ANALYTICS_API_KEY') and
- settings.ANALYTICS_SERVER_URL and settings.ANALYTICS_API_KEY):
- return HttpResponse("Analytics service not configured.", status=501)
-
- url = "{}get?aname={}&course_id={}&apikey={}".format(
- settings.ANALYTICS_SERVER_URL,
- analytics_name,
- urllib.quote(unicode(course_id)),
- settings.ANALYTICS_API_KEY,
- )
-
- try:
- res = requests.get(url)
- except Exception: # pylint: disable=broad-except
- log.exception(u"Error requesting from analytics server at %s", url)
- return HttpResponse("Error requesting from analytics server.", status=500)
-
- if res.status_code is 200:
- payload = json.loads(res.content)
- add_block_ids(payload)
- content = json.dumps(payload)
- # return the successful request content
- return HttpResponse(content, content_type="application/json")
- elif res.status_code is 404:
- # forward the 404 and content
- return HttpResponse(res.content, content_type="application/json", status=404)
- else:
- # 500 on all other unexpected status codes.
- log.error(
- u"Error fetching %s, code: %s, msg: %s",
- url, res.status_code, res.content
- )
- return HttpResponse(
- "Error from analytics server ({}).".format(res.status_code),
- status=500
- )
-
-
@require_POST
def get_user_invoice_preference(request, course_id): # pylint: disable=unused-argument
"""
diff --git a/lms/djangoapps/instructor/views/api_urls.py b/lms/djangoapps/instructor/views/api_urls.py
index 10ac79a528..f55d6df9f6 100644
--- a/lms/djangoapps/instructor/views/api_urls.py
+++ b/lms/djangoapps/instructor/views/api_urls.py
@@ -33,8 +33,6 @@ urlpatterns = patterns(
'instructor.views.api.sale_validation', name="sale_validation"),
url(r'^get_anon_ids$',
'instructor.views.api.get_anon_ids', name="get_anon_ids"),
- url(r'^get_distribution$',
- 'instructor.views.api.get_distribution', name="get_distribution"),
url(r'^get_student_progress_url$',
'instructor.views.api.get_student_progress_url', name="get_student_progress_url"),
url(r'^reset_student_attempts$',
@@ -71,8 +69,6 @@ urlpatterns = patterns(
'instructor.views.api.list_forum_members', name="list_forum_members"),
url(r'^update_forum_role_membership$',
'instructor.views.api.update_forum_role_membership', name="update_forum_role_membership"),
- url(r'^proxy_legacy_analytics$',
- 'instructor.views.api.proxy_legacy_analytics', name="proxy_legacy_analytics"),
url(r'^send_email$',
'instructor.views.api.send_email', name="send_email"),
url(r'^change_due_date$', 'instructor.views.api.change_due_date',
diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py
index d783337b9a..4339a7a7a8 100644
--- a/lms/djangoapps/instructor/views/instructor_dashboard.py
+++ b/lms/djangoapps/instructor/views/instructor_dashboard.py
@@ -97,10 +97,24 @@ def instructor_dashboard_2(request, course_id):
_section_cohort_management(course, access),
_section_student_admin(course, access),
_section_data_download(course, access),
- _section_analytics(course, access),
]
- #check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
+ analytics_dashboard_message = None
+ if settings.ANALYTICS_DASHBOARD_URL:
+ # Construct a URL to the external analytics dashboard
+ analytics_dashboard_url = '{0}/courses/{1}'.format(settings.ANALYTICS_DASHBOARD_URL, unicode(course_key))
+ link_start = "".format(analytics_dashboard_url)
+ analytics_dashboard_message = _(
+ "To gain insights into student enrollment and participation {link_start}"
+ "visit {analytics_dashboard_name}, our new course analytics product{link_end}."
+ )
+ analytics_dashboard_message = analytics_dashboard_message.format(
+ link_start=link_start, link_end=" ", analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME)
+
+ # Temporarily show the "Analytics" section until we have a better way of linking to Insights
+ sections.append(_section_analytics(course, access))
+
+ # Check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
course_mode_has_price = False
paid_modes = CourseMode.paid_modes_for_course(course_key)
if len(paid_modes) == 1:
@@ -136,15 +150,6 @@ def instructor_dashboard_2(request, course_id):
disable_buttons = not _is_small_course(course_key)
- analytics_dashboard_message = None
- if settings.ANALYTICS_DASHBOARD_URL:
- # Construct a URL to the external analytics dashboard
- analytics_dashboard_url = '{0}/courses/{1}'.format(settings.ANALYTICS_DASHBOARD_URL, unicode(course_key))
- link_start = "".format(analytics_dashboard_url)
- analytics_dashboard_message = _("To gain insights into student enrollment and participation {link_start}visit {analytics_dashboard_name}, our new course analytics product{link_end}.")
- analytics_dashboard_message = analytics_dashboard_message.format(
- link_start=link_start, link_end=" ", analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME)
-
context = {
'course': course,
'old_dashboard_url': reverse('instructor_dashboard_legacy', kwargs={'course_id': unicode(course_key)}),
@@ -530,19 +535,20 @@ def _get_dashboard_link(course_key):
def _section_analytics(course, access):
""" Provide data for the corresponding dashboard section """
course_key = course.id
+ analytics_dashboard_url = '{0}/courses/{1}'.format(settings.ANALYTICS_DASHBOARD_URL, unicode(course_key))
+ link_start = "".format(analytics_dashboard_url)
+ insights_message = _("For analytics about your course, go to {analytics_dashboard_name}.")
+
+ insights_message = insights_message.format(
+ analytics_dashboard_name='{0}{1} '.format(link_start, settings.ANALYTICS_DASHBOARD_NAME)
+ )
section_data = {
'section_key': 'instructor_analytics',
'section_display_name': _('Analytics'),
'access': access,
- 'get_distribution_url': reverse('get_distribution', kwargs={'course_id': unicode(course_key)}),
- 'proxy_legacy_analytics_url': reverse('proxy_legacy_analytics', kwargs={'course_id': unicode(course_key)}),
+ 'insights_message': insights_message,
}
- if settings.ANALYTICS_DASHBOARD_URL:
- dashboard_link = _get_dashboard_link(course_key)
- message = _("Demographic data is now available in {dashboard_link}.").format(dashboard_link=dashboard_link)
- section_data['demographic_message'] = message
-
return section_data
diff --git a/lms/envs/bok_choy.env.json b/lms/envs/bok_choy.env.json
index db5c28e966..2d35715fa7 100644
--- a/lms/envs/bok_choy.env.json
+++ b/lms/envs/bok_choy.env.json
@@ -79,7 +79,6 @@
"ENABLE_PAYMENT_FAKE": true,
"ENABLE_VERIFIED_CERTIFICATES": true,
"ENABLE_DISCUSSION_SERVICE": true,
- "ENABLE_INSTRUCTOR_ANALYTICS": true,
"ENABLE_S3_GRADE_DOWNLOADS": true,
"ENABLE_THIRD_PARTY_AUTH": true,
"ENABLE_COMBINED_LOGIN_REGISTRATION": true,
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 1503e61cca..833c8c6d61 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -169,10 +169,6 @@ FEATURES = {
# for all Mongo-backed courses.
'REQUIRE_COURSE_EMAIL_AUTH': True,
- # Analytics experiments - shows instructor analytics tab in LMS instructor dashboard.
- # Enabling this feature depends on installation of a separate analytics server.
- 'ENABLE_INSTRUCTOR_ANALYTICS': False,
-
# enable analytics server.
# WARNING: THIS SHOULD ALWAYS BE SET TO FALSE UNDER NORMAL
# LMS OPERATION. See analytics.py for details about what
@@ -338,10 +334,7 @@ FEATURES = {
# and register for course.
'ALLOW_AUTOMATED_SIGNUPS': False,
- # Display demographic data on the analytics tab in the instructor dashboard.
- 'DISPLAY_ANALYTICS_DEMOGRAPHICS': True,
-
- # Enable display of enrollment counts in instructor and legacy analytics dashboard
+ # Enable display of enrollment counts in instructor dash, analytics section
'DISPLAY_ANALYTICS_ENROLLMENTS': True,
# Show the mobile app links in the footer
diff --git a/lms/envs/dev.py b/lms/envs/dev.py
index a0e3dee83f..cfb864375c 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -30,7 +30,6 @@ FEATURES['SUBDOMAIN_BRANDING'] = True
FEATURES['FORCE_UNIVERSITY_DOMAIN'] = None # show all university courses if in dev (ie don't use HTTP_HOST)
FEATURES['ENABLE_MANUAL_GIT_RELOAD'] = True
FEATURES['ENABLE_PSYCHOMETRICS'] = False # real-time psychometrics (eg item response theory analysis in instructor dashboard)
-FEATURES['ENABLE_INSTRUCTOR_ANALYTICS'] = True
FEATURES['ENABLE_SERVICE_STATUS'] = True
FEATURES['ENABLE_INSTRUCTOR_EMAIL'] = True # Enable email for all Studio courses
FEATURES['REQUIRE_COURSE_EMAIL_AUTH'] = False # Give all courses email (don't require django-admin perms)
diff --git a/lms/static/coffee/src/instructor_dashboard/instructor_analytics.coffee b/lms/static/coffee/src/instructor_dashboard/instructor_analytics.coffee
deleted file mode 100644
index 8f6ef7654d..0000000000
--- a/lms/static/coffee/src/instructor_dashboard/instructor_analytics.coffee
+++ /dev/null
@@ -1,241 +0,0 @@
-###
-Analytics Section
-
-imports from other modules.
-wrap in (-> ... apply) to defer evaluation
-such that the value can be defined later than this assignment (file load order).
-###
-
-plantTimeout = -> window.InstructorDashboard.util.plantTimeout.apply this, arguments
-std_ajax_err = -> window.InstructorDashboard.util.std_ajax_err.apply this, arguments
-
-
-class ProfileDistributionWidget
- constructor: ({@$container, @feature, @title, @endpoint}) ->
- # render template
- template_params =
- title: @title
- feature: @feature
- endpoint: @endpoint
- template_html = $("#profile-distribution-widget-template").text()
- @$container.html Mustache.render template_html, template_params
-
- reset_display: ->
- @$container.find('.display-errors').empty()
- @$container.find('.display-text').empty()
- @$container.find('.display-graph').empty()
- @$container.find('.display-table').empty()
-
- show_error: (msg) ->
- @$container.find('.display-errors').text msg
-
- # display data
- load: ->
- @reset_display()
-
- @get_profile_distributions @feature,
- error: std_ajax_err =>
- `// Translators: "Distribution" refers to a grade distribution. This error message appears when there is an error getting the data on grade distribution.`
- @show_error gettext("Error fetching distribution.")
- success: (data) =>
- feature_res = data.feature_results
- if feature_res.type is 'EASY_CHOICE'
- # display on SlickGrid
- options =
- enableCellNavigation: true
- enableColumnReorder: false
- forceFitColumns: true
-
- columns = [
- id: @feature
- field: @feature
- name: data.feature_display_names[@feature]
- ,
- id: 'count'
- field: 'count'
- name: 'Count'
- ]
-
- grid_data = _.map feature_res.data, (value, key) =>
- datapoint = {}
- datapoint[@feature] = feature_res.choices_display_names[key]
- datapoint['count'] = value
- datapoint
-
- table_placeholder = $ '
', class: 'slickgrid'
- @$container.find('.display-table').append table_placeholder
- grid = new Slick.Grid(table_placeholder, grid_data, columns, options)
- else if feature_res.feature is 'year_of_birth'
- graph_placeholder = $ '
', class: 'graph-placeholder'
- @$container.find('.display-graph').append graph_placeholder
-
- graph_data = _.map feature_res.data, (value, key) -> [parseInt(key), value]
-
- $.plot graph_placeholder, [
- data: graph_data
- ]
- else
- console.warn("unable to show distribution #{feature_res.type}")
- @show_error gettext('Unavailable metric display.')
-
- # fetch distribution data from server.
- # `handler` can be either a callback for success
- # or a mapping e.g. {success: ->, error: ->, complete: ->}
- get_profile_distributions: (feature, handler) ->
- settings =
- dataType: 'json'
- url: @endpoint
- data: feature: feature
-
- if typeof handler is 'function'
- _.extend settings, success: handler
- else
- _.extend settings, handler
-
- $.ajax settings
-
-
-class GradeDistributionDisplay
- constructor: ({@$container, @endpoint}) ->
- template_params = {}
- template_html = $('#grade-distributions-widget-template').text()
- @$container.html Mustache.render template_html, template_params
- @$problem_selector = @$container.find '.problem-selector'
-
- reset_display: ->
- @$container.find('.display-errors').empty()
- @$container.find('.display-text').empty()
- @$container.find('.display-graph').empty()
-
- show_error: (msg) ->
- @$container.find('.display-errors').text msg
-
- load: ->
- @get_grade_distributions
- error: std_ajax_err => @show_error gettext("Error fetching grade distributions.")
- success: (data) =>
- time_updated = gettext("Last Updated: <%= timestamp %>")
- full_time_updated = _.template(time_updated, {timestamp: data.time})
- @$container.find('.last-updated').text full_time_updated
-
- # populate selector
- @$problem_selector.empty()
- for {module_id, block_id, grade_info} in data.data
- label = block_id
- label ?= module_id
-
- @$problem_selector.append $ ' ',
- text: label
- data:
- module_id: module_id
- grade_info: grade_info
-
- @$problem_selector.change =>
- $opt = @$problem_selector.children('option:selected')
- return unless $opt.length > 0
- @reset_display()
- @render_distribution
- module_id: $opt.data 'module_id'
- grade_info: $opt.data 'grade_info'
-
- # one-time first selection of first list item.
- @$problem_selector.change()
-
- render_distribution: ({module_id, grade_info}) ->
- $display_graph = @$container.find('.display-graph')
-
- graph_data = grade_info.map ({grade, max_grade, num_students}) -> [grade, num_students]
- total_students = _.reduce ([0].concat grade_info),
- (accum, {grade, max_grade, num_students}) -> accum + num_students
-
- msg = gettext("<%= num_students %> students scored.")
- full_msg = _.template(msg, {num_students: total_students})
- # show total students
- @$container.find('.display-text').text full_msg
-
- # render to graph
- graph_placeholder = $ '
', class: 'graph-placeholder'
- $display_graph.append graph_placeholder
-
- graph_data = graph_data
-
- $.plot graph_placeholder, [
- data: graph_data
- bars: show: true
- color: '#1d9dd9'
- ]
-
-
- # `handler` can be either a callback for success
- # or a mapping e.g. {success: ->, error: ->, complete: ->}
- #
- # the data passed to the success handler takes this form:
- # {
- # "aname": "ProblemGradeDistribution",
- # "time": "2013-07-31T20:25:56+00:00",
- # "course_id": "MITx/6.002x/2013_Spring",
- # "options": {
- # "course_id": "MITx/6.002x/2013_Spring",
- # "_id": "6fudge2b49somedbid1e1",
- # "data": [
- # {
- # "module_id": "i4x://MITx/6.002x/problem/Capacitors_and_Energy_Storage",
- # "grade_info": [
- # {
- # "grade": 0.0,
- # "max_grade": 100.0,
- # "num_students": 3
- # }, ... for each grade number between 0 and max_grade
- # ],
- # }
- get_grade_distributions: (handler) ->
- settings =
- dataType: 'json'
- url: @endpoint
- data: aname: 'ProblemGradeDistribution'
-
- if typeof handler is 'function'
- _.extend settings, success: handler
- else
- _.extend settings, handler
-
- $.ajax settings
-
-
-# Analytics Section
-class InstructorAnalytics
- constructor: (@$section) ->
- @$section.data 'wrapper', @
-
- @$pd_containers = @$section.find '.profile-distribution-widget-container'
- @$gd_containers = @$section.find '.grade-distributions-widget-container'
-
- @pdws = _.map (@$pd_containers), (container) =>
- new ProfileDistributionWidget
- $container: $(container)
- feature: $(container).data 'feature'
- title: $(container).data 'title'
- endpoint: $(container).data 'endpoint'
-
- @gdws = _.map (@$gd_containers), (container) =>
- new GradeDistributionDisplay
- $container: $(container)
- endpoint: $(container).data 'endpoint'
-
- refresh: ->
- for pdw in @pdws
- pdw.load()
-
- for gdw in @gdws
- gdw.load()
-
- onClickTitle: ->
- @refresh()
-
-
-# export for use
-# create parent namespaces if they do not already exist.
-_.defaults window, InstructorDashboard: {}
-_.defaults window.InstructorDashboard, sections: {}
-_.defaults window.InstructorDashboard.sections,
- InstructorAnalytics: InstructorAnalytics
diff --git a/lms/static/sass/course/instructor/_instructor_2.scss b/lms/static/sass/course/instructor/_instructor_2.scss
index a32f204f52..099d36fe31 100644
--- a/lms/static/sass/course/instructor/_instructor_2.scss
+++ b/lms/static/sass/course/instructor/_instructor_2.scss
@@ -1355,40 +1355,6 @@
}
}
-
-.profile-distribution-widget {
- margin-bottom: ($baseline * 2);
-
- .display-graph .graph-placeholder {
- width: 750px;
- height: 250px;
- }
-
- .display-table {
- .slickgrid {
- height: 250px;
- }
- }
-}
-
-.grade-distributions-widget {
- margin-bottom: $baseline * 2;
-
- .last-updated {
- line-height: 2.2em;
- @include font-size(12);
- }
-
- .display-graph .graph-placeholder {
- width: 750px;
- height: 200px;
- }
-
- .display-text {
- line-height: 2em;
- }
-}
-
input[name="subject"] {
width:600px;
}
@@ -1875,39 +1841,6 @@ input[name="subject"] {
}
-.profile-distribution-widget {
- margin-bottom: ($baseline * 2);
-
- .display-graph .graph-placeholder {
- width: 750px;
- height: 250px;
- }
-
- .display-table {
- .slickgrid {
- height: 250px;
- }
- }
-}
-
-.grade-distributions-widget {
- margin-bottom: ($baseline * 2);
-
- .last-updated {
- line-height: 2.2em;
- @include font-size(12);
- }
-
- .display-graph .graph-placeholder {
- width: 750px;
- height: 200px;
- }
-
- .display-text {
- line-height: 2em;
- }
-}
-
input[name="subject"] {
width:600px;
}
diff --git a/lms/templates/courseware/legacy_instructor_dashboard.html b/lms/templates/courseware/legacy_instructor_dashboard.html
index 7cf15e4416..2aa0376a9e 100644
--- a/lms/templates/courseware/legacy_instructor_dashboard.html
+++ b/lms/templates/courseware/legacy_instructor_dashboard.html
@@ -159,9 +159,6 @@ function goto( mode)
%if show_email_tab:
| ${_("Email")}
%endif
- %if settings.FEATURES.get('ENABLE_INSTRUCTOR_ANALYTICS'):
- | ${_("Analytics")}
- %endif
%if settings.FEATURES.get('CLASS_DASHBOARD'):
| ${_("Metrics")}
%endif
@@ -402,203 +399,6 @@ function goto( mode)
##-----------------------------------------------------------------------------
-%if modeflag.get('Analytics'):
-
- %if not any(analytics_results.values()):
- ${_("No Analytics are available at this time.")}
- %endif
-
- %if analytics_results.get("StudentsDropoffPerDay"):
-
- ${_("Student activity day by day")}
- (${analytics_results["StudentsDropoffPerDay"]['time']})
-
-
-
-
- ${_("Day")}
- ${_("Students")}
-
- %for row in analytics_results['StudentsDropoffPerDay']['data']:
-
- ## For now, just discard the non-date portion
- ${row['last_day'].split("T")[0]}
- ${row['num_students']}
-
- %endfor
-
-
- %endif
-
- %if analytics_results.get("ProblemGradeDistribution"):
-
- ${_("Score distribution for problems")}
- (${analytics_results["ProblemGradeDistribution"]['time']})
-
-
-
-
- ${_("Problem")}
- ${_("Max")}
- ${_("Points Earned (Num Students)")}
-
- %for row in analytics_results['ProblemGradeDistribution']['data']:
-
- ${row['block_id']}
- ${max(grade_record['max_grade'] for grade_record in row["grade_info"])}
- %for grade_record in row["grade_info"]:
-
- %if isinstance(grade_record["grade"], float):
- ${"{grade:.2f}".format(**grade_record)}
- %else:
- ${"{grade}".format(**grade_record)}
- %endif
- (${grade_record["num_students"]})
-
- %endfor
-
- %endfor
-
-
- %endif
-%endif
-
-%if modeflag.get('Metrics'):
- %if not any (metrics_results.values()):
- ${_("There is no data available to display at this time.")}
- %else:
- <%namespace name="d3_stacked_bar_graph" file="/class_dashboard/d3_stacked_bar_graph.js"/>
- <%namespace name="all_section_metrics" file="/class_dashboard/all_section_metrics.js"/>
-
-
-
-
-
- ${_("Loading the latest graphs for you; depending on your class size, this may take a few minutes.")}
-
- %for i in range(0,len(metrics_results['section_display_name'])):
-
-
${_("Section:")} ${metrics_results['section_display_name'][i]}
-
-
-
${_("Count of Students that Opened a Subsection")}
-
${_("Loading")}
-
-
-
${_("Grade Distribution per Problem")}
- %if not metrics_results['section_has_problem'][i]:
-
${_("There are no problems in this section.")}
- %else:
-
${_("Loading")}
- %endif
-
-
- %endfor
-
-
- %endif
-%endif
-
-%if modeflag.get('Analytics In Progress'):
-
- ##This is not as helpful as it could be -- let's give full point distribution
- ##instead.
- %if analytics_results.get("StudentsPerProblemCorrect"):
-
- ${_("Students answering correctly")}
- (${analytics_results["StudentsPerProblemCorrect"]['time']})
-
-
- %endif
-
-
- ${_("Student distribution per country, all courses, Sep 12 to Oct 17, 1 server (shown here as an example):")}
-
-
-
-
-
-## Number of students who dropped off per day before becoming inactive:
-##
-## % if dropoff_per_day is not None:
-## % if dropoff_per_day['status'] == 'success':
-##
-## % else:
-## ${dropoff_per_day['error']}
-## % endif
-## % else:
-## null data
-## % endif
-##
-##
-
-
-##
-##
Daily activity (online version):
-##
-## Day Number of students
-## % for k,v in daily_activity_json['data'].items():
-##
-## ${k} ${v}
-##
-## % endfor
-##
-##
-
-
-%endif
-
-##-----------------------------------------------------------------------------
-
%if datatable and modeflag.get('Psychometrics') is None:
diff --git a/lms/templates/instructor/instructor_dashboard_2/instructor_analytics.html b/lms/templates/instructor/instructor_dashboard_2/instructor_analytics.html
index c75501cd0b..b0828ad5dd 100644
--- a/lms/templates/instructor/instructor_dashboard_2/instructor_analytics.html
+++ b/lms/templates/instructor/instructor_dashboard_2/instructor_analytics.html
@@ -1,71 +1,6 @@
<%! from django.utils.translation import ugettext as _ %>
<%page args="section_data"/>
-
-
-%if settings.FEATURES['ENABLE_INSTRUCTOR_ANALYTICS']:
-
-
-
-
-
-
-%endif
-
-%if settings.FEATURES['DISPLAY_ANALYTICS_DEMOGRAPHICS']:
-
-
-
-
-
-
-
-
-
-%elif section_data['demographic_message']:
- ${section_data['demographic_message']}
-%endif
+
+
${section_data['insights_message']}
+