Skip to content

Commit c2f1693

Browse files
authored
feat(workflow_engine): Add DELETE Workflow Endpoint (#86246)
## Description PR to add support for `DELETE` method on workflows. This will schedule the deletion and use the safe workflow delete to ensure all subsequent models are also cleaned up.
1 parent 49ed7fc commit c2f1693

File tree

3 files changed

+123
-5
lines changed

3 files changed

+123
-5
lines changed

src/sentry/audit_log/register.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -589,23 +589,23 @@
589589
default_manager.add(
590590
AuditLogEvent(
591591
event_id=213,
592-
name="WORFKLOW_ADD",
592+
name="WORKFLOW_ADD",
593593
api_name="workflow.add",
594594
template="added workflow {name}",
595595
)
596596
)
597597
default_manager.add(
598598
AuditLogEvent(
599599
event_id=214,
600-
name="WORFKLOW_EDIT",
600+
name="WORKFLOW_EDIT",
601601
api_name="workflow.edit",
602602
template="edited workflow {name}",
603603
)
604604
)
605605
default_manager.add(
606606
AuditLogEvent(
607607
event_id=215,
608-
name="WORFKLOW_REMOVE",
608+
name="WORKFLOW_REMOVE",
609609
api_name="workflow.remove",
610610
template="removed workflow {name}",
611611
)

src/sentry/workflow_engine/endpoints/organization_workflow_details.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from rest_framework.request import Request
33
from rest_framework.response import Response
44

5+
from sentry import audit_log
56
from sentry.api.api_owners import ApiOwner
67
from sentry.api.api_publish_status import ApiPublishStatus
78
from sentry.api.base import region_silo_endpoint
@@ -15,7 +16,9 @@
1516
RESPONSE_UNAUTHORIZED,
1617
)
1718
from sentry.apidocs.parameters import GlobalParams, WorkflowParams
19+
from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion
1820
from sentry.models.organization import Organization
21+
from sentry.utils.audit import create_audit_entry
1922
from sentry.workflow_engine.endpoints.serializers import WorkflowSerializer
2023
from sentry.workflow_engine.models import Workflow
2124

@@ -69,10 +72,27 @@ def put(self, request: Request, organization: Organization, workflow: Workflow):
6972
"""
7073
Updates a workflow
7174
"""
75+
create_audit_entry(
76+
request=request,
77+
organization=organization,
78+
target_object=workflow.id,
79+
event=audit_log.get_event_id("WORKFLOW_EDIT"),
80+
data=workflow.get_audit_log_data(),
81+
)
82+
7283
pass
7384

7485
def delete(self, request: Request, organization: Organization, workflow: Workflow):
7586
"""
7687
Delete a workflow
7788
"""
78-
pass
89+
RegionScheduledDeletion.schedule(workflow, days=0, actor=request.user)
90+
create_audit_entry(
91+
request=request,
92+
organization=organization,
93+
target_object=workflow.id,
94+
event=audit_log.get_event_id("WORKFLOW_REMOVE"),
95+
data=workflow.get_audit_log_data(),
96+
)
97+
98+
return Response(status=204)

tests/sentry/workflow_engine/endpoints/test_organization_workflow_details.py

+99-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
1+
from sentry import audit_log
12
from sentry.api.serializers import serialize
3+
from sentry.deletions.models.scheduleddeletion import RegionScheduledDeletion
4+
from sentry.deletions.tasks.scheduled import run_scheduled_deletions
5+
from sentry.models.auditlogentry import AuditLogEntry
6+
from sentry.silo.base import SiloMode
27
from sentry.testutils.cases import APITestCase
3-
from sentry.testutils.silo import region_silo_test
8+
from sentry.testutils.helpers import TaskRunner
9+
from sentry.testutils.outbox import outbox_runner
10+
from sentry.testutils.silo import assume_test_silo_mode, region_silo_test
11+
from sentry.workflow_engine.models import Action, DataConditionGroup
12+
from tests.sentry.workflow_engine.test_base import BaseWorkflowTest
413

514

615
class OrganizationWorkflowDetailsBaseTest(APITestCase):
@@ -20,3 +29,92 @@ def test_simple(self):
2029

2130
def test_does_not_exist(self):
2231
self.get_error_response(self.organization.slug, 3, status_code=404)
32+
33+
34+
@region_silo_test
35+
class OrganizationDeleteWorkflowTest(OrganizationWorkflowDetailsBaseTest, BaseWorkflowTest):
36+
method = "DELETE"
37+
38+
def tasks(self):
39+
return TaskRunner()
40+
41+
def setUp(self):
42+
super().setUp()
43+
self.workflow = self.create_workflow(organization_id=self.organization.id)
44+
45+
def test_simple(self):
46+
with outbox_runner():
47+
self.get_success_response(self.organization.slug, self.workflow.id)
48+
49+
assert RegionScheduledDeletion.objects.filter(
50+
model_name="Workflow",
51+
object_id=self.workflow.id,
52+
).exists()
53+
54+
def test_audit_entry(self):
55+
with outbox_runner():
56+
self.get_success_response(self.organization.slug, self.workflow.id)
57+
58+
with assume_test_silo_mode(SiloMode.CONTROL):
59+
assert AuditLogEntry.objects.filter(
60+
target_object=self.workflow.id,
61+
event=audit_log.get_event_id("WORKFLOW_REMOVE"),
62+
actor=self.user,
63+
).exists()
64+
65+
def test_does_not_exist(self):
66+
with outbox_runner():
67+
response = self.get_error_response(self.organization.slug, -1)
68+
assert response.status_code == 404
69+
70+
# Ensure it wasn't deleted
71+
assert not RegionScheduledDeletion.objects.filter(
72+
model_name="Workflow",
73+
object_id=self.workflow.id,
74+
).exists()
75+
76+
def test_delete_configured_workflow__action(self):
77+
action_condition_group, action = self.create_workflow_action(workflow=self.workflow)
78+
79+
with outbox_runner():
80+
self.get_success_response(self.organization.slug, self.workflow.id)
81+
82+
# Ensure the workflow is scheduled for deletion
83+
assert RegionScheduledDeletion.objects.filter(
84+
model_name="Workflow",
85+
object_id=self.workflow.id,
86+
).exists()
87+
88+
# Delete the workflow
89+
with self.tasks():
90+
run_scheduled_deletions()
91+
92+
# Ensure action is removed
93+
assert not Action.objects.filter(id=action.id).exists()
94+
95+
def test_delete_configured_workflow__action_condition(self):
96+
action_condition_group, action = self.create_workflow_action(workflow=self.workflow)
97+
98+
with outbox_runner():
99+
self.get_success_response(self.organization.slug, self.workflow.id)
100+
101+
# Ensure the workflow is scheduled for deletion
102+
assert RegionScheduledDeletion.objects.filter(
103+
model_name="Workflow",
104+
object_id=self.workflow.id,
105+
).exists()
106+
107+
# Actually delete the workflow
108+
with self.tasks():
109+
run_scheduled_deletions()
110+
111+
assert not DataConditionGroup.objects.filter(id=action_condition_group.id).exists()
112+
113+
def test_without_permissions(self):
114+
# Create a workflow with a different organization
115+
new_org = self.create_organization()
116+
workflow = self.create_workflow(organization_id=new_org.id)
117+
118+
with outbox_runner():
119+
response = self.get_error_response(self.organization.slug, workflow.id)
120+
assert response.status_code == 404

0 commit comments

Comments
 (0)