|
| 1 | + |
| 2 | +Evolving the library_content block schema |
| 3 | +######################################### |
| 4 | + |
| 5 | +Status |
| 6 | +****** |
| 7 | + |
| 8 | +**Provisional** |
| 9 | + |
| 10 | +Subject to change due to implementation learnings and stakeholder feedback. |
| 11 | + |
| 12 | +Context |
| 13 | +******* |
| 14 | + |
| 15 | +The library_content block is an existing block type which allows users to include a specified number of blocks into a course, randomly picked per-learner from a "V1" (modulestore-backed) content library, optionally filtered by the blocks' ``capa_type`` (only applicable to ``problem`` blocks). We are incrementally improving this feature: |
| 16 | + |
| 17 | +* We are soon adding support for "V2" (blockstore-backed) content libraries to the library_content block. This will enable an improved library authoring experience and assist our transition away from modulestore and MongoDB. We need this not to break existing usages of the library_content block, and we need there to be a period of simultaneous V1/V2 support overlap to enable a reversible migration of libraries from modulestore to blockstore. Eventually, V1 support will be removed. |
| 18 | + |
| 19 | +* Next, we want to add support for non-randomized, hand-picked reference of library blocks. This is is a highly requested feature across the community. |
| 20 | + |
| 21 | +* In the future, we would like to support other modes: in particular, we want authors to eventually be able to hand-pick a set of blocks from a library, and *then* have the LMS randomly select a specified number of blokcs for each learner. |
| 22 | + |
| 23 | +We need to choose how to evolve the schema of the library_content block to support all of these incremental improvements. This is the current schema: |
| 24 | + |
| 25 | +.. list-table:: |
| 26 | + :header-rows: 1 |
| 27 | + |
| 28 | + * - Field |
| 29 | + - Scope, Type |
| 30 | + - Default |
| 31 | + - Example |
| 32 | + - Description |
| 33 | + |
| 34 | + * - source_library_id |
| 35 | + - settings, str|None |
| 36 | + - None |
| 37 | + - "library-v1:OEX+Lib1" |
| 38 | + - Key of V1 source library; None means no library has been chosen. |
| 39 | + |
| 40 | + * - source_library\_ version |
| 41 | + - settings, str|None |
| 42 | + - None |
| 43 | + - TBD |
| 44 | + - Version of V1 source library (a MongoID string). None means latest. |
| 45 | + |
| 46 | + * - mode |
| 47 | + - settings, str |
| 48 | + - "random" |
| 49 | + - "random" |
| 50 | + - How blocks should be selected. Only valid value is "random". |
| 51 | + |
| 52 | + * - capa_type |
| 53 | + - settings, str |
| 54 | + - "any" |
| 55 | + - "symbolicresponse" |
| 56 | + - Unless "any": Include only problems, and only problems of this response type. |
| 57 | + |
| 58 | + * - children |
| 59 | + - children, list of XBlocks |
| 60 | + - [] |
| 61 | + - ``block-v1:...+type@problem+block@A`` |
| 62 | + ``block-v1:...+type@problem+block@B`` |
| 63 | + ``block-v1:...+type@problem+block@D`` |
| 64 | + ``block-v1:...+type@problem+block@F`` |
| 65 | + - Standard XBlock field that holds references to the child XBlocks. This field is only populated after the library content block is saved, at which time the matching XBlocks (e.g. filtered by capa_type) from the library are copied ("included") to become children of this block. Learners will often only see a subset of these children (up to max_count); see ``selected``. Course author may apply edits that are local to these included child blocks. Such edits to child blocks' Scope.settings fields will persist across library version upgrades, whereas edits to child blocks' Scope.content fields are wiped out when library version is upgraded. |
| 66 | + |
| 67 | + * - max_count |
| 68 | + - settings, int |
| 69 | + - 1 |
| 70 | + - 2 |
| 71 | + - Number of children that LMS will randomly select for each learner. -1 means all. |
| 72 | + |
| 73 | + * - selected |
| 74 | + - user_state, list of (block type, block id) tuples |
| 75 | + - [] |
| 76 | + - [("problem", "A"), ("problem", "F")] |
| 77 | + - An ordered sub-list of the children, selected by the LMS to be shown to a given learner. At most max_count, unless max_count = -1. Once populated, this is kept stable for the learner, unless a the children or max_count are changed, in which case it will have blocks added or removed as necessary. If the list changes, it is re-shuffled. |
| 78 | + |
| 79 | + |
| 80 | +Decision |
| 81 | +******** |
| 82 | + |
| 83 | +We will introduce the support for a "manual selection mode", without explicitly making it *mode* as hinted at by the old block schema. This will lead to a cleaner and more flexible implementation. We will achieve this with two new boolean settings: **manual** and **shuffle**: |
| 84 | + |
| 85 | +* When manual is *disabled* and shuffle is *enabled*, the block will behave as it did before. That is, it will import the entire library (filtered by capa_type) as its children, and present each user a random subset based on max_count. For backwards compatibility, these will be the default value of the settings. |
| 86 | + |
| 87 | +* When manual is *enabled*, shuffle is *disabled*, and max_count is set to -1, the block will behave in the "static" mode. That is, the user will be prompted to select specific blocks in the library, and each user will be presented those blocks in order. |
| 88 | + |
| 89 | +* When manual is *enabled*, any filter fields (currently just capa_type, but perhaps more in the future) will be ignored for the purposes of CMS deciding which blocks to import. That's because in the event that manually-picked children clash with the filters, we need to decide who would "win", and we are deciding here that the manually-picked children would win. However, we could choose to have the block-picker filter blocks based on filters. |
| 90 | + |
| 91 | +We will also remove the **mode** field, as it is no longer needed, and it has only ever had one value. |
| 92 | + |
| 93 | +The interaction between manual, shuffle, and max_count yields a matrix of 8 different behaviors: |
| 94 | + |
| 95 | +.. list-table:: |
| 96 | + |
| 97 | + * - |
| 98 | + - **manual = False** |
| 99 | + - **manual = True** |
| 100 | + |
| 101 | + * - **shuffle = True, max_count = -1** |
| 102 | + - Entire library included; LMS randomizes order per student *(V1's "random mode")*. |
| 103 | + - Author manually includes blocks; LMS randomizes order per student. |
| 104 | + |
| 105 | + * - **shuffle = True, max_count > 0** |
| 106 | + - Entire library included; LMS selects random subset in random order for each student *(V1's "random mode")*. |
| 107 | + - Author manually includes blocks; LMS selects random subset in random order for each student *(V2+'s desired "enhanced static" mode)*. |
| 108 | + |
| 109 | + * - **shuffle = False, max_count = -1** |
| 110 | + - Entire library included and shown to every learner in original order. |
| 111 | + - Author manually includes blocks; they are shown to every learner in original order. *(V2's "static" mode)*. |
| 112 | + |
| 113 | + * - **shuffle = False, max_count > 0** |
| 114 | + - Entire library included, LMS selects random subset in original order *(No known use cases)*. |
| 115 | + - Author manually includes blocks, LMS selects random subset in original order *(No known use cases)*. |
| 116 | + |
| 117 | + |
| 118 | +At first, we will only aim to support the "random mode" behaviors plus the new "static mode" behavior. Validation will be used to ensure that the other modes are not available. In the future, we could expect to loosen this restriction. |
| 119 | + |
| 120 | +The final library_content block schema, with all changes, will look like this: |
| 121 | + |
| 122 | +.. list-table:: |
| 123 | + :header-rows: 1 |
| 124 | + |
| 125 | + * - Field Name |
| 126 | + - Scope, Type |
| 127 | + - Default |
| 128 | + - Example |
| 129 | + - Description |
| 130 | + |
| 131 | + * - source_library_id |
| 132 | + - settings, str|None |
| 133 | + - None |
| 134 | + - "lib:Open-edX:ExampleLib" |
| 135 | + - Key of V1 or V2 source library; None means unselected. |
| 136 | + |
| 137 | + * - source_library\_ version |
| 138 | + - settings, str|None |
| 139 | + - None |
| 140 | + - TBD |
| 141 | + - Version of V1 source library (MongoID string) or V2 source library (a stringified int). None means latest. |
| 142 | + |
| 143 | + * - manual |
| 144 | + - settings, bool |
| 145 | + - False |
| 146 | + - True |
| 147 | + - When False, all library blocks matching capa_type are copied as library_content children, including newly-added library blocks when upgrading source library version. When True, the course author is propmted to pick specific blocks from the library; these blocks become the library_content children. Studio respects these manual block choices (i.e., it won't auto-add new library blocks when the library version is updated). |
| 148 | + |
| 149 | + * - capa_type |
| 150 | + - settings, str |
| 151 | + - "any" |
| 152 | + - "symbolicresponse" |
| 153 | + - Unless "any": Include only problems, and only problems of this response type. Setting manual to True overrides this filter, however it could still be used for filtering in the block-picker UI. *Note: In future versions, we may want to have filters available that are not specific to Problem or any other block type.* |
| 154 | + |
| 155 | + * - children |
| 156 | + - children, list of XBlocks |
| 157 | + - [] |
| 158 | + - ``block-v1:...+type@problem+block@A`` |
| 159 | + ``block-v1:...+type@problem+block@B`` |
| 160 | + ``block-v1:...+type@problem+block@D`` |
| 161 | + ``block-v1:...+type@problem+block@F`` |
| 162 | + - Standard XBlock field that holds references to the child XBlocks. This field is only populated after the library content block is saved, at which time the matching XBlocks (e.g. filtered by capa_type, or hand-picked by author when manual is True) from the library are copied ("included") to become children of this block. Learners will often only see a subset of these children (up to max_count); see ``selected``. Course author may apply edits that are local to these included child blocks. Such edits to child blocks' Scope.settings fields will persist across library version upgrades, whereas edits to child blocks' Scope.content fields are wiped out when library version is upgraded. |
| 163 | + |
| 164 | + * - max_count |
| 165 | + - settings, int |
| 166 | + - 1 |
| 167 | + - 2 |
| 168 | + - Number of children that LMS will randomly select for each learner. -1 means all. |
| 169 | + |
| 170 | + * - shuffle |
| 171 | + - settings, bool |
| 172 | + - True |
| 173 | + - False |
| 174 | + - If False, the order of each learner's selected blocks will match the order of children. If True, the order will be randomized for each learner. |
| 175 | + |
| 176 | + * - selected |
| 177 | + - user_state, list of (block type, block id) tuples |
| 178 | + - [] |
| 179 | + - [("problem", "A"), ("problem", "F")] |
| 180 | + - An ordered sub-list of the children, selected by the LMS to be shown to a given learner. At most max_count, unless max_count = -1. Once populated, this is kept stable for the learner, unless a the children or max_count are changed, in which case it will have blocks added or removed as necessary. If the list changes, it is re-shuffled. |
| 181 | + |
| 182 | + |
| 183 | +.. figure:: ./0003-library-content-block-schema/library-block-flow.svg |
| 184 | + |
| 185 | + The series of transformations library blocks go through, from the source libraries to the learner's unit view. Source `available on LucidChart`_; ask Axim if you need to edit it. |
| 186 | + |
| 187 | +.. _available on LucidChart: https://lucid.app/lucidchart/4cfbb5d6-86f3-4cd6-98cf-c85c123a8cb7/edit?viewport_loc=-208%2C-540%2C2190%2C1564%2C0_0&invitationId=inv_7c5dea04-a713-4f45-b73e-e06e20fcfa9d |
| 188 | + |
| 189 | +Consequences |
| 190 | +************ |
| 191 | + |
| 192 | +We will implement the schema as described above, most likely in the following phases: |
| 193 | + |
| 194 | +#. Add support for V2 library sources to the existing random-only library_content block (no field schema changes yet). |
| 195 | + |
| 196 | +#. Add the manual and shuffle fields. Use validation to ensure that only the following permuations are allowed: |
| 197 | + |
| 198 | + * Existing "random mode" (shuffle = True, manual = False) |
| 199 | + |
| 200 | + * New "static" mode (shuffle = False, manual = True, max_count = -1) |
| 201 | + |
| 202 | +#. Beta release of V2 library authoring on edX.org. |
| 203 | + |
| 204 | +#. Migrate V1 libraries to V2 on edX.org for all users. |
| 205 | + |
| 206 | +Future work, in no particular order: |
| 207 | + |
| 208 | + * If supported by product needs, then loosen restrictions on fields, potentially enabling the full matrix of eight "modes" described above. |
| 209 | + |
| 210 | + * `Remove support for V1 content libraries.`_ |
| 211 | + |
| 212 | +.. _Remove support for V1 content libraries: https://github.com/openedx/edx-platform/issues/32457 |
| 213 | + |
| 214 | + |
| 215 | +Rejected Alternatives |
| 216 | +********************* |
| 217 | + |
| 218 | +* **Utilize the "mode" field to distinguish between random, manual, and any future modes.** This suffers from a matrix problem: with any given block behavior, it is possible that combination of those behaviors is a desirable "mode". For example, combining random and manual modes into a "random-from-manual-selection" is a desired future feature, but that new mode overlaps in functionality with both random and manual modes; in fact, random and manual modes would most just be special-cases of random-from-manual-selection mode. If the block were ever to be extended to incorporate, for example, recommendations, that would further multiply the available modes. The resulting code and interface would be harder to reason about than the flat list of flags and features that we decide on here. |
| 219 | + |
| 220 | +* **Implement V2 support in a separate block rather than the existing block.** This would make it harder to automatically migrate all modulestore libraries into blockstore, as all usages of the V1 library_content block would still exist. The ``library_sourced`` block was an implementation in this direction, but we deleted it. |
| 221 | + |
| 222 | +* **Implement non-randomized modes in a separate block.** This would yield a less flexible user experience, as it would force authors to pick from two separate blocks in the Studio UI depending on whether they want random or non-randomized (which is still feasible with the ADR's direction, but is not mandatory). Furthermore, it would create duplicated logic between the two blocks on the backend, increasing bug surface area. The ``library_sourced`` block was an implementation in this direction, but we deleted it. |
| 223 | + |
| 224 | + |
| 225 | + |
0 commit comments