Skip to content

Conversation

SamuelRoettgermann
Copy link
Contributor

@SamuelRoettgermann SamuelRoettgermann commented Oct 14, 2025

Checklist

General

Server

  • Important: I implemented the changes with a very good performance and prevented too many (unnecessary) and too complex database calls.
  • I strictly followed the principle of data economy for all database calls.
  • I strictly followed the server coding and design guidelines.
  • I added multiple integration tests (Spring) related to the features (with a high test coverage).
  • I added pre-authorization annotations according to the guidelines and checked the course groups for all new REST Calls (security).
  • I documented the Java code using JavaDoc style.

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 and design 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 integration tests (Jest) related to the features (with a high test coverage), while following the test guidelines.
  • I added authorities to all new routes and checked the course groups for displaying navigation elements (links, buttons).
  • I documented the TypeScript code using JSDoc style.
  • I added multiple screenshots/screencasts of my UI changes.
  • I translated all newly inserted strings into English and German.

Changes affecting Programming Exercises

  • High priority: I tested all changes and their related features with all corresponding user types on a test server configured with the integrated lifecycle setup (LocalVC and LocalCI).
  • I tested all changes and their related features with all corresponding user types on a test server configured with LocalVC and Jenkins.

Motivation and Context

Description

Steps for Testing

Prerequisites:

  • 1 Instructor
  • 2 Students
  • 1 Programming Exercise with Complaints enabled
  1. Log in to Artemis
  2. Navigate to Course Administration
  3. ...

Exam Mode Testing

Prerequisites:

  • 1 Instructor
  • 2 Students
  • 1 Exam with a Programming Exercise
  1. Log in to Artemis
  2. Participate in the exam as a student
  3. Make sure that the UI of the programming exercise in the exam mode stays unchanged. You can use the exam mode documentation as reference.
  4. ...

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

  • I (as a reviewer) confirm that the client changes (in particular related to REST calls and UI responsiveness) are implemented with a very good performance even for very large courses with more than 2000 students.
  • I (as a reviewer) confirm that the server changes (in particular related to database calls) are implemented with a very good performance even for very large courses with more than 2000 students.

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Exam Mode Test

  • Test 1
  • Test 2

Performance Tests

  • Test 1
  • Test 2

Test Coverage

Screenshots

Summary by CodeRabbit

  • New Features

    • Instructors can distribute registered students across selected exam rooms via a new button and dialog.
    • Admin exam room overview now shows room ID and default capacity, with sorting by ID, default capacity, and more.
    • Typeahead search and selection of rooms for distribution.
  • Improvements

    • More accurate capacity calculation based on usable seats and default layouts.
    • Uses the newest versions of rooms for overviews and distribution.
  • Localization

    • Added translations for new columns, actions, and detailed error messages (e.g., insufficient seats, invalid/missing room selections).

… needs to be an AbstractAuditingEntity, still need to figure out what to do with capacity and if I even need it but might store capacity with the layouts directly
…l need to return a DTO, still need helper functions
@github-project-automation github-project-automation bot moved this to Work In Progress in Artemis Development Oct 14, 2025
@github-actions github-actions bot added tests server Pull requests that update Java code. (Added Automatically!) client Pull requests that update TypeScript code. (Added Automatically!) core Pull requests that affect the corresponding module exam Pull requests that affect the corresponding module labels Oct 14, 2025
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

Adds exam room distribution capability. Introduces new DTOs, repositories, services, and a REST controller on the backend; updates existing ExamRoom DTO and service. On the client, adds UI components, service, and i18n to select rooms and trigger distribution; extends admin room overview with id and default capacity.

Changes

Cohort / File(s) Summary of changes
DTOs (Java)
src/main/java/.../exam/dto/room/ExamRoomDTO.java, src/main/java/.../exam/dto/room/ExamRoomForDistributionDTO.java
Added id to ExamRoomDTO (record signature change). Introduced ExamRoomForDistributionDTO with validation and JSON inclusion rules.
Repositories (Java)
src/main/java/.../exam/repository/ExamRoomExamAssignmentRepository.java, src/main/java/.../exam/repository/ExamRoomRepository.java, src/test/java/.../exam/test_repository/ExamRoomTestRepository.java
New repository for exam-room exam assignments (delete by examId). ExamRoomRepository: added queries for current room ids, eager fetch by ids, and distribution DTOs; adjusted outdated room query. Test repo: added query for newest room version ids; minor distinct/annotation formatting.
Services (Java)
src/main/java/.../exam/service/ExamRoomDistributionService.java, src/main/java/.../exam/service/ExamRoomService.java
New distribution service to validate capacity, reset assignments, and distribute users. ExamRoomService: constructor adds ExamUserRepository; new methods to get default layout strategy and compute default usable seats; admin overview picks newest room versions; capacity derived from usable seats.
REST Resources (Java)
src/main/java/.../exam/web/ExamRoomDistributionResource.java, src/main/java/.../exam/web/ExamResource.java
New controller with POST distribute-registered-students and GET distribution-data endpoints with access checks and validations. Minor Javadoc formatting change in ExamResource.
Admin Room Overview (Angular)
src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.html, .../exam-rooms.component.ts, .../exam-rooms.component.spec.ts, .../exam-rooms.model.ts
UI shows id and defaultCapacity columns; sorting keys extended to include id/defaultCapacity; data calculation refactored; models updated (ExamRoomDTO gains id, Extended gains defaultCapacity; layoutStrategy capacity required). Tests adapted.
Students Room Distribution Feature (Angular)
src/main/webapp/app/exam/manage/students/exam-students.component.html, .../exam-students.component.ts, .../room-distribution/students-room-distribution-button.component.ts, .../room-distribution/students-room-distribution-dialog.component.html, .../room-distribution/students-room-distribution-dialog.component.ts, .../room-distribution/students-room-distribution.model.ts, .../room-distribution/students-room-distribution.service.ts
Added distribution button to exam-students (non-test exams). New button component opens modal. New dialog template/logic to select rooms and trigger distribution. New DTO model and service for fetching room data and posting distribution requests.
i18n
src/main/webapp/i18n/en/error.json, src/main/webapp/i18n/de/error.json, src/main/webapp/i18n/en/exam.json, src/main/webapp/i18n/en/examRooms.json, src/main/webapp/i18n/de/examRooms.json
Added error messages (room layout issues, examUser errors, seat/ID validations). Added labels for distribution UI and admin overview fields (id, defaultCapacity, rooms fields).
Tests (Java)
src/test/java/.../exam/ExamRoomIntegrationTest.java, src/test/java/.../exam/service/ExamRoomServiceTest.java, src/test/java/.../exam/util/ExamRoomZipFiles.java
Adjusted to new ExamRoomDTO signature (including id). Updated expected counts for realistic ZIP scenario. Updated Javadoc figures.
Tooling
supporting_scripts/.../check-client-module-coverage.mjs
Slightly reduced branches threshold for programming module.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Instructor
  participant UI as Angular UI (Students page)
  participant Modal as Room Distribution Dialog
  participant FE as StudentsRoomDistributionService
  participant API as ExamRoomDistributionResource
  participant SVC as ExamRoomDistributionService
  participant RepoE as ExamRepository
  participant RepoR as ExamRoomRepository
  participant RepoA as ExamRoomExamAssignmentRepository
  participant RepoU as ExamUserRepository
  participant RoomS as ExamRoomService

  Instructor->>UI: Click "Distribute"
  UI->>Modal: Open dialog
  Modal->>FE: GET /api/exam/rooms/distribution-data
  FE->>API: GET distribution-data
  API->>SVC: getRoomDataForDistribution()
  SVC->>RepoR: findAllCurrentExamRoomsForDistribution()
  RepoR-->>SVC: Set<ExamRoomForDistributionDTO>
  SVC-->>API: Set<...>
  API-->>FE: 200 OK + data
  FE-->>Modal: Rooms list
  Instructor->>Modal: Select room(s), Finish
  Modal->>FE: POST distribute-registered-students(courseId, examId, roomIds)
  FE->>API: POST distribute-registered-students
  API->>SVC: distributeRegisteredStudents(examId, roomIds)
  SVC->>RepoE: findByIdWithRegisteredUsers(examId) (eager users)
  SVC->>RepoR: findAllWithEagerLayoutStrategiesByIdIn(ids)
  SVC->>RoomS: getDefaultUsableSeats(room) for each room
  RoomS-->>SVC: Usable seats per room
  SVC->>RepoA: deleteAllByExamId(examId)
  SVC->>RepoA: save assignments for selected rooms
  SVC->>RepoU: persist planned room/seat for users
  SVC-->>API: void
  API-->>FE: 200 OK
  FE-->>Modal: Success
  Modal-->>UI: Emit distributionDone
  UI->>UI: Reload exam users
Loading
sequenceDiagram
  autonumber
  actor Instructor
  participant UI as Angular UI (Typeahead)
  participant FE as StudentsRoomDistributionService
  participant API as ExamRoomDistributionResource
  participant SVC as ExamRoomDistributionService
  participant RepoR as ExamRoomRepository

  Instructor->>UI: Focus and type in room search
  UI->>FE: GET distribution-data
  FE->>API: GET /api/exam/rooms/distribution-data
  API->>SVC: getRoomDataForDistribution()
  SVC->>RepoR: findAllCurrentExamRoomsForDistribution()
  RepoR-->>SVC: Set<ExamRoomForDistributionDTO>
  SVC-->>API: Set<...>
  API-->>FE: 200 OK
  FE-->>UI: HTTP response with rooms
  UI->>UI: Filter and show suggestions
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title uses slashes and a “Feature” prefix, making it noisy, and mentions only the client side even though the pull request introduces significant server-side logic, services, and endpoints; it does not clearly and concisely summarize the primary change. Rename the pull request to a clear, concise sentence such as “Implement exam seating distribution on backend and client” to remove slashes, drop unnecessary prefixes, and accurately reflect both server and client changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.25% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/exam-mode/seating-distribution-client

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 PMD (7.17.0)
src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomDTO.java

[ERROR] Error at ruleset.xml:58:5
56|
57|
58|
^^^^^ Unable to find referenced rule BooleanInstantiation; perhaps the rule name is misspelled?

59|
60|
[WARN] Warning at ruleset.xml:66:5
64|
65|
66|
^^^^^ Use Rule name category/java/bestpractices.xml/DefaultLabelNotLastInSwitch instead of the deprecated Rule name category/java/bestpractices.xml/DefaultLabelNotLastInSwitchStmt. PMD 8.0.0 will remove su

... [truncated 11412 characters] ...

prone.xml/InaccurateNumericLiteral instead of the deprecated Rule name category/ecmascript/errorprone.xml/InnaccurateNumericLiteral. PMD 8.0.0 will remove support for this deprecated Rule name usage.

185|
186|
[ERROR] Cannot load ruleset category/vm/bestpractices.xml: Cannot resolve rule/ruleset reference 'category/vm/bestpractices.xml'. Make sure the resource is a valid file or URL and is on the CLASSPATH. Use --debug (or a fine log level) to see the current classpath.
[WARN] Progressbar rendering conflicts with reporting to STDOUT. No progressbar will be shown. Try running with argument -r to output the report to a file instead.

src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomForDistributionDTO.java

[0.001s][warning][perf,memops] Cannot use file /tmp/hsperfdata_jailuser/1 because it is locked by another process (errno = 11)
[ERROR] Error at ruleset.xml:58:5
56|
57|
58|
^^^^^ Unable to find referenced rule BooleanInstantiation; perhaps the rule name is misspelled?

59|
60|
[WARN] Warning at ruleset.xml:66:5
64|
65|
66|
^^^^^ Use Rule name category/java/bestpractices.xml/DefaultLabelNotLastInSwitc

... [truncated 11539 characters] ...

prone.xml/InaccurateNumericLiteral instead of the deprecated Rule name category/ecmascript/errorprone.xml/InnaccurateNumericLiteral. PMD 8.0.0 will remove support for this deprecated Rule name usage.

185|
186|
[ERROR] Cannot load ruleset category/vm/bestpractices.xml: Cannot resolve rule/ruleset reference 'category/vm/bestpractices.xml'. Make sure the resource is a valid file or URL and is on the CLASSPATH. Use --debug (or a fine log level) to see the current classpath.
[WARN] Progressbar rendering conflicts with reporting to STDOUT. No progressbar will be shown. Try running with argument -r to output the report to a file instead.

src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java

[ERROR] Error at ruleset.xml:58:5
56|
57|
58|
^^^^^ Unable to find referenced rule BooleanInstantiation; perhaps the rule name is misspelled?

59|
60|
[WARN] Warning at ruleset.xml:66:5
64|
65|
66|
^^^^^ Use Rule name category/java/bestpractices.xml/DefaultLabelNotLastInSwitch instead of the deprecated Rule name category/java/bestpractices.xml/DefaultLabelNotLastInSwitchStmt. PMD 8.0.0 will remove su

... [truncated 11412 characters] ...

prone.xml/InaccurateNumericLiteral instead of the deprecated Rule name category/ecmascript/errorprone.xml/InnaccurateNumericLiteral. PMD 8.0.0 will remove support for this deprecated Rule name usage.

