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

Fix single quiz selection flow #12274

Merged
merged 23 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
8bae1d7
Allow any data type to define a functional default.
rtibbles Jun 11, 2024
6e269b8
Remove use of section_id for routing of sections. Prefer sectionIndex.
rtibbles Jun 14, 2024
a183a15
Update channel API to add filtering by quizzes and make consistent wi…
rtibbles Jun 12, 2024
27bf615
Update question titles to be blank if not set by user.
rtibbles Jun 12, 2024
daebea5
Fix bug with resource fetching parameters.
rtibbles Jun 12, 2024
ce7cdcc
Update strings for section editor.
rtibbles Jun 14, 2024
205e66d
Update select quiz workflow to use new components and composable.
rtibbles Jun 14, 2024
53f602e
Clean up unused components and state.
rtibbles Jun 12, 2024
062baa6
Remove previous route tracking in resource selection.
rtibbles Jun 12, 2024
5484434
Fix all references to root quiz creation route to include sectionIndex.
rtibbles Jun 12, 2024
7d8a296
Remove question_count from sections.
rtibbles Jun 13, 2024
dc8e810
Update remaining references to question id to item.
rtibbles Jun 13, 2024
e12fa7f
Update resource selection to add a specific number of resources each …
rtibbles Jun 14, 2024
abc9357
Update validation and messaging to better guide users to add questions.
rtibbles Jun 13, 2024
bde9766
Handle practice quizzes longer than "MAX_QUESTIONS" by spreading them…
rtibbles Jun 13, 2024
5fc9975
Move MAX_QUESTIONS to core constants. Rename for clarity.
rtibbles Jun 14, 2024
eb22aa5
Enforce section questions max length via API serializer.
rtibbles Jun 14, 2024
241c510
Add margin top to topic warning.
rtibbles Jun 14, 2024
6fcbb06
Clean up unused contentnode api endpoint.
rtibbles Jun 14, 2024
5040a84
Ensure that query params are validated before being used to query.
rtibbles Jun 14, 2024
e749af9
Don't show num assessments info for practice quiz selection.
rtibbles Jun 14, 2024
69699d9
Ensure we reset quizHasChanged on save.
rtibbles Jun 16, 2024
9783504
Only reuse necessary params from the route.
rtibbles Jun 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions kolibri/core/assets/src/api-resources/contentNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,6 @@ export default new Resource({
fetchDescendantsAssessments(ids) {
return this.getListEndpoint('descendants_assessments', { ids });
},
fetchNodeAssessments(ids) {
return this.getListEndpoint('node_assessments', { ids });
},
fetchRecommendationsFor(id, getParams) {
return this.fetchDetailCollection('recommendations_for', id, getParams);
},
Expand Down
4 changes: 4 additions & 0 deletions kolibri/core/assets/src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,7 @@ export const Presets = Object.freeze({
FORMAL: 'formal',
NONFORMAL: 'nonformal',
});

// This should be kept in sync with the value in
// kolibri/core/exams/constants.py
export const MAX_QUESTIONS_PER_QUIZ_SECTION = 50;
27 changes: 18 additions & 9 deletions kolibri/core/assets/src/exams/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import uniq from 'lodash/uniq';
import some from 'lodash/some';
import { v4 as uuidv4 } from 'uuid';
import { MAX_QUESTIONS_PER_QUIZ_SECTION } from 'kolibri.coreVue.vuex.constants';
import { ExamResource, ContentNodeResource } from 'kolibri.resources';

/*
Expand Down Expand Up @@ -96,17 +96,26 @@ function annotateQuestionsWithItem(questions) {
export function convertExamQuestionSourcesV2toV3({ question_sources, learners_see_fixed_order }) {
// In V2, question_sources are questions so we add them
// to the newly created section's `questions` property
const questions = question_sources;
return [
{
section_id: uuidv4(),
const questions = question_sources.map(item => {
return {
...item,
// Overwrite the exercise title as the question title
// is user editable in the V3 schema, so we set it to
// blank to indicate it has not been set by an editor.
title: '',
};
});
const sections = [];

while (questions.length > 0) {
sections.push({
section_title: '',
description: '',
questions,
questions: questions.splice(0, MAX_QUESTIONS_PER_QUIZ_SECTION),
learners_see_fixed_order,
question_count: questions.length,
},
];
});
}
return sections;
}

/**
Expand Down
3 changes: 1 addition & 2 deletions kolibri/core/assets/src/objectSpecs.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,7 @@ export function objectWithDefaults(object, spec) {
// set defaults if necessary
if (isUndefined(cloned[dataKey]) && !isUndefined(options.default)) {
// arrays and objects need to use a function to return defaults
const needsFunction = options.type === Array || options.type === Object;
if (needsFunction && options.default !== null) {
if (options.default instanceof Function) {
cloned[dataKey] = options.default();
}
// all other types can be assigned directly
Expand Down
94 changes: 55 additions & 39 deletions kolibri/core/assets/test/exams/utils.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import map from 'lodash/map';
import { ContentNodeResource } from 'kolibri.resources';
import { MAX_QUESTIONS_PER_QUIZ_SECTION } from 'kolibri.coreVue.vuex.constants';
import { convertExamQuestionSources } from '../../src/exams/utils';

// map of content IDs to lists of question IDs
Expand Down Expand Up @@ -116,21 +117,21 @@ describe('exam utils', () => {
{
exercise_id: 'E1',
question_id: 'Q1',
title: 'Question 1',
title: '',
counter_in_exercise: 1,
item: 'E1:Q1',
},
{
exercise_id: 'E1',
question_id: 'Q2',
title: 'Question 2',
title: '',
counter_in_exercise: 2,
item: 'E1:Q2',
},
{
exercise_id: 'E2',
question_id: 'Q1',
title: 'Question 1',
title: '',
counter_in_exercise: 1,
item: 'E2:Q1',
},
Expand All @@ -139,16 +140,12 @@ describe('exam utils', () => {

it('returns an array of newly structured objects with old question sources in questions', async () => {
const converted = await convertExamQuestionSources(exam);
// The section id is randomly generated so just test that it is there and is set on the object
expect(converted.question_sources[0].section_id).toBeTruthy();
expect(converted.question_sources).toEqual([
{
section_id: converted.question_sources[0].section_id,
section_title: '',
description: '',
questions: expectedSources.sort(),
learners_see_fixed_order: true,
question_count: 3,
},
]);
});
Expand All @@ -163,17 +160,15 @@ describe('exam utils', () => {
const converted = await convertExamQuestionSources(exam);
expect(converted.question_sources).toEqual([
{
section_id: converted.question_sources[0].section_id,
section_title: '',
description: '',
questions: expectedSources.sort(),
learners_see_fixed_order: false,
question_count: 3, // There are only 3 questions in the question_sources
},
]);
});
});
describe('convertExamQuestionSources converting from V1 to V2', () => {
describe('convertExamQuestionSources converting from V1 to V3', () => {
it('returns a question_sources array with a counter_in_exercise field', async () => {
const exam = {
data_model_version: 1,
Expand All @@ -200,21 +195,21 @@ describe('exam utils', () => {
{
exercise_id: 'E1',
question_id: 'Q1',
title: 'Question 1',
title: '',
counter_in_exercise: 1,
item: 'E1:Q1',
},
{
exercise_id: 'E1',
question_id: 'Q2',
title: 'Question 2',
title: '',
counter_in_exercise: 2,
item: 'E1:Q2',
},
{
exercise_id: 'E2',
question_id: 'Q1',
title: 'Question 1',
title: '',
counter_in_exercise: 1,
item: 'E2:Q1',
},
Expand Down Expand Up @@ -244,22 +239,43 @@ describe('exam utils', () => {
{
question_id: 'Q1',
exercise_id: 'E1',
title: 'Question 1',
title: '',
counter_in_exercise: 4000,
item: 'E1:Q1',
},
{
question_id: 'Q1',
exercise_id: 'E2',
title: 'Question 2',
title: '',
counter_in_exercise: 1,
item: 'E2:Q1',
},
]);
});
it('renames creates multiple sections if the questions are longer than MAX_QUESTIONS_PER_QUIZ_SECTION', async () => {
const question_sources = [];
for (let i = 0; i < MAX_QUESTIONS_PER_QUIZ_SECTION + 1; i++) {
question_sources.push({
exercise_id: 'E1',
question_id: `Q${i}`,
title: `Question ${i + 1}`,
counter_in_exercise: i + 1,
});
}
const exam = {
data_model_version: 1,
question_sources,
};
const converted = await convertExamQuestionSources(exam);
expect(converted.question_sources.length).toEqual(2);
expect(converted.question_sources[0].questions.length).toEqual(
MAX_QUESTIONS_PER_QUIZ_SECTION
);
expect(converted.question_sources[1].questions.length).toEqual(1);
});
});

describe('convertExamQuestionSources converting from V0 to V2', () => {
describe('convertExamQuestionSources converting from V0 to V3', () => {
it('should return 10 specific ordered questions from 3 exercises', async () => {
const exam = {
data_model_version: 0,
Expand Down Expand Up @@ -303,61 +319,61 @@ describe('exam utils', () => {
counter_in_exercise: 16,
question_id: 'fc60ecb9f83f505fa31e734e517e6523',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 13,
question_id: 'd4623921a2ef5ddaa39048c0f7a6fe06',
exercise_id: 'b186a2a3ae8e51dd867614db03eb3783',
title: 'Find 1 more or 1 less than a number',
title: '',
},
{
counter_in_exercise: 6,
question_id: '2f5fdbc49ce35310abf49971867ac94e',
exercise_id: 'b186a2a3ae8e51dd867614db03eb3783',
title: 'Find 1 more or 1 less than a number',
title: '',
},
{
counter_in_exercise: 15,
question_id: '19421254d90d520d981bd07fd6ede9b2',
exercise_id: 'b9444e7d11395946b2e14edb5dc4670f',
title: 'Count in order',
title: '',
},
{
counter_in_exercise: 1,
question_id: '1e0ce47a58465b2cb298acd3b893dce5',
exercise_id: 'b9444e7d11395946b2e14edb5dc4670f',
title: 'Count in order',
title: '',
},
{
counter_in_exercise: 10,
question_id: 'd3ac055fa4ad599bbd30a00eaeb93e5e',
exercise_id: 'b186a2a3ae8e51dd867614db03eb3783',
title: 'Find 1 more or 1 less than a number',
title: '',
},
{
counter_in_exercise: 1,
question_id: 'a5f508eb2ba05d429812dc43b577ef03',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 17,
question_id: '8358fbbd0a285e9d99da558094eabd4c',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 2,
question_id: '3aeb023925e35001865091de1fb4d3ae',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 3,
question_id: 'a654dec351af5bdf937566e46b7c2fc3',
exercise_id: 'b9444e7d11395946b2e14edb5dc4670f',
title: 'Count in order',
title: '',
},
];

Expand Down Expand Up @@ -386,61 +402,61 @@ describe('exam utils', () => {
counter_in_exercise: 20,
question_id: 'fc5958e2a67d5cd2bd48962e9e1c35c3',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 3,
question_id: 'beb5eae9491c564fb6bc5b9c1421d085',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 2,
question_id: '3aeb023925e35001865091de1fb4d3ae',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 17,
question_id: '8358fbbd0a285e9d99da558094eabd4c',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 6,
question_id: '4dd2526065ee572998a06b1fdae9cb4b',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 5,
question_id: '10e6239f9cf35b75b0cf75ca7f7e6a14',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 12,
question_id: 'e92a277052cf56e2aaae44338fa0bfec',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 9,
question_id: '6ef7996754de54ad92b660d06436976c',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 4,
question_id: 'b19341ebca9a5bdb817cdc31b8d62993',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 8,
question_id: 'e226724765085214acfb807942918fed',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
];
expect(converted.question_sources[0].questions).toEqual(
Expand Down Expand Up @@ -478,19 +494,19 @@ describe('exam utils', () => {
counter_in_exercise: 9,
question_id: '6ef7996754de54ad92b660d06436976c',
exercise_id: '69e5e6abf479581483d441b83d7d76f4',
title: 'Count with small numbers',
title: '',
},
{
counter_in_exercise: 2,
question_id: '952857e446b95c5da36226f59237ffcc',
exercise_id: 'b9444e7d11395946b2e14edb5dc4670f',
title: 'Count in order',
title: '',
},
{
counter_in_exercise: 7,
question_id: '5a56a46b261d5ff7b3f870cf09c6952f',
exercise_id: 'b186a2a3ae8e51dd867614db03eb3783',
title: 'Find 1 more or 1 less than a number',
title: '',
},
];
expect(converted.question_sources[0].questions).toEqual(
Expand Down
Loading