Skip to content

Ahtisham/add ora reminder notification#2389

Open
AhtishamShahid wants to merge 10 commits into
openedx:masterfrom
AhtishamShahid:ahtisham/add_ora_reminder_notification
Open

Ahtisham/add ora reminder notification#2389
AhtishamShahid wants to merge 10 commits into
openedx:masterfrom
AhtishamShahid:ahtisham/add_ora_reminder_notification

Conversation

@AhtishamShahid
Copy link
Copy Markdown
Contributor

This pull request introduces the ORA Reminder feature, which automatically sends periodic notifications to learners who have submitted an Open Response Assessment (ORA) but have not yet completed required peer or self review steps. The implementation involves a new database model, a Celery sweeper task, and integration with the submission flow to create and manage reminders. Documentation and configuration options are also provided.

Key changes include:

Feature Implementation: ORA Reminders

  • Added a new ORAReminder model to persist reminder schedules for learners, including fields for user, course, submission, deadlines, and reminder state. This enables efficient tracking and querying of pending reminders without frequent modulestore lookups.
  • Integrated reminder creation into the submission process by adding _handle_post_submission_notifications in submissions_actions.py, which creates a reminder entry and ensures the sweeper task is running after a qualifying submission.
  • Registered and imported the sweep_ora_reminders Celery task so it is autodiscovered and can process due reminders on a platform-wide schedule.

Documentation and Configuration

  • Added comprehensive documentation in docs/ora_reminders.rst, describing how the reminder system works, configuration options, and operational details for durability and error handling.

Supporting Changes

  • Bumped the package version to 7.1.0 to reflect the addition of this new feature.

Copilot AI review requested due to automatic review settings April 9, 2026 05:57
Copy link
Copy Markdown

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

Adds an ORA Reminder feature to automatically send periodic notifications to learners who have submitted an ORA but still need to complete peer/self review steps, using a persisted reminder schedule and a self-chaining Celery sweeper.

Changes:

  • Added ORAReminder model + migration to persist reminder schedules and sweep efficiently without modulestore lookups.
  • Introduced sweep_ora_reminders Celery task and submission-flow hook to create reminders and ensure the sweeper is running.
  • Added configuration defaults and documentation for enabling/tuning ORA reminders.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
settings/base.py Adds ORA reminder feature flag and default tuning knobs (intervals, batch size, etc.).
openassessment/xblock/utils/ora_reminders.py Implements reminder creation, sweeper task, per-reminder processing, and notification dispatch.
openassessment/xblock/test/test_ora_reminders.py Adds unit tests covering reminder creation, sweeping, and submission-flow orchestration.
openassessment/xblock/test/test_notifications.py Extends notification util tests to cover a new “submission created” notification path.
openassessment/xblock/apis/submissions/submissions_actions.py Hooks reminder creation into the submission flow and starts the sweeper chain.
openassessment/workflow/tasks.py Imports the sweeper task to ensure Celery autodiscovery registers it.
openassessment/workflow/models.py Adds the ORAReminder model to the workflow app.
openassessment/workflow/migrations/0007_orareminder.py Creates the ORAReminder table and sweep index.
openassessment/init.py Bumps package version to 7.1.0.
docs/ora_reminders.rst Documents the feature, configuration, and durability/operations details.

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

reminder.reminder_sent_count += 1
if reminder.reminder_sent_count >= max_count:
reminder.is_active = False
reminder.next_reminder_at = None
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

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

In the "final reminder" path, next_reminder_at is set to None before saving. The ORAReminder.next_reminder_at field is defined as non-nullable in both the model and migration, so this will raise an IntegrityError on save and prevent deactivation after the last reminder. Either keep a non-null timestamp when deactivating or make the field nullable (and update the migration accordingly).

Suggested change
reminder.next_reminder_at = None
reminder.next_reminder_at = now

Copilot uses AI. Check for mistakes.
Comment thread openassessment/xblock/utils/ora_reminders.py
Comment thread openassessment/xblock/test/test_notifications.py Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 13, 2026

Codecov Report

❌ Patch coverage is 99.01575% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 95.43%. Comparing base (ed2bb54) to head (9ed46c8).

Files with missing lines Patch % Lines
openassessment/workflow/tasks.py 50.00% 4 Missing ⚠️
openassessment/xblock/test/test_ora_reminders.py 99.46% 1 Missing and 3 partials ⚠️
openassessment/xblock/utils/ora_reminders.py 98.85% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2389      +/-   ##
==========================================
+ Coverage   95.26%   95.43%   +0.17%     
==========================================
  Files         196      198       +2     
  Lines       21679    22694    +1015     
  Branches     1509     1546      +37     
