Skip to content

feat(workflows) Add a workflow engine implementation of OrganizationIncidentIndexEndpoint.get#110956

Merged
kcons merged 14 commits intomasterfrom
kcons/incidentidx
Mar 25, 2026
Merged

feat(workflows) Add a workflow engine implementation of OrganizationIncidentIndexEndpoint.get#110956
kcons merged 14 commits intomasterfrom
kcons/incidentidx

Conversation

@kcons
Copy link
Member

@kcons kcons commented Mar 18, 2026

This one is a notable perf risk.

This is part of the project described in src/sentry/workflow_engine/docs/legacy_backport.md.

@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Mar 18, 2026
@kcons kcons force-pushed the kcons/incidentidx branch from 5e75882 to 6269622 Compare March 18, 2026 21:57
try:
teams_query, unassigned = parse_team_params(request, organization, teams)
except InvalidParams as err:
return Response(str(err), status=status.HTTP_400_BAD_REQUEST)

Check warning

Code scanning / CodeQL

Information exposure through an exception Medium

Stack trace information
flows to this location and may be exposed to an external user.

Copilot Autofix

AI 1 day ago

In general, to fix this class of problem, avoid returning raw exception objects or their string representations directly to clients. Instead, log or capture detailed error information server-side, and send a generic or tightly-controlled error response to the client. For validation-like errors, return a simple, predefined message or an extracted, sanitized portion of the error, rather than str(err).

Here, we should adjust the except InvalidParams as err: block around lines 241–244. The best fix that preserves existing behavior (a 400 response indicating invalid parameters) while preventing potential information exposure is:

  • Stop returning str(err) directly.
  • Replace it with a generic but still informative message, such as "Invalid team parameters" or a fixed phrase indicating invalid request parameters.
  • Alternatively, if the project has a standard error response format (e.g., {"detail": "..."}), we would follow that, but we haven’t been shown that code, so we should just change the body string.

Concretely, in src/sentry/incidents/endpoints/organization_incident_index.py:

  • Locate the except InvalidParams as err: block around line 243.
  • Replace return Response(str(err), status=status.HTTP_400_BAD_REQUEST) with a Response that uses a fixed, non-sensitive message, e.g. return Response("Invalid team parameters", status=status.HTTP_400_BAD_REQUEST).

This requires no new imports or helper methods and does not change the status code or high-level control flow, only the message content.

Suggested changeset 1
src/sentry/incidents/endpoints/organization_incident_index.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/sentry/incidents/endpoints/organization_incident_index.py b/src/sentry/incidents/endpoints/organization_incident_index.py
--- a/src/sentry/incidents/endpoints/organization_incident_index.py
+++ b/src/sentry/incidents/endpoints/organization_incident_index.py
@@ -240,8 +240,8 @@
         if teams:
             try:
                 teams_query, unassigned = parse_team_params(request, organization, teams)
