Implement unified grading

This commit is contained in:
Elia Bieri 2024-07-31 11:45:42 +02:00
parent c2c3331539
commit 78d18034fa
8 changed files with 352 additions and 211 deletions

View File

@ -6,6 +6,8 @@ import { useCertificateQuery, useCurrentCourseSession } from "@/composables";
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
import { getCertificates } from "@/services/competence";
import { getPreviousRoute } from "@/router/history";
import { mergeCompetenceCertificates } from "./utils";
import { useCourseSessionsStore } from "@/stores/courseSessions";
const props = defineProps<{
courseSlug: string;
@ -15,26 +17,24 @@ const props = defineProps<{
log.debug("CompetenceCertificateDetailPage setup", props);
const courseSession = useCurrentCourseSession();
const certificatesQuery = useCertificateQuery(
props.userId,
props.courseSlug,
courseSession.value
).certificatesQuery;
const store = useCourseSessionsStore();
const certificateQueries = store.allCourseSessions.map((courseSession) => {
return useCertificateQuery(props.userId, props.courseSlug, courseSession)
.certificatesQuery;
});
const certificate = computed(() => {
const certificates = getCertificates(
certificatesQuery.data.value,
props.userId ?? null
);
const competenceCertificatesPerCs = certificateQueries.map((query) => {
return getCertificates(query.data.value, props.userId!)
?.competence_certificates as unknown as CompetenceCertificate[];
});
const certificates = mergeCompetenceCertificates(competenceCertificatesPerCs.flat());
if (!certificates) {
return null;
}
return (
(certificates.competence_certificates as unknown as CompetenceCertificate[]) ?? []
).find((cc) => cc.slug.endsWith(props.certificateSlug));
return certificates.find((cc) => cc.slug.endsWith(props.certificateSlug));
});
</script>

View File

@ -2,11 +2,12 @@
import log from "loglevel";
import { computed, onMounted } from "vue";
import type { CompetenceCertificate } from "@/types";
import { useCertificateQuery, useCurrentCourseSession } from "@/composables";
import { useCertificateQuery } from "@/composables";
import CompetenceCertificateComponent from "@/pages/competence/CompetenceCertificateComponent.vue";
import {
assignmentsUserPoints,
calcCompetencesTotalGrade,
mergeCompetenceCertificates,
} from "@/pages/competence/utils";
import { useRoute } from "vue-router";
import { getCertificates } from "@/services/competence";
@ -23,48 +24,29 @@ log.debug("CompetenceCertificateListPage setup", props);
const route = useRoute();
const store = useCourseSessionsStore();
console.log(
`Loading competence certificates from ${store.allCourseSessions.length} course sessions:`,
store.allCourseSessions.map((cs) => cs.title)
);
const certificateQueries = store.allCourseSessions.map((courseSession) => {
return useCertificateQuery(props.userId, props.courseSlug, courseSession)
.certificatesQuery;
});
const competenceCertificates = computed(() => {
const competenceCertificates: Record<string, CompetenceCertificate> = {};
for (const query of certificateQueries) {
const certificates =
(getCertificates(query.data.value, props.userId ?? null)
?.competence_certificates as unknown as CompetenceCertificate[]) ?? [];
for (const certificate of certificates) {
if (!competenceCertificates[certificate.id]) {
// Competence certificate does not exist yet
competenceCertificates[certificate.id] = certificate;
} else {
// Merge with assignment completions of existing competence certificate. If there are multiple completions for the same assignment, keep the latest one.
competenceCertificates[certificate.id].assignments.push(
...certificate.assignments.filter((assignment) => {
const existingAssignment = competenceCertificates[
certificate.id
].assignments.find((a) => a.id === assignment.id);
return (
!existingAssignment ||
dayjs(assignment.completion?.evaluation_submitted_at).isAfter(
dayjs(existingAssignment.completion?.evaluation_submitted_at)
)
);
})
);
}
}
}
return Object.values(competenceCertificates);
const mergedCertificates = computed(() => {
const competenceCertificatesPerCs = certificateQueries.map((query) => {
return getCertificates(query.data.value, props.userId ?? null)
?.competence_certificates as unknown as CompetenceCertificate[];
});
return mergeCompetenceCertificates(competenceCertificatesPerCs.flat());
});
const assignments = computed(() => {
return competenceCertificates?.value?.flatMap((cc) => cc.assignments);
return mergedCertificates?.value?.flatMap((cc) => cc.assignments);
});
const totalGrade = computed(() => {
return calcCompetencesTotalGrade(competenceCertificates.value ?? []);
return calcCompetencesTotalGrade(mergedCertificates.value ?? []);
});
const userPointsEvaluatedAssignments = computed(() => {
@ -121,7 +103,7 @@ onMounted(async () => {
</div>
<div
v-for="competenceCertificate in competenceCertificates"
v-for="competenceCertificate in mergedCertificates"
:key="competenceCertificate.id"
>
<CompetenceCertificateComponent

View File

@ -4,17 +4,20 @@ import { COMPETENCE_NAVI_CERTIFICATE_QUERY } from "@/graphql/queries";
import { useQuery } from "@urql/vue";
import { computed } from "vue";
import type { CompetenceCertificate } from "@/types";
import { useCurrentCourseSession } from "@/composables";
import { useCertificateQuery, useCurrentCourseSession } from "@/composables";
import {
assignmentsUserPoints,
calcCompetenceCertificateGrade,
calcCompetencesTotalGrade,
competenceCertificateProgressStatusCount,
mergeCompetenceCertificates,
} from "@/pages/competence/utils";
import ItProgress from "@/components/ui/ItProgress.vue";
import SelfEvaluationAndFeedbackOverview from "@/components/selfEvaluationFeedback/SelfEvaluationAndFeedbackOverview.vue";
import { useUserStore } from "@/stores/user";
import { useRouter } from "vue-router";
import { useCourseSessionsStore } from "@/stores/courseSessions";
import { getCertificates } from "@/services/competence";
const props = defineProps<{
courseSlug: string;
@ -22,25 +25,27 @@ const props = defineProps<{
log.debug("CompetenceIndexPage setup", props);
const courseSession = useCurrentCourseSession();
const certificatesQuery = useQuery({
query: COMPETENCE_NAVI_CERTIFICATE_QUERY,
variables: {
courseSlug: props.courseSlug,
courseSessionId: courseSession.value.id,
},
const store = useCourseSessionsStore();
console.log(
`Loading competence certificates from ${store.allCourseSessions.length} course sessions:`,
store.allCourseSessions.map((cs) => cs.title)
);
const certificateQueries = store.allCourseSessions.map((courseSession) => {
return useCertificateQuery(undefined, props.courseSlug, courseSession)
.certificatesQuery;
});
const competenceCertificates = computed(() => {
return (
(certificatesQuery.data.value?.competence_certificate_list
?.competence_certificates as unknown as CompetenceCertificate[]) ?? []
);
const mergedCertificates = computed(() => {
const competenceCertificatesPerCs = certificateQueries.map((query) => {
return getCertificates(query.data.value, null)
?.competence_certificates as unknown as CompetenceCertificate[];
});
return mergeCompetenceCertificates(competenceCertificatesPerCs.flat());
});
const allAssignments = computed(() => {
return competenceCertificates.value.flatMap((cc) => cc.assignments);
return mergedCertificates.value.flatMap((cc) => cc.assignments);
});
const userPointsEvaluatedAssignments = computed(() => {
@ -49,7 +54,7 @@ const userPointsEvaluatedAssignments = computed(() => {
const currentCourseSession = useCurrentCourseSession();
const isLoaded = computed(() => !certificatesQuery.fetching.value);
const isLoaded = computed(() => !certificateQueries.some((q) => q.fetching.value));
const router = useRouter();
</script>
@ -68,7 +73,7 @@ const router = useRouter();
<div v-if="userPointsEvaluatedAssignments > 0">
{{ $t("a.Erfahrungsnote üK") }}:
<span class="font-bold">
{{ calcCompetencesTotalGrade(competenceCertificates ?? []) }}
{{ calcCompetencesTotalGrade(mergedCertificates ?? []) }}
</span>
<span class="rounded-full bg-gray-200 px-2.5 py-0.5 text-sm lg:ml-2">
@ -83,7 +88,7 @@ const router = useRouter();
<div>
<div class="mt-4">
<div
v-for="certificate in competenceCertificates"
v-for="certificate in mergedCertificates"
:key="certificate.id"
class="flex flex-col justify-between py-4 lg:flex-row lg:items-center"
:data-cy="`certificate-${certificate.slug}`"

View File

@ -1,6 +1,7 @@
import type { StatusCount } from "@/components/ui/ItProgress.vue";
import { percentToRoundedGrade } from "@/services/assignmentService";
import type { CompetenceCertificate, CompetenceCertificateAssignment } from "@/types";
import dayjs from "dayjs";
import _ from "lodash";
export function assignmentsMaxEvaluationPoints(
@ -84,3 +85,55 @@ export function competenceCertificateProgressStatusCount(
FAIL: 0,
} as StatusCount;
}
export function mergeCompetenceCertificates(
competenceCertificates: CompetenceCertificate[]
) {
const groupedCompetenceCertificates: Record<
string,
Array<CompetenceCertificate>
> = {};
competenceCertificates.forEach((certificate) => {
if (!certificate) {
return;
}
if (!groupedCompetenceCertificates[certificate.id]) {
groupedCompetenceCertificates[certificate.id] = [];
}
groupedCompetenceCertificates[certificate.id].push(certificate);
});
console.log(
`Found ${Object.keys(groupedCompetenceCertificates).length} competence certificates over all course sessions`
);
return Object.values(groupedCompetenceCertificates).map((certificates) => {
const mergedCertificate: CompetenceCertificate = {
...certificates[0],
assignments: [],
};
certificates.forEach((certificate) => {
certificate.assignments.forEach((assignment) => {
const existingAssignment = mergedCertificate.assignments.find(
(a) => a.id === assignment.id
);
if (!existingAssignment) {
mergedCertificate.assignments.push(assignment);
} else if (
assignment.completion != null &&
(existingAssignment.completion == null ||
dayjs(existingAssignment.completion.evaluation_submitted_at).isBefore(
assignment.completion.evaluation_submitted_at
))
) {
mergedCertificate.assignments.splice(
mergedCertificate.assignments.findIndex((a) => a.id === assignment.id),
1
);
mergedCertificate.assignments.push(assignment);
}
});
});
return mergedCertificate;
});
}

View File

@ -1,26 +1,26 @@
import { login } from "../helpers";
import { login } from "../helpers"
describe("competenceCertificate.cy.js", () => {
beforeEach(() => {});
beforeEach(() => { })
it("check without points", () => {
cy.manageCommand("cypress_reset");
login("test-student1@example.com", "test");
cy.visit("/course/test-lehrgang/competence");
cy.manageCommand("cypress_reset")
login("test-student1@example.com", "test")
cy.visit("/course/test-lehrgang/competence")
cy.get('[data-cy="certificate-total-points-text"]').contains(
"Der Punktestand wird zu einem späteren Zeitpunkt berechnet."
);
)
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
).and("contain", "0 von 2 Kompetenznachweis-Elementen");
).and("contain", "0 von 2 Kompetenznachweis-Elementen")
// check on certificates page
cy.get('[data-cy="certificates-show-all-button"]').click();
cy.get('[data-cy="certificates-show-all-button"]').click()
cy.get('[data-cy="certificate-total-points-text"]').contains(
"Der Punktestand wird zu einem späteren Zeitpunkt berechnet."
);
)
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
@ -29,100 +29,100 @@ describe("competenceCertificate.cy.js", () => {
"contain",
"Der Punktestand wird zu einem späteren Zeitpunkt berechnet."
)
.and("contain", "0 von 2 Kompetenznachweis-Elementen");
.and("contain", "0 von 2 Kompetenznachweis-Elementen")
// check certificate detail page
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
).click();
).click()
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
).should("contain", "Höchstpunktzahl");
).should("contain", "Höchstpunktzahl")
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
).should("contain", "Höchstpunktzahl");
});
).should("contain", "Höchstpunktzahl")
})
it("check with finished passed edoniq test", () => {
cy.manageCommand(
"cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 0"
);
login("test-student1@example.com", "test");
cy.visit("/course/test-lehrgang/competence");
)
login("test-student1@example.com", "test")
cy.visit("/course/test-lehrgang/competence")
cy.get('[data-cy="certificate-total-points-text"]').contains(
"Erfahrungsnote üK: 5"
);
)
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
)
.should("contain", "Note: 5")
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
.and("contain", "1 von 2 Kompetenznachweis-Elementen")
// check on certificates page
cy.get('[data-cy="certificates-show-all-button"]').click();
cy.get('[data-cy="certificates-show-all-button"]').click()
cy.get('[data-cy="certificate-total-points-text"]')
.should("contain", "Erfahrungsnote üK")
.and("contain", "Zwischenstand");
cy.get('[data-cy="certificate-total-grade"]').should("contain", "Note: 5");
.and("contain", "Zwischenstand")
cy.get('[data-cy="certificate-total-grade"]').should("contain", "Note: 5")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
).should("contain", "Note: 5");
).should("contain", "Note: 5")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
).should("contain", "Ungerundete Note: 4.96");
).should("contain", "Ungerundete Note: 4.96")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
)
.and("contain", "Zwischenstand")
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
.and("contain", "1 von 2 Kompetenznachweis-Elementen")
// check certificate detail page
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
).click();
).click()
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
)
.should("contain", "Höchstpunktzahl")
.and("contain", "Ergebnisse abgegeben");
.and("contain", "Ergebnisse abgegeben")
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
)
.should("contain", "19")
.and("contain", "Bewertung freigegeben")
.and("not.contain", "Nicht Bestanden");
.and("not.contain", "Nicht Bestanden")
// it can open learning content page directly
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
).click();
).click()
cy.get('[data-cy="test-result"]')
.should("contain", "19 von 24 Punkten")
.and("contain", "79%");
});
.and("contain", "79%")
})
it("check with finished failed edoniq test", () => {
cy.manageCommand(
"cypress_reset --create-assignment-completion --create-edoniq-test-results 10 24 0"
);
login("test-student1@example.com", "test");
)
login("test-student1@example.com", "test")
// go to certificate detail page
cy.visit(
"/course/test-lehrgang/competence/certificates/kompetenznachweis-1"
);
)
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
).should("contain", "Note: 3");
).should("contain", "Note: 3")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
).should("contain", "Ungerundete Note: 3.08");
).should("contain", "Ungerundete Note: 3.08")
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
@ -130,90 +130,148 @@ describe("competenceCertificate.cy.js", () => {
.should("contain", "10")
.and("contain", "Bewertung freigegeben")
.and("contain", "42%")
.and("contain", "Nicht bestanden");
.and("contain", "Nicht bestanden")
// it can open learning content page directly
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
).click();
).click()
cy.get('[data-cy="test-result"]')
.should("contain", "10 von 24 Punkten")
.and("contain", "42%")
.and("contain", "Nicht bestanden");
});
.and("contain", "Nicht bestanden")
})
it("check with finished edoniq test and finished casework", () => {
cy.manageCommand(
"cypress_reset --create-assignment-evaluation --create-edoniq-test-results 19 24 0"
);
login("test-student1@example.com", "test");
cy.visit("/course/test-lehrgang/competence");
)
login("test-student1@example.com", "test")
cy.visit("/course/test-lehrgang/competence")
cy.get('[data-cy="certificate-total-points-text"]').contains(
"Erfahrungsnote üK: 5.5"
);
)
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
)
.should("contain", "Note: 5.5")
.and("contain", "2 von 2 Kompetenznachweis-Elementen");
.and("contain", "2 von 2 Kompetenznachweis-Elementen")
// check on certificates page
cy.get('[data-cy="certificates-show-all-button"]').click();
cy.get('[data-cy="certificates-show-all-button"]').click()
cy.get('[data-cy="certificate-total-points-text"]')
.should("contain", "Erfahrungsnote üK")
.and("contain", "Note: 5.5")
.and("not.contain", "Zwischenstand");
.and("not.contain", "Zwischenstand")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
)
.and("not.contain", "Zwischenstand")
.and("contain", "2 von 2 Kompetenznachweis-Elementen");
.and("contain", "2 von 2 Kompetenznachweis-Elementen")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
).should("contain", "Note: 5.5");
).should("contain", "Note: 5.5")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
).should("contain", "Ungerundete Note: 5.48");
).should("contain", "Ungerundete Note: 5.48")
// check certificate detail page
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
).click();
).click()
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
)
.should("contain", "24")
.and("contain", "von 24 Punkten")
.and("contain", "Bewertung freigegeben");
.and("contain", "Bewertung freigegeben")
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
)
.should("contain", "19")
.and("contain", "von 24 Punkten")
.and("contain", "Bewertung freigegeben");
});
.and("contain", "Bewertung freigegeben")
})
it("check with finished edoniq test and finished casework in different course sessions", () => {
const TEST_TRAINER2_USER_ID = "299941ae-1e4b-4f45-8180-876c3ad340b4"
const TEST_STUDENT2_USER_ID = "19c40d94-15cc-4198-aaad-ef707c4b0900"
const TEST_COURSE_SESSION_ZURICH_ID = -2
cy.manageCommand(
`cypress_reset --create-assignment-evaluation --assignment-evaluation-user-id ${TEST_TRAINER2_USER_ID} --assignment-completion-user-id ${TEST_STUDENT2_USER_ID} --edoniq-user-id ${TEST_STUDENT2_USER_ID} --edoniq-course-session-id '${TEST_COURSE_SESSION_ZURICH_ID}' --create-edoniq-test-results 19 24 0`
)
login("test-student2@example.com", "test")
cy.visit("/course/test-lehrgang/competence")
cy.get('[data-cy="certificate-total-points-text"]').contains(
"Erfahrungsnote üK: 5.5"
)
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
)
.should("contain", "Note: 5.5")
.and("contain", "2 von 2 Kompetenznachweis-Elementen")
// check on certificates page
cy.get('[data-cy="certificates-show-all-button"]').click()
cy.get('[data-cy="certificate-total-points-text"]')
.should("contain", "Erfahrungsnote üK")
.and("contain", "Note: 5.5")
.and("not.contain", "Zwischenstand")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
)
.and("not.contain", "Zwischenstand")
.and("contain", "2 von 2 Kompetenznachweis-Elementen")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
).should("contain", "Note: 5.5")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
).should("contain", "Ungerundete Note: 5.48")
// check certificate detail page
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
).click()
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
)
.should("contain", "24")
.and("contain", "von 24 Punkten")
.and("contain", "Bewertung freigegeben")
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
)
.should("contain", "19")
.and("contain", "von 24 Punkten")
.and("contain", "Bewertung freigegeben")
})
it("check with finished edoniq test with deducted points", () => {
cy.manageCommand(
"cypress_reset --create-assignment-completion --create-edoniq-test-results 19 24 8"
);
login("test-student1@example.com", "test");
)
login("test-student1@example.com", "test")
// go to certificate detail page
cy.visit(
"/course/test-lehrgang/competence/certificates/kompetenznachweis-1"
);
)
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
).should("contain", "Note: 3.5");
).should("contain", "Note: 3.5")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
).should("contain", "Ungerundete Note: 3.29");
).should("contain", "Ungerundete Note: 3.29")
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"]'
@ -222,61 +280,61 @@ describe("competenceCertificate.cy.js", () => {
.and("contain", "Bewertung freigegeben")
.and("contain", "46%")
.and("contain", "mit Abzug")
.and("contain", "Nicht bestanden");
.and("contain", "Nicht bestanden")
// it can open learning content page directly
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"] [data-cy="open-learning-content"]'
).click();
).click()
cy.get('[data-cy="test-result"]')
.should("contain", "11 von 24 Punkten")
.and("contain", "46%")
.and("contain", "Punkte aus Bewertung: 19")
.and("contain", "Abgezogene Punkte: 8")
.and("contain", "Grund: Edoniq Punkteabzug Test")
.and("contain", "Nicht bestanden");
});
.and("contain", "Nicht bestanden")
})
it("check with finished casework and points deducted", () => {
cy.manageCommand(
"cypress_reset --create-assignment-evaluation --assignment-evaluation-scores 4,6,4,3,2 --assignment-points-deducted 5"
);
login("test-student1@example.com", "test");
cy.visit("/course/test-lehrgang/competence");
)
login("test-student1@example.com", "test")
cy.visit("/course/test-lehrgang/competence")
cy.get('[data-cy="certificate-total-points-text"]').contains(
"Erfahrungsnote üK: 4"
);
)
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
)
.should("contain", "Note: 4")
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
.and("contain", "1 von 2 Kompetenznachweis-Elementen")
// check on certificates page
cy.get('[data-cy="certificates-show-all-button"]').click();
cy.get('[data-cy="certificates-show-all-button"]').click()
cy.get('[data-cy="certificate-total-points-text"]')
.should("contain", "Erfahrungsnote üK")
.and("contain", "Note: 4")
.and("contain", "Zwischenstand");
.and("contain", "Zwischenstand")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1"]'
)
.and("contain", "Zwischenstand")
.and("contain", "1 von 2 Kompetenznachweis-Elementen");
.and("contain", "1 von 2 Kompetenznachweis-Elementen")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade"]'
).should("contain", "Note: 4");
).should("contain", "Note: 4")
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-grade-percent"]'
).should("contain", "Ungerundete Note: 3.92");
).should("contain", "Ungerundete Note: 3.92")
// check certificate detail page
cy.get(
'[data-cy="certificate-test-lehrgang-competencenavi-certificates-kompetenznachweis-1-detail-link"]'
).click();
).click()
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"]'
@ -285,28 +343,28 @@ describe("competenceCertificate.cy.js", () => {
.and("contain", "von 24 Punkten")
.and("contain", "58%")
.and("contain", "mit Abzug")
.and("contain", "Bewertung freigegeben");
.and("contain", "Bewertung freigegeben")
cy.get(
'[data-cy="assignment-test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"] [data-cy="open-learning-content"]'
).click();
cy.get('[data-cy="user-points"]').should("contain", "14");
).click()
cy.get('[data-cy="user-points"]').should("contain", "14")
cy.get('[data-cy="total-points"]').should(
"contain",
"von 24 Punkten (58%)"
);
)
cy.get('[data-cy="points-deducted"]')
.should("contain", "Punkte aus Bewertung: 19")
.and("contain", "Abgezogene Punkte: 5")
.and("contain", "Grund: Assignment Punkteabzug Test");
});
.and("contain", "Grund: Assignment Punkteabzug Test")
})
it("should display link to details", () => {
cy.manageCommand("cypress_reset");
login("test-student1@example.com", "test");
cy.visit("/course/test-lehrgang/competence/self-evaluation-and-feedback");
cy.manageCommand("cypress_reset")
login("test-student1@example.com", "test")
cy.visit("/course/test-lehrgang/competence/self-evaluation-and-feedback")
cy.get('[data-cy^="self-eval-"][data-cy$="-detail-url"]:first').contains(
"Selbsteinschätzung anschauen"
);
});
});
)
})
})

View File

@ -50,20 +50,20 @@
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
const _ = Cypress._;
const _ = Cypress._
Cypress.Commands.add("manageCommand", (command, preCommand = "") => {
const execCommand = `${preCommand} python server/manage.py ${command} --settings=config.settings.test_cypress`;
console.log(execCommand);
const execCommand = `${preCommand} python server/manage.py ${command} --settings=config.settings.test_cypress`
console.log(execCommand)
// hack to add my asdf python instance to the path
// so I can run the test directly from within IntelliJ
let pythonPaths = [
"/Users/daniel/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
"/Users/eliabieri/iterativ/vbv_lernwelt/.direnv/python-3.10.6/bin",
"/Users/eliabieri/iterativ/vbv_lernwelt/.direnv/python-3.10/bin",
"/Users/christiancueni/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
"/Users/renzo/workspace/vbv_lernwelt/.direnv/python-3.10.6/bin",
];
let bashCommand = `PATH=${pythonPaths.join(":")}:$PATH && ${execCommand}`;
]
let bashCommand = `PATH=${pythonPaths.join(":")}:$PATH && ${execCommand}`
return cy
.exec(`bash -c "${bashCommand}"`, {
failOnNonZeroExit: true,
@ -73,14 +73,14 @@ Cypress.Commands.add("manageCommand", (command, preCommand = "") => {
throw new Error(`Execution of "${command}" failed
Exit code: ${result.code}
Stdout:\n${result.stdout}
Stderr:\n${result.stderr}`);
Stderr:\n${result.stderr}`)
}
});
});
})
})
Cypress.Commands.add("manageShellCommand", (command) => {
return cy.manageCommand(`shell -c '${command}'`);
});
return cy.manageCommand(`shell -c '${command}'`)
})
function loadObjectJson(
key,
@ -89,28 +89,28 @@ function loadObjectJson(
serializerModelPath,
valueAsString = false
) {
const djangoModel = _.last(djangoModelPath.split("."));
const djangoModelImportPath = _.initial(djangoModelPath.split(".")).join(".");
const serializerModel = _.last(serializerModelPath.split("."));
const djangoModel = _.last(djangoModelPath.split("."))
const djangoModelImportPath = _.initial(djangoModelPath.split(".")).join(".")
const serializerModel = _.last(serializerModelPath.split("."))
const serializerModelImportPath = _.initial(
serializerModelPath.split(".")
).join(".");
).join(".")
let filterPart = `${key}=${value}`;
let filterPart = `${key}=${value}`
if (valueAsString) {
filterPart = `${key}=\\"${value}\\"`;
filterPart = `${key}=\\"${value}\\"`
}
if (_.isArray(key)) {
filterPart = _.zip(key, value)
.map(([k, v]) => {
if (valueAsString) {
return `${k}=\\"${v}\\"`;
return `${k}=\\"${v}\\"`
} else {
return `${k}=${v}`;
return `${k}=${v}`
}
})
.join(",");
.join(",")
}
const command = `from ${djangoModelImportPath} import ${djangoModel};
@ -119,13 +119,13 @@ function loadObjectJson(
object = ${djangoModel}.objects.filter(${filterPart}).first();
print(create_json_from_objects(object, ${serializerModel}, many=False));
exit();
`.replace(/(?:\r\n|\r|\n)/g, "");
`.replace(/(?:\r\n|\r|\n)/g, "")
return cy.manageShellCommand(command).then((result) => {
const objectJson = JSON.parse(result.stdout);
const objectJson = JSON.parse(result.stdout)
// console.log(command);
console.log(objectJson);
return objectJson;
});
console.log(objectJson)
return objectJson
})
}
Cypress.Commands.add("loadAssignmentCompletion", (key, value) => {
@ -135,8 +135,8 @@ Cypress.Commands.add("loadAssignmentCompletion", (key, value) => {
"vbv_lernwelt.assignment.models.AssignmentCompletion",
"vbv_lernwelt.assignment.serializers.CypressAssignmentCompletionSerializer",
true
);
});
)
})
Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => {
return loadObjectJson(
@ -145,8 +145,8 @@ Cypress.Commands.add("loadSecurityRequestResponseLog", (key, value) => {
"vbv_lernwelt.core.models.SecurityRequestResponseLog",
"vbv_lernwelt.core.serializers.CypressSecurityRequestResponseLogSerializer",
true
);
});
)
})
Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => {
return loadObjectJson(
@ -155,8 +155,8 @@ Cypress.Commands.add("loadExternalApiRequestLog", (key, value) => {
"vbv_lernwelt.core.models.ExternalApiRequestLog",
"vbv_lernwelt.core.serializers.CypressExternalApiRequestLogSerializer",
true
);
});
)
})
Cypress.Commands.add("loadFeedbackResponse", (key, value) => {
return loadObjectJson(
@ -165,8 +165,8 @@ Cypress.Commands.add("loadFeedbackResponse", (key, value) => {
"vbv_lernwelt.feedback.models.FeedbackResponse",
"vbv_lernwelt.feedback.serializers.CypressFeedbackResponseSerializer",
true
);
});
)
})
Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
return loadObjectJson(
@ -175,37 +175,37 @@ Cypress.Commands.add("loadCheckoutInformation", (key, value) => {
"vbv_lernwelt.shop.models.CheckoutInformation",
"vbv_lernwelt.shop.serializers.CypressCheckoutInformationSerializer",
true
);
});
)
})
Cypress.Commands.add("makeSelfEvaluation", (answers) => {
for (let i = 0; i < answers.length; i++) {
const answer = answers[i];
const answer = answers[i]
if (answer) {
cy.get('[data-cy="success"]').click();
cy.get('[data-cy="success"]').click()
} else {
cy.get('[data-cy="fail"]').click();
cy.get('[data-cy="fail"]').click()
}
if (i < answers.length - 1) {
cy.get('[data-cy="next-step"]').click({ force: true });
cy.get('[data-cy="next-step"]').click({ force: true })
} else {
cy.get('[data-cy="complete-and-continue"]').click({ force: true });
cy.get('[data-cy="complete-and-continue"]').click({ force: true })
}
}
});
})
Cypress.Commands.add("learningContentMultiLayoutNextStep", () => {
return cy.get('[data-cy="next-step"]').click({ force: true });
});
return cy.get('[data-cy="next-step"]').click({ force: true })
})
Cypress.Commands.add("learningContentMultiLayoutPreviousStep", () => {
return cy.get('[data-cy="previous-step"]').click({ force: true });
});
return cy.get('[data-cy="previous-step"]').click({ force: true })
})
Cypress.Commands.add("testLearningContentTitle", (title) => {
return cy.get('[data-cy="lc-title"]').should("contain", title);
});
return cy.get('[data-cy="lc-title"]').should("contain", title)
})
Cypress.Commands.add("testLearningContentSubtitle", (subtitle) => {
return cy.get('[data-cy="lc-subtitle"]').should("contain", subtitle);
});
return cy.get('[data-cy="lc-subtitle"]').should("contain", subtitle)
})

