From e89f035634ac349c03a065dcf495cc817da44ff6 Mon Sep 17 00:00:00 2001 From: Ramona Beinstingel <75392103+rabeatwork@users.noreply.github.com> Date: Sun, 10 Nov 2024 17:29:10 +0100 Subject: [PATCH] Programming exercises: Add information box to exercise details page (#9668) --- .../exam-participation-cover.component.html | 10 +- .../exam-start-information.component.html | 29 +- .../exam-start-information.component.ts | 57 ++- ...xercise-headers-information.component.html | 39 ++ ...xercise-headers-information.component.scss | 0 .../exercise-headers-information.component.ts | 296 +++++++++++++ .../shared/result/result.component.html | 4 +- .../shared/result/result.component.ts | 1 + .../result/updating-result.component.html | 1 + .../result/updating-result.component.ts | 1 + .../course-conversations.component.scss | 4 +- .../conversation-messages.component.scss | 4 +- .../overview/course-overview.component.html | 393 +++++++++--------- .../overview/course-overview.component.scss | 14 +- .../app/overview/course-overview.component.ts | 7 +- .../webapp/app/overview/course-overview.scss | 10 - .../discussion-section.component.scss | 2 +- .../course-exercise-details.component.html | 151 +++---- .../course-exercise-details.component.scss | 8 - .../course-exercise-details.component.ts | 154 ++++++- .../course-exercise-details.module.ts | 4 + ...ise-details-student-actions.component.html | 2 + .../submission-result-status.component.html | 21 +- .../submission-result-status.component.ts | 1 + .../code-button/code-button.component.html | 2 +- .../code-button/code-button.component.ts | 1 + .../exercise-action-button.component.html | 2 +- .../components/not-released-tag.component.ts | 2 +- .../open-code-editor-button.component.html | 6 +- .../open-code-editor-button.component.ts | 2 + .../difficulty-level.component.html | 5 + .../difficulty-level.component.scss | 4 + .../difficulty-level.component.ts | 48 +++ .../exercise-categories.component.html | 32 +- .../exercise-categories.component.ts | 1 + .../information-box.component.html | 10 +- .../information-box.component.ts | 34 +- .../layouts/navbar/navbar.component.html | 6 +- .../app/shared/layouts/navbar/navbar.scss | 3 +- .../app/shared/metis/metis.component.scss | 2 +- .../sidebar-card-item.component.html | 2 +- .../app/shared/sidebar/sidebar.component.html | 4 +- src/main/webapp/app/utils/date.utils.ts | 4 + src/main/webapp/content/scss/global.scss | 2 +- .../content/scss/themes/_dark-variables.scss | 7 +- .../scss/themes/_default-variables.scss | 7 +- .../content/scss/themes/theme-default.scss | 2 +- .../webapp/i18n/de/programmingExercise.json | 4 +- .../webapp/i18n/de/student-dashboard.json | 48 ++- .../webapp/i18n/en/programmingExercise.json | 4 +- .../webapp/i18n/en/student-dashboard.json | 46 +- .../course/course-dashboard.component.spec.ts | 109 +++++ ...cise-headers-information.component.spec.ts | 296 +++++++++++++ .../course-exercise-details.component.spec.ts | 2 + .../shared/difficulty-level.component.spec.ts | 74 ++++ .../shared/information-box.component.spec.ts | 56 +++ .../course-tutorial-groups.component.spec.ts | 13 + .../javascript/spec/util/date.utils.spec.ts | 26 +- .../exercises/ExerciseResultPage.ts | 2 +- .../ProgrammingExerciseOverviewPage.ts | 4 +- 60 files changed, 1596 insertions(+), 489 deletions(-) create mode 100644 src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.html create mode 100644 src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.scss create mode 100644 src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.ts create mode 100644 src/main/webapp/app/shared/difficulty-level/difficulty-level.component.html create mode 100644 src/main/webapp/app/shared/difficulty-level/difficulty-level.component.scss create mode 100644 src/main/webapp/app/shared/difficulty-level/difficulty-level.component.ts create mode 100644 src/test/javascript/spec/component/course/course-dashboard.component.spec.ts create mode 100644 src/test/javascript/spec/component/exercises/shared/headers/exercise-headers-information.component.spec.ts create mode 100644 src/test/javascript/spec/component/shared/difficulty-level.component.spec.ts create mode 100644 src/test/javascript/spec/component/shared/information-box.component.spec.ts diff --git a/src/main/webapp/app/exam/participate/exam-cover/exam-participation-cover.component.html b/src/main/webapp/app/exam/participate/exam-cover/exam-participation-cover.component.html index 9d56b746c3c9..9e626d906094 100644 --- a/src/main/webapp/app/exam/participate/exam-cover/exam-participation-cover.component.html +++ b/src/main/webapp/app/exam/participate/exam-cover/exam-participation-cover.component.html @@ -14,9 +14,11 @@

-
- -
+
+
+ +
+
}
} -
+
+
@for (informationBoxData of examInformationBoxData; track informationBoxData) { -
- - - @if (informationBoxData.contentComponent === 'formatedDate') { - - {{ informationBoxData.content | artemisDate: 'long-date' }} - - - {{ informationBoxData.content | artemisDate: 'time' }} - - } @else if (informationBoxData.contentComponent === 'workingTime') { - - } - - -
+ + + @if (informationBoxData.content.type === 'dateTime') { + {{ informationBoxData.content.value | artemisDate }} + } + @if (informationBoxData.content.type === 'workingTime') { + + } + + }
- +
diff --git a/src/main/webapp/app/exam/participate/exam-start-information/exam-start-information.component.ts b/src/main/webapp/app/exam/participate/exam-start-information/exam-start-information.component.ts index 33237b989106..3f02da6bab2b 100644 --- a/src/main/webapp/app/exam/participate/exam-start-information/exam-start-information.component.ts +++ b/src/main/webapp/app/exam/participate/exam-start-information/exam-start-information.component.ts @@ -1,7 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; import { ArtemisSharedModule } from 'app/shared/shared.module'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; -import { InformationBox, InformationBoxComponent } from 'app/shared/information-box/information-box.component'; +import { InformationBox, InformationBoxComponent, InformationBoxContent } from 'app/shared/information-box/information-box.component'; import { Exam } from 'app/entities/exam/exam.model'; import { StudentExam } from 'app/entities/student-exam.model'; import { ArtemisExamSharedModule } from 'app/exam/shared/exam-shared.module'; @@ -45,45 +45,78 @@ export class ExamStartInformationComponent implements OnInit { this.prepareInformationBoxData(); } - buildInformationBox(boxTitle: string, boxContent: string | number, boxContentComponent?: string): InformationBox { + buildInformationBox(boxTitle: string, boxContent: InformationBoxContent, isContentComponent = false): InformationBox { const examInformationBoxData: InformationBox = { title: boxTitle ?? '', - content: boxContent ?? '', - contentComponent: boxContentComponent, + content: boxContent, + isContentComponent: isContentComponent, }; return examInformationBoxData; } prepareInformationBoxData(): void { if (this.moduleNumber) { - const informationBoxModuleNumber = this.buildInformationBox('artemisApp.exam.moduleNumber', this.moduleNumber!); + const boxContentModuleNumber: InformationBoxContent = { + type: 'string', + value: this.moduleNumber, + }; + const informationBoxModuleNumber = this.buildInformationBox('artemisApp.exam.moduleNumber', boxContentModuleNumber); this.examInformationBoxData.push(informationBoxModuleNumber); } if (this.courseName) { - const informationBoxCourseName = this.buildInformationBox('artemisApp.exam.course', this.courseName!); + const boxContentCourseName: InformationBoxContent = { + type: 'string', + value: this.courseName, + }; + const informationBoxCourseName = this.buildInformationBox('artemisApp.exam.course', boxContentCourseName); this.examInformationBoxData.push(informationBoxCourseName); } if (this.examiner) { - const informationBoxExaminer = this.buildInformationBox('artemisApp.examManagement.examiner', this.examiner!); + const boxContentExaminer: InformationBoxContent = { + type: 'string', + value: this.examiner, + }; + const informationBoxExaminer = this.buildInformationBox('artemisApp.examManagement.examiner', boxContentExaminer); this.examInformationBoxData.push(informationBoxExaminer); } if (this.examinedStudent) { - const informationBoxExaminedStudent = this.buildInformationBox('artemisApp.exam.examinedStudent', this.examinedStudent!); + const boxContentExaminedStudent: InformationBoxContent = { + type: 'string', + value: this.examinedStudent, + }; + const informationBoxExaminedStudent = this.buildInformationBox('artemisApp.exam.examinedStudent', boxContentExaminedStudent); this.examInformationBoxData.push(informationBoxExaminedStudent); } if (this.startDate) { - const informationBoxStartDate = this.buildInformationBox('artemisApp.exam.date', this.startDate.toString(), 'formatedDate'); + const boxContentStartDate: InformationBoxContent = { + type: 'dateTime', + value: this.startDate, + }; + const informationBoxStartDate = this.buildInformationBox('artemisApp.exam.date', boxContentStartDate, true); this.examInformationBoxData.push(informationBoxStartDate); } - const informationBoxTotalWorkingTime = this.buildInformationBox('artemisApp.exam.workingTime', this.exam.workingTime!, 'workingTime'); + const boxContentExamWorkingTime: InformationBoxContent = { + type: 'workingTime', + value: this.studentExam, + }; + + const informationBoxTotalWorkingTime = this.buildInformationBox('artemisApp.exam.workingTime', boxContentExamWorkingTime, true); this.examInformationBoxData.push(informationBoxTotalWorkingTime); + const boxContentTotalPoints: InformationBoxContent = { + type: 'string', + value: this.totalPoints?.toString() ?? '', + }; - const informationBoxTotalPoints = this.buildInformationBox('artemisApp.exam.points', this.totalPoints!.toString()); + const informationBoxTotalPoints = this.buildInformationBox('artemisApp.exam.points', boxContentTotalPoints); this.examInformationBoxData.push(informationBoxTotalPoints); if (this.numberOfExercisesInExam) { - const informationBoxNumberOfExercises = this.buildInformationBox('artemisApp.exam.exercises', this.numberOfExercisesInExam!.toString()); + const boxContent: InformationBoxContent = { + type: 'string', + value: this.numberOfExercisesInExam?.toString(), + }; + const informationBoxNumberOfExercises = this.buildInformationBox('artemisApp.exam.exercises', boxContent); this.examInformationBoxData.push(informationBoxNumberOfExercises); } } diff --git a/src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.html b/src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.html new file mode 100644 index 000000000000..30e26e951231 --- /dev/null +++ b/src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.html @@ -0,0 +1,39 @@ +@if (exercise) { +
+ @for (informationBoxItem of informationBoxItems; track informationBoxItem) { + + @switch (informationBoxItem.content.type) { + @case ('difficultyLevel') { + + } + @case ('categories') { + + } + @case ('timeAgo') { + {{ informationBoxItem.content.value | artemisTimeAgo }} + } + @case ('dateTime') { + {{ informationBoxItem.content.value | artemisDate }} + } + @case ('submissionStatus') { + + } + } + + } +
+} diff --git a/src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.scss b/src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.scss new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.ts b/src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.ts new file mode 100644 index 000000000000..547009a136bd --- /dev/null +++ b/src/main/webapp/app/exercises/shared/exercise-headers/exercise-headers-information/exercise-headers-information.component.ts @@ -0,0 +1,296 @@ +import { Component, Input, OnChanges, OnInit } from '@angular/core'; +import { SortService } from 'app/shared/service/sort.service'; +import dayjs from 'dayjs/esm'; +import { Exercise, IncludedInOverallScore, getCourseFromExercise } from 'app/entities/exercise.model'; +import { SubmissionPolicy } from 'app/entities/submission-policy.model'; +import { StudentParticipation } from 'app/entities/participation/student-participation.model'; +import { getExerciseDueDate } from 'app/exercises/shared/exercise/exercise.utils'; +import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model'; +import { Course } from 'app/entities/course.model'; +import { SubmissionType } from 'app/entities/submission.model'; +import { ProgrammingSubmission } from 'app/entities/programming/programming-submission.model'; +import { roundValueSpecifiedByCourseSettings } from 'app/shared/util/utils'; +import { SubmissionResultStatusModule } from 'app/overview/submission-result-status.module'; +import { ExerciseCategoriesModule } from 'app/shared/exercise-categories/exercise-categories.module'; +import { InformationBox, InformationBoxComponent } from 'app/shared/information-box/information-box.component'; +import { ComplaintService } from 'app/complaints/complaint.service'; +import { isDateLessThanAWeekInTheFuture } from 'app/utils/date.utils'; +import { DifficultyLevelComponent } from 'app/shared/difficulty-level/difficulty-level.component'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; + +@Component({ + selector: 'jhi-exercise-headers-information', + templateUrl: './exercise-headers-information.component.html', + standalone: true, + imports: [SubmissionResultStatusModule, ExerciseCategoriesModule, InformationBoxComponent, DifficultyLevelComponent, ArtemisSharedCommonModule], + styleUrls: ['./exercise-headers-information.component.scss'], + /* Our tsconfig file has `preserveWhitespaces: 'true'` which causes whitespace to affect content projection. + We need to set it to 'false 'for this component, otherwise the components with the selector [contentComponent] + will not be projected into their specific slot of the "InformationBoxComponent" component.*/ + preserveWhitespaces: false, +}) +export class ExerciseHeadersInformationComponent implements OnInit, OnChanges { + readonly IncludedInOverallScore = IncludedInOverallScore; + readonly dayjs = dayjs; + + @Input() exercise: Exercise; + @Input() studentParticipation?: StudentParticipation; + @Input() course?: Course; + @Input() submissionPolicy?: SubmissionPolicy; + + dueDate?: dayjs.Dayjs; + programmingExercise?: ProgrammingExercise; + individualComplaintDueDate?: dayjs.Dayjs; + achievedPoints: number = 0; + numberOfSubmissions: number; + informationBoxItems: InformationBox[] = []; + + constructor(private sortService: SortService) {} + + ngOnInit() { + this.dueDate = getExerciseDueDate(this.exercise, this.studentParticipation); + + if (this.course?.maxComplaintTimeDays) { + this.individualComplaintDueDate = ComplaintService.getIndividualComplaintDueDate( + this.exercise, + this.course.maxComplaintTimeDays, + this.studentParticipation?.results?.last(), + this.studentParticipation, + ); + } + this.createInformationBoxItems(); + } + + ngOnChanges() { + this.course = this.course ?? getCourseFromExercise(this.exercise); + + if (this.submissionPolicy?.active && this.submissionPolicy?.submissionLimit) { + this.updateSubmissionPolicyItem(); + } + if (this.studentParticipation?.results?.length) { + // The updated participation by the websocket is not guaranteed to be sorted, find the newest result (highest id) + this.sortService.sortByProperty(this.studentParticipation.results, 'id', false); + + const latestRatedResult = this.studentParticipation.results.filter((result) => result.rated).first(); + if (latestRatedResult) { + this.achievedPoints = roundValueSpecifiedByCourseSettings((latestRatedResult.score! * this.exercise.maxPoints!) / 100, this.course) ?? 0; + } + } + } + + createInformationBoxItems() { + this.addPointsItems(); + this.addDueDateItems(); + this.addStartDateItem(); + this.addSubmissionStatusItem(); + this.addSubmissionPolicyItem(); + this.addDifficultyItem(); + this.addCategoryItems(); + } + + addPointsItems() { + const { maxPoints, bonusPoints } = this.exercise; + if (maxPoints) { + if (bonusPoints) { + let achievedBonusPoints: number = 0; + // If the student has more points than the max points, the bonus points are calculated + if (this.achievedPoints > maxPoints) { + achievedBonusPoints = roundValueSpecifiedByCourseSettings(this.achievedPoints - maxPoints, this.course); + } + const achievedPoints = this.achievedPoints - achievedBonusPoints; + this.informationBoxItems.push(this.getPointsItem('points', maxPoints, achievedPoints)); + this.informationBoxItems.push(this.getPointsItem('bonus', bonusPoints, achievedBonusPoints)); + } else { + this.informationBoxItems.push(this.getPointsItem('points', maxPoints, this.achievedPoints)); + } + } + } + + addDueDateItems() { + const now = dayjs(); + + const dueDateItem = this.getDueDateItem(); + if (dueDateItem) { + this.informationBoxItems.push(dueDateItem); + } + // If the due date is in the past and the assessment due date is in the future, show the assessment due date + if (this.dueDate?.isBefore(now) && this.exercise.assessmentDueDate?.isAfter(now)) { + const assessmentDueItem: InformationBox = { + title: 'artemisApp.courseOverview.exerciseDetails.assessmentDue', + content: { + type: 'dateTime', + value: this.exercise.assessmentDueDate, + }, + isContentComponent: true, + tooltip: 'artemisApp.courseOverview.exerciseDetails.assessmentDueTooltip', + }; + this.informationBoxItems.push(assessmentDueItem); + } + // // If the assessment due date is in the past and the complaint due date is in the future, show the complaint due date + if (this.exercise.assessmentDueDate?.isBefore(now) && this.individualComplaintDueDate?.isAfter(now)) { + const complaintDueItem: InformationBox = { + title: 'artemisApp.courseOverview.exerciseDetails.complaintDue', + content: { + type: 'dateTime', + value: this.individualComplaintDueDate, + }, + isContentComponent: true, + tooltip: 'artemisApp.courseOverview.exerciseDetails.complaintDueTooltip', + }; + this.informationBoxItems.push(complaintDueItem); + } + } + + getDueDateItem(): InformationBox | undefined { + if (this.dueDate) { + const isDueDateInThePast = this.dueDate.isBefore(dayjs()); + // If the due date is less than a day away, the color change to red + const dueDateStatusBadge = this.dueDate.isBetween(dayjs().add(1, 'day'), dayjs()) ? 'danger' : 'body-color'; + // If the due date is less than a week away, text is displayed relatively e.g. 'in 2 days' + const shouldDisplayDueDateRelative = isDateLessThanAWeekInTheFuture(this.dueDate); + + if (isDueDateInThePast) { + return { + title: 'artemisApp.courseOverview.exerciseDetails.submissionDueOver', + content: { + type: 'dateTime', + value: this.dueDate, + }, + isContentComponent: true, + }; + } + + return { + title: 'artemisApp.courseOverview.exerciseDetails.submissionDue', + content: { + type: shouldDisplayDueDateRelative ? 'timeAgo' : 'dateTime', + value: this.dueDate, + }, + isContentComponent: true, + tooltip: shouldDisplayDueDateRelative ? 'artemisApp.courseOverview.exerciseDetails.submissionDueTooltip' : undefined, + contentColor: dueDateStatusBadge, + tooltipParams: { date: this.dueDate?.format('lll') }, + }; + } + } + + addStartDateItem() { + if (this.exercise.startDate && dayjs().isBefore(this.exercise.startDate)) { + // If the start date is less than a week away, text is displayed relatively e.g. 'in 2 days' + const shouldDisplayStartDateRelative = isDateLessThanAWeekInTheFuture(this.exercise.startDate); + const startDateItem: InformationBox = { + title: 'artemisApp.courseOverview.exerciseDetails.startDate', + content: { + type: shouldDisplayStartDateRelative ? 'timeAgo' : 'dateTime', + value: this.exercise.startDate, + }, + isContentComponent: true, + tooltip: shouldDisplayStartDateRelative ? 'artemisApp.exerciseActions.startExerciseBeforeStartDate' : undefined, + }; + this.informationBoxItems.push(startDateItem); + } + } + + addDifficultyItem() { + if (this.exercise.difficulty) { + const difficultyItem: InformationBox = { + title: 'artemisApp.courseOverview.exerciseDetails.difficulty', + content: { + type: 'difficultyLevel', + value: this.exercise.difficulty, + }, + isContentComponent: true, + }; + this.informationBoxItems.push(difficultyItem); + } + } + + addSubmissionStatusItem() { + const submissionStatusItem: InformationBox = { + title: 'artemisApp.courseOverview.exerciseDetails.status', + content: { + type: 'submissionStatus', + value: this.exercise, + }, + isContentComponent: true, + }; + this.informationBoxItems.push(submissionStatusItem); + } + + addCategoryItems() { + const notReleased = this.exercise.releaseDate && dayjs(this.exercise.releaseDate).isAfter(dayjs()); + + if (notReleased || this.exercise.includedInOverallScore !== IncludedInOverallScore.INCLUDED_COMPLETELY || this.exercise.categories?.length) { + const categoryItem: InformationBox = { + title: 'artemisApp.courseOverview.exerciseDetails.categories', + content: { + type: 'categories', + value: this.exercise, + }, + isContentComponent: true, + }; + this.informationBoxItems.push(categoryItem); + } + } + + addSubmissionPolicyItem() { + if (this.submissionPolicy?.active && this.submissionPolicy?.submissionLimit) { + this.informationBoxItems.push(this.getSubmissionPolicyItem()); + } + } + + getSubmissionPolicyItem(): InformationBox { + return { + title: 'artemisApp.programmingExercise.submissionPolicy.submissionLimitTitle', + content: { + type: 'string', + value: `${this.numberOfSubmissions} / ${this.submissionPolicy?.submissionLimit}`, + }, + contentColor: this.submissionPolicy?.submissionLimit ? this.getSubmissionColor() : 'body-color', + tooltip: 'artemisApp.programmingExercise.submissionPolicy.submissionPolicyType.' + this.submissionPolicy?.type + '.tooltip', + tooltipParams: { points: this.submissionPolicy?.exceedingPenalty?.toString() }, + }; + } + + getSubmissionColor(): string { + // default color should be 'body-color', thats why the default submissionsLeft is 2 + const submissionsLeft = this.submissionPolicy?.submissionLimit ? this.submissionPolicy?.submissionLimit - this.numberOfSubmissions : 2; + let submissionColor = 'body-color'; + if (submissionsLeft === 1) submissionColor = 'warning'; + // 0 submissions left or limit is already reached + else if (submissionsLeft <= 0) submissionColor = 'danger'; + return submissionColor; + } + + getPointsItem(title: string, maxPoints: number, achievedPoints: number): InformationBox { + return { + title: 'artemisApp.courseOverview.exerciseDetails.' + title, + content: { + type: 'string', + value: `${achievedPoints} / ${maxPoints}`, + }, + }; + } + + updateSubmissionPolicyItem() { + this.countSubmissions(); + + // need to push and pop the submission policy item to update the number of submissions + const submissionItemIndex = this.informationBoxItems.findIndex((item) => item.title === 'artemisApp.programmingExercise.submissionPolicy.submissionLimitTitle'); + if (submissionItemIndex !== -1) { + this.informationBoxItems.splice(submissionItemIndex, 1, this.getSubmissionPolicyItem()); + } + } + + countSubmissions() { + const commitHashSet = new Set(); + + this.studentParticipation?.results + ?.map((result) => result.submission) + .filter((submission) => submission?.type === SubmissionType.MANUAL) + .map((submission) => (submission as ProgrammingSubmission).commitHash) + .forEach((commitHash: string) => commitHashSet.add(commitHash)); + + this.numberOfSubmissions = commitHashSet.size; + } +} diff --git a/src/main/webapp/app/exercises/shared/result/result.component.html b/src/main/webapp/app/exercises/shared/result/result.component.html index 65c4be9ca9de..01207f3c5052 100644 --- a/src/main/webapp/app/exercises/shared/result/result.component.html +++ b/src/main/webapp/app/exercises/shared/result/result.component.html @@ -60,7 +60,7 @@ } - @if (!isInSidebarCard) { + @if (!isInSidebarCard && showCompletion) { ({{ result!.completionDate | artemisTimeAgo }}) } @@ -97,7 +97,7 @@ } @case (ResultTemplateStatus.MISSING) { -   + @switch (missingResultInfo) { @case (MissingResultInfo.FAILED_PROGRAMMING_SUBMISSION_ONLINE_IDE) { diff --git a/src/main/webapp/app/exercises/shared/result/updating-result.component.ts b/src/main/webapp/app/exercises/shared/result/updating-result.component.ts index 640b02f38bff..ac2f1cc9ae4d 100644 --- a/src/main/webapp/app/exercises/shared/result/updating-result.component.ts +++ b/src/main/webapp/app/exercises/shared/result/updating-result.component.ts @@ -34,6 +34,7 @@ export class UpdatingResultComponent implements OnChanges, OnDestroy { @Input() showBadge = false; @Input() showIcon = true; @Input() isInSidebarCard = false; + @Input() showCompletion = true; @Output() showResult = new EventEmitter(); /** * @property personalParticipation Whether the participation belongs to the user (by being a student) or not (by being an instructor) diff --git a/src/main/webapp/app/overview/course-conversations/course-conversations.component.scss b/src/main/webapp/app/overview/course-conversations/course-conversations.component.scss index 4feac4ecf258..e2919dcc3388 100644 --- a/src/main/webapp/app/overview/course-conversations/course-conversations.component.scss +++ b/src/main/webapp/app/overview/course-conversations/course-conversations.component.scss @@ -20,8 +20,8 @@ } .scrollable-content { - --message-input-height-dev: 193px; - --message-input-height-prod: 177px; + --message-input-height-dev: 164px; + --message-input-height-prod: 148px; overflow-y: auto; overflow-x: hidden; max-height: calc(100vh - var(--header-height) - var(--message-input-height-prod)); diff --git a/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.scss b/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.scss index d6900d231537..83aa144d2d9e 100644 --- a/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.scss +++ b/src/main/webapp/app/overview/course-conversations/layout/conversation-messages/conversation-messages.component.scss @@ -3,8 +3,8 @@ @import 'bootstrap/scss/mixins'; .conversation-messages { - --message-input-height-prod: 171px; - --message-input-height-dev: 187px; + --message-input-height-prod: 142px; + --message-input-height-dev: 158px; --search-height: 52px; --channel-header-height: 52px; diff --git a/src/main/webapp/app/overview/course-overview.component.html b/src/main/webapp/app/overview/course-overview.component.html index 2cd6c2c5c4ee..5fac3f273643 100644 --- a/src/main/webapp/app/overview/course-overview.component.html +++ b/src/main/webapp/app/overview/course-overview.component.html @@ -1,223 +1,230 @@ -@if (!isShownViaLti) { -