fix: refactor best practices checklist logic (#2038)

The Best Practices Checklist behavior was wrong for some cases:

* Video: if duration is null it shouldn't be marked as completed
* Video: if there are no videos it shouldn't be marked as completed
* Unit depth: if course doesn't have units it shouldn't be marked as completed
* Diverse learning sequence: description mentions that 80% should contain multiple content types, so if it is exactly 80% it should be marked as completed
This commit is contained in:
diana-villalvazo-wgu
2025-06-04 13:46:01 -06:00
committed by GitHub
parent 7274316eb8
commit 5df4cd941d
12 changed files with 16 additions and 103 deletions

View File

@@ -71,16 +71,6 @@ const messages = defineMessages({
defaultMessage: 'Learners engage best with short videos followed by opportunities to practice. Ensure that 80% or more of course videos are less than 10 minutes long.',
description: 'Description for a section that prompts a user to follow best practices for video length',
},
mobileFriendlyVideoShortDescription: {
id: 'mobileFriendlyVideoShortDescription',
defaultMessage: 'Create mobile-friendly video',
description: 'Label for a section that describes mobile friendly videos',
},
mobileFriendlyVideoLongDescription: {
id: 'mobileFriendlyVideoLongDescription',
defaultMessage: 'Mobile-friendly videos can be viewed across all supported devices. Ensure that at least 90% of course videos are mobile friendly by uploading course videos to the edX video pipeline.',
description: 'Description for a section that prompts a user to follow best practices for mobile friendly videos',
},
diverseSequencesShortDescription: {
id: 'diverseSequencesShortDescription',
defaultMessage: 'Build diverse learning sequences',

View File

@@ -36,10 +36,6 @@ export const checklistItems = {
id: 'videoDuration',
pacingTypeFilter: filters.ALL,
},
{
id: 'mobileFriendlyVideo',
pacingTypeFilter: filters.ALL,
},
{
id: 'diverseSequences',
pacingTypeFilter: filters.ALL,

View File

@@ -35,18 +35,8 @@ export const hasAssignmentDeadlines = (assignments, dates) => {
export const hasShortVideoDuration = (videos) => {
if (videos.totalNumber === 0) {
return true;
} if (videos.totalNumber > 0 && videos.durations.median <= 600) {
return true;
}
return false;
};
export const hasMobileFriendlyVideos = (videos) => {
if (videos.totalNumber === 0) {
return true;
} if (videos.totalNumber > 0 && (videos.numMobileEncoded / videos.totalNumber) >= 0.9) {
return false;
} if (videos.totalNumber > 0 && videos.durations.median !== null && videos.durations.median <= 600) {
return true;
}
@@ -57,7 +47,7 @@ export const hasDiverseSequences = (subsections) => {
if (subsections.totalVisible === 0) {
return false;
} if (subsections.totalVisible > 0) {
return ((subsections.numWithOneBlockType / subsections.totalVisible) < 0.2);
return ((subsections.numWithOneBlockType / subsections.totalVisible) <= 0.2);
}
return false;
@@ -68,7 +58,7 @@ export const hasWeeklyHighlights = sections => (
);
export const hasShortUnitDepth = units => (
units.numBlocks.median <= 3
units.numBlocks.median <= 3 && units.totalVisible > 0
);
export const hasProctoringEscalationEmail = proctoring => (

View File

@@ -189,8 +189,8 @@ describe('courseCheckValidators utility functions', () => {
);
describe('hasShortVideoDuration', () => {
it('returns true if course run has no videos', () => {
expect(validators.hasShortVideoDuration({ totalNumber: 0 })).toEqual(true);
it('returns false if course run has no videos', () => {
expect(validators.hasShortVideoDuration({ totalNumber: 0 })).toEqual(false);
});
it('returns true if course run videos have a median duration <= to 600', () => {
@@ -204,22 +204,6 @@ describe('courseCheckValidators utility functions', () => {
});
});
describe('hasMobileFriendlyVideos', () => {
it('returns true if course run has no videos', () => {
expect(validators.hasMobileFriendlyVideos({ totalNumber: 0 })).toEqual(true);
});
it('returns true if course run videos are >= 90% mobile friendly', () => {
expect(validators.hasMobileFriendlyVideos({ totalNumber: 10, numMobileEncoded: 9 }))
.toEqual(true);
});
it('returns true if course run videos are < 90% mobile friendly', () => {
expect(validators.hasMobileFriendlyVideos({ totalNumber: 10, numMobileEncoded: 8 }))
.toEqual(false);
});
});
describe('hasDiverseSequences', () => {
it('returns true if < 20% of visible subsections have more than one block type', () => {
expect(validators.hasDiverseSequences({ totalVisible: 10, numWithOneBlockType: 1 }))
@@ -264,6 +248,7 @@ describe('courseCheckValidators utility functions', () => {
describe('hasShortUnitDepth', () => {
it('returns true when course run has median number of blocks <= 3', () => {
const units = {
totalVisible: 2,
numBlocks: {
median: 3,
},
@@ -274,6 +259,7 @@ describe('courseCheckValidators utility functions', () => {
it('returns false when course run has median number of blocks > 3', () => {
const units = {
totalVisible: 2,
numBlocks: {
median: 4,
},

View File

@@ -14,8 +14,6 @@ const getValidatedValue = (data, id) => {
return healthValidators.hasAssignmentDeadlines(data.assignments, data.dates);
case 'videoDuration':
return healthValidators.hasShortVideoDuration(data.videos);
case 'mobileFriendlyVideo':
return healthValidators.hasMobileFriendlyVideos(data.videos);
case 'diverseSequences':
return healthValidators.hasDiverseSequences(data.subsections);
case 'weeklyHighlights':

View File

@@ -88,20 +88,6 @@ describe('getValidatedValue utility function', () => {
expect(spy).toHaveBeenCalledTimes(1);
});
it('mobile friendly video', () => {
const spy = jest.fn();
localValidators.hasMobileFriendlyVideos = spy;
const props = {
data: {
videos: {},
},
};
getValidatedValue(props, 'mobileFriendlyVideo');
expect(spy).toHaveBeenCalledTimes(1);
});
it('diverse sequences', () => {
const spy = jest.fn();
localValidators.hasDiverseSequences = spy;

View File

@@ -481,7 +481,7 @@ describe('<CourseOutline />', () => {
courseId, excludeGraded: true, all: true,
}), store.dispatch);
expect(getByText('4/9 completed')).toBeInTheDocument();
expect(getByText('3/8 completed')).toBeInTheDocument();
});
it('render alerts if checklist api fails', async () => {

View File

@@ -58,10 +58,6 @@ export const BEST_PRACTICES_CHECKLIST = /** @type {const} */ ({
id: 'videoDuration',
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
},
{
id: 'mobileFriendlyVideo',
pacingTypeFilter: CHECKLIST_FILTERS.ALL,
},
{
id: 'diverseSequences',
pacingTypeFilter: CHECKLIST_FILTERS.ALL,

View File

@@ -62,20 +62,7 @@ export const hasShortVideoDuration = (videos) => {
if (totalNumber === 0) {
return true;
}
if (totalNumber > 0 && durations.median <= 600) {
return true;
}
return false;
};
export const hasMobileFriendlyVideos = (videos) => {
const { totalNumber, numMobileEncoded } = videos;
if (totalNumber === 0) {
return true;
}
if (totalNumber > 0 && (numMobileEncoded / totalNumber) >= 0.9) {
if (totalNumber > 0 && durations.median !== null && durations.median <= 600) {
return true;
}
@@ -89,7 +76,7 @@ export const hasDiverseSequences = (subsections) => {
return false;
}
if (totalVisible > 0) {
return ((numWithOneBlockType / totalVisible) < 0.2);
return ((numWithOneBlockType / totalVisible) <= 0.2);
}
return false;
@@ -101,6 +88,6 @@ export const hasWeeklyHighlights = (sections) => {
return highlightsActiveForCourse && highlightsEnabled;
};
export const hasShortUnitDepth = (units) => units.numBlocks.median <= 3;
export const hasShortUnitDepth = (units) => units.numBlocks.median <= 3 && units.totalVisible > 0;
export const hasProctoringEscalationEmail = (proctoring) => proctoring.hasProctoringEscalationEmail;

View File

@@ -204,22 +204,6 @@ describe('courseCheckValidators utility functions', () => {
});
});
describe('hasMobileFriendlyVideos', () => {
it('returns true if course run has no videos', () => {
expect(validators.hasMobileFriendlyVideos({ totalNumber: 0 })).toEqual(true);
});
it('returns true if course run videos are >= 90% mobile friendly', () => {
expect(validators.hasMobileFriendlyVideos({ totalNumber: 10, numMobileEncoded: 9 }))
.toEqual(true);
});
it('returns true if course run videos are < 90% mobile friendly', () => {
expect(validators.hasMobileFriendlyVideos({ totalNumber: 10, numMobileEncoded: 8 }))
.toEqual(false);
});
});
describe('hasDiverseSequences', () => {
it('returns true if < 20% of visible subsections have more than one block type', () => {
expect(validators.hasDiverseSequences({ totalVisible: 10, numWithOneBlockType: 1 }))
@@ -264,6 +248,7 @@ describe('courseCheckValidators utility functions', () => {
describe('hasShortUnitDepth', () => {
it('returns true when course run has median number of blocks <= 3', () => {
const units = {
totalVisible: 2,
numBlocks: {
median: 3,
},
@@ -274,6 +259,7 @@ describe('courseCheckValidators utility functions', () => {
it('returns false when course run has median number of blocks > 3', () => {
const units = {
totalVisible: 2,
numBlocks: {
median: 4,
},

View File

@@ -89,8 +89,8 @@ describe('getChecklistForStatusBar util functions', () => {
};
expect(getCourseBestPracticesChecklist(data)).toEqual({
totalCourseBestPracticesChecks: 4,
completedCourseBestPracticesChecks: 2,
totalCourseBestPracticesChecks: 3,
completedCourseBestPracticesChecks: 1,
});
});
});

View File

@@ -32,8 +32,6 @@ const getChecklistValidatedValue = (data, id) => {
return healthValidators.hasAssignmentDeadlines(assignments, dates);
case 'videoDuration':
return healthValidators.hasShortVideoDuration(videos);
case 'mobileFriendlyVideo':
return healthValidators.hasMobileFriendlyVideos(videos);
case 'diverseSequences':
return healthValidators.hasDiverseSequences(subsections);
case 'weeklyHighlights':