Skip to content

Commit 50c2189

Browse files
committed
feat: add filter in admin
can filter on - env - command - is_spamming_sentry
1 parent d129661 commit 50c2189

File tree

9 files changed

+175
-0
lines changed

9 files changed

+175
-0
lines changed

controller/sentry/admin.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from django_object_actions import DjangoObjectActions, takes_instance_or_queryset
2020

2121
from controller.sentry.choices import EventType, MetricType
22+
from controller.sentry.filters import IsSpammingListFilter
2223
from controller.sentry.forms import BumpForm, MetricForm
2324
from controller.sentry.inlines import AppEventInline, ProjectEventInline
2425
from controller.sentry.mixins import ChartMixin, PrettyTypeMixin, ProjectLinkMixin
@@ -181,6 +182,8 @@ class AppAdmin(
181182
"celery_collect_metrics",
182183
]
183184

185+
list_filter = ["env", "command", IsSpammingListFilter]
186+
184187
search_fields = ["reference", "project__sentry_project_slug", "env", "command"]
185188
ordering = search_fields
186189

controller/sentry/filters.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"""Filters."""
2+
3+
from typing import TYPE_CHECKING
4+
5+
from django.contrib import admin
6+
from django.utils.translation import gettext_lazy as _
7+
8+
from controller.sentry.choices import EventType
9+
10+
if TYPE_CHECKING: # pragma: no cover
11+
from django.contrib.admin import ModelAdmin
12+
from django.db.models import QuerySet
13+
from django.http import HttpRequest
14+
15+
from controller.sentry.models import App
16+
17+
18+
class IsSpammingListFilter(admin.SimpleListFilter):
19+
"""Is Spamming filter."""
20+
21+
title = _("Spamming Sentry")
22+
23+
parameter_name = "spamming"
24+
25+
def lookups(self, request: "HttpRequest", model_admin: "ModelAdmin") -> tuple:
26+
"""Lookup.
27+
28+
Args:
29+
request (HttpRequest): The request
30+
model_admin (ModelAdmin): The admin
31+
32+
Returns:
33+
tuple: The lookup field
34+
"""
35+
return (
36+
("yes", _("Yes")),
37+
("no", _("No")),
38+
)
39+
40+
def queryset(self, request: "HttpRequest", queryset: "QuerySet[App]") -> "QuerySet[App]":
41+
"""Return the filtered queryset.
42+
43+
Args:
44+
request (HttpRequest): The request
45+
queryset (QuerySet[App]): The queryset
46+
47+
Returns:
48+
QuerySet[App]: The filtered queryset
49+
"""
50+
if (value := self.value()) is None:
51+
return queryset
52+
53+
event_type = EventType.FIRING if value == "no" else EventType.DISCARD
54+
55+
return queryset.filter(project__last_event__type=event_type)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 4.1.6 on 2023-02-14 16:03
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
("sentry", "0012_project_detection_result"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="project",
16+
name="last_event",
17+
field=models.ForeignKey(
18+
blank=True,
19+
null=True,
20+
on_delete=django.db.models.deletion.SET_NULL,
21+
related_name="+",
22+
to="sentry.event",
23+
),
24+
),
25+
]

controller/sentry/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Project(models.Model):
3535

3636
detection_param = models.JSONField(default=partial(settings_default_value, "DEFAULT_SPIKE_DETECTION_PARAM"))
3737
detection_result = models.JSONField(blank=True, null=True)
38+
last_event = models.ForeignKey("Event", null=True, blank=True, on_delete=models.SET_NULL, related_name="+")
3839

3940
def __str__(self) -> str:
4041
"""Return Project string."""

controller/sentry/tasks.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,6 @@ def perform_detect(sentry_id) -> None:
161161
previous_signal = signal
162162

163163
Event.objects.bulk_create(events)
164+
if events:
165+
project.last_event = events[-1]
166+
project.save()

controller/sentry/tests/test_admin.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from controller.sentry.admin import AppAdmin
1111
from controller.sentry.choices import EventType
12+
from controller.sentry.filters import IsSpammingListFilter
1213
from controller.sentry.forms import BumpForm, MetricForm
1314
from controller.sentry.inlines import AppEventInline, ProjectEventInline
1415
from controller.sentry.models import App, Event, Project
@@ -456,3 +457,39 @@ def test_project_chart(super_call: Mock, admin_with_user):
456457
super_call.assert_called_once_with(request, project.sentry_id, "", None)
457458