==========================================
+ Hits        20653    21659    +1006     
- Misses        776      781       +5     
- Partials      250      254       +4     
Flag Coverage Δ
unittests 95.43% <99.01%> (+0.17%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread docs/ora_reminders.rst

The notification type ``ora_reminder`` must also be registered in edx-platform's
``openedx.core.djangoapps.notifications.base_notification`` (already included
in edx-platform ≥ the version that ships this feature).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there already a corresponding PR for this or will you be making one, once this is released?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here is PR for edx-platfrom . openedx/openedx-platform#38298

Comment thread docs/ora_reminders.rst Outdated
Comment on lines +48 to +55
* - ``ORA_REMINDER_SWEEP_INTERVAL_SECONDS``
- ``1800``
- How often (in seconds) the sweeper re-schedules itself. Each run
processes all rows whose ``next_reminder_at`` has passed.
* - ``ORA_REMINDER_SWEEP_BATCH_SIZE``
- ``1000``
- Maximum rows processed per sweep cycle. If more rows are due they
will be picked up on the next sweep.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this mean that if there are more than 48,000 reminders that need to be sent, that the ones above 48,000 won't be sent in time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

batch is ordered oldest-first so nothing gets permanently skipped. Also, added a note that ORA_REMINDER_SWEEP_BATCH_SIZE can be increased for high-volume deployments

Comment thread openassessment/workflow/models.py Outdated
"""
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="ora_reminders", on_delete=models.CASCADE)
course_id = models.CharField(max_length=255, db_index=True)
ora_usage_key = models.CharField(max_length=255)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use a UsageKey model field instead: Use a usage key model field instead: https://github.com/openedx/opaque-keys/blob/master/opaque_keys/edx/django/models.py#L263

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated.

Comment thread openassessment/workflow/models.py Outdated
Comment on lines +1130 to +1131
next_reminder_at = models.DateTimeField(db_index=True, null=True, blank=True)
is_active = models.BooleanField(default=True, db_index=True)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

When you instantiate this object, it will be null by default, and it will have is_active set to True those seem like two contradictory settings. Does it make sense to set is_active to False by default? or set auto_now_add to True on the reminder date?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

changed is_active default to False. create_ora_reminder always sets it explicitly to True so runtime behavior is unchanged, but the schema is consistent now

Comment on lines +16 to +28
def on_worker_ready(sender, **kwargs): # pylint: disable=unused-argument
"""
Start the ORA reminder sweep chain when a Celery worker comes online.

Clears the sweep lock first because pending countdown tasks from a
previous worker process are lost on restart. In a multi-worker setup
``cache.add`` inside ``ensure_sweep_chain_running`` still prevents
duplicate chains — only the first worker to re-acquire the lock wins.
"""
from django.core.cache import cache
from openassessment.xblock.utils.ora_reminders import ensure_sweep_chain_running, SWEEP_LOCK_KEY
cache.delete(SWEEP_LOCK_KEY)
ensure_sweep_chain_running()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Won't this break if more than one worker is started? If Worker A is started up and starts running the sweep. Then later an operator needs more workers and starts up worker B, won't worker B clobber the cache key and start running the sweep?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

on worker restart the old chain's countdown tasks are lost but the lock might still be alive. deleting first + using cache.add (atomic) means only one worker wins the lock and starts a single chain. added a comment explaining this in the code

Comment on lines +123 to +124
lms_root_url = getattr(settings, 'LMS_ROOT_URL', '')
content_url = f"{lms_root_url}/courses/{course_id_str}/jump_to/{item_id}"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why use an LMS root URL at all, why not use the relative URL instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

when user clicks on the notification user is redirected to that item, and the notification system is built in a way that would not be able to construct url dynamically

reminder.id, reminder.user_id, reminder.ora_usage_key,
)

if processed:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should the sweep lock be cleared at this point?

@AhtishamShahid AhtishamShahid force-pushed the ahtisham/add_ora_reminder_notification branch from a3fbad0 to 3cdcad2 Compare April 28, 2026 17:56
feat: add reminder_sent_count field to ORAReminder

Adds a PositiveIntegerField with default=0 to track how many notifications
have actually been sent per ORAReminder row, as a prerequisite for replacing
the time-window guard with a per-send counter in the sweeper logic.

fix: replace time-window Guard 1 with reminder_sent_count check

Peer-unavailable deferrals (Guard 5) never increment reminder_sent_count,
so Guard 1 was firing and deactivating rows before any notification was sent
when peer submissions were slow to appear. Replacing the time-window check
with a count check ensures the budget is only consumed by real sends.

fix: restore bridge reminder_window_end, update process_single_reminder docstring

fix: increment reminder_sent_count on send, deactivate when count reaches max

Replace time-window-based advance-schedule logic with count-based termination:
increment reminder_sent_count after each send and deactivate the row when the
count reaches max_count. Remove the temporary bridge variables (window_base,
reminder_window_end, initial_delay_hours) that kept the old block working
during Task 2. Add reminder_sent_count to update_fields so the DB write is
not silently skipped.

docs: update termination condition docs to count-based (was time-window)

fix: reset reminder_sent_count to 0 on workflow step transition

test: assert ensure_sweep_chain_running not called on same-step no-op

test: add regression test for peer-availability window exhaustion bug
@AhtishamShahid AhtishamShahid force-pushed the ahtisham/add_ora_reminder_notification branch from 3cdcad2 to 7083a1d Compare April 28, 2026 18:20
@awais-ansari awais-ansari requested a review from feanil May 4, 2026 06:52
Comment thread docs/ora_reminders.rst
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