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

Setup initial quiz creation frontend data architecture and API #11012

Closed
5 of 18 tasks
nucleogenesis opened this issue Jul 25, 2023 · 1 comment
Closed
5 of 18 tasks

Setup initial quiz creation frontend data architecture and API #11012

nucleogenesis opened this issue Jul 25, 2023 · 1 comment
Assignees

Comments

@nucleogenesis
Copy link
Member

nucleogenesis commented Jul 25, 2023

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 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

kolibri/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 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:

  - Quiz Creation Root
    - Section settings editor (side panel)
        - Select resources (side panel)
      - Replace questions (side panel)
        - Select resources (side panel)
  • 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)

@nucleogenesis
Copy link
Member Author

Closed by #11088

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants