Add EvaluationSummary
This commit is contained in:
parent
d9a6f2dd94
commit
2d6cee9f9f
|
|
@ -4,14 +4,9 @@ import EvaluationContainer from "@/pages/cockpit/assignmentEvaluationPage/Evalua
|
||||||
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
import AssignmentSubmissionResponses from "@/pages/learningPath/learningContentPage/assignment/AssignmentSubmissionResponses.vue";
|
||||||
import { useAssignmentStore } from "@/stores/assignmentStore";
|
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import {
|
import { Assignment, CourseSessionAssignmentDetails, CourseSessionUser } from "@/types";
|
||||||
Assignment,
|
|
||||||
AssignmentCompletion,
|
|
||||||
CourseSessionAssignmentDetails,
|
|
||||||
CourseSessionUser,
|
|
||||||
} from "@/types";
|
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import { onMounted, reactive } from "vue";
|
import { computed, onMounted, reactive } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -25,14 +20,12 @@ log.debug("AssignmentEvaluationPage created", props.assignmentId, props.userId);
|
||||||
export interface StateInterface {
|
export interface StateInterface {
|
||||||
assignment: Assignment | undefined;
|
assignment: Assignment | undefined;
|
||||||
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
||||||
completionData: AssignmentCompletion | undefined;
|
|
||||||
assignmentUser: CourseSessionUser | undefined;
|
assignmentUser: CourseSessionUser | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: StateInterface = reactive({
|
const state: StateInterface = reactive({
|
||||||
assignment: undefined,
|
assignment: undefined,
|
||||||
courseSessionAssignmentDetails: undefined,
|
courseSessionAssignmentDetails: undefined,
|
||||||
completionData: undefined,
|
|
||||||
assignmentUser: undefined,
|
assignmentUser: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -51,7 +44,7 @@ onMounted(async () => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
state.assignment = await assignmentStore.loadAssignment(props.assignmentId);
|
state.assignment = await assignmentStore.loadAssignment(props.assignmentId);
|
||||||
state.completionData = await assignmentStore.loadAssignmentCompletion(
|
await assignmentStore.loadAssignmentCompletion(
|
||||||
props.assignmentId,
|
props.assignmentId,
|
||||||
courseSessionStore.currentCourseSession.id,
|
courseSessionStore.currentCourseSession.id,
|
||||||
props.userId
|
props.userId
|
||||||
|
|
@ -66,11 +59,13 @@ function exit() {
|
||||||
path: `/course/${props.courseSlug}/cockpit/assignment`,
|
path: `/course/${props.courseSlug}/cockpit/assignment`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const assignmentCompletion = computed(() => assignmentStore.assignmentCompletion);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="absolute bottom-0 top-0 z-10 w-full bg-white">
|
<div class="absolute bottom-0 top-0 z-10 w-full bg-white">
|
||||||
<div v-if="state.assignment && state.completionData" class="relative">
|
<div v-if="state.assignment && assignmentCompletion" class="relative">
|
||||||
<header
|
<header
|
||||||
class="relative flex h-12 w-full items-center justify-between border-b border-b-gray-400 bg-white px-4 lg:h-16 lg:px-8"
|
class="relative flex h-12 w-full items-center justify-between border-b border-b-gray-400 bg-white px-4 lg:h-16 lg:px-8"
|
||||||
>
|
>
|
||||||
|
|
@ -102,14 +97,14 @@ function exit() {
|
||||||
</div>
|
</div>
|
||||||
<AssignmentSubmissionResponses
|
<AssignmentSubmissionResponses
|
||||||
:assignment="state.assignment"
|
:assignment="state.assignment"
|
||||||
:assignment-completion-data="state.completionData?.completion_data"
|
:assignment-completion-data="assignmentCompletion.completion_data"
|
||||||
:allow-edit="false"
|
:allow-edit="false"
|
||||||
></AssignmentSubmissionResponses>
|
></AssignmentSubmissionResponses>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-1/2 overflow-y-auto bg-gray-200">
|
<div class="w-1/2 overflow-y-auto bg-gray-200">
|
||||||
<EvaluationContainer
|
<EvaluationContainer
|
||||||
:assignment-completion="state.completionData"
|
:assignment-completion="assignmentCompletion"
|
||||||
:assignment-user="state.assignmentUser"
|
:assignment-user="state.assignmentUser"
|
||||||
:assignment="state.assignment"
|
:assignment="state.assignment"
|
||||||
></EvaluationContainer>
|
></EvaluationContainer>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,19 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import EvaluationIntro from "@/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue";
|
import EvaluationIntro from "@/pages/cockpit/assignmentEvaluationPage/EvaluationIntro.vue";
|
||||||
|
import EvaluationSummary from "@/pages/cockpit/assignmentEvaluationPage/EvaluationSummary.vue";
|
||||||
import EvaluationTask from "@/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue";
|
import EvaluationTask from "@/pages/cockpit/assignmentEvaluationPage/EvaluationTask.vue";
|
||||||
import { calcAssignmentLearningContents } from "@/services/assignmentService";
|
import { calcAssignmentLearningContents } from "@/services/assignmentService";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import { useLearningPathStore } from "@/stores/learningPath";
|
import { useLearningPathStore } from "@/stores/learningPath";
|
||||||
import { useUserStore } from "@/stores/user";
|
import { useUserStore } from "@/stores/user";
|
||||||
import type { Assignment, AssignmentCompletion, CourseSessionUser } from "@/types";
|
import type {
|
||||||
|
Assignment,
|
||||||
|
AssignmentCompletion,
|
||||||
|
AssignmentEvaluationTask,
|
||||||
|
CourseSessionUser,
|
||||||
|
} from "@/types";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { findIndex } from "lodash";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
import { computed, reactive } from "vue";
|
import { computed, reactive } from "vue";
|
||||||
|
|
||||||
|
|
@ -24,7 +31,7 @@ interface StateInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: StateInterface = reactive({
|
const state: StateInterface = reactive({
|
||||||
pageIndex: 0,
|
pageIndex: 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
const courseSessionStore = useCourseSessionsStore();
|
const courseSessionStore = useCourseSessionsStore();
|
||||||
|
|
@ -41,6 +48,15 @@ function nextPage() {
|
||||||
state.pageIndex = Math.min(numTasks.value + 1, state.pageIndex + 1);
|
state.pageIndex = Math.min(numTasks.value + 1, state.pageIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editTask(task: AssignmentEvaluationTask) {
|
||||||
|
log.debug("editTask", task);
|
||||||
|
const taskIndex =
|
||||||
|
findIndex(props.assignment.evaluation_tasks, {
|
||||||
|
id: task.id,
|
||||||
|
}) ?? 0;
|
||||||
|
state.pageIndex = taskIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
function findAssignmentDetail() {
|
function findAssignmentDetail() {
|
||||||
const learningPathStore = useLearningPathStore();
|
const learningPathStore = useLearningPathStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
@ -88,10 +104,18 @@ const dueDate = computed(() =>
|
||||||
:assignment="props.assignment"
|
:assignment="props.assignment"
|
||||||
:task-index="state.pageIndex - 1"
|
:task-index="state.pageIndex - 1"
|
||||||
/>
|
/>
|
||||||
|
<EvaluationSummary
|
||||||
|
v-else
|
||||||
|
:assignment-user="props.assignmentUser"
|
||||||
|
:assignment="props.assignment"
|
||||||
|
:assignment-completion="props.assignmentCompletion"
|
||||||
|
:due-date="dueDate"
|
||||||
|
@edit-task="editTask($event)"
|
||||||
|
></EvaluationSummary>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="sticky bottom-0 border-t p-6">
|
<nav class="sticky bottom-0 border-t bg-gray-200 p-6" v-if="state.pageIndex > 0">
|
||||||
<div class="relative flex flex-row place-content-end">
|
<div class="relative flex flex-row place-content-end">
|
||||||
<button
|
<button
|
||||||
v-if="true"
|
v-if="true"
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,8 @@ async function startEvaluation() {
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p class="my-4">
|
<p class="my-4">
|
||||||
Auf Grund dieser Bewertung wird eine Gesamtpunktzahl und die daraus reslutierende
|
Die Gesamtpunktzahl und die daruas resultierende Note wird auf Grund des
|
||||||
Note berechnet. Genauere Informationen dazu findest du im folgenden
|
hinterlegeten Beurteilungsinstrument berechnet. Willst du mehr dazu erfahren:
|
||||||
Beurteilungsinstrument:
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
maxAssignmentPoints,
|
||||||
|
pointsToGrade,
|
||||||
|
userAssignmentPoints,
|
||||||
|
} from "@/services/assignmentService";
|
||||||
|
import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||||
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
|
import type { Assignment, AssignmentEvaluationTask, CourseSessionUser } from "@/types";
|
||||||
|
import { AssignmentCompletion } from "@/types";
|
||||||
|
import { Dayjs } from "dayjs";
|
||||||
|
import * as log from "loglevel";
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
assignmentUser: CourseSessionUser;
|
||||||
|
assignment: Assignment;
|
||||||
|
assignmentCompletion: AssignmentCompletion;
|
||||||
|
dueDate?: Dayjs;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits(["submitEvaluation", "editTask"]);
|
||||||
|
|
||||||
|
log.debug("EvaluationSummary setup");
|
||||||
|
|
||||||
|
const courseSessionStore = useCourseSessionsStore();
|
||||||
|
const assignmentStore = useAssignmentStore();
|
||||||
|
|
||||||
|
async function submitEvaluation() {
|
||||||
|
log.debug("submitEvaluation");
|
||||||
|
await assignmentStore.evaluateAssignmentCompletion({
|
||||||
|
assignment_user_id: Number(props.assignmentUser.user_id),
|
||||||
|
assignment_id: props.assignment.id,
|
||||||
|
course_session_id: courseSessionStore.currentCourseSession!.id,
|
||||||
|
completion_data: {},
|
||||||
|
completion_status: "evaluation_submitted",
|
||||||
|
});
|
||||||
|
emit("submitEvaluation");
|
||||||
|
}
|
||||||
|
|
||||||
|
function subTaskByPoints(task: AssignmentEvaluationTask, points = 0) {
|
||||||
|
return task.value.sub_tasks.find((subTask) => subTask.points === points);
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluationForTask(task: AssignmentEvaluationTask) {
|
||||||
|
const expertData = props.assignmentCompletion.completion_data[task.id]?.expert_data;
|
||||||
|
if (task.id === "0e701176-a817-427b-b8ea-a7cd59f212cb") {
|
||||||
|
console.log("######################## ", expertData.text, expertData.points);
|
||||||
|
}
|
||||||
|
if (!expertData) {
|
||||||
|
return {
|
||||||
|
points: 0,
|
||||||
|
text: "",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return expertData;
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxPoints = computed(() => maxAssignmentPoints(props.assignment));
|
||||||
|
const userPoints = computed(() =>
|
||||||
|
userAssignmentPoints(props.assignment, props.assignmentCompletion)
|
||||||
|
);
|
||||||
|
const grade = computed(() => pointsToGrade(userPoints.value, maxPoints.value));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h3>Bewertung Freigabe</h3>
|
||||||
|
|
||||||
|
<section class="mb-6 border p-6">
|
||||||
|
<div class="text-lg font-bold">Note: {{ grade }}</div>
|
||||||
|
<div>Gesamtpunktezahl {{ userPoints }} / {{ maxPoints }}</div>
|
||||||
|
|
||||||
|
<p class="my-4">
|
||||||
|
Die Gesamtpunktzahl und die daraus resultierende Note wird auf Grund des
|
||||||
|
hinterlegeten Beurteilungsinstrument berechnet. Willst du mehr dazu erfahren:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<button class="btn-primary" @click="submitEvaluation()">
|
||||||
|
Bewertung freigeben
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div v-for="(task, index) in props.assignment.evaluation_tasks" :key="task.id">
|
||||||
|
<article class="border-t py-4">
|
||||||
|
<div class="flex flex-row justify-between">
|
||||||
|
<div class="mb-4">
|
||||||
|
Bewertungskriterium {{ index + 1 }}: {{ task.value.title }}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="link pl-2text-sm whitespace-nowrap"
|
||||||
|
@click="emit('editTask', task)"
|
||||||
|
>
|
||||||
|
{{ $t("assignment.edit") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="default-wagtail-rich-text mb-2 font-bold">
|
||||||
|
{{ task.value.description }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="mb-4">
|
||||||
|
<div
|
||||||
|
v-html="subTaskByPoints(task, evaluationForTask(task).points).title"
|
||||||
|
></div>
|
||||||
|
<p
|
||||||
|
class="default-wagtail-rich-text"
|
||||||
|
v-html="subTaskByPoints(task, evaluationForTask(task).points).description"
|
||||||
|
></p>
|
||||||
|
<div class="text-sm text-gray-800">
|
||||||
|
{{ evaluationForTask(task).points }} Punkte
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<span class="font-bold">Begründung:</span>
|
||||||
|
{{ evaluationForTask(task).text }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
|
|
@ -7,7 +7,6 @@ import { useAssignmentStore } from "@/stores/assignmentStore";
|
||||||
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
import { useCourseSessionsStore } from "@/stores/courseSessions";
|
||||||
import type {
|
import type {
|
||||||
Assignment,
|
Assignment,
|
||||||
AssignmentCompletion,
|
|
||||||
AssignmentTask,
|
AssignmentTask,
|
||||||
CourseSessionAssignmentDetails,
|
CourseSessionAssignmentDetails,
|
||||||
LearningContent,
|
LearningContent,
|
||||||
|
|
@ -24,14 +23,12 @@ const assignmentStore = useAssignmentStore();
|
||||||
interface State {
|
interface State {
|
||||||
assignment: Assignment | undefined;
|
assignment: Assignment | undefined;
|
||||||
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
courseSessionAssignmentDetails: CourseSessionAssignmentDetails | undefined;
|
||||||
assignmentCompletion: AssignmentCompletion | undefined;
|
|
||||||
pageIndex: number;
|
pageIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state: State = reactive({
|
const state: State = reactive({
|
||||||
assignment: undefined,
|
assignment: undefined,
|
||||||
courseSessionAssignmentDetails: undefined,
|
courseSessionAssignmentDetails: undefined,
|
||||||
assignmentCompletion: undefined,
|
|
||||||
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
// 0 = introduction, 1 - n = tasks, n+1 = submission
|
||||||
pageIndex: 0,
|
pageIndex: 0,
|
||||||
});
|
});
|
||||||
|
|
@ -51,7 +48,7 @@ onMounted(async () => {
|
||||||
state.courseSessionAssignmentDetails = courseSessionsStore.findAssignmentDetails(
|
state.courseSessionAssignmentDetails = courseSessionsStore.findAssignmentDetails(
|
||||||
props.learningContent.id
|
props.learningContent.id
|
||||||
);
|
);
|
||||||
state.assignmentCompletion = await assignmentStore.loadAssignmentCompletion(
|
await assignmentStore.loadAssignmentCompletion(
|
||||||
props.assignmentId,
|
props.assignmentId,
|
||||||
courseSessionId.value
|
courseSessionId.value
|
||||||
);
|
);
|
||||||
|
|
@ -78,6 +75,7 @@ const currentTask = computed(() => {
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
const assignmentCompletion = computed(() => assignmentStore.assignmentCompletion);
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
log.debug("handleBack");
|
log.debug("handleBack");
|
||||||
|
|
@ -115,7 +113,7 @@ const getTitle = () => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="state.assignment && state.assignmentCompletion"></div>
|
<div v-if="state.assignment && assignmentCompletion"></div>
|
||||||
<LearningContentMultiLayout
|
<LearningContentMultiLayout
|
||||||
:current-step="state.pageIndex"
|
:current-step="state.pageIndex"
|
||||||
:subtitle="state.assignment?.title ?? ''"
|
:subtitle="state.assignment?.title ?? ''"
|
||||||
|
|
@ -147,7 +145,7 @@ const getTitle = () => {
|
||||||
v-if="state.pageIndex + 1 === numPages && state.assignment && courseSessionId"
|
v-if="state.pageIndex + 1 === numPages && state.assignment && courseSessionId"
|
||||||
:due-date="dueDate"
|
:due-date="dueDate"
|
||||||
:assignment="state.assignment!"
|
:assignment="state.assignment!"
|
||||||
:assignment-completion-data="state.assignmentCompletion?.completion_data ?? {}"
|
:assignment-completion-data="assignmentCompletion?.completion_data ?? {}"
|
||||||
:course-session-id="courseSessionId!"
|
:course-session-id="courseSessionId!"
|
||||||
@edit-task="jumpToTask($event)"
|
@edit-task="jumpToTask($event)"
|
||||||
></AssignmentSubmissionView>
|
></AssignmentSubmissionView>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { describe, it } from "vitest";
|
||||||
|
import { pointsToGrade } from "../assignmentService";
|
||||||
|
|
||||||
|
describe("assignmentService", () => {
|
||||||
|
it("pointsToGrade", () => {
|
||||||
|
expect(pointsToGrade(24, 24)).toBe(6);
|
||||||
|
expect(pointsToGrade(23, 24)).toBe(6);
|
||||||
|
expect(pointsToGrade(22, 24)).toBe(5.5);
|
||||||
|
expect(pointsToGrade(21, 24)).toBe(5.5);
|
||||||
|
expect(pointsToGrade(20, 24)).toBe(5);
|
||||||
|
expect(pointsToGrade(19, 24)).toBe(5);
|
||||||
|
expect(pointsToGrade(18, 24)).toBe(5);
|
||||||
|
expect(pointsToGrade(17, 24)).toBe(4.5);
|
||||||
|
expect(pointsToGrade(16, 24)).toBe(4.5);
|
||||||
|
expect(pointsToGrade(15, 24)).toBe(4);
|
||||||
|
expect(pointsToGrade(14, 24)).toBe(4);
|
||||||
|
expect(pointsToGrade(13, 24)).toBe(3.5);
|
||||||
|
expect(pointsToGrade(12, 24)).toBe(3.5);
|
||||||
|
expect(pointsToGrade(11, 24)).toBe(3.5);
|
||||||
|
expect(pointsToGrade(10, 24)).toBe(3);
|
||||||
|
expect(pointsToGrade(9, 24)).toBe(3);
|
||||||
|
expect(pointsToGrade(8, 24)).toBe(2.5);
|
||||||
|
expect(pointsToGrade(7, 24)).toBe(2.5);
|
||||||
|
expect(pointsToGrade(6, 24)).toBe(2.5);
|
||||||
|
expect(pointsToGrade(5, 24)).toBe(2);
|
||||||
|
expect(pointsToGrade(4, 24)).toBe(2);
|
||||||
|
expect(pointsToGrade(3, 24)).toBe(1.5);
|
||||||
|
expect(pointsToGrade(2, 24)).toBe(1.5);
|
||||||
|
expect(pointsToGrade(1, 24)).toBe(1);
|
||||||
|
expect(pointsToGrade(0, 24)).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -3,10 +3,14 @@ import { itGet } from "@/fetchHelpers";
|
||||||
import type { LearningPath } from "@/services/learningPath";
|
import type { LearningPath } from "@/services/learningPath";
|
||||||
import { useCockpitStore } from "@/stores/cockpit";
|
import { useCockpitStore } from "@/stores/cockpit";
|
||||||
import type {
|
import type {
|
||||||
|
Assignment,
|
||||||
|
AssignmentCompletion,
|
||||||
AssignmentCompletionStatus,
|
AssignmentCompletionStatus,
|
||||||
CourseSessionUser,
|
CourseSessionUser,
|
||||||
LearningContent,
|
LearningContent,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
|
import { sum } from "d3";
|
||||||
|
import pick from "lodash/pick";
|
||||||
|
|
||||||
export interface AssignmentLearningContent extends LearningContent {
|
export interface AssignmentLearningContent extends LearningContent {
|
||||||
assignmentId: number;
|
assignmentId: number;
|
||||||
|
|
@ -59,10 +63,42 @@ export function calcUserAssignmentCompletionStatus(
|
||||||
userStatus = userAssignmentStatus.completion_status;
|
userStatus = userAssignmentStatus.completion_status;
|
||||||
}
|
}
|
||||||
let progressStatus: StatusCountKey = "unknown";
|
let progressStatus: StatusCountKey = "unknown";
|
||||||
if (["submitted", "evaluation_in_progress", "evaluated"].includes(userStatus)) {
|
if (
|
||||||
|
["submitted", "evaluation_in_progress", "evaluation_submitted"].includes(
|
||||||
|
userStatus
|
||||||
|
)
|
||||||
|
) {
|
||||||
progressStatus = "success";
|
progressStatus = "success";
|
||||||
}
|
}
|
||||||
|
|
||||||
return { userId: u.user_id, userStatus, progressStatus };
|
return { userId: u.user_id, userStatus, progressStatus };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function maxAssignmentPoints(assignment: Assignment) {
|
||||||
|
return sum(assignment.evaluation_tasks.map((task) => task.value.max_points));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function userAssignmentPoints(
|
||||||
|
assignment: Assignment,
|
||||||
|
assignmentCompletion: AssignmentCompletion
|
||||||
|
) {
|
||||||
|
const evaluationTaskIds = assignment.evaluation_tasks.map((task) => {
|
||||||
|
return task.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return sum(
|
||||||
|
Object.entries(pick(assignmentCompletion.completion_data, evaluationTaskIds)).map(
|
||||||
|
(entry) => {
|
||||||
|
return entry[1]?.expert_data?.points ?? 0;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pointsToGrade(points: number, maxPoints: number) {
|
||||||
|
// round to half-grades
|
||||||
|
const grade = Math.round((points / maxPoints) * 10);
|
||||||
|
const halfGrade = grade / 2;
|
||||||
|
return Math.min(halfGrade, 5) + 1;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -509,7 +509,7 @@ export type AssignmentCompletionStatus =
|
||||||
| "in_progress"
|
| "in_progress"
|
||||||
| "submitted"
|
| "submitted"
|
||||||
| "evaluation_in_progress"
|
| "evaluation_in_progress"
|
||||||
| "evaluated";
|
| "evaluation_submitted";
|
||||||
|
|
||||||
export interface UserDataText {
|
export interface UserDataText {
|
||||||
text: string;
|
text: string;
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|
@ -261,7 +260,7 @@ class Migration(migrations.Migration):
|
||||||
(1, "in_progress"),
|
(1, "in_progress"),
|
||||||
(2, "submitted"),
|
(2, "submitted"),
|
||||||
(3, "evaluation_in_progress"),
|
(3, "evaluation_in_progress"),
|
||||||
(4, "evaluated"),
|
(4, "evaluation_submitted"),
|
||||||
],
|
],
|
||||||
default="in_progress",
|
default="in_progress",
|
||||||
max_length=255,
|
max_length=255,
|
||||||
|
|
@ -337,7 +336,7 @@ class Migration(migrations.Migration):
|
||||||
(1, "in_progress"),
|
(1, "in_progress"),
|
||||||
(2, "submitted"),
|
(2, "submitted"),
|
||||||
(3, "evaluation_in_progress"),
|
(3, "evaluation_in_progress"),
|
||||||
(4, "evaluated"),
|
(4, "evaluation_submitted"),
|
||||||
],
|
],
|
||||||
default="in_progress",
|
default="in_progress",
|
||||||
max_length=255,
|
max_length=255,
|
||||||
|
|
|
||||||
|
|
@ -220,7 +220,7 @@ class Assignment(CourseBasePage):
|
||||||
|
|
||||||
AssignmentCompletionStatus = Enum(
|
AssignmentCompletionStatus = Enum(
|
||||||
"AssignmentCompletionStatus",
|
"AssignmentCompletionStatus",
|
||||||
["in_progress", "submitted", "evaluation_in_progress", "evaluated"],
|
["in_progress", "submitted", "evaluation_in_progress", "evaluation_submitted"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -262,7 +262,7 @@ class AssignmentCompletion(models.Model):
|
||||||
|
|
||||||
class AssignmentCompletionAuditLog(models.Model):
|
class AssignmentCompletionAuditLog(models.Model):
|
||||||
"""
|
"""
|
||||||
This model is used to store the "submitted" and "evaluated" data separately
|
This model is used to store the "submitted" and "evaluation_submitted" data separately
|
||||||
"""
|
"""
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ def update_assignment_completion(
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
:param copy_task_data: if true, the task data will be copied to the completion data
|
:param copy_task_data: if true, the task data will be copied to the completion data
|
||||||
used for "submitted" and "evaluated" status, so that we don't lose the question
|
used for "submitted" and "evaluation_submitted" status, so that we don't lose the question
|
||||||
context
|
context
|
||||||
:return: AssignmentCompletion
|
:return: AssignmentCompletion
|
||||||
"""
|
"""
|
||||||
|
|
@ -59,31 +59,33 @@ def update_assignment_completion(
|
||||||
if ac.completion_status in [
|
if ac.completion_status in [
|
||||||
"submitted",
|
"submitted",
|
||||||
"evaluation_in_progress",
|
"evaluation_in_progress",
|
||||||
"evaluated",
|
"evaluation_submitted",
|
||||||
]:
|
]:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{
|
{
|
||||||
"completion_status": f"Cannot update completion status from {ac.completion_status} to submitted"
|
"completion_status": f"Cannot update completion status from {ac.completion_status} to submitted"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
elif completion_status == "evaluated":
|
elif completion_status == "evaluation_submitted":
|
||||||
if ac.completion_status == "evaluated":
|
if ac.completion_status == "evaluation_submitted":
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{
|
{
|
||||||
"completion_status": f"Cannot update completion status from {ac.completion_status} to evaluated"
|
"completion_status": f"Cannot update completion status from {ac.completion_status} to evaluation_submitted"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if completion_status in ["evaluated", "evaluation_in_progress"]:
|
if completion_status in ["evaluation_submitted", "evaluation_in_progress"]:
|
||||||
if evaluation_user is None:
|
if evaluation_user is None:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
{"evaluation_user": "evaluation_user is required for evaluated status"}
|
{
|
||||||
|
"evaluation_user": "evaluation_user is required for evaluation_submitted status"
|
||||||
|
}
|
||||||
)
|
)
|
||||||
ac.evaluation_user = evaluation_user
|
ac.evaluation_user = evaluation_user
|
||||||
|
|
||||||
if completion_status == "submitted":
|
if completion_status == "submitted":
|
||||||
ac.submitted_at = timezone.now()
|
ac.submitted_at = timezone.now()
|
||||||
elif completion_status == "evaluated":
|
elif completion_status == "evaluation_submitted":
|
||||||
ac.evaluated_at = timezone.now()
|
ac.evaluated_at = timezone.now()
|
||||||
|
|
||||||
ac.completion_status = completion_status
|
ac.completion_status = completion_status
|
||||||
|
|
@ -105,7 +107,7 @@ def update_assignment_completion(
|
||||||
|
|
||||||
ac.save()
|
ac.save()
|
||||||
|
|
||||||
if completion_status in ["evaluated", "submitted"]:
|
if completion_status in ["evaluation_submitted", "submitted"]:
|
||||||
acl = AssignmentCompletionAuditLog.objects.create(
|
acl = AssignmentCompletionAuditLog.objects.create(
|
||||||
assignment_user=assignment_user,
|
assignment_user=assignment_user,
|
||||||
assignment=assignment,
|
assignment=assignment,
|
||||||
|
|
|
||||||
|
|
@ -230,7 +230,7 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
"assignment_user_id": self.student.id,
|
"assignment_user_id": self.student.id,
|
||||||
"course_session_id": self.cs.id,
|
"course_session_id": self.cs.id,
|
||||||
"completion_status": "evaluated",
|
"completion_status": "evaluation_submitted",
|
||||||
"completion_data": {
|
"completion_data": {
|
||||||
user_text_input["id"]: {
|
user_text_input["id"]: {
|
||||||
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
"expert_data": {"points": 1, "comment": "Gut gemacht!"}
|
||||||
|
|
@ -245,7 +245,7 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response_json["assignment_user"], self.student.id)
|
self.assertEqual(response_json["assignment_user"], self.student.id)
|
||||||
self.assertEqual(response_json["assignment"], self.assignment.id)
|
self.assertEqual(response_json["assignment"], self.assignment.id)
|
||||||
self.assertEqual(response_json["completion_status"], "evaluated")
|
self.assertEqual(response_json["completion_status"], "evaluation_submitted")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
response_json["completion_data"],
|
response_json["completion_data"],
|
||||||
{
|
{
|
||||||
|
|
@ -261,7 +261,7 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
course_session_id=self.cs.id,
|
course_session_id=self.cs.id,
|
||||||
assignment_id=self.assignment.id,
|
assignment_id=self.assignment.id,
|
||||||
)
|
)
|
||||||
self.assertEqual(db_entry.completion_status, "evaluated")
|
self.assertEqual(db_entry.completion_status, "evaluation_submitted")
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
db_entry.completion_data,
|
db_entry.completion_data,
|
||||||
{
|
{
|
||||||
|
|
@ -272,12 +272,12 @@ class AssignmentApiTestCase(APITestCase):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# `evaluated` will create a new AssignmentCompletionAuditLog
|
# `evaluation_submitted` will create a new AssignmentCompletionAuditLog
|
||||||
acl = AssignmentCompletionAuditLog.objects.get(
|
acl = AssignmentCompletionAuditLog.objects.get(
|
||||||
assignment_user=self.student,
|
assignment_user=self.student,
|
||||||
course_session_id=self.cs.id,
|
course_session_id=self.cs.id,
|
||||||
assignment_id=self.assignment.id,
|
assignment_id=self.assignment.id,
|
||||||
completion_status="evaluated",
|
completion_status="evaluation_submitted",
|
||||||
)
|
)
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
self.assertDictEqual(
|
self.assertDictEqual(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue