-
Notifications
You must be signed in to change notification settings - Fork 307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exam mode
: Allow submission upon extension
#9833
Exam mode
: Allow submission upon extension
#9833
Conversation
Exam mode
: Allow submission upon extension
There was a problem hiding this 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
🧹 Outside diff range and nitpick comments (2)
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java (2)
Line range hint
149-171
: Constructor Contains Many Parameters Indicating Potential Class Responsibility OverloadThe
StudentExamResource
constructor now has a large number of parameters (over 20 dependencies), which can make the code harder to maintain and understand. This might indicate that the class is handling too many responsibilities.Consider refactoring the class to adhere to the single responsibility principle by extracting related functionalities into separate services or components. This can improve maintainability and readability.
269-281
: Refactor Logic into a Separate Method for Improved ReadabilityThe logic within the
updateWorkingTime
method, specifically from lines 269 to 281, is complex and involves multiple nested operations. Extracting this block into a separate private method would enhance readability and maintainability.Apply this diff to refactor the code:
if (!studentExam.isEnded() && wasEndedOriginally) { + unlockProgrammingExerciseParticipations(studentExam); } ... + private void unlockProgrammingExerciseParticipations(StudentExam studentExam) { + studentExam.getExercises().stream() + .filter(ProgrammingExercise.class::isInstance) + .forEach(exercise -> { + var participation = programmingExerciseStudentParticipationRepository + .findByExerciseIdAndStudentLogin(exercise.getId(), studentExam.getUser().getLogin()); + var submissionPolicy = ((ProgrammingExercise) exercise).getSubmissionPolicy(); + participation.ifPresent(participationObj -> { + long inTimeSubmissions = participationObj.getSubmissions().stream() + .filter(submission -> !submission.isLate()) + .count(); + if (submissionPolicy == null || inTimeSubmissions < submissionPolicy.getSubmissionLimit()) { + programmingExerciseParticipationService.unlockStudentRepositoryAndParticipation(participationObj); + } + }); + }); + }
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
📒 Files selected for processing (2)
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java
(5 hunks)src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java
(7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java (1)
Pattern src/main/java/**/*.java
: 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
src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java (1)
Pattern src/test/java/**/*.java
: 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
📓 Learnings (1)
src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java (1)
Learnt from: valentin-boehm
PR: ls1intum/Artemis#7384
File: src/test/java/de/tum/in/www1/artemis/exam/StudentExamIntegrationTest.java:2836-2846
Timestamp: 2024-11-12T12:51:51.201Z
Learning: The `testAbandonStudentExamNotInTime` 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.
🔇 Additional comments (2)
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java (2)
131-132
: Dependency Injection Via Constructor Is Correct
The addition of ProgrammingExerciseStudentParticipationRepository
as a private final
member and its injection via the constructor adheres to best practices and the project's dependency injection guidelines.
138-139
: Dependency Injection Via Constructor Is Correct
The addition of ProgrammingExerciseParticipationService
as a private final
member and its injection via the constructor is appropriate and aligns with the project's conventions for dependency management.
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java
Outdated
Show resolved
Hide resolved
src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java
Outdated
Show resolved
Hide resolved
src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (1)
src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java (1)
436-444
: Optimize query performance by filtering programming exercises.The current query fetches all exercises but only needs programming exercises with submission policies. Consider optimizing the query to only fetch programming exercises.
Apply this diff to optimize the query:
@Query(""" SELECT se FROM StudentExam se - LEFT JOIN FETCH se.exercises ex - LEFT JOIN TREAT(ex AS ProgrammingExercise) progEx - LEFT JOIN FETCH progEx.submissionPolicy sp + LEFT JOIN FETCH se.exercises progEx + JOIN TYPE(progEx) t + LEFT JOIN FETCH progEx.submissionPolicy sp + WHERE se.id = :studentExamId + AND t = ProgrammingExercise WHERE se.id = :studentExamId """)
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java
(1 hunks)src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java
(6 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`src/main/java/**/*.java`: naming:CamelCase; principles:{sin...
src/main/java/**/*.java
: 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
src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java
📓 Learnings (1)
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java (2)
Learnt from: julian-christl
PR: ls1intum/Artemis#9322
File: src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseStudentParticipationRepository.java:0-0
Timestamp: 2024-11-12T12:51:51.201Z
Learning: In Artemis, an `ExerciseGroup` always has an associated `Exam`, so `exerciseGroup.exam` is never null.
Learnt from: julian-christl
PR: ls1intum/Artemis#9322
File: src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseStudentParticipationRepository.java:170-172
Timestamp: 2024-11-12T12:51:46.554Z
Learning: In Artemis, `exercise.exerciseGroup` may be null, as not all exercises belong to an `ExerciseGroup`.
⏰ Context from checks skipped due to timeout of 90000ms (7)
- GitHub Check: Call Build Workflow / Build .war artifact
- GitHub Check: Call Build Workflow / Build and Push Docker Image
- GitHub Check: client-style
- GitHub Check: client-tests
- GitHub Check: server-style
- GitHub Check: server-tests
- GitHub Check: Analyse
🔇 Additional comments (3)
src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java (1)
429-435
: LGTM!The method signature and documentation are clear and follow the repository's conventions.
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java (2)
131-132
: LGTM!The new dependencies are properly injected through constructor injection, following the coding guidelines.
Also applies to: 138-138, 149-150, 170-171
254-283
: Refactor for better code organization and error handling.The code could be improved for better readability, error handling and performance.
Extract the logic into a dedicated method and improve error handling as suggested in the past review:
- boolean wasEndedOriginally = now.isAfter(exam.getEndDate()); - if (wasEndedOriginally) { - studentExam.getExercises().stream().filter(ProgrammingExercise.class::isInstance).forEach(exercise -> { - var programmingExerciseStudentParticipation = programmingExerciseStudentParticipationRepository.findByExerciseIdAndStudentLogin(exercise.getId(), - studentExam.getUser().getLogin()); - var programmingExerciseSubmissionPolicy = ((ProgrammingExercise) exercise).getSubmissionPolicy(); - // Unlock if there is no submission policy - // or there is a submission policy, but its limit was not reached yet - var submissionCount = programmingExerciseStudentParticipationRepository - .findAllWithSubmissionsByExerciseIdAndStudentLogin(exercise.getId(), studentExam.getUser().getLogin()).size(); - if (programmingExerciseSubmissionPolicy == null || submissionCount < programmingExerciseSubmissionPolicy.getSubmissionLimit()) { - programmingExerciseStudentParticipation.ifPresent(programmingExerciseParticipationService::unlockStudentRepositoryAndParticipation); - } - }); - } + unlockProgrammingExerciseParticipationsIfNeeded(studentExam, exam, now); + } + } + + /** + * Unlocks programming exercise participations if the exam was ended originally. + * Only unlocks if submission policy allows or is not present. + */ + private void unlockProgrammingExerciseParticipationsIfNeeded(StudentExam studentExam, Exam exam, ZonedDateTime now) { + if (!now.isAfter(exam.getEndDate())) { + return; + } + + String studentLogin = studentExam.getUser().getLogin(); + studentExam.getExercises().stream() + .filter(ProgrammingExercise.class::isInstance) + .map(ProgrammingExercise.class::cast) + .forEach(programmingExercise -> { + var participation = programmingExerciseStudentParticipationRepository + .findByExerciseIdAndStudentLogin(programmingExercise.getId(), studentLogin); + + if (participation.isEmpty()) { + log.warn("No participation found for programming exercise {} and student {}", + programmingExercise.getId(), studentLogin); + return; + } + + var submissionPolicy = programmingExercise.getSubmissionPolicy(); + if (submissionPolicy == null) { + programmingExerciseParticipationService.unlockStudentRepositoryAndParticipation(participation.get()); + return; + } + + var submissionCount = programmingExerciseStudentParticipationRepository + .findAllWithSubmissionsByExerciseIdAndStudentLogin(programmingExercise.getId(), studentLogin) + .size(); + + if (submissionCount < submissionPolicy.getSubmissionLimit()) { + programmingExerciseParticipationService.unlockStudentRepositoryAndParticipation(participation.get()); + } + });
I added a note to the instructions: Note: If the student handed in their exam, the instructor should first change the student exam to unsubmitted (with the button on the same page) before extending the working time. |
WalkthroughThe changes introduce a new method in the Changes
Sequence Diagram(s)sequenceDiagram
participant C as Client
participant R as StudentExamResource
participant Repo as StudentExamRepository
participant P as ProgrammingExerciseParticipationService
C->>R: updateWorkingTime(examId, newTime)
R->>Repo: findByIdWithExercisesAndSubmissionPolicy(examId)
Repo-->>R: Optional<StudentExam>
alt Exam ended
loop For each programming exercise
R->>P: unlockParticipation(exercise)
end
end
Possibly related PRs
Suggested labels
Suggested reviewers
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.8.0)src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java[ERROR] Error at ruleset.xml:58:5 59| 67| 72| 76| 79| 80| 82| 83| 84| 85| 86| 87| 88| 91| 92| 107| 120| 125| 135| 138| 185| src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java[ERROR] Error at ruleset.xml:58:5 59| 67| 72| 76| 79| 80| 82| 83| 84| 85| 86| 87| 88| 91| 92| 107| 120| 125| 135| 138| 185| src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java[ERROR] Error at ruleset.xml:58:5 59| 67| 72| 76| 79| 80| 82| 83| 84| 85| 86| 87| 88| 91| 92| 107| 120| 125| 135| 138| 185| Tip 🌐 Web search-backed reviews and chat
✨ Finishing Touches
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java (1)
278-278
: 🛠️ Refactor suggestionOptimize database query by counting submissions directly
At line 278~, the code retrieves all submissions to count them, which can be inefficient, especially if there are many submissions. To improve performance, consider adding a repository method that counts the submissions directly in the database.
Add this method to
ProgrammingExerciseStudentParticipationRepository
:@Query("SELECT COUNT(s) FROM ProgrammingSubmission s WHERE s.participation.exercise.id = :exerciseId AND s.participation.student.login = :studentLogin") long countSubmissionsByExerciseIdAndStudentLogin(@Param("exerciseId") Long exerciseId, @Param("studentLogin") String studentLogin);Then, update the code to use this new method:
- var submissionCount = programmingExerciseStudentParticipationRepository - .findAllWithSubmissionsByExerciseIdAndStudentLogin(exercise.getId(), studentExam.getUser().getLogin()).size(); + var submissionCount = programmingExerciseStudentParticipationRepository + .countSubmissionsByExerciseIdAndStudentLogin(programmingExercise.getId(), studentExam.getUser().getLogin());
🧹 Nitpick comments (3)
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java (1)
271-283
: Avoid unnecessary casting by working withProgrammingExercise
directlyIn the loop starting at line 271~, you filter exercises of type
ProgrammingExercise
but continue to handle them asExercise
, performing casts multiple times. To enhance code readability and avoid redundant casting, consider mapping the exercises directly toProgrammingExercise
instances.Apply this diff to refactor the code:
- studentExam.getExercises().stream().filter(ProgrammingExercise.class::isInstance).forEach(exercise -> { - var programmingExerciseStudentParticipation = programmingExerciseStudentParticipationRepository.findByExerciseIdAndStudentLogin(exercise.getId(), + studentExam.getExercises().stream() + .filter(ProgrammingExercise.class::isInstance) + .map(ProgrammingExercise.class::cast) + .forEach(programmingExercise -> { + var programmingExerciseStudentParticipation = programmingExerciseStudentParticipationRepository.findByExerciseIdAndStudentLogin(programmingExercise.getId(), studentExam.getUser().getLogin()); - var programmingExerciseSubmissionPolicy = ((ProgrammingExercise) exercise).getSubmissionPolicy(); + var programmingExerciseSubmissionPolicy = programmingExercise.getSubmissionPolicy(); // Rest of the code... });src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java (2)
162-163
: Fix inconsistent field naming.The repository field names use inconsistent casing:
ProgrammingExerciseTestRepository
andCourseTestRepository
use PascalCase- Other repository fields like
programmingExerciseStudentParticipationTestRepository
use camelCaseApply this diff to maintain consistent camelCase naming:
- private ProgrammingExerciseTestRepository ProgrammingExerciseTestRepository; + private ProgrammingExerciseTestRepository programmingExerciseTestRepository; - private CourseTestRepository CourseTestRepository; + private CourseTestRepository courseTestRepository;Also applies to: 220-220
863-902
: Add test coverage for submission after extension.While the test verifies that the repository is unlocked when working time is updated, it would be valuable to add test cases that verify:
- The student can successfully submit after their working time is extended
- The submission is properly saved and processed
- Edge cases like multiple extensions or extensions after the exam end date
Would you like me to help generate additional test cases to cover these scenarios?
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java
(1 hunks)src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java
(6 hunks)src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java
(7 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`src/main/java/**/*.java`: naming:CamelCase; principles:{sin...
src/main/java/**/*.java
: 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
src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java
`src/test/java/**/*.java`: test_naming: descriptive; test_si...
src/test/java/**/*.java
: 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
src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java
📓 Learnings (2)
src/main/java/de/tum/cit/aet/artemis/exam/web/StudentExamResource.java (2)
Learnt from: julian-christl
PR: ls1intum/Artemis#9322
File: src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseStudentParticipationRepository.java:0-0
Timestamp: 2024-11-12T12:51:51.201Z
Learning: In Artemis, an `ExerciseGroup` always has an associated `Exam`, so `exerciseGroup.exam` is never null.
Learnt from: julian-christl
PR: ls1intum/Artemis#9322
File: src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseStudentParticipationRepository.java:170-172
Timestamp: 2024-11-12T12:51:46.554Z
Learning: In Artemis, `exercise.exerciseGroup` may be null, as not all exercises belong to an `ExerciseGroup`.
src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java (2)
Learnt from: julian-christl
PR: ls1intum/Artemis#9322
File: src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseStudentParticipationRepository.java:0-0
Timestamp: 2024-11-12T12:51:51.201Z
Learning: In Artemis, an `ExerciseGroup` always has an associated `Exam`, so `exerciseGroup.exam` is never null.
Learnt from: julian-christl
PR: ls1intum/Artemis#9322
File: src/main/java/de/tum/cit/aet/artemis/programming/repository/ProgrammingExerciseStudentParticipationRepository.java:170-172
Timestamp: 2024-11-12T12:51:46.554Z
Learning: In Artemis, `exercise.exerciseGroup` may be null, as not all exercises belong to an `ExerciseGroup`.
⏰ Context from checks skipped due to timeout of 90000ms (6)
- GitHub Check: client-style
- GitHub Check: server-style
- GitHub Check: client-tests-selected
- GitHub Check: server-tests
- GitHub Check: client-tests
- GitHub Check: Analyse
🔇 Additional comments (2)
src/main/java/de/tum/cit/aet/artemis/exam/repository/StudentExamRepository.java (1)
437-444
: Verify correct retrieval of submission policies in JPQL queryIn the method
findByIdWithExercisesAndSubmissionPolicy
, ensure that the JPQL query accurately retrieves submission policies associated withProgrammingExercise
instances. The use ofTREAT(ex AS ProgrammingExercise)
should downcast exercises toProgrammingExercise
where applicable.Please confirm that the
TREAT
function is supported in your JPA provider and behaves as expected in this context. Also, verify that this query retrieves the correct data without introducing any unintended side effects.src/test/java/de/tum/cit/aet/artemis/exam/StudentExamIntegrationTest.java (1)
863-902
: Minimize database interactions in tests by using mocks.The test method performs multiple database operations, such as saving
programmingExercise
,course
,participation
,exam
, andstudentExam
. According to the coding guidelines, tests should avoid direct database access and prefer mocking to improve performance and ensure test isolation.Consider mocking the repositories and services involved to simulate database interactions. Utilize Mockito or similar frameworks to mock the behavior of these components. This will make the test faster, more reliable, and in alignment with the
avoid_db_access
guideline.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code LGTM
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested on TS4. Works as described
Checklist
General
Server
Changes affecting Programming Exercises
Motivation and Context
When instructors extend the time of a student after the exam has officially ended, the student can again access the exam, but is unable to push code.
Linked issue
Description
When the instructor extends a student's individual working time after the exam officially ends, student having the extended time should be able to submit their code.
Steps for Testing
Prerequisites:
Test 1 - Without submission policy
Test 2 - With submission policy
Testserver States
Note
These badges show the state of the test servers.
Green = Currently available, Red = Currently locked
Click on the badges to get to the test servers.
Review Progress
Code Review
Manual Tests
Exam Mode Test
Test Coverage
Screenshots
Summary by CodeRabbit