Commit fb8c84a5 by Miles Steele

add analytics proxy endpoint

parent 35ffb1b3
......@@ -5,6 +5,7 @@ Unit tests for instructor.api methods.
import unittest
import json
from urllib import quote
from django.conf import settings
from django.test import TestCase
from nose.tools import raises
from mock import Mock
......@@ -23,6 +24,7 @@ from student.models import CourseEnrollment
from courseware.models import StudentModule
from instructor.access import allow_access
import instructor.views.api
from instructor.views.api import (
_split_input_list, _msk_from_problem_urlname, common_exceptions_400)
from instructor_task.api_helper import AlreadyRunningError
......@@ -118,6 +120,7 @@ class TestInstructorAPIDenyLevels(ModuleStoreTestCase, LoginEnrollmentTestCase):
'list_instructor_tasks',
'list_forum_members',
'update_forum_role_membership',
'proxy_legacy_analytics',
]
for endpoint in staff_level_endpoints:
url = reverse(endpoint, kwargs={'course_id': self.course.id})
......@@ -753,6 +756,95 @@ class TestInstructorAPITaskLists(ModuleStoreTestCase, LoginEnrollmentTestCase):
self.assertEqual(json.loads(response.content), expected_res)
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
@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 = instructor.views.api.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):
self.instructor = AdminFactory.create()
self.course = CourseFactory.create()
self.client.login(username=self.instructor.username, password='test')
def test_analytics_proxy_url(self):
""" Test legacy analytics proxy url generation. """
act = Mock(return_value=self.FakeProxyResponse())
instructor.views.api.requests.get = act
url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id})
response = self.client.get(url, {
'aname': 'ProblemGradeDistribution'
})
print response.content
self.assertEqual(response.status_code, 200)
# check request url
expected_url = "{url}get?aname={aname}&course_id={course_id}&apikey={api_key}".format(
url="http://robotanalyticsserver.netbot:900/",
aname="ProblemGradeDistribution",
course_id=self.course.id,
api_key="robot_api_key",
)
act.assert_called_once_with(expected_url)
def test_analytics_proxy(self):
"""
Test legacy analytics content proxying.
"""
act = Mock(return_value=self.FakeProxyResponse())
instructor.views.api.requests.get = act
url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id})
response = self.client.get(url, {
'aname': 'ProblemGradeDistribution'
})
print response.content
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)
def test_analytics_proxy_reqfailed(self):
""" Test proxy when server reponds with failure. """
act = Mock(return_value=self.FakeBadProxyResponse())
instructor.views.api.requests.get = act
url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id})
response = self.client.get(url, {
'aname': 'ProblemGradeDistribution'
})
print response.content
self.assertEqual(response.status_code, 500)
def test_analytics_proxy_missing_param(self):
""" Test proxy when missing the aname query parameter. """
act = Mock(return_value=self.FakeProxyResponse())
instructor.views.api.requests.get = act
url = reverse('proxy_legacy_analytics', kwargs={'course_id': self.course.id})
response = self.client.get(url, {})
print response.content
self.assertEqual(response.status_code, 400)
self.assertFalse(act.called)
class TestInstructorAPIHelpers(TestCase):
""" Test helpers for instructor.api """
def test_split_input_list(self):
......
......@@ -8,11 +8,15 @@ Many of these GETs may become PUTs in the future.
import re
import logging
import requests
from requests.status_codes import codes
from collections import OrderedDict
from django.conf import settings
from django_future.csrf import ensure_csrf_cookie
from django.views.decorators.cache import cache_control
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from django.http import HttpResponseBadRequest, HttpResponseForbidden
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from util.json_request import JsonResponse
from courseware.access import has_access
......@@ -725,6 +729,57 @@ 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.
"""
analytics_name = request.GET.get('aname')
# abort if misconfigured
if not (hasattr(settings, 'ANALYTICS_SERVER_URL') and hasattr(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,
course_id,
settings.ANALYTICS_API_KEY,
)
try:
res = requests.get(url)
except Exception:
log.exception("Error requesting from analytics server at %s", url)
return HttpResponse("Error requesting from analytics server.", status=500)
if res.status_code is 200:
# return the successful request content
return HttpResponse(res.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(
"Error fetching {}, code: {}, msg: {}".format(
url, res.status_code, res.content
)
)
return HttpResponse(
"Error from analytics server ({}).".format(res.status_code),
status=500
)
def _split_input_list(str_list):
"""
Separate out individual student email from the comma, or space separated string.
......
......@@ -30,4 +30,6 @@ urlpatterns = patterns('', # nopep8
'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"),
)
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