Skip to content

Commit

Permalink
Merge pull request #813 from hmpf/filter-by-event-type-list
Browse files Browse the repository at this point in the history
* Change filtering by event type to a list

When filtering notifications we have the choice to filter one event type
This change makes it to filter by a list, so that we can get
notifications about events of multiple types

* Change docs to show filtering by event type list
* Amend tests to filtering by event type list
* Add more tests for filtering by event types
* Add tests for posting filter with event types
* Migrate: change filter.event_type from str to list
* Hide event_type from V1 Filter API, it should never have been visible anyway.
* Move FilterPreviewSerializer to where it belongs

---------

Co-authored-by: Johanna England <[email protected]>
  • Loading branch information
hmpf and johannaengland authored May 15, 2024
2 parents c026ee0 + 56fc93a commit c00c45f
Show file tree
Hide file tree
Showing 17 changed files with 313 additions and 55 deletions.
1 change: 1 addition & 0 deletions changelog.d/699.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add filtering of events by a list of event types
3 changes: 3 additions & 0 deletions changelog.d/699.removed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Removed `"event_type"` from the V1 Filter API, it should only have been
available in V2 (since it was new) and it has never been in use by the
frontend.
5 changes: 2 additions & 3 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -853,8 +853,7 @@ Notification profile endpoints
"open": true,
"acked": false,
"stateful": true,
"maxlevel": 1,
"event_type": "STA"
"maxlevel": 1
}
}
Expand Down Expand Up @@ -1842,7 +1841,7 @@ Notification profile endpoints
"acked": false,
"stateful": true,
"maxlevel": 1,
"event_type": "STA"
"event_types": ["STA"]
}
}
Expand Down
29 changes: 27 additions & 2 deletions src/argus/notificationprofile/V1/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,44 @@

from rest_framework import fields, serializers

from argus.incident.constants import INCIDENT_LEVELS
from ..models import DestinationConfig, Filter, NotificationProfile
from ..primitive_serializers import FilterBlobSerializer
from ..primitive_serializers import CustomMultipleChoiceField
from ..serializers import TimeslotSerializer
from ..validators import validate_filter_string


class FilterPreviewSerializer(serializers.Serializer):
sourceSystemIds = serializers.ListField(child=serializers.IntegerField(min_value=1), allow_empty=True)
tags = serializers.ListField(child=serializers.CharField(min_length=3), allow_empty=True)


class FilterBlobSerializerV1(serializers.Serializer):
sourceSystemIds = serializers.ListField(
child=serializers.IntegerField(min_value=1),
allow_empty=True,
required=False,
)
tags = serializers.ListField(
child=serializers.CharField(min_length=3),
allow_empty=True,
required=False,
)
open = serializers.BooleanField(required=False, allow_null=True)
acked = serializers.BooleanField(required=False, allow_null=True)
stateful = serializers.BooleanField(required=False, allow_null=True)
maxlevel = serializers.IntegerField(
required=False, allow_null=True, max_value=max(INCIDENT_LEVELS), min_value=min(INCIDENT_LEVELS)
)


class FilterSerializerV1(serializers.ModelSerializer):
filter_string = serializers.CharField(
validators=[validate_filter_string],
help_text='Deprecated: Use "filter" instead',
required=False,
)
filter = FilterBlobSerializer(required=False)
filter = FilterBlobSerializerV1(required=False)

class Meta:
model = Filter
Expand Down
6 changes: 3 additions & 3 deletions src/argus/notificationprofile/V1/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from argus.drf.permissions import IsOwner
from argus.incident.serializers import IncidentSerializer
from ..models import Filter, NotificationProfile
from ..primitive_serializers import FilterBlobSerializer
from .serializers import (
FilterSerializerV1,
FilterBlobSerializerV1,
FilterPreviewSerializer,
ResponseNotificationProfileSerializerV1,
RequestNotificationProfileSerializerV1,
)
from ..serializers import FilterPreviewSerializer


