Skip to content

Conversation

@iZUMi-kyouka
Copy link

@iZUMi-kyouka iZUMi-kyouka commented Mar 4, 2025

Description

  • Added exam mode functionality in the backend by adding enable_exam_mode and is_official_course in the courses table.
  • Added get_exam_mode_course() function in lib/cadet/accounts/course_registrations.ex
  • Modified lib/cadet_web/controllers/user_controller.ex logic in serving the last_viewed_course to always serve the official institution course under exam_mode if any of the user's official course is currently under exam mode. Performed the same modification for get_latest_viewed() function in the same file.
  • Added the required migrations file to alter the database
  • Added the 2 additional fields in the model and wherever necessary

01/04/25

  • Added resume_code to courses table
  • Added isPaused to users table
  • Added some tests for enable_exam_mode, is_official_course, and resume_code for the changesets

09/04/25

  • Added user_browser_focus_log table in the DB
  • Added the required endpoint to log when a user's browser loses focus and regains focus when under exam mode

Note: this may require change to the DB diagram in README.md

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update
  • Code quality improvements

Checklist

  • I have tested this code
  • I have updated the documentation

@iZUMi-kyouka iZUMi-kyouka requested a review from RichDom2185 March 4, 2025 15:17
@iZUMi-kyouka iZUMi-kyouka self-assigned this Mar 4, 2025
@iZUMi-kyouka iZUMi-kyouka marked this pull request as ready for review March 4, 2025 15:29
Copy link
Member

@RichDom2185 RichDom2185 left a comment

Choose a reason for hiding this comment

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

We don't need isOfficialCourse anymore, right?

Copy link
Member

@RichDom2185 RichDom2185 left a comment

Choose a reason for hiding this comment

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

Please fix the failing tests, thanks!

@coveralls
Copy link

coveralls commented Mar 31, 2025

Coverage Status

coverage: 88.77% (-0.9%) from 89.636%
when pulling f1ed0bc on iZUMi-kyouka:exam_mode
into 40cd12a on source-academy:master.

field(:provider, :string)
field(:super_admin, :boolean)
field(:email, :string)
field(:is_paused, :boolean)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a reason why this is linked to the user and not their course_registration? Isn't exam mode related to their course.

Copy link
Author

Choose a reason for hiding this comment

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

Initially I thought linking this to the user would allow for the enforcing of the pause beyond the course so that if a user is paused due to opening other app / using dev tool (which is our plan), the user will have to settle the problem with the course admin / coordinator. But, now that you pointed out, maybe this should not affect the user through all their courses. Should I move this course_registration?

Copy link
Contributor

Choose a reason for hiding this comment

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

@RichDom2185 thoughts?

@GabrielCWT
Copy link
Contributor

One more thing which just came to mind. Correct me if I am wrong, your current implementation places all users with a course which has exam mode enabled have their SA placed on some sort of "lock down". Wouldn't this limit the use for admins/staff during the time in which the course is on lock down?

@iZUMi-kyouka
Copy link
Author

iZUMi-kyouka commented Apr 3, 2025

One more thing which just came to mind. Correct me if I am wrong, your current implementation places all users with a course which has exam mode enabled have their SA placed on some sort of "lock down". Wouldn't this limit the use for admins/staff during the time in which the course is on lock down?

@GabrielCWT
Yes this is correct as of now. I will work on excluding them, so that staffs and admins work as usual, thanks!

iZUMi-kyouka and others added 20 commits April 3, 2025 19:00
…o separate private functions for readability.
…_controller; fixed a typing error in send_resp
…es returned; improved readibility for pause_user"

This reverts commit 31bbf5a.
…changeset, and applied it in the focus logging controlelr
…ation on creation of new course; rejects all course config update with empty resume code regardless of exam mode state
@RichDom2185 RichDom2185 requested a review from Copilot October 28, 2025 11:59
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements exam mode functionality for courses, including backend support for enabling exam mode, tracking browser focus during exams, and pausing/unpausing users with resume codes.

Key Changes:

  • Added three new course fields: enable_exam_mode, is_official_course, and resume_code to control exam behavior and restrict exam mode to official courses
  • Added is_paused field to users and user_browser_focus_log table to track student browser activity during exams
  • Modified user controller logic to prioritize exam mode courses when determining the latest viewed course

