Skip to content

Commit bbdcb4f

Browse files
authored
Merge branch 'master' into natemoo-re/anomaly-detection-charts
2 parents 0f466cc + 629be53 commit bbdcb4f

File tree

26 files changed

+482
-197
lines changed

26 files changed

+482
-197
lines changed

migrations_lockfile.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ remote_subscriptions: 0003_drop_remote_subscription
1212
replays: 0004_index_together
1313
sentry: 0773_make_group_score_nullable
1414
social_auth: 0002_default_auto_field
15-
uptime: 0016_translate_uptime_object_headers_to_lists
15+
uptime: 0017_unique_on_timeout
1616
workflow_engine: 0009_detector_type

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@
156156
"react-lazyload": "^3.2.1",
157157
"react-mentions": "4.4.10",
158158
"react-popper": "^2.3.0",
159-
"react-router-dom": "^6.23.0",
159+
"react-router-dom": "^6.26.2",
160160
"react-select": "4.3.1",
161161
"react-sparklines": "1.7.0",
162162
"react-virtualized": "^9.22.5",
@@ -265,7 +265,9 @@
265265
"last 3 iOS major versions",
266266
"Firefox ESR"
267267
],
268-
"test": ["current node"]
268+
"test": [
269+
"current node"
270+
]
269271
},
270272
"volta": {
271273
"extends": ".volta.json"

src/sentry/integrations/github/tasks/pr_comment.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,9 @@ def github_comment_workflow(pullrequest_id: int, project_id: int):
224224
{
225225
"name": f"Root cause #{i + 1}",
226226
"type": "copilot-chat",
227-
"prompt": f"@sentry root cause issue {str(issue_id)} with PR URL https://github.com/{repo.name}/pull/{str(pullrequest_id)}",
227+
"prompt": f"@sentry root cause issue {str(issue_id)} with PR URL https://github.com/{repo.name}/pull/{str(pr_key)}",
228228
}
229-
for i, issue_id in enumerate(top_24_issues)
229+
for i, issue_id in enumerate(top_24_issues[:3])
230230
]
231231
if enabled_copilot
232232
else None

src/sentry/mediators/alert_rule_actions/creator.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22
from django.utils.functional import cached_property
33

