AA-287 cta events
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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. Don’t worry, we’ll shift all the due dates for you and you won’t 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
|
||||
|
||||
Reference in New Issue
Block a user