Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement manual questions selection workflow #13091

Merged
Merged
2 changes: 1 addition & 1 deletion kolibri/plugins/coach/assets/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ class CoachToolsModule extends KolibriApp {
PageNames.QUIZ_SELECT_RESOURCES_LANDING_SETTINGS,
PageNames.QUIZ_SECTION_ORDER,
PageNames.QUIZ_BOOK_MARKED_RESOURCES,
PageNames.QUIZ_PREVIEW_SELECTED_QUESTIONS,
PageNames.QUIZ_LEARNER_REPORT,
PageNames.LESSON_SUMMARY,
PageNames.LESSON_SUMMARY_BETTER,
Expand All @@ -86,7 +87,6 @@ class CoachToolsModule extends KolibriApp {
PageNames.LESSON_SELECT_RESOURCES_SEARCH_RESULTS,
PageNames.LESSON_SELECT_RESOURCES_BOOKMARKS,
PageNames.LESSON_SELECT_RESOURCES_TOPIC_TREE,
PageNames.LESSON_PREVIEW_SELECTED_QUESTIONS,
];
// If we're navigating to the same page for a quiz summary page, don't set loading
if (
Expand Down
112 changes: 43 additions & 69 deletions kolibri/plugins/coach/assets/src/composables/useQuizCreation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import isEqual from 'lodash/isEqual';
import { enhancedQuizManagementStrings } from 'kolibri-common/strings/enhancedQuizManagementStrings';
import uniq from 'lodash/uniq';
import { MAX_QUESTIONS_PER_QUIZ_SECTION } from 'kolibri/constants';
Expand Down Expand Up @@ -143,6 +142,32 @@ export default function useQuizCreation() {
updateSection({ sectionIndex, questions, resourcePool });
}

/**
* Add an array of questions to a section
* @param {Object} options
* @param {number} options.sectionIndex - The index of the section to add the questions to
* @param {QuizQuestion[]} options.questions - The questions array to add
* @param {QuizExercise[]} options.resources - The resources to add to the exercise map
*/
function addQuestionsToSection({ sectionIndex, questions, resources }) {
const targetSection = get(allSections)[sectionIndex];
if (!targetSection) {
throw new TypeError(`Section with id ${sectionIndex} not found; cannot be updated.`);
}

if (!questions || questions.length === 0) {
throw new TypeError('Questions must be a non-empty array of questions');
}

const newQuestions = questions.filter(
q => !targetSection.questions.map(q => q.item).includes(q.item),
);

const questionsToAdd = [...targetSection.questions, ...newQuestions];

updateSection({ sectionIndex, questions: questionsToAdd, resourcePool: resources });
}

function handleReplacement(replacements) {
const questions = activeQuestions.value.map(question => {
if (selectedActiveQuestions.value.includes(question.item)) {
Expand Down Expand Up @@ -286,46 +311,27 @@ export default function useQuizCreation() {
// Questions / Exercises management
// --------------------------------

/** @param {QuizQuestion} question
/** @param {QuizQuestion[]} questions
* @affects _selectedQuestionIds - Adds question to _selectedQuestionIds if it isn't
* there already */
function addQuestionToSelection(id) {
set(_selectedQuestionIds, uniq([...get(_selectedQuestionIds), id]));
function addQuestionsToSelection(ids) {
set(_selectedQuestionIds, uniq([...get(_selectedQuestionIds), ...ids]));
}

/**
* @param {QuizQuestion} question
* @param {QuizQuestion[]} questions
* @affects _selectedQuestionIds - Removes question from _selectedQuestionIds if it is there */
function removeQuestionFromSelection(id) {
function removeQuestionsFromSelection(ids) {
set(
_selectedQuestionIds,
get(_selectedQuestionIds).filter(_id => id !== _id),
get(_selectedQuestionIds).filter(_id => !ids.includes(_id)),
);
}

function toggleQuestionInSelection(id) {
if (get(_selectedQuestionIds).includes(id)) {
removeQuestionFromSelection(id);
} else {
addQuestionToSelection(id);
}
}

function clearSelectedQuestions() {
set(_selectedQuestionIds, []);
}

function selectAllQuestions() {
if (get(allQuestionsSelected)) {
clearSelectedQuestions();
} else {
set(
_selectedQuestionIds,
get(activeQuestions).map(q => q.item),
);
}
}

// Utilities

// Computed properties
Expand Down Expand Up @@ -386,22 +392,6 @@ export default function useQuizCreation() {
}, []);
});

/** Handling the Select All Checkbox
* See: remove/toggleQuestionFromSelection() & selectAllQuestions() for more */

/** @type {ComputedRef<Boolean>} Whether all active questions are selected */
const allQuestionsSelected = computed(() => {
return Boolean(
get(selectedActiveQuestions).length &&
isEqual(
get(selectedActiveQuestions).sort(),
get(activeQuestions)
.map(q => q.item)
.sort(),
),
);
});

const allSectionsEmpty = computed(() => {
return get(allSections).every(section => section.questions.length === 0);
});
Expand Down Expand Up @@ -434,21 +424,17 @@ export default function useQuizCreation() {
}
});

/** @type {ComputedRef<Boolean>} Whether the select all checkbox should be indeterminate */
const selectAllIsIndeterminate = computed(() => {
return !get(allQuestionsSelected) && !get(noQuestionsSelected);
});

provide('allQuestionsInQuiz', allQuestionsInQuiz);
provide('updateSection', updateSection);
provide('addQuestionsToSection', addQuestionsToSection);
provide('addQuestionsToSectionFromResources', addQuestionsToSectionFromResources);
provide('handleReplacement', handleReplacement);
provide('replaceSelectedQuestions', replaceSelectedQuestions);
provide('addSection', addSection);
provide('removeSection', removeSection);
provide('updateQuiz', updateQuiz);
provide('addQuestionToSelection', addQuestionToSelection);
provide('removeQuestionFromSelection', removeQuestionFromSelection);
provide('addQuestionsToSelection', addQuestionsToSelection);
provide('removeQuestionsFromSelection', removeQuestionsFromSelection);
provide('clearSelectedQuestions', clearSelectedQuestions);
provide('allSections', allSections);
provide('activeSectionIndex', activeSectionIndex);
Expand All @@ -460,12 +446,8 @@ export default function useQuizCreation() {
provide('allResourceMap', allResourceMap);
provide('activeQuestions', activeQuestions);
provide('selectedActiveQuestions', selectedActiveQuestions);
provide('allQuestionsSelected', allQuestionsSelected);
provide('selectAllIsIndeterminate', selectAllIsIndeterminate);
provide('replacementQuestionPool', replacementQuestionPool);
provide('selectAllQuestions', selectAllQuestions);
provide('deleteActiveSelectedQuestions', deleteActiveSelectedQuestions);
provide('toggleQuestionInSelection', toggleQuestionInSelection);

return {
// Methods
Expand All @@ -479,8 +461,8 @@ export default function useQuizCreation() {
initializeQuiz,
updateQuiz,
clearSelectedQuestions,
addQuestionToSelection,
removeQuestionFromSelection,
addQuestionsToSelection,
removeQuestionsFromSelection,

// Computed
quizHasChanged,
Expand All @@ -494,10 +476,8 @@ export default function useQuizCreation() {
activeQuestions,
selectedActiveQuestions,
replacementQuestionPool,
selectAllIsIndeterminate,
selectAllLabel,
allSectionsEmpty,
allQuestionsSelected,
noQuestionsSelected,
allQuestionsInQuiz,
};
Expand All @@ -506,14 +486,15 @@ export default function useQuizCreation() {
export function injectQuizCreation() {
const allQuestionsInQuiz = inject('allQuestionsInQuiz');
const updateSection = inject('updateSection');
const addQuestionsToSection = inject('addQuestionsToSection');
const addQuestionsToSectionFromResources = inject('addQuestionsToSectionFromResources');
const handleReplacement = inject('handleReplacement');
const replaceSelectedQuestions = inject('replaceSelectedQuestions');
const addSection = inject('addSection');
const removeSection = inject('removeSection');
const updateQuiz = inject('updateQuiz');
const addQuestionToSelection = inject('addQuestionToSelection');
const removeQuestionFromSelection = inject('removeQuestionFromSelection');
const addQuestionsToSelection = inject('addQuestionsToSelection');
const removeQuestionsFromSelection = inject('removeQuestionsFromSelection');
const clearSelectedQuestions = inject('clearSelectedQuestions');
const allSections = inject('allSections');
const activeSectionIndex = inject('activeSectionIndex');
Expand All @@ -523,34 +504,27 @@ export function injectQuizCreation() {
const activeResourceMap = inject('activeResourceMap');
const allResourceMap = inject('allResourceMap');
const activeQuestions = inject('activeQuestions');
const allQuestionsSelected = inject('allQuestionsSelected');
const selectAllIsIndeterminate = inject('selectAllIsIndeterminate');
const selectedActiveQuestions = inject('selectedActiveQuestions');
const replacementQuestionPool = inject('replacementQuestionPool');
const selectAllQuestions = inject('selectAllQuestions');
const deleteActiveSelectedQuestions = inject('deleteActiveSelectedQuestions');
const toggleQuestionInSelection = inject('toggleQuestionInSelection');

return {
// Methods
deleteActiveSelectedQuestions,
selectAllQuestions,
updateSection,
addQuestionsToSection,
addQuestionsToSectionFromResources,
handleReplacement,
replaceSelectedQuestions,
addSection,
removeSection,
updateQuiz,
clearSelectedQuestions,
addQuestionToSelection,
removeQuestionFromSelection,
toggleQuestionInSelection,
addQuestionsToSelection,
removeQuestionsFromSelection,

// Computed
allQuestionsSelected,
allQuestionsInQuiz,
selectAllIsIndeterminate,
allSections,
activeSectionIndex,
activeSection,
Expand Down
2 changes: 1 addition & 1 deletion kolibri/plugins/coach/assets/src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const PageNames = {
QUIZ_SELECT_RESOURCES: 'QUIZ_SELECT_RESOURCES',
QUIZ_PREVIEW_RESOURCE: 'QUIZ_PREVIEW_RESOURCE',
QUIZ_PREVIEW_SELECTED_RESOURCES: 'QUIZ_PREVIEW_SELECTED_RESOURCES',
QUIZ_PREVIEW_SELECTED_QUESTIONS: 'QUIZ_PREVIEW_SELECTED_QUESTIONS',
QUIZ_SELECT_RESOURCES_INDEX: 'QUIZ_SELECT_RESOURCES_INDEX',
QUIZ_SELECT_RESOURCES_SEARCH: 'QUIZ_SELECT_RESOURCES_SEARCH',
QUIZ_SELECT_RESOURCES_BOOKMARKS: 'QUIZ_SELECT_RESOURCES_BOOKMARKS',
Expand Down Expand Up @@ -52,7 +53,6 @@ export const PageNames = {
LESSON_SELECT_RESOURCES_TOPIC_TREE: 'LESSON_SELECT_RESOURCES_TOPIC_TREE',
LESSON_SELECT_RESOURCES_SEARCH_RESULTS: 'LESSON_SELECT_RESOURCES_SEARCH_RESULTS',
LESSON_PREVIEW_SELECTED_RESOURCES: 'LESSON_PREVIEW_SELECTED_RESOURCES',
LESSON_PREVIEW_SELECTED_QUESTIONS: 'LESSON_PREVIEW_SELECTED_QUESTIONS',
LESSON_PREVIEW_RESOURCE: 'LESSON_PREVIEW_RESOURCE',
LESSON_LEARNER_REPORT: 'LESSON_LEARNER_REPORT',
LESSON_RESOURCE_LEARNERS_REPORT: 'LESSON_RESOURCE_LEARNERS_REPORT',
Expand Down
6 changes: 6 additions & 0 deletions kolibri/plugins/coach/assets/src/routes/examRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import SelectionIndex from '../views/common/resourceSelection/subPages/Selection
import SelectFromChannels from '../views/common/resourceSelection/subPages/SelectFromTopicTree.vue';
import SelectFromBookmarks from '../views/common/resourceSelection/subPages/SelectFromBookmarks.vue';
import ManageSelectedResources from '../views/common/resourceSelection/subPages/ManageSelectedResources.vue';
import ManageSelectedQuestions from '../views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/subPages/ManageSelectedQuestions.vue';
import PreviewSelectedResources from '../views/common/resourceSelection/subPages/PreviewSelectedResources/index.vue';
import {
generateQuestionDetailHandler,
Expand Down Expand Up @@ -114,6 +115,11 @@ export default [
path: 'preview-resources',
component: ManageSelectedResources,
},
{
name: PageNames.QUIZ_PREVIEW_SELECTED_QUESTIONS,
path: 'preview-questions',
component: ManageSelectedQuestions,
},
{
name: PageNames.QUIZ_SELECT_RESOURCES_SETTINGS,
path: 'settings',
Expand Down
6 changes: 0 additions & 6 deletions kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import PreviewSelectedResources from '../views/common/resourceSelection/subPages
import LessonResourceSelection from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/index.vue';
import SearchFilters from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SearchFilters.vue';
import SelectFromSearchResults from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromSearchResults.vue';
import ManageSelectedQuestions from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/ManageSelectedQuestions.vue';
import { classIdParamRequiredGuard, RouteSegments } from './utils';

const {
Expand Down Expand Up @@ -184,11 +183,6 @@ export default [
};
},
},
{
name: PageNames.LESSON_PREVIEW_SELECTED_QUESTIONS,
path: 'preview-questions',
component: ManageSelectedQuestions,
},
],
},
],
Expand Down
Loading
Loading