feat: added another 'Date' expose-headers for outline api clients (#27221)

Exposed the Date header on the outline api so clients can accurately compute times relative to the dates returned by the API; this was previously done with the course API (#26979)

Browser time is notoriously unreliable for this, especially for a Learner-facing countdown call-to-action based on the access expiration date. (REV-2126)

Using the Date header for this allows the client to make use of information that is already sent, does not require additional calls nor modifying the API, and could be generalized to more or all our APIs without modifying them.
This commit is contained in:
Ben Holt
2021-04-02 10:37:19 -04:00
committed by GitHub
parent c4da6c1fe4
commit ec3c31eb05
3 changed files with 34 additions and 5 deletions

View File

@@ -213,3 +213,13 @@ def reset_course_deadlines(request):
referrer = request.META.get('HTTP_REFERER')
return redirect(referrer) if referrer else HttpResponse()
def expose_header(header, response):
"""
Add a header name to Access-Control-Expose-Headers to allow client code to access that header's value
"""
exposedHeaders = response.get('Access-Control-Expose-Headers', '')
exposedHeaders += f', {header}' if exposedHeaders else header
response['Access-Control-Expose-Headers'] = exposedHeaders
return response

View File

@@ -19,6 +19,7 @@ from rest_framework.response import Response
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.util.views import expose_header
from lms.djangoapps.course_goals.api import (
add_course_goal,
get_course_goal,
@@ -295,6 +296,20 @@ class OutlineTabView(RetrieveAPIView):
return Response(serializer.data)
def finalize_response(self, request, response, *args, **kwargs):
"""
Return the final response, exposing the 'Date' header for computing relative time to the dates in the data.
Important dates such as 'access_expiration' are enforced server-side based on correct time; client-side clocks
are frequently substantially far off which could lead to inaccurate messaging and incorrect expectations.
Therefore, any messaging about those dates should be based on the server time and preferably in relative terms
(time remaining); the 'Date' header is a straightforward and generalizable way for client-side code to get this
reference.
"""
response = super().finalize_response(request, response, *args, **kwargs)
# Adding this header should be moved to global middleware, not just this endpoint
return expose_header('Date', response)
@api_view(['POST'])
@authentication_classes((JwtAuthentication,))

View File

@@ -19,6 +19,7 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.util.views import expose_header
from lms.djangoapps.edxnotes.helpers import is_feature_enabled
from lms.djangoapps.certificates.api import get_certificate_url
from lms.djangoapps.certificates.models import GeneratedCertificate
@@ -444,13 +445,16 @@ class CoursewareInformation(RetrieveAPIView):
def finalize_response(self, request, response, *args, **kwargs):
"""
Return the final response, exposing the 'Date' header for computing relative time to the dates in the data.
Important dates such as 'access_expiration' are enforced server-side based on correct time; client-side clocks
are frequently substantially far off which could lead to inaccurate messaging and incorrect expectations.
Therefore, any messaging about those dates should be based on the server time and preferably in relative terms
(time remaining); the 'Date' header is a straightforward and generalizable way for client-side code to get this
reference.
"""
response = super().finalize_response(request, response, *args, **kwargs)
# Adding this header should be moved somewhere global, not just this endpoint
exposedHeaders = response.get('Access-Control-Expose-Headers', '')
exposedHeaders += ', Date' if exposedHeaders else 'Date'
response['Access-Control-Expose-Headers'] = exposedHeaders
return response
# Adding this header should be moved to global middleware, not just this endpoint
return expose_header('Date', response)
class SequenceMetadata(DeveloperErrorViewMixin, APIView):