-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
feat(ACI): Delete endpoint for a detector #84279
base: master
Are you sure you want to change the base?
Changes from 12 commits
57af780
b06a41e
1052592
0259a25
a11b6f3
c16df7a
5e28ceb
f6e9c87
fda76f6
7620751
f069889
f1891a5
716e888
845a106
3f79434
cfeb4de
ac55163
f6ceec5
75cb83f
763532c
e1a5682
5b25ea8
96e4232
4265fea
add6b3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from sentry.deletions.base import BaseRelation, ModelDeletionTask, ModelRelation | ||
from sentry.workflow_engine.models.data_source import DataSource | ||
|
||
|
||
class DataSourceDeletionTask(ModelDeletionTask[DataSource]): | ||
def get_child_relations(self, instance: DataSource) -> list[BaseRelation]: | ||
from sentry.snuba.models import QuerySubscription | ||
|
||
return [ModelRelation(QuerySubscription, {"id": instance.query_id})] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from sentry.deletions.base import BaseRelation, ModelDeletionTask, ModelRelation | ||
from sentry.workflow_engine.models.detector import Detector | ||
|
||
|
||
class DetectorDeletionTask(ModelDeletionTask[Detector]): | ||
def get_child_relations(self, instance: Detector) -> list[BaseRelation]: | ||
from sentry.workflow_engine.models import DataConditionGroup, DataSource | ||
|
||
# XXX: this assumes a data source is connected to a single detector. it's not possible in the UI | ||
# to do anything else today, but if this changes we'll need to add custom conditional deletion logic | ||
|
||
model_relations: list[BaseRelation] = [ModelRelation(DataSource, {"detector": instance.id})] | ||
|
||
if instance.workflow_condition_group: | ||
model_relations.append( | ||
ModelRelation(DataConditionGroup, {"id": instance.workflow_condition_group.id}) | ||
) | ||
ceorourke marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return model_relations |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,10 +11,14 @@ | |
from sentry.apidocs.constants import ( | ||
RESPONSE_BAD_REQUEST, | ||
RESPONSE_FORBIDDEN, | ||
RESPONSE_NO_CONTENT, | ||
RESPONSE_NOT_FOUND, | ||
RESPONSE_UNAUTHORIZED, | ||
) | ||
from sentry.apidocs.parameters import DetectorParams, GlobalParams | ||
from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion | ||
from sentry.grouping.grouptype import ErrorGroupType | ||
from sentry.models.project import Project | ||
from sentry.workflow_engine.endpoints.serializers import DetectorSerializer | ||
from sentry.workflow_engine.models import Detector | ||
|
||
|
@@ -57,7 +61,7 @@ def convert_args(self, request: Request, detector_id, *args, **kwargs): | |
404: RESPONSE_NOT_FOUND, | ||
}, | ||
) | ||
def get(self, request: Request, project, detector): | ||
def get(self, request: Request, project: Project, detector: Detector): | ||
""" | ||
Fetch a detector | ||
````````````````````````` | ||
|
@@ -69,3 +73,27 @@ def get(self, request: Request, project, detector): | |
DetectorSerializer(), | ||
) | ||
return Response(serialized_detector) | ||
|
||
@extend_schema( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🥔 for adding documentation to the endpoint from the beginning. People make experimental endpoints left and right and the endpoint sits there for long before publishing so everyone forgets the context 😅 |
||
operation_id="Delete a Detector", | ||
parameters=[ | ||
GlobalParams.ORG_ID_OR_SLUG, | ||
GlobalParams.PROJECT_ID_OR_SLUG, | ||
DetectorParams.DETECTOR_ID, | ||
], | ||
responses={ | ||
204: RESPONSE_NO_CONTENT, | ||
403: RESPONSE_FORBIDDEN, | ||
404: RESPONSE_NOT_FOUND, | ||
}, | ||
) | ||
def delete(self, request: Request, project: Project, detector: Detector): | ||
ceorourke marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
Delete a detector | ||
""" | ||
if detector.type == ErrorGroupType.slug: | ||
return Response(status=403) | ||
|
||
RegionScheduledDeletion.schedule(detector, days=0, actor=request.user) | ||
# TODO add audit log entry | ||
return Response(status=204) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from sentry.deletions.tasks.scheduled import run_scheduled_deletions | ||
from sentry.incidents.grouptype import MetricAlertFire | ||
from sentry.snuba.models import QuerySubscription | ||
from sentry.testutils.hybrid_cloud import HybridCloudTestMixin | ||
from sentry.workflow_engine.models import ( | ||
DataCondition, | ||
DataConditionGroup, | ||
DataSource, | ||
DataSourceDetector, | ||
Detector, | ||
DetectorWorkflow, | ||
) | ||
from tests.sentry.workflow_engine.test_base import BaseWorkflowTest | ||
|
||
|
||
class DeleteDetectorTest(BaseWorkflowTest, HybridCloudTestMixin): | ||
def setUp(self): | ||
self.data_condition_group = self.create_data_condition_group() | ||
self.data_condition = self.create_data_condition(condition_group=self.data_condition_group) | ||
self.snuba_query = self.create_snuba_query() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the query subscription is deleted does that mean we always want to delete the related snuba query? We don't have this set up today but I'm not sure why - if I implemented it for this it could affect other things. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we could do that. In the past we didn't because potentially one snuba query could be shared, but in practice we haven't really used that. Could make sense to just check that there aren't other links to the snuba query and then delete it if orphaned. |
||
self.subscription = QuerySubscription.objects.create( | ||
project=self.project, | ||
status=QuerySubscription.Status.ACTIVE.value, | ||
subscription_id="123", | ||
snuba_query=self.snuba_query, | ||
) | ||
self.data_source = self.create_data_source( | ||
organization=self.organization, query_id=self.subscription.id | ||
) | ||
self.detector = self.create_detector( | ||
project_id=self.project.id, | ||
name="Test Detector", | ||
type=MetricAlertFire.slug, | ||
workflow_condition_group=self.data_condition_group, | ||
) | ||
self.workflow = self.create_workflow() | ||
self.data_source_detector = self.create_data_source_detector( | ||
data_source=self.data_source, detector=self.detector | ||
) | ||
self.detector_workflow = DetectorWorkflow.objects.create( | ||
detector=self.detector, workflow=self.workflow | ||
) | ||
|
||
def test_simple(self): | ||
data_source_2 = self.create_data_source(organization=self.organization) | ||
data_source_detector_2 = self.create_data_source_detector( | ||
data_source=data_source_2, detector=self.detector | ||
) | ||
self.ScheduledDeletion.schedule(instance=self.detector, days=0) | ||
|
||
with self.tasks(): | ||
run_scheduled_deletions() | ||
|
||
assert not Detector.objects.filter(id=self.detector.id).exists() | ||
assert not DataSourceDetector.objects.filter( | ||
id__in=[self.data_source_detector.id, data_source_detector_2.id] | ||
).exists() | ||
assert not DetectorWorkflow.objects.filter(id=self.detector_workflow.id).exists() | ||
assert not DataConditionGroup.objects.filter(id=self.data_condition_group.id).exists() | ||
assert not DataCondition.objects.filter(id=self.data_condition.id).exists() | ||
assert not DataSource.objects.filter( | ||
id__in=[self.data_source.id, data_source_2.id] | ||
).exists() | ||
assert not QuerySubscription.objects.filter(id=self.subscription.id).exists() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we also delete
DetectorWorkflow
if that relationship exists? as well as related lookup tables (e.g.AlertRuleDetector
?)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DetectorWorkflow
does get deleted automatically via cascade delete, the test checks for it. I'm not sure that we want to delete the migration tables data here since that will be temporary.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just note that with cascade delete if there are a large number of related rows the delete can fail. That's probably unlikely here, but jfyi