You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Here we will lay the foundation for the rest of the front-end by modernizing (by Kolibri's standards anyhow) how we manage state and component routing. The Kolibri quiz's question source has changed to a new data structure (#11002) and the UI to be built upon the work done here is all-new (while, of course, making use of existing components).
The point is that this is where we will make the decisions around how we organize the state and devise the API we will use to interact with it - so we will start with the acceptance criteria that will define the feature's expectations. We will do this while trying to consider the needs for each aspect of this feature across the board and will share suggestions, thoughts, and questions that need to be resolved in order to complete this issue.
Notes, expectations, guidance
From Vuex -> Composition API
Currently, kolibri/plugins/coach/assets/src/modules/examCreation/* houses the Vuex module that was used for the previous quiz creation system along with some other useful utilities. The new quiz creation front-end state management will use the Composition API.
This is a straightforward use where we are basically mapping to the PinnedDeviceResource API within the context of the user's session. One notable aspect is that we use the getCurrentInstance().proxy.$store to access the Vuex store attached to the root component, so even in the Composition API we still have access to our Vuex stores if needed
This is a bit more complicated in that is uses computed, ref, get, set functions to maintain and use a reference to a reactive piece of data. The quiz creation feature will likely need to make use of some of these features.
Note that get and set here are just functions that abstract away the fact that when you create a ref you need to set it's value property in order to trigger it. You can see an example of this in kolibri/plugins/user_profile/assets/src/composables/useCurrentUser.js where const isLoading = ref({}) then has its actual value updated with isLoading.value = false - using set: set(isLoading, false) which is probably stylistically cleaner and more readable.
Data structure overview
Introduced in #11025, the data structure is made available in kolibri/core/assets/src/exams/utils.js by way of the convertExamQuestionSourcesToV3 function which takes an exam object and returns its question_sources in the V3 format defined below:
# Exam.question_sources V3:
[
# Section 1
{
"section_title": <section title>,"description": <section description>,"resource_pool": [ <contentnode_ids of pool of resources> ],
"questions": [
{
"exercise_id": <exercise_pk>,"question_id": <item_id_within_exercise>,"title": <title of question>,"counter_in_exercise": <unique_count_for_question>,"learners_see_fixed_order": <bool>,
},
]
},
# Section 2
{...}
]
Since we are only going to be creating new exams, however, we likely won't need this particular function until we move to update Coach Reports et al to accommodate the new quiz structure, thus this is only for reference.
JSDoc types
Perhaps thinking about this in terms of data types could be helpful to have helpful "class name" type reference to each part of the data we need to work with. We'll start with the smallest and move up to the wrapping types.
QuizQuestion
/* * @typedef {Object} QuizQuestion A particular question in a Quiz * @property {string} exercise_id The ID of the resource from which the question originates * @property {string} question_id A *unique* identifier of this particular question within the quiz * @property {string} title A title for the question * @property {number} counter_in_exercise A number assigned to separate questions which have the same title to differentiate them */
QuizSection
Note that T[] indicates that it is an array of T -- eg, QuizQuestion[] means "this is an array of QuizQuestions
Note also that we are adding a section_id property for the client-side so that we can reliably update particular sections without concern for keeping their order
/* * @typedef {Object} QuizSection Defines a single section of the quiz * @property {string} section_id A unique ID for the section; !client-side only! * @property {string} section_title The title of the quiz section * @property {string} description A text blob associated with the section * @property {number} question_count The number of questions in the section * @property {QuizQuestion[]} questions The list of QuizQuestion objects in the section * @property {boolean} learners_see_fixed_order A bool flag indicating whether this section is shown in the same order, or randomized, to the learners * @property {ExerciseMap} exercise_pool An array of contentnode ids indicat */// Below is what we should expect around the ExerciseMap type on the `exercise_pool` property/* * @typedef {Object} Exercise A particular exercise that can be selected within a quiz * @property {string} ancestor_id The ID of the parent contentnode * @property {string} content_id The ID for the piece of content * @property {string} id Unique ID for this exercise * @property {bool} is_leaf More or less means "is_not_a_topic" * @property {string} kind Exercise or Topic in our case, most likely see kolibri.core.assets.src.constants.ContentNodeKinds * @property {string} title The resource title *//* * @typedef {Object} ExerciseMap A mapping of an Exercise.id to an Exercise * @property {Exercise} Exercise.id The ID for an exercise that will map to an Exercise object here */
Quiz
/* * @typedef {Object} Quiz The remainder of the quiz object * @property {string} title The title of the whole quiz * @property {QuizSection[]} question_sources A list of the QuizSection objects that make up the quiz * @ * ... other fields hidden for brevity, see kolibri.core.exams.models.Exam ... */
Here we have a Quiz whose question_sources property is an array of QuizSection objects whose questions property is an array of QuizQuestion objects. Now we can think of each item as a bundle of their respective properties.
Acceptance criteria
Quiz creation shared state management
Overall, we want to have a clean and clear interface for the rest of the feature to make use of. Using the Composition API, we should be able to represent clear state concepts and expose functions which are well documented. A few high level goals:
Composition API module(s) should have simple JSDoc declarations for functions and particularly important pieces of state that, at least, define the behavior and usage. Describing parameters and return values is highly encouraged, especially anywhere in which it might be unclear.
Write tests for anything that gets hairy -- or at least stub them out and make a follow-up issue to write them after feature-completion.
Generally, unless noted otherwise, the state described here should be reactive.
Also, note that while this is intended to be comprehensive, there may be things which are missed or there may be tasks that are incomplete at the end of the iteration. With this in mind:
Merge this within the iteration it is assigned so that other work is not blocked
Update other feature issues with any unfinished business wherever it is most relevant
The API should expose methods that:
Can update the Quiz (the title is the only value we care about right now)
Can create, update, delete QuizSections (adding to/removing from the top tabs; populating and receiving updates from the Section Settings side panel)
Takes care to maintain the questions property to match the length of question_count as the latter is updated (assuming there are enough questions in the exercise_pool to match it)
Can read & update the order of the QuizSections within the quiz (will be updated via drag-and-drop)
Can read & update the order of QuizQuestions within a QuizSection(drag-and-drop-again)
Can update the title of any QuizQuestion
Can read the QuizQuestions in a QuizSection(for the primary list of questions, nothing to update about these except their order, as noted above; changing/replacing questions should be handled by updating the QuizSection.questions property)
Can read & update the list of selected QuizQuestions (the ones w/ the KCheckbox selected for further action by the user)
Can replace a subset of a QuizSections questions with another of equal size (... for the replacement bit)
Can get all nodes from a section's exercise_pool property which are not currently in the same section's questions property which are not currently selected (generating the list to be used by the replacement side panel)
Can save and submit a quiz to the backend API (:thumbsup:)
Side panels & routing
Some Vue Router routes will likely be the simplest path forward here. As a hierarchy, the routes are a simple tree which should be simple to implement as routes:
If the side panel is in the "select resources" component, what should be shown in the side panel when they finish selecting resources and click to "go back" to the side panel view they were at previously? (Note, this could be either the "Section Settings" or the "Replace questions" views)
The text was updated successfully, but these errors were encountered:
Overview
Here we will lay the foundation for the rest of the front-end by modernizing (by Kolibri's standards anyhow) how we manage state and component routing. The Kolibri quiz's question source has changed to a new data structure (#11002) and the UI to be built upon the work done here is all-new (while, of course, making use of existing components).
The point is that this is where we will make the decisions around how we organize the state and devise the API we will use to interact with it - so we will start with the acceptance criteria that will define the feature's expectations. We will do this while trying to consider the needs for each aspect of this feature across the board and will share suggestions, thoughts, and questions that need to be resolved in order to complete this issue.
Notes, expectations, guidance
From Vuex -> Composition API
Currently,
kolibri/plugins/coach/assets/src/modules/examCreation/*
houses the Vuex module that was used for the previous quiz creation system along with some other useful utilities. The new quiz creation front-end state management will use the Composition API.Examples in Kolibri of using Composition API
kolibri/plugins/learn/assets/src/composables/usePinnedDevices.js
This is a straightforward use where we are basically mapping to the
PinnedDeviceResource
API within the context of the user's session. One notable aspect is that we use thegetCurrentInstance().proxy.$store
to access the Vuex store attached to the root component, so even in the Composition API we still have access to our Vuex stores if neededkolibri/plugins/learn/assets/src/composables/useLearnerResources.js
This is a bit more complicated in that is uses
computed, ref, get, set
functions to maintain and use a reference to a reactive piece of data. The quiz creation feature will likely need to make use of some of these features.Note that
get
andset
here are just functions that abstract away the fact that when you create aref
you need to set it'svalue
property in order to trigger it. You can see an example of this inkolibri/plugins/user_profile/assets/src/composables/useCurrentUser.js
whereconst isLoading = ref({})
then has its actual value updated withisLoading.value = false
- using set:set(isLoading, false)
which is probably stylistically cleaner and more readable.Data structure overview
Introduced in #11025, the data structure is made available in
kolibri/core/assets/src/exams/utils.js
by way of theconvertExamQuestionSourcesToV3
function which takes anexam
object and returns itsquestion_sources
in the V3 format defined below:Since we are only going to be creating new exams, however, we likely won't need this particular function until we move to update Coach Reports et al to accommodate the new quiz structure, thus this is only for reference.
JSDoc types
Perhaps thinking about this in terms of data types could be helpful to have helpful "class name" type reference to each part of the data we need to work with. We'll start with the smallest and move up to the wrapping types.
QuizQuestion
QuizSection
Note that
T[]
indicates that it is an array ofT
-- eg,QuizQuestion[]
means "this is an array ofQuizQuestion
sNote also that we are adding a
section_id
property for the client-side so that we can reliably update particular sections without concern for keeping their orderQuiz
Here we have a
Quiz
whosequestion_sources
property is an array ofQuizSection
objects whosequestions
property is an array ofQuizQuestion
objects. Now we can think of each item as a bundle of their respective properties.Acceptance criteria
Quiz creation shared state management
Overall, we want to have a clean and clear interface for the rest of the feature to make use of. Using the Composition API, we should be able to represent clear state concepts and expose functions which are well documented. A few high level goals:
Composition API module(s) should have simple JSDoc declarations for functions and particularly important pieces of state that, at least, define the behavior and usage. Describing parameters and return values is highly encouraged, especially anywhere in which it might be unclear.
Write tests for anything that gets hairy -- or at least stub them out and make a follow-up issue to write them after feature-completion.
Generally, unless noted otherwise, the state described here should be reactive.
Also, note that while this is intended to be comprehensive, there may be things which are missed or there may be tasks that are incomplete at the end of the iteration. With this in mind:
Merge this within the iteration it is assigned so that other work is not blocked
Update other feature issues with any unfinished business wherever it is most relevant
The API should expose methods that:
Can update the Quiz (the title is the only value we care about right now)
Can create, update, delete
QuizSection
s (adding to/removing from the top tabs; populating and receiving updates from the Section Settings side panel)Takes care to maintain the
questions
property to match the length ofquestion_count
as the latter is updated (assuming there are enough questions in theexercise_pool
to match it)Can read & update the order of the
QuizSection
s within the quiz (will be updated via drag-and-drop)Can read & update the order of
QuizQuestion
s within aQuizSection
(drag-and-drop-again)Can update the title of any
QuizQuestion
Can read the
QuizQuestion
s in aQuizSection
(for the primary list of questions, nothing to update about these except their order, as noted above; changing/replacing questions should be handled by updating theQuizSection.questions
property)Can read & update the list of selected
QuizQuestion
s (the ones w/ the KCheckbox selected for further action by the user)Can replace a subset of a
QuizSection
s questions with another of equal size (... for the replacement bit)Can get all nodes from a section's
exercise_pool
property which are not currently in the same section'squestions
property which are not currently selected (generating the list to be used by the replacement side panel)Can save and submit a quiz to the backend API (:thumbsup:)
Side panels & routing
Some Vue Router routes will likely be the simplest path forward here. As a hierarchy, the routes are a simple tree which should be simple to implement as routes:
Should we show a side panel?
Which side panel view should be visible?
If the side panel is in the "select resources" component, what should be shown in the side panel when they finish selecting resources and click to "go back" to the side panel view they were at previously? (Note, this could be either the "Section Settings" or the "Replace questions" views)
The text was updated successfully, but these errors were encountered: