Skip to content

Conversation

@Kaumadi2k
Copy link
Collaborator

@Kaumadi2k Kaumadi2k commented Dec 3, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Added functionality to manage exams, including retrieving exams for specific proctors and candidates for exams.
    • Introduced the ability to add comments for candidates related to specific exams.
    • New ProctorComment entity allows multiple comments to be associated with candidates and exams.
  • Enhancements

    • Expanded data retrieval capabilities in the repositories for exams and candidates.

These changes improve the exam management experience, making it easier to track candidates and comments associated with exams.

@coderabbitai
Copy link

coderabbitai bot commented Dec 3, 2024

Walkthrough

The changes in this pull request involve the enhancement of exam management functionalities within the ExamSetterController, ExamSetterService, and related classes. New methods and endpoints have been added to manage exams, candidates, and comments effectively. Specifically, the ExamSetterController introduces new GET and POST endpoints, while the ExamSetterService implements corresponding service methods. Additionally, new fields and relationships have been established in the Candidate, Exam, and ProctorComment classes, along with the creation of a new ProctorCommentRepository interface.

Changes

File Change Summary
src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java - Added ExamManagementService dependency.
- New GET endpoints: getExamsForProctor, getCandidatesForExam.
- New POST endpoint: addComment.
src/main/java/com/testify/Testify_Backend/model/Candidate.java - Added List<ProctorComment> comments field with a one-to-many relationship.
src/main/java/com/testify/Testify_Backend/model/Exam.java - Added List<ProctorComment> proctorComments field with a one-to-many relationship.
src/main/java/com/testify/Testify_Backend/model/ProctorComment.java - Introduced new ProctorComment class with fields for id, candidate, exam, content, and createdAt.
src/main/java/com/testify/Testify_Backend/repository/CandidateRepository.java - Added method findByExamId to query candidates by exam ID.
src/main/java/com/testify/Testify_Backend/repository/ExamRepository.java - Added method findByProctorIdAndOrganizationId to fetch exams by proctor and organization IDs.
src/main/java/com/testify/Testify_Backend/repository/ProctorCommentRepository.java - New interface for CRUD operations on ProctorComment.
src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java - Added methods: getExamsForProctor, getCandidatesForExam, addCommentToCandidate.
src/main/java/com/testify/Testify_Backend/service/ExamSetterServiceImpl.java - Implemented new methods in service implementation.
- Added dependencies for CandidateRepository and ProctorCommentRepository.

Possibly related PRs

  • exam management is updated #46: The exam management is updated PR includes modifications to the ExamManagementController, which may relate to the overall exam management functionalities that are also expanded in the main PR.
  • Candidate Exam Session Management and Persistence #62: The Candidate Exam Session Management and Persistence PR introduces new endpoints for managing exam sessions, which aligns with the new functionalities added in the main PR regarding exam management.
  • Exam grading answers API + grade-scheme API + conflict resolve #64: The Exam grading answers API + grade-scheme API + conflict resolve PR enhances the ExamManagementController with new grading functionalities, which is relevant to the exam management features introduced in the main PR.

🐰 In the land of exams, where knowledge blooms,
New comments and candidates fill all the rooms.
With proctors and tests, we gather and cheer,
A hop and a skip, the new features are here!
Let's celebrate learning, with joy and delight,
For every new change makes our future bright! 🌟


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?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 5

🧹 Outside diff range and nitpick comments (9)
src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java (2)

19-20: Consider adding parameter validation and documentation

The method signature looks good, but consider:

  1. Adding @NotNull annotations for the parameters
  2. Adding Javadoc to document the method's purpose, parameters, and possible exceptions
+    /**
+     * Retrieves exams assigned to a specific proctor within an organization.
+     *
+     * @param proctorId The ID of the proctor
+     * @param organizationId The ID of the organization
+     * @return List of exam responses
+     * @throws EntityNotFoundException if proctor or organization not found
+     */
+    List<ExamResponse> getExamsForProctor(@NotNull Long proctorId, @NotNull Long organizationId);

24-24: Add validation and consider security implications

For the comment addition method:

  1. Add content length validation
  2. Consider adding a user/proctor parameter for audit purposes
  3. Consider returning a response object instead of void to confirm success
-    void addCommentToCandidate(Long candidateId, Long examId, String content);
+    /**
+     * Adds a proctor's comment to a candidate's exam record.
+     *
+     * @param candidateId The ID of the candidate
+     * @param examId The ID of the exam
+     * @param content The comment content
+     * @param proctorId The ID of the proctor making the comment
+     * @return CommentResponse containing the created comment details
+     * @throws EntityNotFoundException if candidate or exam not found
+     * @throws ValidationException if content exceeds maximum length
+     */
+    CommentResponse addCommentToCandidate(
+        @NotNull Long candidateId,
+        @NotNull Long examId,
+        @NotNull @Size(max = 1000) String content,
+        @NotNull Long proctorId
+    );
src/main/java/com/testify/Testify_Backend/service/ExamSetterServiceImpl.java (2)

