Skip to content

Exam mode: Merge student exams page into students page#12558

Open
matyasht wants to merge 70 commits intodevelopfrom
feature/exam-mode/students-page-merge
Open

Exam mode: Merge student exams page into students page#12558
matyasht wants to merge 70 commits intodevelopfrom
feature/exam-mode/students-page-merge

Conversation

@matyasht
Copy link
Copy Markdown
Contributor

@matyasht matyasht commented Apr 19, 2026

Summary

This PR reworks the students page in exam mode and merges the student exams page into it.

Checklist

General

Client

  • Important: I implemented the changes with a very good performance, prevented too many (unnecessary) REST calls and made sure the UI is responsive, even with large data (e.g. using paging).
  • I strictly followed the principle of data economy for all client-server REST calls.
  • I strictly followed the client coding guidelines.
  • I strictly followed the AET UI-UX guidelines.
  • Following the theming guidelines, I specified colors only in the theming variable files and checked that the changes look consistent in both the light and the dark theme.
  • I added multiple screenshots/screencasts of my UI changes.
  • I translated all newly inserted strings into English and German.

Changes affecting Programming Exercises

Motivation and Context

With the student page in exam mode you can add, remove and generally manage students in a exam.
The student exams page is for managing individual exams of students, such as creating them and extending working time for an individual exam.
As each student can only have at most one individual exam, therefore, it does not make sense to have two separate pages for this.

Description

  • Merged Student Exams into the Exam Students page.
  • Migrated UI state logic to Angular signals in exam-students.
  • Switched components to PrimeNG, including the students data table.
  • Added a new add-students dialog with search.
  • Improved progress/status UX (popover bars, tooltips, responsive fixes).
  • Updated related server/routes, translations, and tests (component + Playwright) for the new unified workflow.

Steps for Testing

Exam mode only

Exam Mode Testing

Prerequisites:

  • 1 Instructor
  • 1 Course with 1 Exam
  1. Log in to Artemis
  2. Navigate to the Exam
  3. Go to the Students page
  4. Test the student page with all its features. Try the drop down buttons, the actions, do a responsiveness test etc.

Tip

You can use the scripts below to quickly setup dummy students with 4 different exam states on a server you have admin rights on (TS3 for instance).
To create the students and exams: cleanup-exam-state-scenarios.js
node supporting_scripts/course-scripts/quick-course-setup/setup-exam-state-scenarios.js --server-url https://artemis-test3.artemis.cit.tum.de/api --course-id <your-course-id>
Run this after you are done for cleanup: setup-exam-state-scenarios.js
node supporting_scripts/course-scripts/quick-course-setup/cleanup-exam-state-scenarios.js --server-url https://artemis-test3.artemis.cit.tum.de/api --course-id <your-course-id>

Testserver States

You can manage test servers using Helios. Check environment statuses in the environment list. To deploy to a test server, go to the CI/CD page, find your PR or branch, and trigger the deployment.

Review Progress

Performance Review

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Exam Mode Test

  • Test 1
  • Test 2

Test Coverage

Client

Class/File Line Coverage Lines Expects Ratio
exam-exercise-start-preparation-status.model.ts not found (added) 8 ? ?
exam-management.service.ts 85.98% 334 48 14.4
student-exam-status.component.ts 0.00% 13 ? ?
student-exams.component.ts not found (deleted) ? ? ?
exam-add-students-dialog.component.ts not found (added) 130 ? ?
exam-students-menu-button.component.ts not found (added) 24 ? ?
exam-students.component.ts not found (modified) 704 23 3.3
students-upload-images-button.component.ts not found (deleted) ? ? ?
global.utils.ts 100.00% 57 28 49.1

Server

Class/File Line Coverage Lines
ExamDeletionService.java 95.50% 196
ExamRegistrationService.java 93.13% 254
StudentExamService.java 90.94% 553

Last updated: 2026-04-23 16:50:11 UTC

Screenshots

(design has changed a bit since the video but functionality is the same)
https://youtu.be/a2GT5TerQ9A
image

Summary by CodeRabbit

  • New Features

    • Added an “Add students” dialog and a menu-driven students management button with register actions.
  • UI Improvements

    • Consolidated Student Exams into the main Students page; removed separate Student Exams links/views.
    • Reworked students table with new table component, compact status tags, progress/ETA display, reseat action, and updated row actions.
  • Behavior

    • Exercise-preparation and individual-exam status update immediately after student/exam changes.
  • Localization

    • Added and updated translations and tooltips for student/exam management flows.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (7)
src/main/webapp/app/exam/manage/students/add-students-dialog/exam-add-students-dialog.component.html (2)

21-29: Optional: simplify the search input binding.

With Angular 21 signal-aware forms, [(ngModel)]="searchText" works directly with a writable signal, removing the need for the split [ngModel]/(ngModelChange) pair.

Suggested change
             <input
                 pInputText
                 class="w-100"
                 type="text"
                 [placeholder]="'artemisApp.examManagement.examStudents.addDialog.searchPlaceholder' | artemisTranslate"
-                [ngModel]="searchText()"
-                (ngModelChange)="searchText.set($event)"
+                [(ngModel)]="searchText"
             />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/webapp/app/exam/manage/students/add-students-dialog/exam-add-students-dialog.component.html`
around lines 21 - 29, The search input uses a split binding
([ngModel]="searchText()" and (ngModelChange)="searchText.set($event)") but can
be simplified to a two-way signal-aware binding; replace those two bindings on
the pInputText input with a single [(ngModel)]="searchText" (target the input
element with pInputText and the writable signal named searchText) so the
writable signal handles updates automatically. Ensure the component exposes
searchText as a writable signal compatible with Angular 21 signal-aware forms
before making the change.

85-104: Consider debouncing / truncating / alt-styled register button.

Two minor observations on the register action button:

  • class="p-button-sm ..." uses the legacy size class, whereas the rest of the file uses size="small" on pButton. Prefer the attribute for consistency with PrimeNG 20 conventions.
  • The button content can overflow when all three states (registering/registered/register) are combined with longer translations. A min-width or white-space: nowrap on the cell keeps it from reflowing.

No functional bug; feel free to skip.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/webapp/app/exam/manage/students/add-students-dialog/exam-add-students-dialog.component.html`
around lines 85 - 104, Replace the legacy size class on the register button with
the PrimeNG attribute and prevent content wrapping: remove "p-button-sm" and add
size="small" to the pButton element used in the button that calls
registerStudent(student) and checks
isRegistering(student)/isAlreadyRegistered(student); also ensure the button or
its containing cell (the td using pFrozenColumn) has a non-wrapping layout and a
minimum width (e.g. apply white-space: nowrap and a min-width via a class or
inline style) so the icon+label combos for the three states don’t overflow or
reflow.
src/main/webapp/app/exam/manage/students/exam-students.component.ts (3)

755-771: Inconsistent fallback for missing working time between exam types.

For regular exams this helper returns studentExam.workingTime (possibly undefined → rows sort to the end/start via nullable compare), while for test exams it returns 0 when any field is missing. Rows with 0 will interleave with the lowest real values rather than cluster at one edge. Consider returning undefined in both missing-data cases so compareNullableNumbers handles them consistently.

Suggested change
         if (!studentExam.started || !studentExam.submitted || !studentExam.workingTime || !studentExam.startedDate || !studentExam.submissionDate) {
-            return 0;
+            return undefined;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.ts` around
lines 755 - 771, The getWorkingTimeSortValue function currently returns
studentExam.workingTime (possibly undefined) for normal exams but returns 0 for
test exams when data is missing; change getWorkingTimeSortValue so that for test
exams you also return undefined when required fields are missing (i.e., when
!studentExam.started || !studentExam.submitted || !studentExam.workingTime ||
!studentExam.startedDate || !studentExam.submissionDate) instead of 0, and keep
the existing usedWorkingTime/min logic otherwise—this makes nullability
consistent so compareNullableNumbers will handle missing values uniformly.

280-289: routeData effect can overwrite user state on later navigation data emissions.

toSignal(this.route.data) will re-emit whenever route.data changes (e.g. route reuse on parameter change). The effect unconditionally calls setUpExamInformation(exam), which resets studentExams, isLoading, and re-fetches. If the route doesn’t actually change exams this is harmless, but if navigation re-emits the same exam it re-triggers a full reload on every change-detection cycle where route.data fires. Consider gating by exam.id !== this.exam().id to avoid redundant loads.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.ts` around
lines 280 - 289, The effect listening to routeData() unconditionally calls
setUpExamInformation(exam) on every route.data emission, which can reset
studentExams/isLoading and refetch repeatedly; change the effect in
exam-students.component.ts to first compare the incoming exam id with the
current stored exam (use exam?.id !== this.exam()?.id) and only call
setUpExamInformation(exam) when they differ (or when there is no current exam),
so redundant re-loads are avoided; refer to routeData, effect(...),
setUpExamInformation, and this.exam() when implementing the guard.

483-496: Dead-code opportunity: openIndividualExamsStatusPopover(event) branch ignores a possibly-undefined event.

In the else branch you pass event (the original click event), while the confirmed branch passes undefined, true (defer). This works, but because event here is already Event | undefined, the non-defer path can also receive undefined — in which case popover.show(new MouseEvent('click'), target) is used with an unrelated synthetic event. Not a bug, but consider always passing { defer: true } for consistency so the popover anchors reliably after the sort/paginator re-render.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.ts` around
lines 483 - 496, handleGenerateStudentExams currently calls
openIndividualExamsStatusPopover(event) in the else branch which can pass an
undefined Event and cause a synthetic MouseEvent to be used; change the else
branch to call openIndividualExamsStatusPopover(undefined, true) (i.e., always
pass defer:true) so the popover is consistently anchored after sort/paginator
re-rendering—update the call in handleGenerateStudentExams to use
openIndividualExamsStatusPopover(undefined, true) and keep
generateStudentExams() following it.
src/main/webapp/app/exam/manage/students/exam-students.component.html (2)

216-217: Non-null assertion on asExamUserWithExamData(...).

The helper returns ExamUserWithExamData | undefined, so @let examUser = asExamUserWithExamData(student)!; hides a potential undefined at the type level. In practice student always comes from the table value, but you can drop the cast entirely and type the row directly, which is safer:

Suggested change
-            <ng-template `#body` let-student>
-                `@let` examUser = asExamUserWithExamData(student)!;
+            <ng-template `#body` let-examUser>

and remove the asExamUserWithExamData helper in the TS file (since [value]="allRegisteredUsers()" is already typed as ExamUserWithExamData[], let-examUser in the body template will be correctly typed with Angular's signal-strict-template checking).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.html` around
lines 216 - 217, Remove the non-null assertion and helper usage in the template:
stop using "@let examUser = asExamUserWithExamData(student)!;" and instead rely
on the row typing provided by the table's [value]="allRegisteredUsers()" so the
template uses "let-examUser" (or equivalent let binding) directly; then delete
the now-unused asExamUserWithExamData helper from the component TS
(exam-students.component) so types flow from allRegisteredUsers():
ExamUserWithExamData[] into the template without forcing a non-null assertion.

51-56: Prefer explicit boolean binding for showValue.

showValue="false" passes the string "false". PrimeNG’s booleanAttribute transform coerces it correctly today, but the intent is clearer and future-proof as a property binding:

Suggested change
-                                                    <p-progress-bar
-                                                        [value]="exercisePreparationPercentage()"
-                                                        color="var(--bs-success)"
-                                                        showValue="false"
-                                                        [style]="{ height: '100%' }"
-                                                    />
+                                                    <p-progress-bar
+                                                        [value]="exercisePreparationPercentage()"
+                                                        color="var(--bs-success)"
+                                                        [showValue]="false"
+                                                        [style]="{ height: '100%' }"
+                                                    />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.html` around
lines 51 - 56, The template uses showValue="false" which binds a string; change
it to an explicit property binding [showValue]="false" on the p-progress-bar to
pass a boolean value (keep the rest of the attributes including
[value]="exercisePreparationPercentage()" and [style] as-is).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/webapp/app/exam/manage/students/exam-students.component.html`:
- Around line 3-6: The template currently renders "({{ exam().title }})" even
when exam().title is undefined; update the exam-students component template to
only render the parentheses and title when exam().title is truthy (e.g. using an
*ngIf on the span or a conditional expression) so the header reads "Students"
until exam().title is populated; locate the span that contains "({{ exam().title
}})" in exam-students.component.html and wrap or replace it with a conditional
render that shows the parentheses and title only when exam().title has a value.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.ts`:
- Around line 338-346: The openUploadImagesDialog function assigns signal
references to the modal's inputs, which bypasses Angular's signal initialization
in StudentsUploadImagesDialogComponent; change the assignments on
modalRef.componentInstance (courseId and exam) to pass the current signal values
instead of the signals themselves by invoking the signals (e.g., use
this.courseId() and this.exam()) so the modal receives concrete values.

---

Nitpick comments:
In
`@src/main/webapp/app/exam/manage/students/add-students-dialog/exam-add-students-dialog.component.html`:
- Around line 21-29: The search input uses a split binding
([ngModel]="searchText()" and (ngModelChange)="searchText.set($event)") but can
be simplified to a two-way signal-aware binding; replace those two bindings on
the pInputText input with a single [(ngModel)]="searchText" (target the input
element with pInputText and the writable signal named searchText) so the
writable signal handles updates automatically. Ensure the component exposes
searchText as a writable signal compatible with Angular 21 signal-aware forms
before making the change.
- Around line 85-104: Replace the legacy size class on the register button with
the PrimeNG attribute and prevent content wrapping: remove "p-button-sm" and add
size="small" to the pButton element used in the button that calls
registerStudent(student) and checks
isRegistering(student)/isAlreadyRegistered(student); also ensure the button or
its containing cell (the td using pFrozenColumn) has a non-wrapping layout and a
minimum width (e.g. apply white-space: nowrap and a min-width via a class or
inline style) so the icon+label combos for the three states don’t overflow or
reflow.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.html`:
- Around line 216-217: Remove the non-null assertion and helper usage in the
template: stop using "@let examUser = asExamUserWithExamData(student)!;" and
instead rely on the row typing provided by the table's
[value]="allRegisteredUsers()" so the template uses "let-examUser" (or
equivalent let binding) directly; then delete the now-unused
asExamUserWithExamData helper from the component TS (exam-students.component) so
types flow from allRegisteredUsers(): ExamUserWithExamData[] into the template
without forcing a non-null assertion.
- Around line 51-56: The template uses showValue="false" which binds a string;
change it to an explicit property binding [showValue]="false" on the
p-progress-bar to pass a boolean value (keep the rest of the attributes
including [value]="exercisePreparationPercentage()" and [style] as-is).

In `@src/main/webapp/app/exam/manage/students/exam-students.component.ts`:
- Around line 755-771: The getWorkingTimeSortValue function currently returns
studentExam.workingTime (possibly undefined) for normal exams but returns 0 for
test exams when data is missing; change getWorkingTimeSortValue so that for test
exams you also return undefined when required fields are missing (i.e., when
!studentExam.started || !studentExam.submitted || !studentExam.workingTime ||
!studentExam.startedDate || !studentExam.submissionDate) instead of 0, and keep
the existing usedWorkingTime/min logic otherwise—this makes nullability
consistent so compareNullableNumbers will handle missing values uniformly.
- Around line 280-289: The effect listening to routeData() unconditionally calls
setUpExamInformation(exam) on every route.data emission, which can reset
studentExams/isLoading and refetch repeatedly; change the effect in
exam-students.component.ts to first compare the incoming exam id with the
current stored exam (use exam?.id !== this.exam()?.id) and only call
setUpExamInformation(exam) when they differ (or when there is no current exam),
so redundant re-loads are avoided; refer to routeData, effect(...),
setUpExamInformation, and this.exam() when implementing the guard.
- Around line 483-496: handleGenerateStudentExams currently calls
openIndividualExamsStatusPopover(event) in the else branch which can pass an
undefined Event and cause a synthetic MouseEvent to be used; change the else
branch to call openIndividualExamsStatusPopover(undefined, true) (i.e., always
pass defer:true) so the popover is consistently anchored after sort/paginator
re-rendering—update the call in handleGenerateStudentExams to use
openIndividualExamsStatusPopover(undefined, true) and keep
generateStudentExams() following it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f6ec6724-f72b-485e-9689-5181c097d864

