feat: handle courseware paths more liberally (#395)

Valid courseware URLs currently include:
* /course/:courseId
* /course/:courseId/:sequenceId
* /course/:courseId/:sequenceId/:unitId

In this commit we add support for:
* /course/:courseId/:sectionId
* /course/:courseId/:sectionId/:unitId
* /course/:courseId/:unitId

All URL forms still redirect to:
  /course/:courseId/:sequenceId/:unitId

See ADR #8 for more context.

All changes:
* refactor: allow courseBlocks factory to build multiple sections
* refactor: make CoursewareContainer tests less brittle & stateful
* feat: handle courseware paths more liberally
* refactor: reorder, rename, & comment redirection functions

TNL-7796
This commit is contained in:
Kyle McCormick
2021-04-01 09:10:00 -04:00
committed by GitHub
parent 6a376b20c7
commit 353964e75c
12 changed files with 621 additions and 237 deletions

View File

@@ -75,8 +75,8 @@ export default function buildSimpleCourseAndSequenceMetadata(options = {}) {
});
const courseId = courseMetadata.id;
const simpleCourseBlocks = buildSimpleCourseBlocks(courseId, courseMetadata.name, options);
const { unitBlocks, sequenceBlock } = simpleCourseBlocks;
const sequenceMetadata = options.sequenceMetadata || sequenceBlock.map(block => Factory.build(
const { unitBlocks, sequenceBlocks } = simpleCourseBlocks;
const sequenceMetadata = options.sequenceMetadata || sequenceBlocks.map(block => Factory.build(
'sequenceMetadata',
{},
{

View File

@@ -164,6 +164,7 @@ function normalizeSequenceMetadata(sequence) {
return {
sequence: {
id: sequence.item_id,
blockType: sequence.tag,
unitIds: sequence.items.map(unit => unit.id),
bannerText: sequence.banner_text,
format: sequence.format,

View File

@@ -24,18 +24,18 @@ describe('Data layer integration tests', () => {
// building minimum set of api responses to test all thunks
const courseMetadata = Factory.build('courseMetadata');
const courseId = courseMetadata.id;
const { courseBlocks, unitBlocks, sequenceBlock } = buildSimpleCourseBlocks(courseId);
const { courseBlocks, unitBlocks, sequenceBlocks } = buildSimpleCourseBlocks(courseId);
const sequenceMetadata = Factory.build(
'sequenceMetadata',
{},
{ courseId, unitBlocks, sequenceBlock: sequenceBlock[0] },
{ courseId, unitBlocks, sequenceBlock: sequenceBlocks[0] },
);
let courseUrl = `${courseBaseUrl}/${courseId}`;
courseUrl = appendBrowserTimezoneToUrl(courseUrl);
const sequenceUrl = `${sequenceBaseUrl}/${sequenceMetadata.item_id}`;
const sequenceId = sequenceBlock[0].id;
const sequenceId = sequenceBlocks[0].id;
const unitId = unitBlocks[0].id;
let store;
@@ -115,6 +115,22 @@ describe('Data layer integration tests', () => {
expect(store.getState().courseware.sequenceStatus).toEqual('failed');
});
it('Should result in fetch failure if a non-sequential block is returned', async () => {
const sectionMetadata = {
...sequenceMetadata,
// 'chapter' is the block_type of a Section, which the sequence metadata
// API will happily return if requested, since SectionBlock is implemented
// as a subclass of SequenceBlock.
tag: 'chapter',
};
axiosMock.onGet(sequenceUrl).reply(200, sectionMetadata);
await executeThunk(thunks.fetchSequence(sequenceId), store.dispatch);
expect(loggingService.logError).toHaveBeenCalled();
expect(store.getState().courseware.sequenceStatus).toEqual('failed');
});
it('Should fetch and normalize metadata, and then update existing models with sequence metadata', async () => {
axiosMock.onGet(courseUrl).reply(200, courseMetadata);
axiosMock.onGet(courseBlocksUrlRegExp).reply(200, courseBlocks);

View File

@@ -93,15 +93,26 @@ export function fetchSequence(sequenceId) {
dispatch(fetchSequenceRequest({ sequenceId }));
try {
const { sequence, units } = await getSequenceMetadata(sequenceId);
dispatch(updateModel({
modelType: 'sequences',
model: sequence,
}));
dispatch(updateModels({
modelType: 'units',
models: units,
}));
dispatch(fetchSequenceSuccess({ sequenceId }));
if (sequence.blockType !== 'sequential') {
// Some other block types (particularly 'chapter') can be returned
// by this API. We want to error in that case, since downstream
// courseware code is written to render Sequences of Units.
logError(
`Requested sequence '${sequenceId}' `
+ `has block type '${sequence.blockType}'; expected block type 'sequential'.`,
);
dispatch(fetchSequenceFailure({ sequenceId }));
} else {
dispatch(updateModel({
modelType: 'sequences',
model: sequence,
}));
dispatch(updateModels({
modelType: 'units',
models: units,
}));
dispatch(fetchSequenceSuccess({ sequenceId }));
}
} catch (error) {
logError(error);
dispatch(fetchSequenceFailure({ sequenceId }));