AA-287 cta events

This commit is contained in:
Nicholas D'Alfonso
2020-08-07 16:52:01 -04:00
parent 07f96f2bba
commit 0211a2b0ff
6 changed files with 110 additions and 64 deletions

View File

@@ -9,13 +9,13 @@ from course_modes.models import CourseMode
%>
<%
additional_styling_class = 'on-mobile' if is_mobile_app and not is_learning_mfe else 'has-button'
additional_styling_class = 'on-mobile' if is_mobile_app else 'has-button'
%>
<%def name="reset_dates_banner()">
<div class="banner-cta ${additional_styling_class}">
<div class="banner-cta-text">
% if is_mobile_app and not is_learning_mfe:
% if is_mobile_app:
${_('It looks like you missed some important deadlines based on our suggested schedule. ')}
${_('To keep yourself on track, you can update this schedule and shift the past due assignments into the future by visiting ')}
<a class="mobile-dates-link" href="${web_app_course_url}">edx.org</a>.
@@ -25,12 +25,7 @@ additional_styling_class = 'on-mobile' if is_mobile_app and not is_learning_mfe
${_("To keep yourself on track, you can update this schedule and shift the past due assignments into the future. Don't worry—you won't lose any of the progress you've made when you shift your due dates.")}
% endif
</div>
% if not is_mobile_app or is_learning_mfe:
% if is_learning_mfe:
<div class="banner-cta-button">
<button class="btn" onclick="reset_dates()">${_("Shift due dates")}</button>
</div>
% else:
% if not is_mobile_app:
<div class="banner-cta-button">
<form method="post" action="${reset_deadlines_url}">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
@@ -38,14 +33,13 @@ additional_styling_class = 'on-mobile' if is_mobile_app and not is_learning_mfe
<button class="btn">${_("Shift due dates")}</button>
</form>
</div>
% endif
% endif
</div>
</%def>
<%def name="upgrade_to_reset_banner()">
<div class="banner-cta ${additional_styling_class}">
<div class="banner-cta-text">
% if is_mobile_app and not is_learning_mfe:
% if is_mobile_app:
<strong>${_('You are auditing this course,')}</strong>
${_(' which means that you are unable to participate in graded assignments.')}
${_(' It looks like you missed some important deadlines based on our suggested schedule. Graded assignments and schedule adjustment are available to Verified Track learners.')}
@@ -55,7 +49,7 @@ additional_styling_class = 'on-mobile' if is_mobile_app and not is_learning_mfe
${_(' It looks like you missed some important deadlines based on our suggested schedule. To complete graded assignments as part of this course and shift the past due assignments into the future, you can upgrade today.')}
% endif
</div>
% if not is_mobile_app or is_learning_mfe:
% if not is_mobile_app:
<div class="banner-cta-button">
<a class="personalized_learner_schedules_button" href="${verified_upgrade_link}">
<button type="button">
@@ -69,7 +63,7 @@ additional_styling_class = 'on-mobile' if is_mobile_app and not is_learning_mfe
<%def name="upgrade_to_complete_graded_banner()">
<div class="banner-cta ${additional_styling_class}">
<div class="banner-cta-text">
% if is_mobile_app and not is_learning_mfe:
% if is_mobile_app:
<strong>${_('You are auditing this course,')}</strong>
${_(' which means that you are unable to participate in graded assignments.')}
${_('Graded assignments are available to Verified Track learners.')}
@@ -79,7 +73,7 @@ additional_styling_class = 'on-mobile' if is_mobile_app and not is_learning_mfe
${_(' To complete graded assignments as part of this course, you can upgrade today.')}
% endif
</div>
% if not is_mobile_app or is_learning_mfe:
% if not is_mobile_app:
<div class="banner-cta-button">
<a class="personalized_learner_schedules_button" href="${verified_upgrade_link}">
<button type="button">
@@ -112,11 +106,3 @@ additional_styling_class = 'on-mobile' if is_mobile_app and not is_learning_mfe
% endif
% endif
% endif
% if is_learning_mfe:
<script>
function reset_dates() {
parent.postMessage('reset_dates', '*');
}
</script>
% endif

View File

@@ -32,12 +32,8 @@ from openedx.core.djangolib.markup import HTML
</button>
% if submit_disabled_cta:
<form class="submit-cta" method="post" action="${submit_disabled_cta['link']}">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
% for form_name, form_value in submit_disabled_cta['form_values'].items():
<input type="hidden" name="${form_name}" value="${form_value}">
% endfor
<button class="submit-cta-link-button btn-brand">${submit_disabled_cta['link_name']}
% if submit_disabled_cta.get('event_data'):
<button class="submit-cta-link-button btn-brand" onclick="emit_event(${submit_disabled_cta['event_data']})">${submit_disabled_cta['link_name']}
<span class="submit-cta-description" tabindex="0" role="note" aria-label="description">
<span data-tooltip="${submit_disabled_cta['description']}" data-tooltip-show-on-click="true"
class="fa fa-info-circle fa-lg" aria-hidden="true">
@@ -45,7 +41,22 @@ from openedx.core.djangolib.markup import HTML
</span>
<span class="sr">(${submit_disabled_cta['description']})</span>
</button>
</form>
% else:
<form class="submit-cta" method="post" action="${submit_disabled_cta['link']}">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
% for form_name, form_value in submit_disabled_cta['form_values'].items():
<input type="hidden" name="${form_name}" value="${form_value}">
% endfor
<button class="submit-cta-link-button btn-brand">${submit_disabled_cta['link_name']}
<span class="submit-cta-description" tabindex="0" role="note" aria-label="description">
<span data-tooltip="${submit_disabled_cta['description']}" data-tooltip-show-on-click="true"
class="fa fa-info-circle fa-lg" aria-hidden="true">
</span>
</span>
<span class="sr">(${submit_disabled_cta['description']})</span>
</button>
</form>
% endif
% endif
<div class="submission-feedback" id="submission_feedback_${short_id}">
% if attempts_allowed:
@@ -143,3 +154,9 @@ from openedx.core.djangolib.markup import HTML
is_hidden=True"
/>
</div>
<script>
function emit_event(message) {
parent.postMessage(message, '*');
}
</script>

View File

@@ -34,15 +34,21 @@ from openedx.core.djangolib.markup import HTML
% for vertical_banner_cta in vertical_banner_ctas:
<div class="banner-cta has-button">
<div class="banner-cta-text">${vertical_banner_cta['description']}</div>
<div class="banner-cta-button">
<form method="post" action="${vertical_banner_cta['link']}">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
% for form_name, form_value in vertical_banner_cta['form_values'].items():
<input type="hidden" name="${form_name}" value="${form_value}">
% endfor
<button class="btn">${vertical_banner_cta['link_name']}</button>
</form>
</div>
% if vertical_banner_cta.get('event_data'):
<div class="banner-cta-button">
<button class="btn" onclick="emit_event(${vertical_banner_cta['event_data']})">${vertical_banner_cta['link_name']}</button>
</div>
% else:
<div class="banner-cta-button">
<form method="post" action="${vertical_banner_cta['link']}">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
% for form_name, form_value in vertical_banner_cta['form_values'].items():
<input type="hidden" name="${form_name}" value="${form_value}">
% endfor
<button class="btn">${vertical_banner_cta['link_name']}</button>
</form>
</div>
% endif
</div>
% endfor
% endif
@@ -60,3 +66,9 @@ from openedx.core.djangolib.markup import HTML
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
DateUtilFactory.transform('.localized-datetime');
</%static:require_module_async>
<script>
function emit_event(message) {
parent.postMessage(message, '*');
}
</script>

View File

@@ -30,7 +30,14 @@ class CallToActionService(PluginManager):
'foo': 'bar',
},
# A long-form description to be associated with the CTA
'description': "If you don't want to do this problem, just skip it!"
'description': "If you don't want to do this problem, just skip it!",
# A data set we include if the CTA is being rendered within an iframe. For example,
# we do this in Learning MFE. This dictionary is passed to its's parent container via
# parent.postMessage. Parent containers should use window.onmessage event handler to
# catch this dataset.
'event_data': {
'foo': 'bar',
},
}]
Note: Future versions of this class may add a way to control the CTA method (POST vs GET),

View File

