fix: skip inaccessible learning sequence data (#716)
When normalizing learning sequences, skip inaccessible sequences and also skip sections with only inaccessible sequences. This both imitates the legacy course block behavior and also avoids a failure when merging course block data with LS data when they disagree about which sequences exist.
This commit is contained in:
@@ -98,12 +98,40 @@ export function normalizeLearningSequencesData(learningSequencesData) {
|
||||
sequences: {},
|
||||
};
|
||||
|
||||
// Sequences
|
||||
Object.entries(learningSequencesData.outline.sequences).forEach(([seqId, sequence]) => {
|
||||
if (!sequence.accessible) {
|
||||
// Skipping inaccessible sequences replicates the behavior of the legacy course blocks API
|
||||
return;
|
||||
}
|
||||
|
||||
models.sequences[seqId] = {
|
||||
id: seqId,
|
||||
title: sequence.title,
|
||||
};
|
||||
});
|
||||
|
||||
// Sections
|
||||
learningSequencesData.outline.sections.forEach(section => {
|
||||
// Skipping sections with only inaccessible sequences replicates the behavior of the legacy course blocks API
|
||||
const accessibleSequenceIds = section.sequence_ids.filter(seqId => seqId in models.sequences);
|
||||
if (accessibleSequenceIds.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
models.sections[section.id] = {
|
||||
id: section.id,
|
||||
title: section.title,
|
||||
sequenceIds: accessibleSequenceIds,
|
||||
};
|
||||
});
|
||||
|
||||
// Course
|
||||
const now = new Date();
|
||||
models.courses[learningSequencesData.course_key] = {
|
||||
id: learningSequencesData.course_key,
|
||||
title: learningSequencesData.title,
|
||||
sectionIds: learningSequencesData.outline.sections.map(section => section.id),
|
||||
sectionIds: Object.entries(models.sections).map(([sectionId]) => sectionId),
|
||||
|
||||
// Scan through all the sequences and look for ones that aren't accessible
|
||||
// to us yet because the start date has not yet passed. (Some may be
|
||||
@@ -113,23 +141,6 @@ export function normalizeLearningSequencesData(learningSequencesData) {
|
||||
),
|
||||
};
|
||||
|
||||
// Sections
|
||||
learningSequencesData.outline.sections.forEach(section => {
|
||||
models.sections[section.id] = {
|
||||
id: section.id,
|
||||
title: section.title,
|
||||
sequenceIds: section.sequence_ids,
|
||||
};
|
||||
});
|
||||
|
||||
// Sequences
|
||||
Object.entries(learningSequencesData.outline.sequences).forEach(([seqId, sequence]) => {
|
||||
models.sequences[seqId] = {
|
||||
id: seqId,
|
||||
title: sequence.title,
|
||||
};
|
||||
});
|
||||
|
||||
return models;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
|
||||
import {
|
||||
getCourseBlocks,
|
||||
getCourseMetadata,
|
||||
getLearningSequencesOutline,
|
||||
getSequenceMetadata,
|
||||
postSequencePosition,
|
||||
getBlockCompletion,
|
||||
@@ -96,6 +97,130 @@ describe('Courseware Service', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('When a request to get a learning sequence outline is made', () => {
|
||||
it('returns a normalized outline', async () => {
|
||||
await provider.addInteraction({
|
||||
state: `Outline exists for course_id ${courseId}`,
|
||||
uponReceiving: 'a request to get an outline',
|
||||
withRequest: {
|
||||
method: 'GET',
|
||||
path: `/api/learning_sequences/v1/course_outline/${courseId}`,
|
||||
},
|
||||
willRespondWith: {
|
||||
status: 200,
|
||||
body: {
|
||||
course_key: string('block-v1:edX+DemoX+Demo_Course'),
|
||||
title: string('Demo Course'),
|
||||
outline: {
|
||||
sections: [],
|
||||
sequences: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const normalizedOutline = {
|
||||
courses: {
|
||||
'block-v1:edX+DemoX+Demo_Course': {
|
||||
id: 'block-v1:edX+DemoX+Demo_Course',
|
||||
title: 'Demo Course',
|
||||
sectionIds: [],
|
||||
hasScheduledContent: false,
|
||||
},
|
||||
},
|
||||
sections: {},
|
||||
sequences: {},
|
||||
};
|
||||
const response = await getLearningSequencesOutline(courseId);
|
||||
expect(response).toEqual(normalizedOutline);
|
||||
});
|
||||
|
||||
it('skips inaccessible sequences', async () => {
|
||||
await provider.addInteraction({
|
||||
state: `Outline exists with inaccessible sequences for course_id ${courseId}`,
|
||||
uponReceiving: 'a request to get an outline',
|
||||
withRequest: {
|
||||
method: 'GET',
|
||||
path: `/api/learning_sequences/v1/course_outline/${courseId}`,
|
||||
},
|
||||
willRespondWith: {
|
||||
status: 200,
|
||||
body: {
|
||||
course_key: string('block-v1:edX+DemoX+Demo_Course'),
|
||||
title: string('Demo Course'),
|
||||
outline: like({
|
||||
sections: [
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@partial',
|
||||
title: 'Partially accessible',
|
||||
sequence_ids: [
|
||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible',
|
||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope1',
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@nope',
|
||||
title: 'Wholly inaccessible',
|
||||
sequence_ids: [
|
||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope2',
|
||||
],
|
||||
},
|
||||
],
|
||||
sequences: {
|
||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible': {
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible',
|
||||
title: 'Can access',
|
||||
accessible: true,
|
||||
effective_start: '2019-07-01T17:00:00Z',
|
||||
},
|
||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope1': {
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope1',
|
||||
title: 'Cannot access',
|
||||
accessible: false,
|
||||
effective_start: '9999-07-01T17:00:00Z',
|
||||
},
|
||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope2': {
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@nope2',
|
||||
title: 'Still cannot access',
|
||||
accessible: false,
|
||||
effective_start: '9999-07-01T17:00:00Z',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
const normalizedOutline = {
|
||||
courses: {
|
||||
'block-v1:edX+DemoX+Demo_Course': {
|
||||
id: 'block-v1:edX+DemoX+Demo_Course',
|
||||
title: 'Demo Course',
|
||||
sectionIds: [
|
||||
'block-v1:edX+DemoX+Demo_Course+type@chapter+block@partial',
|
||||
],
|
||||
hasScheduledContent: true,
|
||||
},
|
||||
},
|
||||
sections: {
|
||||
'block-v1:edX+DemoX+Demo_Course+type@chapter+block@partial': {
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@chapter+block@partial',
|
||||
title: 'Partially accessible',
|
||||
sequenceIds: [
|
||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible',
|
||||
],
|
||||
},
|
||||
},
|
||||
sequences: {
|
||||
'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible': {
|
||||
id: 'block-v1:edX+DemoX+Demo_Course+type@sequential+block@accessible',
|
||||
title: 'Can access',
|
||||
},
|
||||
},
|
||||
};
|
||||
const response = await getLearningSequencesOutline(courseId);
|
||||
expect(response).toEqual(normalizedOutline);
|
||||
});
|
||||
});
|
||||
|
||||
describe('When a request to get course metadata is made', () => {
|
||||
it('returns normalized course metadata', async () => {
|
||||
await provider.addInteraction({
|
||||
|
||||
Reference in New Issue
Block a user