Skip to content

Commit 296a5e5

Browse files
committed
fix: three calendar sync bugs
- Fall back to meeting time dates when enrolled terms lack end_date (e.g. Fall 2026 before finals are scheduled), so ICS feed holidays and university events are no longer silently dropped - Switch non-holiday university events in ICS feed to date-range query instead of term_id filter, matching the holiday fix from ce8d29b - Assign missing registration_event local var in detect_term_dates; undefined reference was raising NoMethodError on every nightly sync, silently caught, so term dates were never auto-populated - Fix cancel_events to also destroy merged records ("merged:A+N") when a constituent root UID arrives as CANCELLED from 25live
1 parent ce8d29b commit 296a5e5

3 files changed

Lines changed: 35 additions & 8 deletions

File tree

app/controllers/calendars_controller.rb

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,8 @@ def add_university_events(cal)
182182
enrolled_term_ids = @courses.map(&:term_id).compact.uniq
183183
return if enrolled_term_ids.empty?
184184

185-
terms = Term.where(id: enrolled_term_ids).where.not(start_date: nil).where.not(end_date: nil)
186-
if terms.any?
187-
min_date = terms.minimum(:start_date)
188-
max_date = terms.maximum(:end_date)
185+
min_date, max_date = holiday_date_range_for_terms(enrolled_term_ids)
186+
if min_date && max_date
189187
UniversityCalendarEvent.holidays.in_date_range(min_date, max_date).find_each do |event|
190188
add_university_event_to_calendar(cal, event, force_all_day: true)
191189
end
@@ -197,11 +195,28 @@ def add_university_events(cal)
197195
categories = (user_config.university_event_categories || []) - [ "holiday" ]
198196
return if categories.empty?
199197

200-
UniversityCalendarEvent.by_categories(categories).where(term_id: enrolled_term_ids).find_each do |event|
201-
add_university_event_to_calendar(cal, event)
198+
if min_date && max_date
199+
UniversityCalendarEvent.by_categories(categories).in_date_range(min_date, max_date).find_each do |event|
200+
add_university_event_to_calendar(cal, event)
201+
end
202202
end
203203
end
204204

205+
# Returns [min_date, max_date] for the holiday query range.
206+
# Prefers term start/end dates; falls back to meeting time dates when terms
207+
# lack end_date (e.g. Fall 2026 before finals are scheduled).
208+
def holiday_date_range_for_terms(enrolled_term_ids)
209+
terms = Term.where(id: enrolled_term_ids).where.not(start_date: nil).where.not(end_date: nil)
210+
if terms.any?
211+
return [ terms.minimum(:start_date), terms.maximum(:end_date) ]
212+
end
213+
214+
meeting_times = @courses.flat_map { |c| c.meeting_times.to_a }
215+
min = meeting_times.filter_map(&:start_date).min
216+
max = meeting_times.filter_map(&:end_date).max
217+
[ min, max ]
218+
end
219+
205220
def add_university_event_to_calendar(cal, event, force_all_day: false)
206221
cal.event do |e|
207222
is_all_day = force_all_day || event.all_day || event.category == "holiday"

app/models/university_calendar_event.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ def self.detect_term_dates(year, season)
102102
.select { |event| schedule_available_summary?(event.summary) }
103103
.min_by(&:start_time)
104104

105+
registration_event = matching_events
106+
.select { |event| registration_summary?(event.summary) }
107+
.min_by(&:start_time)
105108
estimated_schedule_available_date = registration_event&.start_time&.to_date&.-(14)
106109

107110
finals_period_event = matching_events

app/services/university_calendar_ics_service.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,12 +105,21 @@ def cancel_events(uids, stats)
105105

106106
uids.each do |uid|
107107
event = @existing_events_by_uid&.[](uid)
108+
109+
# If no direct match, check whether this UID is the root of a merged record
110+
# (stored as "merged:<uid>+<count>"). Non-root constituent UIDs of a merged
111+
# event are not stored, so only the root cancellation triggers cleanup here.
112+
if event.nil? && @existing_events_by_uid
113+
merged_prefix = "merged:#{uid}+"
114+
_key, event = @existing_events_by_uid.find { |k, _v| k.start_with?(merged_prefix) }
115+
end
116+
108117
next unless event
109118

110-
Rails.logger.info("Removing CANCELLED ICS event: #{event.summary} (#{uid})")
119+
Rails.logger.info("Removing CANCELLED ICS event: #{event.summary} (#{uid} -> #{event.ics_uid})")
111120
stats[:changed_categories] << event.category if event.category.present?
112121
event.destroy
113-
@existing_events_by_uid.delete(uid)
122+
@existing_events_by_uid.delete(event.ics_uid)
114123
stats[:cancelled] += 1
115124
end
116125
end

0 commit comments

Comments
 (0)