View File

@ -6,6 +6,7 @@
"test": "echo \"Error: no test specified\" && exit 1",
"cypress:open": "cypress open",
"cypress:ci": "cypress-cloud run --parallel --record",
"cypress:install": "cypress install",
"prettier": "npm run prettier --prefix client"
},
"devDependencies": {

View File

@ -59,12 +59,32 @@ from vbv_lernwelt.shop.models import CheckoutInformation
@click.option(
"--create-assignment-completion/--no-create-assignment-completion",
default=False,
help="will create assignment completion data for test-student1@example.com",
help="will create assignment completion data for test-student1@example.com by default. Other user can be specified with --assignment-completion-user",
)
@click.option(
"--assignment-completion-user-id",
default=TEST_STUDENT1_USER_ID,
help="user to create assignment completion for. Defaults to test-student1@example.com. Hint: Is only evaluated if --create-assignment-completion is set.",
)
@click.option(
"--assignment-completion-course-session-id",
default=TEST_COURSE_SESSION_BERN_ID,
help="course session to create assignment completion in. Defaults to 'Test Bern 2022 a'. Hint: Is only evaluated if --create-assignment-completion is set.",
)
@click.option(
"--create-assignment-evaluation/--no-create-assignment-evaluation",
default=False,
help="will create assignment evaluation data for test-student1@example.com",
help="will create assignment evaluation data for test-student1@example.com by default. Other user can be specified with --assignment-evaluation-user",
)
@click.option(
"--assignment-evaluation-user-id",
default=TEST_TRAINER1_USER_ID,
help="user to create assignment evaluation for. Defaults to test-trainer1@example.com. Hint: Is only evaluated if --create-assignment-completion is set.",
)
@click.option(
"--assignment-evaluation-course-session-id",
default=TEST_COURSE_SESSION_BERN_ID,
help="course session to create assignment evaluation in. Defaults to 'Test Bern 2022 a'. Hint: Is only evaluated if --create-assignment-completion is set.",
)
@click.option(
"--assignment-evaluation-scores",
@ -81,7 +101,17 @@ from vbv_lernwelt.shop.models import CheckoutInformation
type=(int, int, float),
default=(None, None, 0.0),
metavar="USER_POINTS MAX_POINTS POINTS_DEDUCTED",
help="Create edoniq result data for test-student1@example.com with user points and max points",
help="Create edoniq result data for test-student1@example.com by default with user points and max points. Use --edoniq-user-id to specify a different user.",
)
@click.option(
"--edoniq-user-id",
default=TEST_STUDENT1_USER_ID,
help="User to create edoniq test results for. Defaults to test-student1@example.com. Hint: Is only evaluated if --create-edoniq-test-results is set.",
)
@click.option(
"--edoniq-course-session-id",
default=TEST_COURSE_SESSION_BERN_ID,
help="course session to create edoniq test results in. Defaults to 'Test Bern 2022 a'. Hint: Is only evaluated if --create-edoniq-test-results is set.",
)
@click.option(
"--create-feedback-responses/--no-create-feedback-responses",
@ -120,10 +150,16 @@ from vbv_lernwelt.shop.models import CheckoutInformation
)
def command(
create_assignment_completion,
assignment_completion_user_id,
assignment_completion_course_session_id,
create_assignment_evaluation,
assignment_evaluation_user_id,
assignment_evaluation_course_session_id,
assignment_evaluation_scores,
assignment_points_deducted,
create_edoniq_test_results,
edoniq_user_id,
edoniq_course_session_id,
create_feedback_responses,
create_course_completion_performance_criteria,
create_attendance_days,
@ -170,15 +206,19 @@ def command(
assignment=Assignment.objects.get(
slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
),
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
user=User.objects.get(id=TEST_STUDENT1_USER_ID),
course_session=CourseSession.objects.get(
id=assignment_completion_course_session_id
),
user=User.objects.get(id=assignment_completion_user_id),
)
create_test_assignment_submitted_data(
assignment=Assignment.objects.get(
slug="test-lehrgang-assignment-mein-kundenstamm"
),
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
user=User.objects.get(id=TEST_STUDENT1_USER_ID),
course_session=CourseSession.objects.get(
id=assignment_completion_course_session_id
),
user=User.objects.get(id=assignment_completion_user_id),
)
if create_assignment_evaluation:
if not assignment_evaluation_scores:
@ -195,9 +235,11 @@ def command(
assignment=Assignment.objects.get(
slug="test-lehrgang-assignment-überprüfen-einer-motorfahrzeugs-versicherungspolice"
),
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID),
evaluation_user=User.objects.get(id=TEST_TRAINER1_USER_ID),
course_session=CourseSession.objects.get(
id=assignment_evaluation_course_session_id
),
assignment_user=User.objects.get(id=assignment_completion_user_id),
evaluation_user=User.objects.get(id=assignment_evaluation_user_id),
input_scores=assignment_evaluation_scores,
points_deducted=assignment_points_deducted,
)
@ -211,8 +253,8 @@ def command(
assignment=Assignment.objects.get(
slug="test-lehrgang-assignment-edoniq-wissens-und-verständisfragen-circle-fahrzeug-demo"
),
course_session=CourseSession.objects.get(id=TEST_COURSE_SESSION_BERN_ID),
assignment_user=User.objects.get(id=TEST_STUDENT1_USER_ID),
course_session=CourseSession.objects.get(id=edoniq_course_session_id),
assignment_user=User.objects.get(id=edoniq_user_id),
user_points=user_points,
max_points=max_points,
evaluation_points_deducted=points_deducted,