Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/sentry/deletions/defaults/monitor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
from collections.abc import Sequence

from sentry import quotas
from sentry.constants import DataCategory
from sentry.deletions.base import (
BaseRelation,
BulkModelDeletionTask,
Expand All @@ -17,3 +21,8 @@ def get_child_relations(self, instance: Monitor) -> list[BaseRelation]:
),
ModelRelation(models.MonitorEnvironment, {"monitor_id": instance.id}),
]

def delete_instance_bulk(self, instance_list: Sequence[Monitor]) -> None:
if instance_list:
quotas.backend.remove_seats(DataCategory.MONITOR_SEAT, instance_list)
super().delete_instance_bulk(instance_list)
12 changes: 12 additions & 0 deletions src/sentry/quotas/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,18 @@ def remove_seat(self, data_category: DataCategory, seat_object: SeatObject) -> N
Removes an assigned seat.
"""

def remove_seats(self, data_category: DataCategory, seat_objects: Sequence[SeatObject]) -> None:
"""
Removes assigned seats for a batch of objects.

The default implementation simply iterates over ``seat_objects`` and calls
``remove_seat`` for each item. Backends that need to perform additional bookkeeping
(for example, reducing database queries) can override this method to implement a more
efficient bulk removal.
"""
for seat_object in seat_objects:
self.remove_seat(data_category, seat_object)

def check_accept_monitor_checkin(self, project_id: int, monitor_slug: str):
"""
Will return a `PermitCheckInStatus`.
Expand Down
22 changes: 22 additions & 0 deletions tests/sentry/deletions/test_monitor.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from unittest import mock

from sentry.constants import DataCategory
from sentry.deletions.tasks.scheduled import run_scheduled_deletions
from sentry.models.environment import Environment
from sentry.models.project import Project
Expand Down Expand Up @@ -46,3 +49,22 @@ def test_simple(self) -> None:
# Shared objects should continue to exist.
assert Environment.objects.filter(id=env.id).exists()
assert Project.objects.filter(id=project.id).exists()

@mock.patch("sentry.deletions.defaults.monitor.quotas.backend.remove_seats")
def test_removes_monitor_seats_before_delete(self, mock_remove_seats: mock.MagicMock) -> None:
project = self.create_project(name="with-seats")
monitor = Monitor.objects.create(
organization_id=project.organization.id,
project_id=project.id,
config={"schedule": "* * * * *", "schedule_type": ScheduleType.CRONTAB},
)

self.ScheduledDeletion.schedule(instance=monitor, days=0)

with self.tasks():
run_scheduled_deletions()

mock_remove_seats.assert_called_once()
args, _ = mock_remove_seats.call_args
assert args[0] == DataCategory.MONITOR_SEAT
assert list(args[1]) == [monitor]
Loading