""" Receivers of signals sent from django-user-tasks """ import logging import re from urllib.parse import urljoin from django.dispatch import receiver from django.urls import reverse from user_tasks.models import UserTaskArtifact from user_tasks.signals import user_task_stopped from cms.djangoapps.contentstore.toggles import bypass_olx_failure_enabled from cms.djangoapps.contentstore.utils import course_import_olx_validation_is_enabled from .tasks import send_task_complete_email LOGGER = logging.getLogger(__name__) LIBRARY_CONTENT_TASK_NAME_TEMPLATE = 'updating .*type@library_content.* from library' @receiver(user_task_stopped, dispatch_uid="cms_user_task_stopped") def user_task_stopped_handler(sender, **kwargs): # pylint: disable=unused-argument """ Handles sending notifications when a django-user-tasks completes. This is a signal receiver for user_task_stopped. Currently it only sends a generic "task completed" email, and only when a top-level task completes. Eventually it might make more sense to create specific per-task handlers. Arguments: sender (obj): Currently the UserTaskStatus object class **kwargs: See below Keywork Arguments: status (obj): UserTaskStatus of the completed task Returns: None """ def is_library_content_update(task_name: str) -> bool: """ Decides whether to suppress an end-of-task email on the basis that the just-ended task was a library content XBlock update operation, and that emails following such operations amount to spam Arguments: task_name: The name of the just-ended task. By convention, if this was a library content XBlock update task, then the task name follows the pattern prescribed in LibrarySyncChildrenTask (content_libraries under openedx) 'Updating {key} from library'. Moreover, the block type in the task name is always of type 'library_content' for such operations Returns: True if the end-of-task email should be suppressed """ p = re.compile(LIBRARY_CONTENT_TASK_NAME_TEMPLATE) return p.match(task_name) def get_olx_validation_from_artifact(): """ Get olx validation error if available for current task. Returns: olx validation string or None. """ if not course_import_olx_validation_is_enabled(): return None olx_artifact = UserTaskArtifact.objects.filter(status=status, name="OLX_VALIDATION_ERROR").first() if olx_artifact and not bypass_olx_failure_enabled(): return olx_artifact.text status = kwargs['status'] # Only send email when the entire task is complete, should only send when # a chain / chord / etc completes, not on sub-tasks. if status.parent is None: task_name = status.name.lower() # Also suppress emails on library content XBlock updates (too much like spam) if is_library_content_update(task_name): LOGGER.info(f"Suppressing end-of-task email on task {task_name}") return # `name` and `status` are not unique, first is our best guess artifact = UserTaskArtifact.objects.filter(status=status, name="BASE_URL").first() detail_url = None if artifact and artifact.url.startswith(('http://', 'https://')): detail_url = urljoin( artifact.url, reverse('usertaskstatus-detail', args=[status.uuid]) ) user_email = status.user.email olx_validation_text = get_olx_validation_from_artifact() task_args = [task_name, str(status.state_text), user_email, detail_url, olx_validation_text] try: send_task_complete_email.delay(*task_args) except Exception: # pylint: disable=broad-except LOGGER.exception("Unable to queue send_task_complete_email")