Limit course outline to a depth of two

Previously, we'd show three layers deep (section, subsection, and
unit). But now we cut out unit and stop at subsection.

AA-17
This commit is contained in:
Michael Terry
2020-01-30 11:14:36 -05:00
parent f09e9fdc57
commit ea92073e08
5 changed files with 71 additions and 165 deletions

View File

@@ -308,6 +308,35 @@
padding: 0;
list-style-type: none;
// Course outline accordion / link
.outline-button {
display: block;
padding: 10px 0 10px 2px;
border: none;
width: 100%;
text-align: left;
margin: 0;
background: none;
cursor: pointer;
.fa {
color: $blue;
}
.prerequisites-icon {
margin-right: 0.25rem;
}
.complete-checkmark {
border: 1px solid $green;
margin-right: $baseline / 2;
border-radius: 100px;
color: white;
background-color: $green;
float: right;
}
}
.section {
@include media-breakpoint-up(md) {
margin: 0;
@@ -346,7 +375,6 @@
display: inline-block;
margin: 0;
font-weight: $font-regular;
margin-left: $baseline - 2;
display: inline;
}
@@ -354,7 +382,6 @@
.details {
font-size: $body-font-size;
color: theme-color("secondary");
margin-left: 34px;
}
.prerequisite {
@@ -362,72 +389,12 @@
font-weight: $font-bold;
}
}
.vertical {
@include margin-left(16px);
list-style-type: none;
a.outline-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: ($baseline / 2) 0 ($baseline / 2) 0;
margin: 0 0 0 ($baseline);
border-top: 1px solid $border-color;
}
a.outline-item:hover {
text-decoration: none;
.vertical-details {
text-decoration: underline;
}
.complete-checkmark {
text-decoration: none;
}
}
.vertical-details {
float:left;
}
&:hover,
&:focus {
background-color: palette(primary, x-back);
border-radius: $btn-border-radius;
text-decoration: none;
}
.vertical-actions {
.resume-right {
position: relative;
top: calc(50% - (#{$baseline} / 2));
}
}
}
}
}
}
}
}
// Course outline accordion
button.accordion-trigger, button.prerequisite-button {
padding: 10px 0 10px 2px;
border: none;
width: 100%;
text-align: left;
margin: 0;
background: none;
cursor: pointer;
.fa {
color: $blue;
}
}
#expand-collapse-outline-all-button {
float: right;
background-color: $white;
@@ -444,15 +411,6 @@ button.accordion-trigger, button.prerequisite-button {
display: none;
}
.fa-check.fa.complete-checkmark {
border: 1px solid $green;
margin-right: $baseline / 2;
border-radius: 100px;
color: white;
background-color: $green;
float: right;
}
// date summary
.date-summary-container {
.date-summary {

View File

@@ -35,7 +35,7 @@ self_paced = context.get('self_paced', False)
scored = 'scored' if section.get('scored', False) else ''
%>
<li class="outline-item section ${scored}">
<button class="section-name accordion-trigger"
<button class="section-name accordion-trigger outline-button"
aria-expanded="${ 'true' if section_is_auto_opened else 'false' }"
aria-controls="${ section['id'] }_contents"
id="${ section['id'] }">
@@ -52,23 +52,30 @@ self_paced = context.get('self_paced', False)
% for subsection in section.get('children', []):
<%
gated_subsection = subsection['id'] in gated_content
completed_prereqs = gated_content[subsection['id']]['completed_prereqs'] if gated_subsection else False
subsection_is_auto_opened = subsection.get('resume_block') is True
needs_prereqs = not gated_content[subsection['id']]['completed_prereqs'] if gated_subsection else False
scored = 'scored' if subsection.get('scored', False) else ''
graded = 'graded' if subsection.get('graded') else ''
%>
<li class="subsection accordion ${ 'current' if subsection.get('resume_block') else '' } ${graded} ${scored}">
% if gated_subsection and not completed_prereqs:
<a href="${ subsection['lms_web_url'] }">
<button class="subsection-text prerequisite-button"
id="${ subsection['id'] }">
<span class="menu-icon icon fa fa-lock"
aria-hidden="true">
</span>
<h4 class="subsection-title">
${ subsection['display_name'] }
</h4>
<li class="subsection accordion ${ 'current' if subsection.get('resume_block') else '' } ${graded} ${scored}">
<a
% if enable_links:
href="${ subsection['lms_web_url'] }"
% else:
aria-disabled="true"
% endif
class="subsection-text outline-button"
id="${ subsection['id'] }"
>
<h4 class="subsection-title">
${ subsection['display_name'] }
</h4>
% if subsection.get('complete'):
<span class="complete-checkmark fa fa-check" aria-hidden="true"></span>
<span class="sr">${_("Completed")}</span>
% endif
% if needs_prereqs:
<div class="details prerequisite">
<span class="prerequisites-icon icon fa fa-lock" aria-hidden="true"></span>
${ _("Prerequisite: ") }
<%
prerequisite_id = gated_content[subsection['id']]['prerequisite']
@@ -76,21 +83,7 @@ self_paced = context.get('self_paced', False)
%>
${ prerequisite_name }
</div>
% else:
<button class="subsection-text accordion-trigger"
id="${ subsection['id'] }"
aria-expanded="${ 'true' if subsection_is_auto_opened else 'false' }"
aria-controls="${ subsection['id'] }_contents">
<span class="fa fa-chevron-right ${ 'fa-rotate-90' if subsection_is_auto_opened else '' }"
aria-hidden="true"></span>
<h4 class="subsection-title">
${ subsection['display_name'] }
</h4>
% if subsection.get('complete'):
<span class="complete-checkmark fa fa-check" aria-hidden="true"></span>
<span class="sr">${_("Completed")}</span>
% endif
% endif
% endif
<div class="details">
## There are behavior differences between rendering of subsections which have
@@ -150,47 +143,8 @@ self_paced = context.get('self_paced', False)
</span>
% endif
</div> <!-- /details -->
</button> <!-- /subsection-text -->
% if gated_subsection and not completed_prereqs:
</a>
% endif
% if not gated_subsection or (gated_subsection and completed_prereqs):
<ol class="outline-item accordion-panel ${ '' if subsection_is_auto_opened else 'is-hidden' }"
id="${ subsection['id'] }_contents"
aria-labelledby="${ subsection['id'] }"
>
% for vertical in subsection.get('children', []):
<%
vertical_is_access_denied = vertical.get('authorization_denial_reason')
if vertical_is_access_denied:
continue
scored = 'scored' if vertical.get('scored', False) else ''
access_restricted = list(vertical.get('all_denial_reasons', []))
%>
<li class="vertical outline-item focusable ${scored}" data-access-denied="${json.dumps(access_restricted)}">
<a class="outline-item focusable"
% if enable_links:
href="${ vertical['lms_web_url'] }"
% else:
aria-disabled="true"
% endif
id="${ vertical['id'] }">
<div class="vertical-details">
<div class="vertical-title">
${ vertical['display_name'] }
</div>
</div>
% if vertical.get('complete'):
<span class="complete-checkmark fa fa-check" aria-hidden="true"></span>
<span class="sr">${_("Completed")}</span>
% endif
</a>
</li>
% endfor
</ol>
% endif
</li>
</a>
</li>
% endfor
</ol>
</li>

View File

@@ -131,8 +131,6 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
self.assertContains(response, sequential.due.strftime(u'%Y-%m-%d %H:%M:%S'))
self.assertContains(response, sequential.format)
self.assertTrue(sequential.children)
for vertical in sequential.children:
self.assertContains(response, vertical.display_name)
class TestCourseOutlinePageWithPrerequisites(SharedModuleStoreTestCase, MilestonesTestCaseMixin):
@@ -318,14 +316,22 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT
course = CourseFactory.create()
with cls.store.bulk_operations(course.id):
chapter = ItemFactory.create(category='chapter', parent_location=course.location)
chapter2 = ItemFactory.create(category='chapter', parent_location=course.location)
sequential = ItemFactory.create(category='sequential', parent_location=chapter.location)
sequential2 = ItemFactory.create(category='sequential', parent_location=chapter.location)
sequential3 = ItemFactory.create(category='sequential', parent_location=chapter2.location)
sequential4 = ItemFactory.create(category='sequential', parent_location=chapter2.location)
vertical = ItemFactory.create(category='vertical', parent_location=sequential.location)
vertical2 = ItemFactory.create(category='vertical', parent_location=sequential2.location)
course.children = [chapter]
vertical3 = ItemFactory.create(category='vertical', parent_location=sequential3.location)
vertical4 = ItemFactory.create(category='vertical', parent_location=sequential4.location)
course.children = [chapter, chapter2]
chapter.children = [sequential, sequential2]
chapter2.children = [sequential3, sequential4]
sequential.children = [vertical]
sequential2.children = [vertical2]
sequential3.children = [vertical3]
sequential4.children = [vertical4]
if hasattr(cls, 'user'):
CourseEnrollment.enroll(cls.user, course.id)
return course
@@ -407,8 +413,8 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT
response = self.client.get(course_home_url(course))
content = pq(response.content)
# vertical and its parent should be checked
self.assertEqual(len(content('.fa-check')), 2)
# Subsection should be checked
self.assertEqual(len(content('.fa-check')), 1)
def test_start_course(self):
"""
@@ -434,7 +440,6 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT
# Course tree
course = self.course
course_key = CourseKey.from_string(str(course.id))
vertical1 = course.children[0].children[0].children[0]
vertical2 = course.children[0].children[1].children[0]
@@ -534,9 +539,9 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT
)
def test_course_outline_auto_open(self):
"""
Tests that the course outline auto-opens to the first unit
Tests that the course outline auto-opens to the first subsection
in a course if a user has no completion data, and to the
last-accessed unit if a user does have completion data.
last-accessed subsection if a user does have completion data.
"""
def get_sequential_button(url, is_hidden):
is_hidden_string = "is-hidden" if is_hidden else ""
@@ -547,15 +552,14 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT
">"
# Course tree
course = self.course
chapter = course.children[0]
sequential1 = chapter.children[0]
sequential2 = chapter.children[1]
chapter1 = course.children[0]
chapter2 = course.children[1]
response_content = self.client.get(course_home_url(course)).content
stripped_response = text_type(re.sub(b"\\s+", b"", response_content), "utf-8")
self.assertTrue(get_sequential_button(text_type(sequential1.location), False) in stripped_response)
self.assertTrue(get_sequential_button(text_type(sequential2.location), True) in stripped_response)
self.assertIn(get_sequential_button(text_type(chapter1.location), False), stripped_response)
self.assertIn(get_sequential_button(text_type(chapter2.location), True), stripped_response)
content = pq(response_content)
button = content('#expand-collapse-outline-all-button')

View File

@@ -4,22 +4,12 @@ Common utilities for the course experience, including course outline.
from completion.models import BlockCompletion
from django.utils.translation import ugettext as _
from opaque_keys.edx.keys import CourseKey
from six.moves import range
from web_fragments.fragment import Fragment
from lms.djangoapps.course_api.blocks.api import get_blocks
from lms.djangoapps.course_blocks.utils import get_student_module_as_dict
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
from openedx.core.djangolib.markup import HTML
from openedx.core.lib.cache_utils import request_cached
from openedx.features.discounts.applicability import (
can_receive_discount,
get_discount_expiration_date,
discount_percentage
)
from openedx.features.discounts.utils import format_strikeout_price
from xmodule.modulestore.django import modulestore

View File

@@ -2,6 +2,6 @@
set -e
export LOWER_PYLINT_THRESHOLD=1000
export UPPER_PYLINT_THRESHOLD=2100
export UPPER_PYLINT_THRESHOLD=2005
export ESLINT_THRESHOLD=5530
export STYLELINT_THRESHOLD=880