Commit 0fc5b1a6 by Clinton Blackburn

Added Context-Sensitive Help

parent 8fb002b2
......@@ -2,7 +2,7 @@
ROOT = $(shell echo "$$PWD")
COVERAGE = $(ROOT)/build/coverage
PACKAGES = analytics_dashboard courses django_rjs
PACKAGES = analytics_dashboard courses django_rjs help
NUM_PROCESSES = 2
.PHONY: requirements clean
......
......@@ -35,3 +35,4 @@ if ENABLE_OAUTH_TESTS and not (LMS_HOSTNAME and LMS_USERNAME and LMS_PASSWORD):
raise Exception('LMS settings must be set in order to test OAuth.')
TEST_COURSE_ID = os.environ.get('TEST_COURSE_ID', u'edX/DemoX/Demo_Course')
DOC_BASE_URL = os.environ.get('DOC_BASE_URL', 'http://edx-insights.readthedocs.org/en/latest')
......@@ -2,10 +2,10 @@ import locale
import datetime
from bok_choy.promise import EmptyPromise
from analyticsclient.client import Client
from acceptance_tests import API_SERVER_URL, API_AUTH_TOKEN, DASHBOARD_FEEDBACK_EMAIL, SUPPORT_URL, LMS_USERNAME, \
LMS_PASSWORD, DASHBOARD_SERVER_URL, ENABLE_AUTO_AUTH
LMS_PASSWORD, DASHBOARD_SERVER_URL, ENABLE_AUTO_AUTH, DOC_BASE_URL
from pages import LMSLoginPage
......@@ -31,6 +31,11 @@ class AssertMixin(object):
self.assertTrue(element.present)
self.assertNotEqual(element.attrs('href')[0], '#')
def assertHrefEqual(self, selector, href):
element = self.page.q(css=selector)
self.assertTrue(element.present)
self.assertEqual(element.attrs('href')[0], href)
def assertTableColumnHeadingsEqual(self, table_selector, headings):
rows = self.page.q(css=('%s thead th' % table_selector))
self.assertTrue(rows.present)
......@@ -82,8 +87,7 @@ class FooterMixin(AssertMixin):
# check that we have the support link
selector = footer_selector + " a[class=support-link]"
element = self.page.q(css=selector)
self.assertEqual(element.attrs('href')[0], SUPPORT_URL)
self.assertHrefEqual(selector, SUPPORT_URL)
# Verify the terms of service link is present
selector = footer_selector + " a[data-role=tos]"
......@@ -97,6 +101,10 @@ class FooterMixin(AssertMixin):
self.assertTrue(element.present)
self.assertEqual(element.text[0], u'Privacy Policy')
def test_page(self):
super(FooterMixin, self).test_page()
self._test_footer()
class PrimaryNavMixin(object):
def _test_user_menu(self):
......@@ -113,6 +121,9 @@ class PrimaryNavMixin(object):
element = self.page.q(css='ul.dropdown-menu.active-user-nav')
self.assertTrue(element.visible)
def test_page(self):
self._test_user_menu()
class LoginMixin(object):
def setUp(self):
......@@ -137,7 +148,27 @@ class LoginMixin(object):
self.lms_login_page.login(LMS_USERNAME, LMS_PASSWORD)
class CoursePageTestsMixin(LoginMixin, AnalyticsApiClientMixin, FooterMixin, PrimaryNavMixin):
class ContextSensitiveHelpMixin(object):
help_path = 'index.html'
@property
def help_url(self):
return '{0}/{1}'.format(DOC_BASE_URL, self.help_path)
def test_page(self):
# Validate the help link
self.assertHrefEqual('#help', self.help_url)
class AnalyticsDashboardWebAppTestMixin(PrimaryNavMixin, ContextSensitiveHelpMixin, AssertMixin, LoginMixin):
def test_page(self):
self.login()
self.page.visit()
PrimaryNavMixin.test_page(self)
ContextSensitiveHelpMixin.test_page(self)
class CoursePageTestsMixin(AnalyticsApiClientMixin, FooterMixin, AnalyticsDashboardWebAppTestMixin):
""" Mixin for common course page assertions and tests. """
DASHBOARD_DATE_FORMAT = '%B %d, %Y'
......@@ -233,10 +264,7 @@ class CoursePageTestsMixin(LoginMixin, AnalyticsApiClientMixin, FooterMixin, Pri
pass, execution of this parent method will leave the browser on the page being tested.
:return:
"""
self.login()
self.page.visit()
self._test_user_menu()
self._test_footer()
super(CoursePageTestsMixin, self).test_page()
self._test_data_update_message()
@staticmethod
......@@ -285,7 +313,7 @@ class CourseDemographicsPageTestsMixin(CoursePageTestsMixin):
self.assertEqual(element.text[0], self.data_information_message)
def _get_data_update_message(self):
return self._build_data_update_message(self.course.enrollment(self.demographic_type))
return self._build_data_update_message(self.course.enrollment(self.demographic_type))
def _build_data_update_message(self, api_response):
current_data = api_response[0]
......
......@@ -15,6 +15,8 @@ class CourseEngagementTests(CoursePageTestsMixin, WebAppTest):
Tests for the Engagement page.
"""
help_path = 'engagement/Engagement_Content.html'
def setUp(self):
"""
Instantiate the page object.
......
......@@ -11,6 +11,8 @@ _multiprocess_can_split_ = True
class CourseEnrollmentActivityTests(CoursePageTestsMixin, WebAppTest):
help_path = 'enrollment/Enrollment_Activity.html'
def setUp(self):
super(CourseEnrollmentActivityTests, self).setUp()
self.page = CourseEnrollmentActivityPage(self.browser)
......@@ -85,6 +87,8 @@ class CourseEnrollmentActivityTests(CoursePageTestsMixin, WebAppTest):
class CourseEnrollmentGeographyTests(CoursePageTestsMixin, WebAppTest):
help_path = 'enrollment/Enrollment_Geography.html'
def setUp(self):
super(CourseEnrollmentGeographyTests, self).setUp()
self.page = CourseEnrollmentGeographyPage(self.browser)
......
from bok_choy.web_app_test import WebAppTest
from acceptance_tests import TEST_COURSE_ID
from acceptance_tests.mixins import AssertMixin, PrimaryNavMixin, LoginMixin
from acceptance_tests.mixins import AnalyticsDashboardWebAppTestMixin
from pages import CourseIndexPage
_multiprocess_can_split_ = True
class CourseIndexTests(AssertMixin, PrimaryNavMixin, LoginMixin, WebAppTest):
class CourseIndexTests(AnalyticsDashboardWebAppTestMixin, WebAppTest):
def setUp(self):
super(CourseIndexTests, self).setUp()
self.page = CourseIndexPage(self.browser)
......
......@@ -3,7 +3,6 @@ from django.conf import settings
def common(_request):
return {
'help_url': settings.HELP_URL,
'feedback_email': settings.FEEDBACK_EMAIL,
'support_url': settings.SUPPORT_URL,
'terms_of_service_url': settings.TERMS_OF_SERVICE_URL,
......
"""Common settings and globals."""
import ConfigParser
from os.path import abspath, basename, dirname, join, normpath
from sys import path
......@@ -187,6 +187,7 @@ MIDDLEWARE_CLASSES = (
'courses.middleware.CourseMiddleware',
'courses.middleware.CoursePermissionsExceptionMiddleware',
'social.apps.django_app.middleware.SocialAuthExceptionMiddleware',
'help.middleware.HelpURLMiddleware',
)
########## END MIDDLEWARE CONFIGURATION
......@@ -222,6 +223,7 @@ LOCAL_APPS = (
'analytics_dashboard',
'courses',
'django_rjs',
'help',
)
# See: https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
......@@ -384,3 +386,13 @@ FULL_APPLICATION_NAME = '{0} {1}'.format(PLATFORM_NAME, APPLICATION_NAME)
RJS_OUTPUT_DIR = 'dist'
RJS_OPTIMIZATION_ENABLED = False
########## DOCS/HELP CONFIGURATION
DOCS_ROOT = join(dirname(SITE_ROOT), 'docs')
# Load the docs config into memory when the server starts
with open(join(DOCS_ROOT, "config.ini")) as config_file:
DOCS_CONFIG = ConfigParser.ConfigParser()
DOCS_CONFIG.readfp(config_file)
########## END DOCS/HELP CONFIGURATION
......@@ -22,6 +22,7 @@ from courses import permissions
from courses.presenters import CourseEngagementPresenter, CourseEnrollmentPresenter, \
CourseEnrollmentDemographicsPresenter
from courses.utils import is_feature_enabled
from help.views import ContextSensitiveHelpMixin
logger = logging.getLogger(__name__)
......@@ -280,9 +281,14 @@ class CourseView(LoginRequiredMixin, CourseValidMixin, CoursePermissionMixin, Te
return context
class CourseTemplateView(CourseContextMixin, CourseNavBarMixin, CourseView):
class CourseTemplateView(ContextSensitiveHelpMixin, CourseContextMixin, CourseNavBarMixin, CourseView):
update_message = None
@property
def help_token(self):
# Rather than duplicate the definition, simply return the page name.
return self.page_name
def get_last_updated_message(self, last_updated):
if last_updated:
return self.update_message % self.format_last_updated_date_and_time(last_updated)
......
HELP_CONTEXT_TOKEN_NAME = 'help_token'
from help import HELP_CONTEXT_TOKEN_NAME
from help.utils import get_doc_url
class HelpURLMiddleware(object):
"""
Adds a "help_url" entry to the response context.
"""
def process_template_response(self, _request, response):
# Error responses do not have context.
if response.status_code == 500:
return response
page_token = response.context_data.get(HELP_CONTEXT_TOKEN_NAME)
response.context_data['help_url'] = get_doc_url(page_token)
return response
from django import http
from django.template.response import TemplateResponse
from django.test import TestCase
from help import HELP_CONTEXT_TOKEN_NAME
from help.middleware import HelpURLMiddleware
from help.utils import get_doc_url
DOC_BASE_URL = 'http://edx-insights.readthedocs.org/en/latest'
def build_doc_url(path):
return '{0}/{1}'.format(DOC_BASE_URL, path)
DOC_INDEX = build_doc_url('index.html')
DOC_ENROLLMENT_ACTIVITY = build_doc_url('enrollment/Enrollment_Activity.html')
class HelpURLMiddlewareTests(TestCase):
def setUp(self):
self.middleware = HelpURLMiddleware()
def assertHelpURLEqual(self, page_token, expected_url):
request = http.HttpRequest()
context = {}
if page_token is not None:
context[HELP_CONTEXT_TOKEN_NAME] = page_token
response = TemplateResponse(request, None, context)
response = self.middleware.process_template_response(request, response)
self.assertEqual(response.context_data['help_url'], expected_url)
def test_process_template_response(self):
# If the context has no page_token set, help_url should be set to the default docs page.
self.assertHelpURLEqual(None, DOC_INDEX)
# If the context has an invalid page_token set, help_url should be set to the default docs page.
self.assertHelpURLEqual('Not a real token', DOC_INDEX)
# If the context has a valid page_token set, help_url should be set to the corresponding docs page.
self.assertHelpURLEqual('enrollment_activity', DOC_ENROLLMENT_ACTIVITY)
def test_process_template_response_with_error(self):
"""
The middleware should NOT process error responses.
"""
response = http.HttpResponseServerError()
request = http.HttpRequest()
response = self.middleware.process_template_response(request, response)
self.assertFalse(hasattr(response, 'context_data'))
class UtilsTests(TestCase):
def assertValidDocURL(self, page_token, expected_url):
actual = get_doc_url(page_token)
self.assertEqual(actual, expected_url)
def test_get_doc_url(self):
# If no page_token passed, return the default docs page.
self.assertValidDocURL(None, DOC_INDEX)
# If an invalid page_token passed, return the default docs page.
self.assertValidDocURL('Not a real token', DOC_INDEX)
# If valid page_token passed, return the corresponding docs page.
self.assertValidDocURL('enrollment_activity', DOC_ENROLLMENT_ACTIVITY)
import ConfigParser
import logging
from django.conf import settings
log = logging.getLogger(__name__)
def _get_config_value_with_default(section_name, option, default_option="default"):
"""
Args:
section_name: name of the section in the configuration from which the option should be found
option: name of the configuration option
default_option: name of the default configuration option whose value should be returned if the
requested option is not found
"""
try:
return settings.DOCS_CONFIG.get(section_name, option)
except (ConfigParser.NoOptionError, AttributeError):
log.debug("Didn't find a configuration option for '%s' section and '%s' option", section_name, option)
return settings.DOCS_CONFIG.get(section_name, default_option)
def get_doc_url(page_token=None):
"""
Returns:
The URL for the documentation
"""
return "{url_base}/{language}/{version}/{page_path}".format(
url_base=settings.DOCS_CONFIG.get("help_settings", "url_base"),
language=_get_config_value_with_default("locales", settings.LANGUAGE_CODE),
version=settings.DOCS_CONFIG.get("help_settings", "version"),
page_path=_get_config_value_with_default("pages", page_token),
)
from help import HELP_CONTEXT_TOKEN_NAME
class ContextSensitiveHelpMixin(object):
"""
Adds page-specific data to enable context-sensitive help.
"""
help_token = None
def get_context_data(self, **kwargs):
context = super(ContextSensitiveHelpMixin, self).get_context_data(**kwargs)
context[HELP_CONTEXT_TOKEN_NAME] = self.help_token
return context
......@@ -22,8 +22,8 @@ Partial: App-wide header element
<ul class="nav navbar-nav navbar-right">
{% if help_url %}
<li>
<a class="navbar-link " data-track-type="click" data-track-event="edx.bi.help.clicked" href="{{ help_url }}"
target="_blank">{% trans "Help" %}</a>
<a id="help" class="navbar-link " data-track-type="click" data-track-event="edx.bi.help.clicked"
href="{{ help_url }}" target="_blank">{% trans "Help" %}</a>
</li>
{% endif %}
<li class="dropdown">
......
# below are the server-wide settings for documentation
[help_settings]
url_base = http://edx.readthedocs.org/projects/edx-analytics-dashboard
url_base = http://edx-insights.readthedocs.org
version = latest
......@@ -10,12 +10,13 @@ pdf_base = https://media.readthedocs.org/pdf/edx-analytics-dashboard
pdf_file = edx-analytics-dashboard.pdf
# below are the sub-paths to the documentation for the various pages
# NOTE: If any of these page settings change, then their corresponding text should be updated
# in edx-platform/cms/djangoapps/contentstore/features/help.feature
# Below are the sub-paths to the documentation for the various pages. If any of these page settings change, then their
# corresponding text should be updated in the acceptance tests.
[pages]
default = index.html
home = getting_started/get_started.html
enrollment_activity = enrollment/Enrollment_Activity.html
enrollment_geography = enrollment/Enrollment_Geography.html
engagement_content = engagement/Engagement_Content.html
# below are the language directory names for the different locales
[locales]
......
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