-            except InvalidParams as err:
-                return Response(str(err), status=status.HTTP_400_BAD_REQUEST)
+            except InvalidParams:
+                return Response("Invalid team parameters", status=status.HTTP_400_BAD_REQUEST)
 
             team_filter_query = Q(
                 group__detectorgroup__detector__owner_team_id__in=teams_query.values_list(
EOF
@@ -240,8 +240,8 @@
if teams:
try:
teams_query, unassigned = parse_team_params(request, organization, teams)
except InvalidParams as err:
return Response(str(err), status=status.HTTP_400_BAD_REQUEST)
except InvalidParams:
return Response("Invalid team parameters", status=status.HTTP_400_BAD_REQUEST)

team_filter_query = Q(
group__detectorgroup__detector__owner_team_id__in=teams_query.values_list(
Copilot is powered by AI and may make mistakes. Always verify output.
@kcons kcons marked this pull request as ready for review March 18, 2026 22:17
@kcons kcons requested a review from a team as a code owner March 18, 2026 22:17
@github-actions
Copy link
Contributor

Backend Test Failures

Failures on bbf9280 in this run:

tests/sentry/incidents/endpoints/test_organization_incident_index.py::WorkflowEngineIncidentListTest::test_single_written_metric_issuelog
.venv/lib/python3.13/site-packages/django/db/backends/utils.py:105: in _execute
    return self.cursor.execute(sql, params)
src/sentry/db/postgres/decorators.py:16: in inner
    return func(self, *args, **kwargs)
src/sentry/db/postgres/base.py:95: in execute
    return self.cursor.execute(sql, params)
E   psycopg2.errors.ExclusionViolation: conflicting key value violates exclusion constraint "exclude_overlapping_date_start_end"
E   DETAIL:  Key (group_id, tstzrange(date_started, date_ended, '[]'::text))=(9, ["2026-03-20 18:13:04.920366+00",)) conflicts with existing key (group_id, tstzrange(date_started, date_ended, '[]'::text))=(9, ["2026-03-20 18:13:04.885718+00",)).

The above exception was the direct cause of the following exception:
tests/sentry/incidents/endpoints/test_organization_incident_index.py:408: in test_single_written_metric_issue
    GroupOpenPeriod.objects.create(project=self.project, group=group)
src/sentry/silo/base.py:158: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
src/sentry/silo/base.py:158: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/query.py:665: in create
    obj.save(force_insert=True, using=self.db)
src/sentry/silo/base.py:158: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/base.py:902: in save
    self.save_base(
src/sentry/silo/base.py:158: in override
    return original_method(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/base.py:1008: in save_base
    updated = self._save_table(
.venv/lib/python3.13/site-packages/django/db/models/base.py:1169: in _save_table
    results = self._do_insert(
.venv/lib/python3.13/site-packages/django/db/models/base.py:1210: in _do_insert
    return manager._insert(
.venv/lib/python3.13/site-packages/django/db/models/manager.py:87: in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/models/query.py:1873: in _insert
    return query.get_compiler(using=using).execute_sql(returning_fields)
.venv/lib/python3.13/site-packages/django/db/models/sql/compiler.py:1882: in execute_sql
    cursor.execute(sql, params)
.venv/lib/python3.13/site-packages/django/db/backends/utils.py:122: in execute
    return super().execute(sql, params)
.venv/lib/python3.13/site-packages/sentry_sdk/utils.py:1870: in runner
    return original_function(*args, **kwargs)
.venv/lib/python3.13/site-packages/django/db/backends/utils.py:79: in execute
    return self._execute_with_wrappers(
.venv/lib/python3.13/site-packages/django/db/backends/utils.py:92: in _execute_with_wrappers
    return executor(sql, params, many, context)
src/sentry/db/postgres/base.py:70: in _execute__include_sql_in_error
    return execute(sql, params, many, context)
src/sentry/db/postgres/base.py:58: in _execute__clean_params
    return execute(sql, clean_bad_params(params), many, context)
... (16 more lines)

@kcons kcons marked this pull request as draft March 20, 2026 18:15
@kcons kcons marked this pull request as ready for review March 20, 2026 19:16
@kcons kcons marked this pull request as draft March 20, 2026 19:20
@kcons kcons marked this pull request as ready for review March 20, 2026 21:30
@github-actions
Copy link
Contributor

Backend Test Failures

Failures on e03e80b in this run:

tests/sentry/incidents/serializers/test_workflow_engine_incident.py::TestIncidentSerializer::test_detailedlog
tests/sentry/incidents/serializers/test_workflow_engine_incident.py:68: in test_detailed
    assert self._sort_triggers(serialized_incident) == self._sort_triggers(
E   AssertionError: assert {'activities'...one.utc), ...} == {'activities'...one.utc), ...}
E     
E     Omitting 14 identical items, use -vv to show
E     Differing items:
E     {'alertRule': {'aggregate': 'count()', 'comparisonDelta': None, 'createdBy': None, 'dataset': 'events', ...}} != {'alertRule': {'aggregate': 'count()', 'comparisonDelta': None, 'createdBy': None, 'dataset': 'events', ...}}
E     
E     Full diff:
E       {
E           'activities': None,
E           'alertRule': {
E               'aggregate': 'count()',
E               'comparisonDelta': None,
E               'createdBy': None,
E               'dataset': 'events',
E               'dateCreated': datetime.datetime(2024, 12, 11, 3, 21, 34, tzinfo=datetime.timezone.utc),
E               'dateModified': datetime.datetime(2024, 12, 11, 3, 21, 34, tzinfo=datetime.timezone.utc),
E               'description': '',
E               'detectionType': 'static',
E               'environment': None,
E     +         'eventTypes': [
E     +             'error',
E     +         ],
E               'extrapolationMode': 'unknown',
E               'id': '31',
E               'name': 'Charming Antelope',
E               'organizationId': '4555195392327680',
E               'originalAlertRuleId': None,
E               'owner': None,
E               'projects': [
E                   'bar',
E               ],
E               'query': 'level:error',
E               'queryType': 0,
E               'resolution': 1.0,
E     -         'resolveThreshold': 50,
E     +         'resolveThreshold': 50.0,
E     ?                               ++
E               'seasonality': None,
E               'sensitivity': None,
E     +         'snooze': False,
E               'status': 0,
E               'thresholdPeriod': 1,
E               'thresholdType': 0,
E               'timeWindow': 10.0,
E               'triggers': [
E                   {
E                       'actions': [
E                           {
... (71 more lines)
tests/sentry/incidents/serializers/test_workflow_engine_incident.py::TestIncidentSerializer::test_no_incidentlog
tests/sentry/incidents/serializers/test_workflow_engine_incident.py:119: in test_no_incident
    assert self._sort_triggers(serialized_incident) == self._sort_triggers(
E   AssertionError: assert {'activities'...one.utc), ...} == {'activities'...one.utc), ...}
E     
E     Omitting 13 identical items, use -vv to show
E     Differing items:
E     {'alertRule': {'aggregate': 'count()', 'comparisonDelta': None, 'createdBy': None, 'dataset': 'events', ...}} != {'alertRule': {'aggregate': 'count()', 'comparisonDelta': None, 'createdBy': None, 'dataset': 'events', ...}}
E     
E     Full diff:
E       {
E           'activities': None,
E           'alertRule': {
E               'aggregate': 'count()',
E               'comparisonDelta': None,
E               'createdBy': None,
E               'dataset': 'events',
E               'dateCreated': datetime.datetime(2024, 12, 11, 3, 21, 34, tzinfo=datetime.timezone.utc),
E               'dateModified': datetime.datetime(2024, 12, 11, 3, 21, 34, tzinfo=datetime.timezone.utc),
E               'description': '',
E               'detectionType': 'static',
E               'environment': None,
E     +         'eventTypes': [
E     +             'error',
E     +         ],
E               'extrapolationMode': 'unknown',
E               'id': '10000000086',
E               'name': 'Related Snake',
E               'organizationId': '4555195392327680',
E               'originalAlertRuleId': None,
E               'owner': None,
E               'projects': [
E                   'bar',
E               ],
E               'query': 'level:error',
E               'queryType': 0,
E               'resolution': 1.0,
E     -         'resolveThreshold': 50,
E     +         'resolveThreshold': 50.0,
E     ?                               ++
E               'seasonality': None,
E               'sensitivity': None,
E     +         'snooze': False,
E               'status': 0,
E               'thresholdPeriod': 1,
E               'thresholdType': 0,
E               'timeWindow': 10.0,
E               'triggers': [
E                   {
E                       'actions': [
E                           {
... (70 more lines)
tests/sentry/incidents/serializers/test_workflow_engine_incident.py::TestIncidentSerializer::test_simplelog
tests/sentry/incidents/serializers/test_workflow_engine_incident.py:59: in test_simple
    assert self._sort_triggers(serialized_incident) == self._sort_triggers(
E   AssertionError: assert {'activities'...one.utc), ...} == {'activities'...one.utc), ...}
E     
E     Omitting 13 identical items, use -vv to show
E     Differing items:
E     {'alertRule': {'aggregate': 'count()', 'comparisonDelta': None, 'createdBy': None, 'dataset': 'events', ...}} != {'alertRule': {'aggregate': 'count()', 'comparisonDelta': None, 'createdBy': None, 'dataset': 'events', ...}}
E     
E     Full diff:
E       {
E           'activities': None,
E           'alertRule': {
E               'aggregate': 'count()',
E               'comparisonDelta': None,
E               'createdBy': None,
E               'dataset': 'events',
E               'dateCreated': datetime.datetime(2024, 12, 11, 3, 21, 34, tzinfo=datetime.timezone.utc),
E               'dateModified': datetime.datetime(2024, 12, 11, 3, 21, 34, tzinfo=datetime.timezone.utc),
E               'description': '',
E               'detectionType': 'static',
E               'environment': None,
E     +         'eventTypes': [
E     +             'error',
E     +         ],
E               'extrapolationMode': 'unknown',
E               'id': '43',
E               'name': 'On Marmot',
E               'organizationId': '4555195392327680',
E               'originalAlertRuleId': None,
E               'owner': None,
E               'projects': [
E                   'bar',
E               ],
E               'query': 'level:error',
E               'queryType': 0,
E               'resolution': 1.0,
E     -         'resolveThreshold': 50,
E     +         'resolveThreshold': 50.0,
E     ?                               ++
E               'seasonality': None,
E               'sensitivity': None,
E     +         'snooze': False,
E               'status': 0,
E               'thresholdPeriod': 1,
E               'thresholdType': 0,
E               'timeWindow': 10.0,
E               'triggers': [
E                   {
E                       'actions': [
E                           {
... (70 more lines)

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Hardcoded string instead of existing constant for data source type
    • Replaced the hardcoded string 'snuba_query_subscription' with the imported constant DATA_SOURCE_SNUBA_QUERY_SUBSCRIPTION from sentry.incidents.utils.types to ensure consistency.

Create PR

Or push these changes by commenting:

@cursor push 613346b3ff
Preview (613346b3ff)
diff --git a/src/sentry/incidents/endpoints/organization_incident_index.py b/src/sentry/incidents/endpoints/organization_incident_index.py
--- a/src/sentry/incidents/endpoints/organization_incident_index.py
+++ b/src/sentry/incidents/endpoints/organization_incident_index.py
@@ -27,6 +27,7 @@
 from sentry.incidents.grouptype import MetricIssue
 from sentry.incidents.models.alert_rule import AlertRuleActivity, AlertRuleActivityType
 from sentry.incidents.models.incident import Incident, IncidentStatus
+from sentry.incidents.utils.types import DATA_SOURCE_SNUBA_QUERY_SUBSCRIPTION
 from sentry.models.environment import Environment
 from sentry.models.groupopenperiod import GroupOpenPeriod
 from sentry.models.organization import Organization
@@ -291,7 +292,7 @@
                 source_id_as_int=Cast("data_source__source_id", output_field=BigIntegerField())
             )
             .filter(
-                data_source__type="snuba_query_subscription",
+                data_source__type=DATA_SOURCE_SNUBA_QUERY_SUBSCRIPTION,
                 source_id_as_int__in=subscription_qs.values("id"),
             )
             .values("detector_id")

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

@kcons kcons enabled auto-merge (squash) March 25, 2026 19:23
@kcons kcons merged commit 3dd956b into master Mar 25, 2026
63 of 64 checks passed
@kcons kcons deleted the kcons/incidentidx branch March 25, 2026 19:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants