Skip to content

Commit

Permalink
feat: add functionality to see unit draft preview (#1501)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
KristinAoki authored Oct 28, 2024
1 parent 6f11596 commit d47433e
Show file tree
Hide file tree
Showing 17 changed files with 1,194 additions and 171 deletions.
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export const DECODE_ROUTES = {
'/course/:courseId/:sequenceId/:unitId',
'/course/:courseId/:sequenceId',
'/course/:courseId',
'/preview/course/:courseId/:sequenceId/:unitId',
'/preview/course/:courseId/:sequenceId',
],
REDIRECT_HOME: 'home/:courseId',
REDIRECT_SURVEY: 'survey/:courseId',
Expand Down
221 changes: 143 additions & 78 deletions src/courseware/CoursewareContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,104 +19,131 @@ 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 });
}
});
}
});
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
const checkSectionUnitToUnitRedirect = memoize((courseStatus, courseId, sequenceStatus, section, unitId, navigate) => {
export const checkSectionUnitToUnitRedirect = memoize((
courseStatus,
courseId,
sequenceStatus,
section,
unitId,
navigate,
isPreview,
) => {
if (courseStatus === 'loaded' && sequenceStatus === 'failed' && section && unitId) {
navigate(`/course/${courseId}/${unitId}`, { replace: true });
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 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 });
}
}
});

// 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 });
}
}
},
);

// 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 });
}
},
);
Expand Down Expand Up @@ -169,6 +196,7 @@ class CoursewareContainer extends Component {
routeSequenceId,
routeUnitId,
navigate,
isPreview,
} = this.props;

// Load data whenever the course or sequence ID changes.
Expand Down Expand Up @@ -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
Expand All @@ -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 = () => {
Expand Down Expand Up @@ -334,6 +398,7 @@ CoursewareContainer.propTypes = {
fetchCourse: PropTypes.func.isRequired,
fetchSequence: PropTypes.func.isRequired,
navigate: PropTypes.func.isRequired,
isPreview: PropTypes.bool.isRequired,
};

CoursewareContainer.defaultProps = {
Expand Down
Loading

0 comments on commit d47433e

Please sign in to comment.