1
1
import logging
2
+ from dataclasses import dataclass , field
2
3
from typing import TypedDict
3
4
from urllib .parse import urlparse , urlunparse
4
5
from uuid import uuid4
5
6
6
- from django .db import router
7
+ from django .db import router , transaction
7
8
from django .utils .functional import cached_property
8
9
from requests import RequestException
9
10
from requests .models import Response
10
11
11
- from sentry .mediators .external_requests .util import send_and_save_sentry_app_request
12
- from sentry .mediators .mediator import Mediator
13
- from sentry .mediators .param import Param
12
+ from sentry .sentry_apps .external_requests .utils import send_and_save_sentry_app_request
14
13
from sentry .sentry_apps .models .sentry_app_installation import SentryAppInstallation
14
+ from sentry .sentry_apps .services .app .model import RpcSentryAppInstallation
15
15
from sentry .utils import json
16
16
17
- logger = logging .getLogger ("sentry.mediators.external-requests" )
18
-
19
17
DEFAULT_SUCCESS_MESSAGE = "Success!"
20
18
DEFAULT_ERROR_MESSAGE = "Something went wrong!"
21
19
20
+ logger = logging .getLogger ("sentry.sentry_apps.external_requests" )
21
+
22
22
23
23
class AlertRuleActionResult (TypedDict ):
24
24
success : bool
25
25
message : str
26
26
27
27
28
- class AlertRuleActionRequester (Mediator ):
29
- """
30
- Makes a POST request to another service to fetch/update the values for each field in the
31
- AlertRuleAction settings schema
32
- """
33
-
34
- install = Param (SentryAppInstallation )
35
- uri = Param (str )
36
- fields = Param (list , required = False , default = [])
37
- http_method = Param (str , required = False , default = "POST" )
38
- using = router .db_for_write (SentryAppInstallation )
39
-
40
- def call (self ):
41
- return self ._make_request ()
42
-
43
- def _build_url (self ):
44
- urlparts = list (urlparse (self .sentry_app .webhook_url ))
45
- urlparts [2 ] = self .uri
46
- return urlunparse (urlparts )
47
-
48
- def _get_response_message (self , response : Response | None , default_message : str ) -> str :
49
- """
50
- Returns the message from the response body, if in the expected location.
51
- Used to bubble up info from the Sentry App to the UI.
52
- The location should be coordinated with the docs on Alert Rule Action UI Components.
53
- """
54
- if response is None :
55
- message = default_message
56
- else :
57
- try :
58
- message = response .json ().get ("message" , default_message )
59
- except Exception :
60
- message = default_message
61
-
62
- return f"{ self .sentry_app .name } : { message } "
28
+ @dataclass
29
+ class AlertRuleActionRequester :
30
+ install : SentryAppInstallation | RpcSentryAppInstallation
31
+ uri : str
32
+ fields : list [dict [str , str ]] = field (default_factory = list )
33
+ http_method : str | None = "POST"
63
34
64
- def _make_request (self ) -> AlertRuleActionResult :
35
+ def run (self ) -> AlertRuleActionResult :
65
36
try :
66
- response = send_and_save_sentry_app_request (
67
- self ._build_url (),
68
- self .sentry_app ,
69
- self .install .organization_id ,
70
- "alert_rule_action.requested" ,
71
- headers = self ._build_headers (),
72
- method = self .http_method ,
73
- data = self .body ,
74
- )
37
+ with transaction .atomic (router .db_for_write (SentryAppInstallation )):
38
+ response = send_and_save_sentry_app_request (
39
+ url = self ._build_url (),
40
+ sentry_app = self .sentry_app ,
41
+ org_id = self .install .organization_id ,
42
+ event = "alert_rule_action.requested" ,
43
+ headers = self ._build_headers (),
44
+ method = self .http_method ,
45
+ data = self .body ,
46
+ )
47
+
75
48
except RequestException as e :
76
49
logger .info (
77
50
"alert_rule_action.error" ,
@@ -82,14 +55,20 @@ def _make_request(self) -> AlertRuleActionResult:
82
55
"error_message" : str (e ),
83
56
},
84
57
)
58
+
85
59
return AlertRuleActionResult (
86
60
success = False , message = self ._get_response_message (e .response , DEFAULT_ERROR_MESSAGE )
87
61
)
88
62
return AlertRuleActionResult (
89
63
success = True , message = self ._get_response_message (response , DEFAULT_SUCCESS_MESSAGE )
90
64
)
91
65
92
- def _build_headers (self ):
66
+ def _build_url (self ) -> str :
67
+ urlparts = list (urlparse (self .sentry_app .webhook_url ))
68
+ urlparts [2 ] = self .uri
69
+ return urlunparse (urlparts )
70
+
71
+ def _build_headers (self ) -> dict [str , str ]:
93
72
request_uuid = uuid4 ().hex
94
73
95
74
return {
@@ -98,6 +77,22 @@ def _build_headers(self):
98
77
"Sentry-App-Signature" : self .sentry_app .build_signature (self .body ),
99
78
}
100
79
80
+ def _get_response_message (self , response : Response | None , default_message : str ) -> str :
81
+ """
82
+ Returns the message from the response body, if in the expected location.
83
+ Used to bubble up info from the Sentry App to the UI.
84
+ The location should be coordinated with the docs on Alert Rule Action UI Components.
85
+ """
86
+ if response is None :
87
+ message = default_message
88
+ else :
89
+ try :
90
+ message = response .json ().get ("message" , default_message )
91
+ except Exception :
92
+ message = default_message
93
+
94
+ return f"{ self .sentry_app .name } : { message } "
95
+
101
96
@cached_property
102
97
def body (self ):
103
98
return json .dumps (
0 commit comments