Skip to content

Reduce calendar realtime message spam#619

Merged
jon-bell merged 4 commits intostagingfrom
reduce-calendar-rt-spam
Mar 1, 2026
Merged

Reduce calendar realtime message spam#619
jon-bell merged 4 commits intostagingfrom
reduce-calendar-rt-spam

Conversation

@jon-bell
Copy link
Contributor

@jon-bell jon-bell commented Feb 24, 2026

Summary by CodeRabbit

  • Improvements
    • Calendar sync now deduplicates and normalizes events (trimmed titles/descriptions, consistent locations), reducing duplicate or whitespace-induced updates.
    • Change detection now only broadcasts real calendar changes, cutting unnecessary updates and writes.
    • Enrollment (SIS) sync avoids churn, better handles missing/expired entries, and provides more accurate roster updates and metrics.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

Walkthrough

Adds two new SQL functions for safer calendar broadcasting and SIS enrollment sync with NULL-safe change detection and guarded DML; and enhances the calendar-sync TypeScript to normalize, trim, and deduplicate ICS events to avoid false-change churn.

Changes

Cohort / File(s) Summary
Calendar SQL functions
supabase/migrations/20260224140000_calendar_events_broadcast_real_change_filter.sql
Adds public.sync_calendar_events(...) and public.sis_sync_enrollment(...). Implements NULL-safe comparisons (IS DISTINCT FROM), builds add/update/delete lists, performs guarded inserts/updates/deletes, uses temp tables for roster processing, adds admin/instructor authorization checks, and returns detailed change/metric tracking.
Calendar sync client logic
supabase/functions/calendar-sync/index.ts
Adds countNonNullFields and deduplication by UID (keep event with more non-null fields), trims/normalizes title, description, location, and organizer, refines recurrence handling (filter generated occurrences overridden by RECURRENCE-ID), and logs duplicate removals. No public API signature change.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant CalendarSync as Calendar Sync (TS)
    participant DB as Postgres (sync_calendar_events)
    participant Broadcaster as Broadcast/Realtime

    Client->>CalendarSync: Upload/parse ICS -> list of ParsedEvents
    CalendarSync->>CalendarSync: Normalize & deduplicate by UID (countNonNullFields)
    CalendarSync->>DB: call sync_calendar_events(p_class_id, calendar_type, parsed_events, has_discord)
    DB->>DB: compute adds/updates/deletes using IS DISTINCT FROM
    DB->>Broadcaster: emit events for adds/updates/deletes (guarded)
    DB-->>CalendarSync: return result JSON (added/updated/deleted/skipped/errors)
    CalendarSync-->>Client: return sync result
Loading
sequenceDiagram
    participant Admin
    participant DB as Postgres (sis_sync_enrollment)
    participant Invitations as Invitations/Enrollments
    participant Emails as Notification/Email

    Admin->>DB: call sis_sync_enrollment(p_class_id, roster_data, sync_options)
    DB->>DB: authorize admin/instructor, create temp tables, resolve sections
    DB->>Invitations: upsert/update enrollments & invitations (conditional)
    DB->>Emails: trigger invitation/reactivation notifications as needed
    DB-->>Admin: return detailed change/metric JSON
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

New SQL rites and cleaner ICS art,
Changes that spare unchanged parts,
Events deduped, rosters made right,
Fewer writes, quieter night,
Deploy and watch the sync take flight. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Reduce calendar realtime message spam' directly and concisely describes the main objective of the PR, which is to minimize unnecessary broadcasts and writes through deduplication, NULL-safe comparisons, and conditional updates in calendar event synchronization.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch reduce-calendar-rt-spam

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 24, 2026
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
supabase/functions/calendar-sync/index.ts (1)

835-858: ⚠️ Potential issue | 🟠 Major

Avoid date-only keys when overriding occurrences.
Line 837–847 collapses by YYYY-MM-DD, which can drop legitimate same‑day occurrences if multiple instances exist on a single date. Use the full datetime token to match the UID format used in generated occurrences.

