Skip to content

Commit

Permalink
[more-resend]
Browse files Browse the repository at this point in the history
  • Loading branch information
biblicabeebli committed Feb 18, 2025
1 parent 6e00492 commit 20d70f7
Show file tree
Hide file tree
Showing 4 changed files with 284 additions and 112 deletions.
45 changes: 25 additions & 20 deletions libs/utils/participant_app_version_comparison.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from collections.abc import Callable
from operator import ge as gte, gt, le as lte, lt

from constants.message_strings import (ERR_ANDROID_REFERENCE_VERSION_CODE_DIGITS,
ERR_ANDROID_TARGET_VERSION_DIGITS, ERR_IOS_REFERENCE_VERSION_NAME_FORMAT,
ERR_IOS_REFERENCE_VERSION_NULL, ERR_IOS_TARGET_VERSION_FORMAT,
ERR_IOS_VERSION_COMPONENTS_DIGITS, ERR_TARGET_VERSION_CANNOT_BE_MISSING,
ERR_TARGET_VERSION_MUST_BE_STRING, ERR_UNKNOWN_OS_TYPE, ERR_UNKNOWN_TARGET_VERSION)
ERR_ANDROID_REFERENCE_VERSION_CODE_NULL, ERR_ANDROID_TARGET_VERSION_DIGITS,
ERR_IOS_REFERENCE_VERSION_NAME_FORMAT, ERR_IOS_REFERENCE_VERSION_NULL,
ERR_IOS_TARGET_VERSION_FORMAT, ERR_IOS_VERSION_COMPONENTS_DIGITS,
ERR_TARGET_VERSION_CANNOT_BE_MISSING, ERR_TARGET_VERSION_MUST_BE_STRING, ERR_UNKNOWN_OS_TYPE,
ERR_UNKNOWN_TARGET_VERSION)
from constants.user_constants import ANDROID_API, IOS_API


Expand All @@ -22,37 +24,38 @@
"""

class VersionError(ValueError): pass
StrN = str|None

#
## is target version X than reference version
#

def is_this_version_gt_participants(
os_type: str, target_version: str, participant_version_code: str, participant_version_name: str
os_type: StrN, target_version: StrN, participant_version_code: StrN, participant_version_name: StrN
) -> bool:
return _is_this_version_op_than_participants(
gt, os_type, target_version, participant_version_code, participant_version_name
)


def is_this_version_lt_participants(
os_type: str, target_version: str, participant_version_code: str, participant_version_name: str
os_type: StrN, target_version: StrN, participant_version_code: StrN, participant_version_name: StrN
) -> bool:
return _is_this_version_op_than_participants(
lt, os_type, target_version, participant_version_code, participant_version_name
)


def is_this_version_gte_participants(
os_type: str, target_version: str, participant_version_code: str, participant_version_name: str
os_type: StrN, target_version: StrN, participant_version_code: StrN, participant_version_name: StrN
) -> bool:
return _is_this_version_op_than_participants(
gte, os_type, target_version, participant_version_code, participant_version_name
)


def is_this_version_lte_participants(
os_type: str, target_version: str, participant_version_code: str, participant_version_name: str
os_type: StrN, target_version: StrN, participant_version_code: StrN, participant_version_name: StrN
) -> bool:
return _is_this_version_op_than_participants(
lte, os_type, target_version, participant_version_code, participant_version_name
Expand All @@ -62,32 +65,33 @@ def is_this_version_lte_participants(
## is participant's version OPERATOR than target version
#


def is_participants_version_gt_target(
os_type: str, participant_version_code: str, participant_version_name: str, target_version: str
os_type: StrN, participant_version_code: StrN, participant_version_name: StrN, target_version: StrN
) -> bool:
return _is_participants_version_op_than_target(
gt, os_type, participant_version_code, participant_version_name, target_version
)


def is_participants_version_lt_target(
os_type: str, participant_version_code: str, participant_version_name: str, target_version: str
os_type: StrN, participant_version_code: StrN, participant_version_name: StrN, target_version: StrN
) -> bool:
return _is_participants_version_op_than_target(
lt, os_type, participant_version_code, participant_version_name, target_version
)


def is_participants_version_gte_target(
os_type: str, participant_version_code: str, participant_version_name: str, target_version: str
os_type: StrN, participant_version_code: StrN, participant_version_name: StrN, target_version: StrN
) -> bool:
return _is_participants_version_op_than_target(
gte, os_type, participant_version_code, participant_version_name, target_version
)


def is_participants_version_lte_target(
os_type: str, participant_version_code: str, participant_version_name: str, target_version: str
os_type: StrN, participant_version_code: StrN, participant_version_name: StrN, target_version: StrN
) -> bool:
return _is_participants_version_op_than_target(
lte, os_type, participant_version_code, participant_version_name, target_version
Expand All @@ -99,8 +103,8 @@ def is_participants_version_lte_target(


def _is_participants_version_op_than_target(
op: callable, os_type: str, participant_version_code: str, participant_version_name: str,
target_version: str
op: Callable, os_type: StrN, participant_version_code: StrN, participant_version_name: StrN,
target_version: StrN
) -> bool:
_validate_target_and_os(os_type, target_version)
if os_type == IOS_API:
Expand All @@ -113,8 +117,8 @@ def _is_participants_version_op_than_target(


def _is_this_version_op_than_participants(
op: callable, os_type: str, target_version: str, participant_version_code: str,
participant_version_name: str
op: Callable, os_type: StrN, target_version: StrN, participant_version_code: StrN,
participant_version_name: StrN
) -> bool:
_validate_target_and_os(os_type, target_version)
if os_type == IOS_API:
Expand All @@ -126,7 +130,7 @@ def _is_this_version_op_than_participants(
raise VersionError(ERR_UNKNOWN_OS_TYPE(os_type))


def _validate_target_and_os(os_type: str, target_version: str) -> None:
def _validate_target_and_os(os_type: StrN, target_version: StrN) -> None:
if not isinstance(target_version, str):
raise VersionError(ERR_TARGET_VERSION_MUST_BE_STRING(type(target_version)))

Expand All @@ -138,7 +142,7 @@ def _validate_target_and_os(os_type: str, target_version: str) -> None:


def _ios_is_this_version_op_than(
op: callable, target_version: str, participant_version_name: str
op: Callable, target_version: StrN, participant_version_name: StrN
) -> bool:
# version_code for ios looks like 2.x, 2.x.y, or 2.x.yz, or is None
# version_name for ios looks like 2024.21, or is None, or a commit-hash-like string.
Expand Down Expand Up @@ -171,12 +175,13 @@ def _ios_is_this_version_op_than(
return op(target_build, reference_build)
return op(target_year, reference_year)


def _android_is_version_op_than(
op: callable, target_version: str, participant_version_code: str
op: Callable, target_version: StrN, participant_version_code: StrN
) -> bool:
# android is easy, we just compare the version code, which must be digits-only.
if participant_version_code is None:
raise VersionError(ERR_ANDROID_participant_VERSION_CODE_NULL)
raise VersionError(ERR_ANDROID_REFERENCE_VERSION_CODE_NULL)

if not target_version.isdigit():
raise VersionError(ERR_ANDROID_TARGET_VERSION_DIGITS(target_version))
Expand Down
16 changes: 11 additions & 5 deletions services/resend_push_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ def base_resend_logic_participant_query(now: datetime) -> List[ParticipantPKs]:
"participant__last_version_name",
)
)
# log("pushable_participant_info:", pushable_participant_info)

# complex filters, participant app version and os type
pushable_participant_pks = []
Expand All @@ -168,9 +169,10 @@ def base_resend_logic_participant_query(now: datetime) -> List[ParticipantPKs]:


def get_unconfirmed_notification_schedules(
participant: Participant, excluded_pks: list[ScheduledEventPK]
participant: Participant, excluded_pks: list[ScheduledEventPK] = None,
) -> list[ScheduledEvent]:
""" We need to send all unconfirmed surveys to along with all other surveys whenever we send a notification. """
excluded_pks = excluded_pks or []

if participant.os_type != IOS_API:
return []
Expand All @@ -179,8 +181,8 @@ def get_unconfirmed_notification_schedules(
try:
proceed = is_participants_version_gte_target(
participant.os_type,
participant.version_code,
participant.version_name,
participant.last_version_code,
participant.last_version_name,
IOS_APP_MINIMUM_PUSH_NOTIFICATION_RESEND_VERSION,
)
if not proceed:
Expand All @@ -192,8 +194,12 @@ def get_unconfirmed_notification_schedules(
# (this _is_ the logic for getting all unconfirmed notifications)
way_in_the_future = timezone.now() + timedelta(days=365)
resend_uuids = get_resendable_uuids(way_in_the_future, [participant.pk])
events = participant.scheduled_events.filter(uuid__in=resend_uuids).exclude(pk__in=excluded_pks)
return list(events)

# Exclude should be faster than a python deduplication, because this pulls full model objects,
# and there will be some participnts with a lot of schedules until they age out.
return list(
participant.scheduled_events.filter(uuid__in=resend_uuids).exclude(pk__in=excluded_pks)
)


def update_ArchivedEvents_from_SurveyNotificationReports(
Expand Down
17 changes: 13 additions & 4 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -730,13 +730,15 @@ def bulk_generate_archived_events(
]
return ArchivedEvent.objects.bulk_create(events)

def generate_archived_event_matching_absolute_schedule(self, absolute: AbsoluteSchedule, a_uuid: uuid.UUID = None):
def generate_archived_event_matching_absolute_schedule(
self, absolute: AbsoluteSchedule, a_uuid: uuid.UUID = None, participant: Participant = None
):
# absolute is super easy
the_event_time = absolute.event_time(self.default_study.timezone)
# print("the event time:", the_event_time)
return self.generate_archived_event(
absolute.survey,
self.default_participant,
participant or self.default_participant,
ScheduleTypes.absolute,
the_event_time,
a_uuid=a_uuid
Expand Down Expand Up @@ -935,14 +937,21 @@ def set_working_heartbeat_notification_fully_valid(self):

@property
def set_default_participant_all_push_notification_features(self):
self.default_participant.update(
self.set_participant_all_push_notification_features(self.default_participant)

def set_participant_all_push_notification_features(self, participant: Participant):
participant.update(
deleted=False,
permanently_retired=False,
last_version_name=IOS_APP_MINIMUM_PUSH_NOTIFICATION_RESEND_VERSION,
last_version_code="aaah!!! does not matter",
os_type=IOS_API,
last_upload=timezone.now(),
)
self.default_fcm_token
if hasattr(self, "_default_participant") and participant is self._default_participant:
self.default_fcm_token
else:
self.generate_fcm_token(participant)


def compare_dictionaries(first, second, ignore=None):
Expand Down
Loading

0 comments on commit 20d70f7

Please sign in to comment.