@@ -1,3 +1,10 @@
import six
from django.conf import settings
from django.urls import reverse
from django.utils.html import format_html
from django.utils.translation import ugettext as _
from rest_framework.decorators import api_view, authentication_classes, permission_classes
from rest_framework.exceptions import APIException, ParseError
from rest_framework.permissions import IsAuthenticated
@@ -7,6 +14,8 @@ from rest_framework.generics import RetrieveAPIView
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from lms.djangoapps.course_home_api.toggles import course_home_mfe_dates_tab_is_active
from lms.djangoapps.course_home_api.utils import get_microfrontend_url
from lms.djangoapps.courseware.courses import get_course_with_access
from opaque_keys.edx.keys import CourseKey
@@ -31,15 +40,28 @@ def reset_course_deadlines(request):
# If body doesnt contain 'course_key', return 400 to client.
if not course_key:
raise ParseError("'course_key' is required.")
raise ParseError(_("'course_key' is required."))
# If body contains params other than 'course_key', return 400 to client.
if len(request.data) > 1:
raise ParseError("Only 'course_key' is expected.")
raise ParseError(_("Only 'course_key' is expected."))
try:
reset_self_paced_schedule(request.user, course_key)
return Response({'message': 'Deadlines successfully reset.'})
key = CourseKey.from_string(course_key)
if course_home_mfe_dates_tab_is_active(key):
body_link = get_microfrontend_url(course_key=course_key, view_name='dates')
else:
body_link = '{}{}'.format(settings.LMS_ROOT_URL, reverse('dates', args=[six.text_type(course_key)]))
return Response({
'body': format_html('<a href="{}">{}</a>', body_link, _('View all dates')),
'header': format_html(
'<div>{}</div>', _('Your due dates have been successfully shifted to help you stay on track.')
),
'message': _('Deadlines successfully reset.'),
})
except Exception:
raise UnableToResetDeadlines

View File

@@ -1,11 +1,13 @@
import logging
from crum import get_current_request
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
from django.conf import settings
from django.urls import reverse
from django.utils.translation import gettext as _
from lms.djangoapps.course_home_api.utils import is_request_from_learning_mfe
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
log = logging.getLogger(__name__)
@@ -20,22 +22,7 @@ class PersonalizedLearnerScheduleCallToAction:
"""
Return the calls to action associated with the specified category for the given xblock.
See the CallToActionService class constants for a list of recognized categories.
Returns: list of dictionaries, describing the calls to action, with the following keys:
link, link_name, form_values, and description.
If the category is not recognized, an empty list is returned.
An example of a returned list:
[{
'link': 'localhost:18000/skip', # A link to POST to when the Call To Action is taken
'link_name': 'Skip this Problem', # The name of the action
'form_values': { # Any parameters to include with the CTA
'foo': 'bar',
},
# A long-form description to be associated with the CTA
'description': "If you don't want to do this problem, just skip it!"
}]
Look at CallToActionService docstring to see what will be returned.
"""
ctas = []
@@ -43,20 +30,20 @@ class PersonalizedLearnerScheduleCallToAction:
request = get_current_request()
is_mobile_app = request and is_request_from_mobile_app(request)
is_learning_mfe = request and is_request_from_learning_mfe(request)
if is_mobile_app or is_learning_mfe:
if is_mobile_app:
return []
if category == self.CAPA_SUBMIT_DISABLED:
# xblock is a capa problem, and the submit button is disabled. Check if it's because of a personalized
# schedule due date being missed, and if so, we can offer to shift it.
if self._is_block_shiftable(xblock):
ctas.append(self._make_reset_deadlines_cta(xblock))
ctas.append(self._make_reset_deadlines_cta(xblock, is_learning_mfe))
elif category == self.VERTICAL_BANNER:
# xblock is a vertical, so we'll check all the problems inside it. If there are any that will show a
# a "shift dates" CTA under CAPA_SUBMIT_DISABLED, then we'll also show the same CTA as a vertical banner.
if any(self._is_block_shiftable(item) for item in xblock.get_display_items()):
ctas.append(self._make_reset_deadlines_cta(xblock))
ctas.append(self._make_reset_deadlines_cta(xblock, is_learning_mfe))
return ctas
@@ -94,15 +81,30 @@ class PersonalizedLearnerScheduleCallToAction:
PersonalizedLearnerScheduleCallToAction.past_due_class_warnings.add(name)
@staticmethod
def _make_reset_deadlines_cta(xblock):
def _make_reset_deadlines_cta(xblock, is_learning_mfe=False):
from lms.urls import RESET_COURSE_DEADLINES_NAME
return {
course_key = xblock.scope_ids.usage_id.context_key
cta_data = {
'link': reverse(RESET_COURSE_DEADLINES_NAME),
'link_name': _('Shift due dates'),
'form_values': {
'course_id': xblock.scope_ids.usage_id.context_key,
'course_id': course_key,
},
'description': _('To participate in this assignment, the suggested schedule for your course needs '
'updating. Dont worry, well shift all the due dates for you and you wont lose '
'any of your progress.'),
}
if is_learning_mfe:
cta_data['event_data'] = {
'event_name': 'post_event',
'post_data': {
'body_params': {
'course_id': str(course_key),
},
'url': '{}{}'.format(settings.LMS_ROOT_URL, reverse('course-experience-reset-course-deadlines')),
},
}
return cta_data