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

Migrate quizzes to updated resource selection #13043

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
6 changes: 6 additions & 0 deletions kolibri/plugins/coach/assets/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ class CoachToolsModule extends KolibriApp {
PageNames.QUIZ_REPLACE_QUESTIONS,
PageNames.QUIZ_SELECT_PRACTICE_QUIZ,
PageNames.QUIZ_SELECT_RESOURCES,
PageNames.QUIZ_SELECT_RESOURCES_INDEX,
PageNames.QUIZ_SELECT_RESOURCES_BOOKMARKS,
PageNames.QUIZ_SELECT_RESOURCES_TOPIC_TREE,
PageNames.QUIZ_PREVIEW_SELECTED_RESOURCES,
PageNames.QUIZ_SELECT_RESOURCES_SETTINGS,
PageNames.QUIZ_SELECT_RESOURCES_LANDING_SETTINGS,
PageNames.QUIZ_SECTION_ORDER,
PageNames.QUIZ_BOOK_MARKED_RESOURCES,
PageNames.QUIZ_LEARNER_REPORT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ import useFetch from './useFetch';
* This utility handles selection rules, manages fetch states for channels, bookmarks,
* and topic trees, and offers methods to add, remove, or override selected resources.
*
* @param {Object} options
* @param {Object} options.bookmarks Configuration object for bookmarks fetch. It can contain
* `filters` an object with extra query params, and `annotator` a function to annotate the results.
* @param {Object} options.channels Configuration object for channels fetch. It can contain
* `filters` an object with extra query params, and `annotator` a function to annotate the results.
* @param {Object} options.topicTree Configuration object for topic tree fetch. It can contain
* `filters` an object with extra query params, and `annotator` a function to annotate the results.
*
* @typedef {Object} UseResourceSelectionResponse
* @property {Object} topic Topic tree object, contains the information of the topic,
* its ascendants and children.
Expand All @@ -38,7 +46,7 @@ import useFetch from './useFetch';
*
* @returns {UseResourceSelectionResponse}
*/
export default function useResourceSelection() {
export default function useResourceSelection({ bookmarks, channels, topicTree } = {}) {
const store = getCurrentInstance().proxy.$store;
const route = computed(() => store.state.route);
const topicId = computed(() => route.value.query.topicId);
Expand All @@ -47,33 +55,62 @@ export default function useResourceSelection() {
const selectedResources = ref([]);
const topic = ref(null);

const fetchBookmarks = async params => {
const response = await ContentNodeResource.fetchBookmarks(params);
if (bookmarks?.annotator) {
const annotatedResults = await bookmarks.annotator(response.results);
return {
...response,
results: annotatedResults,
};
}
return response;
};
const bookmarksFetch = useFetch({
fetchMethod: () =>
ContentNodeResource.fetchBookmarks({
params: { limit: 25, available: true },
fetchBookmarks({
params: { limit: 25, available: true, ...bookmarks?.filters },
}),
fetchMoreMethod: more =>
ContentNodeResource.fetchBookmarks({
params: more,
}),
});

const fetchChannels = async () => {
const result = await ChannelResource.fetchCollection({
getParams: {
available: true,
...channels?.filters,
},
});
if (channels?.annotator) {
return channels.annotator(result);
}
return result;
};
const channelsFetch = useFetch({
fetchMethod: () =>
ChannelResource.fetchCollection({
getParams: {
available: true,
},
}),
fetchMethod: fetchChannels,
});

const fetchTree = async (params = {}) => {
topic.value = await ContentNodeResource.fetchTree(params);
if (topicTree?.annotator) {
const annotatedResults = await topicTree.annotator(topic.value.children.results);
return {
...topic.value.children,
results: annotatedResults,
};
}
return topic.value.children;
};

const treeFetch = useFetch({
fetchMethod: () => fetchTree({ id: topicId.value, params: { include_coach_content: true } }),
fetchMethod: () =>
fetchTree({
id: topicId.value,
params: { include_coach_content: true, ...topicTree?.filters },
}),
fetchMoreMethod: more => fetchTree(more),
});

Expand Down
10 changes: 9 additions & 1 deletion kolibri/plugins/coach/assets/src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,20 @@ export const PageNames = {
QUIZ_LEARNER_REPORT: 'QUIZ_LEARNER_REPORT',
QUIZ_SECTION_EDITOR: 'QUIZ_SECTION_EDITOR',
QUIZ_REPLACE_QUESTIONS: 'QUIZ_REPLACE_QUESTIONS',
QUIZ_SELECT_RESOURCES: 'QUIZ_SELECT_RESOURCES',
QUIZ_SELECT_RESOURCES_OLD: 'QUIZ_SELECT_RESOURCES_OLD',
QUIZ_SELECT_PRACTICE_QUIZ: 'QUIZ_SELECT_PRACTICE_QUIZ',
QUIZ_SECTION_ORDER: 'QUIZ_SECTION_ORDER',
QUIZ_QUESTION_PAGE_ROOT: 'QUIZ_QUESTION_PAGE_ROOT',
QUIZ_QUESTION_REPORT: 'QUIZ_QUESTION_REPORT',
QUIZ_BOOK_MARKED_RESOURCES: 'QUIZ_BOOK_MARKED_RESOURCES',
QUIZ_SECTION_SIDE_PANEL: 'QUIZ_SECTION_SIDE_PANEL',
QUIZ_SELECT_RESOURCES: 'QUIZ_SELECT_RESOURCES',
QUIZ_PREVIEW_SELECTED_RESOURCES: 'QUIZ_PREVIEW_SELECTED_RESOURCES',
QUIZ_SELECT_RESOURCES_INDEX: 'QUIZ_SELECT_RESOURCES_INDEX',
QUIZ_SELECT_RESOURCES_BOOKMARKS: 'QUIZ_SELECT_RESOURCES_BOOKMARKS',
QUIZ_SELECT_RESOURCES_TOPIC_TREE: 'QUIZ_SELECT_RESOURCES_TOPIC_TREE',
QUIZ_SELECT_RESOURCES_SETTINGS: 'QUIZ_SELECT_RESOURCES_SETTINGS',
QUIZ_SELECT_RESOURCES_LANDING_SETTINGS: 'QUIZ_SELECT_RESOURCES_LANDING_SETTINGS',

/* Lessons */
LESSONS_ROOT: 'LESSONS_ROOT',
Expand Down
101 changes: 78 additions & 23 deletions kolibri/plugins/coach/assets/src/routes/examRoutes.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
import store from 'kolibri/store';
import { PageNames } from '../constants';
import CreateExamPage from '../views/quizzes/CreateExamPage';
import SectionEditor from '../views/quizzes/CreateExamPage/SectionEditor.vue';
import ResourceSelection from '../views/quizzes/CreateExamPage/ResourceSelection.vue';
import ReplaceQuestions from '../views/quizzes/CreateExamPage/ReplaceQuestions.vue';
import SectionEditor from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionEditor.vue';
import ReplaceQuestions from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/ReplaceQuestions.vue';
import ExamsRootPage from '../views/quizzes/ExamsRootPage';
import QuizSummaryPage from '../views/quizzes/QuizSummaryPage';
import SectionOrder from '../views/quizzes/CreateExamPage/SectionOrder';
import SectionOrder from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/SectionOrder.vue';
import SectionSidePanel from '../views/quizzes/CreateExamPage/sidePanels/SectionSidePanel/index.vue';
import QuizResourceSelection from '../views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/index.vue';
import LearnerQuizPage from '../views/common/reports/LearnerQuizPage.vue';
import QuizPreviewPage from '../views/quizzes/reports/QuizPreviewPage.vue';
import { generateExamReportDetailHandler } from '../modules/examReportDetail/handlers';
import QuestionLearnersPage from '../views/common/reports/QuestionLearnersPage.vue';
import SelectionIndex from '../views/common/resourceSelection/subPages/SelectionIndex.vue';
import SelectFromChannels from '../views/common/resourceSelection/subPages/SelectFromChannels.vue';
import SelectFromBookmarks from '../views/common/resourceSelection/subPages/SelectFromBookmarks.vue';
import ManageSelectedResources from '../views/common/resourceSelection/subPages/ManageSelectedResources.vue';
import {
generateQuestionDetailHandler,
questionRootRedirectHandler,
} from '../modules/questionDetail/handlers';
import QuestionsSettings from '../views/quizzes/CreateExamPage/sidePanels/QuizResourceSelection/subPages/QuestionsSettings.vue';
import { classIdParamRequiredGuard, RouteSegments } from './utils';

const {
Expand Down Expand Up @@ -52,31 +58,80 @@ export default [
},
children: [
{
name: PageNames.QUIZ_SECTION_EDITOR,
path: 'edit',
component: SectionEditor,
},
{
name: PageNames.QUIZ_REPLACE_QUESTIONS,
path: 'replace-questions',
component: ReplaceQuestions,
name: PageNames.QUIZ_SECTION_SIDE_PANEL,
path: 'details',
component: SectionSidePanel,
children: [
{
name: PageNames.QUIZ_SECTION_EDITOR,
path: 'edit',
component: SectionEditor,
},
{
name: PageNames.QUIZ_REPLACE_QUESTIONS,
path: 'replace-questions',
component: ReplaceQuestions,
},
{
name: PageNames.QUIZ_SECTION_ORDER,
path: 'section-order',
component: SectionOrder,
},
],
},
{
name: PageNames.QUIZ_SELECT_RESOURCES,
path: 'select-resources/:topic_id?',
component: ResourceSelection,
},
{
name: PageNames.QUIZ_SECTION_ORDER,
path: 'section-order',
component: SectionOrder,
path: 'select-resources',
component: QuizResourceSelection,
redirect: 'select-resources/landing-settings',
children: [
{
name: PageNames.QUIZ_SELECT_RESOURCES_LANDING_SETTINGS,
path: 'landing-settings',
component: QuestionsSettings,
props: {
isLanding: true,
},
},
{
name: PageNames.QUIZ_SELECT_RESOURCES_INDEX,
path: 'index',
component: SelectionIndex,
},
{
name: PageNames.QUIZ_SELECT_RESOURCES_BOOKMARKS,
path: 'bookmarks',
component: SelectFromBookmarks,
},
{
name: PageNames.QUIZ_SELECT_RESOURCES_TOPIC_TREE,
path: 'channels',
component: SelectFromChannels,
},
{
name: PageNames.QUIZ_PREVIEW_SELECTED_RESOURCES,
path: 'preview-resources',
component: ManageSelectedResources,
},
{
name: PageNames.QUIZ_SELECT_RESOURCES_SETTINGS,
path: 'settings',
component: QuestionsSettings,
},
],
},
{
name: PageNames.QUIZ_SELECT_PRACTICE_QUIZ,
path: 'select-quiz/:topic_id?',
component: ResourceSelection,
props: {
selectPracticeQuiz: true,
path: 'select-quiz',
redirect: to => {
const { params } = to;
return {
name: PageNames.QUIZ_SELECT_RESOURCES_INDEX,
params,
query: {
selectPracticeQuiz: true,
},
};
},
},
],
Expand Down
9 changes: 4 additions & 5 deletions kolibri/plugins/coach/assets/src/routes/lessonsRoutes.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,15 @@ import {
generateQuestionDetailHandler,
questionRootRedirectHandler,
} from '../modules/questionDetail/handlers';
import SelectionIndex from '../views/common/resourceSelection/subPages/SelectionIndex.vue';
import LessonLearnerExercisePage from '../views/lessons/reports/LessonLearnerExercisePage.vue';
import QuestionLearnersPage from '../views/common/reports/QuestionLearnersPage.vue';
import EditLessonDetails from '../views/lessons/LessonSummaryPage/sidePanels/EditLessonDetails';
import PreviewSelectedResources from '../views/lessons/LessonSummaryPage/sidePanels/PreviewSelectedResources';
import LessonResourceSelection from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection';
import SelectionIndex from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectionIndex.vue';
import SelectFromBookmarks from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromBookmarks.vue';
import SelectFromChannels from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/SelectFromChannels.vue';
import ManageSelectedResources from '../views/lessons/LessonSummaryPage/sidePanels/LessonResourceSelection/subPages/ManageSelectedResources.vue';

import SelectFromBookmarks from '../views/common/resourceSelection/subPages/SelectFromBookmarks.vue';
import SelectFromChannels from '../views/common/resourceSelection/subPages/SelectFromChannels.vue';
import ManageSelectedResources from '../views/common/resourceSelection/subPages/ManageSelectedResources.vue';
import { classIdParamRequiredGuard, RouteSegments } from './utils';

const {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<template>

<div class="quiz-header">
<span>
{{ quizTitle }}
</span>
<KButtonGroup>
<KButton
icon="settings"
appearance="flat-button"
:text="settingsLabel$()"
@click="onSettingsClick"
/>
<KButton
v-if="!hideSearch"
icon="filter"
:text="searchLabel$()"
/>
</KButtonGroup>
</div>

</template>


<script>

import { coreStrings } from 'kolibri/uiText/commonCoreStrings';
import { enhancedQuizManagementStrings } from 'kolibri-common/strings/enhancedQuizManagementStrings';
import { PageNames } from '../../../constants';

export default {
name: 'QuizResourceSelectionHeader',
setup() {
const { searchLabel$, settingsLabel$ } = coreStrings;

return {
searchLabel$,
settingsLabel$,
};
},
props: {
settings: {
type: Object,
required: true,
},
hideSearch: {
type: Boolean,
default: false,
},
},
computed: {
quizTitle() {
const { selectUpToNResources$, selectUpToNQuestions$ } = enhancedQuizManagementStrings;

if (this.settings.isChoosingManually) {
return selectUpToNQuestions$({ count: this.settings.questionCount });
}
return selectUpToNResources$({ count: this.settings.questionCount });
},
},
methods: {
onSettingsClick() {
this.$router.push({ name: PageNames.QUIZ_SELECT_RESOURCES_SETTINGS });
},
},
};

</script>


<style lang="scss" scoped>

.quiz-header {
display: flex;
align-items: center;
justify-content: space-between;
}

</style>
Loading