Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to filter events by a given list of event types #813

Merged
merged 8 commits into from
May 15, 2024
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
Loading