feat!: drop legacy course home view and related code

This was the "outline tab" view of the course. Preceded by the
course info view, succeeded by the MFE outline tab.

In addition to the course home view itself, this drops related
features:
- Legacy version of Course Goals (MFE has a newer implementation)
- Course home in-course search (MFE has no search)

The old course info view and course about views survive for now.

This also drops a few now-unused feature toggles:
- course_experience.latest_update
- course_experience.show_upgrade_msg_on_course_home
- course_experience.upgrade_deadline_message
- course_home.course_home_use_legacy_frontend

With this change, just the progress and courseware tabs are still
supported in legacy form, if you opt-in with waffle flags. The
outline and dates tabs are offered only by the MFE.

AA-798

(This is identical to previous commit be5c1a6, just reintroduced
now that the e2e tests have been fixed)
This commit is contained in:
Michael Terry
2022-03-15 09:32:14 -04:00
parent 584f400ca8
commit ce5f1bb343
86 changed files with 194 additions and 5747 deletions

View File

@@ -104,10 +104,8 @@ class CourseModeViewTest(CatalogIntegrationMixin, UrlResetMixin, ModuleStoreTest
# Check whether we were correctly redirected
if redirect:
if has_started:
self.assertRedirects(
response, reverse('openedx.course_experience.course_home', kwargs={'course_id': course.id}),
target_status_code=302, # for follow-on redirection to MFE (ideally we'd just be sent there first)
)
mfe_url = f'http://learning-mfe/course/{course.id}/home'
self.assertRedirects(response, mfe_url, fetch_redirect_response=False)
else:
self.assertRedirects(response, reverse('dashboard'))
else:

View File

@@ -40,6 +40,7 @@ from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from openedx.features.course_duration_limits.access import get_user_course_duration, get_user_course_expiration_date
from openedx.features.course_experience import course_home_url
from openedx.features.enterprise_support.api import enterprise_customer_for_request
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.util.db import outer_atomic
@@ -413,7 +414,7 @@ class ChooseModeView(View):
302 to the course if possible or the dashboard if not.
"""
if course.has_started() or user.is_staff:
return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key}))
return redirect(course_home_url(course_key))
else:
return redirect(reverse('dashboard'))

View File

@@ -1,57 +0,0 @@
/**
* Used to ellipsize a section of arbitrary HTML after a specified number of words.
*
* Note: this will modify the DOM structure of root in place.
* To keep the original around, you may want to save the result of cloneNode(true) before calling this method.
*
* Known bug: This method will ignore any special whitespace in the source and simply output single spaces.
* Which means that   will not be respected. This is not considered worth solving at time of writing.
*
* Returns how many words remain (or a negative number if the content got clamped)
*/
function clampHtmlByWords(root, wordsLeft) {
'use strict';
if (root.nodeName === 'SCRIPT' || root.nodeName === 'STYLE') {
return wordsLeft; // early exit and ignore
}
var remaining = wordsLeft;
var nodes = Array.from(root.childNodes ? root.childNodes : []);
var words, chopped;
// First, cut short any text in our node, as necessary
if (root.nodeName === '#text' && root.data) {
// split on words, ignoring any resulting empty strings
words = root.data.split(/\s+/).filter(Boolean);
if (remaining < 0) {
root.data = ''; // eslint-disable-line no-param-reassign
} else if (remaining > words.length) {
remaining -= words.length;
} else {
// OK, let's add an ellipsis and cut some of root.data
chopped = words.slice(0, remaining).join(' ') + '…';
// But be careful to get any preceding space too
if (root.data.match(/^\s/)) {
chopped = ' ' + chopped;
}
root.data = chopped; // eslint-disable-line no-param-reassign
remaining = -1;
}
}
// Now do the same for any child nodes
nodes.forEach(function(node) {
if (remaining < 0) {
root.removeChild(node);
} else {
remaining = clampHtmlByWords(node, remaining);
}
});
return remaining;
}
module.exports = {
clampHtmlByWords: clampHtmlByWords
};

View File

@@ -1,38 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { clampHtmlByWords } from './clamp-html';
let container;
const scriptTag = '<script src="/asset-v1:edX+testX+1T2021+type@asset+block/script.js">const ignore = "me here"; alert("BAD");</script>';
const styleTag = '<style>h1 {color: orange;}</style>';
beforeEach(() => {
container = document.createElement("div");
document.body.appendChild(container);
});
afterEach(() => {
document.body.removeChild(container);
container = null;
});
describe('ClampHtml', () => {
test.each([
['', 0, ''],
['a b', 0, '…'],
['a b', 1, 'a…'],
['a b c', 2, 'a b…'],
['a <i>aa ab</i> b', 2, 'a <i>aa…</i>'],
['a <i>aa ab</i> <em>ac</em>', 2, 'a <i>aa…</i>'],
['a <i>aa <em>aaa</em></i>', 2, 'a <i>aa…</i>'],
['a <i>aa <em>aaa</em> ab</i>', 3, 'a <i>aa <em>aaa…</em></i>'],
['a <i>aa ab</i> b c', 4, 'a <i>aa ab</i> b…'],
[scriptTag + 'a b c', 2, scriptTag + 'a b…'],
[styleTag + 'a b c', 2, styleTag + 'a b…'],
[scriptTag + styleTag + 'a b c', 2, scriptTag + styleTag + 'a b…'],
])('clamps by words: %s, %i', (input, wordsLeft, expected) => {
const div = ReactDOM.render(<div dangerouslySetInnerHTML={{ __html: input }} />, container);
clampHtmlByWords(div, wordsLeft);
expect(div.innerHTML).toEqual(expected);
});
});