From 36bf2db0b232432c861eac5446db089417ec1366 Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Wed, 11 Jun 2025 10:33:17 -0700 Subject: [PATCH] fix: add missing meeting.tasks.fetch_meeting_attendance_task --- ietf/meeting/tasks.py | 19 +++++++++++++++++++ ietf/meeting/tests_tasks.py | 30 ++++++++++++++++++++++++++++++ ietf/meeting/tests_utils.py | 16 +++++++++++++++- ietf/meeting/utils.py | 6 ++++++ 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/ietf/meeting/tasks.py b/ietf/meeting/tasks.py index 20bb602682..dc3fbc99ec 100644 --- a/ietf/meeting/tasks.py +++ b/ietf/meeting/tasks.py @@ -10,6 +10,7 @@ from .utils import generate_proceedings_content from .views import generate_agenda_data from .utils import migrate_registrations, check_migrate_registrations +from .utils import fetch_attendance_from_meetings @shared_task @@ -60,3 +61,21 @@ def proceedings_content_refresh_task(*, all=False): elif all or (num % 24 == now.hour): log.log(f"Refreshing proceedings for meeting {meeting.number}...") generate_proceedings_content(meeting, force_refresh=True) + + +@shared_task +def fetch_meeting_attendance_task(): + # fetch most recent two meetings + meetings = Meeting.objects.filter(type="ietf", date__lte=timezone.now()).order_by("-date")[:2] + try: + stats = fetch_attendance_from_meetings(meetings) + except RuntimeError as err: + log.log(f"Error in fetch_meeting_attendance_task: {err}") + else: + for meeting, meeting_stats in zip(meetings, stats): + log.log( + "Fetched data for meeting {:>3}: {:4d} created, {:4d} updated, {:4d} deleted, {:4d} processed".format( + meeting.number, meeting_stats['created'], meeting_stats['updated'], meeting_stats['deleted'], + meeting_stats['processed'] + ) + ) diff --git a/ietf/meeting/tests_tasks.py b/ietf/meeting/tests_tasks.py index c026a99835..66de212899 100644 --- a/ietf/meeting/tests_tasks.py +++ b/ietf/meeting/tests_tasks.py @@ -3,8 +3,10 @@ import datetime from mock import patch, call from ietf.utils.test_utils import TestCase +from ietf.utils.timezone import date_today from .factories import MeetingFactory from .tasks import proceedings_content_refresh_task, agenda_data_refresh +from .tasks import fetch_meeting_attendance_task class TaskTests(TestCase): @@ -49,3 +51,31 @@ def test_proceedings_content_refresh_task(self, mock_generate): with patch("ietf.meeting.tasks.timezone.now", return_value=hour_01_utc): proceedings_content_refresh_task(all=True) self.assertEqual(mock_generate.call_count, 2) + + @patch("ietf.meeting.tasks.fetch_attendance_from_meetings") + def test_fetch_meeting_attendance_task(self, mock_fetch_attendance): + today = date_today() + meetings = [ + MeetingFactory(type_id="ietf", date=today - datetime.timedelta(days=1)), + MeetingFactory(type_id="ietf", date=today - datetime.timedelta(days=2)), + MeetingFactory(type_id="ietf", date=today - datetime.timedelta(days=3)), + ] + data = { + 'created': 1, + 'updated': 2, + 'deleted': 0, + 'processed': 3, + } + + mock_fetch_attendance.return_value = [data, data] + + fetch_meeting_attendance_task() + self.assertEqual(mock_fetch_attendance.call_count, 1) + self.assertCountEqual(mock_fetch_attendance.call_args[0][0], meetings[0:2]) + + # test handling of RuntimeError + mock_fetch_attendance.reset_mock() + mock_fetch_attendance.side_effect = RuntimeError + fetch_meeting_attendance_task() + self.assertTrue(mock_fetch_attendance.called) + # Good enough that we got here without raising an exception diff --git a/ietf/meeting/tests_utils.py b/ietf/meeting/tests_utils.py index 3cb16202c8..8d912158ce 100644 --- a/ietf/meeting/tests_utils.py +++ b/ietf/meeting/tests_utils.py @@ -13,7 +13,7 @@ from ietf.meeting.factories import MeetingFactory, RegistrationFactory, RegistrationTicketFactory from ietf.meeting.models import Registration from ietf.meeting.utils import (migrate_registrations, get_preferred, process_single_registration, - get_registration_data, sync_registration_data) + get_registration_data, sync_registration_data, fetch_attendance_from_meetings) from ietf.nomcom.models import Volunteer from ietf.nomcom.factories import NomComFactory, nomcom_kwargs_for_year from ietf.person.factories import PersonFactory @@ -341,3 +341,17 @@ def test_process_single_registration_cancelled(self): new_reg, action = process_single_registration(reg_data, meeting) self.assertEqual((new_reg, action), (None, 'deleted')) self.assertEqual(meeting.registration_set.count(), 0) + + @patch("ietf.meeting.utils.sync_registration_data") + def test_fetch_attendance_from_meetings(self, mock_sync_reg_data): + mock_meetings = [object(), object(), object()] + d1 = dict(created=1, updated=2, deleted=0, processed=3) + d2 = dict(created=2, updated=2, deleted=0, processed=4) + d3 = dict(created=1, updated=4, deleted=1, processed=5) + mock_sync_reg_data.side_effect = (d1, d2, d3) + stats = fetch_attendance_from_meetings(mock_meetings) + self.assertEqual( + [mock_sync_reg_data.call_args_list[n][0][0] for n in range(3)], + mock_meetings, + ) + self.assertEqual(stats, [d1, d2, d3]) diff --git a/ietf/meeting/utils.py b/ietf/meeting/utils.py index 5e497f35f2..db67f79b93 100644 --- a/ietf/meeting/utils.py +++ b/ietf/meeting/utils.py @@ -1402,11 +1402,13 @@ def sync_registration_data(meeting): 'created': 0, 'updated': 0, 'deleted': 0, + 'processed': 0, } # Process registrations from reg_data reg_emails = set() for email, data in reg_data['objects'].items(): + stats['processed'] += 1 reg_emails.add(email) # Process this registration @@ -1577,3 +1579,7 @@ def process_single_registration(reg_data, meeting): action_taken = 'updated' return registration, action_taken + + +def fetch_attendance_from_meetings(meetings): + return [sync_registration_data(meeting) for meeting in meetings]