class FilterViewSetV1(viewsets.ModelViewSet):
Expand Down Expand Up @@ -110,7 +110,7 @@ def preview(self, request, **_):
Will eventually take over for the filterpreview endpoint
"""
filter_dict = request.data
serializer = FilterBlobSerializer(data=filter_dict)
serializer = FilterBlobSerializerV1(data=filter_dict)
if not serializer.is_valid():
raise ValidationError(serializer.errors)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 4.2.11 on 2024-05-14 07:51

from django.db import migrations


def change_event_type_to_a_list_in_filter(apps, schema_editor):
Filter = apps.get_model("argus_notificationprofile", "Filter")

for filter_ in Filter.objects.filter(filter__event_type__isnull=False):
if filter_.filter["event_type"]:
filter_.filter["event_types"] = [filter_.filter["event_type"]]
filter_.save()
del filter_.filter["event_type"]
filter_.save()


def change_event_types_to_a_string_in_filter(apps, schema_editor):
# will lose data
Filter = apps.get_model("argus_notificationprofile", "Filter")

for filter_ in Filter.objects.filter(filter__event_types__isnull=False):
if filter_.filter["event_types"]:
filter_.filter["event_type"] = filter_.filter["event_types"][0]
filter_.save()
del filter_.filter["event_types"]
filter_.save()


class Migration(migrations.Migration):
dependencies = [
("argus_notificationprofile", "0016_noop"),
]

operations = [
migrations.RunPython(
change_event_type_to_a_list_in_filter,
change_event_types_to_a_string_in_filter,
)
]
14 changes: 7 additions & 7 deletions src/argus/notificationprofile/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ def is_maxlevel_empty(self):
fallback_filter = self.fallback_filter.get("maxlevel", None)
return not self.filter.get("maxlevel", fallback_filter)

def is_event_type_empty(self):
fallback_filter = self.fallback_filter.get("event_type", None)
return not self.filter.get("event_type", fallback_filter)
def is_event_types_empty(self):
fallback_filter = self.fallback_filter.get("event_types", None)
return not self.filter.get("event_types", fallback_filter)

def are_source_system_ids_empty(self):
fallback_filter = self.fallback_filter.get("sourceSystemIds", None)
Expand All @@ -136,7 +136,7 @@ def is_empty(self):
and self.are_tags_empty()
and self.are_tristates_empty()
and self.is_maxlevel_empty()
and self.is_event_type_empty()
and self.is_event_types_empty()
)

def get_incident_tristate_checks(self, incident) -> Dict[str, TriState]:
Expand All @@ -161,10 +161,10 @@ def incident_fits_maxlevel(self, incident):
return incident.level <= min(filter(None, (self.filter["maxlevel"], fallback_filter)))

def event_fits(self, event):
if self.is_event_type_empty():
if self.is_event_types_empty():
return True
fallback_filter = self.fallback_filter.get("event_type", None)
return event.type == self.filter.get("event_type", fallback_filter)
fallback_filter = self.fallback_filter.get("event_types", None)
return event.type in self.filter.get("event_types", fallback_filter)


class Filter(models.Model):
Expand Down
29 changes: 3 additions & 26 deletions src/argus/notificationprofile/primitive_serializers.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,6 @@
from rest_framework import serializers

from argus.incident.constants import INCIDENT_LEVELS
from argus.incident.models import Event


class FilterBlobSerializer(serializers.Serializer):
sourceSystemIds = serializers.ListField(
child=serializers.IntegerField(min_value=1),
allow_empty=True,
required=False,
)
tags = serializers.ListField(
child=serializers.CharField(min_length=3),
allow_empty=True,
required=False,
)
open = serializers.BooleanField(required=False, allow_null=True)
acked = serializers.BooleanField(required=False, allow_null=True)
stateful = serializers.BooleanField(required=False, allow_null=True)
maxlevel = serializers.IntegerField(
required=False, allow_null=True, max_value=max(INCIDENT_LEVELS), min_value=min(INCIDENT_LEVELS)
)
event_type = serializers.ChoiceField(choices=Event.Type.choices, required=False, allow_null=True)


class FilterPreviewSerializer(serializers.Serializer):
sourceSystemIds = serializers.ListField(child=serializers.IntegerField(min_value=1), allow_empty=True)
tags = serializers.ListField(child=serializers.CharField(min_length=3), allow_empty=True)
class CustomMultipleChoiceField(serializers.MultipleChoiceField):
def to_internal_value(self, value):
return list(super().to_internal_value(value))
27 changes: 26 additions & 1 deletion src/argus/notificationprofile/serializers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
from rest_framework import fields, serializers

from .primitive_serializers import FilterBlobSerializer, FilterPreviewSerializer
from argus.incident.constants import INCIDENT_LEVELS
from argus.incident.models import Event
from .primitive_serializers import CustomMultipleChoiceField
from .media import api_safely_get_medium_object
from .models import DestinationConfig, Filter, Media, NotificationProfile, TimeRecurrence, Timeslot


class FilterBlobSerializer(serializers.Serializer):
sourceSystemIds = serializers.ListField(
child=serializers.IntegerField(min_value=1),
allow_empty=True,
required=False,
)
tags = serializers.ListField(
child=serializers.CharField(min_length=3),
allow_empty=True,
required=False,
)
open = serializers.BooleanField(required=False, allow_null=True)
acked = serializers.BooleanField(required=False, allow_null=True)
stateful = serializers.BooleanField(required=False, allow_null=True)
maxlevel = serializers.IntegerField(
required=False, allow_null=True, max_value=max(INCIDENT_LEVELS), min_value=min(INCIDENT_LEVELS)
)
event_types = CustomMultipleChoiceField(
choices=Event.Type.choices,
required=False,
)


class TimeRecurrenceSerializer(serializers.ModelSerializer):
ALL_DAY_KEY = "all_day"

Expand Down
2 changes: 1 addition & 1 deletion src/argus/notificationprofile/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from rest_framework import serializers

from .constants import DEPRECATED_FILTER_NAMES
from .primitive_serializers import FilterBlobSerializer
from .serializers import FilterBlobSerializer


def validate_filter_string(value: Union[str, dict]):
Expand Down
4 changes: 1 addition & 3 deletions src/argus/notificationprofile/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@
from argus.notificationprofile.media import api_safely_get_medium_object
from argus.notificationprofile.media.base import NotificationMedium
from .models import DestinationConfig, Filter, Media, NotificationProfile, Timeslot
from .primitive_serializers import FilterBlobSerializer
from .serializers import (
DuplicateDestinationSerializer,
FilterSerializer,
FilterPreviewSerializer,
FilterBlobSerializer,
JSONSchemaSerializer,
MediaSerializer,
ResponseDestinationConfigSerializer,
Expand Down Expand Up @@ -245,7 +244,6 @@ def destroy(self, request, *args, **kwargs):

# TODO: change HTTP method to GET, and get query data from URL
class FilterPreviewView(APIView):
@extend_schema(request=FilterPreviewSerializer, responses={"200": IncidentSerializer})
def post(self, request, format=None):
"""
POST a filter, get a list of filtered incidents back
Expand Down
13 changes: 13 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.db import connection
from django.db.migrations.executor import MigrationExecutor


class Migrator:
def __init__(self, connection=connection):
self.executor = MigrationExecutor(connection)

def migrate(self, app_label: str, migration: str):
target = [(app_label, migration)]
self.executor.loader.build_graph()
self.executor.migrate(target)
self.apps = self.executor.loader.project_state(target).apps
Empty file.
Loading

0 comments on commit c00c45f

Please sign in to comment.