✅ Proposed fix (align keys with full datetime UID)
-    for (const mod of modifiedOccurrences) {
-      const baseUid = mod.uid;
-      const dateKey = mod.recurrenceId!.toISOString().split("T")[0];
-      modifiedDates.set(`${baseUid}_${dateKey}`, mod);
-    }
+    for (const mod of modifiedOccurrences) {
+      const baseUid = mod.uid;
+      const dateStr = mod.recurrenceId!.toISOString().replace(/[-:]/g, "").split(".")[0];
+      modifiedDates.set(`${baseUid}_${dateStr}`, mod);
+    }

     const filteredEvents = expandedEvents.filter((event) => {
       if (event.recurrenceId) return true; // Keep modified occurrences
-      const dateKey = event.dtstart.toISOString().split("T")[0];
+      const dateKey = event.dtstart.toISOString().replace(/[-:]/g, "").split(".")[0];
       const originalUid = getBaseUid(event.uid);
       const modKey = `${originalUid}_${dateKey}`;
       return !modifiedDates.has(modKey);
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/calendar-sync/index.ts` around lines 835 - 858, The code
is using date-only keys (YYYY-MM-DD) when building modifiedDates and when
checking modKey, which collapses multiple same-day occurrences; change the
keying to use the full datetime token that matches generated UIDs (the same
format used later for uid suffixes). Specifically, when iterating
modifiedOccurrences build the key using the full recurrence datetime string
(e.g., recurrenceId.toISOString().replace(/[-:]/g,"").split(".")[0]) instead of
dateKey, and when computing modKey for expandedEvents use getBaseUid(event.uid)
+ "_" + the same full-datetime token derived from event.dtstart; keep the
existing normalization that appends `_${dateStr}` in the final map step so keys
and UIDs are consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@supabase/functions/calendar-sync/index.ts`:
- Around line 998-1020: The file fails Prettier formatting around the
deduplication block; run the project's Prettier/formatter and reformat this file
so it passes CI. Open the section containing parsedEventsRaw, eventsByUid,
countNonNullFields and parsedEvents, fix the long log line and any
trailing/leading whitespace or indentation issues, then save and commit the
reformatted file so the pipeline no longer reports a formatting error.

---

Outside diff comments:
In `@supabase/functions/calendar-sync/index.ts`:
- Around line 835-858: The code is using date-only keys (YYYY-MM-DD) when
building modifiedDates and when checking modKey, which collapses multiple
same-day occurrences; change the keying to use the full datetime token that
matches generated UIDs (the same format used later for uid suffixes).
Specifically, when iterating modifiedOccurrences build the key using the full
recurrence datetime string (e.g.,
recurrenceId.toISOString().replace(/[-:]/g,"").split(".")[0]) instead of
dateKey, and when computing modKey for expandedEvents use getBaseUid(event.uid)
+ "_" + the same full-datetime token derived from event.dtstart; keep the
existing normalization that appends `_${dateStr}` in the final map step so keys
and UIDs are consistent.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b03735 and 071fc93.

📒 Files selected for processing (1)
  • supabase/functions/calendar-sync/index.ts

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.

🧹 Nitpick comments (1)
supabase/functions/calendar-sync/index.ts (1)

49-60: Minor: Scoring required fields adds no discriminative value.

start_time and end_time are always present (non-optional strings from toISOString()), so including them in the score adds a constant +2 to every event. The relative comparison still works correctly, but removing them would make the intent clearer—only optional fields actually differentiate events.

✨ Optional: Score only optional fields
 function countNonNullFields(event: ParsedEvent): number {
   let score = 0;
   if (event.title) score++;
   if (event.description) score++;
   if (event.location) score++;
   if (event.queue_name) score++;
   if (event.organizer_name) score++;
-  if (event.start_time) score++;
-  if (event.end_time) score++;
   return score;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@supabase/functions/calendar-sync/index.ts` around lines 49 - 60, The
countNonNullFields function currently includes start_time and end_time which are
always present and thus add constant value; update countNonNullFields to only
count optional/differentiating fields (remove start_time and end_time from the
checks) so the score reflects only optional fields like title, description,
location, queue_name, and organizer_name; keep the function name
countNonNullFields and same return type, simply delete or stop evaluating
event.start_time and event.end_time so deduplication scoring is more meaningful.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@supabase/functions/calendar-sync/index.ts`:
- Around line 49-60: The countNonNullFields function currently includes
start_time and end_time which are always present and thus add constant value;
update countNonNullFields to only count optional/differentiating fields (remove
start_time and end_time from the checks) so the score reflects only optional
fields like title, description, location, queue_name, and organizer_name; keep
the function name countNonNullFields and same return type, simply delete or stop
evaluating event.start_time and event.end_time so deduplication scoring is more
meaningful.

ℹ️ Review info

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 071fc93 and 3bc5325.

📒 Files selected for processing (1)
  • supabase/functions/calendar-sync/index.ts

@jon-bell jon-bell merged commit c84100c into staging Mar 1, 2026
7 of 8 checks passed
@jon-bell jon-bell deleted the reduce-calendar-rt-spam branch March 1, 2026 01:00
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.

1 participant