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

Quiz creation resource selection: Topic selection & "Select all" #11864

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 9 additions & 3 deletions kolibri/plugins/coach/assets/src/composables/useQuizResources.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,17 @@ export default function useQuizResources({ topicId } = {}) {
}

/** @returns {Boolean} Whether the given node should be displayed with a checkbox
* @description Returns whether the given node is an exercise or not -- although, could be
* extended in the future to permit topic-level selection if desired
* @description Returns true for exercises and for topics that have no topic children and no
* more children to load
*/
function hasCheckbox(node) {
return node.kind === ContentNodeKinds.EXERCISE;
return (
node.kind === ContentNodeKinds.EXERCISE ||
// Has children, no more to load, and no children are topics
(node.children &&
!node.children.more &&
!node.children.results.some(c => c.kind === ContentNodeKinds.TOPIC))
);
}

function setResources(r) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,14 @@
</div>
</div>

<div
v-if="showTopicSizeWarning()"
class="shadow"
:style=" { padding: '1em', marginBottom: '1em', backgroundColor: $themePalette.grey.v_100 }"
>
{{ cannotSelectSomeTopicWarning$() }}
</div>

<ResourceSelectionBreadcrumbs
v-if="isTopicIdSet"
:ancestors="topic.ancestors"
Expand All @@ -62,18 +70,19 @@
@clear="clearSearchTerm"
@searchterm="handleSearchTermChange"
/>

<ContentCardList
:contentList="contentList"
:showSelectAll="true"
:showSelectAll="showSelectAll"
:viewMoreButtonState="viewMoreButtonState"
:selectAllChecked="isSelectAllChecked"
:selectAllChecked="selectAllChecked"
:selectAllIndeterminate="selectAllIndeterminate"
:contentIsChecked="contentPresentInWorkingResourcePool"
:contentHasCheckbox="hasCheckbox"
:contentCardMessage="selectionMetadata"
:contentCardLink="contentLink"
:selectAllIndeterminate="selectAllIndeterminate"
:loadingMoreState="loadingMore"
@changeselectall="toggleTopicInWorkingResources"
@changeselectall="handleSelectAll"
@change_content_card="toggleSelected"
@moreresults="fetchMoreResources"
/>
Expand Down Expand Up @@ -154,6 +163,7 @@
numberOfSelectedResources$,
numberOfResources$,
selectedResourcesInformation$,
cannotSelectSomeTopicWarning$,
} = enhancedQuizManagementStrings;

// TODO let's not use text for this
Expand All @@ -178,7 +188,29 @@
* a list of unique resources to avoid unnecessary duplication
*/
function addToWorkingResourcePool(resources = []) {
workingResourcePool.value = uniqWith([...workingResourcePool.value, ...resources], isEqual);
workingResourcePool.value = uniqWith(
[
...workingResourcePool.value,
...resources.filter(r => r.kind === ContentNodeKinds.EXERCISE),
],
isEqual
);
}

/**
* @description Returns the list of Exercises which can possibly be selected from the current
* contentList taking into consideration the logic for whether a topic can be selected or not.
* @returns {QuizExercise[]} - All contents which can be selected
*/
function selectableContentList() {
return contentList.value.reduce((newList, content) => {
if (content.kind === ContentNodeKinds.TOPIC && hasCheckbox(content)) {
newList = [...newList, ...content.children.results];
} else {
newList.push(content);
}
return newList;
}, []);
}

/**
Expand All @@ -202,6 +234,9 @@
*/
function contentPresentInWorkingResourcePool(content) {
const workingResourceIds = workingResourcePool.value.map(wr => wr.id);
if (content.kind === ContentNodeKinds.TOPIC) {
return content.children.results.every(child => workingResourceIds.includes(child.id));
}
return workingResourceIds.includes(content.id);
}

Expand All @@ -226,6 +261,63 @@
});
}

const selectAllChecked = computed(() => {
// Returns true if all the resources in the topic are in the working resource pool
const workingResourceIds = workingResourcePool.value.map(wr => wr.id);
const selectableIds = selectableContentList().map(content => content.id);
return selectableIds.every(id => workingResourceIds.includes(id));
});

const selectAllIndeterminate = computed(() => {
// Returns true if some, but not all, of the resources in the topic are in the working
// resource
const workingResourceIds = workingResourcePool.value.map(wr => wr.id);
const selectableIds = selectableContentList().map(content => content.id);
return !selectAllChecked.value && selectableIds.some(id => workingResourceIds.includes(id));
});

const showSelectAll = computed(() => {
return contentList.value.every(content => hasCheckbox(content));
});