185|
186|
[ERROR] Cannot load ruleset category/vm/bestpractices.xml: Cannot resolve rule/ruleset reference 'category/vm/bestpractices.xml'. Make sure the resource is a valid file or URL and is on the CLASSPATH. Use --debug (or a fine log level) to see the current classpath.
[WARN] Progressbar rendering conflicts with reporting to STDOUT. No progressbar will be shown. Try running with argument -r to output the report to a file instead.

  • 8 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
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: 4

🧹 Nitpick comments (9)
src/main/webapp/i18n/en/exam.json (1)

34-41: Clarify room column labels

The new labels “Number 2” and “Name 2” read like placeholders and will look odd in the UI. Consider wording them as “Alternative Number” / “Alternative Name” (or similar) so users immediately understand what the second column represents.

src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-dialog.component.ts (1)

50-60: Consider using ngOnInit for initialization instead of effect().

While effect() works here, using ngOnInit() for one-time initialization is more conventional and clearer. The effect() API is primarily designed for reactive computations that should re-run when dependencies change.

Apply this diff to refactor to ngOnInit:

-    initEffect = effect(() => {
+    ngOnInit(): void {
         this.studentsRoomDistributionService.getRoomData().subscribe({
             next: (result: HttpResponse<RoomForDistributionDTO[]>) => {
                 this.availableRooms.set(result.body as RoomForDistributionDTO[]);
             },
             error: (error: HttpErrorResponse) => {
                 this.showErrorNotification('examRoomDataLoadFailed', {}, error.message);
                 this.availableRooms.set(undefined);
             },
         });
-    });
+    }

Also update the class declaration:

-export class StudentsRoomDistributionDialogComponent implements OnDestroy {
+export class StudentsRoomDistributionDialogComponent implements OnInit, OnDestroy {
src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.ts (1)

228-238: Add explicit return type for calculateExamRoomData.
Improves readability and type safety.

-    private calculateExamRoomData() {
+    private calculateExamRoomData(): ExamRoomDTOExtended[] | undefined {
         return this.overview()?.newestUniqueExamRooms?.map(
             (examRoomDTO) =>
                 ({
                     ...examRoomDTO,
                     defaultCapacity: this.getDefaultCapacityOfExamRoom(examRoomDTO),
                     maxCapacity: this.getMaxCapacityOfExamRoom(examRoomDTO),
                     layoutStrategyNames: this.getLayoutStrategyNames(examRoomDTO),
                 }) as ExamRoomDTOExtended,
         );
     }
src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-button.component.ts (2)

41-49: Passing InputSignals into modal: verify behavior.
You assign InputSignals to the dialog’s inputs (also InputSignals). This is unconventional and may bypass Angular’s input update flow. Ensure the dialog can call courseId() and exam() safely at runtime.

Prefer passing snapshot values and using plain @input in the dialog (or setInput on the component ref if accessible):

// here:
modalRef.componentInstance.courseId = this.courseId(); // pass number
modalRef.componentInstance.exam = this.exam();         // pass Exam

Then in StudentsRoomDistributionDialogComponent, switch to classic inputs:

@Input() courseId!: number;
@Input() exam!: Exam;

This avoids cross-component signal lifetimes and surprises.


5-5: Minor cleanup: remove unused icons and fix comment.

-import { faPlus, faThLarge, faUpload } from '@fortawesome/free-solid-svg-icons';
+import { faThLarge } from '@fortawesome/free-solid-svg-icons';
@@
-    readonly faPlus = faPlus;
-    readonly faUpload = faUpload;
     readonly faThLarge = faThLarge;
@@
-     * Open up upload dialog for exam users image upload
+     * Open the room distribution dialog for exam users

Also applies to: 33-35, 38-40

src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java (4)

56-62: Unused dependency in constructor

ExamUserRepository is injected but not used in this service. Remove it to reduce coupling and improve testability, unless it’s needed imminently.

Would you like me to scan the codebase for any pending usages before removing?


258-263: Support both 'useable_seats' and 'usable_seats' layout keys

JSON commonly spells it “usable”. Consider accepting both to be lenient with inputs.

-                case "useable_seats" -> layoutStrategy.setType(LayoutStrategyType.FIXED_SELECTION);
+                case "useable_seats", "usable_seats" -> layoutStrategy.setType(LayoutStrategyType.FIXED_SELECTION);

315-318: Null-safe newest-version selection

If createdDate can be null (e.g., pre-audited data), comparator will throw. Make it null-safe:

-        Map<String, ExamRoom> newestRoomByRoomNumberAndName = examRooms.stream().collect(Collectors.toMap(
-                // Use null character as a separator, as it is not allowed in room numbers or names
-                examRoom -> examRoom.getRoomNumber() + "\u0000" + examRoom.getName(), Function.identity(), BinaryOperator.maxBy(Comparator.comparing(ExamRoom::getCreatedDate))));
+        Map<String, ExamRoom> newestRoomByRoomNumberAndName = examRooms.stream().collect(Collectors.toMap(
+                examRoom -> examRoom.getRoomNumber() + "\u0000" + examRoom.getName(),
+                Function.identity(),
+                BinaryOperator.maxBy(Comparator.comparing(ExamRoom::getCreatedDate, Comparator.nullsLast(Comparator.naturalOrder())))));

423-426: Professionalize error message

Avoid whimsical wording in API errors.

-                throw new BadRequestAlertException("Sire, the selected seat " + seatInput + " does not exist in room " + roomNumber, ENTITY_NAME, "room.seatNotFoundFixedSelection",
+                throw new BadRequestAlertException("Selected seat " + seatInput + " does not exist in room " + roomNumber, ENTITY_NAME, "room.seatNotFoundFixedSelection",
                         Map.of("rowIndex", rowIndex, "seatIndex", seatIndex, "roomNumber", roomNumber));
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between abd58e5 and 7953ca7.

⛔ Files ignored due to path filters (1)
  • src/test/resources/test-data/exam-room/rooms-master.zip is excluded by !**/*.zip, !**/*.zip
📒 Files selected for processing (29)
  • src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomDTO.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomForDistributionDTO.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomExamAssignmentRepository.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomRepository.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java (10 hunks)
  • src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java (0 hunks)
  • src/main/java/de/tum/cit/aet/artemis/exam/web/ExamRoomDistributionResource.java (1 hunks)
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.html (1 hunks)
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.spec.ts (2 hunks)
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.ts (4 hunks)
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.model.ts (2 hunks)
  • src/main/webapp/app/exam/manage/students/exam-students.component.html (1 hunks)
  • src/main/webapp/app/exam/manage/students/exam-students.component.ts (2 hunks)
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-button.component.ts (1 hunks)
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-dialog.component.html (1 hunks)
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-dialog.component.ts (1 hunks)
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.model.ts (1 hunks)
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.service.ts (1 hunks)
  • src/main/webapp/i18n/de/error.json (1 hunks)
  • src/main/webapp/i18n/de/examRooms.json (1 hunks)
  • src/main/webapp/i18n/en/error.json (1 hunks)
  • src/main/webapp/i18n/en/exam.json (1 hunks)
  • src/main/webapp/i18n/en/examRooms.json (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java (9 hunks)
  • src/test/java/de/tum/cit/aet/artemis/exam/service/ExamRoomServiceTest.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/exam/test_repository/ExamRoomTestRepository.java (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/exam/util/ExamRoomZipFiles.java (1 hunks)
  • supporting_scripts/code-coverage/module-coverage-client/check-client-module-coverage.mjs (1 hunks)
💤 Files with no reviewable changes (1)
  • src/main/java/de/tum/cit/aet/artemis/exam/web/ExamResource.java
🧰 Additional context used
📓 Path-based instructions (5)
src/main/webapp/**/*.ts

⚙️ CodeRabbit configuration file

angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

Files:

  • src/main/webapp/app/exam/manage/students/exam-students.component.ts
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.model.ts
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.spec.ts
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.model.ts
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-dialog.component.ts
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.ts
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.service.ts
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-button.component.ts
src/main/webapp/**/*.html

⚙️ CodeRabbit configuration file

@if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

Files:

  • src/main/webapp/app/exam/manage/students/exam-students.component.html
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.html
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-dialog.component.html
src/test/java/**/*.java

⚙️ CodeRabbit configuration file

test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

Files:

  • src/test/java/de/tum/cit/aet/artemis/exam/test_repository/ExamRoomTestRepository.java
  • src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java
  • src/test/java/de/tum/cit/aet/artemis/exam/util/ExamRoomZipFiles.java
  • src/test/java/de/tum/cit/aet/artemis/exam/service/ExamRoomServiceTest.java
src/main/java/**/*.java

⚙️ CodeRabbit configuration file

naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

Files:

  • src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomForDistributionDTO.java
  • src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomExamAssignmentRepository.java
  • src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java
  • src/main/java/de/tum/cit/aet/artemis/exam/web/ExamRoomDistributionResource.java
  • src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomDTO.java
  • src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java
  • src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomRepository.java
src/main/webapp/i18n/de/**/*.json

⚙️ CodeRabbit configuration file

German language translations should be informal (dutzen) and should never be formal (sietzen). So the user should always be addressed with "du/dein" and never with "sie/ihr".

Files:

  • src/main/webapp/i18n/de/error.json
  • src/main/webapp/i18n/de/examRooms.json
🧠 Learnings (10)
📓 Common learnings
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11419
File: src/main/java/de/tum/cit/aet/artemis/exam/web/ExamRoomDistributionResource.java:62-78
Timestamp: 2025-09-25T20:43:41.712Z
Learning: In the ExamRoomDistributionService.distributeRegisteredStudents method in src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java, ExamRoom entities are shared resources that are dynamically assigned to exams through the ExamRoomExamAssignment join table. Rooms don't "belong" to exams until the distribution process creates these assignments. The method first clears existing assignments for the exam, then creates new ones for the provided room IDs.
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11378
File: src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java:13-16
Timestamp: 2025-09-15T11:18:26.439Z
Learning: In ExamRoomDistributionService.distributeRegisteredStudents method in src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java, the team has decided not to use Transactional annotation despite multiple repository operations, based on human reviewer consultation.
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11378
File: src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java:72-79
Timestamp: 2025-09-15T11:21:15.983Z
Learning: SamuelRoettgermann prefers iterative development approach - initial implementations should be working starting points rather than full-fledged, finished implementations. Optimization and robustness improvements can be added later.
📚 Learning: 2025-09-25T20:48:13.391Z
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11419
File: src/test/java/de/tum/cit/aet/artemis/exam/test_repository/ExamRoomTestRepository.java:18-34
Timestamp: 2025-09-25T20:48:13.391Z
Learning: In ExamRoomTestRepository JPQL queries in src/test/java/de/tum/cit/aet/artemis/exam/test_repository/ExamRoomTestRepository.java, CTE (Common Table Expression) syntax with bare attribute names like `roomNumber`, `name`, `createdDate` inside the WITH clause works correctly without requiring entity aliases, confirmed working by SamuelRoettgermann in their Artemis/Hibernate setup.

Applied to files:

  • src/test/java/de/tum/cit/aet/artemis/exam/test_repository/ExamRoomTestRepository.java
  • src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomRepository.java
📚 Learning: 2025-09-06T19:03:45.260Z
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11111
File: src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.ts:71-76
Timestamp: 2025-09-06T19:03:45.260Z
Learning: In the ExamRoomsComponent (src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.ts), the numberOfUniqueLayoutStrategies computed signal is intentionally designed to sum the total number of layout strategies across all unique exam rooms, not to count distinct layout strategies by name. The author prefers the more descriptive name "numberOfLayoutStrategiesOfUniqueRooms" to better reflect this intention.

Applied to files:

  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.spec.ts
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.model.ts
  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.ts
📚 Learning: 2025-08-30T20:20:17.236Z
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11111
File: src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java:0-0
Timestamp: 2025-08-30T20:20:17.236Z
Learning: In ExamRoomIntegrationTest.validateAdminOverview(), the first assertion should use subset containment (contains) rather than exact equality because the admin overview shows all newest unique exam rooms in the system including those from previous uploads, not just rooms from the current upload being tested. The test only needs to verify that the expected rooms from the current upload are present in the response.

Applied to files:

  • src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.spec.ts
  • src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java
  • src/test/java/de/tum/cit/aet/artemis/exam/service/ExamRoomServiceTest.java
📚 Learning: 2025-09-25T20:43:41.712Z
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11419
File: src/main/java/de/tum/cit/aet/artemis/exam/web/ExamRoomDistributionResource.java:62-78
Timestamp: 2025-09-25T20:43:41.712Z
Learning: In the ExamRoomDistributionService.distributeRegisteredStudents method in src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java, ExamRoom entities are shared resources that are dynamically assigned to exams through the ExamRoomExamAssignment join table. Rooms don't "belong" to exams until the distribution process creates these assignments. The method first clears existing assignments for the exam, then creates new ones for the provided room IDs.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomForDistributionDTO.java
  • src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java
  • src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java
  • src/main/java/de/tum/cit/aet/artemis/exam/web/ExamRoomDistributionResource.java
  • src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java
  • src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomRepository.java
  • src/test/java/de/tum/cit/aet/artemis/exam/service/ExamRoomServiceTest.java
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.service.ts
📚 Learning: 2025-09-15T11:18:26.439Z
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11378
File: src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java:13-16
Timestamp: 2025-09-15T11:18:26.439Z
Learning: In ExamRoomDistributionService.distributeRegisteredStudents method in src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java, the team has decided not to use Transactional annotation despite multiple repository operations, based on human reviewer consultation.

Applied to files:

  • src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomExamAssignmentRepository.java
  • src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java
  • src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java
  • src/main/java/de/tum/cit/aet/artemis/exam/web/ExamRoomDistributionResource.java
  • src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java
  • src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomRepository.java
  • src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.service.ts
📚 Learning: 2025-09-25T20:28:36.905Z
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11419
File: src/main/java/de/tum/cit/aet/artemis/exam/domain/ExamUser.java:16-17
Timestamp: 2025-09-25T20:28:36.905Z
Learning: In the Artemis codebase, ExamUser entity uses ExamSeatDTO as a transient field for performance reasons. SamuelRoettgermann tested domain value objects but they caused 60x slower performance. This architectural exception is approved by maintainers due to significant performance benefits and Artemis naming convention requirements.

Applied to files:

  • src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java
📚 Learning: 2024-06-10T19:44:09.116Z
Learnt from: jakubriegel
PR: ls1intum/Artemis#8050
File: src/test/java/de/tum/in/www1/artemis/plagiarism/PlagiarismUtilService.java:62-66
Timestamp: 2024-06-10T19:44:09.116Z
Learning: The `createCourseWithUsers` method in `PlagiarismUtilService.java` uses fixed inputs as it is designed to be a test helper method for simplifying the setup of courses and users in tests.

Applied to files:

  • src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java
📚 Learning: 2025-08-14T21:24:50.201Z
Learnt from: SamuelRoettgermann
PR: ls1intum/Artemis#11111
File: src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java:150-152
Timestamp: 2025-08-14T21:24:50.201Z
Learning: In the ExamRoomService.parseAndStoreExamRoomDataFromZipFile method in src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java, IllegalArgumentException cannot be thrown in the try block, so only IOException needs to be caught when parsing ZIP files containing exam room JSON data.

Applied to files:

  • src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java
  • src/test/java/de/tum/cit/aet/artemis/exam/util/ExamRoomZipFiles.java
  • src/test/java/de/tum/cit/aet/artemis/exam/service/ExamRoomServiceTest.java
📚 Learning: 2024-06-10T19:44:09.116Z
Learnt from: valentin-boehm
PR: ls1intum/Artemis#7384
File: src/test/java/de/tum/in/www1/artemis/exam/StudentExamIntegrationTest.java:988-993
Timestamp: 2024-06-10T19:44:09.116Z
Learning: The `testSubmitStudentExam_differentUser` method does not require additional checks to verify the state of `studentExam1` after receiving a `HttpStatus.FORBIDDEN` because the control flow in the `StudentExamResource` is straightforward and ensures no state change occurs.

Applied to files:

  • src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java
🧬 Code graph analysis (11)
src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomForDistributionDTO.java (2)
src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomDeletionSummaryDTO.java (1)
  • JsonInclude (5-7)
src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomAdminOverviewDTO.java (1)
  • JsonInclude (9-12)
src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomExamAssignmentRepository.java (1)
src/main/java/de/tum/cit/aet/artemis/exam/config/ExamEnabled.java (1)
  • ExamEnabled (13-25)
src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java (2)
src/main/java/de/tum/cit/aet/artemis/exam/web/ExamRoomDistributionResource.java (1)
  • Conditional (31-96)
src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java (1)
  • Conditional (45-524)
src/test/java/de/tum/cit/aet/artemis/exam/ExamRoomIntegrationTest.java (2)
src/main/webapp/app/core/admin/exam-rooms/exam-rooms.model.ts (3)
  • ExamRoomAdminOverviewDTO (38-43)
  • ExamRoomDTO (8-15)
  • ExamRoomLayoutStrategyDTO (23-27)
src/test/java/de/tum/cit/aet/artemis/exam/util/ExamRoomZipFiles.java (1)
  • ExamRoomZipFiles (12-93)
src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-dialog.component.ts (2)
src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-button.component.ts (1)
  • Component (9-51)
src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.model.ts (1)
  • RoomForDistributionDTO (1-8)
src/main/java/de/tum/cit/aet/artemis/exam/web/ExamRoomDistributionResource.java (3)
src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java (1)
  • Conditional (32-126)
src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java (1)
  • Conditional (45-524)
src/main/java/de/tum/cit/aet/artemis/exam/config/ExamEnabled.java (1)
  • ExamEnabled (13-25)
src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomDTO.java (1)
src/main/webapp/app/core/admin/exam-rooms/exam-rooms.model.ts (1)
  • ExamRoomDTO (8-15)
src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomRepository.java (1)
src/main/java/de/tum/cit/aet/artemis/exam/config/ExamEnabled.java (1)
  • ExamEnabled (13-25)
src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.ts (1)
src/main/webapp/app/core/admin/exam-rooms/exam-rooms.model.ts (2)
  • ExamRoomDTOExtended (17-21)
  • ExamRoomDTO (8-15)
src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.service.ts (1)
src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.model.ts (1)
  • RoomForDistributionDTO (1-8)
src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-button.component.ts (2)
src/main/webapp/app/exam/manage/students/exam-students.component.ts (1)
  • Component (35-323)
src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-dialog.component.ts (1)
  • Component (17-171)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Build and Push Docker Image / Build linux/amd64 Docker Image for ls1intum/artemis
  • GitHub Check: Build and Push Docker Image / Build linux/arm64 Docker Image for ls1intum/artemis
  • GitHub Check: Build .war artifact
  • GitHub Check: client-style
  • GitHub Check: client-tests
  • GitHub Check: server-style
  • GitHub Check: server-tests
  • GitHub Check: bean-instantiation-check
  • GitHub Check: Analyse
  • GitHub Check: Mend Security Check
🔇 Additional comments (21)
supporting_scripts/code-coverage/module-coverage-client/check-client-module-coverage.mjs (1)

115-115: Verify the programming module branch coverage threshold update.

The PR touches multiple files under src/main/webapp/app/programming/, leading to a branch coverage drop from 76.30% to 76.26%.

  • If this reduction stems from unavoidable untested logic, document the rationale in the PR description.
  • Otherwise, please add tests to restore the previous 76.30% threshold.
src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.model.ts (1)

1-8: LGTM!

The interface definition is clean and correctly typed. It properly aligns with the backend DTO structure and follows TypeScript/Angular naming conventions.

src/main/webapp/i18n/de/examRooms.json (1)

37-37: LGTM!

The German translations are appropriate and clear. "ID" is correctly left as-is (technical term), and "Standardkapazität" is proper German for "default capacity".

Also applies to: 41-41

src/main/webapp/app/exam/manage/students/exam-students.component.ts (1)

28-28: LGTM!

The import and component registration follow Angular standalone component patterns correctly. The integration is minimal and non-invasive.

Also applies to: 44-44

src/main/webapp/app/core/admin/exam-rooms/exam-rooms.model.ts (2)

9-9: LGTM!

The addition of id as a readonly property and defaultCapacity as a required field properly aligns with the backend DTO changes and supports the new admin UI features.

Also applies to: 18-18


26-26: Capacity is always provided for ExamRoomLayoutStrategyDTO; making it mandatory is safe. All front-end object literals include a numeric capacity.

src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.html (2)

69-73: LGTM!

The new sortable columns for id and defaultCapacity are correctly implemented with proper sorting directives, icons, and i18n bindings.

Also applies to: 89-93


107-112: LGTM!

The data cells correctly display the new id and defaultCapacity fields. Tracking by examRoom.id instead of examRoom.roomNumber is a better practice for stable row identity.

Also applies to: 125-128

src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomExamAssignmentRepository.java (1)

13-24: LGTM!

The repository interface follows Spring Data JPA best practices:

  • Proper annotations including @Conditional(ExamEnabled.class) for feature gating
  • @Lazy initialization appropriate for optional features
  • deleteAllByExamId method correctly annotated with @Transactional and @Modifying
  • Method name follows Spring Data naming conventions

Based on learnings

src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution-dialog.component.ts (1)

62-64: LGTM!

The component implementation is solid:

  • Proper signal usage with WritableSignal and computed()
  • Memory cleanup correctly handles Subject unsubscription
  • Search and filtering logic with subsequence matching provides good UX
  • Room selection management maintains immutability
  • Helper methods are well-structured and focused

Also applies to: 89-91, 93-110, 117-123, 130-141, 155-170

src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomForDistributionDTO.java (1)

8-11: LGTM!

The DTO record is well-designed:

  • Modern Java record pattern for immutable DTOs
  • Proper Jakarta validation with @NotBlank on required fields and @Nullable on optional fields
  • @JsonInclude(JsonInclude.Include.NON_EMPTY) appropriately excludes null/empty values from JSON
  • Aligns perfectly with the TypeScript RoomForDistributionDTO interface
  • Follows coding guidelines for naming, records, and minimal data transfer

As per coding guidelines

src/main/webapp/app/core/admin/exam-rooms/exam-rooms.component.ts (3)

90-91: LGTM: computed data extraction is clear.


93-93: Sort keys extended; confirm numeric sorting for id.
If SortService compares as strings, numeric ids might sort incorrectly (e.g., 2 > 10). Please verify sortByProperty handles numbers.


107-107: Good UX: sort immediately after loading overview.

src/main/java/de/tum/cit/aet/artemis/exam/dto/room/ExamRoomDTO.java (1)

10-11: LGTM: id added to DTO record.
Breaking API acknowledged; TS model appears aligned.

Please confirm all server mappers/tests were updated to populate id to avoid serialization gaps.

src/main/webapp/app/exam/manage/students/room-distribution/students-room-distribution.service.ts (1)

10-17: LGTM: endpoints and typings look correct.
Paths align with backend; using observe: 'response' is fine.

Also applies to: 26-28

src/main/java/de/tum/cit/aet/artemis/exam/web/ExamRoomDistributionResource.java (2)

63-80: LGTM: access check, validation, and delegation are sound.
Custom error keys for empty/invalid room IDs are helpful.


88-95: LGTM: distribution data endpoint is minimal and clear.

src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomDistributionService.java (2)

78-88: Consistency risk: delete → re-create without a transactional boundary

Between deleteAllByExamId, saveAll(assignments), and saving users, a failure can leave partial state (e.g., assignments updated but users not saved). I know the team opted to avoid @transactional, but please at least:

  • Compute and validate seat allocation before performing any writes (fail fast).
  • Ensure idempotency via unique constraints on (examId, examRoomId) in ExamRoomExamAssignment.
  • Consider wrapping the three write steps in a best-effort retry or compensating logic.

Would you like me to propose a fail-fast write ordering that keeps the current non-transactional approach?

Based on learnings


70-76: Edge-case: capacity computed from default layout only

You rely on default layout capacity; distribution then uses getDefaultUsableSeats again. Ensure capacities and actual usable seats cannot diverge (e.g., after data updates). If divergence is possible, compute usable seats once and reuse for both the check and assignment.

src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java (1)

380-395: Should fixed-selection ignore SeatCondition?

getUsableSeatsFixedSelection uses all seats (onlyUsableSeats=false). If a seat is flagged non-usable, do we still allow selection via layout JSON? If not, validate and reject such selections.

Comment on lines +68 to +77
SELECT id
FROM (
SELECT er.id AS id, er.roomNumber AS roomNumber, er.name AS name, er.createdDate AS createdDate, ROW_NUMBER() OVER (
PARTITION BY er.roomNumber, er.name
ORDER BY er.createdDate DESC, er.id DESC
) AS rowNumber
FROM ExamRoom er
)
WHERE rowNumber = 1
""")
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Alias missing on windowed subquery causes runtime failure

The derived table needs an alias in JPQL/SQL. As written, Hibernate will emit ... FROM (SELECT …) WHERE ..., which is illegal SQL and fails at runtime. Please alias the subselect and reference that alias in the SELECT/WHERE clause.

-    @Query("""
-            SELECT id
-            FROM (
+    @Query("""
+            SELECT latest.id
+            FROM (
                 SELECT er.id AS id, er.roomNumber AS roomNumber, er.name AS name, er.createdDate AS createdDate, ROW_NUMBER() OVER (
                     PARTITION BY er.roomNumber, er.name
                     ORDER BY er.createdDate DESC, er.id DESC
                 ) AS rowNumber
                 FROM ExamRoom er
-            )
-            WHERE rowNumber = 1
+            ) latest
+            WHERE latest.rowNumber = 1
             """)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SELECT id
FROM (
SELECT er.id AS id, er.roomNumber AS roomNumber, er.name AS name, er.createdDate AS createdDate, ROW_NUMBER() OVER (
PARTITION BY er.roomNumber, er.name
ORDER BY er.createdDate DESC, er.id DESC
) AS rowNumber
FROM ExamRoom er
)
WHERE rowNumber = 1
""")
@Query("""
SELECT latest.id
FROM (
SELECT er.id AS id, er.roomNumber AS roomNumber, er.name AS name, er.createdDate AS createdDate, ROW_NUMBER() OVER (
PARTITION BY er.roomNumber, er.name
ORDER BY er.createdDate DESC, er.id DESC
) AS rowNumber
FROM ExamRoom er
) latest
WHERE latest.rowNumber = 1
""")
🤖 Prompt for AI Agents
In src/main/java/de/tum/cit/aet/artemis/exam/repository/ExamRoomRepository.java
around lines 68 to 77, the windowed subquery is missing an alias which produces
invalid SQL; fix it by giving the derived table an alias (e.g., "t") immediately
after the closing parenthesis and update the outer query to reference that alias
(e.g., SELECT t.id FROM ( ... ) t WHERE t.rowNumber = 1) so the generated
JPQL/SQL is valid.

Comment on lines +91 to +101
private void distributeExamUsersToUsableSeatsInRooms(Exam exam, Set<ExamRoom> examRoomsForExam) {
Map<String, List<ExamSeatDTO>> roomNumberToUsableSeatsDefaultLayout = new HashMap<>();
for (ExamRoom examRoom : examRoomsForExam) {
roomNumberToUsableSeatsDefaultLayout.put(examRoom.getRoomNumber(), examRoomService.getDefaultUsableSeats(examRoom));
}

setPlannedRoomAndPlannedSeatForExamUsersRandomly(exam, roomNumberToUsableSeatsDefaultLayout);

examUserRepository.saveAll(exam.getExamUsers());
examRepository.save(exam);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Uneven and non-deterministic distribution; remove redundant add and balance across rooms

Current logic fills rooms in arbitrary HashMap/Set order and may pack early rooms completely while leaving others unused. Also, re-adding an already-associated ExamUser to exam may be redundant and risky during iteration.

Apply a balanced, deterministic round‑robin assignment and drop the extra add:

-        Map<String, List<ExamSeatDTO>> roomNumberToUsableSeatsDefaultLayout = new HashMap<>();
+        Map<String, List<ExamSeatDTO>> roomNumberToUsableSeatsDefaultLayout = new HashMap<>();
         for (ExamRoom examRoom : examRoomsForExam) {
             roomNumberToUsableSeatsDefaultLayout.put(examRoom.getRoomNumber(), examRoomService.getDefaultUsableSeats(examRoom));
         }
 
-        setPlannedRoomAndPlannedSeatForExamUsersRandomly(exam, roomNumberToUsableSeatsDefaultLayout);
+        assignExamUsersToSeatsBalanced(exam, roomNumberToUsableSeatsDefaultLayout);
 
         examUserRepository.saveAll(exam.getExamUsers());
         examRepository.save(exam);
     }
 
-    private void setPlannedRoomAndPlannedSeatForExamUsersRandomly(Exam exam, Map<String, List<ExamSeatDTO>> roomNumberToUsableSeats) {
-        Iterator<ExamUser> examUsersIterator = exam.getExamUsers().iterator();
-
-        for (var roomNumberToUsableSeatsEntry : roomNumberToUsableSeats.entrySet()) {
-            final String roomNumber = roomNumberToUsableSeatsEntry.getKey();
-            final List<ExamSeatDTO> usableSeatsForThisRoom = roomNumberToUsableSeatsEntry.getValue();
-
-            for (ExamSeatDTO seat : usableSeatsForThisRoom) {
-                if (!examUsersIterator.hasNext()) {
-                    return;
-                }
-
-                ExamUser nextExamUser = examUsersIterator.next();
-                nextExamUser.setPlannedRoom(roomNumber);
-                nextExamUser.setPlannedSeat(seat.name());
-                exam.addExamUser(nextExamUser);
-            }
-        }
-    }
+    private void assignExamUsersToSeatsBalanced(Exam exam, Map<String, List<ExamSeatDTO>> roomNumberToUsableSeats) {
+        // Deterministic room order
+        var sortedRoomNumbers = new java.util.ArrayList<>(roomNumberToUsableSeats.keySet());
+        java.util.Collections.sort(sortedRoomNumbers);
+        var seatsByRoom = new java.util.LinkedHashMap<String, List<ExamSeatDTO>>();
+        for (var rn : sortedRoomNumbers) {
+            seatsByRoom.put(rn, roomNumberToUsableSeats.get(rn));
+        }
+
+        // Round-robin seat order across rooms
+        int maxPerRoom = seatsByRoom.values().stream().mapToInt(List::size).max().orElse(0);
+        List<Map.Entry<String, ExamSeatDTO>> assignmentOrder = new ArrayList<>();
+        for (int i = 0; i < maxPerRoom; i++) {
+            for (var e : seatsByRoom.entrySet()) {
+                var seats = e.getValue();
+                if (i < seats.size()) {
+                    assignmentOrder.add(Map.entry(e.getKey(), seats.get(i)));
+                }
+            }
+        }
+
+        Iterator<ExamUser> it = exam.getExamUsers().iterator();
+        for (var seatEntry : assignmentOrder) {
+            if (!it.hasNext()) {
+                break;
+            }
+            ExamUser user = it.next();
+            user.setPlannedRoom(seatEntry.getKey());
+            user.setPlannedSeat(seatEntry.getValue().name());
+            // no re-adding to exam collection
+        }
+    }

As per coding guidelines

Also applies to: 103-121

Comment on lines +405 to +415
private static List<List<ExamSeatDTO>> getSortedRowsWithSortedSeats(ExamRoom examRoom, boolean onlyUsableSeats) {
List<ExamSeatDTO> examSeats = examRoom.getSeats();
if (onlyUsableSeats) {
examSeats = examSeats.stream().filter(examSeatDTO -> examSeatDTO.seatCondition() == SeatCondition.USABLE).toList();
}
Map<Double, List<ExamSeatDTO>> rowsByRowHeight = examSeats.stream().collect(Collectors.groupingBy(ExamSeatDTO::yCoordinate));

// Sort rows by y ascending, and seats within row by x ascending
return rowsByRowHeight.entrySet().stream().sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getValue().stream().sorted(Comparator.comparingDouble(ExamSeatDTO::xCoordinate)).toList()).toList();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Row grouping by exact double equality is brittle; use epsilon clustering

Grouping rows by exact y doubles can split a visual row into multiple due to tiny floating-point differences. Cluster by y within a tolerance and sort seats by x:

-    private static List<List<ExamSeatDTO>> getSortedRowsWithSortedSeats(ExamRoom examRoom, boolean onlyUsableSeats) {
-        List<ExamSeatDTO> examSeats = examRoom.getSeats();
-        if (onlyUsableSeats) {
-            examSeats = examSeats.stream().filter(examSeatDTO -> examSeatDTO.seatCondition() == SeatCondition.USABLE).toList();
-        }
-        Map<Double, List<ExamSeatDTO>> rowsByRowHeight = examSeats.stream().collect(Collectors.groupingBy(ExamSeatDTO::yCoordinate));
-
-        // Sort rows by y ascending, and seats within row by x ascending
-        return rowsByRowHeight.entrySet().stream().sorted(Map.Entry.comparingByKey())
-                .map(entry -> entry.getValue().stream().sorted(Comparator.comparingDouble(ExamSeatDTO::xCoordinate)).toList()).toList();
-    }
+    private static List<List<ExamSeatDTO>> getSortedRowsWithSortedSeats(ExamRoom examRoom, boolean onlyUsableSeats) {
+        List<ExamSeatDTO> seats = examRoom.getSeats();
+        if (onlyUsableSeats) {
+            seats = seats.stream().filter(s -> s.seatCondition() == SeatCondition.USABLE).toList();
+        }
+        // Sort all seats first by y, then by x
+        seats = seats.stream().sorted(Comparator.comparingDouble(ExamSeatDTO::yCoordinate).thenComparingDouble(ExamSeatDTO::xCoordinate)).toList();
+        List<List<ExamSeatDTO>> rows = new ArrayList<>();
+        final double Y_EPS = 0.5d; // tolerance for grouping seats into the same row
+        for (ExamSeatDTO seat : seats) {
+            if (rows.isEmpty() || Math.abs(rows.get(rows.size() - 1).get(0).yCoordinate() - seat.yCoordinate()) > Y_EPS) {
+                rows.add(new ArrayList<>());
+            }
+            rows.get(rows.size() - 1).add(seat);
+        }
+        return rows;
+    }

As per coding guidelines

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static List<List<ExamSeatDTO>> getSortedRowsWithSortedSeats(ExamRoom examRoom, boolean onlyUsableSeats) {
List<ExamSeatDTO> examSeats = examRoom.getSeats();
if (onlyUsableSeats) {
examSeats = examSeats.stream().filter(examSeatDTO -> examSeatDTO.seatCondition() == SeatCondition.USABLE).toList();
}
Map<Double, List<ExamSeatDTO>> rowsByRowHeight = examSeats.stream().collect(Collectors.groupingBy(ExamSeatDTO::yCoordinate));
// Sort rows by y ascending, and seats within row by x ascending
return rowsByRowHeight.entrySet().stream().sorted(Map.Entry.comparingByKey())
.map(entry -> entry.getValue().stream().sorted(Comparator.comparingDouble(ExamSeatDTO::xCoordinate)).toList()).toList();
}
private static List<List<ExamSeatDTO>> getSortedRowsWithSortedSeats(ExamRoom examRoom, boolean onlyUsableSeats) {
List<ExamSeatDTO> seats = examRoom.getSeats();
if (onlyUsableSeats) {
seats = seats.stream()
.filter(s -> s.seatCondition() == SeatCondition.USABLE)
.toList();
}
// Sort all seats first by y, then by x
seats = seats.stream()
.sorted(Comparator.comparingDouble(ExamSeatDTO::yCoordinate)
.thenComparingDouble(ExamSeatDTO::xCoordinate))
.toList();
List<List<ExamSeatDTO>> rows = new ArrayList<>();
final double Y_EPS = 0.5d; // tolerance for grouping seats into the same row
for (ExamSeatDTO seat : seats) {
if (rows.isEmpty() ||
Math.abs(rows.get(rows.size() - 1).get(0).yCoordinate() - seat.yCoordinate()) > Y_EPS) {
rows.add(new ArrayList<>());
}
rows.get(rows.size() - 1).add(seat);
}
return rows;
}
🤖 Prompt for AI Agents
In src/main/java/de/tum/cit/aet/artemis/exam/service/ExamRoomService.java around
lines 405 to 415, grouping rows by exact double equality on yCoordinate is
brittle and can split a visual row due to tiny floating-point differences;
change the grouping to cluster seats whose yCoordinate values are within a small
epsilon (e.g., 1e-3) instead of using Collectors.groupingBy on the raw double.
Implementation: optionally filter by usable seats first, sort all seats by
yCoordinate ascending, iterate and create clusters by starting a new cluster
when the next seat's y differs from the current cluster's representative by more
than EPS (use cluster mean or first seat y as representative), then for each
cluster sort its seats by xCoordinate ascending and return the list of clusters
sorted by their representative y. Ensure EPS is a named constant and ordering is
stable.

Comment on lines 206 to +208
private getMaxCapacityOfExamRoom(examRoom: ExamRoomDTO): number {
return examRoom.layoutStrategies?.map((layoutStrategy) => layoutStrategy.capacity ?? 0).reduce((max, curr) => Math.max(max, curr), 0) ?? 0;
return examRoom.layoutStrategies?.map((layoutStrategy) => layoutStrategy.capacity).reduce((max, curr) => Math.max(max, curr), 0) ?? 0;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: optional chaining misuse can throw at runtime.
Chains like layoutStrategies?.map(...).reduce(...) still call reduce on undefined. Same for filter → map → at. Use nullish fallback to [] or chain ?. consistently.

Apply this diff:

-    private getMaxCapacityOfExamRoom(examRoom: ExamRoomDTO): number {
-        return examRoom.layoutStrategies?.map((layoutStrategy) => layoutStrategy.capacity).reduce((max, curr) => Math.max(max, curr), 0) ?? 0;
-    }
+    private getMaxCapacityOfExamRoom(examRoom: ExamRoomDTO): number {
+        return (examRoom.layoutStrategies ?? []).reduce((max, s) => Math.max(max, s.capacity), 0);
+    }

-    private getDefaultCapacityOfExamRoom(examRoom: ExamRoomDTO): number {
-        return (
-            examRoom.layoutStrategies
-                ?.filter((layoutStrategy) => layoutStrategy.name.toLowerCase() === 'default')
-                .map((layoutStrategy) => layoutStrategy.capacity)
-                .at(0) ?? 0
-        );
-    }
+    private getDefaultCapacityOfExamRoom(examRoom: ExamRoomDTO): number {
+        return (examRoom.layoutStrategies ?? []).find((ls) => ls.name.toLowerCase() === 'default')?.capacity ?? 0;
+    }

Also update getLayoutStrategyNames to avoid a similar issue:

-        return (
-            examRoom.layoutStrategies
-                ?.map((layoutStrategy) => layoutStrategy.name)
-                .sort()
-                .join(', ') ?? ''
-        );
+        return (examRoom.layoutStrategies ?? []).map((ls) => ls.name).sort().join(', ');

Also applies to: 210-217

@github-project-automation github-project-automation bot moved this from Work In Progress to Ready For Review in Artemis Development Oct 14, 2025
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅SkippedFailedTime ⏱
End-to-End (E2E) Test Report1 ran1 passed0 skipped0 failed1s 752ms
TestResultTime ⏱
No test annotations available

@helios-aet helios-aet bot temporarily deployed to artemis-test3.artemis.cit.tum.de October 14, 2025 09:06 Inactive
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!) core Pull requests that affect the corresponding module exam Pull requests that affect the corresponding module 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.

1 participant