44
from sentry.coreapi import APIError
5-
from sentry.mediators.external_requests.alert_rule_action_requester import (
5+
from sentry.mediators.mediator import Mediator
6+
from sentry.mediators.param import Param
7+
from sentry.sentry_apps.external_requests.alert_rule_action_requester import (
68
AlertRuleActionRequester,
79
AlertRuleActionResult,
810
)
9-
from sentry.mediators.mediator import Mediator
10-
from sentry.mediators.param import Param
1111
from sentry.sentry_apps.models.sentry_app_component import SentryAppComponent
1212
from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation
1313

1414

1515
class AlertRuleActionCreator(Mediator):
1616
using = router.db_for_write(SentryAppComponent)
1717
install = Param(SentryAppInstallation)
18-
fields = Param(object, default=[]) # array of dicts
18+
fields = Param(list, default=[]) # array of dicts
1919

2020
def call(self) -> AlertRuleActionResult:
2121
uri = self._fetch_sentry_app_uri()
@@ -32,12 +32,11 @@ def _fetch_sentry_app_uri(self):
3232
def _make_external_request(self, uri=None):
3333
if uri is None:
3434
raise APIError("Sentry App request url not found")
35-
36-
self.response = AlertRuleActionRequester.run(
35+
self.response = AlertRuleActionRequester(
3736
install=self.install,
3837
uri=uri,
3938
fields=self.fields,
40-
)
39+
).run()
4140

4241
@cached_property
4342
def sentry_app(self):
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
from .alert_rule_action_requester import AlertRuleActionRequester # NOQA
21
from .issue_link_requester import IssueLinkRequester # NOQA
32
from .select_requester import SelectRequester # NOQA

src/sentry/mediators/external_requests/issue_link_requester.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@
1010

1111
from sentry.coreapi import APIError
1212
from sentry.http import safe_urlread
13-
from sentry.mediators.external_requests.util import send_and_save_sentry_app_request, validate
1413
from sentry.mediators.mediator import Mediator
1514
from sentry.mediators.param import Param
1615
from sentry.models.group import Group
16+
from sentry.sentry_apps.external_requests.utils import send_and_save_sentry_app_request, validate
1717
from sentry.sentry_apps.services.app import RpcSentryAppInstallation
1818
from sentry.users.services.user import RpcUser
1919
from sentry.utils import json

src/sentry/mediators/external_requests/select_requester.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66

77
from sentry.coreapi import APIError
88
from sentry.http import safe_urlread
9-
from sentry.mediators.external_requests.util import send_and_save_sentry_app_request, validate
109
from sentry.mediators.mediator import Mediator
1110
from sentry.mediators.param import Param
11+
from sentry.sentry_apps.external_requests.utils import send_and_save_sentry_app_request, validate
1212
from sentry.sentry_apps.services.app import RpcSentryAppInstallation
1313
from sentry.utils import json
1414

src/sentry/mediators/external_requests/alert_rule_action_requester.py renamed to src/sentry/sentry_apps/external_requests/alert_rule_action_requester.py

+47-52
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,50 @@
11
import logging
2+
from dataclasses import dataclass, field
23
from typing import TypedDict
34
from urllib.parse import urlparse, urlunparse
45
from uuid import uuid4
56

6-
from django.db import router
7+
from django.db import router, transaction
78
from django.utils.functional import cached_property
89
from requests import RequestException
910
from requests.models import Response
1011

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
1413
from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation
14+
from sentry.sentry_apps.services.app.model import RpcSentryAppInstallation
1515
from sentry.utils import json
1616

17-
logger = logging.getLogger("sentry.mediators.external-requests")
18-
1917
DEFAULT_SUCCESS_MESSAGE = "Success!"
2018
DEFAULT_ERROR_MESSAGE = "Something went wrong!"
2119

20+
logger = logging.getLogger("sentry.sentry_apps.external_requests")
21+
2222

2323
class AlertRuleActionResult(TypedDict):
2424
success: bool
2525
message: str
2626

2727

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"
6334

64-
def _make_request(self) -> AlertRuleActionResult:
35+
def run(self) -> AlertRuleActionResult:
6536
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+
7548
except RequestException as e:
7649
logger.info(
7750
"alert_rule_action.error",
@@ -82,14 +55,20 @@ def _make_request(self) -> AlertRuleActionResult:
8255
"error_message": str(e),
8356
},
8457
)
58+
8559
return AlertRuleActionResult(
8660
success=False, message=self._get_response_message(e.response, DEFAULT_ERROR_MESSAGE)
8761
)
8862
return AlertRuleActionResult(
8963
success=True, message=self._get_response_message(response, DEFAULT_SUCCESS_MESSAGE)
9064
)
9165

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]:
9372
request_uuid = uuid4().hex
9473

