Skip to content

Commit b3a0d1a

Browse files
Christinarlongandrewshie-sentry
authored andcommitted
chore(sentry apps): add slos for issue link req (#87775)
1 parent 6b76771 commit b3a0d1a

File tree

3 files changed

+238
-66
lines changed

3 files changed

+238
-66
lines changed

src/sentry/sentry_apps/external_issues/issue_link_creator.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55

66
from sentry.models.group import Group
77
from sentry.sentry_apps.external_issues.external_issue_creator import ExternalIssueCreator
8-
from sentry.sentry_apps.external_requests.issue_link_requester import IssueLinkRequester
8+
from sentry.sentry_apps.external_requests.issue_link_requester import (
9+
IssueLinkRequester,
10+
IssueRequestActionType,
11+
)
912
from sentry.sentry_apps.models.platformexternalissue import PlatformExternalIssue
1013
from sentry.sentry_apps.services.app import RpcSentryAppInstallation
1114
from sentry.sentry_apps.utils.errors import SentryAppSentryError
@@ -31,8 +34,12 @@ def run(self) -> PlatformExternalIssue:
3134
return external_issue
3235

3336
def _verify_action(self) -> None:
34-
if self.action not in VALID_ACTIONS:
35-
raise SentryAppSentryError(message=f"Invalid action: {self.action}", status_code=401)
37+
try:
38+
self.action = IssueRequestActionType(self.action)
39+
except ValueError as e:
40+
raise SentryAppSentryError(
41+
message=f"Invalid action: {self.action}", status_code=500
42+
) from e
3643

3744
def _make_external_request(self) -> dict[str, Any]:
3845
response = IssueLinkRequester(
@@ -41,7 +48,7 @@ def _make_external_request(self) -> dict[str, Any]:
4148
group=self.group,
4249
fields=self.fields,
4350
user=self.user,
44-
action=self.action,
51+
action=IssueRequestActionType(self.action),
4552
).run()
4653
return response
4754

src/sentry/sentry_apps/external_requests/issue_link_requester.py

Lines changed: 79 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import logging
22
from dataclasses import dataclass
3+
from enum import StrEnum
34
from typing import Any
45
from urllib.parse import urlparse
56
from uuid import uuid4
@@ -10,6 +11,12 @@
1011
from sentry.http import safe_urlread
1112
from sentry.models.group import Group
1213
from sentry.sentry_apps.external_requests.utils import send_and_save_sentry_app_request, validate
14+
from sentry.sentry_apps.metrics import (
15+
SentryAppEventType,
16+
SentryAppExternalRequestHaltReason,
17+
SentryAppInteractionEvent,
18+
SentryAppInteractionType,
19+
)
1320
from sentry.sentry_apps.services.app import RpcSentryAppInstallation
1421
from sentry.sentry_apps.utils.errors import SentryAppIntegratorError
1522
from sentry.users.models.user import User
@@ -18,6 +25,15 @@
1825

1926
logger = logging.getLogger("sentry.sentry_apps.external_requests")
2027
ACTION_TO_PAST_TENSE = {"create": "created", "link": "linked"}
28+
FAILURE_REASON_BASE = f"{SentryAppEventType.EXTERNAL_ISSUE_LINKED}.{{}}"
29+
BAD_RESPONSE_HALT_REASON = FAILURE_REASON_BASE.format(
30+
SentryAppExternalRequestHaltReason.BAD_RESPONSE
31+
)
32+
33+
34+
class IssueRequestActionType(StrEnum):
35+
CREATE = "create"
36+
LINK = "link"
2137

2238

2339
@dataclass
@@ -56,64 +72,74 @@ class IssueLinkRequester:
5672
group: Group
5773
fields: dict[str, Any]
5874
user: RpcUser | User
59-
action: str
75+
action: IssueRequestActionType
6076

6177
def run(self) -> dict[str, Any]:
6278
response: dict[str, str] = {}
63-
64-
try:
65-
request = send_and_save_sentry_app_request(
66-
self._build_url(),
67-
self.sentry_app,
68-
self.install.organization_id,
69-
f"external_issue.{ACTION_TO_PAST_TENSE[self.action]}",
70-
headers=self._build_headers(),
71-
method="POST",
72-
data=self.body,
73-
)
74-
body = safe_urlread(request)
75-
response = json.loads(body)
76-
except (json.JSONDecodeError, TypeError):
77-
raise SentryAppIntegratorError(
78-
message=f"Unable to parse response from {self.sentry_app.slug}",
79-
webhook_context={
80-
"error_type": "issue-link-requester.invalid-json",
81-
"uri": self.uri,
82-
"response": body,
83-
"installation_uuid": self.install.uuid,
84-
},
85-
status_code=500,
86-
)
87-
except RequestException as e:
88-
error_type = "issue-link-requester.error"
89-
extras = {
90-
"sentry_app": self.sentry_app.slug,
91-
"installation_uuid": self.install.uuid,
92-
"project": self.group.project.slug,
93-
"group": self.group.id,
79+
event = SentryAppEventType(f"external_issue.{ACTION_TO_PAST_TENSE[self.action]}")
80+
with SentryAppInteractionEvent(
81+
operation_type=SentryAppInteractionType.EXTERNAL_REQUEST,
82+
event_type=event,
83+
).capture() as lifecycle:
84+
extras: dict[str, Any] = {
9485
"uri": self.uri,
95-
"error_message": str(e),
86+
"installation_uuid": self.install.uuid,
87+
"sentry_app_slug": self.sentry_app.slug,
88+
"project_slug": self.group.project.slug,
89+
"group_id": self.group.id,
9690
}
97-
logger.info(error_type, extra=extras)
98-
raise SentryAppIntegratorError(
99-
message=f"Issue occured while trying to contact {self.sentry_app.slug} to link issue",
100-
webhook_context={"error_type": error_type, **extras},
101-
status_code=500,
102-
)
103-
104-
if not self._validate_response(response):
105-
raise SentryAppIntegratorError(
106-
message=f"Invalid response format from sentry app {self.sentry_app} when linking issue",
107-
webhook_context={
108-
"error_type": "issue-link-requester.invalid-response",
109-
"response": response,
110-
"installation_uuid": self.install.uuid,
111-
"uri": self.uri,
112-
},
113-
status_code=500,
114-
)
115-
116-
return response
91+
try:
92+
request = send_and_save_sentry_app_request(
93+
self._build_url(),
94+
self.sentry_app,
95+
self.install.organization_id,
96+
event,
97+
headers=self._build_headers(),
98+
method="POST",
99+
data=self.body,
100+
)
101+
response_body = safe_urlread(request)
102+
103+
response = json.loads(response_body)
104+
except (json.JSONDecodeError, TypeError) as e:
105+
extras["response_body"] = response_body
106+
lifecycle.record_halt(
107+
halt_reason=e,
108+
extra={"halt_reason": BAD_RESPONSE_HALT_REASON, **extras},
109+
)
110+
111+
raise SentryAppIntegratorError(
112+
message=f"Unable to parse response from {self.sentry_app.slug}",
113+
webhook_context={"error_type": BAD_RESPONSE_HALT_REASON, **extras},
114+
status_code=500,
115+
)
116+
except RequestException as e:
117+
extras["error_message"] = str(e)
118+
lifecycle.record_halt(
119+
halt_reason=e,
120+
extra={"halt_reason": BAD_RESPONSE_HALT_REASON, **extras},
121+
)
122+
123+
raise SentryAppIntegratorError(
124+
message=f"Issue occured while trying to contact {self.sentry_app.slug} to link issue",
125+
webhook_context={"error_type": BAD_RESPONSE_HALT_REASON, **extras},
126+
status_code=500,
127+
)
128+
129+
if not self._validate_response(response):
130+
extras["response"] = response
131+
lifecycle.record_halt(
132+
halt_reason=BAD_RESPONSE_HALT_REASON,
133+
extra={"halt_reason": BAD_RESPONSE_HALT_REASON, **extras},
134+
)
135+
136+
raise SentryAppIntegratorError(
137+
message=f"Invalid response format from sentry app {self.sentry_app} when linking issue",
138+
webhook_context={"error_type": BAD_RESPONSE_HALT_REASON, **extras},
139+
status_code=500,
140+
)
141+
142+
return response
117143

118144
def _build_url(self) -> str:
119145
urlparts = urlparse(self.sentry_app.webhook_url)

0 commit comments

Comments
 (0)