Skip to content

Commit

Permalink
Revert "#1953 - IIR - Integrate permission check into contact info lo…
Browse files Browse the repository at this point in the history
…okup" (#1997)
  • Loading branch information
AdamKing0126 authored Sep 18, 2024
1 parent 3cca8b4 commit 0c7d88d
Show file tree
Hide file tree
Showing 20 changed files with 55 additions and 2,299 deletions.
8 changes: 0 additions & 8 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,4 @@ fileignoreconfig:
checksum: 34d12acdf749363555c31add4e7e7afa9e2a27afd792bd98c85f331b87bd7112
- filename: scripts/trigger_task.py
checksum: 0e9d244dbe285de23fc84bb643407963dacf7d25a3358373f01f6272fb217778
- filename: tests/app/notifications/test_process_notifications_for_profile_v3.py
checksum: 4e15e63d349635131173ffdd7aebcd547621db08de877ef926d3a41fde72d065
- filename: app/template/rest.py
checksum: 1e5bdac8bc694d50f8f656dec127dd036b7b1b5b6156e3282d3411956c71ba0b
- filename: app/service/rest.py
checksum: b42aefd1ae0e6ea76e75db4cf14d425facd0941943b17f7ba2e41f850ad1ec23
- filename: app/celery/contact_information_tasks.py
checksum: 80d0acf88bafb1358583016b9e143f4523ef1160d6eacdc9754ca68859b90eae
version: "1.0"
208 changes: 26 additions & 182 deletions app/celery/contact_information_tasks.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,14 @@
from flask import current_app
from app import notify_celery, va_profile_client
from celery import Task
from app.celery.common import can_retry, handle_max_retries_exceeded
from app.celery.exceptions import AutoRetryException
from app.celery.service_callback_tasks import check_and_queue_callback_task
from app.va.identifier import IdentifierType
from app.va.va_profile import VAProfileRetryableException, VAProfileNonRetryableException, NoContactInfoException
from app.dao.notifications_dao import get_notification_by_id, dao_update_notification, update_notification_status_by_id
from app.models import NOTIFICATION_PERMANENT_FAILURE, EMAIL_TYPE, SMS_TYPE
from app.exceptions import NotificationTechnicalFailureException, NotificationPermanentFailureException
from app.feature_flags import FeatureFlag, is_feature_enabled
from app.models import (
NOTIFICATION_PERMANENT_FAILURE,
NOTIFICATION_PREFERENCES_DECLINED,
EMAIL_TYPE,
SMS_TYPE,
Notification,
RecipientIdentifier,
)
from app.va.identifier import IdentifierType
from app.va.va_profile import (
VAProfileRetryableException,
VAProfileNonRetryableException,
NoContactInfoException,
VAProfileResult,
)
from app.va.va_profile.exceptions import VAProfileIDNotFoundException, CommunicationItemNotFoundException
from flask import current_app
from app.va.va_profile.exceptions import VAProfileIDNotFoundException
from notifications_utils.statsd_decorators import statsd
from requests import Timeout

Expand All @@ -38,197 +24,55 @@
)
@statsd(namespace='tasks')
def lookup_contact_info(
self: Task,
notification_id: str,
self,
notification_id,
):
"""
Celery task to look up contact information (email/phone number) for a given notification.
If the feature flag, VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP, is enabled,
also check for related communication permissions.
Args:
self (Task): The Celery task instance.
notification_id (str): The ID of the notification for which to look up contact information.
Raises:
AutoRetryException: If a retryable exception occurs during the lookup process.
NotificationTechnicalFailureException: If the maximum retries have been exceeded.
NotificationPermanentFailureException: If the exception indicates a permanent failure.
Exception: If an unhandled exception occurs.
Returns:
None
"""
current_app.logger.info('Looking up contact information for notification_id: %s.', notification_id)

notification = get_notification_by_id(notification_id)
recipient_identifier = notification.recipient_identifiers[IdentifierType.VA_PROFILE_ID.value]
should_send = notification.default_send

try:
if is_feature_enabled(FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP):
result = get_profile_result(notification, recipient_identifier)
recipient = result.recipient
should_send = result.communication_allowed
permission_message = result.permission_message
if EMAIL_TYPE == notification.notification_type:
recipient = va_profile_client.get_email(recipient_identifier)
elif SMS_TYPE == notification.notification_type:
recipient = va_profile_client.get_telephone(recipient_identifier)
else:
recipient = get_recipient(
notification.notification_type,
notification_id,
recipient_identifier,
raise NotImplementedError(
f'The task lookup_contact_info failed for notification {notification_id}. '
f'{notification.notification_type} is not supported'
)
except Exception as e:
handle_lookup_contact_info_exception(self, notification, recipient_identifier, e)

notification.to = recipient
dao_update_notification(notification)

if not should_send:
handle_communication_not_allowed(notification, recipient_identifier, permission_message)


def get_recipient(
notification_type: str,
notification_id: str,
recipient_identifier: RecipientIdentifier,
) -> str:
"""
Retrieve the recipient email or phone number.
Args:
notification_type (str): The type of recipient info requested.
notification_id (str): The notification ID associated with this request.
recipient_identifier (RecipientIdentifier): The VA profile ID to retrieve the profile for.
Returns:
str: The recipient email or phone number.
"""
if notification_type == EMAIL_TYPE:
return va_profile_client.get_email(recipient_identifier)
elif notification_type == SMS_TYPE:
return va_profile_client.get_telephone(recipient_identifier)
else:
raise NotImplementedError(
f'The task lookup_contact_info failed for notification {notification_id}. '
f'{notification_type} is not supported'
)


def get_profile_result(
notification: Notification,
recipient_identifier: RecipientIdentifier,
) -> VAProfileResult:
"""
Retrieve the result of looking up contact info from VA Profile.
Args:
notification (Notification): The Notification object to get contact info and permissions for.
recipient_identifier (RecipientIdentifier): The VA profile ID to retrieve the profile for.
Returns:
VAProfileResult: The contact info result from VA Profile.
"""
if notification.notification_type == EMAIL_TYPE:
return va_profile_client.get_email_with_permission(recipient_identifier, notification.default_send)
elif notification.notification_type == SMS_TYPE:
return va_profile_client.get_telephone_with_permission(recipient_identifier, notification.default_send)
else:
raise NotImplementedError(
f'The task lookup_contact_info failed for notification {notification.id}. '
f'{notification.notification_type} is not supported'
)


def handle_lookup_contact_info_exception(
lookup_task: Task, notification: Notification, recipient_identifier: RecipientIdentifier, e: Exception
):
"""
Handles exceptions that occur during the lookup of contact information.
Args:
lookup_task (Task): The task object that is performing the lookup.
notification (Notification): The notification object associated with the lookup.
recipient_identifier (RecipientIdentifier): The identifier of the recipient.
e (Exception): The exception that was raised during the lookup.
Raises:
AutoRetryException: If the exception is retryable and the task can be retried.
NotificationTechnicalFailureException: If the maximum retries have been exceeded.
NotificationPermanentFailureException: If the exception indicates a permanent failure.
Exception: If an unhandled exception occurs.
Returns:
str or None: A message indicating the result of the exception handling, or None if no action is needed.
"""
if isinstance(e, (Timeout, VAProfileRetryableException)):
if can_retry(lookup_task.request.retries, lookup_task.max_retries, notification.id):
current_app.logger.warning('Unable to get contact info for notification id: %s, retrying', notification.id)
except (Timeout, VAProfileRetryableException) as e:
if can_retry(self.request.retries, self.max_retries, notification_id):
current_app.logger.warning('Unable to get contact info for notification id: %s, retrying', notification_id)
raise AutoRetryException(f'Found {type(e).__name__}, autoretrying...', e, e.args)
else:
msg = handle_max_retries_exceeded(notification.id, 'lookup_contact_info')
msg = handle_max_retries_exceeded(notification_id, 'lookup_contact_info')
check_and_queue_callback_task(notification)
raise NotificationTechnicalFailureException(msg)
elif isinstance(e, NoContactInfoException):
except NoContactInfoException as e:
message = (
f"Can't proceed after querying VA Profile for contact information for {notification.id}. "
f"Can't proceed after querying VA Profile for contact information for {notification_id}. "
'Stopping execution of following tasks. Notification has been updated to permanent-failure.'
)
current_app.logger.warning('%s - %s: %s', e.__class__.__name__, str(e), message)

update_notification_status_by_id(
notification.id, NOTIFICATION_PERMANENT_FAILURE, status_reason=e.failure_reason
notification_id, NOTIFICATION_PERMANENT_FAILURE, status_reason=e.failure_reason
)
check_and_queue_callback_task(notification)
raise NotificationPermanentFailureException(message) from e
elif isinstance(e, (VAProfileIDNotFoundException, VAProfileNonRetryableException)):
except (VAProfileIDNotFoundException, VAProfileNonRetryableException) as e:
current_app.logger.exception(e)
message = (
f'The task lookup_contact_info failed for notification {notification.id}. '
f'The task lookup_contact_info failed for notification {notification_id}. '
'Notification has been updated to permanent-failure'
)
update_notification_status_by_id(
notification.id, NOTIFICATION_PERMANENT_FAILURE, status_reason=e.failure_reason
notification_id, NOTIFICATION_PERMANENT_FAILURE, status_reason=e.failure_reason
)
check_and_queue_callback_task(notification)
raise NotificationPermanentFailureException(message) from e
elif isinstance(e, CommunicationItemNotFoundException):
current_app.logger.info(
'Communication item for recipient %s not found on notification %s',
recipient_identifier.id_value,
notification.id,
)

return None if notification.default_send else 'No recipient opt-in found for explicit preference'
else:
current_app.logger.exception(f'Unhandled exception for notification {notification.id}: {e}')
raise e


def handle_communication_not_allowed(
notification: Notification, recipient_identifier: RecipientIdentifier, permission_message: str
):
"""
Handles the scenario where communication is not allowed for a given notification.
Args:
notification (Notification): The notification object associated with the communication.
recipient_identifier (RecipientIdentifier): The identifier of the recipient.
permission_message (str): The message indicating the reason for permission denial.
Raises:
NotificationPermanentFailureException: If the recipient has declined permission to receive notifications.
"""
if is_feature_enabled(FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP):
current_app.logger.info(
'Permission denied for recipient %s for notification %s',
recipient_identifier.id_value,
notification.id,
)
reason = permission_message if permission_message is not None else 'Contact preferences set to false'
update_notification_status_by_id(notification.id, NOTIFICATION_PREFERENCES_DECLINED, status_reason=reason)

message = f'The recipient for notification {notification.id} has declined permission to receive notifications.'
current_app.logger.info(message)

check_and_queue_callback_task(notification)
raise NotificationPermanentFailureException(message)
notification.to = recipient
dao_update_notification(notification)
9 changes: 0 additions & 9 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import datetime
import uuid
import itertools
from functools import cached_property
from typing import Dict, Any
from app import (
DATETIME_FORMAT,
Expand Down Expand Up @@ -1387,14 +1386,6 @@ class Notification(db.Model):
{},
)

@cached_property
def default_send(self):
if self.template.communication_item_id:
communication_item = CommunicationItem.query.get(self.template.communication_item_id)
if communication_item:
return communication_item.default_send_indicator
return True

@property
def personalisation(self):
if self._personalisation:
Expand Down
5 changes: 1 addition & 4 deletions app/notifications/process_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,10 +275,7 @@ def send_to_queue_for_recipient_info_based_on_recipient_identifier(

tasks.append(lookup_contact_info.si(notification.id).set(queue=QueueNames.LOOKUP_CONTACT_INFO))

if (
not is_feature_enabled(FeatureFlag.VA_PROFILE_V3_COMBINE_CONTACT_INFO_AND_PERMISSIONS_LOOKUP)
and communication_item_id
):
if communication_item_id:
tasks.append(
lookup_recipient_communication_permissions.si(notification.id).set(
queue=QueueNames.COMMUNICATION_ITEM_PERMISSIONS
Expand Down
5 changes: 1 addition & 4 deletions app/va/va_profile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,4 @@
VAProfileNonRetryableException,
NoContactInfoException,
)
from .va_profile_client import ( # noqa: F401
VAProfileClient,
VAProfileResult,
)
from .va_profile_client import VAProfileClient # noqa: F401
2 changes: 1 addition & 1 deletion app/va/va_profile/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ class ContactPreferencesException(VAProfileNonRetryableException):
failure_reason = 'VA Profile contact preferences not allowing contact'


class CommunicationItemNotFoundException(VAProfileNonRetryableException):
class CommunicationItemNotFoundException(Exception):
failure_reason = 'No communication bio found from VA Profile'
Loading

0 comments on commit 0c7d88d

Please sign in to comment.