92-100: Optimize transaction management and simplify code using streams

  • Add readOnly = true to the @Transactional annotation to optimize performance for read-only operations.
  • Simplify the mapping from List<Exam> to List<ExamResponse> using streams for cleaner code.

Apply this diff:

 @Override
-@Transactional
+@Transactional(readOnly = true)
 public List<ExamResponse> getExamsForProctor(Long proctorId, Long organizationId) {
     List<Exam> exams = examRepository.findByProctorIdAndOrganizationId(proctorId, organizationId); // Ensure repository method returns a List
-    List<ExamResponse> examResponses = new ArrayList<>();
-    for (Exam exam : exams) {
-        examResponses.add(modelMapper.map(exam, ExamResponse.class));
-    }
+    List<ExamResponse> examResponses = exams.stream()
+        .map(exam -> modelMapper.map(exam, ExamResponse.class))
+        .collect(Collectors.toList());
     return examResponses;
 }

104-112: Simplify collection mapping with streams

Use streams to convert Set<Candidate> to Set<CandidateResponse> more concisely.

Apply this diff:

 @Override
 public Set<CandidateResponse> getCandidatesForExam(Long examId) {
     Set<Candidate> candidates = candidateRepository.findByExamId(examId);
-    Set<CandidateResponse> candidateResponses = new HashSet<>();
-    for (Candidate candidate : candidates) {
-        candidateResponses.add(modelMapper.map(candidate, CandidateResponse.class));
-    }
+    Set<CandidateResponse> candidateResponses = candidates.stream()
+        .map(candidate -> modelMapper.map(candidate, CandidateResponse.class))
+        .collect(Collectors.toSet());
     return candidateResponses;
 }
src/main/java/com/testify/Testify_Backend/model/ProctorComment.java (2)

29-29: Enforce non-null constraint on content field

Add @Column(nullable = false) to ensure that the content field is not null in the database.

Apply this diff:

+    @Column(nullable = false)
     private String content;

31-31: Automatically set createdAt using @PrePersist

Use the @PrePersist annotation to automatically set createdAt before persisting.

Add the following method to the class:

@PrePersist
public void prePersist() {
    this.createdAt = LocalDateTime.now();
}
src/main/java/com/testify/Testify_Backend/model/Candidate.java (1)

30-31: Use Set instead of List for comments to prevent duplicates

Change comments to a Set to avoid duplicate entries and improve performance.

Apply this diff:

 @OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true)
-    private List<ProctorComment> comments;
+    private Set<ProctorComment> comments;

Don't forget to import java.util.Set.

src/main/java/com/testify/Testify_Backend/repository/CandidateRepository.java (1)

27-28: Simplify repository method using derived query

Replace the custom @Query with a derived query method provided by Spring Data JPA.

Apply this diff:

-    @Query("SELECT c FROM Candidate c JOIN c.exams e WHERE e.id = :examId")
-    Set<Candidate> findByExamId(@Param("examId") Long examId);
+    Set<Candidate> findAllByExamsId(Long examId);
src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java (1)

Line range hint 48-75: Add API documentation.

Consider adding OpenAPI/Swagger annotations to document the new endpoints, their parameters, request/response models, and possible error scenarios.

Example for one endpoint:

@Operation(summary = "Get exams for proctor", description = "Retrieves all exams assigned to a proctor in an organization")
@ApiResponses(value = {
    @ApiResponse(responseCode = "200", description = "Successfully retrieved exams"),
    @ApiResponse(responseCode = "400", description = "Invalid proctor or organization ID"),
    @ApiResponse(responseCode = "404", description = "Proctor or organization not found")
})
@GetMapping("/proctor/{proctorId}/{organizationId}")
public ResponseEntity<List<ExamResponse>> getExamsForProctor(
    @Parameter(description = "ID of the proctor") @PathVariable Long proctorId,
    @Parameter(description = "ID of the organization") @PathVariable Long organizationId
) {
    // ... existing code ...
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 38658ae and f084cf2.

📒 Files selected for processing (9)
  • src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java (4 hunks)
  • src/main/java/com/testify/Testify_Backend/model/Candidate.java (2 hunks)
  • src/main/java/com/testify/Testify_Backend/model/Exam.java (1 hunks)
  • src/main/java/com/testify/Testify_Backend/model/ProctorComment.java (1 hunks)
  • src/main/java/com/testify/Testify_Backend/repository/CandidateRepository.java (2 hunks)
  • src/main/java/com/testify/Testify_Backend/repository/ExamRepository.java (1 hunks)
  • src/main/java/com/testify/Testify_Backend/repository/ProctorCommentRepository.java (1 hunks)
  • src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java (2 hunks)
  • src/main/java/com/testify/Testify_Backend/service/ExamSetterServiceImpl.java (5 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/java/com/testify/Testify_Backend/repository/ProctorCommentRepository.java
🔇 Additional comments (4)
src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java (2)

4-5: LGTM! Import statements are appropriate

The new imports align with the response types used in the added method signatures.


21-23: Consider using consistent collection types

The interface uses both List and Set for collections. Consider standardizing to one type unless there's a specific reason for the difference.

src/main/java/com/testify/Testify_Backend/repository/ExamRepository.java (1)

35-36: Validate the necessity of custom query

Ensure that the custom @Query is necessary and cannot be replaced with a derived query for maintainability.

Since you are joining on a collection, the custom query might be required. If possible, consider a derived query like findByProctorsIdAndOrganizationId(Long proctorId, Long organizationId).

src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java (1)

27-27: 🛠️ Refactor suggestion

Remove unused service dependency.

The examManagementService field appears to be unused in the controller. Either remove it or document its intended use.

-    private final ExamManagementService examManagementService;

Likely invalid or redundant comment.

Comment on lines +125 to +137
public void addCommentToCandidate(Long candidateId, Long examId, String content) {
Candidate candidate = candidateRepository.findById(candidateId)
.orElseThrow(() -> new RuntimeException("Candidate not found"));
Exam exam = examRepository.findById(examId).orElseThrow(() -> new RuntimeException("Exam not found"));

ProctorComment comment = new ProctorComment();
comment.setCandidate(candidate);
comment.setExam(exam);
comment.setContent(content);
comment.setCreatedAt(LocalDateTime.now());

proctorCommentRepository.save(comment);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance exception handling and transaction management

  • Annotate the method with @Transactional to ensure atomicity of database operations.
  • Replace generic RuntimeException with more specific exceptions like EntityNotFoundException for clearer error handling.

Apply this diff:

+@Transactional
 public void addCommentToCandidate(Long candidateId, Long examId, String content) {
     Candidate candidate = candidateRepository.findById(candidateId)
-            .orElseThrow(() -> new RuntimeException("Candidate not found"));
+            .orElseThrow(() -> new EntityNotFoundException("Candidate not found with id: " + candidateId));
     Exam exam = examRepository.findById(examId)
-            .orElseThrow(() -> new RuntimeException("Exam not found"));
+            .orElseThrow(() -> new EntityNotFoundException("Exam not found with id: " + examId));

     ProctorComment comment = new ProctorComment();
     comment.setCandidate(candidate);
     comment.setExam(exam);
     comment.setContent(content);
     comment.setCreatedAt(LocalDateTime.now());

     proctorCommentRepository.save(comment);
 }

Ensure to import jakarta.persistence.EntityNotFoundException or define a custom exception class if appropriate.

📝 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
public void addCommentToCandidate(Long candidateId, Long examId, String content) {
Candidate candidate = candidateRepository.findById(candidateId)
.orElseThrow(() -> new RuntimeException("Candidate not found"));
Exam exam = examRepository.findById(examId).orElseThrow(() -> new RuntimeException("Exam not found"));
ProctorComment comment = new ProctorComment();
comment.setCandidate(candidate);
comment.setExam(exam);
comment.setContent(content);
comment.setCreatedAt(LocalDateTime.now());
proctorCommentRepository.save(comment);
}
@Transactional
public void addCommentToCandidate(Long candidateId, Long examId, String content) {
Candidate candidate = candidateRepository.findById(candidateId)
.orElseThrow(() -> new EntityNotFoundException("Candidate not found with id: " + candidateId));
Exam exam = examRepository.findById(examId)
.orElseThrow(() -> new EntityNotFoundException("Exam not found with id: " + examId));
ProctorComment comment = new ProctorComment();
comment.setCandidate(candidate);
comment.setExam(exam);
comment.setContent(content);
comment.setCreatedAt(LocalDateTime.now());
proctorCommentRepository.save(comment);
}

Comment on lines +119 to +121
@OneToMany(mappedBy = "exam", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProctorComment> proctorComments;

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider initializing the List and managing JSON serialization.

While the relationship mapping is correct, consider these improvements:

  1. Initialize the List to prevent potential NPEs
  2. Add @JsonIgnore to prevent infinite recursion during serialization
  3. Add a helper method to manage the bidirectional relationship

Apply this diff:

    @OneToMany(mappedBy = "exam", cascade = CascadeType.ALL, orphanRemoval = true)
+   @JsonIgnore
-   private List<ProctorComment> proctorComments;
+   private List<ProctorComment> proctorComments = new ArrayList<>();
+
+   public void addProctorComment(ProctorComment comment) {
+       proctorComments.add(comment);
+       comment.setExam(this);
+   }
+
+   public void removeProctorComment(ProctorComment comment) {
+       proctorComments.remove(comment);
+       comment.setExam(null);
+   }
📝 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
@OneToMany(mappedBy = "exam", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProctorComment> proctorComments;
@OneToMany(mappedBy = "exam", cascade = CascadeType.ALL, orphanRemoval = true)
@JsonIgnore
private List<ProctorComment> proctorComments = new ArrayList<>();
public void addProctorComment(ProctorComment comment) {
proctorComments.add(comment);
comment.setExam(this);
}
public void removeProctorComment(ProctorComment comment) {
proctorComments.remove(comment);
comment.setExam(null);
}

Comment on lines +71 to +75
@PostMapping("/{candidateId}/{examId}/proctorComments")
public ResponseEntity<String> addComment(@PathVariable Long candidateId, @PathVariable Long examId, @RequestBody String content) {
examSetterService.addCommentToCandidate(candidateId, examId, content);
return ResponseEntity.ok("Comment added successfully");
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance POST endpoint with proper request mapping and validation.

The endpoint needs:

  1. Proper @RequestBody mapping with a DTO
  2. Content-Type specification
  3. Input validation
  4. Better error handling

Apply these improvements:

     @PostMapping("/{candidateId}/{examId}/proctorComments")
+    @Consumes(MediaType.APPLICATION_JSON_VALUE)
-    public ResponseEntity<String> addComment(@PathVariable Long candidateId, @PathVariable Long examId, @RequestBody String content) {
+    public ResponseEntity<String> addComment(
+        @PathVariable Long candidateId,
+        @PathVariable Long examId,
+        @Valid @RequestBody ProctorCommentRequest request
+    ) {
+        if (candidateId == null || candidateId <= 0 || examId == null || examId <= 0) {
+            return ResponseEntity.badRequest().body("Invalid candidate or exam ID");
+        }
+        if (request.getContent() == null || request.getContent().trim().isEmpty()) {
+            return ResponseEntity.badRequest().body("Comment content cannot be empty");
+        }
-        examSetterService.addCommentToCandidate(candidateId, examId, content);
+        examSetterService.addCommentToCandidate(candidateId, examId, request.getContent());
         return ResponseEntity.ok("Comment added successfully");
     }

Create a new DTO:

@Data
public class ProctorCommentRequest {
    @NotBlank(message = "Comment content is required")
    private String content;
}

Comment on lines +48 to +52
@GetMapping("/proctor/{proctorId}/{organizationId}")
public ResponseEntity<List<ExamResponse>> getExamsForProctor(@PathVariable Long proctorId, @PathVariable Long organizationId) {
List<ExamResponse> exams = examSetterService.getExamsForProctor(proctorId, organizationId);
return ResponseEntity.ok(exams);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation and error handling.

The endpoint needs validation for:

  • Non-null and positive IDs
  • Existence of proctor and organization
  • Proctor's association with the organization

Add validation and error handling:

     @GetMapping("/proctor/{proctorId}/{organizationId}")
     public ResponseEntity<List<ExamResponse>> getExamsForProctor(@PathVariable Long proctorId, @PathVariable Long organizationId) {
+        if (proctorId == null || proctorId <= 0 || organizationId == null || organizationId <= 0) {
+            return ResponseEntity.badRequest().build();
+        }
         List<ExamResponse> exams = examSetterService.getExamsForProctor(proctorId, organizationId);
         return ResponseEntity.ok(exams);
     }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +54 to +58
@GetMapping("/{examId}/candidates")
public ResponseEntity<Set<CandidateResponse>> getCandidatesForExam(@PathVariable Long examId) {
Set<CandidateResponse> candidates = examSetterService.getCandidatesForExam(examId);
return ResponseEntity.ok(candidates);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation and error handling.

The endpoint needs validation for:

  • Non-null and positive exam ID
  • Existence of exam

Add validation and error handling:

     @GetMapping("/{examId}/candidates")
     public ResponseEntity<Set<CandidateResponse>> getCandidatesForExam(@PathVariable Long examId) {
+        if (examId == null || examId <= 0) {
+            return ResponseEntity.badRequest().build();
+        }
         Set<CandidateResponse> candidates = examSetterService.getCandidatesForExam(examId);
         return ResponseEntity.ok(candidates);
     }

Committable suggestion skipped: line range outside the PR's diff.

@Kaumadi2k Kaumadi2k merged commit c8bd74a into main Dec 3, 2024
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants