Skip to content

Conversation

@VIHANGAGIT
Copy link
Collaborator

@VIHANGAGIT VIHANGAGIT commented Dec 4, 2024

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a PreventBackButton component to prevent users from navigating back in browser history.
    • Added automatic exam submission when the countdown timer reaches zero in the TimeContainer.
    • Enhanced CandidateDashboard with real-time checks for ongoing exam sessions.
    • Improved grading logic and added a grade submission button in ExamMcqResults.
    • Updated ExamSummary to better manage session data and error handling.
    • Implemented fullscreen mode functionality in ExamView.
  • Bug Fixes

    • Enhanced error handling across various components to improve user experience.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 4, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request introduces several significant changes across multiple components related to exam functionality. A new PreventBackButton component is added to prevent users from navigating back in their browser history. The TimeContainer component is enhanced to manage exam submission based on a countdown timer. The CandidateDashboard now includes an API call to check for ongoing exams, while ExamMcqResults and ExamSummary improve grading and session management. Additionally, ExamView introduces fullscreen functionality, enhancing user interaction during exams.

Changes

File Path Change Summary
src/components/Exam/PreventBackButton.tsx Introduced PreventBackButton component to prevent back navigation using popstate event.
src/components/Exam/TimeContainer.tsx Added countdown timer functionality to automatically submit the exam when time expires; updated UI for time display.
src/components/Exam/index.ts Exported the new PreventBackButton component.
src/pages/candidate/CandidateDashboard.tsx Implemented an API call to check for ongoing exam sessions on component mount; improved modal handling.
src/pages/candidate/ExamMcqResults.tsx Removed redundant Question interface; added state variables for grading; updated grading logic and error handling.
src/pages/candidate/ExamSummary.tsx Enhanced session management logic; updated error handling and static content for exam details.
src/pages/candidate/ExamView.tsx Added fullscreen functionality with event listeners for fullscreen change; improved lifecycle management.

Possibly related PRs

  • Exam Summary #38: Introduces the PreventBackButton component related to routing and navigation functionalities.
  • Added Candidate Exam summary availability status  #56: Adds functionality for managing exam summary availability status, potentially connecting to navigation and state management.
  • Candidate - Exam Feedback #70: Involves navigation to the feedback page after exam submission, related to back navigation prevention.
  • MCQ Grading UI #108: Introduces the ExamMcqResults component for displaying exam results, enhancing user experience.
  • Deamon #137: The PreventBackButton component itself is directly related as it is the main focus of the changes, designed to prevent users from navigating back in their browser history during exam sessions.

🐰 In a world where exams can be tough,
A button to stop you from going back, is enough!
With timers counting down, and grades to see,
We hop through our tests, so carefree!
So here’s to the changes, both big and small,
Let’s celebrate learning, and have a ball! 🎉


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between cfa2d4a and 61309cb.

📒 Files selected for processing (1)
  • src/pages/candidate/CandidateDashboard.tsx (2 hunks)

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

🧹 Outside diff range and nitpick comments (7)
src/pages/candidate/ExamMcqResults.tsx (1)

214-217: Provide user feedback upon grade submission

Currently, success or error messages are logged to the console. For better user experience, display feedback using UI notifications.

Proposed Solution:

Use Ant Design's message component:

if (result.success) {
- console.log('Grade submitted successfully');
+ message.success('Grade submitted successfully');
} else {
- console.error('Error submitting grade');
+ message.error('Error submitting grade');
}
src/components/Exam/PreventBackButton.tsx (2)

5-9: Handle preventDefault and pushState carefully to avoid unexpected behavior

While preventing back navigation, modifying the history stack can lead to a cluttered history and may confuse users if not handled properly.

Proposed Solution:

Consider using event.preventDefault() and event.stopImmediatePropagation() without pushing new states repeatedly:

const preventBack = (event: any) => {
  event.preventDefault();
+ event.stopImmediatePropagation();
- window.history.pushState(null, document.title, window.location.href);
};

Alternatively, ensure that pushState is not called excessively by checking the history state.


26-26: Remove default export to avoid ambiguity

Having both named and default exports of the same component can cause confusion and import issues.

Proposed Solution:

Remove the default export if it's not necessary:

- export default PreventBackButton;
src/pages/candidate/CandidateDashboard.tsx (2)

47-55: Handle API errors and loading states appropriately

Ensure that error handling provides feedback to the user and that loading states are managed to prevent multiple API calls.

Proposed Solution:

Consider adding a loading state and displaying error messages to the user if the API call fails.


78-79: Pass state safely when navigating

