feat: add functionality to see unit draft preview (#1501)
* feat: add functionality to see unit draft preview * feat: add tests for course link redirects * fix: course redirect unit to sequnce unit redirect * fix: test coverage
This commit is contained in:
@@ -19,62 +19,50 @@ import { handleNextSectionCelebration } from './course/celebration';
|
||||
import withParamsAndNavigation from './utils';
|
||||
|
||||
// Look at where this is called in componentDidUpdate for more info about its usage
|
||||
const checkResumeRedirect = memoize((courseStatus, courseId, sequenceId, firstSequenceId, navigate) => {
|
||||
if (courseStatus === 'loaded' && !sequenceId) {
|
||||
// Note that getResumeBlock is just an API call, not a redux thunk.
|
||||
getResumeBlock(courseId).then((data) => {
|
||||
// This is a replace because we don't want this change saved in the browser's history.
|
||||
if (data.sectionId && data.unitId) {
|
||||
navigate(`/course/${courseId}/${data.sectionId}/${data.unitId}`, { replace: true });
|
||||
} else if (firstSequenceId) {
|
||||
navigate(`/course/${courseId}/${firstSequenceId}`, { replace: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Look at where this is called in componentDidUpdate for more info about its usage
|
||||
const checkSectionUnitToUnitRedirect = memoize((courseStatus, courseId, sequenceStatus, section, unitId, navigate) => {
|
||||
if (courseStatus === 'loaded' && sequenceStatus === 'failed' && section && unitId) {
|
||||
navigate(`/course/${courseId}/${unitId}`, { replace: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Look at where this is called in componentDidUpdate for more info about its usage
|
||||
const checkSectionToSequenceRedirect = memoize((courseStatus, courseId, sequenceStatus, section, unitId, navigate) => {
|
||||
if (courseStatus === 'loaded' && sequenceStatus === 'failed' && section && !unitId) {
|
||||
// If the section is non-empty, redirect to its first sequence.
|
||||
if (section.sequenceIds && section.sequenceIds[0]) {
|
||||
navigate(`/course/${courseId}/${section.sequenceIds[0]}`, { replace: true });
|
||||
// Otherwise, just go to the course root, letting the resume redirect take care of things.
|
||||
} else {
|
||||
navigate(`/course/${courseId}`, { replace: true });
|
||||
export const checkResumeRedirect = memoize(
|
||||
(courseStatus, courseId, sequenceId, firstSequenceId, navigate, isPreview) => {
|
||||
if (courseStatus === 'loaded' && !sequenceId) {
|
||||
// Note that getResumeBlock is just an API call, not a redux thunk.
|
||||
getResumeBlock(courseId).then((data) => {
|
||||
// This is a replace because we don't want this change saved in the browser's history.
|
||||
if (data.sectionId && data.unitId) {
|
||||
const baseUrl = `/course/${courseId}/${data.sectionId}`;
|
||||
const sequenceUrl = isPreview ? `/preview${baseUrl}` : baseUrl;
|
||||
navigate(`${sequenceUrl}/${data.unitId}`, { replace: true });
|
||||
} else if (firstSequenceId) {
|
||||
navigate(`/course/${courseId}/${firstSequenceId}`, { replace: true });
|
||||
}
|
||||
}, () => {});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Look at where this is called in componentDidUpdate for more info about its usage
|
||||
export const checkSectionUnitToUnitRedirect = memoize((
|
||||
courseStatus,
|
||||
courseId,
|
||||
sequenceStatus,
|
||||
section,
|
||||
unitId,
|
||||
navigate,
|
||||
isPreview,
|
||||
) => {
|
||||
if (courseStatus === 'loaded' && sequenceStatus === 'failed' && section && unitId) {
|
||||
const baseUrl = `/course/${courseId}`;
|
||||
const courseUrl = isPreview ? `/preview${baseUrl}` : baseUrl;
|
||||
navigate(`${courseUrl}/${unitId}`, { replace: true });
|
||||
}
|
||||
});
|
||||
|
||||
// Look at where this is called in componentDidUpdate for more info about its usage
|
||||
const checkUnitToSequenceUnitRedirect = memoize(
|
||||
(courseStatus, courseId, sequenceStatus, sequenceMightBeUnit, sequenceId, section, routeUnitId, navigate) => {
|
||||
if (courseStatus === 'loaded' && sequenceStatus === 'failed' && !section && !routeUnitId) {
|
||||
if (sequenceMightBeUnit) {
|
||||
// If the sequence failed to load as a sequence, but it is marked as a possible unit, then
|
||||
// we need to look up the correct parent sequence for it, and redirect there.
|
||||
const unitId = sequenceId; // just for clarity during the rest of this method
|
||||
getSequenceForUnitDeprecated(courseId, unitId).then(
|
||||
parentId => {
|
||||
if (parentId) {
|
||||
navigate(`/course/${courseId}/${parentId}/${unitId}`, { replace: true });
|
||||
} else {
|
||||
navigate(`/course/${courseId}`, { replace: true });
|
||||
}
|
||||
},
|
||||
() => { // error case
|
||||
navigate(`/course/${courseId}`, { replace: true });
|
||||
},
|
||||
);
|
||||
export const checkSectionToSequenceRedirect = memoize(
|
||||
(courseStatus, courseId, sequenceStatus, section, unitId, navigate) => {
|
||||
if (courseStatus === 'loaded' && sequenceStatus === 'failed' && section && !unitId) {
|
||||
// If the section is non-empty, redirect to its first sequence.
|
||||
if (section.sequenceIds && section.sequenceIds[0]) {
|
||||
navigate(`/course/${courseId}/${section.sequenceIds[0]}`, { replace: true });
|
||||
// Otherwise, just go to the course root, letting the resume redirect take care of things.
|
||||
} else {
|
||||
// Invalid sequence that isn't a unit either. Redirect up to main course.
|
||||
navigate(`/course/${courseId}`, { replace: true });
|
||||
}
|
||||
}
|
||||
@@ -82,41 +70,80 @@ const checkUnitToSequenceUnitRedirect = memoize(
|
||||
);
|
||||
|
||||
// Look at where this is called in componentDidUpdate for more info about its usage
|
||||
const checkSequenceToSequenceUnitRedirect = memoize((courseId, sequenceStatus, sequence, unitId, navigate) => {
|
||||
if (sequenceStatus === 'loaded' && sequence.id && !unitId) {
|
||||
if (sequence.unitIds !== undefined && sequence.unitIds.length > 0) {
|
||||
const nextUnitId = sequence.unitIds[sequence.activeUnitIndex];
|
||||
// This is a replace because we don't want this change saved in the browser's history.
|
||||
navigate(`/course/${courseId}/${sequence.id}/${nextUnitId}`, { replace: true });
|
||||
export const checkUnitToSequenceUnitRedirect = memoize((
|
||||
courseStatus,
|
||||
courseId,
|
||||
sequenceStatus,
|
||||
sequenceMightBeUnit,
|
||||
sequenceId,
|
||||
section,
|
||||
routeUnitId,
|
||||
navigate,
|
||||
isPreview,
|
||||
) => {
|
||||
if (courseStatus === 'loaded' && sequenceStatus === 'failed' && !section && !routeUnitId) {
|
||||
if (sequenceMightBeUnit) {
|
||||
// If the sequence failed to load as a sequence, but it is marked as a possible unit, then
|
||||
// we need to look up the correct parent sequence for it, and redirect there.
|
||||
const unitId = sequenceId; // just for clarity during the rest of this method
|
||||
getSequenceForUnitDeprecated(courseId, unitId).then(
|
||||
parentId => {
|
||||
if (parentId) {
|
||||
const baseUrl = `/course/${courseId}/${parentId}`;
|
||||
const sequenceUrl = isPreview ? `/preview${baseUrl}` : baseUrl;
|
||||
navigate(`${sequenceUrl}/${unitId}`, { replace: true });
|
||||
} else {
|
||||
navigate(`/course/${courseId}`, { replace: true });
|
||||
}
|
||||
},
|
||||
() => { // error case
|
||||
navigate(`/course/${courseId}`, { replace: true });
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// Invalid sequence that isn't a unit either. Redirect up to main course.
|
||||
navigate(`/course/${courseId}`, { replace: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Look at where this is called in componentDidUpdate for more info about its usage
|
||||
const checkSequenceUnitMarkerToSequenceUnitRedirect = memoize(
|
||||
(courseId, sequenceStatus, sequence, unitId, navigate) => {
|
||||
export const checkSequenceToSequenceUnitRedirect = memoize(
|
||||
(courseId, sequenceStatus, sequence, unitId, navigate, isPreview) => {
|
||||
if (sequenceStatus === 'loaded' && sequence.id && !unitId) {
|
||||
if (sequence.unitIds !== undefined && sequence.unitIds.length > 0) {
|
||||
const baseUrl = `/course/${courseId}/${sequence.id}`;
|
||||
const sequenceUrl = isPreview ? `/preview${baseUrl}` : baseUrl;
|
||||
const nextUnitId = sequence.unitIds[sequence.activeUnitIndex];
|
||||
// This is a replace because we don't want this change saved in the browser's history.
|
||||
navigate(`${sequenceUrl}/${nextUnitId}`, { replace: true });
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Look at where this is called in componentDidUpdate for more info about its usage
|
||||
export const checkSequenceUnitMarkerToSequenceUnitRedirect = memoize(
|
||||
(courseId, sequenceStatus, sequence, unitId, navigate, isPreview) => {
|
||||
if (sequenceStatus !== 'loaded' || !sequence.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseUrl = `/course/${courseId}/${sequence.id}`;
|
||||
const hasUnits = sequence.unitIds?.length > 0;
|
||||
|
||||
if (unitId === 'first') {
|
||||
if (hasUnits) {
|
||||
if (hasUnits) {
|
||||
const sequenceUrl = isPreview ? `/preview${baseUrl}` : baseUrl;
|
||||
if (unitId === 'first') {
|
||||
const firstUnitId = sequence.unitIds[0];
|
||||
navigate(`/course/${courseId}/${sequence.id}/${firstUnitId}`, { replace: true });
|
||||
} else {
|
||||
// No units... go to general sequence page
|
||||
navigate(`/course/${courseId}/${sequence.id}`, { replace: true });
|
||||
}
|
||||
} else if (unitId === 'last') {
|
||||
if (hasUnits) {
|
||||
navigate(`${sequenceUrl}/${firstUnitId}`, { replace: true });
|
||||
} else if (unitId === 'last') {
|
||||
const lastUnitId = sequence.unitIds[sequence.unitIds.length - 1];
|
||||
navigate(`/course/${courseId}/${sequence.id}/${lastUnitId}`, { replace: true });
|
||||
} else {
|
||||
// No units... go to general sequence page
|
||||
navigate(`/course/${courseId}/${sequence.id}`, { replace: true });
|
||||
navigate(`${sequenceUrl}/${lastUnitId}`, { replace: true });
|
||||
}
|
||||
} else {
|
||||
// No units... go to general sequence page
|
||||
navigate(baseUrl, { replace: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -169,6 +196,7 @@ class CoursewareContainer extends Component {
|
||||
routeSequenceId,
|
||||
routeUnitId,
|
||||
navigate,
|
||||
isPreview,
|
||||
} = this.props;
|
||||
|
||||
// Load data whenever the course or sequence ID changes.
|
||||
@@ -197,7 +225,7 @@ class CoursewareContainer extends Component {
|
||||
// Check resume redirect:
|
||||
// /course/:courseId -> /course/:courseId/:sequenceId/:unitId
|
||||
// based on sequence/unit where user was last active.
|
||||
checkResumeRedirect(courseStatus, courseId, sequenceId, firstSequenceId, navigate);
|
||||
checkResumeRedirect(courseStatus, courseId, sequenceId, firstSequenceId, navigate, isPreview);
|
||||
|
||||
// Check section-unit to unit redirect:
|
||||
// /course/:courseId/:sectionId/:unitId -> /course/:courseId/:unitId
|
||||
@@ -210,33 +238,69 @@ class CoursewareContainer extends Component {
|
||||
// otherwise, we could get stuck in a redirect loop, since a sequence that failed to load
|
||||
// would endlessly redirect to itself through `checkSectionUnitToUnitRedirect`
|
||||
// and `checkUnitToSequenceUnitRedirect`.
|
||||
checkSectionUnitToUnitRedirect(courseStatus, courseId, sequenceStatus, sectionViaSequenceId, routeUnitId, navigate);
|
||||
checkSectionUnitToUnitRedirect(
|
||||
courseStatus,
|
||||
courseId,
|
||||
sequenceStatus,
|
||||
sectionViaSequenceId,
|
||||
routeUnitId,
|
||||
navigate,
|
||||
isPreview,
|
||||
);
|
||||
|
||||
// Check section to sequence redirect:
|
||||
// /course/:courseId/:sectionId -> /course/:courseId/:sequenceId
|
||||
// by redirecting to the first sequence within the section.
|
||||
checkSectionToSequenceRedirect(courseStatus, courseId, sequenceStatus, sectionViaSequenceId, routeUnitId, navigate);
|
||||
checkSectionToSequenceRedirect(
|
||||
courseStatus,
|
||||
courseId,
|
||||
sequenceStatus,
|
||||
sectionViaSequenceId,
|
||||
routeUnitId,
|
||||
navigate,
|
||||
);
|
||||
|
||||
// Check unit to sequence-unit redirect:
|
||||
// /course/:courseId/:unitId -> /course/:courseId/:sequenceId/:unitId
|
||||
// by filling in the ID of the parent sequence of :unitId.
|
||||
checkUnitToSequenceUnitRedirect((
|
||||
courseStatus, courseId, sequenceStatus, sequenceMightBeUnit,
|
||||
sequenceId, sectionViaSequenceId, routeUnitId, navigate
|
||||
));
|
||||
checkUnitToSequenceUnitRedirect(
|
||||
courseStatus,
|
||||
courseId,
|
||||
sequenceStatus,
|
||||
sequenceMightBeUnit,
|
||||
sequenceId,
|
||||
sectionViaSequenceId,
|
||||
routeUnitId,
|
||||
navigate,
|
||||
isPreview,
|
||||
);
|
||||
|
||||
// Check sequence to sequence-unit redirect:
|
||||
// /course/:courseId/:sequenceId -> /course/:courseId/:sequenceId/:unitId
|
||||
// by filling in the ID the most-recently-active unit in the sequence, OR
|
||||
// the ID of the first unit the sequence if none is active.
|
||||
checkSequenceToSequenceUnitRedirect(courseId, sequenceStatus, sequence, routeUnitId, navigate);
|
||||
checkSequenceToSequenceUnitRedirect(
|
||||
courseId,
|
||||
sequenceStatus,
|
||||
sequence,
|
||||
routeUnitId,
|
||||
navigate,
|
||||
isPreview,
|
||||
);
|
||||
|
||||
// Check sequence-unit marker to sequence-unit redirect:
|
||||
// /course/:courseId/:sequenceId/first -> /course/:courseId/:sequenceId/:unitId
|
||||
// /course/:courseId/:sequenceId/last -> /course/:courseId/:sequenceId/:unitId
|
||||
// by filling in the ID the first or last unit in the sequence.
|
||||
// "Sequence unit marker" is an invented term used only in this component.
|
||||
checkSequenceUnitMarkerToSequenceUnitRedirect(courseId, sequenceStatus, sequence, routeUnitId, navigate);
|
||||
checkSequenceUnitMarkerToSequenceUnitRedirect(
|
||||
courseId,
|
||||
sequenceStatus,
|
||||
sequence,
|
||||
routeUnitId,
|
||||
navigate,
|
||||
isPreview,
|
||||
);
|
||||
}
|
||||
|
||||
handleUnitNavigationClick = () => {
|
||||
@@ -334,6 +398,7 @@ CoursewareContainer.propTypes = {
|
||||
fetchCourse: PropTypes.func.isRequired,
|
||||
fetchSequence: PropTypes.func.isRequired,
|
||||
navigate: PropTypes.func.isRequired,
|
||||
isPreview: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
CoursewareContainer.defaultProps = {
|
||||
|
||||
Reference in New Issue
Block a user