Merge branch 'dev' into extra/discussions
This commit is contained in:
		
						commit
						3c20b52b27
					
				
					 6 changed files with 115 additions and 69 deletions
				
			
		|  | @ -1,12 +1,13 @@ | ||||||
| import { getAssignmentRepository, getGroupRepository, getSubmissionRepository } from '../data/repositories.js'; | import { getSubmissionRepository } from '../data/repositories.js'; | ||||||
| import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | import { LearningObjectIdentifier } from '../entities/content/learning-object-identifier.js'; | ||||||
| import { NotFoundException } from '../exceptions/not-found-exception.js'; | import { NotFoundException } from '../exceptions/not-found-exception.js'; | ||||||
| import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; | import { mapToSubmission, mapToSubmissionDTO } from '../interfaces/submission.js'; | ||||||
| import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; | import { SubmissionDTO } from '@dwengo-1/common/interfaces/submission'; | ||||||
| import { fetchStudent } from './students.js'; | import { fetchStudent } from './students.js'; | ||||||
| import { getExistingGroupFromGroupDTO } from './groups.js'; | import { fetchGroup, getExistingGroupFromGroupDTO } from './groups.js'; | ||||||
| import { Submission } from '../entities/assignments/submission.entity.js'; | import { Submission } from '../entities/assignments/submission.entity.js'; | ||||||
| import { Language } from '@dwengo-1/common/util/language'; | import { Language } from '@dwengo-1/common/util/language'; | ||||||
|  | import { fetchAssignment } from './assignments.js'; | ||||||
| 
 | 
 | ||||||
| export async function fetchSubmission(loId: LearningObjectIdentifier, submissionNumber: number): Promise<Submission> { | export async function fetchSubmission(loId: LearningObjectIdentifier, submissionNumber: number): Promise<Submission> { | ||||||
|     const submissionRepository = getSubmissionRepository(); |     const submissionRepository = getSubmissionRepository(); | ||||||
|  | @ -64,15 +65,18 @@ export async function getSubmissionsForLearningObjectAndAssignment( | ||||||
|     groupId?: number |     groupId?: number | ||||||
| ): Promise<SubmissionDTO[]> { | ): Promise<SubmissionDTO[]> { | ||||||
|     const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); |     const loId = new LearningObjectIdentifier(learningObjectHruid, language, version); | ||||||
|     const assignment = await getAssignmentRepository().findByClassIdAndAssignmentId(classId, assignmentId); |  | ||||||
| 
 | 
 | ||||||
|     let submissions: Submission[]; |     try { | ||||||
|     if (groupId !== undefined) { |         let submissions: Submission[]; | ||||||
|         const group = await getGroupRepository().findByAssignmentAndGroupNumber(assignment!, groupId); |         if (groupId !== undefined) { | ||||||
|         submissions = await getSubmissionRepository().findAllSubmissionsForLearningObjectAndGroup(loId, group!); |             const group = await fetchGroup(classId, assignmentId, groupId); | ||||||
|     } else { |             submissions = await getSubmissionRepository().findAllSubmissionsForLearningObjectAndGroup(loId, group); | ||||||
|         submissions = await getSubmissionRepository().findAllSubmissionsForLearningObjectAndAssignment(loId, assignment!); |         } else { | ||||||
|  |             const assignment = await fetchAssignment(classId, assignmentId); | ||||||
|  |             submissions = await getSubmissionRepository().findAllSubmissionsForLearningObjectAndAssignment(loId, assignment); | ||||||
|  |         } | ||||||
|  |         return submissions.map((s) => mapToSubmissionDTO(s)); | ||||||
|  |     } catch (_) { | ||||||
|  |         return []; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     return submissions.map((s) => mapToSubmissionDTO(s)); |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -38,12 +38,13 @@ | ||||||
|                 </template> |                 </template> | ||||||
|             </v-list-item> |             </v-list-item> | ||||||
|             <v-divider></v-divider> |             <v-divider></v-divider> | ||||||
|             <v-expansion-panels v-model="expanded"> |             <div class="nav-scroll-area"> | ||||||
|                 <using-query-result |                 <v-expansion-panels v-model="expanded"> | ||||||
|                     :query-result="allLearningPathsResult" |                     <using-query-result | ||||||
|                     v-slot="learningPaths: { data: LearningPath[] }" |                         :query-result="allLearningPathsResult" | ||||||
|                 > |                         v-slot="learningPaths: { data: LearningPath[] }" | ||||||
|                     <v-expansion-panel |                     > | ||||||
|  |                         <v-expansion-panel | ||||||
|                         v-for="learningPath in learningPaths.data" |                         v-for="learningPath in learningPaths.data" | ||||||
|                         :key="learningPath.hruid" |                         :key="learningPath.hruid" | ||||||
|                         :value="learningPath.hruid" |                         :value="learningPath.hruid" | ||||||
|  | @ -59,9 +60,10 @@ | ||||||
|                                 /> |                                 /> | ||||||
|                             </v-lazy> |                             </v-lazy> | ||||||
|                         </v-expansion-panel-text> |                         </v-expansion-panel-text> | ||||||
|                     </v-expansion-panel> |                         </v-expansion-panel> | ||||||
|                 </using-query-result> |                     </using-query-result> | ||||||
|             </v-expansion-panels> |                 </v-expansion-panels> | ||||||
|  |             </div> | ||||||
|         </div> |         </div> | ||||||
|     </v-navigation-drawer> |     </v-navigation-drawer> | ||||||
|     <div class="control-bar-above-content"> |     <div class="control-bar-above-content"> | ||||||
|  | @ -82,4 +84,10 @@ | ||||||
|         padding-top: 2%; |         padding-top: 2%; | ||||||
|         font-size: 36px; |         font-size: 36px; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     .nav-scroll-area { | ||||||
|  |         overflow-y: auto; | ||||||
|  |         flex-grow: 1; | ||||||
|  |         min-height: 0; | ||||||
|  |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -14,7 +14,7 @@ export class QuestionController extends BaseController { | ||||||
|     loId: LearningObjectIdentifierDTO; |     loId: LearningObjectIdentifierDTO; | ||||||
| 
 | 
 | ||||||
|     constructor(loId: LearningObjectIdentifierDTO) { |     constructor(loId: LearningObjectIdentifierDTO) { | ||||||
|         super(`learningObject/${loId.hruid}/:${loId.version}/questions`); |         super(`learningObject/${loId.hruid}/${loId.version}/questions`); | ||||||
|         this.loId = loId; |         this.loId = loId; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -13,13 +13,11 @@ | ||||||
|     import authService from "@/services/auth/auth-service.ts"; |     import authService from "@/services/auth/auth-service.ts"; | ||||||
|     import { LearningPathNode } from "@/data-objects/learning-paths/learning-path-node.ts"; |     import { LearningPathNode } from "@/data-objects/learning-paths/learning-path-node.ts"; | ||||||
|     import LearningPathGroupSelector from "@/views/learning-paths/LearningPathGroupSelector.vue"; |     import LearningPathGroupSelector from "@/views/learning-paths/LearningPathGroupSelector.vue"; | ||||||
|     import { useCreateQuestionMutation, useQuestionsQuery } from "@/queries/questions"; |     import { useQuestionsQuery } from "@/queries/questions"; | ||||||
|     import type { QuestionsResponse } from "@/controllers/questions"; |     import type { QuestionsResponse } from "@/controllers/questions"; | ||||||
|     import type { LearningObjectIdentifierDTO } from "@dwengo-1/common/interfaces/learning-content"; |     import type { LearningObjectIdentifierDTO } from "@dwengo-1/common/interfaces/learning-content"; | ||||||
|     import QandA from "@/components/QandA.vue"; |     import QandA from "@/components/QandA.vue"; | ||||||
|     import type { QuestionDTO } from "@dwengo-1/common/interfaces/question"; |     import type { QuestionDTO } from "@dwengo-1/common/interfaces/question"; | ||||||
|     import { useStudentAssignmentsQuery, useStudentGroupsQuery } from "@/queries/students"; |  | ||||||
|     import type { AssignmentDTO } from "@dwengo-1/common/interfaces/assignment"; |  | ||||||
|     import QuestionNotification from "@/components/QuestionNotification.vue"; |     import QuestionNotification from "@/components/QuestionNotification.vue"; | ||||||
|     import QuestionBox from "@/components/QuestionBox.vue"; |     import QuestionBox from "@/components/QuestionBox.vue"; | ||||||
|     import { AccountType } from "@dwengo-1/common/util/account-types"; |     import { AccountType } from "@dwengo-1/common/util/account-types"; | ||||||
|  | @ -147,19 +145,6 @@ | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const studentAssignmentsQueryResult = useStudentAssignmentsQuery( |  | ||||||
|         authService.authState.user?.profile.preferred_username, |  | ||||||
|     ); |  | ||||||
| 
 |  | ||||||
|     const loID: LearningObjectIdentifierDTO = { |  | ||||||
|         hruid: props.learningObjectHruid as string, |  | ||||||
|         language: props.language, |  | ||||||
|     }; |  | ||||||
|     const createQuestionMutation = useCreateQuestionMutation(loID); |  | ||||||
|     const groupsQueryResult = useStudentGroupsQuery(authService.authState.user?.profile.preferred_username); |  | ||||||
| 
 |  | ||||||
|     const questionInput = ref(""); |  | ||||||
| 
 |  | ||||||
|     const discussionLink = computed( |     const discussionLink = computed( | ||||||
|         () => |         () => | ||||||
|             "/discussion" + |             "/discussion" + | ||||||
|  | @ -229,7 +214,7 @@ | ||||||
|                     </template> |                     </template> | ||||||
|                 </v-list-item> |                 </v-list-item> | ||||||
|                 <v-divider></v-divider> |                 <v-divider></v-divider> | ||||||
|                 <div> |                 <div class="nav-scroll-area"> | ||||||
|                     <using-query-result |                     <using-query-result | ||||||
|                         :query-result="learningObjectListQueryResult" |                         :query-result="learningObjectListQueryResult" | ||||||
|                         v-slot="learningObjects: { data: LearningObject[] }" |                         v-slot="learningObjects: { data: LearningObject[] }" | ||||||
|  | @ -417,4 +402,10 @@ | ||||||
|     .discussion-link a:hover { |     .discussion-link a:hover { | ||||||
|         text-decoration: underline; |         text-decoration: underline; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     .nav-scroll-area { | ||||||
|  |         overflow-y: auto; | ||||||
|  |         flex-grow: 1; | ||||||
|  |         min-height: 0; | ||||||
|  |     } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -18,6 +18,15 @@ | ||||||
|         version: number; |         version: number; | ||||||
|         group: { forGroup: number; assignmentNo: number; classId: string }; |         group: { forGroup: number; assignmentNo: number; classId: string }; | ||||||
|     }>(); |     }>(); | ||||||
|  | 
 | ||||||
|  |     function parseContent(content: string): SubmissionData { | ||||||
|  |         if (content === "") { | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return JSON.parse(content); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     const emit = defineEmits<(e: "update:submissionData", value: SubmissionData) => void>(); |     const emit = defineEmits<(e: "update:submissionData", value: SubmissionData) => void>(); | ||||||
| 
 | 
 | ||||||
|     const submissionQuery = useSubmissionsQuery( |     const submissionQuery = useSubmissionsQuery( | ||||||
|  | @ -35,7 +44,7 @@ | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     function emitSubmission(submission: SubmissionDTO): void { |     function emitSubmission(submission: SubmissionDTO): void { | ||||||
|         emitSubmissionData(JSON.parse(submission.content)); |         emitSubmissionData(parseContent(submission.content)); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     watch(submissionQuery.data, () => { |     watch(submissionQuery.data, () => { | ||||||
|  | @ -47,12 +56,13 @@ | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const lastSubmission = computed<SubmissionData>(() => { |     const lastSubmission = computed<SubmissionData | undefined>(() => { | ||||||
|         const submissions = submissionQuery.data.value; |         const submissions = submissionQuery.data.value; | ||||||
|         if (!submissions || submissions.length === 0) { |         if (!submissions || submissions.length === 0) { | ||||||
|             return undefined; |             return undefined; | ||||||
|         } |         } | ||||||
|         return JSON.parse(submissions[submissions.length - 1].content); | 
 | ||||||
|  |         return parseContent(submissions[submissions.length - 1].content); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const showSubmissionTable = computed(() => props.submissionData !== undefined && props.submissionData.length > 0); |     const showSubmissionTable = computed(() => props.submissionData !== undefined && props.submissionData.length > 0); | ||||||
|  |  | ||||||
|  | @ -89,6 +89,15 @@ | ||||||
|                 props.selectedLearningPath.language !== parsedLearningPath.value.language), |                 props.selectedLearningPath.language !== parsedLearningPath.value.language), | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     const selectedLearningPathLink = computed(() => { | ||||||
|  |         if (!props.selectedLearningPath) { | ||||||
|  |             return undefined; | ||||||
|  |         } | ||||||
|  |         const { hruid, language } = props.selectedLearningPath; | ||||||
|  |         const startNode = props.selectedLearningPath.nodes.find((it) => it.start_node); | ||||||
|  |         return `/learningPath/${hruid}/${language}/${startNode.learningobject_hruid}`; | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|     function getErrorMessage(): string | null { |     function getErrorMessage(): string | null { | ||||||
|         if (postError.value) { |         if (postError.value) { | ||||||
|             return t(extractErrorMessage(postError.value)); |             return t(extractErrorMessage(postError.value)); | ||||||
|  | @ -104,7 +113,43 @@ | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
|     <v-card :title="props.selectedLearningPath ? t('editLearningPath') : t('newLearningPath')"> |     <v-card> | ||||||
|  |         <template v-slot:title> | ||||||
|  |             <div class="title-container"> | ||||||
|  |                 <span class="title">{{ | ||||||
|  |                     props.selectedLearningPath ? t("editLearningPath") : t("newLearningPath") | ||||||
|  |                 }}</span> | ||||||
|  |                 <span class="actions"> | ||||||
|  |                     <v-btn | ||||||
|  |                         @click="uploadLearningPath" | ||||||
|  |                         prependIcon="mdi mdi-check" | ||||||
|  |                         :loading="isPostPending || isPutPending" | ||||||
|  |                         :disabled="parsedLearningPath.hruid === DEFAULT_LEARNING_PATH.hruid || isIdModified" | ||||||
|  |                         variant="text" | ||||||
|  |                     > | ||||||
|  |                         {{ props.selectedLearningPath ? t("saveChanges") : t("create") }} | ||||||
|  |                     </v-btn> | ||||||
|  |                     <button-with-confirmation | ||||||
|  |                         @confirm="deleteLearningPath" | ||||||
|  |                         :disabled="!props.selectedLearningPath" | ||||||
|  |                         :text="t('delete')" | ||||||
|  |                         color="red" | ||||||
|  |                         prependIcon="mdi mdi-delete" | ||||||
|  |                         :confirmQueryText="t('learningPathDeleteQuery')" | ||||||
|  |                         variant="text" | ||||||
|  |                     /> | ||||||
|  |                     <v-btn | ||||||
|  |                         :href="selectedLearningPathLink" | ||||||
|  |                         target="_blank" | ||||||
|  |                         prepend-icon="mdi mdi-open-in-new" | ||||||
|  |                         :disabled="!props.selectedLearningPath" | ||||||
|  |                         variant="text" | ||||||
|  |                     > | ||||||
|  |                         {{ t("open") }} | ||||||
|  |                     </v-btn> | ||||||
|  |                 </span> | ||||||
|  |             </div> | ||||||
|  |         </template> | ||||||
|         <template v-slot:text> |         <template v-slot:text> | ||||||
|             <json-editor-vue v-model="learningPath"></json-editor-vue> |             <json-editor-vue v-model="learningPath"></json-editor-vue> | ||||||
|             <v-alert |             <v-alert | ||||||
|  | @ -115,33 +160,21 @@ | ||||||
|                 :text="getErrorMessage()!" |                 :text="getErrorMessage()!" | ||||||
|             ></v-alert> |             ></v-alert> | ||||||
|         </template> |         </template> | ||||||
|         <template v-slot:actions> |  | ||||||
|             <v-btn |  | ||||||
|                 @click="uploadLearningPath" |  | ||||||
|                 prependIcon="mdi mdi-check" |  | ||||||
|                 :loading="isPostPending || isPutPending" |  | ||||||
|                 :disabled="parsedLearningPath.hruid === DEFAULT_LEARNING_PATH.hruid || isIdModified" |  | ||||||
|             > |  | ||||||
|                 {{ props.selectedLearningPath ? t("saveChanges") : t("create") }} |  | ||||||
|             </v-btn> |  | ||||||
|             <button-with-confirmation |  | ||||||
|                 @confirm="deleteLearningPath" |  | ||||||
|                 :disabled="!props.selectedLearningPath" |  | ||||||
|                 :text="t('delete')" |  | ||||||
|                 color="red" |  | ||||||
|                 prependIcon="mdi mdi-delete" |  | ||||||
|                 :confirmQueryText="t('learningPathDeleteQuery')" |  | ||||||
|             /> |  | ||||||
|             <v-btn |  | ||||||
|                 :href="`/learningPath/${props.selectedLearningPath?.hruid}/${props.selectedLearningPath?.language}/start`" |  | ||||||
|                 target="_blank" |  | ||||||
|                 prepend-icon="mdi mdi-open-in-new" |  | ||||||
|                 :disabled="!props.selectedLearningPath" |  | ||||||
|             > |  | ||||||
|                 {{ t("open") }} |  | ||||||
|             </v-btn> |  | ||||||
|         </template> |  | ||||||
|     </v-card> |     </v-card> | ||||||
| </template> | </template> | ||||||
| 
 | 
 | ||||||
| <style scoped></style> | <style scoped> | ||||||
|  |     .title-container { | ||||||
|  |         display: flex; | ||||||
|  |         flex-direction: row; | ||||||
|  |         flex-wrap: wrap; | ||||||
|  |     } | ||||||
|  |     .title { | ||||||
|  |         flex: 1; | ||||||
|  |     } | ||||||
|  |     .actions { | ||||||
|  |         display: flex; | ||||||
|  |         flex-wrap: wrap; | ||||||
|  |         gap: 10px; | ||||||
|  |     } | ||||||
|  | </style> | ||||||
|  |  | ||||||
		Reference in a new issue