Skip to content

Commit 016f426

Browse files
authored
feat(slack): Add toggle flags for threads (#71188)
Add flags to toggle features on and off for Slack threads https://github.com/getsentry/sentry/assets/5581484/e0df749f-d0c9-48d2-a225-12b341552a92
1 parent adf626b commit 016f426

File tree

6 files changed

+70
-12
lines changed

6 files changed

+70
-12
lines changed

src/sentry/api/endpoints/organization_details.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737
DEBUG_FILES_ROLE_DEFAULT,
3838
EVENTS_MEMBER_ADMIN_DEFAULT,
3939
GITHUB_COMMENT_BOT_DEFAULT,
40+
ISSUE_ALERTS_THREAD_DEFAULT,
4041
JOIN_REQUESTS_DEFAULT,
4142
LEGACY_RATE_LIMIT_OPTIONS,
43+
METRIC_ALERTS_THREAD_DEFAULT,
4244
PROJECT_RATE_LIMIT_DEFAULT,
4345
REQUIRE_SCRUB_DATA_DEFAULT,
4446
REQUIRE_SCRUB_DEFAULTS_DEFAULT,
@@ -176,6 +178,18 @@
176178
),
177179
("aggregatedDataConsent", "sentry:aggregated_data_consent", bool, DATA_CONSENT_DEFAULT),
178180
("genAIConsent", "sentry:gen_ai_consent", bool, DATA_CONSENT_DEFAULT),
181+
(
182+
"issueAlertsThreadFlag",
183+
"sentry:issue_alerts_thread_flag",
184+
bool,
185+
ISSUE_ALERTS_THREAD_DEFAULT,
186+
),
187+
(
188+
"metricAlertsThreadFlag",
189+
"sentry:metric_alerts_thread_flag",
190+
bool,
191+
METRIC_ALERTS_THREAD_DEFAULT,
192+
),
179193
)
180194

181195
DELETION_STATUSES = frozenset(
@@ -222,6 +236,8 @@ class OrganizationSerializer(BaseOrganizationSerializer):
222236
githubOpenPRBot = serializers.BooleanField(required=False)
223237
githubNudgeInvite = serializers.BooleanField(required=False)
224238
githubPRBot = serializers.BooleanField(required=False)
239+
issueAlertsThreadFlag = serializers.BooleanField(required=False)
240+
metricAlertsThreadFlag = serializers.BooleanField(required=False)
225241
aggregatedDataConsent = serializers.BooleanField(required=False)
226242
genAIConsent = serializers.BooleanField(required=False)
227243
require2FA = serializers.BooleanField(required=False)

src/sentry/api/serializers/models/organization.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
DEBUG_FILES_ROLE_DEFAULT,
3434
EVENTS_MEMBER_ADMIN_DEFAULT,
3535
GITHUB_COMMENT_BOT_DEFAULT,
36+
ISSUE_ALERTS_THREAD_DEFAULT,
3637
JOIN_REQUESTS_DEFAULT,
38+
METRIC_ALERTS_THREAD_DEFAULT,
3739
PROJECT_RATE_LIMIT_DEFAULT,
3840
REQUIRE_SCRUB_DATA_DEFAULT,
3941
REQUIRE_SCRUB_DEFAULTS_DEFAULT,
@@ -429,6 +431,8 @@ class DetailedOrganizationSerializerResponse(_DetailedOrganizationSerializerResp
429431
aggregatedDataConsent: bool
430432
genAIConsent: bool
431433
isDynamicallySampled: bool
434+
issueAlertsThreadFlag: bool
435+
metricAlertsThreadFlag: bool
432436

433437

434438
class DetailedOrganizationSerializer(OrganizationSerializer):
@@ -547,6 +551,12 @@ def serialize( # type: ignore[explicit-override, override]
547551
"aggregatedDataConsent": bool(
548552
obj.get_option("sentry:aggregated_data_consent", DATA_CONSENT_DEFAULT)
549553
),
554+
"issueAlertsThreadFlag": bool(
555+
obj.get_option("sentry:issue_alerts_thread_flag", ISSUE_ALERTS_THREAD_DEFAULT)
556+
),
557+
"metricAlertsThreadFlag": bool(
558+
obj.get_option("sentry:metric_alerts_thread_flag", METRIC_ALERTS_THREAD_DEFAULT)
559+
),
550560
}
551561
)
552562

src/sentry/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,8 @@ def from_str(cls, string: str) -> int:
643643
JOIN_REQUESTS_DEFAULT = True
644644
AI_SUGGESTED_SOLUTION = True
645645
GITHUB_COMMENT_BOT_DEFAULT = True
646+
ISSUE_ALERTS_THREAD_DEFAULT = True
647+
METRIC_ALERTS_THREAD_DEFAULT = True
646648
DATA_CONSENT_DEFAULT = False
647649

648650
# `sentry:events_member_admin` - controls whether the 'member' role gets the event:admin scope

src/sentry/integrations/slack/service.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import orjson
66

7+
from sentry.constants import ISSUE_ALERTS_THREAD_DEFAULT
78
from sentry.integrations.repository import get_default_issue_alert_repository
89
from sentry.integrations.repository.issue_alert import (
910
IssueAlertNotificationMessage,
@@ -16,6 +17,7 @@
1617
)
1718
from sentry.integrations.utils.common import get_active_integration_for_organization
1819
from sentry.models.activity import Activity
20+
from sentry.models.options.organization_option import OrganizationOption
1921
from sentry.models.rule import Rule
2022
from sentry.notifications.notifications.activity.archive import ArchiveActivityNotification
2123
from sentry.notifications.notifications.activity.base import ActivityNotification
@@ -114,6 +116,22 @@ def notify_all_threads_for_activity(self, activity: Activity) -> None:
114116
)
115117
return None
116118

119+
organization_id = activity.group.organization.id
120+
# If the feature is turned off for the organization, exit early as there's nothing to do
121+
if not OrganizationOption.objects.get_value(
122+
organization=activity.group.organization,
123+
key="sentry:issue_alerts_thread_flag",
124+
default=ISSUE_ALERTS_THREAD_DEFAULT,
125+
):
126+
self._logger.info(
127+
"feature is turned off for this organization",
128+
extra={
129+
"activity_id": activity.id,
130+
"organization_id": organization_id,
131+
"project_id": activity.project.id,
132+
},
133+
)
134+
return None
117135
# The same message is sent to all the threads, so this needs to only happen once
118136
notification_to_send = self._get_notification_message_to_send(activity=activity)
119137
if not notification_to_send:
@@ -126,15 +144,15 @@ def notify_all_threads_for_activity(self, activity: Activity) -> None:
126144
return None
127145

128146
integration = get_active_integration_for_organization(
129-
organization_id=activity.group.organization.id,
147+
organization_id=organization_id,
130148
provider=ExternalProviderEnum.SLACK,
131149
)
132150
if integration is None:
133151
self._logger.info(
134152
"no integration found for activity",
135153
extra={
136154
"activity_id": activity.id,
137-
"organization_id": activity.project.organization_id,
155+
"organization_id": organization_id,
138156
"project_id": activity.project.id,
139157
},
140158
)

src/sentry/integrations/slack/utils/notifications.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import sentry_sdk
88

99
from sentry import features
10-
from sentry.constants import ObjectStatus
10+
from sentry.constants import METRIC_ALERTS_THREAD_DEFAULT, ObjectStatus
1111
from sentry.incidents.charts import build_metric_alert_chart
1212
from sentry.incidents.models.alert_rule import AlertRuleTriggerAction
1313
from sentry.incidents.models.incident import Incident, IncidentStatus
@@ -19,6 +19,7 @@
1919
from sentry.integrations.slack.client import SlackClient
2020
from sentry.integrations.slack.message_builder.incidents import SlackIncidentsMessageBuilder
2121
from sentry.models.integrations.integration import Integration
22+
from sentry.models.options.organization_option import OrganizationOption
2223
from sentry.services.hybrid_cloud.integration import integration_service
2324
from sentry.shared_integrations.exceptions import ApiError
2425
from sentry.shared_integrations.response import BaseApiResponse, MappingApiResponse
@@ -72,15 +73,22 @@ def send_incident_alert_notification(
7273

7374
repository: MetricAlertNotificationMessageRepository = get_default_metric_alert_repository()
7475
parent_notification_message = None
75-
try:
76-
parent_notification_message = repository.get_parent_notification_message(
77-
alert_rule_id=incident.alert_rule_id,
78-
incident_id=incident.id,
79-
trigger_action_id=action.id,
80-
)
81-
except Exception:
82-
# if there's an error trying to grab a parent notification, don't let that error block this flow
83-
pass
76+
# Only grab the parent notification message for thread use if the feature is on
77+
# Otherwise, leave it empty, and it will not create a thread
78+
if OrganizationOption.objects.get_value(
79+
organization=organization,
80+
key="sentry:metric_alerts_thread_flag",
81+
default=METRIC_ALERTS_THREAD_DEFAULT,
82+
):
83+
try:
84+
parent_notification_message = repository.get_parent_notification_message(
85+
alert_rule_id=incident.alert_rule_id,
86+
incident_id=incident.id,
87+
trigger_action_id=action.id,
88+
)
89+
except Exception:
90+
# if there's an error trying to grab a parent notification, don't let that error block this flow
91+
pass
8492

8593
new_notification_message_object = NewMetricAlertNotificationMessage(
8694
incident_id=incident.id,

tests/sentry/api/endpoints/test_organization_details.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,8 @@ def test_various_options(self, mock_get_repositories):
426426
"allowJoinRequests": False,
427427
"aggregatedDataConsent": True,
428428
"genAIConsent": True,
429+
"issueAlertsThreadFlag": False,
430+
"metricAlertsThreadFlag": False,
429431
}
430432

431433
# needed to set require2FA
@@ -489,6 +491,8 @@ def test_various_options(self, mock_get_repositories):
489491
assert "to {}".format(data["githubNudgeInvite"]) in log.data["githubNudgeInvite"]
490492
assert "to {}".format(data["aggregatedDataConsent"]) in log.data["aggregatedDataConsent"]
491493
assert "to {}".format(data["genAIConsent"]) in log.data["genAIConsent"]
494+
assert "to {}".format(data["issueAlertsThreadFlag"]) in log.data["issueAlertsThreadFlag"]
495+
assert "to {}".format(data["metricAlertsThreadFlag"]) in log.data["metricAlertsThreadFlag"]
492496

493497
@responses.activate
494498
@patch(

0 commit comments

Comments
 (0)