Skip to content

Commit 54bacf7

Browse files
authored
Merge branch 'master' into locality-category
2 parents 3043b15 + bea1e86 commit 54bacf7

50 files changed

Lines changed: 1225 additions & 830 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/sentry/api/bases/organization_events.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,7 @@ def get_dataset(self, request: Request, organization: Organization) -> Any:
156156
and not EAPOccurrencesComparator.should_use_experimental_data("api.events.endpoints")
157157
):
158158
raise ParseError(detail=f"{dataset_label} is not supported currently")
159-
elif dataset_label == SupportedTraceItemType.REPLAYS.value and not features.has(
160-
"organizations:events-use-replays-dataset", organization, actor=request.user
161-
):
159+
elif dataset_label == SupportedTraceItemType.REPLAYS.value:
162160
raise ParseError(detail=f"dataset must be one of: {', '.join(PUBLIC_DATASET_LABELS)}")
163161
result = get_dataset(dataset_label)
164162
if result is None:

src/sentry/deletions/tasks/hybrid_cloud.py

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
ROW_WATERMARK = "row"
4747
WATERMARK_PREFIXES = (TOMBSTONE_WATERMARK, ROW_WATERMARK)
4848

49+
WRITE_WATERMARK_TO_POSTGRES_OPTION = "hybrid_cloud.write_deletion_watermark_to_postgres"
50+
READ_WATERMARK_FROM_POSTGRES_OPTION = "hybrid_cloud.read_deletion_watermark_from_postgres"
51+
4952

5053
@dataclass
5154
class WatermarkBatch:
@@ -96,7 +99,7 @@ def _write_watermark(
9699
)
97100

