From 6ddc17a4ecece79fb5e0e524a7d5abc4e91f26f5 Mon Sep 17 00:00:00 2001 From: Tanner Prestegard <tanner.prestegard@ligo.org> Date: Mon, 17 Sep 2018 12:15:05 -0500 Subject: [PATCH] Overhaul of LVAlert infrastructure New format for LVAlerts (XMPP alerts). New class-based functionality for issuing alerts for events and superevents. See https://git.ligo.org/lscsoft/gracedb/issues/8. --- gracedb/alerts/events/utils.py | 110 +++++++--- gracedb/alerts/main.py | 24 +- gracedb/alerts/superevents/utils.py | 207 +++++------------- gracedb/alerts/xmpp.py | 39 ++-- gracedb/api/v1/events/views.py | 60 ++--- gracedb/api/v1/superevents/serializers.py | 2 +- .../api/v1/superevents/tests/test_access.py | 6 + gracedb/api/v1/superevents/views.py | 3 +- gracedb/events/view_logic.py | 37 ++-- gracedb/events/view_utils.py | 31 ++- gracedb/events/views.py | 31 ++- gracedb/superevents/utils.py | 202 ++++++++++------- 12 files changed, 371 insertions(+), 381 deletions(-) diff --git a/gracedb/alerts/events/utils.py b/gracedb/alerts/events/utils.py index 727ffa4bf..1a865b7fa 100644 --- a/gracedb/alerts/events/utils.py +++ b/gracedb/alerts/events/utils.py @@ -1,56 +1,100 @@ from __future__ import absolute_import import logging +from django.contrib.contenttypes.models import ContentType from django.urls import reverse +from guardian.models import GroupObjectPermission from core.urls import build_absolute_uri from events.shortcuts import is_event -from events.view_utils import eventToDict, eventLogToDict +from events.view_utils import eventToDict, eventLogToDict, signoffToDict, \ + emObservationToDict, embbEventLogToDict, groupeventpermissionToDict, \ + labelToDict, voeventToDict from ..main import issue_alerts +from ..utils import AlertIssuerWithParentObject # Set up logger logger = logging.getLogger(__name__) -def event_alert_helper(obj, serializer=eventToDict, request=None): - """ - If obj is not an event, assume it is an object with a relation to - an Event object. - """ +# NOTE: we have to be careful in all of these serializers since we want to +# serialize the event subclass always, not the base event object. +class AlertIssuerWithParentEvent(AlertIssuerWithParentObject): + parent_serializer_class = staticmethod(eventToDict) - # If superevent_id is None, assume obj is a Superevent - if is_event(obj): - graceid = event.graceid() - else: - try: - graceid = obj.event.graceid() - except: - # TODO: raise appropriate error - pass + def serialize_obj(self): + return self.serializer_class(self.obj) - # Construct URL for web view - url = build_absolute_uri(reverse('view', args=[graceid]), request) + def serialize_parent(self): + return self.parent_serializer_class(self.get_parent_obj(), + is_alert=True) - # Serialize the object into a dictionary - obj_dict = serializer(obj) + def _get_parent_obj(self): + # Assumes that the obj has a direct relation to an event + if not hasattr(self.obj, 'event'): + raise AttributeError(('object of class {0} does not have a direct ' + 'relationship to an event').format( + self.obj.__class__.__name__)) + # Make sure we have the event "subclass" + return self.obj.event.get_subclass_or_self() - return url, obj_dict + def issue_alerts(self): + issue_alerts(self.get_parent_obj(), self.alert_type, + self.serialize_obj(), self.serialize_parent()) -def issue_alert_for_event_log(log, request=None): +class EventAlertIssuer(AlertIssuerWithParentEvent): + serializer_class = staticmethod(eventToDict) + alert_types = ['new', 'update', 'selected_as_preferred', + 'removed_as_preferred', 'added_to_superevent', + 'removed_from_superevent'] - # Get URL for event webview and serialized log - url, serialized_object = event_alert_helper(log, eventLogToDict, request) + def serialize_obj(self): + return self.serializer_class(self.obj.get_subclass_or_self(), + is_alert=True) - # Description - if log.filename: - description = "UPLOAD: '{filename}'".format(filename=log.filename) - else: - description = "LOG:" - description += " {message}".format(message=log.comment) + def _get_parent_obj(self): + return self.obj - # Send alerts - issue_alerts(log.event, alert_type="update", url=url, - description=description, serialized_object=serialized_object, - file_name=log.filename) +class EventLogAlertIssuer(AlertIssuerWithParentEvent): + serializer_class = staticmethod(eventLogToDict) + alert_types = ['log'] + + +class EventLabelAlertIssuer(AlertIssuerWithParentEvent): + serializer_class = staticmethod(labelToDict) + alert_types = ['label_added', 'label_removed'] + + +class EventVOEventAlertIssuer(AlertIssuerWithParentEvent): + serializer_class = staticmethod(voeventToDict) + alert_types = ['voevent'] + + +class EventEMObservationAlertIssuer(AlertIssuerWithParentEvent): + serializer_class = staticmethod(emObservationToDict) + alert_types = ['emobservation'] + + +class EventEMBBEventLogAlertIssuer(AlertIssuerWithParentEvent): + serializer_class = staticmethod(embbEventLogToDict) + alert_types = ['embb_event_log'] + + +class EventSignoffAlertIssuer(AlertIssuerWithParentEvent): + serializer_class = staticmethod(signoffToDict) + alert_types = ['signoff_created', 'signoff_updated', 'signoff_deleted'] + + +class EventPermissionsAlertIssuer(EventAlertIssuer): + serializer_class = staticmethod(groupeventpermissionToDict) + alert_types = ['exposed', 'hidden'] + + def serialize_obj(self): + """self.obj should be an event here""" + gops = GroupObjectPermission.objects.filter( + object_pk=self.obj.pk, + content_type=ContentType.objects.get_for_model(self.obj)) + gop_list = [self.serializer_class(gop) for gop in gops] + return gop_list diff --git a/gracedb/alerts/main.py b/gracedb/alerts/main.py index 03f36bac2..4c468ec2a 100644 --- a/gracedb/alerts/main.py +++ b/gracedb/alerts/main.py @@ -93,35 +93,29 @@ def get_alert_recips_for_label(event_or_superevent, label): .select_related('user') phone_recips |= trigger.contacts.exclude(phone="") \ .select_related('user') - #email_recips.extend([c for c in - # trigger.contacts.all().select_related('user') if c.email]) - #phone_recips.extend([c for c in - # trigger.contacts.all().select_related('user') if c.phone]) return check_recips(email_recips), check_recips(phone_recips) -def issue_alerts(event_or_superevent, alert_type, url=None, file_name="", - description="", label=None, serialized_object=None): - - # Check alert_type - if alert_type not in ["new", "label", "update", "signoff"]: - raise ValueError(("alert_type is {0}, should be 'new', 'label', " - "'update', or 'signoff'").format(alert_type)) +def issue_alerts(event_or_superevent, alert_type, serialized_object, + serialized_parent=None): # Send XMPP alert if settings.SEND_XMPP_ALERTS: - issue_xmpp_alert(event_or_superevent, alert_type, file_name, - description=description, serialized_object=serialized_object) + issue_xmpp_alert(event_or_superevent, alert_type, serialized_object, + serialized_parent=serialized_parent) # Below here, we only do processing for email and phone alerts ------------ + if not (settings.SEND_EMAIL_ALERTS or settings.SEND_PHONE_ALERTS): + return # TODO: make phone and e-mail alerts work for superevents if is_superevent(event_or_superevent): return - # We currently don't send phone or email alerts for updates or signoffs - if alert_type == "update" or alert_type == "signoff": + # Phone and email alerts are currently only for "new" and "label_added" + # alert_types, for events only. + if (alert_type not in ['new', 'label_added']): return # Don't send phone or email alerts for MDC events or Test events diff --git a/gracedb/alerts/superevents/utils.py b/gracedb/alerts/superevents/utils.py index b09eff780..b36931796 100644 --- a/gracedb/alerts/superevents/utils.py +++ b/gracedb/alerts/superevents/utils.py @@ -1,181 +1,78 @@ -from __future__ import absolute_import -import logging - -from django.urls import reverse -from django.contrib.auth.models import Group as AuthGroup - -from rest_framework.renderers import JSONRenderer - from api.v1.superevents.serializers import SupereventSerializer, \ SupereventLogSerializer, SupereventLabelSerializer, \ - SupereventEMObservationSerializer, SupereventVOEventSerializer, \ + SupereventVOEventSerializer, SupereventEMObservationSerializer, \ SupereventSignoffSerializer, SupereventGroupObjectPermissionSerializer -from core.urls import build_absolute_uri -from superevents.shortcuts import is_superevent from ..main import issue_alerts +from ..utils import AlertIssuerWithParentObject -# Set up logger -logger = logging.getLogger(__name__) - - -def superevent_alert_helper(obj, serializer=SupereventSerializer, - request=None): - """ - Assume non-superevent objects passed to this function have a - foreign key link to a superevent - """ - - # If superevent_id is None, assume obj is a Superevent - if is_superevent(obj): - superevent_id = obj.superevent_id - else: - try: - superevent_id = obj.superevent.superevent_id - except: - # TODO: raise appropriate error - pass - - # Construct URL for web view - url = build_absolute_uri(reverse('superevents:view', args=[superevent_id]), - request) - - # Serialize the object into a dictionary - obj_dict = serializer(obj).data - - return url, obj_dict - - -def issue_alert_for_superevent_creation(superevent, request=None): - - # Get URL and serialized superevent - url, serialized_object = superevent_alert_helper(superevent, - request=request) - - # Description - description = "NEW: superevent {0}".format(superevent.superevent_id) - - # Send alerts - issue_alerts(superevent, alert_type="new", url=url, - description=description, serialized_object=serialized_object) - - -#def issue_alert_for_superevent_update(superevent, request=None): -# # Get URL and serialized superevent -# url, serialized_object = superevent_alert_helper(superevent, -# request=request) -# -# # Description -# # TODO: fix -# description = "UPDATE: superevent {0}".format(superevent.superevent_id) -# -# # Send alerts -# issue_alerts(superevent, alert_type="update", url=url, -# description=description, serialized_object=serialized_object) - - -def issue_alert_for_superevent_log(log, request=None): - - # Get URL for superevent webview and serialized log - url, serialized_object = superevent_alert_helper(log, - SupereventLogSerializer, request=request) - - # Description - if log.filename: - description = "UPLOAD: '{filename}'".format(filename=log.filename) - else: - description = "LOG:" - description += " {message}".format(message=log.comment) - - # Send alerts - issue_alerts(log.superevent, alert_type="update", url=url, - description=description, serialized_object=serialized_object, - file_name=log.filename) - - -def issue_alert_for_superevent_label_creation(labelling, request=None): - - # Get URL for superevent webview and serialized label - url, serialized_object = superevent_alert_helper(labelling, - SupereventLabelSerializer, request=request) - - # Description - description = "LABEL: {label} added".format(label=labelling.label.name) - - # Send alerts - # NOTE: current alerts don't include an object (change this?) - issue_alerts(labelling.superevent, alert_type="label", url=url, - description=description, serialized_object=serialized_object) +class AlertIssuerWithParentSuperevent(AlertIssuerWithParentObject): + parent_serializer_class = SupereventSerializer -def issue_alert_for_superevent_label_removal(labelling, request=None): - # Get URL for superevent webview and serialized label - url, serialized_object = superevent_alert_helper(labelling, - SupereventLabelSerializer, request=request) + def serialize_parent(self): + return self.parent_serializer_class(self.get_parent_obj()).data - # Description - description = "UPDATE: {label} removed".format(label=labelling.label.name) + def _get_parent_obj(self): + # Assumes that the obj has a direct relation to a superevent + if not hasattr(self.obj, 'superevent'): + raise AttributeError(('object of class {0} does not have a direct ' + 'relationship to a superevent').format( + self.obj.__class__.__name__)) + return self.obj.superevent - # Send alerts - issue_alerts(labelling.superevent, alert_type="update", url=url, - description=description, serialized_object=serialized_object) + def issue_alerts(self): + issue_alerts(self.get_parent_obj(), self.alert_type, + self.serialize_obj(), self.serialize_parent()) -def issue_alert_for_superevent_voevent(voevent, request=None): - # Get URL for superevent webview and serialized voevent - url, serialized_object = superevent_alert_helper(voevent, - SupereventVOEventSerializer, request=request) +class SupereventAlertIssuer(AlertIssuerWithParentSuperevent): + serializer_class = SupereventSerializer + alert_types = ['new', 'update', 'event_added', 'event_removed', + 'confirmed_as_gw'] - # Description - description = "VOEVENT: {filename}".format(filename=voevent.filename) + def _get_parent_obj(self): + return self.obj - # Send alerts - issue_alerts(voevent.superevent, alert_type="update", url=url, - file_name=voevent.filename, description=description, - serialized_object=serialized_object) +class SupereventLogAlertIssuer(AlertIssuerWithParentSuperevent): + serializer_class = SupereventLogSerializer + alert_types = ['log'] -def issue_alert_for_superevent_emobservation(emobservation, request=None): - # Get URL for superevent webview and serialized emo - url, serialized_object = superevent_alert_helper(emobservation, - SupereventEMObservationSerializer, request=request) - # Description - description = "New EMBB observation record for {group}".format( - group=emobservation.group.name) +class SupereventLabelAlertIssuer(AlertIssuerWithParentSuperevent): + serializer_class = SupereventLabelSerializer + alert_types = ['label_added', 'label_removed'] - # Send alerts - issue_alerts(emobservation.superevent, alert_type="update", url=url, - description=description, serialized_object=serialized_object) +class SupereventVOEventAlertIssuer(AlertIssuerWithParentSuperevent): + serializer_class = SupereventVOEventSerializer + alert_types = ['voevent'] -def issue_alert_for_superevent_signoff(signoff, request=None): - # Get URL for superevent webview and serialized signoff - url, serialized_object = superevent_alert_helper(signoff, - SupereventSignoffSerializer, request=request) - # Description - description = signoff.status +class SupereventEMObservationAlertIssuer(AlertIssuerWithParentSuperevent): + serializer_class = SupereventEMObservationSerializer + alert_types = ['emobservation'] - # Send alerts - issue_alerts(signoff.superevent, alert_type="signoff", url=url, - description=description, serialized_object=serialized_object) +class SupereventSignoffAlertIssuer(AlertIssuerWithParentSuperevent): + serializer_class = SupereventSignoffSerializer + alert_types = ['signoff_created', 'signoff_updated', 'signoff_deleted'] -def issue_alert_for_superevent_permissions(superevent, request=None): - # Construct URL for web view - url = build_absolute_uri(reverse('superevents:view', args=[ - superevent.superevent_id]), request) - # Get serialized permissions - gops = superevent.supereventgroupobjectpermission_set.all() - gop_group_pks = gops.values_list('group', flat=True).distinct() - group_queryset = AuthGroup.objects.filter(pk__in=gop_group_pks) - serialized_list = SupereventGroupObjectPermissionSerializer( - group_queryset, many=True) +class SupereventPermissionsAlertIssuer(AlertIssuerWithParentSuperevent): + serializer_class = SupereventGroupObjectPermissionSerializer + alert_types = ['exposed', 'hidden'] - # Description - description = 'Permissions updated' + def serialize_obj(self): + """ + self.obj should be a superevent, but we want to return the list + of group object permissions here. + """ + # NOTE: it seems really weird to return a list of permissions here. + # But exposing/hiding a list of permissions does in fact change + # multiple permissions. Should give this some more thought. + gops = self.obj.supereventgroupobjectpermission_set.all() + return self.serializer_class(gops, many=True).data - # Send alerts - issue_alerts(superevent, alert_type="update", url=url, - description=description, serialized_object=serialized_list) + def _get_parent_obj(self): + return self.obj diff --git a/gracedb/alerts/xmpp.py b/gracedb/alerts/xmpp.py index 63b4f39d2..584787f12 100644 --- a/gracedb/alerts/xmpp.py +++ b/gracedb/alerts/xmpp.py @@ -63,8 +63,8 @@ def get_xmpp_node_names(event_or_superevent): return node_names -def issue_xmpp_alert(event_or_superevent, alert_type="new", file_name="", - description="", serialized_object=None): +def issue_xmpp_alert(event_or_superevent, alert_type, serialized_object, + serialized_parent=None): """ serialized_object should be a dict """ @@ -76,33 +76,30 @@ def issue_xmpp_alert(event_or_superevent, alert_type="new", file_name="", # Determine LVAlert node names node_names = get_xmpp_node_names(event_or_superevent) - # Get object id - if is_superevent(event_or_superevent): - object_id = event_or_superevent.superevent_id - elif is_event(event_or_superevent): - object_id = event_or_superevent.graceid() + # Get uid - FIXME when graceid is switched to a property for events + # instead of a callable + if is_event(event_or_superevent): + uid = event_or_superevent.graceid() else: - error_msg = ('Object is of {0} type; should be an event ' - 'or superevent').format(type(event_or_superevent)) - logger.error(error_msg) - raise TypeError(error_msg) + uid = event_or_superevent.graceid # Create the output dictionary and serialize as JSON. lva_data = { - 'file': file_name, - 'uid': object_id, + 'uid': uid, 'alert_type': alert_type, - 'description': description, - 'labels': [label.name for label in event_or_superevent.labels.all()] + 'data': serialized_object, } - if serialized_object is not None: - lva_data['object'] = serialized_object + # Add serialized "parent" object + if serialized_parent is not None: + lva_data['object'] = serialized_parent + + # Dump to JSON format: # simplejson.dumps is needed to properly handle Decimal fields msg = simplejson.dumps(lva_data) # Log message for debugging - logger.debug("issue_xmpp_alert: sending message {msg} for {object_id}" \ - .format(msg=msg, object_id=object_id)) + logger.info("issue_xmpp_alert: sending message {msg} for {uid}" \ + .format(msg=msg, uid=uid)) # Get manager ready for LVAlert Overseer (?) if settings.USE_LVALERT_OVERSEER: @@ -118,8 +115,8 @@ def issue_xmpp_alert(event_or_superevent, alert_type="new", file_name="", # Log message logger.info(("issue_xmpp_alert: sending alert type {alert_type} " - "with message {msg_id} for {obj_id} to {node}").format( - alert_type=alert_type, msg_id=message_id, obj_id=object_id, + "with message {msg_id} for {uid} to {node}").format( + alert_type=alert_type, msg_id=message_id, uid=uid, node=node_name)) # Try to send with LVAlert Overseer (if enabled) diff --git a/gracedb/api/v1/events/views.py b/gracedb/api/v1/events/views.py index f5bcfd397..1a04d2dcb 100644 --- a/gracedb/api/v1/events/views.py +++ b/gracedb/api/v1/events/views.py @@ -32,7 +32,8 @@ from rest_framework.renderers import BaseRenderer, JSONRenderer, \ from rest_framework.response import Response from rest_framework.views import APIView -from alerts.old_alert import issueAlertForUpdate +from alerts.events.utils import EventAlertIssuer, EventLogAlertIssuer, \ + EventVOEventAlertIssuer, EventPermissionsAlertIssuer from api.backends import LigoAuthentication from core.http import check_and_serve_file from core.vfile import VersionedFile @@ -48,7 +49,8 @@ from events.view_logic import create_label, get_performance_info, \ delete_label, _createEventFromForm, create_eel, create_emobservation from events.view_utils import eventToDict, eventLogToDict, labelToDict, \ embbEventLogToDict, voeventToDict, emObservationToDict, signoffToDict, \ - skymapViewerEMObservationToDict, BadFARRange, check_query_far_range + skymapViewerEMObservationToDict, BadFARRange, check_query_far_range, \ + groupeventpermissionToDict from superevents.models import Superevent from .throttles import EventCreationThrottle, AnnotationThrottle from ...utils import api_reverse @@ -609,6 +611,13 @@ class EventDetail(APIView): # of exceptions here. return Response("Bad Data", status=status.HTTP_400_BAD_REQUEST) + + # Save event + event.save() + + # Issue alert + EventAlertIssuer(event, alert_type='update').issue_alerts() + return Response(status=status.HTTP_202_ACCEPTED) #================================================================== @@ -842,13 +851,7 @@ class EventLogList(APIView): # response['tagWarning'] = tw_dict['tagWarning'] # Issue alert. - description = "LOG: " - fname = "" - if uploadedFile: - description = "UPLOAD: '%s' " % uploadedFile.name - fname = uploadedFile.name - issueAlertForUpdate(event, description+message, doxmpp=True, - filename=fname, serialized_object=rv) + EventLogAlertIssuer(logentry, alert_type='log').issue_alerts() return response @@ -903,6 +906,7 @@ class EMBBEventLogList(APIView): @event_and_auth_required def post(self, request, event): try: + # Alert is issued in this code eel = create_eel(request.data, event, request.user) except ValueError, e: return Response("%s" % str(e), status=status.HTTP_400_BAD_REQUEST) @@ -917,11 +921,6 @@ class EMBBEventLogList(APIView): response = Response(rv, status=status.HTTP_201_CREATED) response['Location'] = rv['self'] - # Issue alert. - description = "New EMBB log entry." - issueAlertForUpdate(event, description, doxmpp=True, - filename="", serialized_object=rv) - return response class EMBBEventLogDetail(APIView): @@ -993,6 +992,7 @@ class EMObservationList(APIView): @event_and_auth_required def post(self, request, event): try: + # Create EMObservation - alert is issued inside this code emo = create_emobservation(request, event) except ValueError, e: return Response("%s" % str(e), status=status.HTTP_400_BAD_REQUEST) @@ -1254,24 +1254,6 @@ class EventLogTagDetail(APIView): #================================================================== # Permission Resources -def groupeventpermissionToDict(gop, event, request=None): - """Convert a group object permission to a dictionary. - Output depends on the level of specificity. - """ - - rv = {} - rv['group'] = gop.group.name - rv['graceid'] = event.graceid() - perm_shortname = gop.permission.codename.split('_')[0] - rv['permission'] = perm_shortname - # We want a link to the self only. End of the line. - rv['links'] = { - "self" : api_reverse("events:groupeventpermission-detail", - args=[event.graceid(),gop.group.name,perm_shortname], - request=request) - } - return rv - def getContentType(event): return ContentType.objects.get_for_model(event) @@ -1398,6 +1380,9 @@ class GroupEventPermissionDetail(APIView): status_code = status.HTTP_200_OK if created: status_code = status.HTTP_201_CREATED + # Issue alert + EventPermissionsAlertIssuer(gop, alert_type='exposed') \ + .issue_alerts() return Response(rv, status=status_code) # @@ -1420,6 +1405,9 @@ class GroupEventPermissionDetail(APIView): permission=permission) gop.delete() event.refresh_perms() + # Issue alert + EventPermissionsAlertIssuer(gop, alert_type='hidden') \ + .issue_alerts() # XXX if the event is a subclass, we need to delete perms on the # underlying event as well. @@ -1575,9 +1563,7 @@ class Files(APIView): pass try: - description = "UPLOAD: {0}".format(filename) - issueAlertForUpdate(event, description, doxmpp=True, - filename=filename, serialized_object = eventLogToDict(logentry)) + EventLogAlertIssuer(logentry, alert_type='log').issue_alerts() except: # XXX something should be done here. pass @@ -1697,9 +1683,7 @@ class VOEventList(APIView): #rv['tagWarning'] = 'Error tagging VOEvent log message as em_follow.' # Issue alert. - description = "VOEVENT: %s" % filename - issueAlertForUpdate(event, description, doxmpp=True, - filename=filename, serialized_object=rv) + EventVOEventAlertIssuer(voevent, alert_type='voevent').issue_alerts() response = Response(rv, status=status.HTTP_201_CREATED) response['Location'] = rv['links']['self'] diff --git a/gracedb/api/v1/superevents/serializers.py b/gracedb/api/v1/superevents/serializers.py index 28a56ae6d..394d33066 100644 --- a/gracedb/api/v1/superevents/serializers.py +++ b/gracedb/api/v1/superevents/serializers.py @@ -243,7 +243,7 @@ class SupereventEventSerializer(serializers.ModelSerializer): submitter = validated_data.pop('user') add_event_to_superevent(superevent, event, submitter, add_superevent_log=True, add_event_log=True, - issue_superevent_alert=True, issue_event_alert=True) + issue_alert=True) return event diff --git a/gracedb/api/v1/superevents/tests/test_access.py b/gracedb/api/v1/superevents/tests/test_access.py index af959b433..950f9a42e 100644 --- a/gracedb/api/v1/superevents/tests/test_access.py +++ b/gracedb/api/v1/superevents/tests/test_access.py @@ -1351,6 +1351,8 @@ class TestSupereventLogList(AccessManagersGroupAndUserSetup, def test_lvem_user_create_log(self): """LV-EM user can create logs for exposed superevents only""" + # Create tag since external users' logs will be tagged with 'lvem' + Tag.objects.create(name=settings.EXTERNAL_ACCESS_TAGNAME) log_data = {'comment': 'test comment'} # Internal-only superevent @@ -2238,6 +2240,10 @@ class TestSupereventEMObservationList(SupereventSetup, GraceDbApiTestBase): def test_lvem_user_create_emobservation_for_exposed_superevent(self): """LV-EM user can create EMObservations for exposed superevents""" + # Have to create lvem tag since there will be a log created to + # document the EMObservation creation and it will be tagged + # with 'lvem' since it was created by an external user. + Tag.objects.create(name=settings.EXTERNAL_ACCESS_TAGNAME) url = v_reverse('superevents:superevent-emobservation-list', args=[self.lvem_superevent.superevent_id]) response = self.request_as_user(url, "POST", self.lvem_user, diff --git a/gracedb/api/v1/superevents/views.py b/gracedb/api/v1/superevents/views.py index 89253b50d..f7fe15830 100644 --- a/gracedb/api/v1/superevents/views.py +++ b/gracedb/api/v1/superevents/views.py @@ -141,8 +141,7 @@ class SupereventEventViewSet(mixins.ListModelMixin, def perform_destroy(self, instance): remove_event_from_superevent(instance.superevent, instance, self.request.user, add_superevent_log=True, - add_event_log=True, issue_superevent_alert=True, - issue_event_alert=True) + add_event_log=True, issue_alert=True) class SupereventLabelViewSet(viewsets.ModelViewSet, diff --git a/gracedb/events/view_logic.py b/gracedb/events/view_logic.py index 9b7c651c7..06db6ecfb 100644 --- a/gracedb/events/view_logic.py +++ b/gracedb/events/view_logic.py @@ -16,8 +16,8 @@ from .view_utils import eventToDict, eventLogToDict, emObservationToDict, \ labelToDict from .permission_utils import assign_default_event_perms -from alerts.old_alert import issueAlert, issueAlertForLabel, issueAlertForUpdate, \ - issueXMPPAlert +from alerts.events.utils import EventAlertIssuer, EventLabelAlertIssuer, \ + EventEMObservationAlertIssuer, EventEMBBEventLogAlertIssuer from core.vfile import VersionedFile from django.contrib.contenttypes.models import ContentType @@ -154,10 +154,7 @@ def _createEventFromForm(request, form): # Send an alert. # XXX This reverse will give the web-interface URL, not the REST URL. # This could be a problem if anybody ever tries to use it. - issueAlert(event, - request.build_absolute_uri(reverse("file-download", args=[event.graceid(),f.name])), - request.build_absolute_uri(reverse("view", args=[event.graceid()])), - eventToDict(event, request=request)) + EventAlertIssuer(event, alert_type='new').issue_alerts() except Exception, e: message = "Problem issuing an alert (%s)" % e logger.warning(message) @@ -215,12 +212,9 @@ def create_label(event, request, labelName, doAlert=True, doXMPP=True): logger.exception('Problem saving log message (%s)' % str(e)) d['error'] = str(e) - # Serialize the labelling object - serialized_label = labelToDict(labelling) - try: - issueAlertForLabel(event, label, doXMPP, event_url=event_url, - serialized_object=serialized_label) + EventLabelAlertIssuer(labelling, alert_type='label_added') \ + .issue_alerts() except Exception as e: logger.exception('Problem issuing alert (%s)' % str(e)) d['warning'] = "Problem issuing alert (%s)" % str(e) @@ -263,15 +257,10 @@ def delete_label(event, request, labelName, doXMPP=True): logger.exception('Problem saving log message (%s)' % str(e)) d['error'] = str(e) - # Serialize deleted labelling object - serialized_label = labelToDict(this_label) - # send an XMPP alert, no email or phone alerts try: - if doXMPP: - issueXMPPAlert(event, "", alert_type="update", - description="Label {0} removed".format(label.name), - serialized_object=serialized_label) + EventLabelAlertIssuer(this_label, alert_type='label_removed') \ + .issue_alerts() except Exception as e: logger.exception('Problem issuing alert (%s)' % str(e)) d['warning'] = "Problem issuing alert (%s)" % str(e) @@ -454,6 +443,11 @@ def create_eel(d, event, user): eel.validateMakeRects() eel.save() + + # Issue alert + EventEMBBEventLogAlertIssuer(eel, alert_type='embb_event_log') \ + .issue_alerts() + return eel # @@ -579,12 +573,11 @@ def create_emobservation(request, event): try: description = "New EMBB observation record for {group}".format( group=emo.group) - object = emObservationToDict(emo, request) - issueAlertForUpdate(event, description, doxmpp=True, - filename="", serialized_object=object) + EventEMObservationAlertIssuer(emo, alert_type='emobservation') \ + .issue_alerts() except Exception, e: # XXX Should probably send back warnings, as in the other cases. - pass + logger.error('error sending alert for emobservation: {0}'.format(e)) # Write a log message log = EventLog.objects.create(issuer=user, comment=description, event=event) diff --git a/gracedb/events/view_utils.py b/gracedb/events/view_utils.py index e8d344de4..1db5ca57e 100644 --- a/gracedb/events/view_utils.py +++ b/gracedb/events/view_utils.py @@ -132,7 +132,7 @@ def reverse(name, *args, **kw): #--------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------- -def eventToDict(event, columns=None, request=None): +def eventToDict(event, columns=None, request=None, is_alert=False): """Convert an Event to a dictionary.""" rv = {} graceid = event.graceid() @@ -173,7 +173,10 @@ def eventToDict(event, columns=None, request=None): # for labelling in event.labelling_set.all()]) # XXX Try to produce a dictionary of analysis specific attributes. Duck typing. # XXX These extra attributes should only be seen by internal users. - if request and request.user and not is_external(request.user): + # So we only do this part if the user account is internal *OR* if this is + # for an LVAlert + if ((request and request.user and not is_external(request.user)) or + is_alert): rv['extra_attributes'] = {} try: # GrbEvent @@ -629,6 +632,30 @@ def signoffToDict(signoff): 'signoff_type': signoff.signoff_type, } +def groupeventpermissionToDict(gop, event=None, request=None): + """Convert a group object permission to a dictionary. + Output depends on the level of specificity. + """ + + # Hacky temporary measure + if event is None: + event = gop.content_object + + rv = {} + rv['group'] = gop.group.name + rv['permission'] = gop.permission.codename + #rv['graceid'] = event.graceid() + #perm_shortname = gop.permission.codename.split('_')[0] + #rv['permission'] = perm_shortname + # We want a link to the self only. End of the line. + #rv['links'] = { + # "self" : api_reverse("events:groupeventpermission-detail", + # args=[event.graceid(),gop.group.name,perm_shortname], + # request=request) + # } + return rv + + #--------------------------------------------------------------------------------------- #--------------------------------------------------------------------------------------- # Miscellany diff --git a/gracedb/events/views.py b/gracedb/events/views.py index 24e9e83a1..fc5254bd2 100644 --- a/gracedb/events/views.py +++ b/gracedb/events/views.py @@ -30,7 +30,9 @@ from .view_utils import flexigridResponse, jqgridResponse from .view_utils import get_recent_events_string from .view_utils import eventLogToDict from .view_utils import signoffToDict -from alerts.old_alert import issueAlertForUpdate, issueXMPPAlert +from alerts.events.utils import EventAlertIssuer, EventLogAlertIssuer, \ + EventSignoffAlertIssuer, EventVOEventAlertIssuer, \ + EventPermissionsAlertIssuer from superevents.models import Superevent # Set up logging @@ -156,6 +158,9 @@ def voevent(request, event): # second argument should be a serial_number. voevent = buildVOEvent(event, voevent_type=voevent_type, request=request, internal=internal) + + # Issue alert + EventVOEventAlertIssuer(voevent, alert_type='voevent').issue_alerts() # Exceptions caused by user errors of some sort. except VOEventBuilderException, e: return HttpResponseBadRequest(str(e)) @@ -193,10 +198,12 @@ def _create(request): form = CreateEventForm(request.POST, request.FILES) if form.is_valid(): + # Alert is issued in this function event, warnings = _createEventFromForm(request, form) if not event: # problem creating event... XXX need an error page for this. raise Exception("\n".join(warnings)) + return HttpResponseRedirect(reverse(view, args=[event.graceid()])) else: rv['form'] = form @@ -287,9 +294,7 @@ def logentry(request, event, num=None): else: desc = "LOG: " fname = "" - issueAlertForUpdate(event, desc+elog.comment, doxmpp=True, - filename=fname, - serialized_object=eventLogToDict(elog, request=request)) + EventLogAlertIssuer(elog, alert_type='log').issue_alerts() except Exception as e: log.error('Error issuing alert: %s' % str(e)) return HttpResponse("Failed to send alert for log message: %s" \ @@ -913,6 +918,9 @@ def update_event_perms_for_group(event, group, action): GroupObjectPermission.objects.get_or_create( content_type=ctype, group=group, permission=change, object_pk=event.id) + + # Issue alert + EventPermissionsAlertIssuer(event, alert_type='exposed').issue_alerts() elif action=='protect': # Retrieve both group object permissions # Delete them @@ -932,6 +940,8 @@ def update_event_perms_for_group(event, group, action): except GroupObjectPermission.DoesNotExist: # Couldn't find it. Take no action. pass + # Issue alert + EventPermissionsAlertIssuer(event, alert_type='hidden').issue_alerts() # lastly event.refresh_perms() @@ -985,6 +995,7 @@ def modify_permissions(request, event): def embblogentry(request, event, num=None): if request.method == "POST": try: + # Alert is issued inside this function create_eel(request.POST, event, request.user) except ValueError, e: return HttpResponseBadRequest(str(e)) @@ -1029,6 +1040,7 @@ def embblogentry(request, event, num=None): def emobservation_entry(request, event, num=None): if request.method == "POST": try: + # Alert is issued in this function create_emobservation(request, event) except ValueError, e: return HttpResponseBadRequest(str(e)) @@ -1172,8 +1184,8 @@ def modify_signoff(request, event): pass # Issue an alert. - issueXMPPAlert(event, location='', alert_type="signoff", description=status, - serialized_object = signoffToDict(signoff)) + EventSignoffAlertIssuer(signoff, alert_type='signoff_created') \ + .issue_alerts() elif action=='edit': # get the existing object @@ -1215,6 +1227,9 @@ def modify_signoff(request, event): tag.event_logs.add(logentry) except: pass + # Issue an alert. + EventSignoffAlertIssuer(signoff, alert_type='signoff_deleted') \ + .issue_alerts() else: if status==None: msg = "Please select a valid status." @@ -1236,8 +1251,8 @@ def modify_signoff(request, event): signoff.comment = comment signoff.save() # Issue an alert. - issueXMPPAlert(event, location='', alert_type="signoff", description=status, - serialized_object = signoffToDict(signoff)) + EventSignoffAlertIssuer(signoff, alert_type='signoff_updated') \ + .issue_alerts() # Create a log message msg = "updated %s signoff status as %s" % (signoff_type, status) diff --git a/gracedb/superevents/utils.py b/gracedb/superevents/utils.py index 26b6ffed6..f44e47c7b 100644 --- a/gracedb/superevents/utils.py +++ b/gracedb/superevents/utils.py @@ -10,19 +10,17 @@ from .buildVOEvent import construct_voevent_file from .models import Superevent, Log, Labelling, EMObservation, EMFootprint, \ VOEvent, Signoff from .shortcuts import is_superevent -from alerts.events.utils import issue_alert_for_event_log -from alerts.superevents.utils import issue_alert_for_superevent_creation, \ - issue_alert_for_superevent_log, \ - issue_alert_for_superevent_label_creation, \ - issue_alert_for_superevent_label_removal, \ - issue_alert_for_superevent_emobservation, \ - issue_alert_for_superevent_voevent, issue_alert_for_superevent_signoff, \ - issue_alert_for_superevent_permissions +from alerts.events.utils import EventAlertIssuer, EventLogAlertIssuer +from alerts.superevents.utils import SupereventAlertIssuer, \ + SupereventLogAlertIssuer, SupereventLabelAlertIssuer, \ + SupereventVOEventAlertIssuer, SupereventEMObservationAlertIssuer, \ + SupereventSignoffAlertIssuer, SupereventPermissionsAlertIssuer from core.permission_utils import expose_log_to_lvem, expose_log_to_public, \ hide_log_from_lvem, hide_log_from_public, assign_perms_to_obj, \ remove_perms_from_obj from core.vfile import create_versioned_file from events.models import Event, EventLog, Tag, Label +from events.permission_utils import is_external from events.shortcuts import is_event # Set up logger @@ -40,8 +38,6 @@ SUPEREVENT_PERMS = { } -# TODO: -# Add decorator to check access permissions (??) not sure if we should do it here or in the viewset itself def create_superevent(submitter, t_start, t_0, t_end, preferred_event, events=[], labels=[], category='P', add_log_message=True, issue_alert=True): @@ -90,37 +86,36 @@ def create_superevent(submitter, t_start, t_0, t_end, preferred_event, # Superevent log message and alerts are taken care of elsewhere, but we # want to record logs for the individual events # NOTE: we don't have to worry about a repeat here for the preferred event - # since events comes directly from the serializer and hasn't been updated - # to include the preferred event (like it would be if we accessed - # s.events.all()) + # since the 'events' list comes directly from the serializer and hasn't + # been updated to include the preferred event (like it would be if we + # accessed s.events.all() directly) # Alerts aren't issued here since we want to do that *after* the superevent # creation alert is issued below. - event_log_list = [] for event in events: - _, el = add_event_to_superevent(s, event, submitter, + add_event_to_superevent(s, event, submitter, add_superevent_log=False, add_event_log=True, - issue_superevent_alert=False, issue_event_alert=False) - if el is not None: - event_log_list.append(el) + issue_alert=False) # Issue all relevant alerts if issue_alert: # Send "new" alert about superevent creation - issue_alert_for_superevent_creation(s) + SupereventAlertIssuer(s, alert_type='new').issue_alerts() - # "Manually" issue alerts for preferred_event and events - issue_alert_for_event_log(pref_event_log) + # "Manually" issue alerts for preferred_event + EventAlertIssuer(preferred_event, alert_type='selected_as_preferred') \ + .issue_alerts() - for el in event_log_list: - issue_alert_for_event_log(el) + # "Manually" issue alerts for events + for event in events: + EventAlertIssuer(event, alert_type='added_to_superevent') \ + .issue_alerts() # Add labels for label in labels: l = add_label_to_superevent(s, label, submitter, add_log_message=True, issue_alert=issue_alert) - # Look at event creation functions to see if there is anything else we should add here. - # CREATE DIRECTORY + # Create superevent data directory os.makedirs(s.datadir) return s @@ -154,26 +149,37 @@ def update_superevent(superevent, updater, add_log_message=True, update_comment = "Updated superevent parameters: {0}".format( ", ".join(updates)) update_log = create_log(updater, update_comment, superevent, - issue_alert=issue_alert) + issue_alert=False) - # Write event log messages if preferred event changed + # If preferred event changed, do a few things if new_params.has_key('preferred_event') and \ (old_params['preferred_event'] != new_params['preferred_event']): - # Old preferred event + # Write log for old preferred event old_msg = ("Removed as preferred event for superevent: " "{superevent_id}").format(superevent_id= superevent.superevent_id) old_log = create_log(updater, old_msg, - old_params['preferred_event'], issue_alert=issue_alert) + old_params['preferred_event'], issue_alert=False) - # New preferred event + # Write log for new preferred event new_msg = ("Set as preferred event for superevent: " "{superevent_id}").format(superevent_id= superevent.superevent_id) new_log = create_log(updater, new_msg, - new_params['preferred_event'], issue_alert=issue_alert) - - # TODO: issue alert separately from log creation + new_params['preferred_event'], issue_alert=False) + + # Issue alerts for both + if issue_alert: + # Old + EventAlertIssuer(old_params['preferred_event'], + alert_type='removed_as_preferred').issue_alerts() + # New + EventAlertIssuer(new_params['preferred_event'], + alert_type='selected_as_preferred').issue_alerts() + + # Superevent alerts + if issue_alert: + SupereventAlertIssuer(superevent, alert_type='update').issue_alerts() return superevent @@ -198,19 +204,21 @@ def create_log(issuer, comment, event_or_superevent, filename="", if is_superevent(event_or_superevent): log_dict['superevent'] = event_or_superevent - LogModel = Log - alert_func = issue_alert_for_superevent_log + log_attr = 'log_set' + alerter_class = SupereventLogAlertIssuer elif is_event(event_or_superevent): log_dict['event'] = event_or_superevent - LogModel = EventLog - alert_func = issue_alert_for_event_log + log_attr = 'eventlog_set' + alerter_class = EventLogAlertIssuer else: - # TODO: raise error - logger.error(type(event_or_superevent)) - pass + err_msg = "object is of type '{0}'; should be event or superevent" \ + .format(type(event_or_superevent)) + logger.error(err_msg) + raise TypeError(err_msg) # Create log object - log = LogModel.objects.create(**log_dict) + log_set = getattr(event_or_superevent, log_attr) + log = log_set.create(**log_dict) # Create versioned file if data_file: @@ -226,26 +234,36 @@ def create_log(issuer, comment, event_or_superevent, filename="", add_tag_to_log(log, t, issuer, issue_alert=False) if issue_alert: - alert_func(log) + alerter_class(log, alert_type='log').issue_alerts() - # TODO: - # If user is external, add LV-EM tagname to this log message + # If user is external, add LV-EM tagname to this log message and expose it + # TODO: should it be exposed to the public? Or to LV-EM only? We will + # stick with LV-EM only for now. + if is_external(issuer) and not autogenerated: + lvem_tag = Tag.objects.get(name=settings.EXTERNAL_ACCESS_TAGNAME) + add_tag_to_log(log, lvem_tag, issuer, add_log_message=True) return log def get_log_parent(log): - # Determine if this is an event or superevent log + """Utility function to determine if this is an event or superevent log""" + if isinstance(log, Log): return log.superevent elif isinstance(log, EventLog): return log.event else: - # TODO: raise exception - pass + err_msg = ("object is of type '{0}'; should be superevent log or " + "event log").format(type(log)) + logger.error(err_msg) + raise TypeError(err_msg) def add_tag_to_log(log, tag, user, add_log_message=True, issue_alert=False): + # Presently, we don't issue alerts for tag addition or for the logs + # that are generated as a result. + # Add tag to log log.tags.add(tag) @@ -271,13 +289,15 @@ def add_tag_to_log(log, tag, user, add_log_message=True, issue_alert=False): tag_name=tag.name) event_or_superevent = get_log_parent(log) log_for_tag_addition = create_log(user, comment, event_or_superevent, - issue_alert=issue_alert, autogenerated=True) + issue_alert=False, autogenerated=True) return log_for_tag_addition def remove_tag_from_log(log, tag, user, add_log_message=True, issue_alert=False): + # Presently, we don't issue alerts for tag addition or for the logs + # that are generated as a result. # Remove tag from log log.tags.remove(tag) @@ -304,17 +324,14 @@ def remove_tag_from_log(log, tag, user, add_log_message=True, N=log.N, tag_name=tag.name) event_or_superevent = get_log_parent(log) log_for_tag_removal = create_log(user, comment, event_or_superevent, - issue_alert=issue_alert, autogenerated=True) + issue_alert=False, autogenerated=True) + return log_for_tag_removal def add_event_to_superevent(superevent, event, user, add_event_log=True, - add_superevent_log=True, issue_event_alert=True, - issue_superevent_alert=True): - """ - We return log objects in case they are needed elsewhere - """ + add_superevent_log=True, issue_alert=True): # Check that the event is of the correct type to be added # to a superevent @@ -330,31 +347,32 @@ def add_event_to_superevent(superevent, event, user, add_event_log=True, superevent.events.add(event) # Create superevent log message to record event addtion? - superevent_log_for_event_addition = None if add_superevent_log: # Record event addition in superevent logs superevent_comment = 'Added event: {graceid}'.format( graceid=event.graceid()) superevent_log_for_event_addition = create_log(user, - superevent_comment, superevent, issue_alert=issue_superevent_alert, + superevent_comment, superevent, issue_alert=False, autogenerated=True) # Create event log message to record addition to superevent? - event_log_for_addition_to_superevent = None if add_event_log: # Record addition to superevent in event logs event_comment = 'Added to superevent: {superevent_id}'.format( superevent_id=superevent.superevent_id) event_log_for_addition_to_superevent = create_log(user, event_comment, - event, issue_alert=issue_event_alert, autogenerated=True) + event, issue_alert=False, autogenerated=True) - return superevent_log_for_event_addition, \ - event_log_for_addition_to_superevent + # Issue alerts + if issue_alert: + SupereventAlertIssuer(superevent, alert_type='event_added') \ + .issue_alerts() + EventAlertIssuer(event, alert_type='added_to_superevent') \ + .issue_alerts() def remove_event_from_superevent(superevent, event, user, add_event_log=True, - add_superevent_log=True, issue_event_alert=True, - issue_superevent_alert=True): + add_superevent_log=True, issue_alert=True): """ This function should be within a try-except block to catch exceptions and convert them to the appropriate response. @@ -374,18 +392,21 @@ def remove_event_from_superevent(superevent, event, user, add_event_log=True, superevent_comment = 'Removed event: {graceid}'.format( graceid=event.graceid()) superevent_log_for_event_removal = create_log(user, superevent_comment, - superevent, issue_alert=issue_superevent_alert, autogenerated=True) + superevent, issue_alert=False, autogenerated=True) # Create event log message to record removal from superevent? - event_log_for_removal_from_superevent = None if add_event_log: event_comment ='Removed from superevent: {superevent_id}'.format( superevent_id=superevent.superevent_id) event_log_for_removal_from_superevent = create_log(user, event_comment, - event, issue_alert=issue_event_alert, autogenerated=True) + event, issue_alert=False, autogenerated=True) - return superevent_log_for_event_removal, \ - event_log_for_removal_from_superevent + # Issue alerts + if issue_alert: + SupereventAlertIssuer(superevent, alert_type='event_removed') \ + .issue_alerts() + EventAlertIssuer(event, alert_type='removed_from_superevent') \ + .issue_alerts() def add_label_to_superevent(superevent, label, user, add_log_message=True, @@ -403,7 +424,8 @@ def add_label_to_superevent(superevent, label, user, add_log_message=True, issue_alert=False, autogenerated=True) if issue_alert: - issue_alert_for_superevent_label_creation(labelling) + SupereventLabelAlertIssuer(labelling, alert_type='label_added') \ + .issue_alerts() return labelling, log_for_label_addition @@ -425,10 +447,12 @@ def remove_label_from_superevent(labelling, user, add_log_message=True, # labelling object still exists in memory, even though it has been # removed from the database and does not have an ID anymore if issue_alert: - issue_alert_for_superevent_label_removal(labelling) + SupereventLabelAlertIssuer(labelling, alert_type='label_removed') \ + .issue_alerts() return log_for_label_removal + def get_or_create_tag(tag_name, display_name=None): tag, created = Tag.objects.get_or_create(name=tag_name) @@ -441,9 +465,11 @@ def get_or_create_tag(tag_name, display_name=None): def get_or_create_tags(tag_name_list, display_name_list=[]): - # TODO: make this a useful error + # Check that lists are the same length if display_name_list is + # provided if display_name_list and (len(display_name_list) != len(tag_name_list)): - raise ValueError('') + raise ValueError('If a list of display names is provided, it must ' + 'have the same length as the list of tag names') tag_list = [] for i, tag_name in enumerate(tag_name_list): @@ -491,9 +517,14 @@ def confirm_superevent_as_gw(superevent, user, add_log_message=True, if add_log_message: message = ("Confirmed as a gravitational wave: ID changed from " "{old} -> {new}").format(old=old_id, new=superevent.superevent_id) - gw_log = create_log(user, message, superevent, issue_alert=issue_alert, + gw_log = create_log(user, message, superevent, issue_alert=False, autogenerated=False) + # Issue alert + if issue_alert: + SupereventAlertIssuer(superevent, alert_type='confirmed_as_gw') \ + .issue_alerts() + return gw_log @@ -525,7 +556,8 @@ def create_emobservation_for_superevent(superevent, submitter, ra_list, # Issue alert if issue_alert: - issue_alert_for_superevent_emobservation(emo) + SupereventEMObservationAlertIssuer(emo, alert_type='emobservation') \ + .issue_alerts() return emo @@ -573,12 +605,12 @@ def create_voevent_for_superevent(superevent, issuer, voevent_type, # Issue an alert if issue_alert: - issue_alert_for_superevent_voevent(voevent) + SupereventVOEventAlertIssuer(voevent, alert_type='voevent') \ + .issue_alerts() return voevent -# TODO: wrap this function in a try-except block in the form def create_signoff(superevent, user, signoff_type, signoff_instrument, signoff_status, signoff_comment, add_log_message=True, issue_alert=True): @@ -611,7 +643,8 @@ def create_signoff(superevent, user, signoff_type, signoff_instrument, # Issue alert if issue_alert: - issue_alert_for_superevent_signoff(signoff) + SupereventSignoffAlertIssuer(signoff, alert_type='signoff_created') \ + .issue_alerts() return signoff @@ -626,8 +659,6 @@ def update_signoff(signoff, user, status, comment, add_log_message=True, updated_attributes = {k: v for k,v in new_data.items() if getattr(signoff, k, None) != v} old_data = {k: getattr(signoff, k) for k in updated_attributes} - logger.debug(updated_attributes) - logger.debug(old_data) # Get superevent superevent = signoff.superevent @@ -701,9 +732,10 @@ def update_signoff(signoff, user, status, comment, add_log_message=True, signoff_log = create_log(user, full_comment, superevent, issue_alert=False, tags=[em_follow]) - # Issue alert #TODO: make this specifically for a signoff update + # Issue alert if issue_alert: - issue_alert_for_superevent_signoff(signoff) + SupereventSignoffAlertIssuer(signoff, alert_type='signoff_updated') \ + .issue_alerts() return signoff @@ -747,7 +779,8 @@ def delete_signoff(signoff, user, add_log_message=True, # Alert if issue_alert: - issue_alert_for_superevent_log(signoff_log) + SupereventSignoffAlertIssuer(signoff, alert_type='signoff_deleted') \ + .issue_alerts() def expose_superevent(superevent, user, add_log_message=True, @@ -775,7 +808,8 @@ def expose_superevent(superevent, user, add_log_message=True, # Send alert if issue_alert: - issue_alert_for_superevent_permissions(superevent) + SupereventPermissionsAlertIssuer(superevent, alert_type='exposed') \ + .issue_alerts() def hide_superevent(superevent, user, add_log_message=True, @@ -803,5 +837,5 @@ def hide_superevent(superevent, user, add_log_message=True, # Send alert if issue_alert: - issue_alert_for_superevent_permissions(superevent) - + SupereventPermissionsAlertIssuer(superevent, alert_type='hidden') \ + .issue_alerts() -- GitLab