📥 Commits

Reviewing files that changed from the base of the PR and between 8d362c9 and 0df3852.

📒 Files selected for processing (6)
  • src/main/webapp/app/exam/manage/student-exams/student-exam-status/student-exam-status.component.html
  • src/main/webapp/app/exam/manage/students/add-students-dialog/exam-add-students-dialog.component.html
  • src/main/webapp/app/exam/manage/students/exam-students-menu-button/exam-students-menu-button.component.html
  • src/main/webapp/app/exam/manage/students/exam-students.component.html
  • src/main/webapp/app/exam/manage/students/exam-students.component.ts
  • src/test/playwright/support/pageobjects/exam/StudentExamManagementPage.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/test/playwright/support/pageobjects/exam/StudentExamManagementPage.ts

Comment thread src/main/webapp/app/exam/manage/students/exam-students.component.ts
@github-actions
Copy link
Copy Markdown

@matyasht Test coverage has been automatically updated in the PR description.

Copy link
Copy Markdown
Contributor

@Claudia-Anthropica Claudia-Anthropica left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matyasht Nice consolidation of the two exam pages into one. The signal migration, PrimeNG table, and the new add-students dialog all look clean. Just a few small things inline.

Comment thread src/main/webapp/app/exam/manage/students/exam-students.component.ts Outdated
Comment thread src/main/webapp/app/exam/manage/students/exam-students.component.ts
Comment thread src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts (1)

168-188: ⚠️ Potential issue | 🟠 Major

Mock the reload request in removal tests.

removeFromExam() and removeAllStudents() update the table through reloadExamWithRegisteredUsers(), which calls examManagementService.find(...). These tests only mock the remove call, so the asserted component state is never produced unless the reload request is mocked too.