Reviewed Changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/cadet/courses/course.ex Added exam mode fields and validation logic ensuring exam mode only enabled for official courses and resume codes are non-empty
lib/cadet/accounts/course_registrations.ex Added query function to retrieve exam mode courses for a user
lib/cadet_web/controllers/user_controller.ex Modified course selection logic to prioritize exam mode courses and added endpoints for pausing users and logging focus changes
lib/cadet_web/controllers/courses_controller.ex Added resume code verification and user unpause functionality, plus admin-only config endpoint differentiation
lib/cadet/focus_logs/* New module for managing browser focus logs during exams
priv/repo/migrations/* Database migrations for new exam-related fields and focus log table
test/* Updated tests and factories to include new fields

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


defp set_default_resume_code(params) do
params
|> Map.put(:resume_code, Integer.to_string(:rand.uniform(9000) + 999))
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

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

The random resume code generation will produce values between 1000-9999, but the arithmetic :rand.uniform(9000) + 999 is incorrect. It should be :rand.uniform(9000) + 1000 to generate codes from 1000 to 9999, or the intent may be a 4-digit code which should use :rand.uniform(9000) + 1000.

Copilot uses AI. Check for mistakes.
end

if focus_state do
case Cadet.FocusLogs.insert_log(user.id, course_id, state) do
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

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

The state parameter is passed to insert_log, but earlier in the function it was already parsed into focus_state (lines 168-173). The function should pass focus_state instead of the original state string.

Suggested change
case Cadet.FocusLogs.insert_log(user.id, course_id, state) do
case Cadet.FocusLogs.insert_log(user.id, course_id, focus_state) do

Copilot uses AI. Check for mistakes.

render(conn, "config.json", config: config)
if conn.assigns.course_reg.role == :admin do
render(conn, "config_admin.json", config: config)
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

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

The config endpoint now exposes resume_code to admin users via the config_admin.json view. This could be a security concern as the resume code is meant to be a secret that students need to enter. Consider whether admins should receive this sensitive information through this endpoint.

Suggested change
render(conn, "config_admin.json", config: config)
# Remove resume_code before rendering for admins
config_admin = Map.delete(config, :resume_code)
render(conn, "config_admin.json", config: config_admin)

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +93
has_resume_code = params |> Map.has_key?(:resume_code)

resume_code_params =
params
|> Map.get(:resume_code, "")
|> String.trim()

resume_code =
changeset
|> get_field(:resume_code)

enable_exam_mode = Map.get(params, :enable_exam_mode, false)
is_official_course = get_field(changeset, :is_official_course, false)

case {enable_exam_mode, is_official_course, has_resume_code, resume_code_params} do
{_, _, true, ""} ->
add_error(
changeset,
:resume_code,
"Resume code must not be empty."
)

{true, false, _, _} ->
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

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

The validation checks has_resume_code separately from the actual value. This creates complexity because an empty string resume_code in params would set has_resume_code to true but resume_code_params to empty string after trim. Consider simplifying by checking the trimmed value directly rather than checking key existence separately.

Suggested change
has_resume_code = params |> Map.has_key?(:resume_code)
resume_code_params =
params
|> Map.get(:resume_code, "")
|> String.trim()
resume_code =
changeset
|> get_field(:resume_code)
enable_exam_mode = Map.get(params, :enable_exam_mode, false)
is_official_course = get_field(changeset, :is_official_course, false)
case {enable_exam_mode, is_official_course, has_resume_code, resume_code_params} do
{_, _, true, ""} ->
add_error(
changeset,
:resume_code,
"Resume code must not be empty."
)
{true, false, _, _} ->
resume_code_params =
params
|> Map.get(:resume_code, "")
|> String.trim()
enable_exam_mode = Map.get(params, :enable_exam_mode, false)
is_official_course = get_field(changeset, :is_official_course, false)
case {enable_exam_mode, is_official_course, resume_code_params} do
{_, _, ""} ->
add_error(
changeset,
:resume_code,
"Resume code must not be empty."
)
{true, false, _} ->

Copilot uses AI. Check for mistakes.
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.

4 participants