Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
efdc185
fix(profiling): render single-sample continuous profile chunks (#116234)
JoshuaKGoldberg May 27, 2026
c0c1db8
fix(dynamic-sampling): use the correct field name for dynamic samplin…
shellmayr May 27, 2026
31cb4e0
fix(metrics): Resolve flaky metrics tab tests (#116280)
nsdeschenes May 27, 2026
aff6a03
ref(search): Add EAP API attribute visibility checks (#116091)
nsdeschenes May 27, 2026
18e55f3
style: add right padding to seer header copy button (#116286)
obostjancic May 27, 2026
1b67236
feat(low-value-spans): Add configuration issue UI (#116271)
ArthurKnaus May 27, 2026
3df62b0
ref(snuba): Port query subscriptions consumer to taskbroker raw mode …
untitaker May 27, 2026
a1a1c16
chore(dashboards): remove text widget flag defintion (#116212)
nikkikapadia May 27, 2026
7787bda
ref(replays): Remove unused data export notifications endpoint (#116232)
DominikB2014 May 27, 2026
df7db7a
chore(relocation) Exclude Email model from relocations v2 (#116256)
markstory May 27, 2026
b8b171b
fix(relocation) Remove invalid token scopes during export (#116214)
markstory May 27, 2026
aa346d8
feat(preprod): Display snapshot image tags in card headers (#115723)
mtopo27 May 27, 2026
b93e3af
fix(replays): Shrink timeline hover timestamp (#116268)
scttcper May 27, 2026
dec0ff2
ref: instruct agents to prefer type inference over call-side generics…
TkDodo May 27, 2026
005f194
test(eap): Query typed-colon attribute as boolean instead of number (…
phacops May 27, 2026
63656dd
ref(github-enterprise): Use monospace font for private key field (#11…
evanpurkhiser May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .agents/skills/generate-frontend-forms/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -914,7 +914,13 @@ mutationOptions={{

Type the `mutationFn` with the API's data type, **not** the zod schema type. The schema is for client-side field validation — the mutation should accept whatever the API endpoint accepts. Don't use generic types like `Record<string, unknown>` either, as that breaks TanStack Form's ability to narrow field types.

**NEVER pass call-site generics to `mutationOptions`, `useMutation`, or any TanStack Query function.** Types must be inferred, not asserted. See the full rules in `static/AGENTS.md` under "TanStack Query Type Inference."

```tsx
// ❌ NEVER pass generics to mutationOptions/useMutation
mutationOptions<unknown, RequestError, Variables, Context>({...})
useMutation<Response, RequestError, Variables>({...})

// ❌ Don't use generic types - breaks field type narrowing
const opts = mutationOptions({
mutationFn: (data: Record<string, unknown>) => fetchMutation({...}),
Expand All @@ -925,9 +931,15 @@ const opts = mutationOptions({
mutationFn: (data: Partial<z.infer<typeof preferencesSchema>>) => fetchMutation({...}),
});

// ✅ Use the API's data type
// ❌ Don't explicitly type context — it's inferred from onMutate return
type MyContext = {previousData: UserDetails};

// ❌ Don't use RequestError as the error generic — use runtime narrowing instead

// ✅ Use the API's data type on mutationFn, let everything else be inferred
const opts = mutationOptions({
mutationFn: (data: Partial<UserDetails>) => fetchMutation({...}),
mutationFn: (data: Partial<UserDetails>) =>
fetchMutation<UserDetails>({...}),
});
```

Expand Down
27 changes: 24 additions & 3 deletions .agents/skills/migrate-frontend-forms/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,15 +216,24 @@ mutationOptions={{

Make sure the zod schema's types are compatible with (i.e., assignable to) the API type. For example, if the API expects a string union like `'off' | 'low' | 'high'`, use `z.enum(['off', 'low', 'high'])` instead of `z.string()`.

**Don't pass generics to `useMutation` either.** Type the `mutationFn` payload, and let `fetchMutation<TReturn>` carry the return type. `useMutation<TData, TError, TVariables>` is not the codebase style.
**NEVER pass call-site generics to `useMutation`, `mutationOptions`, or any TanStack Query function.** This applies to ALL generics — data, error, variables, AND context. Types must be inferred, not asserted. See the full rules in `static/AGENTS.md` under "TanStack Query Type Inference."

```tsx
// ❌ Generics on useMutation
// ❌ Generics on useMutation — NEVER do this
const mutation = useMutation<CodeOwner, RequestError, [Payload]>({
mutationFn: ([payload]) => fetchMutation({url, method: 'POST', data: payload}),
});

// ✅ Type the payload; fetchMutation<T> carries the return type
// ❌ Generics on mutationOptions — NEVER do this either
mutationOptions<unknown, RequestError, Variables, MyContext>({...})

// ❌ Explicit context type — inferred from onMutate return
type MyContext = {changeId: string};

// ❌ RequestError as error generic — it's a type assertion in disguise
// Other things can go wrong that would NOT yield a RequestError

// ✅ Type the mutationFn payload; fetchMutation<T> carries the return type
const mutation = useMutation({
mutationFn: (payload: {codeMappingId: string; raw: string}) =>
fetchMutation<CodeOwner>({
Expand All @@ -233,6 +242,18 @@ const mutation = useMutation({
data: payload,
}),
});

// ✅ Context is inferred from onMutate, error is Error by default
mutationOptions({
mutationFn: (variables: MyVars) => fetchMutation<MyResponse>({...}),
onMutate: async () => {
return {changeId: uniqueId()}; // context type inferred from this
},
onError: (_error, _vars, context) => {
// context?.changeId is typed automatically
// _error is Error — use runtime narrowing for RequestError
},
})
```

### mapFormErrors → `setFieldErrors`
Expand Down
8 changes: 0 additions & 8 deletions fixtures/backup/app-user-with-empty-email.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
[
{
"model": "sentry.email",
"pk": 9,
"fields": {
"email": "",
"date_added": "2023-07-26T19:19:37.201Z"
}
},
{
"model": "sentry.user",
"pk": 34,
Expand Down
6 changes: 0 additions & 6 deletions src/sentry/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,6 @@
from sentry.relocation.api.endpoints.recover import RelocationRecoverEndpoint
from sentry.relocation.api.endpoints.retry import RelocationRetryEndpoint
from sentry.relocation.api.endpoints.unpause import RelocationUnpauseEndpoint
from sentry.replays.endpoints.data_export_notifications import DataExportNotificationsEndpoint
from sentry.replays.endpoints.organization_replay_count import OrganizationReplayCountEndpoint
from sentry.replays.endpoints.organization_replay_details import OrganizationReplayDetailsEndpoint
from sentry.replays.endpoints.organization_replay_events_meta import (
Expand Down Expand Up @@ -3795,11 +3794,6 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
AcceptOrganizationInvite.as_view(),
name="sentry-api-0-organization-accept-organization-invite",
),
re_path(
r"^data-export/notifications/google-cloud/$",
DataExportNotificationsEndpoint.as_view(),
name="sentry-api-0-data-export-notifications",
),
re_path(
r"^notification-defaults/$",
NotificationDefaultsEndpoints.as_view(),
Expand Down
1 change: 1 addition & 0 deletions src/sentry/backup/services/import_export/impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@ def filter_objects(queryset_iterator):
# keep track of the new pk in the input map as well.
in_pk_map.insert(model_name, item.pk, item.pk, ImportKind.Inserted)
out_pk_map.insert(model_name, item.pk, item.pk, ImportKind.Inserted)
item.normalize_before_relocation_export()
yield item

def yield_objects():
Expand Down
1 change: 1 addition & 0 deletions src/sentry/conf/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,7 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str:
"sentry.seer.entrypoints.operator",
"sentry.seer.entrypoints.slack.messaging",
"sentry.seer.entrypoints.slack.tasks",
"sentry.snuba.query_subscriptions.run",
"sentry.snuba.tasks",
"sentry.tasks.activity",
"sentry.tasks.assemble",
Expand Down
6 changes: 6 additions & 0 deletions src/sentry/db/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,12 @@ def normalize_before_relocation_import(

return old_pk

def normalize_before_relocation_export(self) -> None:
"""
Called during export and enables records to clean/prep their data for export.
"""
pass

def write_relocation_import(
self, _s: ImportScope, _f: ImportFlags
) -> tuple[int, ImportKind] | None:
Expand Down
12 changes: 6 additions & 6 deletions src/sentry/dynamic_sampling/per_org/tasks/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DynamicSamplingQueryFilters(StrEnum):


class DynamicSamplingQueryFields(StrEnum):
ROOT_PROJECT = "sentry.dsc.root_project"
DSC_PROJECT_ID = "sentry.dsc.project_id"
COUNT = "count()"
COUNT_SAMPLE = "count_sample()"

Expand Down Expand Up @@ -126,11 +126,11 @@ def get_eap_project_volumes(
),
"query_string": DynamicSamplingQueryFilters.IS_SEGMENT,
"selected_columns": [
DynamicSamplingQueryFields.ROOT_PROJECT,
DynamicSamplingQueryFields.DSC_PROJECT_ID,
DynamicSamplingQueryFields.COUNT,
DynamicSamplingQueryFields.COUNT_SAMPLE,
],
"orderby": [DynamicSamplingQueryFields.ROOT_PROJECT],
"orderby": [DynamicSamplingQueryFields.DSC_PROJECT_ID],
"referrer": Referrer.DYNAMIC_SAMPLING_PER_ORG_GET_EAP_PROJECT_VOLUMES.value,
"config": SearchResolverConfig(
auto_fields=True,
Expand All @@ -141,13 +141,13 @@ def get_eap_project_volumes(
):
total = _get_aggregate_int(row, DynamicSamplingQueryFields.COUNT)
keep = _get_aggregate_int(row, DynamicSamplingQueryFields.COUNT_SAMPLE)
root_project = row.get(DynamicSamplingQueryFields.ROOT_PROJECT)
if root_project is None:
dsc_project_id = row.get(DynamicSamplingQueryFields.DSC_PROJECT_ID)
if dsc_project_id is None:
continue

project_volumes.append(
ProjectVolume(
project_id=ProjectId(int(root_project)),
project_id=ProjectId(int(dsc_project_id)),
total=total,
keep=keep,
drop=max(total - keep, 0),
Expand Down
2 changes: 0 additions & 2 deletions src/sentry/features/temporary.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ def register_temporary_features(manager: FeatureManager) -> None:
manager.add("organizations:dashboards-prebuilt-insights-dashboards", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable the details widget for dashboards
manager.add("organizations:dashboards-details-widget", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable text widgets for dashboards
manager.add("organizations:dashboards-text-widgets", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable Insights to Dashboards UI migration
manager.add("organizations:insights-to-dashboards-ui-rollout", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True)
# Enable AI-powered dashboard generation via Seer
Expand Down
11 changes: 11 additions & 0 deletions src/sentry/models/apitoken.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,17 @@ def sanitize_relocation_json(
lambda _: hashed_refresh_token,
)

def normalize_before_relocation_export(self) -> None:
"""
Trim ApiToken scopes to eliminate scopes that are no longer valid.

We have historical ApiToken records with the `project:distribution` scope
which is no longer valid for ApiTokens.
"""
from sentry.receivers.tokens import enforce_scope_hierarchy

enforce_scope_hierarchy(self)

@property
def organization_id(self) -> int | None:
from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation
Expand Down
25 changes: 0 additions & 25 deletions src/sentry/replays/endpoints/data_export_notifications.py

This file was deleted.

Loading
Loading