Skip to content

Commit

Permalink
Merge pull request #4182 from marcellamaki/incomplete-completion-meta…
Browse files Browse the repository at this point in the history
…data

Refactor the default completion and duration content completion optio…
  • Loading branch information
marcellamaki authored Jul 4, 2023
2 parents 893525e + a47d924 commit 3155ca9
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
importedChannelLink,
secondsToHms,
getCompletionCriteriaLabels,
getCompletionDataFromNode,
} from '../utils';
import router from '../router';
import { RouteNames } from '../constants';
Expand Down Expand Up @@ -475,6 +476,76 @@ describe('channelEdit utils', () => {
});

describe(`getCompletionCriteriaLabels`, () => {
describe(`setting default values for completion and duration`, () => {
describe(`for audio and video content`, () => {
it(`returns 'When time spent is equal to duration' completion label and duration label equal to the file length in hh:mm:ss format`, () => {
expect(
getCompletionCriteriaLabels(
{
extra_fields: {
options: {},
},
kind: 'audio',
},
[{ duration: 100 }]
)
).toEqual({
completion: 'When time spent is equal to duration',
duration: '01:40',
});
});
it(`returns 'When time spent is equal to duration' completion label and duration label equal to the file length in hh:mm:ss format`, () => {
expect(
getCompletionCriteriaLabels(
{
extra_fields: {
options: {},
},
kind: 'video',
},
[{ duration: 100 }]
)
).toEqual({
completion: 'When time spent is equal to duration',
duration: '01:40',
});
});
});
describe(`for documents`, () => {
it(`returns 'Viewed in its entirety' completion label and empty duration label`, () => {
expect(
getCompletionCriteriaLabels(
{
extra_fields: {
options: {},
},
kind: 'document',
},
[]
)
).toEqual({
completion: 'Viewed in its entirety',
duration: '-',
});
});
});
describe(`for exercises`, () => {
it(`sets the Completion Criteria model to 'mastery'`, () => {
expect(
getCompletionDataFromNode(
{
extra_fields: {
options: {},
},
kind: 'exercise',
},
[]
).completionModel
).toEqual('mastery');
});
});
});

describe(`for 'reference' completion criteria`, () => {
it(`returns 'Reference material' completion label and empty duration label`, () => {
expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -614,7 +614,7 @@
return getCompletionCriteriaLabels(this.node).completion;
},
duration() {
return getCompletionCriteriaLabels(this.node).duration;
return getCompletionCriteriaLabels(this.node, this.files).duration;
},
files() {
return sortBy(this.getContentNodeFiles(this.nodeId), f => f.preset.order);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@
<script>
import get from 'lodash/get';
import {
defaultCompletionCriteriaModels,
defaultCompletionCriteriaThresholds,
CompletionOptionsDropdownMap,
completionCriteriaToDropdownMap,
} from '../../utils';
import MasteryCriteriaMofNFields from './MasteryCriteriaMofNFields';
import ActivityDuration from './ActivityDuration.vue';
import MasteryCriteriaGoal from './MasteryCriteriaGoal';
Expand All @@ -125,59 +131,6 @@
const DEFAULT_SHORT_ACTIVITY = 600;
const DEFAULT_LONG_ACTIVITY = 3000;
const defaultCompletionCriteriaModels = {
[ContentKindsNames.VIDEO]: CompletionCriteriaModels.TIME,
[ContentKindsNames.AUDIO]: CompletionCriteriaModels.TIME,
[ContentKindsNames.DOCUMENT]: CompletionCriteriaModels.PAGES,
[ContentKindsNames.H5P]: CompletionCriteriaModels.DETERMINED_BY_RESOURCE,
[ContentKindsNames.HTML5]: CompletionCriteriaModels.APPROX_TIME,
[ContentKindsNames.EXERCISE]: CompletionCriteriaModels.MASTERY,
};
const defaultCompletionCriteriaThresholds = {
// Audio and Video threshold defaults are dynamic based
// on the duration of the file itself.
[ContentKindsNames.DOCUMENT]: '100%',
[ContentKindsNames.HTML5]: 300,
// We cannot set an automatic default threshold for exercises.
};
const completionCriteriaToDropdownMap = {
[CompletionCriteriaModels.TIME]: CompletionDropdownMap.completeDuration,
[CompletionCriteriaModels.APPROX_TIME]: CompletionDropdownMap.completeDuration,
[CompletionCriteriaModels.PAGES]: CompletionDropdownMap.allContent,
[CompletionCriteriaModels.DETERMINED_BY_RESOURCE]: CompletionDropdownMap.determinedByResource,
[CompletionCriteriaModels.MASTERY]: CompletionDropdownMap.goal,
[CompletionCriteriaModels.REFERENCE]: CompletionDropdownMap.reference,
};
const CompletionOptionsDropdownMap = {
[ContentKindsNames.DOCUMENT]: [
CompletionDropdownMap.allContent,
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.reference,
],
[ContentKindsNames.EXERCISE]: [CompletionDropdownMap.goal, CompletionDropdownMap.practiceQuiz],
[ContentKindsNames.HTML5]: [
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.determinedByResource,
CompletionDropdownMap.reference,
],
[ContentKindsNames.H5P]: [
CompletionDropdownMap.determinedByResource,
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.reference,
],
[ContentKindsNames.VIDEO]: [
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.reference,
],
[ContentKindsNames.AUDIO]: [
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.reference,
],
};
export default {
name: 'CompletionOptions',
components: {
Expand Down
101 changes: 95 additions & 6 deletions contentcuration/contentcuration/frontend/channelEdit/utils.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,73 @@
import translator from './translator';
import { RouteNames } from './constants';
import { ContentKindsNames } from 'shared/leUtils/ContentKinds';
import { MasteryModelsNames } from 'shared/leUtils/MasteryModels';
import { metadataStrings, constantStrings } from 'shared/mixins';
import {
AssessmentItemTypes,
CompletionCriteriaModels,
SHORT_LONG_ACTIVITY_MIDPOINT,
CompletionDropdownMap,
} from 'shared/constants';

// The constant mapping below is used to set
// default completion criteria and durations
// both as initial values in the edit modal, and
// to ensure backwards compatibility for contentnodes
// that were added before this was in place
export const defaultCompletionCriteriaModels = {
[ContentKindsNames.VIDEO]: CompletionCriteriaModels.TIME,
[ContentKindsNames.AUDIO]: CompletionCriteriaModels.TIME,
[ContentKindsNames.DOCUMENT]: CompletionCriteriaModels.PAGES,
[ContentKindsNames.H5P]: CompletionCriteriaModels.DETERMINED_BY_RESOURCE,
[ContentKindsNames.HTML5]: CompletionCriteriaModels.APPROX_TIME,
[ContentKindsNames.EXERCISE]: CompletionCriteriaModels.MASTERY,
};

export const defaultCompletionCriteriaThresholds = {
// Audio and Video threshold defaults are dynamic based
// on the duration of the file itself.
[ContentKindsNames.DOCUMENT]: '100%',
[ContentKindsNames.HTML5]: 300,
// We cannot set an automatic default threshold for exercises.
};

export const completionCriteriaToDropdownMap = {
[CompletionCriteriaModels.TIME]: CompletionDropdownMap.completeDuration,
[CompletionCriteriaModels.APPROX_TIME]: CompletionDropdownMap.completeDuration,
[CompletionCriteriaModels.PAGES]: CompletionDropdownMap.allContent,
[CompletionCriteriaModels.DETERMINED_BY_RESOURCE]: CompletionDropdownMap.determinedByResource,
[CompletionCriteriaModels.MASTERY]: CompletionDropdownMap.goal,
[CompletionCriteriaModels.REFERENCE]: CompletionDropdownMap.reference,
};

export const CompletionOptionsDropdownMap = {
[ContentKindsNames.DOCUMENT]: [
CompletionDropdownMap.allContent,
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.reference,
],
[ContentKindsNames.EXERCISE]: [CompletionDropdownMap.goal, CompletionDropdownMap.practiceQuiz],
[ContentKindsNames.HTML5]: [
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.determinedByResource,
CompletionDropdownMap.reference,
],
[ContentKindsNames.H5P]: [
CompletionDropdownMap.determinedByResource,
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.reference,
],
[ContentKindsNames.VIDEO]: [
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.reference,
],
[ContentKindsNames.AUDIO]: [
CompletionDropdownMap.completeDuration,
CompletionDropdownMap.reference,
],
};

/**
* Get correct answer index/indices out of an array of answer objects.
* @param {String} questionType single/multiple selection, true/false, input question
Expand Down Expand Up @@ -178,6 +238,30 @@ export function secondsToHms(seconds) {
return hms;
}

/**
* Set a default duration for audiovisual content
* equal to the file length in seconds (parsed), or a fallback placeholder
*
* @param {Array} files
* @returns {String|Number} {human-readable duration}
*/
export function getAudioVideoDefaultDuration(files) {
return files && files[0] ? secondsToHms(files[0].duration) : '-';
}

/**
* Set a default completion threshold for audiovisual content
* equal to the file's duration
*
* @param {Object} node
* @returns {String|Number} {human-readable threshold}
*/
export function generateDefaultThreshold(node) {
if (node.kind !== ContentKindsNames.AUDIO || node.kind !== ContentKindsNames.VIDEO) {
return defaultCompletionCriteriaThresholds[node.kind];
}
}

/**
* Gathers data from a content node related to its completion.
*
Expand All @@ -190,15 +274,18 @@ export function getCompletionDataFromNode(node) {
return;
}

const { model, threshold } = node.extra_fields?.options?.completion_criteria || {};
const masteryModel = threshold?.mastery_model;
const completionCriteria = node.extra_fields?.options?.completion_criteria
? JSON.parse(JSON.stringify(node.extra_fields?.options?.completion_criteria))
: null;
const threshold = completionCriteria?.threshold || generateDefaultThreshold(node);
const model = completionCriteria?.model || defaultCompletionCriteriaModels[node.kind];
const suggestedDurationType = node.extra_fields?.suggested_duration_type;
const suggestedDuration = node.suggested_duration;

return {
completionModel: model,
completionThreshold: threshold,
masteryModel,
masteryModel: threshold?.mastery_model,
suggestedDurationType,
suggestedDuration,
};
Expand Down Expand Up @@ -226,8 +313,8 @@ export function isLongActivity(node) {
* @param {Object} node
* @returns {Object} { completion, duration }
*/
export function getCompletionCriteriaLabels(node) {
if (!node) {
export function getCompletionCriteriaLabels(node = {}, files = []) {
if (!node && !files) {
return;
}

Expand All @@ -250,7 +337,9 @@ export function getCompletionCriteriaLabels(node) {

case CompletionCriteriaModels.TIME:
labels.completion = metadataStrings.$tr('completeDuration');
if (suggestedDuration) {
if (node.kind === ContentKindsNames.AUDIO || node.kind === ContentKindsNames.VIDEO) {
labels.duration = getAudioVideoDefaultDuration(files);
} else if (suggestedDuration) {
labels.duration = secondsToHms(suggestedDuration);
}
break;
Expand Down

0 comments on commit 3155ca9

Please sign in to comment.