Merge pull request #14708 from open-craft/haikuginger/dashboard-consent-banner
[ENT-225] Add consent declined banner to dashboard
This commit is contained in:
@@ -102,6 +102,7 @@ from util.milestones_helpers import (
|
||||
)
|
||||
|
||||
from util.password_policy_validators import validate_password_strength
|
||||
from util.enterprise_helpers import get_dashboard_consent_notification
|
||||
import third_party_auth
|
||||
from third_party_auth import pipeline, provider
|
||||
from student.helpers import (
|
||||
@@ -684,6 +685,8 @@ def dashboard(request):
|
||||
{'email': user.email, 'platform_name': platform_name}
|
||||
)
|
||||
|
||||
enterprise_message = get_dashboard_consent_notification(request, user, course_enrollments)
|
||||
|
||||
# Global staff can see what courses errored on their dashboard
|
||||
staff_access = False
|
||||
errored_courses = {}
|
||||
@@ -804,6 +807,7 @@ def dashboard(request):
|
||||
display_sidebar_on_dashboard = len(order_history_list) or verification_status in valid_verification_statuses
|
||||
|
||||
context = {
|
||||
'enterprise_message': enterprise_message,
|
||||
'enrollment_message': enrollment_message,
|
||||
'redirect_message': redirect_message,
|
||||
'course_enrollments': course_enrollments,
|
||||
|
||||
@@ -6,13 +6,16 @@ import logging
|
||||
from functools import wraps
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.cache import cache
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.http import urlencode
|
||||
from django.core.cache import cache
|
||||
from django.utils.translation import ugettext as _
|
||||
from edxmako.shortcuts import render_to_string
|
||||
from edx_rest_api_client.client import EdxRestApiClient
|
||||
try:
|
||||
from enterprise import utils as enterprise_utils
|
||||
from enterprise.models import EnterpriseCourseEnrollment
|
||||
from enterprise.utils import consent_necessary_for_course
|
||||
except ImportError:
|
||||
pass
|
||||
@@ -23,6 +26,7 @@ import hashlib
|
||||
import six
|
||||
|
||||
|
||||
CONSENT_FAILED_PARAMETER = 'consent_failed'
|
||||
ENTERPRISE_CUSTOMER_BRANDING_OVERRIDE_DETAILS = 'enterprise_customer_branding_override_details'
|
||||
LOGGER = logging.getLogger("edx.enterprise_helpers")
|
||||
|
||||
@@ -249,7 +253,14 @@ def get_enterprise_consent_url(request, course_id, user=None, return_to=None):
|
||||
|
||||
url_params = {
|
||||
'course_id': course_id,
|
||||
'next': request.build_absolute_uri(return_path)
|
||||
'next': request.build_absolute_uri(return_path),
|
||||
'failure_url': request.build_absolute_uri(
|
||||
reverse('dashboard') + '?' + urlencode(
|
||||
{
|
||||
CONSENT_FAILED_PARAMETER: course_id
|
||||
}
|
||||
)
|
||||
),
|
||||
}
|
||||
querystring = urlencode(url_params)
|
||||
full_url = reverse('grant_data_sharing_permissions') + '?' + querystring
|
||||
@@ -366,3 +377,68 @@ def get_enterprise_learner_data(site, user):
|
||||
enterprise_learner_data = EnterpriseApiClient().fetch_enterprise_learner_data(site=site, user=user)
|
||||
if enterprise_learner_data:
|
||||
return enterprise_learner_data['results']
|
||||
|
||||
|
||||
def get_dashboard_consent_notification(request, user, course_enrollments):
|
||||
"""
|
||||
If relevant to the request at hand, create a banner on the dashboard indicating consent failed.
|
||||
|
||||
Args:
|
||||
request: The WSGIRequest object produced by the user browsing to the Dashboard page.
|
||||
user: The logged-in user
|
||||
course_enrollments: A list of the courses to be rendered on the Dashboard page.
|
||||
|
||||
Returns:
|
||||
str: Either an empty string, or a string containing the HTML code for the notification banner.
|
||||
"""
|
||||
enrollment = None
|
||||
enterprise_enrollment = None
|
||||
course_id = request.GET.get(CONSENT_FAILED_PARAMETER)
|
||||
|
||||
if course_id:
|
||||
for course_enrollment in course_enrollments:
|
||||
if str(course_enrollment.course_id) == course_id:
|
||||
enrollment = course_enrollment
|
||||
break
|
||||
|
||||
try:
|
||||
enterprise_enrollment = EnterpriseCourseEnrollment.objects.get(
|
||||
course_id=course_id,
|
||||
enterprise_customer_user__user_id=user.id,
|
||||
)
|
||||
except EnterpriseCourseEnrollment.DoesNotExist:
|
||||
pass
|
||||
|
||||
if enterprise_enrollment and enrollment:
|
||||
enterprise_customer = enterprise_enrollment.enterprise_customer_user.enterprise_customer
|
||||
contact_info = getattr(enterprise_customer, 'contact_email', None)
|
||||
|
||||
if contact_info is None:
|
||||
message_template = _(
|
||||
'If you have concerns about sharing your data, please contact your administrator '
|
||||
'at {enterprise_customer_name}.'
|
||||
)
|
||||
else:
|
||||
message_template = _(
|
||||
'If you have concerns about sharing your data, please contact your administrator '
|
||||
'at {enterprise_customer_name} at {contact_info}.'
|
||||
)
|
||||
|
||||
message = message_template.format(
|
||||
enterprise_customer_name=enterprise_customer.name,
|
||||
contact_info=contact_info,
|
||||
)
|
||||
title = _(
|
||||
'Enrollment in {course_name} was not complete.'
|
||||
).format(
|
||||
course_name=enrollment.course_overview.display_name,
|
||||
)
|
||||
|
||||
return render_to_string(
|
||||
'util/enterprise_consent_declined_notification.html',
|
||||
{
|
||||
'title': title,
|
||||
'message': message,
|
||||
}
|
||||
)
|
||||
return ''
|
||||
|
||||
@@ -13,7 +13,9 @@ from util.enterprise_helpers import (
|
||||
insert_enterprise_pipeline_elements,
|
||||
data_sharing_consent_required,
|
||||
set_enterprise_branding_filter_param,
|
||||
get_dashboard_consent_notification,
|
||||
get_enterprise_branding_filter_param,
|
||||
get_enterprise_consent_url,
|
||||
get_enterprise_customer_logo_url
|
||||
)
|
||||
|
||||
@@ -192,3 +194,144 @@ class TestEnterpriseHelpers(unittest.TestCase):
|
||||
mock_get_consent_url.assert_called_once()
|
||||
mock_enterprise_enabled.assert_called_once()
|
||||
mock_consent_necessary.assert_called_once()
|
||||
|
||||
@mock.patch('util.enterprise_helpers.consent_needed_for_course')
|
||||
def test_get_enterprise_consent_url(self, needed_for_course_mock):
|
||||
"""
|
||||
Verify that get_enterprise_consent_url correctly builds URLs.
|
||||
"""
|
||||
needed_for_course_mock.return_value = True
|
||||
|
||||
request_mock = mock.MagicMock(
|
||||
user=None,
|
||||
build_absolute_uri=lambda x: 'http://localhost:8000' + x # Don't do it like this in prod. Ever.
|
||||
)
|
||||
|
||||
course_id = 'course-v1:edX+DemoX+Demo_Course'
|
||||
return_to = 'info'
|
||||
|
||||
expected_url = (
|
||||
'/enterprise/grant_data_sharing_permissions?course_id=course-v1%3AedX%2BDemoX%2BDemo_'
|
||||
'Course&failure_url=http%3A%2F%2Flocalhost%3A8000%2Fdashboard%3Fconsent_failed%3Dcou'
|
||||
'rse-v1%253AedX%252BDemoX%252BDemo_Course&next=http%3A%2F%2Flocalhost%3A8000%2Fcours'
|
||||
'es%2Fcourse-v1%3AedX%2BDemoX%2BDemo_Course%2Finfo'
|
||||
)
|
||||
actual_url = get_enterprise_consent_url(request_mock, course_id, return_to=return_to)
|
||||
self.assertEqual(actual_url, expected_url)
|
||||
|
||||
def test_get_dashboard_consent_notification_no_param(self):
|
||||
"""
|
||||
Test that the output of the consent notification renderer meets expectations.
|
||||
"""
|
||||
request = mock.MagicMock(
|
||||
GET={}
|
||||
)
|
||||
notification_string = get_dashboard_consent_notification(
|
||||
request, None, None
|
||||
)
|
||||
self.assertEqual(notification_string, '')
|
||||
|
||||
def test_get_dashboard_consent_notification_no_enrollments(self):
|
||||
request = mock.MagicMock(
|
||||
GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
|
||||
)
|
||||
enrollments = []
|
||||
user = mock.MagicMock(id=1)
|
||||
notification_string = get_dashboard_consent_notification(
|
||||
request, user, enrollments,
|
||||
)
|
||||
self.assertEqual(notification_string, '')
|
||||
|
||||
def test_get_dashboard_consent_notification_no_matching_enrollments(self):
|
||||
request = mock.MagicMock(
|
||||
GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
|
||||
)
|
||||
enrollments = [mock.MagicMock(course_id='other_course_id')]
|
||||
user = mock.MagicMock(id=1)
|
||||
notification_string = get_dashboard_consent_notification(
|
||||
request, user, enrollments,
|
||||
)
|
||||
self.assertEqual(notification_string, '')
|
||||
|
||||
def test_get_dashboard_consent_notification_no_matching_ece(self):
|
||||
request = mock.MagicMock(
|
||||
GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
|
||||
)
|
||||
enrollments = [mock.MagicMock(course_id='course-v1:edX+DemoX+Demo_Course')]
|
||||
user = mock.MagicMock(id=1)
|
||||
notification_string = get_dashboard_consent_notification(
|
||||
request, user, enrollments,
|
||||
)
|
||||
self.assertEqual(notification_string, '')
|
||||
|
||||
@mock.patch('util.enterprise_helpers.EnterpriseCourseEnrollment')
|
||||
def test_get_dashboard_consent_notification_no_contact_info(self, ece_mock):
|
||||
mock_get_ece = ece_mock.objects.get
|
||||
ece_mock.DoesNotExist = Exception
|
||||
mock_ece = mock_get_ece.return_value
|
||||
mock_ece.enterprise_customer_user = mock.MagicMock(
|
||||
enterprise_customer=mock.MagicMock(
|
||||
contact_email=None
|
||||
)
|
||||
)
|
||||
mock_ec = mock_ece.enterprise_customer_user.enterprise_customer
|
||||
mock_ec.name = 'Veridian Dynamics'
|
||||
|
||||
request = mock.MagicMock(
|
||||
GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
|
||||
)
|
||||
enrollments = [
|
||||
mock.MagicMock(
|
||||
course_id='course-v1:edX+DemoX+Demo_Course',
|
||||
course_overview=mock.MagicMock(
|
||||
display_name='edX Demo Course',
|
||||
)
|
||||
),
|
||||
]
|
||||
user = mock.MagicMock(id=1)
|
||||
notification_string = get_dashboard_consent_notification(
|
||||
request, user, enrollments,
|
||||
)
|
||||
expected_message = (
|
||||
'If you have concerns about sharing your data, please contact your '
|
||||
'administrator at Veridian Dynamics.'
|
||||
)
|
||||
self.assertIn(expected_message, notification_string)
|
||||
expected_header = 'Enrollment in edX Demo Course was not complete.'
|
||||
self.assertIn(expected_header, notification_string)
|
||||
|
||||
@mock.patch('util.enterprise_helpers.EnterpriseCourseEnrollment')
|
||||
def test_get_dashboard_consent_notification_contact_info(self, ece_mock):
|
||||
mock_get_ece = ece_mock.objects.get
|
||||
ece_mock.DoesNotExist = Exception
|
||||
mock_ece = mock_get_ece.return_value
|
||||
mock_ece.enterprise_customer_user = mock.MagicMock(
|
||||
enterprise_customer=mock.MagicMock(
|
||||
contact_email='v.palmer@veridiandynamics.com'
|
||||
)
|
||||
)
|
||||
mock_ec = mock_ece.enterprise_customer_user.enterprise_customer
|
||||
mock_ec.name = 'Veridian Dynamics'
|
||||
|
||||
request = mock.MagicMock(
|
||||
GET={'consent_failed': 'course-v1:edX+DemoX+Demo_Course'}
|
||||
)
|
||||
enrollments = [
|
||||
mock.MagicMock(
|
||||
course_id='course-v1:edX+DemoX+Demo_Course',
|
||||
course_overview=mock.MagicMock(
|
||||
display_name='edX Demo Course',
|
||||
)
|
||||
),
|
||||
]
|
||||
user = mock.MagicMock(id=1)
|
||||
notification_string = get_dashboard_consent_notification(
|
||||
request, user, enrollments,
|
||||
)
|
||||
expected_message = (
|
||||
'If you have concerns about sharing your data, please contact your '
|
||||
'administrator at Veridian Dynamics at v.palmer@veridiandynamics.com.'
|
||||
)
|
||||
self.assertIn(expected_message, notification_string)
|
||||
expected_header = 'Enrollment in edX Demo Course was not complete.'
|
||||
self.assertIn(expected_header, notification_string)
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<%page expression_filter="h"/>
|
||||
<div class="wrapper-msg urgency-info">
|
||||
<div class="msg">
|
||||
<span class="msg-icon fa fa-info-circle" aria-hidden="true"></span>
|
||||
<div class="msg-content">
|
||||
<h2 class="title">${ title }</h2>
|
||||
<div class="copy">
|
||||
<p class='consent-declined-message'>${ message }</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,6 +118,20 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.urgency-info {
|
||||
background: $msg-bg;
|
||||
.msg {
|
||||
color: $white;
|
||||
}
|
||||
.msg-icon {
|
||||
font-size: 2.5em;
|
||||
padding: 20px;
|
||||
}
|
||||
.msg-content {
|
||||
max-width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
&.alert {
|
||||
border-top: 3px solid $alert-color;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,12 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
${enrollment_message | n, decode.utf8}
|
||||
</div>
|
||||
%endif
|
||||
|
||||
%if enterprise_message:
|
||||
<div class="dashboard-banner">
|
||||
${ enterprise_message | n, decode.utf8 }
|
||||
</div>
|
||||
%endif
|
||||
</div>
|
||||
|
||||
<main id="main" aria-label="Content" tabindex="-1">
|
||||
|
||||
@@ -52,7 +52,7 @@ edx-lint==0.4.3
|
||||
astroid==1.3.8
|
||||
edx-django-oauth2-provider==1.1.4
|
||||
edx-django-sites-extensions==2.1.1
|
||||
edx-enterprise==0.27.6
|
||||
edx-enterprise==0.28.0
|
||||
edx-oauth2-provider==1.2.0
|
||||
edx-opaque-keys==0.4.0
|
||||
edx-organizations==0.4.3
|
||||
|
||||
Reference in New Issue
Block a user