Skip to content

Commit 1719847

Browse files
authored
Merge branch 'master' into aliu/timeout-ui
2 parents c795960 + f5e6ec5 commit 1719847

File tree

548 files changed

+12217
-28435
lines changed

Some content is hidden

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

548 files changed

+12217
-28435
lines changed

.github/file-filters.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
sentry_frontend_workflow_file: &sentry_frontend_workflow_file
44
- added|modified: '.github/workflows/frontend.yml'
55

6+
sentry_frontend_snapshots_workflow_file: &sentry_frontend_snapshots_workflow_file
7+
- added|modified: '.github/workflows/frontend-snapshots.yml'
8+
69
# Provides list of changed files to test (jest)
710
# getsentry/sentry does not use the list directly, instead we shard tests inside jest.config.js
811
testable_modified: &testable_modified
@@ -30,6 +33,7 @@ typecheckable_rules_changed: &typecheckable_rules_changed
3033
# Trigger to apply the 'Scope: Frontend' label to PRs
3134
frontend_all: &frontend_all
3235
- *sentry_frontend_workflow_file
36+
- *sentry_frontend_snapshots_workflow_file
3337
- added|modified: '**/*.{ts,tsx,js,jsx,mjs}'
3438
- added|modified: 'static/**/*.{less,json,yml,md,mdx}'
3539
- added|modified: '{vercel,tsconfig,biome,package}.json'

.github/workflows/frontend-optional.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,17 @@ jobs:
9090

9191
- uses: ./.github/actions/setup-node-pnpm
9292

93+
- name: jest transform cache
94+
uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
95+
with:
96+
path: |
97+
.cache/jest
98+
~/.cache/swc
99+
key: jest-cache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'jest.config.ts') }}-${{ matrix.instance }}
100+
restore-keys: |
101+
jest-cache-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', 'jest.config.ts') }}-
102+
jest-cache-${{ runner.os }}-
103+
93104
- name: Download jest-balance.json
94105
id: download-artifact
95106
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11

.github/workflows/frontend-snapshots.yml

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -69,37 +69,19 @@ jobs:
6969

7070
- name: Install sentry-cli
7171
if: ${{ !cancelled() }}
72-
run: curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION=3.3.4 sh
72+
run: curl -sL https://sentry.io/get-cli/ | SENTRY_CLI_VERSION=3.3.5 sh
7373

7474
- name: Upload snapshots
7575
id: upload
7676
if: ${{ !cancelled() }}
7777
env:
7878
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_SNAPSHOTS_AUTH_TOKEN }}
79-
run: |
80-
ARGS=(
81-
--log-level=debug
82-
--auth-token "${{ secrets.SENTRY_SNAPSHOTS_AUTH_TOKEN }}"
83-
build snapshots "${{ env.SNAPSHOT_OUTPUT_DIR }}"
84-
--app-id sentry-frontend
85-
--project sentry-frontend
86-
--head-sha "${{ github.event.pull_request.head.sha || github.sha }}"
87-
--vcs-provider github
88-
--head-repo-name "${{ github.repository }}"
89-
)
90-
91-
# PR-only flags: base-sha, base-ref, base-repo-name, head-ref, pr-number
92-
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
93-
ARGS+=(
94-
--base-sha "${{ github.event.pull_request.base.sha }}"
95-
--base-repo-name "${{ github.repository }}"
96-
--head-ref "${{ github.head_ref }}"
97-
--base-ref "${{ github.base_ref }}"
98-
--pr-number "${{ github.event.number }}"
99-
)
100-
fi
101-
102-
sentry-cli "${ARGS[@]}"
79+
run: >
80+
sentry-cli
81+
--log-level=debug
82+
build snapshots "${{ env.SNAPSHOT_OUTPUT_DIR }}"
83+
--app-id sentry-frontend
84+
--project sentry-frontend
10385
10486
- name: Report upload failure to Sentry
10587
if: ${{ failure() && steps.upload.outcome == 'failure' }}

CHANGES

Lines changed: 2286 additions & 0 deletions
Large diffs are not rendered by default.

