Fix self-evalution checkboxes

This commit is contained in:
Daniel Egger 2022-09-28 16:19:01 +02:00
parent e230c0b8e5
commit bdae082550
11 changed files with 84 additions and 62 deletions

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { Circle } from '@/services/circle'
import type { Circle } from '@/services/circle'
import ItFullScreenModal from '@/components/ui/ItFullScreenModal.vue'
const props = defineProps<{

View File

@ -1,6 +1,6 @@
<script setup lang="ts">
import ItCheckbox from '@/components/ui/ItCheckbox.vue'
import type { LearningContent, LearningSequence } from '@/types'
import type { CourseCompletionStatus, LearningContent, LearningSequence } from '@/types'
import { useCircleStore } from '@/stores/circle'
import { computed } from 'vue'
import _ from 'lodash'
@ -14,7 +14,11 @@ const props = defineProps<{
const circleStore = useCircleStore()
function toggleCompleted(learningContent: LearningContent) {
circleStore.markCompletion(learningContent, !learningContent.completed)
let completionStatus: CourseCompletionStatus = 'success'
if (learningContent.completion_status === 'success') {
completionStatus = 'fail'
}
circleStore.markCompletion(learningContent, completionStatus)
}
const someFinished = computed(() => {
@ -36,7 +40,7 @@ const allFinished = computed(() => {
const continueTranslationKeyTuple = computed(() => {
if (props.learningSequence && circleStore.circle) {
const lastFinished = _.findLast(circleStore.circle.flatLearningContents, (learningContent) => {
return learningContent.completed
return learningContent.completion_status === 'success'
})
if (!lastFinished) {
@ -91,7 +95,7 @@ const learningSequenceBorderClass = computed(() => {
class="flex gap-4 pb-3 lg:pb-6"
>
<ItCheckbox
:modelValue="learningContent.completed"
:modelValue="learningContent.completion_status === 'success'"
:onToggle="() => toggleCompleted(learningContent)"
:data-cy="`${learningContent.slug}`"
>

View File

@ -2,7 +2,7 @@
import * as log from 'loglevel'
import { computed, reactive } from 'vue'
import { useCircleStore } from '@/stores/circle'
import { LearningUnit } from '@/types'
import type { LearningUnit } from '@/types'
log.debug('LearningContent.vue setup')
@ -62,24 +62,24 @@ function handleContinue() {
<div class="mt-4 lg:mt-8 flex flex-col lg:flex-row justify-between gap-6">
<button
@click="circleStore.markCompletion(currentQuestion, true)"
@click="circleStore.markCompletion(currentQuestion, 'success')"
class="flex-1 inline-flex items-center text-left p-4 border"
:class="{
'border-green-500': currentQuestion.completed,
'border-2': currentQuestion.completed,
'border-gray-500': !currentQuestion.completed,
'border-green-500': currentQuestion.completion_status === 'success',
'border-2': currentQuestion.completion_status === 'success',
'border-gray-500': currentQuestion.completion_status !== 'success',
}"
>
<it-icon-smiley-happy class="w-16 h-16 mr-4"></it-icon-smiley-happy>
<span class="font-bold text-xl"> Ja, ich kann das. </span>
</button>
<button
@click="circleStore.markCompletion(currentQuestion, false)"
@click="circleStore.markCompletion(currentQuestion, 'fail')"
class="flex-1 inline-flex items-center text-left p-4 border"
:class="{
'border-orange-500': currentQuestion.completed === false,
'border-2': currentQuestion.completed === false,
'border-gray-500': currentQuestion.completed === true || currentQuestion.completed === undefined,
'border-orange-500': currentQuestion.completion_status === 'fail',
'border-2': currentQuestion.completion_status === 'fail',
'border-gray-500': currentQuestion.completion_status !== 'fail'
}"
>
<it-icon-smiley-thinking class="w-16 h-16 mr-4"></it-icon-smiley-thinking>

View File

@ -1,13 +1,14 @@
import type {
CircleChild,
CircleCompletion,
CircleGoal,
CircleJobSituation,
CourseCompletion,
CourseCompletionStatus,
CourseWagtailPage,
LearningContent,
LearningSequence,
LearningUnit,
LearningUnitQuestion,
CourseWagtailPage,
} from '@/types'
import type { LearningPath } from '@/services/learningPath'
@ -23,6 +24,7 @@ function _createEmptyLearningUnit(parentLearningSequence: LearningSequence): Lea
parentLearningSequence: parentLearningSequence,
children: [],
last: true,
completion_status: 'unknown',
}
}
@ -112,7 +114,7 @@ export function parseLearningSequences (circle: Circle, children: CircleChild[])
export class Circle implements CourseWagtailPage {
readonly type = 'learnpath.Circle';
readonly learningSequences: LearningSequence[];
readonly completed: boolean;
completion_status: CourseCompletionStatus = 'unknown'
nextCircle?: Circle;
previousCircle?: Circle;
@ -129,7 +131,6 @@ export class Circle implements CourseWagtailPage {
public readonly parentLearningPath?: LearningPath,
) {
this.learningSequences = parseLearningSequences(this, this.children);
this.completed = false;
}
public static fromJson(json: any, learningPath?: LearningPath): Circle {
@ -187,7 +188,7 @@ export class Circle implements CourseWagtailPage {
public someFinishedInLearningSequence(translationKey: string): boolean {
if (translationKey) {
return this.flatChildren.filter((lc) => {
return lc.completed && lc.parentLearningSequence?.translation_key === translationKey;
return lc.completion_status === 'success' && lc.parentLearningSequence?.translation_key === translationKey;
}).length > 0;
}
@ -197,7 +198,7 @@ export class Circle implements CourseWagtailPage {
public allFinishedInLearningSequence(translationKey: string): boolean {
if (translationKey) {
const finishedContents = this.flatChildren.filter((lc) => {
return lc.completed && lc.parentLearningSequence?.translation_key === translationKey;
return lc.completion_status === 'success' && lc.parentLearningSequence?.translation_key === translationKey;
}).length;
const totalContents = this.flatChildren.filter((lc) => {
@ -209,15 +210,15 @@ export class Circle implements CourseWagtailPage {
return false;
}
public parseCompletionData(completionData: CircleCompletion[]) {
public parseCompletionData(completionData: CourseCompletion[]) {
this.flatChildren.forEach((page) => {
const pageIndex = completionData.findIndex((e) => {
return e.page_key === page.translation_key;
});
if (pageIndex >= 0) {
page.completed = completionData[pageIndex].completed;
page.completion_status = completionData[pageIndex].completion_status;
} else {
page.completed = undefined;
page.completion_status = 'unknown';
}
});

View File

@ -1,11 +1,18 @@
import * as _ from 'lodash'
import type { CircleCompletion, LearningContent, LearningPathChild, CourseWagtailPage, Topic } from '@/types'
import type {
CourseCompletion,
CourseCompletionStatus,
CourseWagtailPage,
LearningContent,
LearningPathChild,
Topic,
} from '@/types'
import { Circle } from '@/services/circle'
function getLastCompleted(learningPathKey: string, completionData: CircleCompletion[]) {
return _.orderBy(completionData, ['updated_at'], 'desc').find((c: CircleCompletion) => {
return c.completed && c.learning_path_key === learningPathKey && c.page_type === 'learnpath.LearningContent'
function getLastCompleted(courseId: number, completionData: CourseCompletion[]) {
return _.orderBy(completionData, ['updated_at'], 'desc').find((c: CourseCompletion) => {
return c.completion_status === 'success' && c.course === courseId && c.page_type === 'learnpath.LearningContent'
})
}
@ -14,9 +21,10 @@ export class LearningPath implements CourseWagtailPage {
public topics: Topic[]
public circles: Circle[]
public nextLearningContent?: LearningContent
readonly completion_status: CourseCompletionStatus = 'unknown'
public static fromJson(json: any, completionData: CircleCompletion[]): LearningPath {
return new LearningPath(json.id, json.slug, json.title, json.translation_key, json.children, completionData)
public static fromJson(json: any, completionData: CourseCompletion[]): LearningPath {
return new LearningPath(json.id, json.slug, json.title, json.translation_key, json.course.id, json.children, completionData)
}
constructor(
@ -24,8 +32,9 @@ export class LearningPath implements CourseWagtailPage {
public readonly slug: string,
public readonly title: string,
public readonly translation_key: string,
public readonly courseId: number,
public children: LearningPathChild[],
completionData?: any
completionData?: CourseCompletion[]
) {
// parse children
this.topics = []
@ -42,7 +51,9 @@ export class LearningPath implements CourseWagtailPage {
}
if (page.type === 'learnpath.Circle') {
const circle = Circle.fromJson(page, this)
circle.parseCompletionData(completionData)
if (completionData) {
circle.parseCompletionData(completionData)
}
if (topic) {
topic.circles.push(circle)
}
@ -59,17 +70,21 @@ export class LearningPath implements CourseWagtailPage {
this.topics.push(topic)
}
this.calcNextLearningContent(completionData)
if (completionData) {
this.calcNextLearningContent(completionData)
}
}
public calcNextLearningContent(completionData: CircleCompletion[]): void {
public calcNextLearningContent(completionData: CourseCompletion[]): void {
this.nextLearningContent = undefined
const lastCompletedLearningContent = getLastCompleted(this.translation_key, completionData)
const lastCompletedLearningContent = getLastCompleted(this.courseId, completionData)
if (lastCompletedLearningContent) {
const lastCircle = this.circles.find(
(circle) => circle.translation_key === lastCompletedLearningContent.circle_key
(circle) => {
return circle.flatLearningContents.find((learningContent) => learningContent.translation_key === lastCompletedLearningContent.page_key)
}
)
if (lastCircle) {
const lastLearningContent = lastCircle.flatLearningContents.find(

View File

@ -2,7 +2,7 @@ import * as log from 'loglevel'
import { defineStore } from 'pinia'
import type { LearningContent, LearningUnit, LearningUnitQuestion } from '@/types'
import type { CourseCompletionStatus, LearningContent, LearningUnit, LearningUnitQuestion } from '@/types'
import type { Circle } from '@/services/circle'
import { itPost } from '@/fetchHelpers'
import { useLearningPathStore } from '@/stores/learningPath'
@ -61,12 +61,12 @@ export const useCircleStore = defineStore({
return learningUnit
},
async markCompletion(page: LearningContent | LearningUnitQuestion, flag = true) {
async markCompletion(page: LearningContent | LearningUnitQuestion, completion_status:CourseCompletionStatus='success') {
try {
page.completed = flag;
const completionData = await itPost('/api/completion/circle/mark/', {
page.completion_status = completion_status;
const completionData = await itPost('/api/course/completion/mark/', {
page_key: page.translation_key,
completed: page.completed,
completion_status: page.completion_status,
});
if (this.circle) {
this.circle.parseCompletionData(completionData);
@ -100,10 +100,10 @@ export const useCircleStore = defineStore({
},
calcSelfEvaluationStatus(learningUnit: LearningUnit) {
if (learningUnit.children.length > 0) {
if (learningUnit.children.every((q) => q.completed)) {
if (learningUnit.children.every((q) => q.completion_status === 'success')) {
return true;
}
if (learningUnit.children.some((q) => q.completed !== undefined)) {
if (learningUnit.children.some((q) => q.completion_status === 'fail')) {
return false;
}
}
@ -111,7 +111,7 @@ export const useCircleStore = defineStore({
},
continueFromLearningContent(currentLearningContent: LearningContent) {
if (currentLearningContent) {
this.markCompletion(currentLearningContent, true);
this.markCompletion(currentLearningContent, 'success');
const nextLearningContent = currentLearningContent.nextLearningContent;
const currentParent = currentLearningContent.parentLearningUnit;

View File

@ -20,7 +20,7 @@ export const useLearningPathStore = defineStore({
return this.learningPath;
}
const learningPathData = await itGet(`/api/course/page/${slug}/`);
const completionData = await itGet(`/api/completion/learning_path/${learningPathData.translation_key}/`);
const completionData = await itGet(`/api/course/completion/${learningPathData.course.id}/`);
if (!learningPathData) {
throw `No learning path found with: ${slug}`;

View File

@ -1,8 +1,17 @@
import type { Circle } from '@/services/circle'
export type LearningContentType = 'assignment' | 'book' | 'document' |
'exercise' | 'media_library' | 'online_training' |
'resource' | 'test' | 'video';
export type CourseCompletionStatus = 'unknown' | 'fail' | 'success'
export type LearningContentType =
| 'assignment'
| 'book'
| 'document'
| 'exercise'
| 'media_library'
| 'online_training'
| 'resource'
| 'test'
| 'video'
export interface LearningContentBlock {
type: LearningContentType
@ -111,7 +120,7 @@ export interface CourseWagtailPage {
readonly title: string;
readonly slug: string;
readonly translation_key: string;
completed?: boolean;
completion_status: CourseCompletionStatus;
}
export interface LearningContent extends CourseWagtailPage {
@ -163,17 +172,17 @@ export interface Topic extends CourseWagtailPage {
export type LearningPathChild = Topic | WagtailCircle;
export interface CircleCompletion {
export interface CourseCompletion {
id: number;
created_at: string;
updated_at: string;
user: number;
page_key: string;
page_type: string;
circle_key: string;
learning_path_key: string;
completed: boolean;
json_data: any;
page_slug: string;
course: number;
completion_status: CourseCompletionStatus;
additional_json_data: any;
}
export interface CircleDiagramData {

View File

@ -4,7 +4,7 @@ import { onMounted, reactive, watch } from 'vue'
import { useCircleStore } from '@/stores/circle'
import { useAppStore } from '@/stores/app'
import LearningContent from '@/components/circle/LearningContent.vue'
import { LearningContent as LearningContentType } from '@/types'
import type { LearningContent as LearningContentType } from '@/types'
log.debug('LearningContentView created')

View File

@ -50,11 +50,6 @@ urlpatterns = [
path(r"api/course/completion/mark/", mark_course_completion, name="mark_course_completion"),
path(r"api/course/completion/<course_id>/", request_course_completion, name="request_course_completion"),
# completion
# path(r"api/completion/circle/<uuid:circle_key>/", request_circle_completion, name="request_circle_completion"),
# path(r"api/completion/learning_path/<uuid:learning_path_key>/", request_learning_path_completion, name="request_learning_path_completion"),
# path(r"api/completion/circle/mark/", mark_circle_completion, name="mark_circle_completion"),
# testing and debug
path('server/raise_error/', user_passes_test(lambda u: u.is_superuser, login_url='/login/')(raise_example_error), ),
path("server/checkratelimit/", check_rate_limit),

View File

@ -16,8 +16,6 @@ from vbv_lernwelt.learnpath.serializer_helpers import get_it_serializer_class
class LearningPath(Page):
# PageChooserPanel('related_page', 'demo.PublisherPage'),
content_panels = Page.content_panels
subpage_types = ['learnpath.Circle', 'learnpath.Topic']
parent_page_types = ['course.CoursePage']
@ -35,7 +33,7 @@ class LearningPath(Page):
@classmethod
def get_serializer_class(cls):
return get_it_serializer_class(
cls, ['id', 'title', 'slug', 'type', 'translation_key', 'children']
cls, ['id', 'title', 'slug', 'type', 'translation_key', 'children', 'course']
)