function handleSelectAll(isChecked) {
if (isChecked) {
this.addToWorkingResourcePool(selectableContentList());
} else {
this.contentList.forEach(content => {
var contentToRemove = [];
if (content.kind === ContentNodeKinds.TOPIC) {
contentToRemove = content.children.results;
} else {
contentToRemove.push(content);
}
contentToRemove.forEach(c => {
this.removeFromWorkingResourcePool(c);
});
});
}
}

/**
* @param {Object} param
* @param {ContentNode} param.content
* @param {boolean} param.checked
* @affects workingResourcePool - Adds or removes the content from the workingResourcePool
* When given a topic, it adds or removes all the exercises in the topic from the
* workingResourcePool. This assumes that topics which should not be added are not able to
* be checked and does not do any additional checks.
*/
function toggleSelected({ content, checked }) {
content = content.kind === ContentNodeKinds.TOPIC ? content.children.results : [content];
if (checked) {
this.addToWorkingResourcePool(content);
} else {
content.forEach(c => {
this.removeFromWorkingResourcePool(c);
});
}
}

const {
hasCheckbox,
topic,
Expand Down Expand Up @@ -341,6 +433,11 @@
}

return {
selectAllChecked,
selectAllIndeterminate,
showSelectAll,
handleSelectAll,
toggleSelected,
topic,
topicId,
contentList,
Expand All @@ -353,6 +450,7 @@
resetWorkingResourcePool,
contentPresentInWorkingResourcePool,
//contentList,
cannotSelectSomeTopicWarning$,
Copy link
Member

@AllanOXDi AllanOXDi Feb 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also want to note that this string is showing at up at the first level when I use back navigation to navigate back . In addition, I tested with QA channel and tigil-fajod but one will not show up at the end of the routes when using the back button to exit the navigation except the active working resource pool .
Screenshot 2024-02-27 at 00 03 02

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that once #11862 is merged with this that this will be resolved - I'll make note of this to check it specifically in testing after merging.

sectionSettings$,
selectFromBookmarks$,
numberOfSelectedBookmarks$,
Expand Down Expand Up @@ -382,20 +480,6 @@
isTopicIdSet() {
return this.$route.params.topic_id;
},
isSelectAllChecked() {
// Returns true if all the resources in the topic are in the working resource pool
const workingResourceIds = this.workingResourcePool.map(wr => wr.id);
return this.contentList.every(content => workingResourceIds.includes(content.id));
},
selectAllIndeterminate() {
// Returns true if some, but not all, of the resources in the topic are in the working
// resource
const workingResourceIds = this.workingResourcePool.map(wr => wr.id);
return (
!this.isSelectAllChecked &&
this.contentList.some(content => workingResourceIds.includes(content.id))
);
},

getBookmarksLink() {
// Inject the showBookmarks parameter so that
Expand Down Expand Up @@ -423,6 +507,12 @@
},
},
methods: {
showTopicSizeWarningCard(content) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me think that we need to properly update this method to handle to topic selection- for now I think it's getting lost in the way we are handling back route navigation.

return !this.hasCheckbox(content) && content.kind === ContentNodeKinds.TOPIC;
},
showTopicSizeWarning() {
return this.contentList.some(this.showTopicSizeWarningCard);
},
/** @public */
focusFirstEl() {
this.$refs.textbox.focus();
Expand All @@ -446,22 +536,6 @@

return {}; // or return {} if you prefer an empty object
},
toggleSelected({ content, checked }) {
if (checked) {
this.addToWorkingResourcePool([content]);
} else {
this.removeFromWorkingResourcePool(content);
}
},
toggleTopicInWorkingResources(isChecked) {
if (isChecked) {
this.addToWorkingResourcePool(this.contentList);
} else {
this.contentList.forEach(content => {
this.removeFromWorkingResourcePool(content);
});
}
},
topicListingLink({ topicId }) {
return this.$router.getRoute(
PageNames.QUIZ_SELECT_RESOURCES,
Expand Down Expand Up @@ -607,4 +681,9 @@
margin-top: 2em;
}

.shadow {
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14),
0 2px 1px -1px rgba(0, 0, 0, 0.12);
}

</style>
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@
:link="contentCardLink(content)"
:numCoachContents="content.num_coach_contents"
:isLeaf="content.is_leaf"
/>
>
<template #notice>
<slot name="notice" :content="content"></slot>
</template>
</LessonContentCard>
</li>
</ul>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
:isTopic="isTopic"
/>
</div>
<slot name="notice"></slot>
</div>

</router-link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,8 @@ export const enhancedQuizManagementStrings = createTranslator('EnhancedQuizManag
message:
'{count, number, integer} of {total, number, integer} {total, plural, one {resource selected} other {resources selected}}',
},
cannotSelectSomeTopicWarning: {
message:
'You can only select folders with 12 or less exercises and no subfolders to avoid oversized quizzes.',
},
});