eslint.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ export default typescript.config([
462462
'@sentry/no-flag-comments': 'error',
463463
'@sentry/no-static-translations': 'error',
464464
'@sentry/no-styled-shortcut': 'error',
465+
'@sentry/no-unnecessary-use-callback': 'error',
465466
},
466467
},
467468
{

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,10 @@ dependencies = [
8787
"sentry-forked-email-reply-parser>=0.5.12.post1",
8888
"sentry-kafka-schemas>=2.1.27",
8989
"sentry-ophio>=1.1.3",
90-
"sentry-protos>=0.8.11",
90+
"sentry-protos>=0.8.12",
9191
"sentry-redis-tools>=0.5.0",
92-
"sentry-relay>=0.9.26",
92+
"sentry-relay>=0.9.27",
93+
"sentry-scm>=0.1.7",
9394
"sentry-sdk[http2]>=2.47.0",
9495
"sentry-usage-accountant>=0.0.10",
9596
# remove once there are no unmarked transitive dependencies on setuptools

scripts/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ const CONSTANTS: Record<string, string> = {
207207
'IssueTaxonomy.ERRORS_AND_OUTAGES': 'errors-outages',
208208
'IssueTaxonomy.BREACHED_METRICS': 'breached-metrics',
209209
'IssueTaxonomy.WARNINGS': 'warnings',
210+
'IssueTaxonomy.SENTRY_CONFIGURATION': 'sentry-configuration',
210211
};
211212

212213
function resolveTemplate(expr: string): string {

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = sentry
3-
version = 26.4.0.dev0
3+
version = 26.5.0.dev0
44
description = A realtime logging and aggregation server.
55
long_description = file: README.md
66
long_description_content_type = text/markdown

src/sentry/api/authentication.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -789,19 +789,47 @@ class ViewerContextAuthentication(BaseAuthentication):
789789
"""
790790

791791
def authenticate(self, request: Request) -> tuple[Any, Any] | None:
792-
from sentry.viewer_context import viewer_context_from_header
792+
from sentry.viewer_context import _get_verification_keys, viewer_context_from_header
793793

794794
header = request.META.get("HTTP_X_VIEWER_CONTEXT")
795795
if not header:
796796
return None
797797

798798
signature = request.META.get("HTTP_X_VIEWER_CONTEXT_SIGNATURE")
799+
verification_keys = _get_verification_keys()
799800
vc = viewer_context_from_header(header, signature)
801+
800802
if vc is None or vc.user_id is None:
803+
# TODO(jstanley): Temporary logging for debugging non-public prod 401s
804+
# during X-Viewer-Context propagation (Seer code mode callbacks).
805+
# Remove once the auth issue is resolved.
806+
logger.warning(
807+
"viewer_context_auth.failed",
808+
extra={
809+
"reason": "vc_not_resolved" if vc is None else "no_user_id",
810+
"header_length": len(header),
811+
"header_is_jwt": "." in header and header.count(".") == 2,
812+
"signature_present": signature is not None,
813+
"signature_length": len(signature) if signature else 0,
814+
"verification_key_count": len(verification_keys),
815+
"path": request.path,
816+
},
817+
)
801818
return None
802819

803820
user = user_service.get_user(user_id=vc.user_id)
804821
if user is None or not user.is_active:
822+
# TODO(jstanley): Temporary logging for debugging non-public prod 401s
823+
# during X-Viewer-Context propagation (Seer code mode callbacks).
824+
# Remove once the auth issue is resolved.
825+
logger.warning(
826+
"viewer_context_auth.failed",
827+
extra={
828+
"reason": "user_not_found" if user is None else "user_inactive",
829+
"vc_user_id": vc.user_id,
830+
"path": request.path,
831+
},
832+
)
805833
return None
806834

807835
sentry_sdk.get_isolation_scope().set_tag("viewer_context_auth", True)

src/sentry/api/endpoints/internal/feature_flags.py

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from collections.abc import Mapping
2+
13
from django.conf import settings
24
from rest_framework.request import Request
35
from rest_framework.response import Response
@@ -10,6 +12,30 @@
1012
from sentry.runner.settings import configure, discover_configs
1113

1214

15+
def _coerce_early_feature_flag_value(value: object) -> bool | None:
16+
"""
17+
Map API input to a bool for SENTRY_FEATURES. Accepts JSON booleans, 0/1
18+
(common in scripts), and the strings "true"/"false" (case-insensitive).
19+
Returns None if the value cannot be interpreted safely.
20+
"""
21+
if isinstance(value, bool):
22+
return value
23+
if isinstance(value, int):
24+
if value == 1:
25+
return True
26+
if value == 0:
27+
return False
28+
return None
29+
if isinstance(value, str):
30+
lowered = value.lower()
31+
if lowered == "true":
32+
return True
33+
if lowered == "false":
34+
return False
35+
return None
36+
return None
37+
38+
1339
@all_silo_endpoint
1440
class InternalFeatureFlagsEndpoint(Endpoint):
1541
permission_classes = (SuperuserPermission,)
@@ -36,18 +62,38 @@ def put(self, request: Request) -> Response:
3662
if not settings.SENTRY_SELF_HOSTED:
3763
return Response("You are not self-hosting Sentry.", status=403)
3864

39-
data = request.data.keys()
40-
valid_feature_flags = [flag for flag in data if SENTRY_EARLY_FEATURES.get(flag, False)]
65+
payload: object = request.data
66+
if not isinstance(payload, Mapping):
67+
return Response(
68+
{"detail": "Feature flag updates must be a JSON object."},
69+
status=400,
70+
)
71+
72+
valid_feature_flags = [flag for flag in payload if flag in SENTRY_EARLY_FEATURES]
73+
coerced_values: dict[str, bool] = {}
74+
for valid_flag in valid_feature_flags:
75+
coerced = _coerce_early_feature_flag_value(payload.get(valid_flag))
76+
if coerced is None:
77+
return Response(
78+
{
79+
"detail": (
80+
f'Feature flag "{valid_flag}" must be true or false '
81+
f"(boolean, 0 or 1, or the string true or false)."
82+
)
83+
},
84+
status=400,
85+
)
86+
coerced_values[valid_flag] = coerced
87+
4188
_, py, yml = discover_configs()
4289
# Open the file for reading and writing
4390
with open(py, "r+") as file:
4491
lines = file.readlines()
4592
# print(lines)
4693
for valid_flag in valid_feature_flags:
4794
match_found = False
48-
new_string = (
49-
f'\nSENTRY_FEATURES["{valid_flag}"]={request.data.get(valid_flag, False)}\n'
50-
)
95+
python_bool = "True" if coerced_values[valid_flag] else "False"
96+
new_string = f'\nSENTRY_FEATURES["{valid_flag}"]={python_bool}\n'
5197
# Search for the string match and update lines
5298
for i, line in enumerate(lines):
5399
if valid_flag in line:

0 commit comments

Comments
 (0)