458459
assert response.context_data == expected_context
460+
461+
462+
@pytest.mark.parametrize("user_group", ["Developer"])
463+
@pytest.mark.admin_site(model_class=App)
464+
def test_app_filter(admin_with_user):
465+
site, request = admin_with_user
466+
467+
project = Project.objects.create(sentry_id="123")
468+
event = Event.objects.create(project=project, type=EventType.DISCARD, timestamp=timezone.now())
469+
project.last_event = event
470+
project.save()
471+
app_discard = App.objects.create(reference="app_discard", project=project)
472+
473+
project = Project.objects.create(sentry_id="456")
474+
event = Event.objects.create(project=project, type=EventType.FIRING, timestamp=timezone.now())
475+
project.last_event = event
476+
project.save()
477+
app_firing = App.objects.create(reference="app_firing", project=project)
478+
479+
App.objects.create(reference="app_without")
480+
481+
filter_yes = IsSpammingListFilter(None, {"spamming": "yes"}, App, site)
482+
filter_no = IsSpammingListFilter(None, {"spamming": "no"}, App, site)
483+
filter_none = IsSpammingListFilter(None, {}, App, site)
484+
485+
assert filter_yes.lookups(request, site) == (("yes", "Yes"), ("no", "No"))
486+
487+
assert list(filter_yes.queryset(request, App.objects.all())) == list(
488+
App.objects.filter(reference=app_discard.reference)
489+
)
490+
491+
assert list(filter_no.queryset(request, App.objects.all())) == list(
492+
App.objects.filter(reference=app_firing.reference)
493+
)
494+
495+
assert list(filter_none.queryset(request, App.objects.all())) == list(App.objects.all())

controller/sentry/tests/test_tasks.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,49 @@ def test_perform_detect(spike_detector_mock: MagicMock, client_mock: MagicMock):
185185

186186
project.refresh_from_db()
187187
assert project.events.exclude(reference=event.reference).count() == 2
188+
assert project.last_event == project.events.last()
189+
assert project.detection_result == dump
190+
191+
192+
@patch("controller.sentry.tasks.PaginatedSentryClient")
193+
@patch("controller.sentry.tasks.SpikesDetector")
194+
@pytest.mark.django_db
195+
def test_perform_detect_no_new(spike_detector_mock: MagicMock, client_mock: MagicMock):
196+
sentry_id = "123"
197+
project = Project(sentry_id=sentry_id)
198+
project.save()
199+
200+
event = Event(project=project, type=EventType.DISCARD, timestamp=parser.parse("2023-02-01T17:00:00Z"))
201+
event.save()
202+
203+
result = object()
204+
client_mock.return_value.get_stats.return_value = result
205+
206+
detector: MagicMock = spike_detector_mock.from_project.return_value
207+
dump = {"test": "a"}
208+
detector.compute_sentry.return_value = (
209+
OrderedDict(
210+
[
211+
("2023-02-01T15:00:00Z", 0),
212+
("2023-02-01T16:00:00Z", 1),
213+
("2023-02-01T17:00:00Z", 0),
214+
]
215+
),
216+
dump,
217+
)
218+
219+
perform_detect(sentry_id)
220+
221+
client_mock.assert_called_once_with()
222+
client_mock.return_value.get_stats.assert_called_once_with(sentry_id)
223+
224+
spike_detector_mock.from_project.assert_called_once_with(project)
225+
226+
detector.compute_sentry.assert_called_once_with(result)
227+
228+
project.refresh_from_db()
229+
assert project.events.exclude(reference=event.reference).count() == 0
230+
assert project.last_event is None
188231
assert project.detection_result == dump
189232

190233

docs/references/sentry.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ Submodules
2323
sentry/choices
2424
sentry/detector
2525
sentry/exceptions
26+
sentry/filters
2627
sentry/forms
2728
sentry/inlines
2829
sentry/mixins

docs/references/sentry/filters.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Filters
2+
=======
3+
4+
.. automodule:: controller.sentry.filters
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:

0 commit comments

Comments
 (0)