Ensure that sensitive information is not passed via the state object when navigating, as it may not persist across page reloads.

Proposed Solution:

Store necessary data in a more persistent storage like sessionStorage or use URL parameters.

navigate('/candidate/exam/view', {
  state: { id: examId, name: examName, type: examType },
});
src/pages/candidate/ExamSummary.tsx (2)

Line range hint 67-71: Replace mock implementation with actual session check

The checkActiveSession function uses a mock implementation instead of making an actual API call.

Consider implementing the actual session check:

 const checkActiveSession = async () => {
     try {
-        // Mock the response for active session check
-        const response = { data: { active: false } }; // Mocking as no API exists
+        const token = sessionStorage.getItem('accessToken');
+        if (!token) {
+            setSessionExists(false);
+            return;
+        }
+
+        const examId = id;
+        const response = await axios.get(
+            `http://localhost:8080/api/v1/exam/check-session/${examId}`,
+            { headers: { 'Authorization': `Bearer ${token}` } }
+        );
         setSessionExists(response.data.active);
     } catch (error) {
         console.error('Error checking active session:', error);
         setSessionExists(false);
     }
 };

Line range hint 51-55: Address security concerns in API implementation

Several security considerations need to be addressed:

  1. API URLs are hardcoded
  2. No CSRF protection in POST requests
  3. Sensitive exam data stored in sessionStorage could be accessed by other tabs

Consider these security enhancements:

  1. Move API URLs to environment configuration
  2. Add CSRF protection to POST requests
  3. Consider using more secure storage methods for sensitive data

Example implementation:

+ import { API_BASE_URL } from '../config';
+ import { getCsrfToken } from '../utils/security';

- const url = `http://localhost:8080/api/v1/candidate/exams/${id}`;
+ const url = `${API_BASE_URL}/api/v1/candidate/exams/${id}`;

 const response = await axios.post(
     'http://localhost:8080/api/v1/exam/start',
     { examId: id },
-    { headers: { 'Authorization': `Bearer ${token}` } }
+    { 
+      headers: { 
+        'Authorization': `Bearer ${token}`,
+        'X-CSRF-Token': getCsrfToken(),
+      } 
+    }
 );

Also applies to: 104-110

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 28bbdee and cfa2d4a.

📒 Files selected for processing (7)
  • src/components/Exam/PreventBackButton.tsx (1 hunks)
  • src/components/Exam/TimeContainer.tsx (4 hunks)
  • src/components/Exam/index.ts (1 hunks)
  • src/pages/candidate/CandidateDashboard.tsx (2 hunks)
  • src/pages/candidate/ExamMcqResults.tsx (9 hunks)
  • src/pages/candidate/ExamSummary.tsx (1 hunks)
  • src/pages/candidate/ExamView.tsx (2 hunks)
🔇 Additional comments (5)
src/pages/candidate/ExamMcqResults.tsx (1)

Line range hint 342-404: Ensure data is loaded before rendering exam details

There is a risk of examDetails or examResults being undefined, which can cause runtime errors.

Proposed Solution:

Add checks before mapping over examDetails:

- {!loading && examDetails.map((question, index) => {
+ {!loading && examDetails && examResults && examDetails.map((question, index) => {
src/components/Exam/index.ts (1)

8-8: Exported component is correctly added

The PreventBackButton component is properly exported and will be available for use in other parts of the application.

src/components/Exam/TimeContainer.tsx (2)

105-105: Disable submit button appropriately

The submit button is disabled when remainingTimeInSeconds <= 0. Ensure this logic aligns with when the exam should be submitted.

Proposed Solution:

Verify that the button disabling logic matches the exam submission criteria.


68-72: ⚠️ Potential issue

Ensure handleSubmitExam is not called multiple times due to rapid state changes

The useEffect dependency array is missing remainingTimeInSeconds, which may cause handleSubmitExam to be called multiple times.

Proposed Solution:

Include remainingTimeInSeconds in the dependency array:

useEffect(() => {
  if (remainingTimeInSeconds <= 0) {
    handleSubmitExam();
  }
- }, []);
+ }, [remainingTimeInSeconds]);

Likely invalid or redundant comment.

src/pages/candidate/CandidateDashboard.tsx (1)

33-36: ⚠️ Potential issue

Avoid early returns in useEffect without cleanup

Returning early inside a useEffect hook can prevent cleanup functions from running.

Proposed Solution:

Refactor the code to avoid early returns:

if (!userString || !token) {
  console.error("User information or token not found in session storage.");
- return;
+ return;
}

However, since useEffect cleanup functions are not affected here, and there is no cleanup, this might be acceptable. Verify whether early returns are acceptable in this context.

Comment on lines +30 to 32

interface UserAnswer {
questionId: number;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Define a specific interface for gradingScheme instead of using any[]

Using any[] for the gradingScheme state can lead to type safety issues and make the code harder to maintain. Defining a specific interface improves code readability and type safety.

Proposed Solution:

Define an interface for the grading scheme:

interface GradingScheme {
  minMarks: number;
  maxMarks: number;
  grade: string;
}

Update the state declaration:

- const [gradingScheme, setGradingScheme] = useState<any[]>([]);
+ const [gradingScheme, setGradingScheme] = useState<GradingScheme[]>([]);

Comment on lines +53 to +55
console.error("User information or token not found in session storage.");
return;
}
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

Avoid returning early without rendering in a React component

Returning early from a React functional component without rendering can cause unexpected behavior. Instead, consider rendering a fallback UI or redirecting the user.

Proposed Solution:

Use conditional rendering or navigate the user appropriately:

if (!userString || !token) {
  console.error("User information or token not found in session storage.");
- return;
+ navigate('/login');
+ return null;
}

if (!candidateId) {
  console.error("Candidate ID not found in user object.");
- return;
+ navigate('/login');
+ return null;
}

Also applies to: 60-63

Comment on lines +127 to +132
let totalMarks = 0;
let correctAnswersMarks = 0;
let skippedQuestions = 0;

// Assuming each correct answer is worth 10 marks
const marksPerAnswer = 10;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use consistent variables for marks calculation

The value 10 is hardcoded in multiple places for marks calculation. This can cause inconsistencies if the marking scheme changes. Use the marksPerAnswer variable consistently.

Proposed Solution:

Use marksPerAnswer instead of hardcoded values:

// Define marksPerAnswer at the top if not already defined
const marksPerAnswer = 10;

// Update calculations
- let correctAnswersMarks = 0;
+ let correctAnswersMarks = 0;

...

if (selectedOption.optionId === correctOption.optionId) {
  correctAnswersCount += 1;
- correctAnswersMarks += marksPerAnswer;
+ correctAnswersMarks += marksPerAnswer;
}

...

const percentage = totalMarks > 0 ? Math.round((correctAnswersMarks / totalMarks) * 100) : 0;

...

Scored {correctAnswersMarks} out of {totalMarks}

Also applies to: 151-151, 190-190, 291-291

Comment on lines +104 to +105
`http://localhost:8080/api/v1/grade/1/grading-scheme`,
{
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

Avoid hardcoding IDs in API endpoints

The API call uses a hardcoded ID (1) for the grading scheme. This can lead to errors if the grading scheme ID varies. Use a dynamic ID based on the context, such as examId or another relevant identifier.

Proposed Solution:

Replace the hardcoded ID with a dynamic value:

const gradingResponse = await fetch(
-  `http://localhost:8080/api/v1/grade/1/grading-scheme`,
+  `http://localhost:8080/api/v1/grade/${examId}/grading-scheme`,
  {
    method: 'GET',
    headers: {
      Authorization: `Bearer ${token}`,
    },
  }
);

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

Comment on lines +164 to +168
const onFullscreenChange = () => {
if (!document.fullscreenElement) {
console.log('Exited fullscreen');
}
};
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

Enhance fullscreen exit handling for exam integrity

Currently, the code only logs when fullscreen is exited. For exam integrity, consider warning the user or auto-requesting fullscreen again.

Consider this enhancement:

 const onFullscreenChange = () => {
   if (!document.fullscreenElement) {
-    console.log('Exited fullscreen');
+    message.warning('Fullscreen mode is required for the exam. Re-entering fullscreen...');
+    // Give a short delay before re-requesting fullscreen
+    setTimeout(requestFullscreen, 1000);
+    
+    // Optional: Log attempt to exit fullscreen for monitoring
+    const sessionId = sessionStorage.getItem('sessionId');
+    axios.post(
+      'http://localhost:8080/api/v1/exam/log-fullscreen-exit',
+      { sessionId },
+      { headers: { Authorization: `Bearer ${token}` } }
+    ).catch(console.error);
   }
 };

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

Comment on lines +156 to +161
const requestFullscreen = () => {
const elem = document.documentElement;
if (elem.requestFullscreen) {
elem.requestFullscreen();
}
};
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

Enhance fullscreen implementation for better exam security

The current fullscreen implementation has several security gaps:

  1. No handling of browser prefixes for fullscreen API
  2. No error handling for fullscreen request
  3. No prevention or warning when user exits fullscreen mode

Consider this enhanced implementation:

 const requestFullscreen = () => {
   const elem = document.documentElement;
-  if (elem.requestFullscreen) {
+  try {
+    if (elem.requestFullscreen) {
       elem.requestFullscreen();
+    } else if (elem.webkitRequestFullscreen) {
+      elem.webkitRequestFullscreen();
+    } else if (elem.msRequestFullscreen) {
+      elem.msRequestFullscreen();
+    } else if (elem.mozRequestFullscreen) {
+      elem.mozRequestFullscreen();
+    } else {
+      throw new Error('Fullscreen API not supported');
     }
+  } catch (error) {
+    console.error('Failed to enter fullscreen:', error);
+    message.error('Fullscreen mode is required for the exam. Please use a supported browser.');
+  }
 };
📝 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
const requestFullscreen = () => {
const elem = document.documentElement;
if (elem.requestFullscreen) {
elem.requestFullscreen();
}
};
const requestFullscreen = () => {
const elem = document.documentElement;
try {
if (elem.requestFullscreen) {
elem.requestFullscreen();
} else if (elem.webkitRequestFullscreen) {
elem.webkitRequestFullscreen();
} else if (elem.msRequestFullscreen) {
elem.msRequestFullscreen();
} else if (elem.mozRequestFullscreen) {
elem.mozRequestFullscreen();
} else {
throw new Error('Fullscreen API not supported');
}
} catch (error) {
console.error('Failed to enter fullscreen:', error);
message.error('Fullscreen mode is required for the exam. Please use a supported browser.');
}
};

Comment on lines +104 to +110
const sessionId = response.data.sessionId;
console.log('sessionId:', response.data.endTime);
sessionStorage.setItem('sessionId', sessionId);
sessionStorage.setItem('endTime', response.data.endTime);
sessionStorage.setItem('examType', examData?.examType || '');
sessionStorage.setItem('examId', id);
sessionStorage.setItem('examName', examName);
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

Fix incorrect logging and add response validation

There are several issues in the session management code:

  1. Console.log is logging endTime but labeled as sessionId
  2. No validation of response data before storage
  3. Missing error handling for invalid response data

Consider this enhanced implementation:

-    const sessionId = response.data.sessionId;
-    console.log('sessionId:',  response.data.endTime); 
-    sessionStorage.setItem('sessionId', sessionId);
-    sessionStorage.setItem('endTime', response.data.endTime);
-    sessionStorage.setItem('examType', examData?.examType || '');
-    sessionStorage.setItem('examId', id);
-    sessionStorage.setItem('examName', examName);
+    // Validate required response data
+    const { sessionId, endTime } = response.data;
+    if (!sessionId || !endTime) {
+      throw new Error('Invalid response: missing required session data');
+    }
+    
+    try {
+      // Store session data
+      sessionStorage.setItem('sessionId', sessionId);
+      sessionStorage.setItem('endTime', endTime);
+      sessionStorage.setItem('examType', examData?.examType || '');
+      sessionStorage.setItem('examId', id);
+      sessionStorage.setItem('examName', examName);
+    } catch (error) {
+      console.error('Failed to store session data:', error);
+      // Cleanup any partially stored data
+      ['sessionId', 'endTime', 'examType', 'examId', 'examName'].forEach(key => 
+        sessionStorage.removeItem(key)
+      );
+      throw new Error('Failed to store session data');
+    }
📝 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
const sessionId = response.data.sessionId;
console.log('sessionId:', response.data.endTime);
sessionStorage.setItem('sessionId', sessionId);
sessionStorage.setItem('endTime', response.data.endTime);
sessionStorage.setItem('examType', examData?.examType || '');
sessionStorage.setItem('examId', id);
sessionStorage.setItem('examName', examName);
// Validate required response data
const { sessionId, endTime } = response.data;
if (!sessionId || !endTime) {
throw new Error('Invalid response: missing required session data');
}
try {
// Store session data
sessionStorage.setItem('sessionId', sessionId);
sessionStorage.setItem('endTime', endTime);
sessionStorage.setItem('examType', examData?.examType || '');
sessionStorage.setItem('examId', id);
sessionStorage.setItem('examName', examName);
} catch (error) {
console.error('Failed to store session data:', error);
// Cleanup any partially stored data
['sessionId', 'endTime', 'examType', 'examId', 'examName'].forEach(key =>
sessionStorage.removeItem(key)
);
throw new Error('Failed to store session data');
}

@tharindra26 tharindra26 merged commit cea2dc9 into main Dec 4, 2024
1 check failed
@coderabbitai coderabbitai bot mentioned this pull request Dec 4, 2024
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.

3 participants