From 0c693d20cb207ef0d9b0e5ef7b174a309991b8bd Mon Sep 17 00:00:00 2001 From: Lucas Welscher Date: Wed, 28 Aug 2024 20:53:08 +0200 Subject: [PATCH 1/3] `Exam mode`: Show exercise group title in breadcrumbs --- .../app/course/manage/course-management.service.ts | 2 +- .../programming-exercise-participation.service.ts | 4 ++-- .../app/exercises/shared/exercise/exercise.service.ts | 8 ++------ .../app/shared/layouts/navbar/entity-title.service.ts | 11 +++++++++++ 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/webapp/app/course/manage/course-management.service.ts b/src/main/webapp/app/course/manage/course-management.service.ts index 6b1a0a567412..0f442cada5ea 100644 --- a/src/main/webapp/app/course/manage/course-management.service.ts +++ b/src/main/webapp/app/course/manage/course-management.service.ts @@ -676,7 +676,7 @@ export class CourseManagementService { this.entityTitleService.setTitle(EntityType.COURSE, [course?.id], course?.title); course?.exercises?.forEach((exercise) => { - this.entityTitleService.setTitle(EntityType.EXERCISE, [exercise.id], exercise.title); + this.entityTitleService.setExerciseTitle(exercise); }); course?.lectures?.forEach((lecture) => this.entityTitleService.setTitle(EntityType.LECTURE, [lecture.id], lecture.title)); course?.exams?.forEach((exam) => this.entityTitleService.setTitle(EntityType.EXAM, [exam.id], exam.title)); diff --git a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts index 7af098d1a5a7..1f26936b1507 100644 --- a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts +++ b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts @@ -3,11 +3,11 @@ import { Injectable } from '@angular/core'; import { AccountService } from 'app/core/auth/account.service'; import { Participation } from 'app/entities/participation/participation.model'; import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model'; +import { CommitInfo } from 'app/entities/programming-submission.model'; import { Result } from 'app/entities/result.model'; import { EntityTitleService, EntityType } from 'app/shared/layouts/navbar/entity-title.service'; import { createRequestOption } from 'app/shared/util/request.util'; import { Observable, map, tap } from 'rxjs'; -import { CommitInfo } from 'app/entities/programming-submission.model'; export interface IProgrammingExerciseParticipationService { getLatestResultWithFeedback: (participationId: number, withSubmission: boolean) => Observable; @@ -81,7 +81,7 @@ export class ProgrammingExerciseParticipationService implements IProgrammingExer sendTitlesToEntityTitleService(participation: Participation | undefined) { if (participation?.exercise) { const exercise = participation.exercise; - this.entityTitleService.setTitle(EntityType.EXERCISE, [exercise.id], exercise.title); + this.entityTitleService.setExerciseTitle(exercise); if (exercise.course) { const course = exercise.course; diff --git a/src/main/webapp/app/exercises/shared/exercise/exercise.service.ts b/src/main/webapp/app/exercises/shared/exercise/exercise.service.ts index dffb26604cc7..447578f78d76 100644 --- a/src/main/webapp/app/exercises/shared/exercise/exercise.service.ts +++ b/src/main/webapp/app/exercises/shared/exercise/exercise.service.ts @@ -485,12 +485,8 @@ export class ExerciseService { } public sendExerciseTitleToTitleService(exercise?: Exercise) { - // we only want to show the exercise group name as exercise name to the student for exam exercises. - // for tutors and more privileged users, we want to show the exercise title - if (exercise?.exerciseGroup && !exercise?.isAtLeastTutor) { - this.entityTitleService.setTitle(EntityType.EXERCISE, [exercise?.id], exercise?.exerciseGroup.title); - } else { - this.entityTitleService.setTitle(EntityType.EXERCISE, [exercise?.id], exercise?.title); + if (exercise) { + this.entityTitleService.setExerciseTitle(exercise); } if (exercise?.course) { this.entityTitleService.setTitle(EntityType.COURSE, [exercise.course.id], exercise.course.title); diff --git a/src/main/webapp/app/shared/layouts/navbar/entity-title.service.ts b/src/main/webapp/app/shared/layouts/navbar/entity-title.service.ts index d342f8eb1464..a50aab27ddc4 100644 --- a/src/main/webapp/app/shared/layouts/navbar/entity-title.service.ts +++ b/src/main/webapp/app/shared/layouts/navbar/entity-title.service.ts @@ -1,6 +1,7 @@ import { HttpClient, HttpResponse } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { captureException } from '@sentry/angular'; +import { Exercise } from 'app/entities/exercise.model'; import { EMPTY, Observable, ReplaySubject, Subject } from 'rxjs'; export enum EntityType { @@ -88,6 +89,16 @@ export class EntityTitleService { } } + public setExerciseTitle(exercise: Exercise) { + // we only want to show the exercise group name as exercise name to the students for exam exercises. + // for tutors and more privileged users, we want to show the exercise title + if (exercise.exerciseGroup && !exercise?.isAtLeastTutor) { + this.setTitle(EntityType.EXERCISE, [exercise.id], exercise.exerciseGroup.title); + } else { + this.setTitle(EntityType.EXERCISE, [exercise.id], exercise.title); + } + } + /** * Fetches the title of the given entity from the server. * From e65456504e29260861c8767b283da6779e01fac0 Mon Sep 17 00:00:00 2001 From: Lucas Welscher Date: Wed, 28 Aug 2024 21:11:12 +0200 Subject: [PATCH 2/3] add client tests --- .../spec/service/entity-title.service.spec.ts | 31 +++++++++++++++++++ .../spec/service/exercise.service.spec.ts | 25 ++------------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/test/javascript/spec/service/entity-title.service.spec.ts b/src/test/javascript/spec/service/entity-title.service.spec.ts index 1e8165930ba7..a0d25c344ae6 100644 --- a/src/test/javascript/spec/service/entity-title.service.spec.ts +++ b/src/test/javascript/spec/service/entity-title.service.spec.ts @@ -1,3 +1,4 @@ +import { Exercise } from 'app/entities/exercise.model'; import { EntityTitleService, EntityType } from 'app/shared/layouts/navbar/entity-title.service'; import { TestBed, fakeAsync, tick } from '@angular/core/testing'; import { MockHttpService } from '../helpers/mocks/service/mock-http.service'; @@ -115,4 +116,34 @@ describe('EntityTitleService', () => { service.setTitle(type, ids, title); expect(captureSpy).toHaveBeenCalledOnce(); }); + + it('sets the exercise group title for students during an exam', () => { + const exercise = { id: 1, exerciseGroup: { title: 'Group Title' }, isAtLeastTutor: false } as Exercise; + service.setExerciseTitle(exercise); + + let result: string | undefined = undefined; + service.getTitle(EntityType.EXERCISE, [1]).subscribe((title) => (result = title)); + + expect(result).toBe('Group Title'); + }); + + it('sets the exercise title for tutors and more privileged users', () => { + const exercise = { id: 1, exerciseGroup: { title: 'Group Title' }, isAtLeastTutor: true, title: 'Exercise Title' } as Exercise; + service.setExerciseTitle(exercise); + + let result: string | undefined = undefined; + service.getTitle(EntityType.EXERCISE, [1]).subscribe((title) => (result = title)); + + expect(result).toBe('Exercise Title'); + }); + + it('sets the exercise title for course exercises', () => { + const exercise = { id: 1, isAtLeastTutor: false, title: 'Exercise Title' } as Exercise; + service.setExerciseTitle(exercise); + + let result: string | undefined = undefined; + service.getTitle(EntityType.EXERCISE, [1]).subscribe((title) => (result = title)); + + expect(result).toBe('Exercise Title'); + }); }); diff --git a/src/test/javascript/spec/service/exercise.service.spec.ts b/src/test/javascript/spec/service/exercise.service.spec.ts index 8e58498bef7a..67a213552873 100644 --- a/src/test/javascript/spec/service/exercise.service.spec.ts +++ b/src/test/javascript/spec/service/exercise.service.spec.ts @@ -22,7 +22,7 @@ import { SafeHtml } from '@angular/platform-browser'; import { ExerciseCategory } from 'app/entities/exercise-category.model'; import { Observable } from 'rxjs'; import { AccountService } from 'app/core/auth/account.service'; -import { EntityTitleService, EntityType } from 'app/shared/layouts/navbar/entity-title.service'; +import { EntityTitleService } from 'app/shared/layouts/navbar/entity-title.service'; import { ProfileService } from 'app/shared/layouts/profiles/profile.service'; describe('Exercise Service', () => { @@ -358,7 +358,7 @@ describe('Exercise Service', () => { const profileService = TestBed.inject(ProfileService); const accountServiceSpy = jest.spyOn(accountService, 'setAccessRightsForExerciseAndReferencedCourse'); - const entityTitleServiceSpy = jest.spyOn(entityTitleService, 'setTitle'); + const entityTitleServiceSpy = jest.spyOn(entityTitleService, 'setExerciseTitle'); const profileServiceSpy = jest.spyOn(profileService, 'getProfileInfo'); const category = { @@ -387,7 +387,7 @@ describe('Exercise Service', () => { expect(accountServiceSpy).toHaveBeenCalledWith(expect.objectContaining({ id: exerciseFromServer.id })); expect(entityTitleServiceSpy).toHaveBeenCalledOnce(); - expect(entityTitleServiceSpy).toHaveBeenCalledWith(EntityType.EXERCISE, [exerciseFromServer.id], exerciseFromServer.title); + expect(entityTitleServiceSpy).toHaveBeenCalledWith(exerciseFromServer); expect(profileServiceSpy).not.toHaveBeenCalled(); }); @@ -520,23 +520,4 @@ describe('Exercise Service', () => { method: 'PUT', }); }); - - it('should correctly send the exercise name to the title service', () => { - const entityTitleService = TestBed.inject(EntityTitleService); - const examExerciseForStudent = { id: 1, title: 'exercise', exerciseGroup: { id: 1, title: 'exercise group' } } as Exercise; - const examExerciseForTutor = { ...examExerciseForStudent, isAtLeastTutor: true } as Exercise; - const courseExerciseForStudent = { ...examExerciseForStudent, exerciseGroup: undefined, course: { id: 2, title: 'course' } } as Exercise; - const courseExerciseForTutor = { ...courseExerciseForStudent, isAtLeastTutor: true } as Exercise; - const entityTitleServiceSpy = jest.spyOn(entityTitleService, 'setTitle'); - service.sendExerciseTitleToTitleService(examExerciseForStudent); - expect(entityTitleServiceSpy).toHaveBeenCalledWith(EntityType.EXERCISE, [1], 'exercise group'); - service.sendExerciseTitleToTitleService(examExerciseForTutor); - expect(entityTitleServiceSpy).toHaveBeenCalledWith(EntityType.EXERCISE, [1], 'exercise'); - service.sendExerciseTitleToTitleService(courseExerciseForStudent); - expect(entityTitleServiceSpy).toHaveBeenCalledWith(EntityType.EXERCISE, [1], 'exercise'); - expect(entityTitleServiceSpy).toHaveBeenCalledWith(EntityType.COURSE, [2], 'course'); - service.sendExerciseTitleToTitleService(courseExerciseForTutor); - expect(entityTitleServiceSpy).toHaveBeenCalledWith(EntityType.EXERCISE, [1], 'exercise'); - expect(entityTitleServiceSpy).toHaveBeenCalledWith(EntityType.COURSE, [2], 'course'); - }); }); From c6c95eac1d22ed88b66f5f2e8932742ced42f781 Mon Sep 17 00:00:00 2001 From: Lucas Welscher Date: Wed, 4 Sep 2024 13:49:09 +0200 Subject: [PATCH 3/3] fix build error --- .../services/programming-exercise-participation.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts index 1f26936b1507..e5ece788a0fb 100644 --- a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts +++ b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts @@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'; import { AccountService } from 'app/core/auth/account.service'; import { Participation } from 'app/entities/participation/participation.model'; import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model'; -import { CommitInfo } from 'app/entities/programming-submission.model'; +import { CommitInfo } from 'app/entities/programming/programming-submission.model'; import { Result } from 'app/entities/result.model'; import { EntityTitleService, EntityType } from 'app/shared/layouts/navbar/entity-title.service'; import { createRequestOption } from 'app/shared/util/request.util';