Skip to content

Commit 7373c8b

Browse files
committed
Merge branch 'master' into scttcper/incident-add-uuid
2 parents 73c08d4 + f7de14b commit 7373c8b

File tree

28 files changed

+432
-112
lines changed

28 files changed

+432
-112
lines changed

src/sentry/conf/server.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ def env(
118118
SENTRY_INTEGRATION_ERROR_LOG_REDIS_CLUSTER = "default"
119119
SENTRY_DEBUG_FILES_REDIS_CLUSTER = "default"
120120
SENTRY_MONITORS_REDIS_CLUSTER = "default"
121+
SENTRY_STATISTICAL_DETECTORS_REDIS_CLUSTER = "default"
121122

122123
# Hosts that are allowed to use system token authentication.
123124
# http://en.wikipedia.org/wiki/Reserved_IP_addresses
@@ -1602,23 +1603,30 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
16021603
"organizations:relay": True,
16031604
# Enable Sentry Functions
16041605
"organizations:sentry-functions": False,
1605-
# Enable experimental session replay backend APIs
1606+
# Enable core Session Replay backend APIs
16061607
"organizations:session-replay": False,
1607-
# Enable Session Replay showing in the sidebar
1608+
# Enable core Session Replay link in the sidebar
16081609
"organizations:session-replay-ui": True,
1609-
# Enable experimental session replay SDK for recording on Sentry
1610+
# Enable core Session Replay SDK for recording on sentry.io
16101611
"organizations:session-replay-sdk": False,
1612+
# Enable core Session Replay SDK for recording onError events on sentry.io
16111613
"organizations:session-replay-sdk-errors-only": False,
16121614
# Enable data scrubbing of replay recording payloads in Relay.
16131615
"organizations:session-replay-recording-scrubbing": False,
1616+
# Enable the Replay Details > Accessibility tab
1617+
"organizations:session-replay-a11y-tab": False,
16141618
# Enable linking from 'new issue' slack notifs to the issue replay list
16151619
"organizations:session-replay-slack-new-issue": False,
1620+
# Enable linking from 'new issue' email notifs to the issue replay list
16161621
"organizations:session-replay-issue-emails": False,
1617-
"organizations:session-replay-weekly-email": False,
1618-
"organizations:session-replay-trial-ended-banner": False,
1619-
"organizations:session-replay-trace-table": False,
16201622
# Enable optimized serach feature.
16211623
"organizations:session-replay-optimized-search": False,
1624+
# Enable linking from 'weekly email' summaries to the issue replay list
1625+
"organizations:session-replay-weekly-email": False,
1626+
# Enable the Replay Details > Performance tab
1627+
"organizations:session-replay-trace-table": False,
1628+
# Enable the AM1 trial ended banner on sentry.io
1629+
"organizations:session-replay-trial-ended-banner": False,
16221630
# Enable the new suggested assignees feature
16231631
"organizations:streamline-targeting-context": False,
16241632
# Enable the new experimental starfish view

src/sentry/features/__init__.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,16 +181,17 @@
181181
default_manager.add("organizations:org-roles-for-teams", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
182182
default_manager.add("organizations:sentry-functions", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
183183
default_manager.add("organizations:session-replay", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
184-
default_manager.add("organizations:session-replay-ui", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
185-
default_manager.add("organizations:session-replay-sdk", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
186-
default_manager.add("organizations:session-replay-sdk-errors-only", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
184+
default_manager.add("organizations:session-replay-a11y-tab", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
185+
default_manager.add("organizations:session-replay-issue-emails", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
186+
default_manager.add("organizations:session-replay-optimized-search", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
187187
default_manager.add("organizations:session-replay-recording-scrubbing", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
188+
default_manager.add("organizations:session-replay-sdk-errors-only", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
189+
default_manager.add("organizations:session-replay-sdk", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
188190
default_manager.add("organizations:session-replay-slack-new-issue", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
189-
default_manager.add("organizations:session-replay-weekly-email", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
190-
default_manager.add("organizations:session-replay-issue-emails", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
191191
default_manager.add("organizations:session-replay-trace-table", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
192192
default_manager.add("organizations:session-replay-trial-ended-banner", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
193-
default_manager.add("organizations:session-replay-optimized-search", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
193+
default_manager.add("organizations:session-replay-ui", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
194+
default_manager.add("organizations:session-replay-weekly-email", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
194195
default_manager.add("organizations:set-grouping-config", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
195196
default_manager.add("organizations:slack-overage-notifications", OrganizationFeature, FeatureHandlerStrategy.REMOTE)
196197
default_manager.add("organizations:sdk-crash-detection", OrganizationFeature, FeatureHandlerStrategy.INTERNAL)
Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,10 @@
11
# Generated by Django 3.2.20 on 2023-08-01 20:51
22

3-
from django.db import IntegrityError, migrations, transaction
4-
from django.db.models import Max
5-
63
from sentry.new_migrations.migrations import CheckedMigration
7-
from sentry.utils.query import RangeQuerySetWrapperWithProgressBar
8-
9-
10-
def backfill_tombstones(apps, schema_editor):
11-
RegionTombstone = apps.get_model("sentry", "RegionTombstone")
12-
ControlTombstone = apps.get_model("sentry", "ControlTombstone")
13-
14-
max_rt = RegionTombstone.objects.aggregate(Max("id"))["id__max"] or 0
15-
max_ct = ControlTombstone.objects.aggregate(Max("id"))["id__max"] or 0
16-
17-
for rt in RangeQuerySetWrapperWithProgressBar(RegionTombstone.objects.filter(id__lte=max_rt)):
18-
try:
19-
with transaction.atomic("default"):
20-
ControlTombstone.objects.create(
21-
table_name=rt.table_name, object_identifier=rt.object_identifier
22-
)
23-
except IntegrityError:
24-
pass
25-
26-
for ct in RangeQuerySetWrapperWithProgressBar(ControlTombstone.objects.filter(id__lte=max_ct)):
27-
try:
28-
with transaction.atomic("default"):
29-
RegionTombstone.objects.create(
30-
table_name=ct.table_name, object_identifier=ct.object_identifier
31-
)
32-
except IntegrityError:
33-
pass
344

355

6+
# This migration has been run in production SaaS to fix an optimization issue with tombstones.
7+
# It is not necessary to run in other environments, and produces problems with the split db world.
368
class Migration(CheckedMigration):
379
# This flag is used to mark that a migration shouldn't be automatically run in production. For
3810
# the most part, this should only be used for operations where it's safe to run the migration
@@ -50,10 +22,4 @@ class Migration(CheckedMigration):
5022
("sentry", "0535_add_created_date_to_outbox_model"),
5123
]
5224

53-
operations = [
54-
migrations.RunPython(
55-
backfill_tombstones,
56-
migrations.RunPython.noop,
57-
hints={"tables": ["sentry_controltombstone", "sentry_regiontombstone"]},
58-
),
59-
]
25+
operations = []

src/sentry/monitors/validators.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import pytz
22
from croniter import croniter
33
from django.core.exceptions import ValidationError
4-
from django.utils.translation import gettext_lazy as _
54
from drf_spectacular.types import OpenApiTypes
65
from drf_spectacular.utils import extend_schema_field
76
from rest_framework import serializers
87

8+
from sentry.api.base import (
9+
DEFAULT_SLUG_ERROR_MESSAGE,
10+
DEFAULT_SLUG_PATTERN,
11+
PreventNumericSlugMixin,
12+
)
913
from sentry.api.fields.empty_integer import EmptyIntegerField
1014
from sentry.api.serializers.rest_framework import CamelSnakeSerializer
1115
from sentry.api.serializers.rest_framework.project import ProjectField
@@ -194,15 +198,15 @@ def validate(self, attrs):
194198
return attrs
195199

196200

197-
class MonitorValidator(CamelSnakeSerializer):
201+
class MonitorValidator(CamelSnakeSerializer, PreventNumericSlugMixin):
198202
project = ProjectField(scope="project:read")
199203
name = serializers.CharField(max_length=128)
200204
slug = serializers.RegexField(
201-
r"^[a-zA-Z0-9_-]+$",
205+
DEFAULT_SLUG_PATTERN,
202206
max_length=MAX_SLUG_LENGTH,
203207
required=False,
204208
error_messages={
205-
"invalid": _("Invalid monitor slug. Must match the pattern [a-zA-Z0-9_-]+")
209+
"invalid": DEFAULT_SLUG_ERROR_MESSAGE,
206210
},
207211
)
208212
status = serializers.ChoiceField(
@@ -228,7 +232,7 @@ def validate_slug(self, value):
228232
slug=value, organization_id=self.context["organization"].id
229233
).exists():
230234
raise ValidationError(f'The slug "{value}" is already in use.')
231-
235+
value = super().validate_slug(value)
232236
return value
233237

234238
def update(self, instance, validated_data):

src/sentry/tasks/post_process.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,13 +684,17 @@ def update_event_groups(event: Event, group_states: Optional[GroupStates] = None
684684
# deprecated event.group and event.group_id usage, kept here for backwards compatibility
685685
event.group, _ = get_group_with_redirect(event.group_id)
686686
event.group_id = event.group.id
687+
# We buffer updates to last_seen, assume its at least >= the event datetime
688+
event.group.last_seen = max(event.datetime, event.group.last_seen)
687689

688690
# Re-bind Group since we're reading the Event object
689691
# from cache, which may contain a stale group and project
690692
group_states = group_states or ([{"id": event.group_id}] if event.group_id else [])
691693
rebound_groups = []
692694
for group_state in group_states:
693695
rebound_group = get_group_with_redirect(group_state["id"])[0]
696+
# We buffer updates to last_seen, assume its at least >= the event datetime
697+
rebound_group.last_seen = max(event.datetime, rebound_group.last_seen)
694698

695699
# We fetch buffered updates to group aggregates here and populate them on the Group. This
696700
# helps us avoid problems with processing group ignores and alert rules that rely on these

static/app/components/pluginConfig.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class PluginConfig extends Component<Props, State> {
132132
</Button>
133133
)}
134134
<Button
135-
size="sm"
135+
size="xs"
136136
onClick={this.handleDisablePlugin}
137137
disabled={!hasWriteAccess}
138138
>

static/app/components/replays/virtualizedGrid/headerCell.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ interface SortSpanFrame {
2727
type Props = {
2828
field: string;
2929
handleSort: (fieldName: string) => void;
30-
label: string;
30+
label: ReactNode;
3131
sortConfig: SortCrumbs | SortBreadcrumbFrame | SortSpanFrame;
3232
style: CSSProperties;
3333
tooltipTitle: undefined | ReactNode;

static/app/routes.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,9 @@ function buildRoutes() {
291291
<IndexRoute
292292
component={make(
293293
() =>
294-
import('sentry/views/settings/account/notifications/notificationSettings')
294+
import(
295+
'sentry/views/settings/account/notifications/notificationSettingsController'
296+
)
295297
)}
296298
/>
297299
<Route

static/app/utils/replays/hooks/useActiveReplayTab.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import useOrganization from 'sentry/utils/useOrganization';
88
import useUrlParams from 'sentry/utils/useUrlParams';
99

1010
export enum TabKey {
11+
A11Y = 'a11y',
1112
CONSOLE = 'console',
1213
DOM = 'dom',
1314
ERRORS = 'errors',
@@ -18,11 +19,16 @@ export enum TabKey {
1819
}
1920

2021
function isReplayTab(tab: string, organization: Organization): tab is TabKey {
22+
const hasA11yTab = organization.features.includes('session-replay-a11y-tab');
2123
const hasPerfTab = organization.features.includes('session-replay-trace-table');
2224

25+
if (tab === TabKey.A11Y) {
26+
return hasA11yTab;
27+
}
2328
if (tab === TabKey.PERF) {
2429
return hasPerfTab;
2530
}
31+
2632
return Object.values<string>(TabKey).includes(tab);
2733
}
2834

static/app/utils/useHotkeys.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ type Hotkey = {
2929
callback: (e: KeyboardEvent) => void;
3030
/**
3131
* Defines the matching shortcuts.
32+
*
33+
* Multiple shortcuts may be passed as a list.
34+
*
35+
* The format for shorcuts is `<modifiers>+<key>` For example `shift+t` or
36+
* `command+shift+t`.
3237
*/
3338
match: string[] | string;
3439
/**
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type Props = {};
2+
3+
function A11y({}: Props) {
4+
return <div />;
5+
}
6+
7+
export default A11y;

static/app/views/replays/detail/layout/focusArea.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {useReplayContext} from 'sentry/components/replays/replayContext';
22
import useActiveReplayTab, {TabKey} from 'sentry/utils/replays/hooks/useActiveReplayTab';
33
import useOrganization from 'sentry/utils/useOrganization';
4+
import A11y from 'sentry/views/replays/detail/accessibility/index';
45
import Console from 'sentry/views/replays/detail/console';
56
import DomMutations from 'sentry/views/replays/detail/domMutations';
67
import ErrorList from 'sentry/views/replays/detail/errorList/index';
@@ -18,6 +19,8 @@ function FocusArea({}: Props) {
1819
const organization = useOrganization();
1920

2021
switch (getActiveTab()) {
22+
case TabKey.A11Y:
23+
return <A11y />;
2124
case TabKey.NETWORK:
2225
return (
2326
<NetworkList

static/app/views/replays/detail/layout/focusTabs.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ import {Fragment, ReactNode} from 'react';
22
import queryString from 'query-string';
33

44
import FeatureBadge from 'sentry/components/featureBadge';
5+
import ExternalLink from 'sentry/components/links/externalLink';
56
import ListLink from 'sentry/components/links/listLink';
67
import ScrollableTabs from 'sentry/components/replays/scrollableTabs';
8+
import {Tooltip} from 'sentry/components/tooltip';
79
import {t} from 'sentry/locale';
810
import type {Organization} from 'sentry/types';
911
import {trackAnalytics} from 'sentry/utils/analytics';
@@ -12,6 +14,7 @@ import {useLocation} from 'sentry/utils/useLocation';
1214
import useOrganization from 'sentry/utils/useOrganization';
1315

1416
function getReplayTabs(organization: Organization): Record<TabKey, ReactNode> {
17+
const hasA11yTab = organization.features.includes('session-replay-a11y-tab');
1518
const hasPerfTab = organization.features.includes('session-replay-trace-table');
1619

1720
return {
@@ -29,6 +32,21 @@ function getReplayTabs(organization: Organization): Record<TabKey, ReactNode> {
2932
</Fragment>
3033
) : null,
3134
[TabKey.DOM]: t('DOM Events'),
35+
[TabKey.A11Y]: hasA11yTab ? (
36+
<Fragment>
37+
<Tooltip
38+
isHoverable
39+
title={
40+
<ExternalLink href="https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility">
41+
{t('What is accessibility?')}
42+
</ExternalLink>
43+
}
44+
>
45+
{t('a11y')}
46+
</Tooltip>
47+
<FeatureBadge type="alpha" />
48+
</Fragment>
49+
) : null,
3250
[TabKey.MEMORY]: t('Memory'),
3351
};
3452
}

static/app/views/settings/account/accountNotificationFineTuningV2.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const accountNotifications = [
4646
'approval',
4747
'quota',
4848
'spikeProtection',
49+
'reports',
4950
];
5051

5152
type ANBPProps = {
@@ -141,7 +142,7 @@ type State = DeprecatedAsyncView['state'] & {
141142
projects: Project[] | null;
142143
};
143144

144-
class AccountNotificationFineTuning extends DeprecatedAsyncView<Props, State> {
145+
class AccountNotificationFineTuningV2 extends DeprecatedAsyncView<Props, State> {
145146
getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
146147
const {fineTuneType: pathnameType} = this.props.params;
147148
const fineTuneType = getNotificationTypeFromPathname(pathnameType);
@@ -274,7 +275,7 @@ class AccountNotificationFineTuning extends DeprecatedAsyncView<Props, State> {
274275
<Form
275276
saveOnBlur
276277
apiMethod="PUT"
277-
apiEndpoint={`/users/me/notifications-v2/${fineTuneType}/`}
278+
apiEndpoint={`/users/me/notifications/${fineTuneType}/`}
278279
initialData={fineTuneData}
279280
>
280281
{isProject && hasProjects && (
@@ -312,4 +313,4 @@ const StyledPanelHeader = styled(PanelHeader)`
312313
}
313314
`;
314315

315-
export default withOrganizations(AccountNotificationFineTuning);
316+
export default withOrganizations(AccountNotificationFineTuningV2);

static/app/views/settings/account/notifications/notificationSettingsByTypeV2.tsx

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {Fragment} from 'react';
2+
import styled from '@emotion/styled';
23

34
import DeprecatedAsyncComponent from 'sentry/components/deprecatedAsyncComponent';
45
import Form from 'sentry/components/forms/form';
56
import JsonForm from 'sentry/components/forms/jsonForm';
67
import {Field} from 'sentry/components/forms/types';
8+
import Panel from 'sentry/components/panels/panel';
79
import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
810
import {t} from 'sentry/locale';
911
import ConfigStore from 'sentry/stores/configStore';
@@ -311,7 +313,7 @@ class NotificationSettingsByTypeV2 extends DeprecatedAsyncComponent<Props, State
311313
initialData={this.getInitialTopOptionData()}
312314
onSubmitSuccess={() => this.trackTuningUpdated('general')}
313315
>
314-
<JsonForm
316+
<TopJsonForm
315317
title={
316318
isGroupedByProject(notificationType)
317319
? t('All Projects')
@@ -326,7 +328,7 @@ class NotificationSettingsByTypeV2 extends DeprecatedAsyncComponent<Props, State
326328
apiEndpoint="/users/me/notification-providers/"
327329
initialData={this.getProviderInitialData()}
328330
>
329-
<JsonForm fields={this.getProviderFields()} />
331+
<BottomJsonForm fields={this.getProviderFields()} />
330332
</Form>
331333
<NotificationSettingsByEntity
332334
notificationType={notificationType}
@@ -342,3 +344,19 @@ class NotificationSettingsByTypeV2 extends DeprecatedAsyncComponent<Props, State
342344
}
343345

344346
export default withOrganizations(NotificationSettingsByTypeV2);
347+
348+
export const TopJsonForm = styled(JsonForm)`
349+
${Panel} {
350+
border-bottom: 0;
351+
margin-bottom: 0;
352+
border-bottom-right-radius: 0;
353+
border-bottom-left-radius: 0;
354+
}
355+
`;
356+
357+
export const BottomJsonForm = styled(JsonForm)`
358+
${Panel} {
359+
border-top-right-radius: 0;
360+
border-top-left-radius: 0;
361+
}
362+
`;

0 commit comments

Comments
 (0)