From 5f2de114a9d3b1c59750ef39f38e5a80cd259415 Mon Sep 17 00:00:00 2001 From: Marieke Date: Wed, 17 Apr 2024 23:35:05 +0200 Subject: [PATCH 001/304] adminpanel tabel met mock data --- frontend/src/i18n/locales/en.ts | 3 ++ frontend/src/i18n/locales/nl.ts | 3 ++ frontend/src/views/AdminView.vue | 50 +++++++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index b26dbe76..455c8388 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -43,4 +43,7 @@ export default { courses: "My courses", announcements: "Announcements", }, + admin: { + users: "Users", + }, }; diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index ad7aa9b7..6315c590 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -43,4 +43,7 @@ export default { courses: "Mijn vakken", announcements: "Meldingen", }, + admin: { + users: "Gebruikers", + }, }; diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index 141a911f..1415aa2a 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -1,3 +1,51 @@ + + From 20c0edabbfe291b7fcd278415304f9aed58c36de Mon Sep 17 00:00:00 2001 From: Bram Reyniers Date: Fri, 19 Apr 2024 08:49:31 +0200 Subject: [PATCH 002/304] only require certificates when running during development --- frontend/vite.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 39603e6d..f73aa5fb 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -6,7 +6,7 @@ import vue from "@vitejs/plugin-vue"; import fs from "fs"; import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"; -const isTesting = process.env.NODE_ENV === 'test' +const needsCertificate = process.env.NODE_ENV === 'development' export default defineConfig({ plugins: [ @@ -22,7 +22,7 @@ export default defineConfig({ }, }, server: { - https: isTesting ? undefined : { + https: !needsCertificate ? undefined : { key: fs.readFileSync('./local-cert/localhost-key.pem'), cert: fs.readFileSync('./local-cert/localhost.pem') }, From 1a68e601b36a4d68f0e5ec2e126f2a1440476c27 Mon Sep 17 00:00:00 2001 From: Bram Reyniers Date: Fri, 19 Apr 2024 08:55:17 +0200 Subject: [PATCH 003/304] quick fix --- .../src/components/project/submit/SubmitCard.vue | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/project/submit/SubmitCard.vue b/frontend/src/components/project/submit/SubmitCard.vue index 024042e0..d94f5576 100644 --- a/frontend/src/components/project/submit/SubmitCard.vue +++ b/frontend/src/components/project/submit/SubmitCard.vue @@ -3,10 +3,12 @@ {{ $t("submit.submit_title") }} - +

Loading...

+

Error