9574
return {
@@ -98,6 +77,22 @@ def _build_headers(self):
9877
"Sentry-App-Signature": self.sentry_app.build_signature(self.body),
9978
}
10079

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+
10196
@cached_property
10297
def body(self):
10398
return json.dumps(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Generated by Django 5.1.1 on 2024-10-08 19:37
2+
3+
import django.db.models.functions.comparison
4+
import django.db.models.functions.text
5+
from django.db import migrations, models
6+
7+
from sentry.new_migrations.migrations import CheckedMigration
8+
9+
10+
class Migration(CheckedMigration):
11+
# This flag is used to mark that a migration shouldn't be automatically run in production.
12+
# This should only be used for operations where it's safe to run the migration after your
13+
# code has deployed. So this should not be used for most operations that alter the schema
14+
# of a table.
15+
# Here are some things that make sense to mark as post deployment:
16+
# - Large data migrations. Typically we want these to be run manually so that they can be
17+
# monitored and not block the deploy for a long period of time while they run.
18+
# - Adding indexes to large tables. Since this can take a long time, we'd generally prefer to
19+
# run this outside deployments so that we don't block them. Note that while adding an index
20+
# is a schema change, it's completely safe to run the operation after the code has deployed.
21+
# Once deployed, run these manually via: https://develop.sentry.dev/database-migrations/#migration-deployment
22+
23+
is_post_deployment = False
24+
25+
dependencies = [
26+
("uptime", "0016_translate_uptime_object_headers_to_lists"),
27+
]
28+
29+
operations = [
30+
migrations.RemoveConstraint(
31+
model_name="uptimesubscription",
32+
name="uptime_uptimesubscription_unique_subscription_check",
33+
),
34+
migrations.AddConstraint(
35+
model_name="uptimesubscription",
36+
constraint=models.UniqueConstraint(
37+
models.F("url"),
38+
models.F("interval_seconds"),
39+
models.F("timeout_ms"),
40+
models.F("method"),
41+
django.db.models.functions.text.MD5("headers"),
42+
django.db.models.functions.comparison.Coalesce(
43+
django.db.models.functions.text.MD5("body"), models.Value("")
44+
),
45+
name="uptime_uptimesubscription_unique_subscription_check",
46+
),
47+
),
48+
]

src/sentry/uptime/models.py

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class Meta:
7171
models.UniqueConstraint(
7272
"url",
7373
"interval_seconds",
74+
"timeout_ms",
7475
"method",
7576
MD5("headers"),
7677
Coalesce(MD5("body"), Value("")),

static/app/types/legacyReactRouter.tsx

+9-9
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,47 @@ import type {
1313
Query,
1414
} from 'history';
1515

16-
export interface Params {
16+
interface Params {
1717
[key: string]: string;
1818
}
1919

20-
export type RoutePattern = string;
20+
type RoutePattern = string;
2121
export type RouteComponent = React.ComponentClass<any> | React.FunctionComponent<any>;
2222

23-
export interface RouteComponents {
23+
interface RouteComponents {
2424
[name: string]: RouteComponent;
2525
}
2626

27-
export interface RouterState<Q = any> {
27+
interface RouterState<Q = any> {
2828
components: RouteComponent[];
2929
location: Location<Q>;
3030
params: Params;
3131
routes: PlainRoute[];
3232
}
3333

34-
export interface RedirectFunction {
34+
interface RedirectFunction {
3535
(location: LocationDescriptor): void;
3636
(state: LocationState, pathname: Pathname | Path, query?: Query): void;
3737
}
3838

3939
type AnyFunction = (...args: any[]) => any;
4040

41-
export type EnterHook = (
41+
type EnterHook = (
4242
nextState: RouterState,
4343
replace: RedirectFunction,
4444
callback?: AnyFunction
4545
) => any;
4646

47-
export type LeaveHook = (prevState: RouterState) => any;
47+
type LeaveHook = (prevState: RouterState) => any;
4848

49-
export type ChangeHook = (
49+
type ChangeHook = (
5050
prevState: RouterState,
5151
nextState: RouterState,
5252
replace: RedirectFunction,
5353
callback?: AnyFunction
5454
) => any;
5555

56-
export type RouteHook = (nextLocation?: Location) => any;
56+
type RouteHook = (nextLocation?: Location) => any;
5757

5858
type ComponentCallback = (err: any, component: RouteComponent) => any;
5959
type ComponentsCallback = (err: any, components: RouteComponents) => any;

static/app/views/alerts/rules/uptime/uptimeAlertForm.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {Observer} from 'mobx-react';
66
import {Button} from 'sentry/components/button';
77
import Confirm from 'sentry/components/confirm';
88
import FieldWrapper from 'sentry/components/forms/fieldGroup/fieldWrapper';
9+
import HiddenField from 'sentry/components/forms/fields/hiddenField';
910
import SelectField from 'sentry/components/forms/fields/selectField';
1011
import SentryMemberTeamSelectorField from 'sentry/components/forms/fields/sentryMemberTeamSelectorField';
1112
import SentryProjectSelectorField from 'sentry/components/forms/fields/sentryProjectSelectorField';
@@ -268,6 +269,7 @@ export function UptimeAlertForm({project, handleDelete, rule}: Props) {
268269
border: 'none',
269270
}}
270271
/>
272+
<HiddenField name="timeoutMs" defaultValue={10000} />
271273
</FormRow>
272274
</List>
273275
</Form>

0 commit comments

Comments
 (0)