Example fix pattern
 it('should remove users from the exam', () => {
     const examServiceStub = vi.spyOn(examManagementService, 'removeStudentFromExam').mockReturnValue(of(new HttpResponse<void>()));
+    const examWithOneUser = {
+        ...examWithCourse,
+        examUsers: [{ didCheckImage: false, didCheckLogin: false, didCheckName: false, didCheckRegistrationNumber: false, ...user1, user: user1 } as ExamUser],
+    } as Exam;
+    vi.spyOn(examManagementService, 'find').mockReturnValue(of(new HttpResponse({ body: examWithOneUser })));
     fixture.detectChanges();

Apply the same pattern in the two removeAllStudents tests with an exam body whose examUsers array is empty.

Also applies to: 209-243

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts`
around lines 168 - 188, The tests for removeFromExam and removeAllStudents need
to also mock the reload request that reloadExamWithRegisteredUsers triggers
(examManagementService.find) so the component state reflects the server-side
change; update the spec for removeFromExam (and the two removeAllStudents tests)
to spyOn/mock examManagementService.find to return an
Observable<HttpResponse<Exam>> with an exam body having the updated examUsers
array (e.g., one user removed or empty), then run
fixture.detectChanges()/changeDetectorRef.detectChanges() and assert against
component.allRegisteredUsers() as before; locate these changes around the
removeFromExam, removeAllStudents, and reloadExamWithRegisteredUsers usages in
exam-students.component.spec.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/webapp/app/exam/manage/students/exam-students.component.html`:
- Around line 51-56: In exam-students.component.html the p-progress-bar
currently sets showValue as a string which passes a truthy value; change the
attribute to a property binding [showValue]="false" on the <p-progress-bar>
element so the boolean false is passed correctly (locate the p-progress-bar
element rendering exercisePreparationPercentage()).

In `@src/main/webapp/app/exam/manage/students/exam-students.component.ts`:
- Around line 590-615: The current setExercisePreparationStatus can mark
isAllExercisesPrepared true when remainingExams === 0 even if some exams failed;
update setExercisePreparationStatus so isAllExercisesPrepared is set to true
only when there are no remaining exams AND no failures (e.g., remainingExams ===
0 && (newStatus?.failed ?? 0) === 0); locate the logic in
setExercisePreparationStatus and change the branch that sets
this.isAllExercisesPrepared.set(remainingExams === 0) to use the combined
condition, keeping existing updates to exercisePreparationStatus,
exercisePreparationRunning, exercisePreparationPercentage, and
exercisePreparationEta intact.
- Around line 380-416: The loading flag is only cleared on successful responses,
so add error handlers to the relevant subscriptions to always call
this.isLoading.set(false) and surface a user-visible error (or at least log it).
Concretely, update the subscribe calls on examManagementService.find,
examManagementService.getExerciseStartStatus, and
studentExamService.findAllForExam so their error callbacks set
this.isLoading.set(false) and show an error via the app's alert/notification
mechanism (e.g., this.alertService.error(...)) or fallback to console.error; you
can also factor the behavior into a small helper method like
handleLoadError(error) that logs/shows the message and sets isLoading to false,
and call that from each subscription error handler.

---

Outside diff comments:
In `@src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts`:
- Around line 168-188: The tests for removeFromExam and removeAllStudents need
to also mock the reload request that reloadExamWithRegisteredUsers triggers
(examManagementService.find) so the component state reflects the server-side
change; update the spec for removeFromExam (and the two removeAllStudents tests)
to spyOn/mock examManagementService.find to return an
Observable<HttpResponse<Exam>> with an exam body having the updated examUsers
array (e.g., one user removed or empty), then run
fixture.detectChanges()/changeDetectorRef.detectChanges() and assert against
component.allRegisteredUsers() as before; locate these changes around the
removeFromExam, removeAllStudents, and reloadExamWithRegisteredUsers usages in
exam-students.component.spec.ts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c2b016fe-7014-45e3-abff-bcd2582019d9

📥 Commits

Reviewing files that changed from the base of the PR and between 0df3852 and ca2ca76.

📒 Files selected for processing (3)
  • src/main/webapp/app/exam/manage/students/exam-students.component.html
  • src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts
  • src/main/webapp/app/exam/manage/students/exam-students.component.ts

Comment thread src/main/webapp/app/exam/manage/students/exam-students.component.ts
Comment thread src/main/webapp/app/exam/manage/students/exam-students.component.ts
@github-actions
Copy link
Copy Markdown

@matyasht Test coverage has been automatically updated in the PR description.

Copy link
Copy Markdown
Contributor

@Claudia-Anthropica Claudia-Anthropica left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matyasht Nice work merging these two pages — the PrimeNG migration and signal-based architecture look solid overall. The main blocker is that 3 client tests are failing in CI because the removal methods now reload from the server but the tests don't mock find(). The server test failure (IrisTutorSuggestion) is unrelated.

Comment thread src/main/webapp/app/exam/manage/students/exam-students.component.ts Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 23, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts (2)

171-181: Use an ExamUser fixture factory instead of repeated object spreads.

This avoids duplicating the check flags and keeps the test fixtures consistently shaped.

Example refactor
const createExamUser = (user: User): ExamUser =>
    Object.assign(new ExamUser(), {
        didCheckImage: false,
        didCheckLogin: false,
        didCheckName: false,
        didCheckRegistrationNumber: false,
        user,
    });

Then replace the repeated inline objects with createExamUser(user1) / createExamUser(user2).

As per coding guidelines, **/*.{ts,tsx}: “Avoid spread operator for objects in TypeScript”.

Also applies to: 217-225

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts`
around lines 171 - 181, The test fixtures create ExamUser objects by repeating
object spreads for flags and user data which duplicates shape and violates the
guideline against object spreads; define a factory like createExamUser(user:
User): ExamUser that returns a new ExamUser instance with didCheckImage/
didCheckLogin/ didCheckName/ didCheckRegistrationNumber set to false and user
assigned, then replace the inline spread objects in
exam-students.component.spec.ts (both places where examUsers are constructed)
with createExamUser(user1)/createExamUser(user2) to keep fixtures consistent and
avoid repeated spreads.

74-83: Return a fresh StudentExam fixture from the mock.

This keeps tests isolated if the component or future assertions mutate the returned response body.

Refactor fixture creation
-    const studentExams: StudentExam[] = [
+    const createStudentExams = (): StudentExam[] => [
         Object.assign(new StudentExam(), {
             id: 123,
             user: user1,
             workingTime: 3600,
             started: true,
             submitted: false,
             examSessions: [{}, {}],
         }),
     ];
...
                 MockProvider(StudentExamService, {
-                    findAllForExam: () => of(new HttpResponse({ body: studentExams })),
+                    findAllForExam: () => of(new HttpResponse({ body: createStudentExams() })),
                 }),

Also applies to: 106-108

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts`
around lines 74 - 83, The test currently reuses a shared StudentExam object in
the studentExams array, which can be mutated across assertions; update the mocks
in exam-students.component.spec.ts (the studentExams fixtures used around the
studentExams variable and the similar fixture at lines ~106-108) to return fresh
StudentExam instances for each mock response—create new StudentExam() objects
with Object.assign (or a small factory function) each time the mock is called
(e.g., when stubbing the service method used by ExamStudentsComponent) so every
test gets an independent fixture.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts`:
- Around line 171-181: The test fixtures create ExamUser objects by repeating
object spreads for flags and user data which duplicates shape and violates the
guideline against object spreads; define a factory like createExamUser(user:
User): ExamUser that returns a new ExamUser instance with didCheckImage/
didCheckLogin/ didCheckName/ didCheckRegistrationNumber set to false and user
assigned, then replace the inline spread objects in
exam-students.component.spec.ts (both places where examUsers are constructed)
with createExamUser(user1)/createExamUser(user2) to keep fixtures consistent and
avoid repeated spreads.
- Around line 74-83: The test currently reuses a shared StudentExam object in
the studentExams array, which can be mutated across assertions; update the mocks
in exam-students.component.spec.ts (the studentExams fixtures used around the
studentExams variable and the similar fixture at lines ~106-108) to return fresh
StudentExam instances for each mock response—create new StudentExam() objects
with Object.assign (or a small factory function) each time the mock is called
(e.g., when stubbing the service method used by ExamStudentsComponent) so every
test gets an independent fixture.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 59c6eb28-dc18-4628-aebe-4f7e3e3682d2

📥 Commits

Reviewing files that changed from the base of the PR and between ca2ca76 and 9dfeb16.

📒 Files selected for processing (5)
  • src/main/webapp/app/exam/manage/students/exam-students.component.html
  • src/main/webapp/app/exam/manage/students/exam-students.component.spec.ts
  • src/main/webapp/app/exam/manage/students/exam-students.component.ts
  • src/main/webapp/i18n/de/exam.json
  • src/main/webapp/i18n/en/exam.json
✅ Files skipped from review due to trivial changes (1)
  • src/main/webapp/app/exam/manage/students/exam-students.component.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/webapp/i18n/de/exam.json

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 23, 2026
@github-actions
Copy link
Copy Markdown

@matyasht Test coverage has been automatically updated in the PR description.

Copy link
Copy Markdown
Contributor

@Claudia-Anthropica Claudia-Anthropica left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matyasht Great consolidation of the two exam pages — the PrimeNG migration, signal architecture, and the new add-students dialog all look clean. Just one race condition to fix with the loading state, see inline.

Comment thread src/main/webapp/app/exam/manage/students/exam-students.component.ts Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 23, 2026
Copy link
Copy Markdown
Contributor

@Claudia-Anthropica Claudia-Anthropica left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matyasht Nice consolidation of the two exam pages — the signal architecture, PrimeNG table, add-students dialog, and the server-side cache invalidation all look clean. One remaining issue with error handling on the student exam fetch, see inline.

Comment thread src/main/webapp/app/exam/manage/students/exam-students.component.ts
@github-actions
Copy link
Copy Markdown

@matyasht Test coverage has been automatically updated in the PR description.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 23, 2026
@github-actions
Copy link
Copy Markdown

@matyasht Test coverage has been automatically updated in the PR description.

@matyasht matyasht force-pushed the feature/exam-mode/students-page-merge branch from 3627103 to ac8736a Compare April 23, 2026 16:33
@github-actions
Copy link
Copy Markdown

@matyasht Test coverage has been automatically updated in the PR description.

Copy link
Copy Markdown
Contributor

@Claudia-Anthropica Claudia-Anthropica left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matyasht Great job addressing all the earlier feedback — the signal architecture, PrimeNG table, and server-side cache invalidation all look solid. Two things remaining: the new add-students dialog needs a test file, and there's a missing error handler on the exercise start status fetch. See inline.

return !!student.login && this.currentlyRegisteringLogins().has(student.login);
}

registerStudent(student: User): void {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matyasht [medium] This component has 130+ lines of non-trivial logic (three-set registration tracking, optimistic UI, HTTP calls, search filtering) but no test file at all. Your own coverage table shows "not found (added)". You should add at least a basic spec covering registerStudent success/error, loadCourseStudents, and the isAlreadyRegistered/isRegistering guards.

🤖 Prompt for AI agents

Create a new test file src/main/webapp/app/exam/manage/students/add-students-dialog/exam-add-students-dialog.component.spec.ts for ExamAddStudentsDialogComponent. Mock CourseManagementService.getAllUsersInCourseGroup and ExamManagementService.addStudentToExam. Write tests for: (1) openDialog loads students, (2) registerStudent calls the service and updates localRegisteredLogins, (3) registerStudent error shows alert, (4) isAlreadyRegistered returns true for registered logins, (5) search filtering works correctly.

}

const courseId = this.courseId();
this.examManagementService.getExerciseStartStatus(courseId, exam.id).subscribe((res) => this.setExercisePreparationStatus(res.body ?? undefined));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@matyasht [low] This getExerciseStartStatus subscription has no error handler. If it fails (e.g. network blip), the exercise preparation status silently stays empty. Not a crash, but the user gets no feedback. A simple error: () => {} or onError(this.alertService, error) would be consistent with how you handle the other subscriptions in this method.

🤖 Prompt for AI agents

In src/main/webapp/app/exam/manage/students/exam-students.component.ts line 429, the getExerciseStartStatus subscription has no error handler. Add an error callback like error: (error: HttpErrorResponse) => onError(this.alertService, error) to match the pattern used by the findAllForExam subscription a few lines below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client Pull requests that update TypeScript code. (Added Automatically!) exam Pull requests that affect the corresponding module playwright ready for review server Pull requests that update Java code. (Added Automatically!) tests

Projects

Status: Ready For Review

Development

Successfully merging this pull request may close these issues.

2 participants