From b61e1d332540cafc7496c7466bb848454d1f8761 Mon Sep 17 00:00:00 2001 From: Marcella Maki Date: Mon, 24 Oct 2022 12:46:06 -0400 Subject: [PATCH 1/6] Update h5p and html model and threshold conditions --- .../components/edit/CompletionOptions.vue | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue index 78d23af0df..2b3f30bf61 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue @@ -265,14 +265,17 @@ } if (this.kind === ContentKindsNames.HTML5) { + console.log(this.value, this.value.model); if ( !this.value['model'] || this.value.model === CompletionCriteriaModels.APPROX_TIME || - this.value.model === CompletionCriteriaModels.TIME || - this.value.model === CompletionCriteriaModels.REFERENCE + this.value.model === CompletionCriteriaModels.TIME ) { return CompletionDropdownMap.completeDuration; + } else if (this.value.model === CompletionCriteriaModels.REFERENCE) { + return CompletionDropdownMap.reference; } + return CompletionDropdownMap.determinedByResource; } @@ -353,10 +356,16 @@ // FOR H5P/HTML5 if (this.kind === ContentKindsNames.HTML5 || this.kind === ContentKindsNames.H5P) { if (value === CompletionDropdownMap.determinedByResource) { + // when richard adds the final constant, add that condition here update.completion_criteria = { model: CompletionCriteriaModels.DETERMINED_BY_RESOURCE, threshold: null, }; + } else if (value === CompletionDropdownMap.completeDuration) { + update.completion_criteria = { + model: CompletionCriteriaModels.TIME, + threshold: this.value.suggested_duration || 0, + }; } } From f9c31aa1c4d30802ca097b3f49d13c886f5d2da0 Mon Sep 17 00:00:00 2001 From: Marcella Maki Date: Thu, 27 Oct 2022 09:18:19 -0400 Subject: [PATCH 2/6] Updates to completion duration criteria to ensure data persistence. Does not include practice quiz fixes --- .../components/edit/ActivityDuration.vue | 14 +++-- .../components/edit/CompletionOptions.vue | 54 ++++++++++--------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/ActivityDuration.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/ActivityDuration.vue index 2209dda4ac..7c56c71eb2 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/ActivityDuration.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/ActivityDuration.vue @@ -23,6 +23,7 @@ :items="availableNumbers" :menu-props="menuProps" :attach="attach" + :rules="minutesRules" /> @@ -102,10 +103,7 @@ if (this.audioVideoUpload) { return false; } - return ( - this.selectedDuration !== DurationDropdownMap.EXACT_TIME && - this.selectedCompletion === CompletionDropdownMap.completeDuration - ); + return this.selectedCompletion === CompletionDropdownMap.completeDuration; }, showOptionalLabel() { return this.selectedDuration !== DurationDropdownMap.EXACT_TIME; @@ -153,8 +151,14 @@ return getShortActivityDurationValidators().map(translateValidator); } else if (this.selectedDuration === DurationDropdownMap.LONG_ACTIVITY) { return getLongActivityDurationValidators().map(translateValidator); + } else if ( + this.selectedDuration === DurationDropdownMap.EXACT_TIME && + this.selectedCompletion === CompletionDropdownMap.completeDuration + ) { + return getActivityDurationValidators().map(translateValidator); + } else { + return []; } - return getActivityDurationValidators().map(translateValidator); }, }, created() { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue index 2b3f30bf61..8a9bf25e60 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue @@ -175,15 +175,9 @@ hideDurationDropdown() { // named "hide" instead of "show" because "show" is the default behavior if (this.value) { - if (this.kind === ContentKindsNames.H5P) { - return true; - } return ( - this.currentCompletionDropdown === CompletionDropdownMap.reference || - (this.value.model === CompletionCriteriaModels.REFERENCE && - !this.currentCompletionDropdown) || - //should be hidden if model is reference and we're getting this from the BE - this.currentCompletionDropdown === CompletionDropdownMap.determinedByResource + this.value.model == CompletionCriteriaModels.REFERENCE || + this.value.model == CompletionCriteriaModels.DETERMINED_BY_RESOURCE ); } return false; @@ -221,22 +215,15 @@ - Long activity, short activity, or exact time are chosen in HTML5 */ if (this.value) { - const switchingFromReferenceBetweenAllContentViewedAndCompleteDuration = - this.value.suggested_duration === null || this.value.suggested_duration_type === null; + if ( + this.value.model == CompletionCriteriaModels.REFERENCE || + this.value.model == CompletionCriteriaModels.DETERMINED_BY_RESOURCE + ) { + return false; + } if (!this.audioVideoResource) { - if (this.kind === ContentKindsNames.HTML5 || this.kind === ContentKindsNames.H5P) { - if (this.value.model !== CompletionCriteriaModels.REFERENCE) { - if (!this.currentCompletionDropdown) { - return true; - } - return this.currentCompletionDropdown === CompletionDropdownMap.completeDuration; - } - } - return !( - this.value.model === CompletionCriteriaModels.REFERENCE || - switchingFromReferenceBetweenAllContentViewedAndCompleteDuration - ); + return true; } } return this.audioVideoResource && this.value.model !== CompletionCriteriaModels.REFERENCE; @@ -250,6 +237,10 @@ return CompletionDropdownMap.completeDuration; } + if (this.value.model === CompletionCriteriaModels.REFERENCE) { + return CompletionDropdownMap.reference; + } + if (this.kind === ContentKindsNames.DOCUMENT) { if (!this.value['model']) { return CompletionDropdownMap.allContent; @@ -265,7 +256,6 @@ } if (this.kind === ContentKindsNames.HTML5) { - console.log(this.value, this.value.model); if ( !this.value['model'] || this.value.model === CompletionCriteriaModels.APPROX_TIME || @@ -311,6 +301,12 @@ threshold: null, }; } + if (value === CompletionDropdownMap.completeDuration) { + update.completion_criteria = { + model: CompletionCriteriaModels.TIME, + threshold: this.fileDuration || this.value.suggested_duration, + }; + } // FOR DOCUMENTS if (this.kind === ContentKindsNames.DOCUMENT) { @@ -327,6 +323,13 @@ model: CompletionCriteriaModels.REFERENCE, threshold: null, }; + } else if (value === CompletionDropdownMap.completeDuration) { + // set to '1' as the minimum here, due to validation rules requiring > 0 + update.suggested_duration = this.value.suggested_duration || 60; + update.completion_criteria = { + model: CompletionCriteriaModels.TIME, + threshold: this.value.suggested_duration || 60, + }; } else { update.suggested_duration_type = this.value.suggested_duration_type; update.suggested_duration = this.value.suggested_duration; @@ -356,15 +359,15 @@ // FOR H5P/HTML5 if (this.kind === ContentKindsNames.HTML5 || this.kind === ContentKindsNames.H5P) { if (value === CompletionDropdownMap.determinedByResource) { - // when richard adds the final constant, add that condition here update.completion_criteria = { model: CompletionCriteriaModels.DETERMINED_BY_RESOURCE, threshold: null, }; } else if (value === CompletionDropdownMap.completeDuration) { + update.suggested_duration = this.value.suggested_duration || 60; update.completion_criteria = { model: CompletionCriteriaModels.TIME, - threshold: this.value.suggested_duration || 0, + threshold: this.value.suggested_duration || 60, }; } } @@ -796,6 +799,7 @@ return []; } } + return getDurationValidators().map(translateValidator); }, }, From 29de82b83eff60912cd371688017dc6e320906a7 Mon Sep 17 00:00:00 2001 From: Marcella Maki Date: Thu, 27 Oct 2022 12:11:47 -0400 Subject: [PATCH 3/6] Remove required duration from exercises. Fix display of practice quizzes --- .../channelEdit/components/edit/CompletionOptions.vue | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue index 8a9bf25e60..d558994def 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue @@ -280,8 +280,7 @@ } if (this.kind === ContentKindsNames.EXERCISE) { - // if the practice quiz flag is set, return "practice quiz" - if (this.practiceQuizzesAllowed && this.value.modality === ContentModalities.QUIZ) { + if (this.value.modality === ContentModalities.QUIZ) { return CompletionDropdownMap.practiceQuiz; } return CompletionDropdownMap.goal; @@ -377,8 +376,8 @@ if (value === CompletionDropdownMap.practiceQuiz) { update.modality = ContentModalities.QUIZ; update.completion_criteria = { - model: this.value.model, - threshold: this.value.threshold, + model: this.value.model || CompletionCriteriaModels.MASTERY, + threshold: this.value.threshold || { mastery_model: MasteryModelsNames.DO_ALL }, }; } else { update.modality = null; @@ -790,6 +789,10 @@ durationRules() { const defaultStateForDocument = this.currentCompletionDropdown === null; if (this.value) { + // duration never required for exercises + if (this.value.model === CompletionCriteriaModels.MASTERY) { + return []; + } const allContentViewedIsChosenInCompletionDropdown = this.currentCompletionDropdown === CompletionDropdownMap.allContent || (this.value.model === CompletionCriteriaModels.PAGES && From 61d2ea724009d6402e2dc63f9a0467831272d855 Mon Sep 17 00:00:00 2001 From: Marcella Maki Date: Thu, 27 Oct 2022 14:13:52 -0400 Subject: [PATCH 4/6] Update tests --- .../components/edit/CompletionOptions.vue | 9 ++- .../edit/__tests__/completionOptions.spec.js | 67 ++++--------------- 2 files changed, 20 insertions(+), 56 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue index d558994def..e668e12ef3 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue @@ -177,7 +177,8 @@ if (this.value) { return ( this.value.model == CompletionCriteriaModels.REFERENCE || - this.value.model == CompletionCriteriaModels.DETERMINED_BY_RESOURCE + this.value.model == CompletionCriteriaModels.DETERMINED_BY_RESOURCE || + this.kind === ContentKindsNames.H5P ); } return false; @@ -790,7 +791,11 @@ const defaultStateForDocument = this.currentCompletionDropdown === null; if (this.value) { // duration never required for exercises - if (this.value.model === CompletionCriteriaModels.MASTERY) { + if ( + this.value.model === CompletionCriteriaModels.MASTERY || + this.value.model === CompletionCriteriaModels.DETERMINED_BY_RESOURCE || + this.kind === ContentKindsNames.H5P + ) { return []; } const allContentViewedIsChosenInCompletionDropdown = diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/__tests__/completionOptions.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/edit/__tests__/completionOptions.spec.js index 5361515e52..59a9ddfecb 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/__tests__/completionOptions.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/__tests__/completionOptions.spec.js @@ -83,23 +83,23 @@ describe('CompletionOptions', () => { }); expect(wrapper.vm.completionDropdown).toBe('allContent'); }); - it(`'All content viewed' should be displayed if the model in the backend is 'reference'`, () => { + it(`'All content viewed' should be displayed if the model in the backend is 'pages'`, () => { const wrapper = mount(CompletionOptions, { propsData: { kind: 'document', - value: { model: 'reference' }, + value: { model: 'pages', threshold: '100%' }, }, }); expect(wrapper.vm.completionDropdown).toBe('allContent'); }); - it(`'All content viewed' should be displayed if the model in the backend is 'pages'`, () => { + it(`'Reference' should be displayed if the model in the backend is 'reference'`, () => { const wrapper = mount(CompletionOptions, { propsData: { kind: 'document', - value: { model: 'pages', threshold: '100%' }, + value: { model: 'reference' }, }, }); - expect(wrapper.vm.completionDropdown).toBe('allContent'); + expect(wrapper.vm.completionDropdown).toBe('reference'); }); it(`'Complete duration' should be displayed if 'exact time'`, () => { const wrapper = mount(CompletionOptions, { @@ -145,7 +145,7 @@ describe('CompletionOptions', () => { }); }); describe(`exercise`, () => { - it(`'Practice until goal is met' should be displayed by default if 'practice quiz' is enabled `, () => { + it(`'When goal is met' should be displayed by default`, () => { const wrapper = mount(CompletionOptions, { propsData: { kind: 'exercise', @@ -156,7 +156,7 @@ describe('CompletionOptions', () => { }); }); describe(`html5 or h5p`, () => { - it(`'Complete duration' should be displayed by default for html5`, () => { + it(`'When time spent is equal to duration' should be displayed by default for html5 and h5p`, () => { const wrapper = mount(CompletionOptions, { propsData: { kind: 'html5', @@ -165,15 +165,6 @@ describe('CompletionOptions', () => { }); expect(wrapper.vm.completionDropdown).toBe('completeDuration'); }); - it(`'Determined by this resource' should be displayed if there is no model in the backend for h5p`, () => { - const wrapper = mount(CompletionOptions, { - propsData: { - kind: 'h5p', - value: { model: null }, - }, - }); - expect(wrapper.vm.completionDropdown).toBe('determinedByResource'); - }); }); }); describe(`changing states`, () => { @@ -191,11 +182,10 @@ describe('CompletionOptions', () => { expect(wrapper.emitted('input')).toBeTruthy(); }); }); - describe(`audio/video`, () => { + describe(`reference hint`, () => { it(`'Reference hint is visible when 'Reference' is selected`, () => { const wrapper = mount(CompletionOptions, { propsData: { - kind: 'audio', value: { model: 'reference' }, }, }); @@ -203,7 +193,7 @@ describe('CompletionOptions', () => { }); }); describe(`exercise`, () => { - it(`Goal and MofN components should not be displayed when switching to PQ from PUGIM`, async () => { + it(`Goal and MofN components should not be displayed when 'Practice Quiz' is selected`, async () => { const wrapper = mount(CompletionOptions, { propsData: { kind: 'exercise', @@ -625,22 +615,7 @@ describe('CompletionOptions', () => { }); describe(`html5 or h5p`, () => { describe(`when completion dropdown is 'Determined by this resource'`, () => { - it(`minutes input is displayed when 'Short activity' is selected`, async () => { - const wrapper = mount(CompletionOptions, { - propsData: { - kind: 'html5', - value: { - suggested_duration: null, - model: CompletionCriteriaModels.DETERMINED_BY_RESOURCE, - }, - }, - }); - wrapper.find({ ref: 'duration' }).vm.$emit('input', 'shortActivity'); - await wrapper.vm.$nextTick(); - expect(wrapper.find({ ref: 'activity_duration' }).exists()).toBe(true); - expect(wrapper.vm.showActivityDurationInput).toBe(true); - }); - it(`minutes input is displayed when 'Long activity' is selected`, async () => { + it(`duration dropdown is always hidden`, async () => { const wrapper = mount(CompletionOptions, { propsData: { kind: 'html5', @@ -650,25 +625,9 @@ describe('CompletionOptions', () => { }, }, }); - wrapper.find({ ref: 'duration' }).vm.$emit('input', 'longActivity'); - await wrapper.vm.$nextTick(); - expect(wrapper.find({ ref: 'activity_duration' }).exists()).toBe(true); - expect(wrapper.vm.showActivityDurationInput).toBe(true); - }); - it(`minutes input is displayed when 'Exact time' is selected`, async () => { - const wrapper = mount(CompletionOptions, { - propsData: { - kind: 'html5', - value: { - suggested_duration: null, - model: CompletionCriteriaModels.DETERMINED_BY_RESOURCE, - }, - }, - }); - wrapper.find({ ref: 'duration' }).vm.$emit('input', 'exactTime'); - await wrapper.vm.$nextTick(); - expect(wrapper.find({ ref: 'activity_duration' }).exists()).toBe(true); - expect(wrapper.vm.showActivityDurationInput).toBe(true); + expect(wrapper.find({ ref: 'activity_duration' }).exists()).toBe(false); + expect(wrapper.find({ ref: 'duration' }).exists()).toBe(false); + expect(wrapper.vm.showActivityDurationInput).toBe(false); }); it(`minutes input is hidden and reference hint is displayed when 'Reference' is selected`, () => { const wrapper = mount(CompletionOptions, { From 0071088232eb810b26028c1167fa61295a023902 Mon Sep 17 00:00:00 2001 From: Marcella Maki Date: Thu, 27 Oct 2022 16:27:41 -0400 Subject: [PATCH 5/6] Code cleanup and revert some (mistaken) changes --- .../components/edit/ActivityDuration.vue | 3 +- .../components/edit/CompletionOptions.vue | 29 +++++-------------- .../edit/__tests__/completionOptions.spec.js | 5 +++- 3 files changed, 12 insertions(+), 25 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/ActivityDuration.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/ActivityDuration.vue index 7c56c71eb2..2afa529798 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/ActivityDuration.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/ActivityDuration.vue @@ -156,9 +156,8 @@ this.selectedCompletion === CompletionDropdownMap.completeDuration ) { return getActivityDurationValidators().map(translateValidator); - } else { - return []; } + return []; }, }, created() { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue index e668e12ef3..6640ba7f77 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue @@ -177,8 +177,7 @@ if (this.value) { return ( this.value.model == CompletionCriteriaModels.REFERENCE || - this.value.model == CompletionCriteriaModels.DETERMINED_BY_RESOURCE || - this.kind === ContentKindsNames.H5P + this.value.model == CompletionCriteriaModels.DETERMINED_BY_RESOURCE ); } return false; @@ -788,27 +787,13 @@ return false; }, durationRules() { - const defaultStateForDocument = this.currentCompletionDropdown === null; - if (this.value) { - // duration never required for exercises - if ( - this.value.model === CompletionCriteriaModels.MASTERY || - this.value.model === CompletionCriteriaModels.DETERMINED_BY_RESOURCE || - this.kind === ContentKindsNames.H5P - ) { - return []; - } - const allContentViewedIsChosenInCompletionDropdown = - this.currentCompletionDropdown === CompletionDropdownMap.allContent || - (this.value.model === CompletionCriteriaModels.PAGES && - this.currentCompletionDropdown === CompletionDropdownMap.allContent); - - if (defaultStateForDocument || allContentViewedIsChosenInCompletionDropdown) { - return []; - } + if ( + this.currentCompletionDropdown === CompletionDropdownMap.completeDuration || + this.completionDropdown === CompletionDropdownMap.completeDuration + ) { + return getDurationValidators().map(translateValidator); } - - return getDurationValidators().map(translateValidator); + return []; }, }, methods: { diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/__tests__/completionOptions.spec.js b/contentcuration/contentcuration/frontend/channelEdit/components/edit/__tests__/completionOptions.spec.js index 59a9ddfecb..8e92f8acbe 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/__tests__/completionOptions.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/__tests__/completionOptions.spec.js @@ -268,7 +268,10 @@ describe('CompletionOptions', () => { const wrapper = mount(CompletionOptions, { propsData: { kind: 'h5p', - value: { suggested_duration: null }, + value: { + suggested_duration: null, + model: CompletionCriteriaModels.DETERMINED_BY_RESOURCE, + }, }, }); const dropdown = wrapper.find({ ref: 'duration' }); From 14c076d522daecb23b4fc7f02ba0a4cf429eb1de Mon Sep 17 00:00:00 2001 From: Marcella Maki Date: Thu, 27 Oct 2022 16:49:34 -0400 Subject: [PATCH 6/6] Properly handle input from minutes, rather than setting default --- .../components/edit/CompletionOptions.vue | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue index 6640ba7f77..59aeb4d31e 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue +++ b/contentcuration/contentcuration/frontend/channelEdit/components/edit/CompletionOptions.vue @@ -583,7 +583,10 @@ ) { if (duration === DurationDropdownMap.EXACT_TIME) { update.suggested_duration_type = SuggestedDurationTypesMap.TIME; - update.suggested_duration = this.value.suggested_duration || 60; + update.suggested_duration = this.handleMinutesInputFromActivityDuration( + this.value.suggested_duration, + duration + ); } if (duration === DurationDropdownMap.SHORT_ACTIVITY) { update.suggested_duration_type = SuggestedDurationTypesMap.APPROX_TIME; @@ -634,7 +637,10 @@ } if (duration === DurationDropdownMap.EXACT_TIME) { update.suggested_duration_type = SuggestedDurationTypesMap.TIME; - update.suggested_duration = this.value.suggested_duration || 60; + update.suggested_duration = this.handleMinutesInputFromActivityDuration( + this.value.suggested_duration, + duration + ); update.completion_criteria = { model: CompletionCriteriaModels.TIME, threshold: update.suggested_duration, @@ -668,7 +674,10 @@ } if (duration === DurationDropdownMap.EXACT_TIME) { update.suggested_duration_type = SuggestedDurationTypesMap.TIME; - update.suggested_duration = this.value.suggested_duration || 60; + update.suggested_duration = this.handleMinutesInputFromActivityDuration( + this.value.suggested_duration, + duration + ); update.completion_criteria = { model: this.value.model, threshold: this.value.threshold,