+ - + @@ -30,10 +32,17 @@ diff --git a/frontend/src/components/ApolloHeader.vue b/frontend/src/components/ApolloHeader.vue index 35ea45d3..3587afb4 100644 --- a/frontend/src/components/ApolloHeader.vue +++ b/frontend/src/components/ApolloHeader.vue @@ -1,5 +1,5 @@ @@ -36,6 +44,7 @@ import type Project from "@/models/Project"; import { useSubjectInstructorsQuery, useSubjectQuery } from "@/queries/Subject"; import { computed } from "vue"; import { toRefs } from "vue"; +import { Quill } from "@vueup/vue-quill"; const props = defineProps<{ project: Project; @@ -46,6 +55,12 @@ const { project } = toRefs(props); const { data: subject } = useSubjectQuery(computed(() => project.value.subject_id)); const { data: instructors } = useSubjectInstructorsQuery(computed(() => project.value.subject_id)); + +const renderQuillContent = (content: string) => { + const quill = new Quill(document.createElement("div")); + quill.root.innerHTML = content; + return quill.root.innerHTML; +}; diff --git a/frontend/src/components/project/submit/SubmitInfo.vue b/frontend/src/components/project/submit/SubmitInfo.vue index d269db9a..39007414 100644 --- a/frontend/src/components/project/submit/SubmitInfo.vue +++ b/frontend/src/components/project/submit/SubmitInfo.vue @@ -6,17 +6,19 @@ {{ $t("submit.latest_submission") }} {{ $d(latestSubmission.date, "long") }} - No submissions found. -
- {{ - $t("submit.status_submission", { status: latestSubmission.status }) - }} -
- - - {{ $t("submit.new_submission") }} - - + + {{ $t("submit.no_submission") }} + + + {{ $t("submit.status_submission", { status: SubmissionStatus[latestSubmission.status] }) }} + + + + + {{ $t("submit.new_submission") }} + + + @@ -26,6 +28,7 @@ import { toRefs, computed } from "vue"; import type Project from "@/models/Project"; import type Group from "@/models/Group"; import type Submission from "@/models/Submission"; +import { SubmissionStatus } from "@/models/Submission"; const props = defineProps<{ group: Group; @@ -36,17 +39,30 @@ const { group, project } = toRefs(props); const { data: submissions } = useSubmissionQuery(); -const latestSubmission = computed( - () => - submissions.value?.filter( - (submission: Submission) => - submission.group_id === group.value.id && submission.project_id === project.value.id - )[0] -); +const latestSubmission = computed(() => { + // Log all submissions before filtering + console.log("All submissions:", submissions.value); + + // Filter submissions for the specific group and project + const filteredSubmissions = submissions.value?.filter( + (submission: Submission) => + submission.group_id === group.value.id && submission.project_id === project.value.id + ); + + // Sort the filtered submissions by date in descending order + const sortedSubmissions = filteredSubmissions?.sort((a, b) => { + return new Date(b.date).getTime() - new Date(a.date).getTime(); + }); + + // Return the latest submission (first item after sorting) + return sortedSubmissions?.[0]; +}); + +const submissionStatus = computed(() =>{ + SubmissionStatus[latestSubmission?.value.status] + return $t() +}); + - + diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 255d0402..07ef0988 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -27,15 +27,18 @@ export default { latest_submission: "Latest submission:", new_submission: "Submit new", status_submission: "Submission was: {status}", + no_submission: "No submission found", }, project: { deadline: "Deadline", details_button: "Project details", - group_button: "Join Group", + group_button: "Join group", needhelp_button: "Need help", group: "Group {number}", assignment: "Assignment:", myProject: "My projects:", + return_course: "Back to subject", + capacity_group: "Maximum amount of group members: ", }, navigation: { home: "Home", diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index 3ea22ba4..86340b01 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -23,19 +23,22 @@ export default { add_files_button: "Bestanden toevoegen", no_files_added: "Je hebt nog geen bestanden toegevoegd.", no_files_warning: "Voeg ten minste een bestand toe om een indiening te maken.", - submissions: "Indiening zone", + submissions: "Indieningszone", latest_submission: "Laatste indiening:", new_submission: "Nieuwe indiening", - status_submission: "Indiening was: {status}", + status_submission: "Indiening is: {status}", + no_submission: "Geen indiening teruggevonden", }, project: { deadline: "Deadline", details_button: "Naar project", - group_button: "Vind Groep", + group_button: "Vind groep", needhelp_button: "Hulp nodig", group: "Groep {number}", assignment: "Opdracht:", myProject: "Mijn projecten:", + return_course: "Terug naar vak", + capacity_group: "Maximaal aantal leden per groep: ", }, navigation: { home: "Hoofdscherm", diff --git a/frontend/src/models/Submission.ts b/frontend/src/models/Submission.ts index c87ad7fe..598f8680 100644 --- a/frontend/src/models/Submission.ts +++ b/frontend/src/models/Submission.ts @@ -3,6 +3,12 @@ export default interface Submission { group_id: number; date: Date; project_id: number; - status: string; + status: number; files_uuid: string; } + +export enum SubmissionStatus { + Denied = 0, + InProgress = 1, + Accepted = 2, +} diff --git a/frontend/src/views/ProjectView.vue b/frontend/src/views/ProjectView.vue index 796a8f92..88dd9402 100644 --- a/frontend/src/views/ProjectView.vue +++ b/frontend/src/views/ProjectView.vue @@ -8,12 +8,17 @@ - + + + {{ $t("project.return_course") }} + + + {{ $t("project.group", { number: group!.id }) }} - + {{ $t("project.group_button") }} @@ -53,6 +58,8 @@ const { const isDataLoading = computed(() => isProjectLoading.value || isGroupLoading.value); const isDataError = computed(() => isProjectError.value || isGroupError.value); + +const isSoloProject = computed(() => project.value.capacity === 1); diff --git a/frontend/src/views/subject/SubjectView.vue b/frontend/src/views/subject/SubjectView.vue index 90392ed9..16f18617 100644 --- a/frontend/src/views/subject/SubjectView.vue +++ b/frontend/src/views/subject/SubjectView.vue @@ -10,7 +10,7 @@ timeout="3000" color="primary" > - Register link copied to clipboard. + {{ $t("subject.register_link_button.snackbar")}} @@ -29,11 +30,7 @@ const emit = defineEmits<{ const onAcademicYearChanged = (academicYear: number) => { emit("academic-year-changed", academicYear); -} - +}; - + diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 0976c18f..c5859ecf 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -88,8 +88,9 @@ export default { register_link_button: { title: "Register link", snackbar: "Register link copied to clipboard.", - tooltip: "Copy register link for this subject, this can be shared with students to register for the subject." - } + tooltip: + "Copy register link for this subject, this can be shared with students to register for the subject.", + }, }, subjects: { title: "My Subjects", diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index dc9f4ed7..25f85145 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -88,7 +88,8 @@ export default { register_link_button: { title: "Registreer link", snackbar: "Registreer link gekopieerd naar klembord.", - tooltip: "Kopieer de registreer link voor dit vak, deze kan gedeeld worden met studenten om zich te registreren voor het vak.", + tooltip: + "Kopieer de registreer link voor dit vak, deze kan gedeeld worden met studenten om zich te registreren voor het vak.", }, }, diff --git a/frontend/src/queries/Subject.ts b/frontend/src/queries/Subject.ts index db01b78c..9103eea1 100644 --- a/frontend/src/queries/Subject.ts +++ b/frontend/src/queries/Subject.ts @@ -1,4 +1,4 @@ -import {useQuery, type UseQueryReturnType} from "@tanstack/vue-query"; +import { useQuery, type UseQueryReturnType } from "@tanstack/vue-query"; import { getSubject, getSubjectInstructors, @@ -6,9 +6,10 @@ import { getSubjects, getSubjectStudents, getSubjectByUuid, - registerToSubject, getSubjectUuid, + registerToSubject, + getSubjectUuid, } from "@/services/subject"; -import {type Ref, computed} from "vue"; +import { type Ref, computed } from "vue"; import type Subject from "@/models/Subject"; import type User from "@/models/User"; import type Project from "@/models/Project"; @@ -80,7 +81,7 @@ export function useSubjectDetailsQuery( getSubjectInstructors(subjectId.value!), getSubjectStudents(subjectId.value!), getSubjectProjects(subjectId.value!), - getSubjectUuid(subjectId.value!) + getSubjectUuid(subjectId.value!), ])) as [Subject, User[], User[], Project[], string]; return { @@ -113,4 +114,3 @@ export function registerSubjectQuery(uuid: Ref): UseQueryReturnType false, }); } - diff --git a/frontend/src/views/SubmitView.vue b/frontend/src/views/SubmitView.vue index 8c6a611e..4fbdd550 100644 --- a/frontend/src/views/SubmitView.vue +++ b/frontend/src/views/SubmitView.vue @@ -1,4 +1,4 @@ - @@ -36,6 +44,7 @@ import type Project from "@/models/Project"; import { useSubjectInstructorsQuery, useSubjectQuery } from "@/queries/Subject"; import { computed } from "vue"; import { toRefs } from "vue"; +import { Quill } from "@vueup/vue-quill"; const props = defineProps<{ project: Project; @@ -46,6 +55,12 @@ const { project } = toRefs(props); const { data: subject } = useSubjectQuery(computed(() => project.value.subject_id)); const { data: instructors } = useSubjectInstructorsQuery(computed(() => project.value.subject_id)); + +const renderQuillContent = (content: string) => { + const quill = new Quill(document.createElement("div")); + quill.root.innerHTML = content; + return quill.root.innerHTML; +}; diff --git a/frontend/src/components/project/submit/SubmitInfo.vue b/frontend/src/components/project/submit/SubmitInfo.vue index d269db9a..39007414 100644 --- a/frontend/src/components/project/submit/SubmitInfo.vue +++ b/frontend/src/components/project/submit/SubmitInfo.vue @@ -6,17 +6,19 @@ {{ $t("submit.latest_submission") }} {{ $d(latestSubmission.date, "long") }} - No submissions found. -
- {{ - $t("submit.status_submission", { status: latestSubmission.status }) - }} -
- - - {{ $t("submit.new_submission") }} - - + + {{ $t("submit.no_submission") }} + + + {{ $t("submit.status_submission", { status: SubmissionStatus[latestSubmission.status] }) }} + + + + + {{ $t("submit.new_submission") }} + + + @@ -26,6 +28,7 @@ import { toRefs, computed } from "vue"; import type Project from "@/models/Project"; import type Group from "@/models/Group"; import type Submission from "@/models/Submission"; +import { SubmissionStatus } from "@/models/Submission"; const props = defineProps<{ group: Group; @@ -36,17 +39,30 @@ const { group, project } = toRefs(props); const { data: submissions } = useSubmissionQuery(); -const latestSubmission = computed( - () => - submissions.value?.filter( - (submission: Submission) => - submission.group_id === group.value.id && submission.project_id === project.value.id - )[0] -); +const latestSubmission = computed(() => { + // Log all submissions before filtering + console.log("All submissions:", submissions.value); + + // Filter submissions for the specific group and project + const filteredSubmissions = submissions.value?.filter( + (submission: Submission) => + submission.group_id === group.value.id && submission.project_id === project.value.id + ); + + // Sort the filtered submissions by date in descending order + const sortedSubmissions = filteredSubmissions?.sort((a, b) => { + return new Date(b.date).getTime() - new Date(a.date).getTime(); + }); + + // Return the latest submission (first item after sorting) + return sortedSubmissions?.[0]; +}); + +const submissionStatus = computed(() =>{ + SubmissionStatus[latestSubmission?.value.status] + return $t() +}); + - + diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 255d0402..07ef0988 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -27,15 +27,18 @@ export default { latest_submission: "Latest submission:", new_submission: "Submit new", status_submission: "Submission was: {status}", + no_submission: "No submission found", }, project: { deadline: "Deadline", details_button: "Project details", - group_button: "Join Group", + group_button: "Join group", needhelp_button: "Need help", group: "Group {number}", assignment: "Assignment:", myProject: "My projects:", + return_course: "Back to subject", + capacity_group: "Maximum amount of group members: ", }, navigation: { home: "Home", diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index 3ea22ba4..86340b01 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -23,19 +23,22 @@ export default { add_files_button: "Bestanden toevoegen", no_files_added: "Je hebt nog geen bestanden toegevoegd.", no_files_warning: "Voeg ten minste een bestand toe om een indiening te maken.", - submissions: "Indiening zone", + submissions: "Indieningszone", latest_submission: "Laatste indiening:", new_submission: "Nieuwe indiening", - status_submission: "Indiening was: {status}", + status_submission: "Indiening is: {status}", + no_submission: "Geen indiening teruggevonden", }, project: { deadline: "Deadline", details_button: "Naar project", - group_button: "Vind Groep", + group_button: "Vind groep", needhelp_button: "Hulp nodig", group: "Groep {number}", assignment: "Opdracht:", myProject: "Mijn projecten:", + return_course: "Terug naar vak", + capacity_group: "Maximaal aantal leden per groep: ", }, navigation: { home: "Hoofdscherm", diff --git a/frontend/src/models/Submission.ts b/frontend/src/models/Submission.ts index c87ad7fe..598f8680 100644 --- a/frontend/src/models/Submission.ts +++ b/frontend/src/models/Submission.ts @@ -3,6 +3,12 @@ export default interface Submission { group_id: number; date: Date; project_id: number; - status: string; + status: number; files_uuid: string; } + +export enum SubmissionStatus { + Denied = 0, + InProgress = 1, + Accepted = 2, +} diff --git a/frontend/src/views/ProjectView.vue b/frontend/src/views/ProjectView.vue index 796a8f92..88dd9402 100644 --- a/frontend/src/views/ProjectView.vue +++ b/frontend/src/views/ProjectView.vue @@ -8,12 +8,17 @@
- + + + {{ $t("project.return_course") }} + + + {{ $t("project.group", { number: group!.id }) }} - + {{ $t("project.group_button") }} @@ -53,6 +58,8 @@ const { const isDataLoading = computed(() => isProjectLoading.value || isGroupLoading.value); const isDataError = computed(() => isProjectError.value || isGroupError.value); + +const isSoloProject = computed(() => project.value.capacity === 1); diff --git a/frontend/src/components/project/submit/SubmitInfo.vue b/frontend/src/components/project/submit/SubmitInfo.vue index 39007414..7727de93 100644 --- a/frontend/src/components/project/submit/SubmitInfo.vue +++ b/frontend/src/components/project/submit/SubmitInfo.vue @@ -10,7 +10,7 @@ {{ $t("submit.no_submission") }} - {{ $t("submit.status_submission", { status: SubmissionStatus[latestSubmission.status] }) }} + {{ $t("submit.status_submission", { status: getSubmissionStatus }) }} @@ -29,19 +29,20 @@ import type Project from "@/models/Project"; import type Group from "@/models/Group"; import type Submission from "@/models/Submission"; import { SubmissionStatus } from "@/models/Submission"; +import {useI18n} from "vue-i18n"; const props = defineProps<{ group: Group; project: Project; }>(); +const { t } = useI18n(); + const { group, project } = toRefs(props); const { data: submissions } = useSubmissionQuery(); const latestSubmission = computed(() => { - // Log all submissions before filtering - console.log("All submissions:", submissions.value); // Filter submissions for the specific group and project const filteredSubmissions = submissions.value?.filter( @@ -58,9 +59,13 @@ const latestSubmission = computed(() => { return sortedSubmissions?.[0]; }); -const submissionStatus = computed(() =>{ - SubmissionStatus[latestSubmission?.value.status] - return $t() +const getSubmissionStatus = computed(() => { + if (latestSubmission.value) { + const statusKey = SubmissionStatus[latestSubmission.value.status]; + return t("submit." + statusKey); + } else { + return ""; + } }); diff --git a/frontend/src/composables/useIsTeacher.ts b/frontend/src/composables/useIsTeacher.ts index 8f052a76..31e5c67c 100644 --- a/frontend/src/composables/useIsTeacher.ts +++ b/frontend/src/composables/useIsTeacher.ts @@ -2,7 +2,7 @@ import { computed } from "vue"; import { useUserQuery } from "@/queries/User"; export default function useIsTeacher() { - const { data: user } = useUserQuery(); + const { data: user } = useUserQuery(null); const isTeacher = computed(() => user.value?.is_teacher || false); return { isTeacher }; } diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 07ef0988..742c5cbd 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -26,8 +26,11 @@ export default { submissions: "Submission zone", latest_submission: "Latest submission:", new_submission: "Submit new", - status_submission: "Submission was: {status}", + status_submission: "Submission is: {status}", no_submission: "No submission found", + Denied: "denied", + InProgress: "in progress", + Accepted: "accepted", }, project: { deadline: "Deadline", diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index 86340b01..decd4978 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -28,6 +28,9 @@ export default { new_submission: "Nieuwe indiening", status_submission: "Indiening is: {status}", no_submission: "Geen indiening teruggevonden", + Denied: "afgewezen", + InProgress: "in behandeling", + Accepted: "geaccepteerd", }, project: { deadline: "Deadline", diff --git a/frontend/src/models/Subject.ts b/frontend/src/models/Subject.ts index 366a626f..57087a95 100644 --- a/frontend/src/models/Subject.ts +++ b/frontend/src/models/Subject.ts @@ -1,6 +1,9 @@ export default interface Subject { id: number; name: string; + academic_year: string; + uuid: string; + email: string; } export interface UserSubjectList { diff --git a/frontend/src/queries/Subject.ts b/frontend/src/queries/Subject.ts index 6cd286d5..a7d2348c 100644 --- a/frontend/src/queries/Subject.ts +++ b/frontend/src/queries/Subject.ts @@ -11,7 +11,6 @@ import { import { type Ref, computed } from "vue"; import type Subject from "@/models/Subject"; import type User from "@/models/User"; -import type Subject from "@/models/Subject"; import type Project from "@/models/Project"; import type SubjectDetails from "@/models/SubjectDetails"; diff --git a/frontend/src/views/ProjectView.vue b/frontend/src/views/ProjectView.vue index 88dd9402..3daeae2f 100644 --- a/frontend/src/views/ProjectView.vue +++ b/frontend/src/views/ProjectView.vue @@ -5,7 +5,7 @@
- + @@ -13,17 +13,22 @@ {{ $t("project.return_course") }} - + {{ $t("project.group", { number: group!.id }) }} - + {{ $t("project.group_button") }} - + + + + {{ $t("project.group", { number: group!.id }) }} + +
@@ -32,11 +37,12 @@ From 8776c73ba8b8667d56ed93697134c10af0ed1757 Mon Sep 17 00:00:00 2001 From: miboelae Date: Thu, 2 May 2024 14:37:58 +0200 Subject: [PATCH 052/304] run linter --- .../src/components/project/ProjectInfo.vue | 9 +-- .../components/project/submit/SubmitInfo.vue | 4 +- frontend/src/views/ProjectView.vue | 57 ++++++++++++------- 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/frontend/src/components/project/ProjectInfo.vue b/frontend/src/components/project/ProjectInfo.vue index 70ae4af5..56635c11 100644 --- a/frontend/src/components/project/ProjectInfo.vue +++ b/frontend/src/components/project/ProjectInfo.vue @@ -46,8 +46,8 @@ import type Project from "@/models/Project"; import type Group from "@/models/Group"; import SubmitInfo from "@/components/project/submit/SubmitInfo.vue"; -import {toRefs} from "vue"; -import {Quill} from "@vueup/vue-quill"; +import { toRefs } from "vue"; +import { Quill } from "@vueup/vue-quill"; import User from "@/models/User"; import Subject from "@/models/Subject"; @@ -60,18 +60,15 @@ const props = defineProps<{ const { project, group, instructors, subject } = toRefs(props); - const renderQuillContent = (content: string) => { const quill = new Quill(document.createElement("div")); quill.root.innerHTML = content; return quill.root.innerHTML; }; - diff --git a/frontend/src/views/ProjectView.vue b/frontend/src/views/ProjectView.vue index 50c42419..70dd4fb3 100644 --- a/frontend/src/views/ProjectView.vue +++ b/frontend/src/views/ProjectView.vue @@ -5,7 +5,12 @@
- + @@ -13,17 +18,27 @@ {{ $t("project.return_course") }} - + {{ $t("project.group", { number: group!.id }) }} - + {{ $t("project.group_button") }} - + {{ $t("project.edit") }} @@ -38,11 +53,11 @@ diff --git a/frontend/src/components/project/submit/SubmitInfo.vue b/frontend/src/components/project/submit/SubmitInfo.vue index d269db9a..05939494 100644 --- a/frontend/src/components/project/submit/SubmitInfo.vue +++ b/frontend/src/components/project/submit/SubmitInfo.vue @@ -9,23 +9,26 @@ No submissions found.
{{ - $t("submit.status_submission", { status: latestSubmission.status }) + $t("submit.status_submission", { status: Status[latestSubmission.status] }) }}
- - + + {{ $t("submit.new_submission") }} - + + {{ $t("project.submissions_list") }} + + diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index f1554149..3f371178 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -6,6 +6,7 @@ export default { loading: { loading_page: "Loading...", }, + no: "no", }, login: { about: "The official submission application of Ghent University", @@ -28,6 +29,18 @@ export default { new_submission: "Submit new", status_submission: "Submission was: {status}", }, + submission: { + status: "Submission status:", + datetime: "Submission time:", + remarks: "Remarks", + remarks_empty: "No remarks for this submission", + files: "Files", + download_info: "Click on filename to download", + after_deadline: "After deadline", + submissions_title: "Submissions for project {project}", + no_submissions: "No submissions yet", + docker_test: "Tests Output", + }, project: { deadline: "Deadline", details_button: "Project details", @@ -36,6 +49,7 @@ export default { group: "Group {number}", assignment: "Assignment:", myProject: "My projects:", + submissions_list: "All submissions", not_found: "No projects found.", archived: "Archived", not_finished: "Not Finished", diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index 603bbb73..f6b54120 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -6,6 +6,7 @@ export default { loading: { loading_page: "Aan het laden...", }, + no: "geen", }, login: { about: "De officiële indienapplicatie van de Universiteit Gent", @@ -28,6 +29,18 @@ export default { new_submission: "Nieuwe indiening", status_submission: "Indiening was: {status}", }, + submission: { + status: "Indiening status: {status}", + datetime: "Indiening tijdstip:", + remarks: "Opmerkingen", + remarks_empty: "Geen opmerkingen voor deze indiening", + files: "Bestanden", + download_info: "Klik op bestandsnaam om te downloaden", + after_deadline: "Na deadline", + submissions_title: "Indieningen voor project {project}", + no_submissions: "Nog geen indieningen", + docker_test: "Testen Output", + }, project: { deadline: "Deadline", details_button: "Naar project", @@ -36,6 +49,7 @@ export default { group: "Groep {number}", assignment: "Opdracht:", myProject: "Mijn projecten:", + submissions_list: "Alle indieningen", not_found: "Geen projecten teruggevonden.", archived: "Gearchiveerd", not_finished: "Onafgewerkt", diff --git a/frontend/src/models/File.ts b/frontend/src/models/File.ts new file mode 100644 index 00000000..c9dacd33 --- /dev/null +++ b/frontend/src/models/File.ts @@ -0,0 +1,4 @@ +export default interface FileInfo { + filename: string; + media_type: string; +} diff --git a/frontend/src/models/Submission.ts b/frontend/src/models/Submission.ts index c87ad7fe..afd7dcf6 100644 --- a/frontend/src/models/Submission.ts +++ b/frontend/src/models/Submission.ts @@ -1,8 +1,24 @@ +export enum Status { + InProgress = 1, + Accepted = 2, + Rejected = 3, + Crashed = 4, +} + +export interface TestResult { + succeeded: boolean; + value: string; +} + export default interface Submission { id: number; group_id: number; date: Date; project_id: number; - status: string; + status: Status; files_uuid: string; + remarks: string; + stdout: string | undefined; + stderr: string | undefined; + testresults: TestResult[]; } diff --git a/frontend/src/queries/Group.ts b/frontend/src/queries/Group.ts index 41c78afb..f3b33b56 100644 --- a/frontend/src/queries/Group.ts +++ b/frontend/src/queries/Group.ts @@ -4,7 +4,15 @@ import type { Ref } from "vue"; import type { UseMutationReturnType, UseQueryReturnType } from "@tanstack/vue-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query"; import { computed } from "vue"; -import { createGroups, getGroupWithProjectId, getUserGroups, joinGroup } from "@/services/group"; +import { + createGroups, + getGroup, + getGroupWithProjectId, + getSubmissions, + getUserGroups, + joinGroup, +} from "@/services/group"; +import type Submission from "@/models/Submission"; function USER_GROUPS_QUERY_KEY(): string[] { return ["groups"]; @@ -14,6 +22,22 @@ function PROJECT_USER_GROUP_QUERY_KEY(projectId: number): (string | number)[] { return ["group", "project", projectId]; } +function GROUP_QUERY_KEY(groupId: number): (string | number)[] { + return ["group", groupId]; +} + +function submissionsQueryKey(groupId: number): (string | number)[] { + return ["submissions", groupId]; +} + +export function useGroupQuery(groupId: Ref): UseQueryReturnType { + return useQuery({ + queryKey: GROUP_QUERY_KEY(groupId.value!), + queryFn: () => getGroup(groupId.value!), + enabled: computed(() => groupId.value !== undefined), + }); +} + /** * Get all groups of the current user * @returns An array of Group objects @@ -37,7 +61,7 @@ export function useUserGroupQuery( return useQuery({ queryKey: computed(() => PROJECT_USER_GROUP_QUERY_KEY(projectId.value!)), queryFn: () => getGroupWithProjectId(groups.value!, projectId.value!), - enabled: () => groups.value !== undefined, + enabled: computed(() => groups.value !== undefined), }); } @@ -80,3 +104,14 @@ export function useJoinGroupMutation(): UseMutationReturnType< }, }); } + +// Hook for fetching all submissions belonging to a group +export function useSubmissionsQuery( + groupId: Ref +): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => submissionsQueryKey(groupId.value!)), + queryFn: () => getSubmissions(groupId.value!), + enabled: computed(() => groupId.value !== undefined), + }); +} diff --git a/frontend/src/queries/Project.ts b/frontend/src/queries/Project.ts index 144ad603..22f5e843 100644 --- a/frontend/src/queries/Project.ts +++ b/frontend/src/queries/Project.ts @@ -7,14 +7,7 @@ import { } from "@tanstack/vue-query"; import type Project from "@/models/Project"; import type { ProjectForm } from "@/models/Project"; -import type Submission from "@/models/Submission"; -import { - getProject, - createSubmission, - getSubmissions, - createProject, - getProjects, -} from "@/services/project"; +import { getProject, createProject, getProjects } from "@/services/project"; import { type Ref, computed } from "vue"; // Key generator for project queries @@ -26,10 +19,6 @@ function PROJECTS_QUERY_KEY(): string[] { return ["projects"]; } -function SUBMISSIONS_QUERY_KEY(): string[] { - return ["submissions"]; -} - // Hook for fetching project details export function useProjectQuery( projectId: Ref @@ -48,18 +37,6 @@ export function useProjectsQuery(): UseQueryReturnType { }); } -// Hook for creating a new submission -export function useCreateSubmissionMutation( - groupId: Ref -): UseMutationReturnType { - return useMutation({ - mutationFn: (formData) => createSubmission(groupId.value!, formData), - onError: (error) => { - console.error("Submission creation failed", error); - alert("Could not create submission. Please try again."); - }, - }); -} export function useCreateProjectMutation(): UseMutationReturnType< number, Error, @@ -79,10 +56,3 @@ export function useCreateProjectMutation(): UseMutationReturnType< }, }); } - -export function useSubmissionQuery(): UseQueryReturnType { - return useQuery({ - queryKey: SUBMISSIONS_QUERY_KEY(), - queryFn: () => getSubmissions(), - }); -} diff --git a/frontend/src/queries/Submission.ts b/frontend/src/queries/Submission.ts new file mode 100644 index 00000000..cd0ec4d7 --- /dev/null +++ b/frontend/src/queries/Submission.ts @@ -0,0 +1,56 @@ +import type Submission from "@/models/Submission"; +import { createSubmission, getFiles, getSubmission } from "@/services/submission"; +import { computed, type Ref } from "vue"; +import { + useQuery, + type UseMutationReturnType, + type UseQueryReturnType, + useMutation, +} from "@tanstack/vue-query"; +import type FileInfo from "@/models/File"; + +// Key generator for submission queries +function submissionQueryKey(submissionId: number): (string | number)[] { + return ["submission", submissionId]; +} + +function FILES_QUERY_KEY(submissionId: number): (string | number)[] { + return ["files", submissionId]; +} + +// Hook for fetching submission details +export function useSubmissionQuery( + submissionId: Ref +): UseQueryReturnType { + return useQuery({ + queryKey: computed(() => submissionQueryKey(submissionId.value!)), + queryFn: () => getSubmission(submissionId.value!), + enabled: computed(() => submissionId.value !== undefined), + retry: false, + }); +} + +// Hook for fetching all files of a submission +export function useFilesQuery( + submissionId: Ref +): UseQueryReturnType { + return useQuery({ + queryKey: FILES_QUERY_KEY(submissionId.value!), + queryFn: () => getFiles(submissionId.value!), + enabled: computed(() => submissionId.value !== undefined), + retry: false, + }); +} + +// Hook for creating a new submission +export function useCreateSubmissionMutation( + groupId: Ref +): UseMutationReturnType { + return useMutation({ + mutationFn: (formData) => createSubmission(groupId.value!, formData), + onError: (error) => { + console.error("Submission creation failed", error); + alert("Could not create submission. Please try again."); + }, + }); +} diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 5d3bbbc6..aaccd17a 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -82,6 +82,18 @@ const router = createRouter({ component: () => import("../views/SubjectRegisterView.vue"), props: (route) => ({ uuid: String(route.params.uuid) }), }, + { + path: "/submissions/:submissionId(\\d+)", + name: "submission", + component: () => import("../views/SubmissionView.vue"), + props: (route) => ({ submissionId: Number(route.params.submissionId) }), + }, + { + path: "/groups/:groupId(\\d+)/submissions", + name: "submissions", + component: () => import("../views/SubmissionsView.vue"), + props: (route) => ({ groupId: Number(route.params.groupId) }), + }, { path: "/settings", name: "settings", diff --git a/frontend/src/services/group.ts b/frontend/src/services/group.ts index 58fa10f3..05bc74f4 100644 --- a/frontend/src/services/group.ts +++ b/frontend/src/services/group.ts @@ -1,7 +1,11 @@ import type Group from "@/models/Group"; import type { GroupForm } from "@/models/Group"; -import { authorized_fetch } from "@/services/index"; import type Submission from "@/models/Submission"; +import { authorized_fetch } from "@/services/index"; + +export async function getGroup(groupId: number): Promise { + return authorized_fetch(`/api/groups/${groupId}`, { method: "GET" }); +} export async function getUserGroups(): Promise { return authorized_fetch<{ groups: Group[] }>(`/api/users/me/groups`, { method: "GET" }).then( @@ -33,17 +37,6 @@ export async function createGroups(projectId: number, groups: GroupForm[]): Prom return Promise.all(createPromises); } -export async function createSubmission(groupId: number, formData: FormData): Promise { - return authorized_fetch( - `/api/submissions/?group_id=${groupId}`, - { - method: "POST", - body: formData, - }, - true - ); -} - export async function joinGroup(groupId: number, uid: string): Promise { try { await authorized_fetch(`/api/groups/${groupId}/${uid}`, { @@ -57,3 +50,7 @@ export async function joinGroup(groupId: number, uid: string): Promise { throw error; } } + +export async function getSubmissions(groupId: number): Promise { + return authorized_fetch(`/api/groups/${groupId}/submissions`, { method: "GET" }); +} diff --git a/frontend/src/services/index.ts b/frontend/src/services/index.ts index 7b98d657..d5912551 100644 --- a/frontend/src/services/index.ts +++ b/frontend/src/services/index.ts @@ -13,7 +13,8 @@ const BASE_URL = import.meta.env.VITE_API_URL; export async function authorized_fetch( endpoint: string, requestOptions: RequestInit, - omitContentType: boolean = false + omitContentType: boolean = false, + json: boolean = true ): Promise { const { token, isLoggedIn } = storeToRefs(useAuthStore()); const { refresh } = useAuthStore(); @@ -38,7 +39,8 @@ export async function authorized_fetch( await refresh(); throw new Error("Not authenticated"); } else if (!response.ok) { - throw new Error(response.statusText); + const error = await response.json(); + throw new Error(error.detail); } - return response.json(); + return json ? response.json() : response; } diff --git a/frontend/src/services/project.ts b/frontend/src/services/project.ts index ed562e29..cf108433 100644 --- a/frontend/src/services/project.ts +++ b/frontend/src/services/project.ts @@ -1,6 +1,5 @@ import type Project from "@/models/Project"; import type { ProjectForm } from "@/models/Project"; -import type Submission from "@/models/Submission"; import { authorized_fetch } from "@/services"; export async function createProject(projectData: ProjectForm): Promise { @@ -32,19 +31,3 @@ export async function getProjects(): Promise { }); return result.projects; } - -// Function to create a new submission for a specific group -export async function createSubmission(groupId: number, formData: FormData): Promise { - return authorized_fetch( - `/api/submissions/?group_id=${groupId}`, - { - method: "POST", - body: formData, - }, - true - ); -} - -export async function getSubmissions(): Promise { - return authorized_fetch(`/api/submissions/`, { method: "GET" }); -} diff --git a/frontend/src/services/submission.ts b/frontend/src/services/submission.ts new file mode 100644 index 00000000..e8f70eb9 --- /dev/null +++ b/frontend/src/services/submission.ts @@ -0,0 +1,26 @@ +import type Submission from "@/models/Submission"; +import { authorized_fetch } from "."; +import type FileInfo from "@/models/File"; + +// Function to fetch a specific submission by its ID +export async function getSubmission(submissionId: number): Promise { + return authorized_fetch(`/api/submissions/${submissionId}`, { method: "GET" }); +} + +// Function to create a new submission for a specific group +export async function createSubmission(groupId: number, formData: FormData): Promise { + return authorized_fetch( + `/api/submissions/?group_id=${groupId}`, + { + method: "POST", + body: formData, + }, + true + ); +} + +export async function getFiles(submissionId: number): Promise { + return authorized_fetch(`/api/submissions/${submissionId}/files`, { + method: "GET", + }); +} diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts new file mode 100644 index 00000000..f08c2875 --- /dev/null +++ b/frontend/src/utils.ts @@ -0,0 +1,14 @@ +import { authorized_fetch } from "./services"; + +export async function download_file(url: string, filename: string) { + const data = await authorized_fetch(url, { method: "GET" }, false, false).then( + (response) => response.blob() + ); + + const objectUrl = URL.createObjectURL(data); + const link = document.createElement("a"); + link.href = objectUrl; + link.setAttribute("download", filename); + document.body.appendChild(link); + link.click(); +} diff --git a/frontend/src/views/SubmissionView.vue b/frontend/src/views/SubmissionView.vue new file mode 100644 index 00000000..53fb277c --- /dev/null +++ b/frontend/src/views/SubmissionView.vue @@ -0,0 +1,33 @@ + + + + + diff --git a/frontend/src/views/SubmissionsView.vue b/frontend/src/views/SubmissionsView.vue new file mode 100644 index 00000000..c94742c6 --- /dev/null +++ b/frontend/src/views/SubmissionsView.vue @@ -0,0 +1,58 @@ + + + From 54b87f19a23dbdda3626d52ed370939453521988 Mon Sep 17 00:00:00 2001 From: Pieter Janin Date: Fri, 3 May 2024 17:57:05 +0200 Subject: [PATCH 055/304] alembic readme --- README.md | 33 ++++++++++++++++++------------ backend/README.md | 2 ++ backend/alembic/README | 1 - backend/alembic/README.md | 43 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 14 deletions(-) delete mode 100644 backend/alembic/README create mode 100644 backend/alembic/README.md diff --git a/README.md b/README.md index 55f619f3..fc1ef1c3 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,33 @@ -# UGent-5 +# Apollo -## Rolverdeling +Apollo is een indieningsplatform waar lesgevers flexibel vereisten kunnen stellen waaraan +indieningen van studenten moeten voldoen. Deze vereisten kunnen in de vorm zijn +van checks op de ingediende bestandsstructuur en/of automatisch lopende tests +wanneer een indiening gemaakt wordt. -| Rol | Verantwoordelijke | -| ------------- | ------------- | -| Groepsleider | Marieke Sinnaeve | -| Technische lead | Bram Reyniers | -| Systeembeheerder | Xander Bil | -| Customer Relations Officer | Pieter Janin | -| Frontendbeheerder | Mattis Cauwel | -| Backendbeheerder | Dries Huybens | -| Documentatiebeheerder | Pieter Janin | -| Testbeheerder | Michaël Boelaert | +Studenten krijgen feedback te zien over hun indiening en weten zo of hun indiening +voldoet aan de projectvereisten. ## Wiki Informatie over de gebruikte technologieën, de gebruikershandleiding en meer kan je vinden in de [wiki](https://github.com/SELab-2/UGent-5/wiki). -## Setup ontwikkelomgeving +## Voor ontwikkelaars De instructies voor het opzetten van de ontwikkelomgeving van de frontend kan je [hier](frontend/README.md) vinden. De instructies voor de backend staan [hier](backend/REAMDE.md). ## API Geautomatiseerde clients kunnen interageren met de webapplicatie via de [API](https://sel2-5.ugent.be/api/docs). + +## Het team + +| | | +|------------------|---------------------------------------------------| +| Xander Bil | Systeembeheerder | +| Michaël Boelaert | Testbeheerder | +| Mattis Cauwel | Frontendbeheerder | +| Dries Huybens | Backendbeheerder | +| Pieter Janin | Customer Relations Officer, Documentatiebeheerder | +| Bram Reyniers | Technische lead | +| Marieke Sinnaeve | Team lead | diff --git a/backend/README.md b/backend/README.md index 73f5e952..52991678 100644 --- a/backend/README.md +++ b/backend/README.md @@ -71,6 +71,8 @@ DATABASE_URI="postgresql://username:password@localhost:5432/dbname" alembic upgrade head ``` +You can find more info about alembic [here](alembic/README.md). + #### Managing the database ```sh # Stop the database container diff --git a/backend/alembic/README b/backend/alembic/README deleted file mode 100644 index 2500aa1b..00000000 --- a/backend/alembic/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. diff --git a/backend/alembic/README.md b/backend/alembic/README.md new file mode 100644 index 00000000..66f22a7a --- /dev/null +++ b/backend/alembic/README.md @@ -0,0 +1,43 @@ +# Alembic + +From the docs: + +> [Alembic](https://alembic.sqlalchemy.org/en/latest/) is a lightweight database +migration tool for usage with the [SQLAlchemy](https://www.sqlalchemy.org/) +Database Toolkit for Python. + +It allows us to generate database schemas from Python SQLAlchemy code, found in each +`models.py` file. + +## Usage + +Here are some of the most commonly used commands you might need. + +#### Automatically generate a revision script after modifying database models in Python: + +```sh +alembic revision --autogenerate -m "my_revision_name" +``` + +Make sure to manually review the generated script in `alembic/versions` +and apply adjustments if needed. + +#### Run a migration: this will upgrade the database schema to the most recent revision. + +```sh +alembic upgrade head +``` + +#### Undo the most recent revision: + +```sh +alembic downgrade -1 +``` + +#### Reset the database to its initial (empty) state: + +```sh +alembic downgrade base +``` + +For more examples, see the [official alembic tutorial](https://alembic.sqlalchemy.org/en/latest/tutorial.html). From 22e08d5b788204a7078a915220bcb156f3476242 Mon Sep 17 00:00:00 2001 From: Pieter Janin Date: Fri, 3 May 2024 17:59:37 +0200 Subject: [PATCH 056/304] hoofdletter --- backend/alembic/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/alembic/README.md b/backend/alembic/README.md index 66f22a7a..5161364b 100644 --- a/backend/alembic/README.md +++ b/backend/alembic/README.md @@ -40,4 +40,4 @@ alembic downgrade -1 alembic downgrade base ``` -For more examples, see the [official alembic tutorial](https://alembic.sqlalchemy.org/en/latest/tutorial.html). +For more examples, see the [official Alembic tutorial](https://alembic.sqlalchemy.org/en/latest/tutorial.html). From a8da6a8935d8465b1968b2015829dcb89a2268ac Mon Sep 17 00:00:00 2001 From: Marieke Date: Sat, 4 May 2024 14:48:23 +0200 Subject: [PATCH 057/304] theme store works --- frontend/src/App.vue | 5 +++ frontend/src/components/ApolloHeader.vue | 11 ++++-- .../components/navigation/DropDownList.vue | 2 +- frontend/src/components/navigation/NavBar.vue | 2 +- .../{ => switcher}/LocaleSwitcher.vue | 1 - .../src/components/switcher/ThemeSwitcher.vue | 39 +++++++++++++++++++ frontend/src/stores/theme-store.ts | 16 ++++++++ frontend/src/views/LoginView.vue | 2 +- 8 files changed, 71 insertions(+), 7 deletions(-) rename frontend/src/components/{ => switcher}/LocaleSwitcher.vue (96%) create mode 100644 frontend/src/components/switcher/ThemeSwitcher.vue create mode 100644 frontend/src/stores/theme-store.ts diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 6fc4b7d4..b0d41a0c 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -19,6 +19,8 @@ import NavBar from "@/components/navigation/NavBar.vue"; import { computed, onBeforeMount, ref } from "vue"; import { useI18n } from "vue-i18n"; import { useLocale } from "@/stores/locale-store"; +import { useThemeStore } from "@/stores/theme-store"; +import { useTheme } from 'vuetify'; const navBar = ref | null>(null); @@ -28,7 +30,10 @@ const hideHeader = computed(() => currentRoute.meta.hideHeader); onBeforeMount(() => { const { locale } = useI18n(); const { selectedLocale } = useLocale(); + const { storedTheme } = useThemeStore(); + const theme = useTheme(); locale.value = selectedLocale; + theme.global.name.value = storedTheme }); diff --git a/frontend/src/components/ApolloHeader.vue b/frontend/src/components/ApolloHeader.vue index 8c4387cf..8cfded1b 100644 --- a/frontend/src/components/ApolloHeader.vue +++ b/frontend/src/components/ApolloHeader.vue @@ -10,6 +10,7 @@
+
@@ -17,8 +18,9 @@ + + diff --git a/frontend/src/stores/theme-store.ts b/frontend/src/stores/theme-store.ts new file mode 100644 index 00000000..46eab399 --- /dev/null +++ b/frontend/src/stores/theme-store.ts @@ -0,0 +1,16 @@ +import { defineStore } from "pinia"; +import { ref } from "vue"; + +export const useThemeStore = defineStore("theme", () => { + const storedTheme = ref(localStorage.getItem("theme")); + + function setTheme(newTheme?: string | null) { + if (!newTheme) { + return; + } + storedTheme.value = newTheme; + localStorage.setItem("theme", newTheme); + } + + return { storedTheme, setTheme }; +}); diff --git a/frontend/src/views/LoginView.vue b/frontend/src/views/LoginView.vue index c68b9cbf..7644ffc3 100644 --- a/frontend/src/views/LoginView.vue +++ b/frontend/src/views/LoginView.vue @@ -21,7 +21,7 @@ From 346cfe871bd60e5d758b5fbff322c46aa4de5dfa Mon Sep 17 00:00:00 2001 From: Pieter Janin Date: Sat, 4 May 2024 15:14:21 +0200 Subject: [PATCH 058/304] beter zo --- backend/alembic/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/alembic/README.md b/backend/alembic/README.md index 5161364b..d5cbbb71 100644 --- a/backend/alembic/README.md +++ b/backend/alembic/README.md @@ -19,8 +19,8 @@ Here are some of the most commonly used commands you might need. alembic revision --autogenerate -m "my_revision_name" ``` -Make sure to manually review the generated script in `alembic/versions` -and apply adjustments if needed. +Make sure to review the generated script in `alembic/versions` +and make adjustments if needed. #### Run a migration: this will upgrade the database schema to the most recent revision. From bafd0f06fdd81a0f1b9ebba5dafd22f263bff60b Mon Sep 17 00:00:00 2001 From: Pieter Janin Date: Sat, 4 May 2024 15:33:50 +0200 Subject: [PATCH 059/304] main readme engels --- README.md | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index fc1ef1c3..4489966d 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,38 @@ # Apollo -Apollo is een indieningsplatform waar lesgevers flexibel vereisten kunnen stellen waaraan -indieningen van studenten moeten voldoen. Deze vereisten kunnen in de vorm zijn -van checks op de ingediende bestandsstructuur en/of automatisch lopende tests -wanneer een indiening gemaakt wordt. +Apollo is an online submission platform where instructors can flexibly set requirements for +student submissions. These requirements can range from simple checks on the submitted +file structure to test scripts that run when a submission is made. -Studenten krijgen feedback te zien over hun indiening en weten zo of hun indiening -voldoet aan de projectvereisten. +Students quickly receive feedback on their submission, allowing them to know if it meets +the project requirements. + +This repository hosts the web application's source code. To use Apollo, visit https://sel2-5.ugent.be. ## Wiki -Informatie over de gebruikte technologieën, de gebruikershandleiding en meer kan je vinden in de [wiki](https://github.com/SELab-2/UGent-5/wiki). +Documentation, including a user manual for teachers, can be found in the +[Apollo wiki](https://github.com/SELab-2/UGent-5/wiki). + +## For Developers -## Voor ontwikkelaars +Instructions for setting up the frontend development environment can be found +[here](frontend/README.md). -De instructies voor het opzetten van de ontwikkelomgeving van de frontend kan je [hier](frontend/README.md) vinden. De instructies voor de backend staan [hier](backend/REAMDE.md). +Instructions for the backend are located [here](backend/REAMDE.md). ## API -Geautomatiseerde clients kunnen interageren met de webapplicatie via de [API](https://sel2-5.ugent.be/api/docs). +Automated clients can interact with the web application via the [API](https://sel2-5.ugent.be/api/docs). -## Het team +## The team | | | |------------------|---------------------------------------------------| -| Xander Bil | Systeembeheerder | -| Michaël Boelaert | Testbeheerder | -| Mattis Cauwel | Frontendbeheerder | -| Dries Huybens | Backendbeheerder | -| Pieter Janin | Customer Relations Officer, Documentatiebeheerder | -| Bram Reyniers | Technische lead | -| Marieke Sinnaeve | Team lead | +| Xander Bil | System Administrator | +| Michaël Boelaert | Test Manager | +| Mattis Cauwel | Frontend Manager | +| Dries Huybens | Backend Manager | +| Pieter Janin | Customer Relations Officer, Documentation Manager | +| Bram Reyniers | Technical Lead | +| Marieke Sinnaeve | Team Lead | From 6946511b1edba74a93d8caec4f88a0c69dcee4e1 Mon Sep 17 00:00:00 2001 From: Pieter Janin Date: Sat, 4 May 2024 16:22:43 +0200 Subject: [PATCH 060/304] submission requirements fix --- .../18fb90307213_project_requirements_fix.py | 35 ++++++ backend/src/project/models.py | 2 +- backend/tests/test_project.py | 3 +- backend/tests/test_submission.py | 102 ++++++++++++++++-- 4 files changed, 133 insertions(+), 9 deletions(-) create mode 100644 backend/alembic/versions/18fb90307213_project_requirements_fix.py diff --git a/backend/alembic/versions/18fb90307213_project_requirements_fix.py b/backend/alembic/versions/18fb90307213_project_requirements_fix.py new file mode 100644 index 00000000..d0a94668 --- /dev/null +++ b/backend/alembic/versions/18fb90307213_project_requirements_fix.py @@ -0,0 +1,35 @@ +"""project_requirements_fix: fixes bug where project requirements would remain in the database after the parent +project would be deleted. + +Revision ID: 18fb90307213 +Revises: e0c97995e669 +Create Date: 2024-05-04 15:42:43.114843 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = '18fb90307213' +down_revision: Union[str, None] = 'e0c97995e669' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('requirement', 'project_id', + existing_type=sa.INTEGER(), + nullable=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('requirement', 'project_id', + existing_type=sa.INTEGER(), + nullable=True) + # ### end Alembic commands ### diff --git a/backend/src/project/models.py b/backend/src/project/models.py index 627bee4a..1efa8ab4 100644 --- a/backend/src/project/models.py +++ b/backend/src/project/models.py @@ -49,7 +49,7 @@ class Requirement(Base): id: Mapped[int] = mapped_column(primary_key=True) project_id: Mapped[int] = mapped_column(ForeignKey( - "project.id", ondelete="CASCADE"), nullable=True) + "project.id", ondelete="CASCADE"), nullable=False) project: Mapped["Project"] = relationship(back_populates="requirements") # True for mandatory False for prohibited diff --git a/backend/tests/test_project.py b/backend/tests/test_project.py index daedd7aa..277b68df 100644 --- a/backend/tests/test_project.py +++ b/backend/tests/test_project.py @@ -18,8 +18,7 @@ "enroll_deadline": future_date.strftime("%Y-%m-%dT%H:%M:%SZ"), "is_visible": True, "capacity": 1, - "requirements": [], - "test_files": [], + "requirements": [{"mandatory": "false", "value": "*.pdf"}], } diff --git a/backend/tests/test_submission.py b/backend/tests/test_submission.py index 5c152e14..b2f9ed50 100644 --- a/backend/tests/test_submission.py +++ b/backend/tests/test_submission.py @@ -1,19 +1,57 @@ -import shutil +from datetime import datetime, timezone, timedelta import pytest +import pytest_asyncio from httpx import AsyncClient +from sqlalchemy.ext.asyncio import AsyncSession + from src.auth.exceptions import NotAuthorized import os -from src.docker_tests.utils import submissions_path +from src.user.service import set_admin # Import Fixtures from tests.test_group import group_id from tests.test_project import project_id from tests.test_subject import subject_id +from tests.test_docker import cleanup_files + +subject = {"name": "test subject"} +future_date = datetime.now(timezone.utc) + timedelta(weeks=1) +project_with_reqs = { + "name": "test project", + "subject_id": 0, # temp needs to be filled in by actual subject id + "deadline": future_date.strftime("%Y-%m-%dT%H:%M:%SZ"), + "description": "test", + "enroll_deadline": future_date.strftime("%Y-%m-%dT%H:%M:%SZ"), + "is_visible": True, + "capacity": 1, + "requirements": [{"mandatory": "true", "value": "*.py"}, {"mandatory": "false", "value": "*.pdf"}], +} + +group_data = {"team_name": "test group", "project_id": 0} + + +@pytest_asyncio.fixture +async def project_with_reqs_id(client: AsyncClient, db: AsyncSession, subject_id: int) -> int: + """Create new project""" + project_with_reqs["subject_id"] = subject_id + await set_admin(db, "test", True) + response = await client.post("/api/projects/", json=project_with_reqs) + await set_admin(db, "test", False) + return response.json()["id"] + + +@pytest_asyncio.fixture +async def group_with_reqs_id(client: AsyncClient, db: AsyncSession, project_with_reqs_id: int): + group_data["project_id"] = project_with_reqs_id + await set_admin(db, "test", True) + response = await client.post("/api/groups/", json=group_data) + await set_admin(db, "test", False) + return response.json()["id"] @pytest.mark.asyncio -async def test_create_submission(client: AsyncClient, group_id: int): +async def test_create_submission(client: AsyncClient, group_id: int, cleanup_files): with open("testfile1.txt", "w") as f: f.write("content1") with open("testfile2.txt", "w") as f: @@ -29,7 +67,6 @@ async def test_create_submission(client: AsyncClient, group_id: int): # Submit await client.post(f"/api/groups/{group_id}") # Join group response = await client.post(f"/api/submissions/", params={"group_id": group_id}, files=files) - files_uuid = response.json()['files_uuid'] assert response.status_code == 201 # Leave group @@ -64,7 +101,60 @@ async def test_create_submission(client: AsyncClient, group_id: int): # cleanup files os.remove("testfile1.txt") os.remove("testfile2.txt") - shutil.rmtree(submissions_path(files_uuid)) + await cleanup_files(id) -# TODO: check submission with project requirements +@pytest.mark.asyncio +async def test_project_requirements(client: AsyncClient, group_with_reqs_id: int, cleanup_files): + mandatory_path = "mandatory.py" + forbidden_path = "forbidden.pdf" + optional_path = "whatever.txt" + with open(mandatory_path, "w") as f: + f.write("content1") + with open(forbidden_path, "w") as f: + f.write("content2") + with open(optional_path, "w") as f: + f.write("content2") + + mandatory = ('files', open(mandatory_path, 'rb')) + forbidden = ('files', open(forbidden_path, 'rb')) + optional = ('files', open(optional_path, 'rb')) + + await client.post(f"/api/groups/{group_with_reqs_id}") # Join group + + # Submit without mandatory + response = await client.post("/api/submissions/", params={"group_id": group_with_reqs_id}, files=[optional]) + assert response.status_code == 422 + assert len(response.json()["detail"]) == 1 + assert response.json()["detail"][0]["requirement"] == "*.py" + assert response.json()["detail"][0]["type"] == "mandatory" + + # Submit with forbidden + response = await client.post( + "/api/submissions/", params={"group_id": group_with_reqs_id}, files=[optional, forbidden] + ) + assert response.status_code == 422 + assert len(response.json()["detail"]) == 2 + reqs = [(req["requirement"], req["type"]) for req in response.json()["detail"]] + assert ("*.py", "mandatory") in reqs + assert ("*.pdf", "forbidden") in reqs + + # Submit with forbidden and mandatory + response = await client.post( + "/api/submissions/", params={"group_id": group_with_reqs_id}, files=[optional, forbidden, mandatory] + ) + assert response.status_code == 422 + assert len(response.json()["detail"]) == 1 + reqs = [(req["requirement"], req["type"]) for req in response.json()["detail"]] + assert ("*.pdf", "forbidden") == reqs[0] + + # Submit with mandatory + response = await client.post( + "/api/submissions/", params={"group_id": group_with_reqs_id}, files=[optional, mandatory] + ) + assert response.status_code == 201 + await cleanup_files(response.json()["id"]) + + # cleanup files + for path in [mandatory_path, forbidden_path, optional_path]: + os.remove(path) From a1a130aed129b3edcbf16c458504973c9c68cc82 Mon Sep 17 00:00:00 2001 From: Pieter Janin Date: Sat, 4 May 2024 16:44:48 +0200 Subject: [PATCH 061/304] fix #162 --- backend/src/project/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/project/models.py b/backend/src/project/models.py index 1efa8ab4..175964f3 100644 --- a/backend/src/project/models.py +++ b/backend/src/project/models.py @@ -27,7 +27,7 @@ class Project(Base): ) requirements: Mapped[List["Requirement"]] = relationship( - back_populates="project", lazy="joined") + back_populates="project", lazy="joined", passive_deletes="all") # see submission/models/Submission test_files_uuid: Mapped[str | None] = mapped_column(nullable=True) From 9a09110c65b17bbf85fcba0c944fd6fd161b540c Mon Sep 17 00:00:00 2001 From: Pieter Janin Date: Sat, 4 May 2024 16:55:48 +0200 Subject: [PATCH 062/304] autopep --- .../versions/18fb90307213_project_requirements_fix.py | 8 ++++---- backend/src/project/models.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/alembic/versions/18fb90307213_project_requirements_fix.py b/backend/alembic/versions/18fb90307213_project_requirements_fix.py index d0a94668..b2c40390 100644 --- a/backend/alembic/versions/18fb90307213_project_requirements_fix.py +++ b/backend/alembic/versions/18fb90307213_project_requirements_fix.py @@ -22,14 +22,14 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.alter_column('requirement', 'project_id', - existing_type=sa.INTEGER(), - nullable=False) + existing_type=sa.INTEGER(), + nullable=False) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.alter_column('requirement', 'project_id', - existing_type=sa.INTEGER(), - nullable=True) + existing_type=sa.INTEGER(), + nullable=True) # ### end Alembic commands ### diff --git a/backend/src/project/models.py b/backend/src/project/models.py index 175964f3..b0bb0a4d 100644 --- a/backend/src/project/models.py +++ b/backend/src/project/models.py @@ -27,7 +27,8 @@ class Project(Base): ) requirements: Mapped[List["Requirement"]] = relationship( - back_populates="project", lazy="joined", passive_deletes="all") # see submission/models/Submission + # see submission/models/Submission -> testresults + back_populates="project", lazy="joined", passive_deletes="all") test_files_uuid: Mapped[str | None] = mapped_column(nullable=True) From 41bc1999dd20270732f09e5399b67227f9153823 Mon Sep 17 00:00:00 2001 From: Marieke Date: Sat, 4 May 2024 17:34:40 +0200 Subject: [PATCH 063/304] dark theme colors --- frontend/src/components/ApolloHeader.vue | 4 ++-- .../components/home/listcontent/DeadlineItem.vue | 6 +++--- .../components/home/listcontent/SubjectItem.vue | 6 +++--- .../src/components/navigation/DropDownList.vue | 4 ++++ frontend/src/components/navigation/NavBar.vue | 1 - frontend/src/components/navigation/NavButton.vue | 4 +--- .../src/components/switcher/LocaleSwitcher.vue | 4 ++-- frontend/src/plugins/vuetify.ts | 14 ++++++++++---- 8 files changed, 25 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/ApolloHeader.vue b/frontend/src/components/ApolloHeader.vue index 8cfded1b..3d7945f5 100644 --- a/frontend/src/components/ApolloHeader.vue +++ b/frontend/src/components/ApolloHeader.vue @@ -45,7 +45,7 @@ const { isLoggedIn } = storeToRefs(useAuthStore()); } .v-app-bar-nav-icon{ - color: rgb(var(--v-theme-secondary)); + color: rgb(var(--v-theme-navtext)); } .leftContent { @@ -55,7 +55,7 @@ const { isLoggedIn } = storeToRefs(useAuthStore()); } .logout { - color: rgb(var(--v-theme-secondary)); + color: rgb(var(--v-theme-navtext)); } .switcher{ diff --git a/frontend/src/components/home/listcontent/DeadlineItem.vue b/frontend/src/components/home/listcontent/DeadlineItem.vue index ab9f7d88..61286694 100644 --- a/frontend/src/components/home/listcontent/DeadlineItem.vue +++ b/frontend/src/components/home/listcontent/DeadlineItem.vue @@ -51,7 +51,7 @@ const navigateToProject = () => { diff --git a/frontend/src/components/home/listcontent/SubjectItem.vue b/frontend/src/components/home/listcontent/SubjectItem.vue index 18445acc..0cf2fe63 100644 --- a/frontend/src/components/home/listcontent/SubjectItem.vue +++ b/frontend/src/components/home/listcontent/SubjectItem.vue @@ -41,12 +41,12 @@ const navigateToCourse = () => { align-items: center; transition: background-color 0.3s; cursor: pointer; - background-color: #ffffff; + background-color: rgb(var(--v-theme-background)); border-radius: 2px; } .coursebtn:hover { - background-color: #E9EDFA; + background-color: rgb(var(--v-theme-tertiary)); } .chevron { @@ -56,6 +56,6 @@ const navigateToCourse = () => { } .teacher { - color: lightslategrey; + color: rgb(var(--v-theme-textsecondary)) } diff --git a/frontend/src/components/navigation/DropDownList.vue b/frontend/src/components/navigation/DropDownList.vue index a75b0b47..ae6cc021 100644 --- a/frontend/src/components/navigation/DropDownList.vue +++ b/frontend/src/components/navigation/DropDownList.vue @@ -6,12 +6,16 @@ + + + diff --git a/frontend/src/assets/base.scss b/frontend/src/assets/base.scss index 84eca241..c6059b62 100644 --- a/frontend/src/assets/base.scss +++ b/frontend/src/assets/base.scss @@ -5,7 +5,7 @@ --color-primary-dark: color-mod(var(--color-primary) shade(15%)); --color-primary-bg: color-mod(var(--color-primary) alpha(30)); - --color-secondary: #EBEFFB; + --color-secondary: #ebeffb; --color-accent: #ffd200; --color-accent-light: color-mod(var(--color-accent) tint(15%)); diff --git a/frontend/src/components/ApolloHeader.vue b/frontend/src/components/ApolloHeader.vue index 3d7945f5..3882ce78 100644 --- a/frontend/src/components/ApolloHeader.vue +++ b/frontend/src/components/ApolloHeader.vue @@ -10,7 +10,7 @@
- +
@@ -20,7 +20,7 @@ import { RouterLink } from "vue-router"; import { useDisplay } from "vuetify"; import LocaleSwitcher from "./switcher/LocaleSwitcher.vue"; import DropDownMobile from "@/components/navigation/DropDownMobile.vue"; -import ThemeSwitcher from "@/components/switcher/ThemeSwitcher.vue" +import ThemeSwitcher from "@/components/switcher/ThemeSwitcher.vue"; import LogoutButton from "@/components/buttons/LogoutButton.vue"; import { useAuthStore } from "@/stores/auth-store"; import { storeToRefs } from "pinia"; @@ -44,7 +44,7 @@ const { isLoggedIn } = storeToRefs(useAuthStore()); padding: 5px 0; } -.v-app-bar-nav-icon{ +.v-app-bar-nav-icon { color: rgb(var(--v-theme-navtext)); } @@ -58,7 +58,7 @@ const { isLoggedIn } = storeToRefs(useAuthStore()); color: rgb(var(--v-theme-navtext)); } -.switcher{ +.switcher { margin-left: 20px; } diff --git a/frontend/src/components/home/listcontent/DeadlineItem.vue b/frontend/src/components/home/listcontent/DeadlineItem.vue index 61286694..743ff2d1 100644 --- a/frontend/src/components/home/listcontent/DeadlineItem.vue +++ b/frontend/src/components/home/listcontent/DeadlineItem.vue @@ -1,8 +1,6 @@ + diff --git a/frontend/src/components/project/ProjectMiniCard.vue b/frontend/src/components/project/ProjectMiniCard.vue index f6e1b66b..dd90fc78 100644 --- a/frontend/src/components/project/ProjectMiniCard.vue +++ b/frontend/src/components/project/ProjectMiniCard.vue @@ -1,5 +1,5 @@ From d47464aa767ec0ed4aa15ead7925e2d486e9c4bd Mon Sep 17 00:00:00 2001 From: Marieke Date: Wed, 15 May 2024 12:57:49 +0200 Subject: [PATCH 161/304] test setup --- frontend/tests/views/GroupsView.spec.ts | 87 +++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 frontend/tests/views/GroupsView.spec.ts diff --git a/frontend/tests/views/GroupsView.spec.ts b/frontend/tests/views/GroupsView.spec.ts new file mode 100644 index 00000000..fd888012 --- /dev/null +++ b/frontend/tests/views/GroupsView.spec.ts @@ -0,0 +1,87 @@ +import {mount} from "@vue/test-utils"; +import {expect, describe, it, vi} from "vitest"; +import GroupsView from "@/views/GroupsView.vue" +import {ref} from "vue"; +import {useCreateGroupsMutation} from "../../src/queries/Group"; + +const mockProject = { + name: "testproject" +} + +const testProjectQuery = { + data: mockProject, + isLoading: ref(true), + isError: ref(true), + setIsError(value){ + this.isError.value = value; + }, + setIsLoading(value){ + this.isLoading.value = value; + } +}; + +vi.mock('@/queries/Project', () => ({ + useProjectQuery: vi.fn(() => testProjectQuery), +})); + +const testProjectGroupsQuery = { + isLoading: ref(true), + isError: ref(true), + setIsError(value){ + this.isError.value = value; + }, + setIsLoading(value){ + this.isLoading.value = value; + } +}; + +const testCreateGroupsMutation = { + mutateAsync: vi.fn() +} + +vi.mock('@/queries/Group', () => ({ + useProjectGroupsQuery: vi.fn(() => testProjectGroupsQuery), + useCreateGroupsMutation: vi.fn(() => testCreateGroupsMutation) +})) + +const testUserQuery ={ + isLoading: ref(true), + isError: ref(true), + setIsError(value){ + this.isError.value = value; + }, + setIsLoading(value){ + this.isLoading.value = value; + } +} + +vi.mock('@/queries/User', () => ({ + useUserQuery: vi.fn(() => testUserQuery), +})) + +const testSubjectStudentsQuery ={ + isLoading: ref(true), + isError: ref(true), + setIsError(value){ + this.isError.value = value; + }, + setIsLoading(value){ + this.isLoading.value = value; + } +} + +vi.mock('@/queries/Subject', () => ({ + useSubjectStudentsQuery: vi.fn(() => testSubjectStudentsQuery), +})) + +describe("GroupsView", () => { + const wrapper = mount(GroupsView, { + props: { + projectId: 1 + } + }); + + it("render if loading", () => { + expect(wrapper.text()).toContain("Aan het laden...") + }) +}) From 2d1fe9a4795531344fe1f4810a3c46c46cfbf4f6 Mon Sep 17 00:00:00 2001 From: Marieke Date: Wed, 15 May 2024 13:45:00 +0200 Subject: [PATCH 162/304] =?UTF-8?q?ok=C3=A9=20rebase=20werkt=20eindelijk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/home/cards/GroupCard.vue | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/components/home/cards/GroupCard.vue b/frontend/src/components/home/cards/GroupCard.vue index d0e1043e..538c49b6 100644 --- a/frontend/src/components/home/cards/GroupCard.vue +++ b/frontend/src/components/home/cards/GroupCard.vue @@ -45,8 +45,6 @@ const amountOfMembers = computed(() => { return group.value.members.length; }); -const isTeacher = computed(() => user.value.is_teacher || false); - const toGroupPage = async () => { router.push(`/groups/${group.value.id}`); }; From e13df2d96b27c75c0cabd6f4713d1d43d16fbcb3 Mon Sep 17 00:00:00 2001 From: Pieter Janin Date: Wed, 15 May 2024 13:50:39 +0200 Subject: [PATCH 163/304] move tests for consistency --- .../{project/submit => submission}/SubmitCard.spec.ts | 2 +- .../{project/submit => submission}/SubmitForm.spec.ts | 2 +- .../{project/submit => submission}/SubmitInfo.spec.ts | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename frontend/tests/components/{project/submit => submission}/SubmitCard.spec.ts (94%) rename frontend/tests/components/{project/submit => submission}/SubmitForm.spec.ts (94%) rename frontend/tests/components/{project/submit => submission}/SubmitInfo.spec.ts (100%) diff --git a/frontend/tests/components/project/submit/SubmitCard.spec.ts b/frontend/tests/components/submission/SubmitCard.spec.ts similarity index 94% rename from frontend/tests/components/project/submit/SubmitCard.spec.ts rename to frontend/tests/components/submission/SubmitCard.spec.ts index c828d9f2..4950d76b 100644 --- a/frontend/tests/components/project/submit/SubmitCard.spec.ts +++ b/frontend/tests/components/submission/SubmitCard.spec.ts @@ -1,6 +1,6 @@ import {mount} from "@vue/test-utils"; import {expect, describe, it, vi} from "vitest"; -import SubmitCard from "../../../../src/components/submission/SubmitCard.vue" +import SubmitCard from "../../../src/components/submission/SubmitCard.vue" import {ref} from "vue"; diff --git a/frontend/tests/components/project/submit/SubmitForm.spec.ts b/frontend/tests/components/submission/SubmitForm.spec.ts similarity index 94% rename from frontend/tests/components/project/submit/SubmitForm.spec.ts rename to frontend/tests/components/submission/SubmitForm.spec.ts index 80e906f4..89a09190 100644 --- a/frontend/tests/components/project/submit/SubmitForm.spec.ts +++ b/frontend/tests/components/submission/SubmitForm.spec.ts @@ -1,6 +1,6 @@ import {mount} from "@vue/test-utils"; import {expect, describe, it, vi} from "vitest"; -import SubmitForm from "../../../../src/components/submission/SubmitForm.vue" +import SubmitForm from "../../../src/components/submission/SubmitForm.vue" import {ref} from "vue"; const mockRouter = { diff --git a/frontend/tests/components/project/submit/SubmitInfo.spec.ts b/frontend/tests/components/submission/SubmitInfo.spec.ts similarity index 100% rename from frontend/tests/components/project/submit/SubmitInfo.spec.ts rename to frontend/tests/components/submission/SubmitInfo.spec.ts From b19ae62e9a821c818fa187866301ec7302fee08e Mon Sep 17 00:00:00 2001 From: Marieke Date: Wed, 15 May 2024 14:41:45 +0200 Subject: [PATCH 164/304] groupsview frontend test --- frontend/src/views/GroupsView.vue | 5 +- frontend/tests/views/GroupsView.spec.ts | 96 ++++++++++++++++++------- 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/frontend/src/views/GroupsView.vue b/frontend/src/views/GroupsView.vue index ecad3039..1387fe90 100644 --- a/frontend/src/views/GroupsView.vue +++ b/frontend/src/views/GroupsView.vue @@ -60,7 +60,10 @@ const { isError: isGroupError, } = useProjectGroupsQuery(projectId); -const { data: user, isLoading: isUserLoading, isError: isUserError } = useUserQuery(null); +const { data: user, + isLoading: isUserLoading, + isError: isUserError +} = useUserQuery(null); const { data: allStudents, diff --git a/frontend/tests/views/GroupsView.spec.ts b/frontend/tests/views/GroupsView.spec.ts index fd888012..f7516e03 100644 --- a/frontend/tests/views/GroupsView.spec.ts +++ b/frontend/tests/views/GroupsView.spec.ts @@ -2,12 +2,26 @@ import {mount} from "@vue/test-utils"; import {expect, describe, it, vi} from "vitest"; import GroupsView from "@/views/GroupsView.vue" import {ref} from "vue"; -import {useCreateGroupsMutation} from "../../src/queries/Group"; const mockProject = { name: "testproject" } +const mockGroups = [ + {id: 1, team_name: "Group 1"} +] + +const mockUser = { + uid: "student", + setUid(value){ + this.uid = value; + } +} + +const mockInstructors = [ + {uid: "teacher"} +] + const testProjectQuery = { data: mockProject, isLoading: ref(true), @@ -25,14 +39,9 @@ vi.mock('@/queries/Project', () => ({ })); const testProjectGroupsQuery = { - isLoading: ref(true), - isError: ref(true), - setIsError(value){ - this.isError.value = value; - }, - setIsLoading(value){ - this.isLoading.value = value; - } + data: mockGroups, + isLoading: ref(false), + isError: ref(false), }; const testCreateGroupsMutation = { @@ -45,14 +54,9 @@ vi.mock('@/queries/Group', () => ({ })) const testUserQuery ={ - isLoading: ref(true), - isError: ref(true), - setIsError(value){ - this.isError.value = value; - }, - setIsLoading(value){ - this.isLoading.value = value; - } + data: ref(mockUser), + isLoading: ref(false), + isError: ref(false), } vi.mock('@/queries/User', () => ({ @@ -60,20 +64,33 @@ vi.mock('@/queries/User', () => ({ })) const testSubjectStudentsQuery ={ - isLoading: ref(true), - isError: ref(true), - setIsError(value){ - this.isError.value = value; - }, - setIsLoading(value){ - this.isLoading.value = value; - } + isLoading: ref(false), + isError: ref(false), +} + +const testSubjectInstructorsQuery ={ + data: ref(mockInstructors), + isLoading: ref(false), + isError: ref(false), } vi.mock('@/queries/Subject', () => ({ useSubjectStudentsQuery: vi.fn(() => testSubjectStudentsQuery), + useSubjectInstructorsQuery: vi.fn(() => testSubjectInstructorsQuery), })) +vi.mock("@/components/StudentsDialog.vue", () => ({ + default: { + template: "
", + }, +})); + +vi.mock("@/components/home/cards/GroupCard.vue", () => ({ + default: { + template: "
", + }, +})); + describe("GroupsView", () => { const wrapper = mount(GroupsView, { props: { @@ -83,5 +100,32 @@ describe("GroupsView", () => { it("render if loading", () => { expect(wrapper.text()).toContain("Aan het laden...") - }) + }); + + it("render if error", async () => { + testProjectQuery.setIsLoading(false) + await wrapper.vm.$nextTick(); + expect(wrapper.text()).toContain("Fout bij het laden van de pagina") + }); + + it("render groupsview", async () => { + testProjectQuery.setIsError(false) + await wrapper.vm.$nextTick(); + const text = wrapper.text() + expect(text).toContain("Project: testproject") + expect(wrapper.findComponent(".studentsDialog").exists()).toBeTruthy() + expect(wrapper.findComponent(".groupCard").exists()).toBeTruthy() + }); + + it("render if user is student", () => { + expect(wrapper.findComponent({name: "VBtn"}).exists()).toBeFalsy() + }); + + it("render if user is teacher", async () => { + testUserQuery.data.value.setUid("teacher") + await wrapper.vm.$nextTick(); + const button = wrapper.findComponent({name: "VBtn"}) + expect(button.exists()).toBeTruthy() + expect(button.text()).toContain("Nieuwe groep") + }); }) From b4df23452cb2f8b139d28098ba2844946c3cf8ea Mon Sep 17 00:00:00 2001 From: Marieke Date: Wed, 15 May 2024 17:16:45 +0200 Subject: [PATCH 165/304] frontend testen --- .../{home/cards => groups}/GroupCard.vue | 8 +- .../{ => groups}/StudentsDialog.vue | 4 +- frontend/src/views/GroupsView.vue | 10 +- .../components/buttons/GroupButtons.spec.ts | 162 ++++++++++++++++++ .../tests/components/groups/GroupCard.spec.ts | 85 +++++++++ .../components/groups/StudentsDialog.spec.ts | 19 ++ frontend/tests/views/GroupsView.spec.ts | 4 +- 7 files changed, 279 insertions(+), 13 deletions(-) rename frontend/src/components/{home/cards => groups}/GroupCard.vue (91%) rename frontend/src/components/{ => groups}/StudentsDialog.vue (94%) create mode 100644 frontend/tests/components/buttons/GroupButtons.spec.ts create mode 100644 frontend/tests/components/groups/GroupCard.spec.ts create mode 100644 frontend/tests/components/groups/StudentsDialog.spec.ts diff --git a/frontend/src/components/home/cards/GroupCard.vue b/frontend/src/components/groups/GroupCard.vue similarity index 91% rename from frontend/src/components/home/cards/GroupCard.vue rename to frontend/src/components/groups/GroupCard.vue index 538c49b6..556b9e0a 100644 --- a/frontend/src/components/home/cards/GroupCard.vue +++ b/frontend/src/components/groups/GroupCard.vue @@ -1,7 +1,7 @@ diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index 1139b087..ff0ef84f 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -126,6 +126,6 @@ export default { close: "sluiten", all_students: "Alle studenten", all_students_course: "Alle studenten in vak:", - to_grouppage: "Naar groepspagina" + to_grouppage: "Naar groepspagina", }, }; diff --git a/frontend/src/views/GroupView.vue b/frontend/src/views/GroupView.vue index 5d64f8f8..fd51cb92 100644 --- a/frontend/src/views/GroupView.vue +++ b/frontend/src/views/GroupView.vue @@ -107,8 +107,7 @@ const { mutateAsync: removeStudent } = useRemoveUserFromGroupMutation(); diff --git a/frontend/src/views/GroupsView.vue b/frontend/src/views/GroupsView.vue index bd33454f..a1ab3a27 100644 --- a/frontend/src/views/GroupsView.vue +++ b/frontend/src/views/GroupsView.vue @@ -4,7 +4,7 @@

{{ $t("group.error") }}

{{ "Project: " + project!.name }}

- +
@@ -27,7 +27,9 @@ {{ $t("group.not_found2") }}
- {{ $t("group.create_group") }} + {{ + $t("group.create_group") + }}
@@ -60,10 +62,7 @@ const { isError: isGroupError, } = useProjectGroupsQuery(projectId); -const { data: user, - isLoading: isUserLoading, - isError: isUserError -} = useUserQuery(null); +const { data: user, isLoading: isUserLoading, isError: isUserError } = useUserQuery(null); const { data: allStudents, @@ -120,8 +119,7 @@ async function createGroup() { From cfa21bbbf6b4b10004607beff4e3e28811afdbe3 Mon Sep 17 00:00:00 2001 From: Pieter Janin Date: Wed, 15 May 2024 17:47:51 +0200 Subject: [PATCH 167/304] file extension warnings --- backend/src/submission/utils.py | 4 +- .../components/project/RequirementsCard.vue | 35 ++++++++++++++- .../src/components/submission/SubmitCard.vue | 18 ++------ .../src/components/submission/SubmitForm.vue | 45 ++++++++++++++----- frontend/src/i18n/locales/en.ts | 2 + frontend/src/i18n/locales/nl.ts | 2 + frontend/src/models/Project.ts | 8 +++- frontend/src/queries/Submission.ts | 10 ++--- .../components/submission/SubmitCard.spec.ts | 2 +- 9 files changed, 89 insertions(+), 37 deletions(-) diff --git a/backend/src/submission/utils.py b/backend/src/submission/utils.py index bc741ea6..0852c925 100644 --- a/backend/src/submission/utils.py +++ b/backend/src/submission/utils.py @@ -32,10 +32,10 @@ def upload_files(files: list[UploadFile], project: Project) -> str: matches = [file for file in filelist if fnmatch.fnmatch(file, r.value)] if not r.mandatory and len(matches): - errors.append({"type": "forbidden", "requirement": r.value, + errors.append({"mandatory": False, "value": r.value, "msg": f"Forbidden file(s) found: {r.value}", "files": matches}) elif r.mandatory and not len(matches): - errors.append({"type": "mandatory", "requirement": r.value, + errors.append({"mandatory": True, "value": r.value, "msg": f"Required file not found: {r.value}"}) if len(errors): diff --git a/frontend/src/components/project/RequirementsCard.vue b/frontend/src/components/project/RequirementsCard.vue index 88983bb0..807f5f7d 100644 --- a/frontend/src/components/project/RequirementsCard.vue +++ b/frontend/src/components/project/RequirementsCard.vue @@ -11,12 +11,38 @@ {{ req.value }} + + {{ $t("project.forbidden") }} {{ req.value }} + + @@ -26,10 +52,11 @@ diff --git a/frontend/src/components/submission/SubmitForm.vue b/frontend/src/components/submission/SubmitForm.vue index a2865e9c..5e33e5dd 100644 --- a/frontend/src/components/submission/SubmitForm.vue +++ b/frontend/src/components/submission/SubmitForm.vue @@ -1,9 +1,12 @@ @@ -51,4 +72,8 @@ async function formOnSubmit(event: SubmitEvent) { .v-textarea { margin-top: 30px; } + +.submission-form { + margin-top: 30px; +} diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index 68a12643..4110d50d 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -63,6 +63,8 @@ export default { requirements: "File requirements", mandatory: "Mandatory", forbidden: "Forbidden", + unmet_mandatory: "This mandatory file was not included in your submission.", + unmet_forbidden: "These submitted files are not allowed:", to_subject: "To subject", }, navigation: { diff --git a/frontend/src/i18n/locales/nl.ts b/frontend/src/i18n/locales/nl.ts index 2dd421e8..4d16ee7f 100644 --- a/frontend/src/i18n/locales/nl.ts +++ b/frontend/src/i18n/locales/nl.ts @@ -63,6 +63,8 @@ export default { requirements: "Bestandsvereisten", mandatory: "Verplicht", forbidden: "Verboden", + unmet_mandatory: "Dit verplichte bestand is niet aanwezig in jouw indiening.", + unmet_forbidden: "Deze ingediende bestanden zijn verboden:", to_subject: "Naar vak", }, navigation: { diff --git a/frontend/src/models/Project.ts b/frontend/src/models/Project.ts index d40cd07f..a45ee974 100644 --- a/frontend/src/models/Project.ts +++ b/frontend/src/models/Project.ts @@ -5,7 +5,7 @@ export default interface Project { // groupProjectType: string; // selectedTeachers: string[]; // Assuming you store only teacher IDs subject_id: number; - requirements: [Requirement]; + requirements: Requirement[]; description: string; capacity: number; } @@ -31,6 +31,12 @@ export interface Requirement { value: string; } +export interface UnmetRequirement { + requirement: Requirement; + files: string[] | undefined; +} + + export enum FilterOptions { All = "All", Completed = "Completed", diff --git a/frontend/src/queries/Submission.ts b/frontend/src/queries/Submission.ts index cd0ec4d7..dbe14ac2 100644 --- a/frontend/src/queries/Submission.ts +++ b/frontend/src/queries/Submission.ts @@ -45,12 +45,8 @@ export function useFilesQuery( // Hook for creating a new submission export function useCreateSubmissionMutation( groupId: Ref -): UseMutationReturnType { - return useMutation({ - mutationFn: (formData) => createSubmission(groupId.value!, formData), - onError: (error) => { - console.error("Submission creation failed", error); - alert("Could not create submission. Please try again."); - }, +): UseMutationReturnType { + return useMutation({ + mutationFn: (formData) => createSubmission(groupId.value!, formData) }); } diff --git a/frontend/tests/components/submission/SubmitCard.spec.ts b/frontend/tests/components/submission/SubmitCard.spec.ts index 4950d76b..87960f54 100644 --- a/frontend/tests/components/submission/SubmitCard.spec.ts +++ b/frontend/tests/components/submission/SubmitCard.spec.ts @@ -23,7 +23,7 @@ vi.mock("@/components/project/ProjectMiniCard.vue", () => ({ vi.mock("@/components/project/submit/SubmitForm.vue", () => ({ default: { - template: "
", + template: "
", }, })); From 4cd02db96227eeb675a610c2231e45252b640ebd Mon Sep 17 00:00:00 2001 From: miboelae Date: Wed, 15 May 2024 19:26:09 +0200 Subject: [PATCH 168/304] reroute whenever leaving/joining group --- frontend/src/components/project/ProjectSideBar.vue | 5 +++++ frontend/src/i18n/locales/en.ts | 1 + frontend/src/i18n/locales/nl.ts | 1 + 3 files changed, 7 insertions(+) diff --git a/frontend/src/components/project/ProjectSideBar.vue b/frontend/src/components/project/ProjectSideBar.vue index de694310..9ecdcea7 100644 --- a/frontend/src/components/project/ProjectSideBar.vue +++ b/frontend/src/components/project/ProjectSideBar.vue @@ -14,6 +14,11 @@ {{ $t("project.group_button") }}
+ + + {{ $t("project.to_groups") }} + + Date: Wed, 15 May 2024 23:03:52 +0200 Subject: [PATCH 169/304] cleanup --- .../components/project/RequirementsCard.vue | 42 ++++++++++++------- .../src/components/submission/SubmitForm.vue | 8 ++-- frontend/src/i18n/locales/en.ts | 1 + frontend/src/i18n/locales/nl.ts | 3 +- 4 files changed, 34 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/project/RequirementsCard.vue b/frontend/src/components/project/RequirementsCard.vue index 807f5f7d..140369a2 100644 --- a/frontend/src/components/project/RequirementsCard.vue +++ b/frontend/src/components/project/RequirementsCard.vue @@ -6,60 +6,67 @@ - {{ $t("project.mandatory") }} - + {{ $t("project.mandatory") }} - + {{ req.value }} - diff --git a/frontend/src/components/submission/SubmitCard.vue b/frontend/src/components/submission/SubmitCard.vue index af8b61f8..51845eb1 100644 --- a/frontend/src/components/submission/SubmitCard.vue +++ b/frontend/src/components/submission/SubmitCard.vue @@ -1,15 +1,15 @@ diff --git a/frontend/src/components/submission/SubmitForm.vue b/frontend/src/components/submission/SubmitForm.vue index 8d176903..20a9dbbb 100644 --- a/frontend/src/components/submission/SubmitForm.vue +++ b/frontend/src/components/submission/SubmitForm.vue @@ -1,6 +1,10 @@ diff --git a/frontend/src/components/subject/subjectview/body/projects/SubjectProjectsPage.vue b/frontend/src/components/subject/subjectview/body/projects/SubjectProjectsPage.vue index b84a79ec..b0717710 100644 --- a/frontend/src/components/subject/subjectview/body/projects/SubjectProjectsPage.vue +++ b/frontend/src/components/subject/subjectview/body/projects/SubjectProjectsPage.vue @@ -20,7 +20,6 @@ :project="project" > -

{{ $t("subject.projectsPage.no_projects") }}

@@ -78,7 +77,8 @@ const updateFilterOption = (option: FilterOptions) => { justify-content: center; align-items: center; height: 400px; /* Adjust height as needed */ - border: 1px solid #ccc; + border-radius: 3px; + background-color: rgb(var(--v-theme-background)); } .placeholder p { diff --git a/frontend/src/components/subject/subjectview/body/projects/list/SubjectProjectsList.vue b/frontend/src/components/subject/subjectview/body/projects/list/SubjectProjectsList.vue index ffc25f33..6ef81755 100644 --- a/frontend/src/components/subject/subjectview/body/projects/list/SubjectProjectsList.vue +++ b/frontend/src/components/subject/subjectview/body/projects/list/SubjectProjectsList.vue @@ -9,7 +9,7 @@ v-for="(filter, index) in filterOptions" :key="index" :value="FilterOptions[filter]" - color="primary" + color="text" variant="tonal" > {{ $t(`subject.projectsPage.${filter.toLowerCase()}`) }} @@ -73,7 +73,7 @@ watch(activeButton, (newVal: string) => { diff --git a/frontend/src/components/subject/subjectview/header/SubjectHeaderContainer.vue b/frontend/src/components/subject/subjectview/header/SubjectHeaderContainer.vue index c3019e11..bb1e50ce 100644 --- a/frontend/src/components/subject/subjectview/header/SubjectHeaderContainer.vue +++ b/frontend/src/components/subject/subjectview/header/SubjectHeaderContainer.vue @@ -1,24 +1,17 @@ diff --git a/frontend/src/views/CreateProjectView.vue b/frontend/src/views/CreateProjectView.vue index 52bf5817..211f8b3c 100644 --- a/frontend/src/views/CreateProjectView.vue +++ b/frontend/src/views/CreateProjectView.vue @@ -8,18 +8,14 @@ {{ $t("project.not_found") }}
- + - - - - + + @@ -45,21 +52,7 @@ @update:capacity="handleCapacityChange" @update:selected-option="handleOptionChange" required - /> - - - - - - @@ -112,11 +105,7 @@ /> - - - {{ $t("submit.submit_button") }} - - + {{ $t("submit.submit_button") }} {{ errorMessage }} @@ -475,4 +464,13 @@ function divideStudentsIntoGroups(students: User[], capacity: number) { .alert-bottom-margin { margin-bottom: 15px; } + +.createproject { + border-radius: 3px; + margin-top: 25px; +} + +.radiolist { + padding: 5px; +} diff --git a/frontend/src/views/GroupView.vue b/frontend/src/views/GroupView.vue index f82abb6c..961b68e5 100644 --- a/frontend/src/views/GroupView.vue +++ b/frontend/src/views/GroupView.vue @@ -111,7 +111,10 @@ const isTeacher = computed(() => { if (!user.value || !instructors.value) { return false; } - return instructors.value.some((instructor) => instructor.uid === user.value.uid); + return ( + user.value.is_admin || + instructors.value.some((instructor) => instructor.uid === user.value.uid) + ); }); const amountOfMembers = computed(() => { diff --git a/frontend/src/views/GroupsView.vue b/frontend/src/views/GroupsView.vue index eca52fcf..140c0061 100644 --- a/frontend/src/views/GroupsView.vue +++ b/frontend/src/views/GroupsView.vue @@ -2,35 +2,44 @@

{{ $t("default.loading.loading_page") }}

{{ $t("group.error") }}

-
-

{{ "Project: " + project!.name }}

- - -
- - {{ $t("group.groups") }} - {{ $t("group.members") }} - {{ $t("group.actions") }} - - + +

{{ "Project: " + project!.name }}

+ + +
+ + {{ $t("group.groups") }} + {{ $t("group.members") }} + {{ $t("group.actions") }} + + +
+
+ + {{ $t("group.not_found2") }} + +
+ {{ + $t("group.create_group") + }} +
+ + -
-
- - {{ $t("group.not_found2") }} - -
- {{ - $t("group.create_group") - }} -
+ +
@@ -43,6 +52,7 @@ import { useCurrentUserQuery } from "@/queries/User"; import { type GroupForm } from "@/models/Group"; import { useSubjectInstructorsQuery, useSubjectStudentsQuery } from "@/queries/Subject"; import StudentsDialog from "@/components/groups/StudentsDialog.vue"; +import BackButton from "@/components/buttons/BackButton.vue"; const props = defineProps<{ projectId: number; @@ -99,7 +109,6 @@ const isTeacher = computed(() => { return false; } return ( - user.value.is_teacher || user.value.is_admin || instructors.value.some((instructor) => instructor.uid === user.value.uid) ); @@ -124,6 +133,7 @@ async function createGroup() { diff --git a/frontend/src/views/HomeScreenView.vue b/frontend/src/views/HomeScreenView.vue index de629bd6..8bd6cb57 100644 --- a/frontend/src/views/HomeScreenView.vue +++ b/frontend/src/views/HomeScreenView.vue @@ -1,9 +1,10 @@