Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: sort DatedMeasures within a RequestSnapshot #443

Merged
merged 26 commits into from
Jan 22, 2025
Merged
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a983175
build: release 3.24.0
Nov 20, 2024
37844fd
feat: add request migration script 1.6.0
pl-buiquang Nov 19, 2024
19c20d5
build: release version 3.24.1
pl-buiquang Nov 21, 2024
a40675e
fix: add extra info to logs
Nov 26, 2024
ba81e96
fix(maintenance): message on deletion and prevent sooner ending (#428)
pl-buiquang Dec 3, 2024
c67eaf1
build: set version to 3.24.3
pl-buiquang Dec 3, 2024
2dca8fb
refactor(start)!: add new docker entrypoint options (#427)
pl-buiquang Nov 27, 2024
70d1288
fix(logger): listen 0.0.0.0 for socket logger
pl-buiquang Nov 27, 2024
30e3c55
feat(logging): readd default console logger
pl-buiquang Nov 28, 2024
89abc3b
feat: add configurable socket logger host for gunicorn
pl-buiquang Nov 28, 2024
a0c0f93
feat(auth): cache oidc issuer certs (#433)
pl-buiquang Dec 20, 2024
2c5cd04
fix(start): add more options for Docker entrypoint
Dec 23, 2024
878f0b0
Revert "feat(auth): cache oidc issuer certs (#433)"
Dec 23, 2024
055486a
fix(cache): revert caching OIDC server certs
Dec 23, 2024
64499e3
fix(exports): validate payload when passed a FHIR filter
Dec 23, 2024
b37abc7
fix(DMs): fix decorators
Jan 1, 2025
843500f
build: set version to 3.24.7
Jan 2, 2025
024d83e
fix(DMs): small and diverse fixes
Jan 2, 2025
3cae422
refactor(accesses): add cache and fail fast flow
Jan 6, 2025
4ad115d
test: add missing fields when creating accesses
Jan 7, 2025
e1fca0a
refactor: leverage cache and Django ORM capabilities
Jan 7, 2025
193eb09
fix(accesses): add env var for managers list file
Jan 8, 2025
b214943
refactor: add some debug log for auth
pl-buiquang Jan 16, 2025
35adb39
build: set hotfix version 3.24.11
pl-buiquang Jan 16, 2025
381fc2f
fix(cohort): sort DMs within an RQS
Jan 21, 2025
6156271
fix: merge main into hotfix_3.24.12_sort_dms_within_rqs
Jan 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix(maintenance): message on deletion and prevent sooner ending (#428)
pl-buiquang committed Dec 3, 2024
commit ba81e966ce8e1bb9a4f412aba66f8af283d9dd74
36 changes: 30 additions & 6 deletions admin_cohort/services/maintenance.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import datetime
import logging
from datetime import timedelta
from typing import Union, Optional
@@ -22,6 +23,7 @@


class WSMaintenanceInfo(BaseModel):
id: int
subject: Optional[str]
maintenance_start: str
maintenance_end: str
@@ -36,6 +38,7 @@ class WSMaintenance(WebSocketMessage):

def maintenance_phase_to_info(maintenance: MaintenancePhase) -> WSMaintenanceInfo:
return WSMaintenanceInfo(
id=maintenance.id,
subject=maintenance.subject,
maintenance_start=maintenance.start_datetime.isoformat(),
maintenance_end=maintenance.end_datetime.isoformat(),
@@ -58,20 +61,41 @@ def get_maintenance_with_event():
return event_to_start, event_to_end

@staticmethod
def send_maintenance_notification(maintenance_info: WSMaintenanceInfo):
def send_deleted_maintenance_notification(maintenance_info: MaintenancePhase):
now = timezone.now()
if maintenance_info.end_datetime >= now >= maintenance_info.start_datetime:
deleted_maintenance = maintenance_phase_to_info(maintenance_info)
maintenance_service.send_maintenance_notification(deleted_maintenance, force_active_state=False)

@staticmethod
def send_maintenance_notification(maintenance_info: WSMaintenanceInfo, force_active_state: Optional[bool] = None):
"""
Send a maintenance notification to all clients.
Except if there is a current maintenance active and the message is an end maintenance message.
"""
now = timezone.now()
start_time = dateutil.parser.parse(maintenance_info.maintenance_start)
end_time = dateutil.parser.parse(maintenance_info.maintenance_end)
maintenance_info.active = start_time < now < end_time
maintenance_info.active = force_active_state if force_active_state is not None else start_time < now < end_time
logging.info(f"Sending maintenance notification: {maintenance_info}")
WebsocketManager.send_to_client("__all__", WSMaintenance(type=WebSocketMessageType.MAINTENANCE, info=maintenance_info))
current_maintenances = MaintenancePhase.objects.filter(start_datetime__lte=now, end_datetime__gte=now).order_by('-end_datetime').all()
current_active_maintenances = [cur for cur in
current_maintenances
if cur.id != maintenance_info.id]
if maintenance_info.active or not current_active_maintenances:
WebsocketManager.send_to_client("__all__", WSMaintenance(type=WebSocketMessageType.MAINTENANCE, info=maintenance_info))

@staticmethod
def get_next_maintenance() -> Union[MaintenancePhase, None]:
now = timezone.now()
current = MaintenancePhase.objects.filter(start_datetime__lte=now, end_datetime__gte=now) \
def get_current_maintenance(now: Optional[datetime] = None) -> Optional[MaintenancePhase]:
ref_now = now or timezone.now()
return MaintenancePhase.objects.filter(start_datetime__lte=ref_now, end_datetime__gte=ref_now) \
.order_by('-end_datetime') \
.first()

@staticmethod
def get_next_maintenance() -> Union[MaintenancePhase, None]:
now = timezone.now()
current = MaintenanceService.get_current_maintenance(now)
if current:
return current
next_maintenance = MaintenancePhase.objects.filter(start_datetime__gte=now) \
3 changes: 3 additions & 0 deletions admin_cohort/tasks.py
Original file line number Diff line number Diff line change
@@ -11,6 +11,9 @@ def maintenance_notifier_checker():
maintenance_notifier.s(maintenance_phase_to_info(event).model_dump()).apply_async(eta=event.start_datetime)
for event in event_to_end:
maintenance_notifier.s(maintenance_phase_to_info(event).model_dump()).apply_async(eta=event.end_datetime)
current_maintenance = MaintenanceService.get_current_maintenance()
if current_maintenance:
maintenance_notifier.s(maintenance_phase_to_info(current_maintenance).model_dump()).apply_async()


@shared_task
62 changes: 62 additions & 0 deletions admin_cohort/tests/test_maintenance.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from datetime import timedelta
from unittest.mock import patch, MagicMock

from django.test import TestCase
from django.utils import timezone

from admin_cohort.services.maintenance import MaintenanceService, WSMaintenanceInfo


class TestMaintenanceService(TestCase):
@patch('admin_cohort.services.maintenance.maintenance_phase_to_info')
@patch('admin_cohort.services.maintenance.MaintenanceService.send_maintenance_notification')
def test_send_deleted_maintenance_notification_within_time_range(self, mock_send_notification, mock_phase_to_info):
now = timezone.now()
maintenance_info = MagicMock()
maintenance_info.start_datetime = now - timedelta(minutes=5)
maintenance_info.end_datetime = now + timedelta(minutes=5)
mock_phase_to_info.return_value = WSMaintenanceInfo(id=1, maintenance_start=str(maintenance_info.start_datetime),
maintenance_end=str(maintenance_info.end_datetime), active=True, type='test',
subject='test', message='test')

MaintenanceService.send_deleted_maintenance_notification(maintenance_info)
mock_send_notification.assert_called_once()

@patch('admin_cohort.services.maintenance.maintenance_phase_to_info')
@patch('admin_cohort.services.maintenance.MaintenanceService.send_maintenance_notification')
def test_send_deleted_maintenance_notification_outside_time_range(self, mock_send_notification, mock_phase_to_info):
now = timezone.now()
maintenance_info = MagicMock()
maintenance_info.start_datetime = now - timedelta(minutes=10)
maintenance_info.end_datetime = now - timedelta(minutes=5)

MaintenanceService.send_deleted_maintenance_notification(maintenance_info)
mock_send_notification.assert_not_called()

@patch('admin_cohort.services.maintenance.dateutil.parser.parse')
@patch('admin_cohort.services.maintenance.MaintenancePhase.objects.filter')
@patch('admin_cohort.services.maintenance.WebsocketManager.send_to_client')
def test_send_maintenance_notification_active(self, mock_send_to_client, mock_filter, mock_parse):
now = timezone.now()
maintenance_info = WSMaintenanceInfo(id=1, maintenance_start=str(now - timedelta(minutes=5)),
maintenance_end=str(now + timedelta(minutes=5)), active=True, type='test',
subject='test', message='test')
mock_parse.side_effect = [now - timedelta(minutes=5), now + timedelta(minutes=5)]
mock_filter.return_value.exclude.return_value = []

MaintenanceService.send_maintenance_notification(maintenance_info)
mock_send_to_client.assert_called_once()

@patch('admin_cohort.services.maintenance.dateutil.parser.parse')
@patch('admin_cohort.services.maintenance.MaintenancePhase.objects.filter')
@patch('admin_cohort.services.maintenance.WebsocketManager.send_to_client')
def test_send_maintenance_notification_inactive_with_current_active(self, mock_send_to_client, mock_filter, mock_parse):
now = timezone.now()
maintenance_info = WSMaintenanceInfo(id=1, maintenance_start=str(now - timedelta(minutes=10)),
maintenance_end=str(now - timedelta(minutes=5)), active=False, type='test',
subject='test', message='test')
mock_parse.side_effect = [now - timedelta(minutes=10), now - timedelta(minutes=5)]
mock_filter.return_value.order_by.return_value.all.return_value = [MagicMock()]

MaintenanceService.send_maintenance_notification(maintenance_info)
mock_send_to_client.assert_not_called()
4 changes: 4 additions & 0 deletions admin_cohort/views/maintenance_phase.py
Original file line number Diff line number Diff line change
@@ -37,3 +37,7 @@ def next(self, request, *args, **kwargs):
q = maintenance_service.get_next_maintenance()
d = self.get_serializer(q).data if q is not None else {}
return Response(d)

def destroy(self, request, *args, **kwargs):
maintenance_service.send_deleted_maintenance_notification(self.get_object())
return super().destroy(request, *args, **kwargs)