From 92c816966db64e3dcbe11445abfdbc5b4bc22aa3 Mon Sep 17 00:00:00 2001 From: Michael Roytman <mroytman@edx.org> Date: Tue, 26 Jun 2018 15:11:21 -0400 Subject: [PATCH] change course_validation endpoint to support option to get data for only graded assignments and add ability to scroll to elements on the course outline page --- .../contentstore/api/tests/test_validation.py | 8 +-- .../api/views/course_validation.py | 59 +++++++++++++------ cms/static/js/views/pages/course_outline.js | 38 ++++++++++++ cms/templates/js/course-outline.underscore | 2 +- 4 files changed, 85 insertions(+), 22 deletions(-) diff --git a/cms/djangoapps/contentstore/api/tests/test_validation.py b/cms/djangoapps/contentstore/api/tests/test_validation.py index acaa4dc0c68..87142cb3bec 100644 --- a/cms/djangoapps/contentstore/api/tests/test_validation.py +++ b/cms/djangoapps/contentstore/api/tests/test_validation.py @@ -77,11 +77,10 @@ class CourseValidationViewTest(SharedModuleStoreTestCase, APITestCase): self.assertEqual(resp.status_code, status.HTTP_200_OK) expected_data = { 'assignments': { - 'num_with_dates_before_end': 0, - 'num_with_dates': 0, - 'total_visible': 1, - 'num_with_dates_after_start': 0, 'total_number': 1, + 'total_visible': 1, + 'assignments_with_dates_before_start': [], + 'assignments_with_dates_after_end': [], }, 'dates': { 'has_start_date': True, @@ -99,4 +98,5 @@ class CourseValidationViewTest(SharedModuleStoreTestCase, APITestCase): }, 'is_self_paced': True, } + self.assertDictEqual(resp.data, expected_data) diff --git a/cms/djangoapps/contentstore/api/views/course_validation.py b/cms/djangoapps/contentstore/api/views/course_validation.py index f6c68e9e17c..b0b7ee35329 100644 --- a/cms/djangoapps/contentstore/api/views/course_validation.py +++ b/cms/djangoapps/contentstore/api/views/course_validation.py @@ -33,6 +33,7 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): * grades * certificates * updates + * graded_only (boolean) - whether to included graded subsections only in the assignments information. **GET Response Values** @@ -45,9 +46,8 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): * assignments * total_number - total number of assignments in the course. * total_visible - number of assignments visible to learners in the course. - * num_with_dates - number of assignments with due dates. - * num_with_dates_after_start - number of assignments with due dates after the start date. - * num_with_dates_before_end - number of assignments with due dates before the end date. + * assignments_with_dates_before_start - assignments with due dates before the start date. + * assignments_with_dates_after_end - assignments with due dates after the end date. * grades * sum_of_weights - sum of weights for all assignments in the course (valid ones should equal 1). * certificates @@ -77,7 +77,7 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): ) if get_bool_param(request, 'assignments', all_requested): response.update( - assignments=self._assignments_validation(course) + assignments=self._assignments_validation(course, request) ) if get_bool_param(request, 'grades', all_requested): response.update( @@ -106,28 +106,54 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): has_end_date=course.end is not None, ) - def _assignments_validation(self, course): + def _assignments_validation(self, course, request): assignments, visible_assignments = self._get_assignments(course) - assignments_with_dates = [a for a in visible_assignments if a.due] + assignments_with_dates = [ + a for a in visible_assignments if a.due + ] - num_with_dates = len(assignments_with_dates) - num_with_dates_after_start = ( - len([a for a in assignments_with_dates if a.due > course.start]) + assignments_with_dates_before_start = ( + [ + {'id': unicode(a.location), 'display_name': a.display_name} + for a in assignments_with_dates + if a.due < course.start + ] if self._has_start_date(course) - else 0 + else [] ) - num_with_dates_before_end = ( - len([a for a in assignments_with_dates if a.due < course.end]) + + assignments_with_dates_after_end = ( + [ + {'id': unicode(a.location), 'display_name': a.display_name} + for a in assignments_with_dates + if a.due > course.end + ] if course.end - else 0 + else [] ) + if get_bool_param(request, 'graded_only', False): + assignments_with_dates = [ + a + for a in visible_assignments + if a.due and a.graded + ] + + assignments_with_dates_before_start = [ + {'id': unicode(a.location), 'display_name': a.display_name} + for a in assignments_with_dates if a.due < course.start + ] + + assignments_with_dates_after_end = [ + {'id': unicode(a.location), 'display_name': a.display_name} + for a in assignments_with_dates if a.due > course.end + ] + return dict( total_number=len(assignments), total_visible=len(visible_assignments), - num_with_dates=num_with_dates, - num_with_dates_after_start=num_with_dates_after_start, - num_with_dates_before_end=num_with_dates_before_end, + assignments_with_dates_before_start=assignments_with_dates_before_start, + assignments_with_dates_after_end=assignments_with_dates_after_end, ) def _grades_validation(self, course): @@ -158,7 +184,6 @@ class CourseValidationView(DeveloperErrorViewMixin, GenericAPIView): for section in sections for assignment_usage_key in section.children ] - visible_sections = [ s for s in sections if not s.visible_to_staff_only and not s.hide_from_toc diff --git a/cms/static/js/views/pages/course_outline.js b/cms/static/js/views/pages/course_outline.js index 7e65dd8e3d4..00335e92a39 100644 --- a/cms/static/js/views/pages/course_outline.js +++ b/cms/static/js/views/pages/course_outline.js @@ -18,6 +18,18 @@ define([ 'click .button-toggle-expand-collapse': 'toggleExpandCollapse' }, + /** + * keep a running timeout counter of 5,000 milliseconds + * for finding an element; see afterRender and scrollToElement function + */ + findElementPollingTimeout: 5000, + + /** + * used as the delay parameter to setTimeout in scrollToElement + * function for polling for an element + */ + pollingDelay: 100, + options: { collapsedClass: 'is-collapsed' }, @@ -90,6 +102,32 @@ define([ return $.Deferred().resolve().promise(); }, + afterRender: function() { + this.scrollToElement(); + }, + + /** + * recursively poll for element specified by the URL fragment + * at 100 millisecond intervals until element is found or + * Polling is reached + */ + scrollToElement: function () { + this.findElementPollingTimeout -= this.pollingDelay; + + const elementID = window.location.hash.replace("#", ""); + + if (this.findElementPollingTimeout > 0) { + if (elementID) { + const element = document.getElementById(elementID); + if (element) { + element.scrollIntoView(); + } else { + setTimeout(this.scrollToElement, this.pollingDelay); + } + } + } + }, + hasContent: function() { return this.model.hasChildren(); }, diff --git a/cms/templates/js/course-outline.underscore b/cms/templates/js/course-outline.underscore index 5599a001812..13aadc7c2e5 100644 --- a/cms/templates/js/course-outline.underscore +++ b/cms/templates/js/course-outline.underscore @@ -97,7 +97,7 @@ if (is_proctored_exam) { %> <% if (parentInfo) { %> <li class="outline-item outline-<%- xblockType %> <%- visibilityClass %> is-draggable <%- includesChildren ? 'is-collapsible' : '' %> <%- isCollapsed ? 'is-collapsed' : '' %>" - data-parent="<%- parentInfo.get('id') %>" data-locator="<%- xblockInfo.get('id') %>"> + data-parent="<%- parentInfo.get('id') %>" data-locator="<%- xblockInfo.get('id') %>" id="<%- xblockInfo.get('id') %>"> <span class="draggable-drop-indicator draggable-drop-indicator-before"><span class="icon fa fa-caret-right" aria-hidden="true"></span></span> <% if (xblockInfo.isHeaderVisible()) { %> -- GitLab