98101
# Dual-write deletion watermarks to Redis and Postgres
99-
if options.get("hybrid_cloud.write_deletion_watermark_to_postgres"):
102+
if options.get(WRITE_WATERMARK_TO_POSTGRES_OPTION):
100103
try:
101104
_watermark_model(field).objects.update_or_create(
102105
prefix=prefix,
@@ -116,20 +119,54 @@ def _write_watermark(
116119
)
117120

118121

119-
def get_watermark(prefix: str, field: HybridCloudForeignKey[Any, Any]) -> tuple[int, str]:
120-
client = _get_redis_client()
121-
key = get_watermark_key(prefix, field)
122-
v = client.get(key)
122+
def _watermark_row_lookup(prefix: str, field: HybridCloudForeignKey[Any, Any]) -> dict[str, str]:
123+
return dict(
124+
prefix=prefix,
125+
table_name=field.model._meta.db_table,
126+
field_name=field.name,
127+
)
128+
129+
130+
def _read_redis_watermark(
131+
prefix: str, field: HybridCloudForeignKey[Any, Any]
132+
) -> tuple[int, str] | None:
133+
v = _get_redis_client().get(get_watermark_key(prefix, field))
123134
if v is None:
124-
result = (0, uuid4().hex)
125-
_write_watermark(prefix, field, *result)
126-
return result
135+
return None
127136
lower, transaction_id = json.loads(v)
128137
if not (isinstance(lower, int) and isinstance(transaction_id, str)):
129138
raise TypeError("Expected watermarks data to be a tuple of (int, str)")
130139
return lower, transaction_id
131140

132141

142+
def _read_postgres_watermark(
143+
prefix: str, field: HybridCloudForeignKey[Any, Any]
144+
) -> tuple[int, str] | None:
145+
row = _watermark_model(field).objects.filter(**_watermark_row_lookup(prefix, field)).first()
146+
if row is None:
147+
return None
148+
return row.low_bound, row.transaction_id
149+
150+
151+
def _postgres_is_source_of_truth() -> bool:
152+
return bool(options.get(WRITE_WATERMARK_TO_POSTGRES_OPTION)) and bool(
153+
options.get(READ_WATERMARK_FROM_POSTGRES_OPTION)
154+
)
155+
156+
157+
def get_watermark(prefix: str, field: HybridCloudForeignKey[Any, Any]) -> tuple[int, str]:
158+
if _postgres_is_source_of_truth():
159+
watermark = _read_postgres_watermark(prefix, field)
160+
else:
161+
watermark = _read_redis_watermark(prefix, field)
162+
163+
if watermark is None:
164+
result = (0, uuid4().hex)
165+
_write_watermark(prefix, field, *result)
166+
return result
167+
return watermark
168+
169+
133170
def set_watermark(
134171
prefix: str, field: HybridCloudForeignKey[Any, Any], value: int, prev_transaction_id: str
135172
) -> None:

src/sentry/features/temporary.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,8 +234,6 @@ def register_temporary_features(manager: FeatureManager) -> None:
234234
manager.add("organizations:replay-ai-summaries", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
235235
# Enable reading replay details using EAP query
236236
manager.add("organizations:replay-details-eap-query", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
237-
# Enable using the events replays dataset
238-
manager.add("organizations:events-use-replays-dataset", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
239237
# Verify a recommended event's replay exists in the replays dataset before it is surfaced on issue details
240238
manager.add("organizations:issue-details-verify-recommended-replay", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False)
241239
# Enable using the events api with a sql interface

src/sentry/notifications/notification_action/action_handler_registry/sentry_app_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class SentryAppActionHandler(ActionHandler):
3333
"description": "The data schema for a Sentry App Action",
3434
"type": "object",
3535
"properties": {
36-
"settings": {"type": ["array", "object"]},
36+
"settings": {"type": "array"},
3737
},
3838
"additionalProperties": False,
3939
}

src/sentry/options/defaults.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2530,6 +2530,11 @@
25302530
default=False,
25312531
flags=FLAG_AUTOMATOR_MODIFIABLE,
25322532
)
2533+
register(
2534+
"hybrid_cloud.read_deletion_watermark_from_postgres",
2535+
default=False,
2536+
flags=FLAG_AUTOMATOR_MODIFIABLE,
2537+
)
25332538

25342539
# List of event IDs to pass through
25352540
register(

src/sentry/organizations/services/organization/model.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,23 +163,24 @@ def get_audit_log_metadata(self, user_email: str | None = None) -> dict[str, Any
163163
}
164164

165165

166-
# Add new organization flags to RpcOrganizationFlags first, only add them here after
167-
# they have been replicated via Organization.handle_async_replication logic
166+
# Only flags that `serialize_organization_mapping_flags` populates belong here; anything else
167+
# silently reads as its default. Declare new organization flags on `RpcOrganizationFlags` below.
168168
class RpcOrganizationMappingFlags(RpcModel):
169169
early_adopter: bool = False
170170
require_2fa: bool = False
171171
allow_joinleave: bool = False
172172
enhanced_privacy: bool = False
173173
disable_shared_issues: bool = False
174174
disable_new_visibility_features: bool = False
175-
require_email_verification: bool = False
176-
codecov_access: bool = False
177175
disable_member_project_creation: bool = False
178176
prevent_superuser_access: bool = False
179177
disable_member_invite: bool = False
180178

181179

182180
class RpcOrganizationFlags(RpcOrganizationMappingFlags):
181+
require_email_verification: bool = False
182+
codecov_access: bool = False
183+
183184
def as_int(self) -> int:
184185
# Must maintain the same order as the ORM's `Organization.flags` fields
185186
return flags_to_bits(

src/sentry/seer/agent/client_models.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,6 @@ class SeerRunState(BaseModel):
269269
# plain str so a new Seer reason does not fail validation mid-deploy.
270270
failure_reason: str | None = None
271271
updated_at: str
272-
failure_reason: str | None = None
273272
owner_user_id: int | None = None
274273
pending_user_input: PendingUserInput | None = None
275274
repo_pr_states: dict[str, RepoPRState] = Field(default_factory=dict)

src/sentry/seer/smart_assignment/delivery.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ def _validate_resolve_verdict(
126126

127127
if candidate.identifier_kind == "email":
128128
users = user_service.get_many_by_email(
129-
emails=[value], organization_id=organization_id, is_verified=True
129+
emails=[value], organization_id=organization_id, is_verified=False
130130
)
131131
resolved.append(users[0].id if users else None)
132132
continue

static/app/components/forms/jsonForm.spec.tsx

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -72,50 +72,6 @@ describe('JsonForm', () => {
7272
expect(screen.queryByText('Field Label 2')).not.toBeVisible();
7373
});
7474

75-
it('initiallyCollapsed prop from children form groups override json form initiallyCollapsed prop', () => {
76-
const forms: JsonFormObject[] = [
77-
{
78-
title: 'Form1 title',
79-
fields: [
80-
{
81-
name: 'name',
82-
type: 'string',
83-
required: true,
84-
label: 'Field Label 1 ',
85-
placeholder: 'e.g. John Doe',
86-
},
87-
],
88-
},
89-
{
90-
title: 'Form2 title',
91-
fields: [
92-
{
93-
name: 'name',
94-
type: 'string',
95-
required: true,
96-
label: 'Field Label 2',
97-
placeholder: 'e.g. Abdullah Khan',
98-
},
99-
],
100-
initiallyCollapsed: false, // Prevents this form group from being collapsed
101-
},
102-
];
103-
render(
104-
<JsonForm
105-
forms={forms}
106-
additionalFieldProps={{user}}
107-
collapsible
108-
initiallyCollapsed
109-
/>
110-
);
111-
112-
expect(screen.getByText('Form1 title')).toBeInTheDocument();
113-
expect(screen.getByText('Form2 title')).toBeInTheDocument();
114-
115-
expect(screen.queryByText('Field Label 1')).not.toBeVisible();
116-
expect(screen.queryByText('Field Label 2')).toBeVisible();
117-
});
118-
11975
it('should ALWAYS hide panel, if all fields have visible set to false AND there is no renderHeader & renderFooter', () => {
12076
const hiddenFields: JsonFormObject[] = [
12177
{

static/app/components/forms/jsonForm.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,9 @@ function JsonForm({
106106
fields,
107107
formPanelProps,
108108
title: formTitle,
109-
initiallyCollapsed: formInitiallyCollapsed,
110109
}: {
111110
fields: FieldObject[];
112111
formPanelProps: ChildFormPanelProps;
113-
initiallyCollapsed?: boolean;
114112
title?: React.ReactNode;
115113
}) => {
116114
const displayForm = shouldDisplayForm(fields);
@@ -124,7 +122,7 @@ function JsonForm({
124122
title={formTitle}
125123
fields={fields}
126124
{...formPanelProps}
127-
initiallyCollapsed={formInitiallyCollapsed ?? formPanelProps.initiallyCollapsed}
125+
initiallyCollapsed={formPanelProps.initiallyCollapsed}
128126
/>
129127
);
130128
};

0 commit comments

Comments
 (0)