diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index d1bfce0f68a0..9a7b27d8e79a 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -856,8 +856,6 @@ tests/sentry/api/endpoints/test_organization_attribute_mappings.py @get
/.agents/skills/lint-fix/ @getsentry/design-engineering
/.agents/skills/lint-new/ @getsentry/design-engineering
/.agents/skills/react-component-documentation/ @getsentry/design-engineering
-# Orphaned files we had to touch to fix errors
-/static/gsAdmin/views/sentryAppDetails.spec.tsx @getsentry/design-engineering
## End of Frontend Platform
# Coding Workflows
diff --git a/.github/actions/setup-devservices/action.yml b/.github/actions/setup-devservices/action.yml
deleted file mode 100644
index 061ff20d9b72..000000000000
--- a/.github/actions/setup-devservices/action.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-name: 'Early Devservices'
-description: 'Starts devservices in the background so image pulls overlap with venv setup'
-inputs:
- mode:
- description: 'devservices mode (must match the mode passed to setup-sentry)'
- required: true
- timeout-minutes:
- description: 'Maximum minutes for devservices up'
- required: false
- default: '10'
-
-runs:
- using: 'composite'
- steps:
- - uses: astral-sh/setup-uv@884ad927a57e558e7a70b92f2bccf9198a4be546 # v6
- with:
- version: '0.9.28'
- enable-cache: false
-
- - name: Start devservices in background
- shell: bash --noprofile --norc -euo pipefail {0}
- run: |
- DS_VERSION=$(python3 -c "
- import tomllib
- with open('uv.lock', 'rb') as f:
- lock = tomllib.load(f)
- for pkg in lock['package']:
- if pkg['name'] == 'devservices':
- print(pkg['version'])
- break
- ")
- echo "Installing devservices==${DS_VERSION}"
- uv venv /tmp/ds-venv --python python3 -q
- uv pip install --python /tmp/ds-venv/bin/python -q \
- --index-url https://pypi.devinfra.sentry.io/simple \
- "devservices==${DS_VERSION}"
- (set +e; timeout ${{ inputs.timeout-minutes }}m /tmp/ds-venv/bin/devservices up --mode ${{ inputs.mode }}; echo $? > /tmp/ds-exit) \
- > /tmp/ds.log 2>&1 &
diff --git a/.github/actions/setup-devservices/bootstrap-snuba.py b/.github/actions/setup-devservices/bootstrap-snuba.py
deleted file mode 100755
index c0f4ea8a7e7b..000000000000
--- a/.github/actions/setup-devservices/bootstrap-snuba.py
+++ /dev/null
@@ -1,250 +0,0 @@
-#!/usr/bin/env python3
-"""Bootstrap per-worker Snuba instances for CI.
-
-Overlaps the expensive ClickHouse table setup with the devservices
-health-check wait.
-
-Phase 1 (early): As soon as ClickHouse is accepting queries, create
- per-worker databases and run ``snuba bootstrap --force``.
-Phase 2 (after devservices): Stop snuba-snuba-1 and start per-worker
- API containers. We must wait for devservices to finish first —
- stopping the container while devservices is health-checking it would
- cause a timeout.
-
-Requires: XDIST_WORKERS env var
-Reads: /tmp/ds-exit (written by setup-devservices/wait.sh)
-Writes: /tmp/snuba-bootstrap-exit
-"""
-
-from __future__ import annotations
-
-import os
-import subprocess
-import sys
-import time
-from concurrent.futures import ThreadPoolExecutor, as_completed
-from functools import partial
-from pathlib import Path
-from typing import Any, Callable
-from urllib.error import URLError
-from urllib.request import urlopen
-
-DS_EXIT = Path("/tmp/ds-exit")
-SNUBA_EXIT = Path("/tmp/snuba-bootstrap-exit")
-
-SNUBA_ENV = {
- "CLICKHOUSE_HOST": "clickhouse",
- "CLICKHOUSE_PORT": "9000",
- "CLICKHOUSE_HTTP_PORT": "8123",
- "DEFAULT_BROKERS": "kafka:9093",
- "REDIS_HOST": "redis",
- "REDIS_PORT": "6379",
- "REDIS_DB": "1",
- "SNUBA_SETTINGS": "docker",
-}
-
-ENV_ARGS = [flag for k, v in SNUBA_ENV.items() for flag in ("-e", f"{k}={v}")]
-
-
-def retry(
- fn: Callable[[], Any], *, attempts: int = 3, delay: int = 5, label: str = "operation"
-) -> Any:
- for i in range(attempts):
- try:
- return fn()
- except Exception:
- if i == attempts - 1:
- raise
- log(f"{label} failed (attempt {i + 1}/{attempts}), retrying in {delay}s...")
- time.sleep(delay)
-
-
-def log(msg: str) -> None:
- print(msg, flush=True)
-
-
-def fail(msg: str) -> None:
- log(f"::error::{msg}")
- SNUBA_EXIT.write_text("1")
- sys.exit(1)
-
-
-def http_ok(url: str) -> bool:
- try:
- with urlopen(url, timeout=3):
- return True
- except (URLError, OSError):
- return False
-
-
-def docker(
- *args: str, check: bool = False, timeout: int | None = None
-) -> subprocess.CompletedProcess[str]:
- return subprocess.run(
- ["docker", *args], capture_output=True, text=True, check=check, timeout=timeout
- )
-
-
-def docker_inspect(container: str, fmt: str) -> str:
- r = docker("inspect", container, "--format", fmt)
- return r.stdout.strip() if r.returncode == 0 else ""
-
-
-def inspect_snuba_container() -> tuple[str, str]:
- image = docker_inspect("snuba-snuba-1", "{{.Config.Image}}")
- network = docker_inspect(
- "snuba-snuba-1",
- "{{range $k, $v := .NetworkSettings.Networks}}{{$k}}{{end}}",
- )
- if not image or not network:
- fail("Could not inspect snuba-snuba-1 container")
- return image, network
-
-
-def run_parallel(fn: Callable[[int], Any], workers: range, *, fail_fast: bool = True) -> int:
- """Run fn(i) in parallel for each i in workers. Returns 0 on full success."""
- rc = 0
- with ThreadPoolExecutor(max_workers=len(workers)) as pool:
- futs = {pool.submit(fn, i): i for i in workers}
- for fut in as_completed(futs):
- try:
- fut.result()
- except Exception as e:
- if fail_fast:
- fail(str(e))
- log(f"ERROR: {e}")
- rc = 1
- return rc
-
-
-def wait_for_prerequisites(timeout: int = 300) -> None:
- log("Waiting for ClickHouse and Snuba container...")
- start = time.monotonic()
- while True:
- if time.monotonic() - start > timeout:
- fail("Timed out waiting for Snuba bootstrap prerequisites")
- if http_ok("http://localhost:8123/") and docker_inspect("snuba-snuba-1", "{{.Id}}"):
- break
- time.sleep(2)
- log(f"Prerequisites ready ({time.monotonic() - start:.0f}s)")
-
-
-def wait_for_devservices(timeout: int = 300) -> None:
- start = time.monotonic()
- while not DS_EXIT.exists():
- if time.monotonic() - start > timeout:
- fail("Timed out waiting for devservices to finish")
- time.sleep(1)
- rc = int(DS_EXIT.read_text().strip())
- if rc != 0:
- fail(f"devservices failed (exit {rc}), skipping Phase 2")
-
-
-def bootstrap_worker(worker_id: int, *, image: str, network: str) -> None:
- """Create a ClickHouse database and run snuba bootstrap."""
- db = f"default_gw{worker_id}"
-
- def create_db() -> None:
- with urlopen(
- "http://localhost:8123/", f"CREATE DATABASE IF NOT EXISTS {db}".encode(), timeout=30
- ):
- pass
-
- retry(create_db, label=f"CREATE DATABASE {db}")
-
- def run_bootstrap() -> None:
- r = docker(
- "run",
- "--rm",
- "--network",
- network,
- "-e",
- f"CLICKHOUSE_DATABASE={db}",
- *ENV_ARGS,
- image,
- "bootstrap",
- "--force",
- )
- for line in (r.stdout + r.stderr).strip().splitlines()[-3:]:
- log(line)
- if r.returncode != 0:
- raise RuntimeError(f"snuba bootstrap failed for worker {worker_id}")
-
- retry(run_bootstrap, label=f"snuba bootstrap gw{worker_id}")
-
-
-def start_worker_container(worker_id: int, *, image: str, network: str) -> None:
- """Start a per-worker Snuba API container and wait for health."""
- db = f"default_gw{worker_id}"
- port = 1230 + worker_id
- name = f"snuba-gw{worker_id}"
-
- docker("rm", "-f", name)
-
- r = docker(
- "run",
- "-d",
- "--name",
- name,
- "--network",
- network,
- "-p",
- f"{port}:1218",
- "-e",
- f"CLICKHOUSE_DATABASE={db}",
- *ENV_ARGS,
- "-e",
- "DEBUG=1",
- image,
- "api",
- )
- if r.returncode != 0:
- raise RuntimeError(f"docker run {name} failed: {r.stderr.strip()}")
-
- for attempt in range(1, 31):
- if http_ok(f"http://127.0.0.1:{port}/health"):
- log(f"{name} healthy on port {port}")
- return
- if attempt == 30:
- r = docker("logs", name)
- for line in (r.stdout + r.stderr).strip().splitlines()[-20:]:
- log(line)
- raise RuntimeError(f"{name} failed health check after 30 attempts")
- time.sleep(2)
-
-
-def main() -> None:
- workers_str = os.environ.get("XDIST_WORKERS")
- if not workers_str:
- fail("XDIST_WORKERS must be set")
- workers = range(int(workers_str))
- start = time.monotonic()
-
- wait_for_prerequisites()
- image, network = inspect_snuba_container()
-
- log("Phase 1: bootstrapping ClickHouse databases")
- run_parallel(partial(bootstrap_worker, image=image, network=network), workers)
- log(f"Phase 1 done ({time.monotonic() - start:.0f}s)")
-
- wait_for_devservices()
- try:
- docker("stop", "snuba-snuba-1", timeout=30)
- except subprocess.TimeoutExpired:
- log("WARNING: docker stop snuba-snuba-1 timed out, killing")
- docker("kill", "snuba-snuba-1")
-
- log("Phase 2: starting per-worker Snuba API containers")
- rc = run_parallel(
- partial(start_worker_container, image=image, network=network),
- workers,
- fail_fast=False,
- )
-
- log(f"Snuba bootstrap complete ({time.monotonic() - start:.0f}s total)")
- SNUBA_EXIT.write_text(str(rc))
- sys.exit(rc)
-
-
-if __name__ == "__main__":
- main()
diff --git a/.github/actions/setup-devservices/wait.sh b/.github/actions/setup-devservices/wait.sh
deleted file mode 100755
index 00c319469d73..000000000000
--- a/.github/actions/setup-devservices/wait.sh
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/bin/bash
-set -euo pipefail
-
-# Wait for the background devservices process started by the setup-devservices action.
-# Usage: wait.sh [timeout_seconds]
-TIMEOUT=${1:-600}
-
-SECONDS=0
-while [ ! -f /tmp/ds-exit ]; do
- if [ $SECONDS -gt "$TIMEOUT" ]; then
- echo "::error::Timed out waiting for devservices after ${TIMEOUT}s"
- cat /tmp/ds.log
- exit 1
- fi
- sleep 2
-done
-
-DS_RC=$(< /tmp/ds-exit)
-if [ "$DS_RC" -ne 0 ]; then
- echo "::error::devservices up failed (exit $DS_RC)"
- cat /tmp/ds.log
- exit 1
-fi
-
-echo "DJANGO_LIVE_TEST_SERVER_ADDRESS=$(docker network inspect bridge --format='{{(index .IPAM.Config 0).Gateway}}')" >> "$GITHUB_ENV"
-docker ps -a
diff --git a/.github/codeowners-coverage-baseline.txt b/.github/codeowners-coverage-baseline.txt
index 734fcb38d43f..1f28e295e561 100644
--- a/.github/codeowners-coverage-baseline.txt
+++ b/.github/codeowners-coverage-baseline.txt
@@ -591,6 +591,7 @@ static/app/components/events/breadcrumbs/testUtils.tsx
static/app/components/events/breadcrumbs/utils.tsx
static/app/components/events/device.tsx
static/app/components/events/errorLevel.tsx
+static/app/components/events/eventAnnotation.tsx
static/app/components/events/eventAttachmentActions.tsx
static/app/components/events/eventAttachments.spec.tsx
static/app/components/events/eventAttachments.tsx
diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml
index 652d2ca72b93..8bad8cfafc06 100644
--- a/.github/workflows/acceptance.yml
+++ b/.github/workflows/acceptance.yml
@@ -68,12 +68,6 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
name: Checkout sentry
- - run: mkdir -p config/chartcuterie
-
- - uses: ./.github/actions/setup-devservices
- with:
- mode: acceptance-ci
-
- name: Step configurations
id: config
run: |
@@ -104,12 +98,6 @@ jobs:
id: setup
with:
mode: acceptance-ci
- skip-devservices: 'true'
-
- - name: Wait for devservices
- run: |
- sentry init
- ./.github/actions/setup-devservices/wait.sh
- name: Run acceptance tests (#${{ steps.setup.outputs.matrix-instance-number }} of ${{ steps.setup.outputs.matrix-instance-total }})
run: make run-acceptance
@@ -117,11 +105,6 @@ jobs:
- name: Inspect failure
if: failure()
run: |
- if [ -f /tmp/ds.log ]; then
- echo "--- devservices startup log ---"
- cat /tmp/ds.log
- fi
-
if command -v devservices; then
devservices logs
fi
diff --git a/.github/workflows/backend-with-coverage.yml b/.github/workflows/backend-with-coverage.yml
new file mode 100644
index 000000000000..65f1496ce765
--- /dev/null
+++ b/.github/workflows/backend-with-coverage.yml
@@ -0,0 +1,199 @@
+name: backend - with test coverage
+
+on:
+ workflow_dispatch:
+ workflow_call:
+ # Every 30 minutes
+ schedule:
+ - cron: '0,30 * * * *'
+
+env:
+ SENTRY_SKIP_SELENIUM_PLUGIN: '1'
+
+jobs:
+ # Skip generating coverage if already exists in GCS
+ check-existing-coverage:
+ name: check for existing coverage
+ runs-on: ubuntu-24.04
+ timeout-minutes: 5
+ permissions:
+ contents: read
+ id-token: write
+ outputs:
+ has-coverage: ${{ steps.check-coverage.outputs.exists }}
+ commit-sha: ${{ steps.get-sha.outputs.sha }}
+ steps:
+ - name: Determine commit SHA
+ id: get-sha
+ run: |
+ if [[ "${{ github.event_name }}" == "pull_request" ]]; then
+ COMMIT_SHA="${{ github.event.pull_request.head.sha }}"
+ else
+ COMMIT_SHA="${{ github.sha }}"
+ fi
+ echo "sha=${COMMIT_SHA}" >> "$GITHUB_OUTPUT"
+
+ - name: Authenticate to Google Cloud
+ uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2.1.3
+ with:
+ project_id: sentry-dev-tooling
+ workload_identity_provider: ${{ secrets.SENTRY_GCP_DEV_WORKLOAD_IDENTITY_POOL }}
+ service_account: ${{ secrets.COLLECT_TEST_DATA_SERVICE_ACCOUNT_EMAIL }}
+
+ - name: Check if coverage exists
+ id: check-coverage
+ env:
+ COMMIT_SHA: ${{ steps.get-sha.outputs.sha }}
+ run: |
+ if gcloud storage ls "gs://sentry-coverage-data/${COMMIT_SHA}/.coverage.combined" &>/dev/null; then
+ echo "Coverage already exists for commit ${COMMIT_SHA}, skipping test run"
+ echo "exists=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "No coverage found for commit ${COMMIT_SHA}, will run tests"
+ echo "exists=false" >> "$GITHUB_OUTPUT"
+ fi
+
+ calculate-shards:
+ name: calculate test shards
+ if: needs.check-existing-coverage.outputs.has-coverage != 'true'
+ needs: [check-existing-coverage]
+ runs-on: ubuntu-24.04
+ timeout-minutes: 5
+ outputs:
+ shard-count: ${{ steps.calculate-shards.outputs.shard-count }}
+ shard-indices: ${{ steps.calculate-shards.outputs.shard-indices }}
+ steps:
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+
+ - name: Setup sentry env
+ uses: ./.github/actions/setup-sentry
+ id: setup
+ with:
+ mode: backend-ci
+ skip-devservices: true
+
+ - name: Calculate test shards
+ id: calculate-shards
+ run: python3 .github/workflows/scripts/calculate-backend-test-shards.py
+
+ backend-test-with-cov-context:
+ name: backend test
+ if: needs.check-existing-coverage.outputs.has-coverage != 'true'
+ runs-on: ubuntu-24.04
+ needs: [check-existing-coverage, calculate-shards]
+ timeout-minutes: 60
+ strategy:
+ fail-fast: false
+ matrix:
+ instance: ${{ fromJSON(needs.calculate-shards.outputs.shard-indices) }}
+ env:
+ MATRIX_INSTANCE_TOTAL: ${{ needs.calculate-shards.outputs.shard-count }}
+ TEST_GROUP_STRATEGY: roundrobin
+ steps:
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+
+ - name: Setup sentry env
+ uses: ./.github/actions/setup-sentry
+ id: setup
+ with:
+ mode: backend-ci
+
+ - name: Download odiff binary
+ run: |
+ curl -sL https://registry.npmjs.org/odiff-bin/-/odiff-bin-4.3.2.tgz \
+ | tar -xz --strip-components=2 package/raw_binaries/odiff-linux-x64
+ sudo install -m 755 odiff-linux-x64 /usr/local/bin/odiff
+ rm odiff-linux-x64
+
+ - name: Run backend test with coverage (${{ steps.setup.outputs.matrix-instance-number }} of ${{ steps.setup.outputs.matrix-instance-total }})
+ run: make test-backend-ci-with-coverage
+
+ - name: Validate coverage database
+ if: always()
+ run: |
+ set -euxo pipefail
+ if [[ ! -f .coverage ]]; then
+ echo "Error: No .coverage file found after tests"
+ exit 1
+ fi
+ python -c "import sqlite3; sqlite3.connect('.coverage').execute('SELECT COUNT(*) FROM file')"
+
+ - name: Upload raw coverage sqlite as artifact
+ if: always()
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
+ with:
+ name: pycoverage-sqlite-${{ github.run_id }}-${{ steps.setup.outputs.matrix-instance-number }}
+ path: .coverage
+ if-no-files-found: error
+ retention-days: 7
+ include-hidden-files: true
+
+ combine-coverage:
+ name: combine coverage
+ # Only upload coverage if all test shards pass - incomplete coverage could cause selective testing to skip tests incorrectly
+ if: needs.check-existing-coverage.outputs.has-coverage != 'true'
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: read
+ id-token: write
+ actions: read # used for DIM metadata
+ needs: [check-existing-coverage, backend-test-with-cov-context, calculate-shards]
+ steps:
+ - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
+
+ - uses: astral-sh/setup-uv@884ad927a57e558e7a70b92f2bccf9198a4be546 # v6
+ with:
+ version: '0.9.28'
+ enable-cache: false
+
+ - name: Download all coverage artifacts
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
+ with:
+ pattern: pycoverage-sqlite-${{ github.run_id }}-*
+ path: .artifacts/all-coverage
+
+ - name: Verify all shards produced coverage
+ env:
+ EXPECTED_SHARD_COUNT: ${{ needs.calculate-shards.outputs.shard-count }}
+ run: |
+ set -euxo pipefail
+
+ echo "Downloaded coverage artifacts:"
+ ls -la .artifacts/all-coverage || true
+
+ COVERAGE_FILE_COUNT=$(find .artifacts/all-coverage -name ".coverage" -type f | wc -l)
+ echo "Found ${COVERAGE_FILE_COUNT} coverage files, expected ${EXPECTED_SHARD_COUNT}"
+
+ if [[ "$COVERAGE_FILE_COUNT" -ne "$EXPECTED_SHARD_COUNT" ]]; then
+ echo "Error: Missing coverage files. Expected ${EXPECTED_SHARD_COUNT}, found ${COVERAGE_FILE_COUNT}"
+ echo "This indicates some test shards failed to produce coverage."
+ find .artifacts/all-coverage -name ".coverage" -type f
+ exit 1
+ fi
+
+ - name: Combine all coverage databases
+ run: |
+ set -euxo pipefail
+ uvx --with covdefaults --with sentry-covdefaults-disable-branch-coverage \
+ coverage combine $(find .artifacts/all-coverage -name ".coverage" -type f)
+
+ if [[ ! -f .coverage ]]; then
+ echo "Error: Combined coverage file was not created"
+ exit 1
+ fi
+
+ mv .coverage ".coverage.combined"
+
+ - name: Authenticate to Google Cloud
+ id: gcloud-auth
+ uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed # v2.1.3
+ with:
+ project_id: sentry-dev-tooling
+ workload_identity_provider: ${{ secrets.SENTRY_GCP_DEV_WORKLOAD_IDENTITY_POOL }}
+ service_account: ${{ secrets.COLLECT_TEST_DATA_SERVICE_ACCOUNT_EMAIL }}
+
+ - name: Upload coverage to GCS
+ uses: google-github-actions/upload-cloud-storage@c0f6160ff80057923ff50e5e567695cea181ec23 # v2.2.4
+ with:
+ path: .coverage.combined
+ destination: sentry-coverage-data/${{ needs.check-existing-coverage.outputs.commit-sha }}
diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml
index fbcde1793f94..23b62cfad424 100644
--- a/.github/workflows/backend.yml
+++ b/.github/workflows/backend.yml
@@ -59,23 +59,11 @@ jobs:
node-version-file: '.node-version'
- uses: pnpm/action-setup@9b5745cdf0a2e8c2620f0746130f809adb911c19 # v4
-
- - name: Start devservices early
- uses: ./.github/actions/setup-devservices
- with:
- mode: default
-
- name: Setup sentry python env
uses: ./.github/actions/setup-sentry
id: setup
with:
mode: default
- skip-devservices: 'true'
-
- - name: Wait for devservices
- run: |
- sentry init
- ./.github/actions/setup-devservices/wait.sh
- name: Run API docs tests
run: |
@@ -246,28 +234,11 @@ jobs:
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - name: Start devservices early
- uses: ./.github/actions/setup-devservices
- with:
- mode: backend-ci
-
- name: Setup sentry env
uses: ./.github/actions/setup-sentry
id: setup
with:
mode: backend-ci
- skip-devservices: 'true'
-
- - name: Wait for devservices
- run: |
- sentry init
- # Start Snuba per-worker bootstrap in background — it polls for
- # ClickHouse readiness itself, so it can begin before all containers
- # are healthy, overlapping with the remaining devservices health checks.
- if [ "${XDIST_PER_WORKER_SNUBA}" = "1" ]; then
- python3 ./.github/actions/setup-devservices/bootstrap-snuba.py &
- fi
- ./.github/actions/setup-devservices/wait.sh
- name: Download odiff binary
run: |
@@ -276,23 +247,59 @@ jobs:
sudo install -m 755 odiff-linux-x64 /usr/local/bin/odiff
rm odiff-linux-x64
- - name: Wait for Snuba bootstrap
+ - name: Bootstrap per-worker Snuba instances
if: env.XDIST_PER_WORKER_SNUBA == '1'
run: |
- SECONDS=0
- while [ ! -f /tmp/snuba-bootstrap-exit ]; do
- if [ $SECONDS -gt 600 ]; then
- echo "::error::Timed out waiting for Snuba bootstrap after 600s"
- exit 1
- fi
- sleep 2
- done
- SNUBA_RC=$(cat /tmp/snuba-bootstrap-exit)
- if [ "$SNUBA_RC" -ne 0 ]; then
- echo "::error::Snuba per-worker bootstrap failed"
+ set -eo pipefail
+ SNUBA_IMAGE=$(docker inspect snuba-snuba-1 --format '{{.Config.Image}}')
+ SNUBA_NETWORK=$(docker inspect snuba-snuba-1 --format '{{range $k, $v := .NetworkSettings.Networks}}{{$k}}{{end}}')
+ if [ -z "$SNUBA_IMAGE" ] || [ -z "$SNUBA_NETWORK" ]; then
+ echo "ERROR: Could not inspect snuba-snuba-1 container. Is devservices running?"
exit 1
fi
+ docker stop snuba-snuba-1 || true
+
+ PIDS=()
+ for i in $(seq 0 $(( ${XDIST_WORKERS} - 1 ))); do
+ (
+ WORKER_DB="default_gw${i}"
+ WORKER_PORT=$((1230 + i))
+ curl -sf 'http://localhost:8123/' --data-binary "CREATE DATABASE IF NOT EXISTS ${WORKER_DB}"
+ docker run --rm --network "$SNUBA_NETWORK" \
+ -e "CLICKHOUSE_DATABASE=${WORKER_DB}" -e "CLICKHOUSE_HOST=clickhouse" \
+ -e "CLICKHOUSE_PORT=9000" -e "CLICKHOUSE_HTTP_PORT=8123" \
+ -e "DEFAULT_BROKERS=kafka:9093" -e "REDIS_HOST=redis" \
+ -e "REDIS_PORT=6379" -e "REDIS_DB=1" -e "SNUBA_SETTINGS=docker" \
+ "$SNUBA_IMAGE" bootstrap --force 2>&1 | tail -3
+ docker run -d --name "snuba-gw${i}" --network "$SNUBA_NETWORK" \
+ -p "${WORKER_PORT}:1218" \
+ -e "CLICKHOUSE_DATABASE=${WORKER_DB}" -e "CLICKHOUSE_HOST=clickhouse" \
+ -e "CLICKHOUSE_PORT=9000" -e "CLICKHOUSE_HTTP_PORT=8123" \
+ -e "DEFAULT_BROKERS=kafka:9093" -e "REDIS_HOST=redis" \
+ -e "REDIS_PORT=6379" -e "REDIS_DB=1" -e "SNUBA_SETTINGS=docker" \
+ -e "DEBUG=1" "$SNUBA_IMAGE" api
+
+ for attempt in $(seq 1 30); do
+ if curl -sf "http://127.0.0.1:${WORKER_PORT}/health" > /dev/null 2>&1; then
+ echo "snuba-gw${i} healthy on port ${WORKER_PORT}"
+ break
+ fi
+ if [ "$attempt" -eq 30 ]; then
+ echo "ERROR: snuba-gw${i} failed health check after 30 attempts"
+ docker logs "snuba-gw${i}" 2>&1 | tail -20 || true
+ exit 1
+ fi
+ sleep 2
+ done
+ ) &
+ PIDS+=($!)
+ done
+
+ for pid in "${PIDS[@]}"; do
+ wait "$pid" || { echo "ERROR: Snuba bootstrap subshell (PID $pid) failed"; exit 1; }
+ done
+
- name: Download selected tests artifact
if: needs.select-tests.outputs.has-selected-tests == 'true'
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
@@ -332,11 +339,6 @@ jobs:
- name: Inspect failure
if: failure()
run: |
- if [ -f /tmp/ds.log ]; then
- echo "--- devservices startup log ---"
- cat /tmp/ds.log
- fi
-
if command -v devservices; then
devservices logs
fi
@@ -373,22 +375,11 @@ jobs:
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - name: Start devservices early
- uses: ./.github/actions/setup-devservices
- with:
- mode: default
-
- name: Setup sentry env
uses: ./.github/actions/setup-sentry
id: setup
with:
mode: default
- skip-devservices: 'true'
-
- - name: Wait for devservices
- run: |
- sentry init
- ./.github/actions/setup-devservices/wait.sh
- name: run tests
run: |
@@ -422,22 +413,11 @@ jobs:
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - name: Start devservices early
- uses: ./.github/actions/setup-devservices
- with:
- mode: migrations
-
- name: Setup sentry env
uses: ./.github/actions/setup-sentry
id: setup
with:
mode: migrations
- skip-devservices: 'true'
-
- - name: Wait for devservices
- run: |
- sentry init
- ./.github/actions/setup-devservices/wait.sh
- name: Run test
env:
@@ -509,21 +489,10 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - name: Start devservices early
- uses: ./.github/actions/setup-devservices
- with:
- mode: backend-ci
-
- name: Setup sentry env
uses: ./.github/actions/setup-sentry
with:
mode: backend-ci
- skip-devservices: 'true'
-
- - name: Wait for devservices
- run: |
- sentry init
- ./.github/actions/setup-devservices/wait.sh
- name: Sync API Urls to TypeScript
run: |
@@ -546,22 +515,11 @@ jobs:
- name: Checkout sentry
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - name: Start devservices early
- uses: ./.github/actions/setup-devservices
- with:
- mode: migrations
-
- name: Setup sentry env
uses: ./.github/actions/setup-sentry
id: setup
with:
mode: migrations
- skip-devservices: 'true'
-
- - name: Wait for devservices
- run: |
- sentry init
- ./.github/actions/setup-devservices/wait.sh
- name: Migration & lockfile checks
env:
@@ -591,22 +549,11 @@ jobs:
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - name: Start devservices early
- uses: ./.github/actions/setup-devservices
- with:
- mode: migrations
-
- name: Setup sentry env
uses: ./.github/actions/setup-sentry
id: setup
with:
mode: migrations
- skip-devservices: 'true'
-
- - name: Wait for devservices
- run: |
- sentry init
- ./.github/actions/setup-devservices/wait.sh
- name: Run test
run: |
diff --git a/.github/workflows/changelog-preview.yml b/.github/workflows/changelog-preview.yml
new file mode 100644
index 000000000000..63088abe3b5a
--- /dev/null
+++ b/.github/workflows/changelog-preview.yml
@@ -0,0 +1,21 @@
+name: Changelog Preview
+on:
+ pull_request_target:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ - edited
+ - labeled
+ - unlabeled
+permissions:
+ contents: write
+ pull-requests: write
+ statuses: write
+
+jobs:
+ changelog-preview:
+ uses: getsentry/craft/.github/workflows/changelog-preview.yml@f4889d04564e47311038ecb6b910fef6b6cf1363 # v2
+ with:
+ comment: false
+ secrets: inherit
diff --git a/.github/workflows/codeowners-coverage.yml b/.github/workflows/codeowners-coverage.yml
index ed6e31c42858..e66921860362 100644
--- a/.github/workflows/codeowners-coverage.yml
+++ b/.github/workflows/codeowners-coverage.yml
@@ -6,8 +6,7 @@ on:
# push:
# branches:
# - master
- # pull_request:
- workflow_dispatch:
+ pull_request:
# Cancel in progress workflows on pull_requests.
# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
diff --git a/.github/workflows/openapi-diff.yml b/.github/workflows/openapi-diff.yml
index 556d036b53b4..9c510beafba3 100644
--- a/.github/workflows/openapi-diff.yml
+++ b/.github/workflows/openapi-diff.yml
@@ -27,25 +27,12 @@ jobs:
token: ${{ github.token }}
filters: .github/file-filters.yml
- - name: Start devservices early
- uses: ./.github/actions/setup-devservices
- if: steps.changes.outputs.api_docs == 'true'
- with:
- mode: migrations
-
- name: Setup sentry env
uses: ./.github/actions/setup-sentry
with:
mode: migrations
- skip-devservices: 'true'
if: steps.changes.outputs.api_docs == 'true'
- - name: Wait for devservices
- if: steps.changes.outputs.api_docs == 'true'
- run: |
- sentry init
- ./.github/actions/setup-devservices/wait.sh
-
- name: Checkout getsentry/sentry-api-schema
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
if: steps.changes.outputs.api_docs == 'true'
diff --git a/.github/workflows/openapi.yml b/.github/workflows/openapi.yml
index 1ed5a0720e9a..35a7bad47fde 100644
--- a/.github/workflows/openapi.yml
+++ b/.github/workflows/openapi.yml
@@ -32,25 +32,12 @@ jobs:
token: ${{ github.token }}
filters: .github/file-filters.yml
- - name: Start devservices early
- uses: ./.github/actions/setup-devservices
- if: steps.changes.outputs.api_docs == 'true'
- with:
- mode: migrations
-
- name: Setup sentry env
uses: ./.github/actions/setup-sentry
with:
mode: migrations
- skip-devservices: 'true'
if: steps.changes.outputs.api_docs == 'true'
- - name: Wait for devservices
- if: steps.changes.outputs.api_docs == 'true'
- run: |
- sentry init
- ./.github/actions/setup-devservices/wait.sh
-
- name: Checkout getsentry/sentry-api-schema
if: steps.changes.outputs.api_docs == 'true'
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
diff --git a/AGENTS.md b/AGENTS.md
index 017e343af6c1..535b35d1917e 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -130,19 +130,13 @@ make reset-db
#### Development Setup
```bash
-# Start the full development server (requires devservices up)
+# Start the development server
pnpm run dev
# Start only the UI development server with hot reload
-# Proxies API requests to production sentry.io
pnpm run dev-ui
```
-**Dev server URLs:**
-
-- Full devserver: http://dev.getsentry.net:8000
-- Frontend-only (`dev-ui`): https://sentry.dev.getsentry.net:7999/
-
#### Typechecking
Typechecking only works on the entire project. Individual files cannot be checked.
diff --git a/eslint.config.ts b/eslint.config.ts
index e965a5c74563..f2f7497d420f 100644
--- a/eslint.config.ts
+++ b/eslint.config.ts
@@ -405,7 +405,6 @@ export default typescript.config([
'prefer-promise-reject-errors': 'off', // Disabled in favor of @typescript-eslint/prefer-promise-reject-errors
'object-shorthand': ['error', 'properties'],
'prefer-arrow-callback': ['error', {allowNamedFunctions: true}],
- quotes: ['error', 'single', {avoidEscape: true, allowTemplateLiterals: false}],
radix: 'error',
'require-await': 'off', // Disabled in favor of @typescript-eslint/require-await
'spaced-comment': [
diff --git a/jest.config.ts b/jest.config.ts
index 006dc2c36b22..04d12ff49b36 100644
--- a/jest.config.ts
+++ b/jest.config.ts
@@ -273,14 +273,7 @@ if (
* node_modules, but some packages which use ES6 syntax only NEED to be
* transformed.
*/
-const ESM_NODE_MODULES = [
- 'screenfull',
- 'cbor2',
- 'nuqs',
- 'color',
- 'marked',
- '@sentry\\+sqlish',
-];
+const ESM_NODE_MODULES = ['screenfull', 'cbor2', 'nuqs', 'color', 'marked'];
const config: Config.InitialOptions = {
verbose: false,
@@ -308,11 +301,6 @@ const config: Config.InitialOptions = {
'^echarts/(.*)': '
The suggestions will be the values for the provided keys. The following example,
will show suggestions for the id key when the user types a value
- that matches the regex pattern {'/^[0-9]{3}$/'}.
+ that matches the regex pattern {`/^[0-9]{3}$/`}.
The raw search will be replaced with option(s) in the dropdown. The options will
be the values for the provided keys. The following example shows the prop set as{' '}
- {"replaceRawSearchKeys={['span.description']}"}.
+ {`replaceRawSearchKeys={['span.description']}`}.
You can also pass multiple values in the prop to replace multiple keys.{' '}
- {"replaceRawSearchKeys={['span.op', 'span.description']}"}.
+ {`replaceRawSearchKeys={['span.op', 'span.description']}`}.
Input:
-Config:
,
siblingName: getSiblingName(params.platformOptions.siblingOption),
@@ -50,7 +50,7 @@ export const onboarding: OnboardingConfig,
siblingName: getSiblingName(params.platformOptions.siblingOption),
@@ -87,7 +87,7 @@ export const onboarding: OnboardingConfig,
diff --git a/static/app/gettingStartedDocs/dart/logs.tsx b/static/app/gettingStartedDocs/dart/logs.tsx
index 982efc917ba8..6104991f4982 100644
--- a/static/app/gettingStartedDocs/dart/logs.tsx
+++ b/static/app/gettingStartedDocs/dart/logs.tsx
@@ -91,7 +91,7 @@ export const logs: OnboardingConfig = {
{
type: 'code',
language: 'dart',
- code: 'Sentry.logger.fmt.info("Test log from %s", ["Sentry"])',
+ code: `Sentry.logger.fmt.info("Test log from %s", ["Sentry"])`,
},
{
type: 'text',
diff --git a/static/app/gettingStartedDocs/deno/onboarding.tsx b/static/app/gettingStartedDocs/deno/onboarding.tsx
index a6e98a7826fb..f057ca9253c0 100644
--- a/static/app/gettingStartedDocs/deno/onboarding.tsx
+++ b/static/app/gettingStartedDocs/deno/onboarding.tsx
@@ -19,7 +19,7 @@ export const onboarding: OnboardingConfig = {
{
label: 'npm registry',
language: 'javascript',
- code: 'import * as Sentry from "npm:@sentry/deno";',
+ code: `import * as Sentry from "npm:@sentry/deno";`,
},
],
},
diff --git a/static/app/gettingStartedDocs/electron/feedback.tsx b/static/app/gettingStartedDocs/electron/feedback.tsx
index ce1ce2c93fd2..6677566a7430 100644
--- a/static/app/gettingStartedDocs/electron/feedback.tsx
+++ b/static/app/gettingStartedDocs/electron/feedback.tsx
@@ -50,7 +50,7 @@ export const feedback: OnboardingConfig = {
language: 'javascript',
label: 'JavaScript',
code: getFeedbackSDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/electron/renderer";',
+ importStatement: `import * as Sentry from "@sentry/electron/renderer";`,
dsn: params.dsn.public,
feedbackOptions: params.feedbackOptions,
}),
diff --git a/static/app/gettingStartedDocs/electron/onboarding.tsx b/static/app/gettingStartedDocs/electron/onboarding.tsx
index 28f0d07fe74a..708a96ca1917 100644
--- a/static/app/gettingStartedDocs/electron/onboarding.tsx
+++ b/static/app/gettingStartedDocs/electron/onboarding.tsx
@@ -66,12 +66,12 @@ export const onboarding: OnboardingConfig = {
{
type: 'text',
text: t(
- 'One way to verify your setup is by intentionally causing an error that breaks your application.'
+ `One way to verify your setup is by intentionally causing an error that breaks your application.`
),
},
{
type: 'text',
- text: t('Calling an undefined function will throw a JavaScript exception:'),
+ text: t(`Calling an undefined function will throw a JavaScript exception:`),
},
{
type: 'code',
@@ -81,7 +81,7 @@ export const onboarding: OnboardingConfig = {
{
type: 'text',
text: t(
- 'With Electron you can test native crash reporting by triggering a crash:'
+ `With Electron you can test native crash reporting by triggering a crash:`
),
},
{
diff --git a/static/app/gettingStartedDocs/electron/replay.tsx b/static/app/gettingStartedDocs/electron/replay.tsx
index 2e861853efe7..86b5f4347fce 100644
--- a/static/app/gettingStartedDocs/electron/replay.tsx
+++ b/static/app/gettingStartedDocs/electron/replay.tsx
@@ -48,7 +48,7 @@ export const replay: OnboardingConfig = {
language: 'javascript',
label: 'JavaScript',
code: getReplaySDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/electron/renderer";',
+ importStatement: `import * as Sentry from "@sentry/electron/renderer";`,
dsn: params.dsn.public,
mask: params.replayOptions?.mask,
block: params.replayOptions?.block,
diff --git a/static/app/gettingStartedDocs/flutter/logs.tsx b/static/app/gettingStartedDocs/flutter/logs.tsx
index 7807c79c610e..16f133f07250 100644
--- a/static/app/gettingStartedDocs/flutter/logs.tsx
+++ b/static/app/gettingStartedDocs/flutter/logs.tsx
@@ -93,7 +93,7 @@ export const logs: OnboardingConfig = {
{
type: 'code',
language: 'dart',
- code: 'Sentry.logger.fmt.info("Test log from %s", ["Sentry"])',
+ code: `Sentry.logger.fmt.info("Test log from %s", ["Sentry"])`,
},
{
type: 'text',
diff --git a/static/app/gettingStartedDocs/go/logs.tsx b/static/app/gettingStartedDocs/go/logs.tsx
index 660e90f18902..db838b311f7a 100644
--- a/static/app/gettingStartedDocs/go/logs.tsx
+++ b/static/app/gettingStartedDocs/go/logs.tsx
@@ -74,7 +74,7 @@ func main() {
,
}
diff --git a/static/app/gettingStartedDocs/javascript-angular/utils.tsx b/static/app/gettingStartedDocs/javascript-angular/utils.tsx
index 84bdd7f1cfa1..3fad7412e35d 100644
--- a/static/app/gettingStartedDocs/javascript-angular/utils.tsx
+++ b/static/app/gettingStartedDocs/javascript-angular/utils.tsx
@@ -41,11 +41,11 @@ const getIntegrations = (params: Params): string[] => {
const integrations = [];
if (params.isPerformanceSelected) {
- integrations.push('Sentry.browserTracingIntegration()');
+ integrations.push(`Sentry.browserTracingIntegration()`);
}
if (params.isProfilingSelected) {
- integrations.push('Sentry.browserProfilingIntegration()');
+ integrations.push(`Sentry.browserProfilingIntegration()`);
}
if (params.isReplaySelected) {
diff --git a/static/app/gettingStartedDocs/javascript-astro/feedback.tsx b/static/app/gettingStartedDocs/javascript-astro/feedback.tsx
index 5d81183c988c..d1d9a3df08fd 100644
--- a/static/app/gettingStartedDocs/javascript-astro/feedback.tsx
+++ b/static/app/gettingStartedDocs/javascript-astro/feedback.tsx
@@ -47,7 +47,7 @@ export const feedback: OnboardingConfig = {
label: 'JavaScript',
language: 'javascript',
code: getFeedbackSDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/astro";',
+ importStatement: `import * as Sentry from "@sentry/astro";`,
dsn: params.dsn.public,
feedbackOptions: params.feedbackOptions,
}),
diff --git a/static/app/gettingStartedDocs/javascript-astro/replay.tsx b/static/app/gettingStartedDocs/javascript-astro/replay.tsx
index 37414b33f9c0..ee5373f5df51 100644
--- a/static/app/gettingStartedDocs/javascript-astro/replay.tsx
+++ b/static/app/gettingStartedDocs/javascript-astro/replay.tsx
@@ -115,7 +115,7 @@ import * as Sentry from "@sentry/astro";`,
{
type: 'text',
text: tct(
- 'The [code:sentry.client.config.js] file allows you to configure client-side SDK options including replay settings. Learn more about manual SDK initialization [link:here].',
+ `The [code:sentry.client.config.js] file allows you to configure client-side SDK options including replay settings. Learn more about manual SDK initialization [link:here].`,
{
code: ,
link: (
diff --git a/static/app/gettingStartedDocs/javascript-ember/utils.tsx b/static/app/gettingStartedDocs/javascript-ember/utils.tsx
index 1d705170547c..3757460fe6b2 100644
--- a/static/app/gettingStartedDocs/javascript-ember/utils.tsx
+++ b/static/app/gettingStartedDocs/javascript-ember/utils.tsx
@@ -10,7 +10,7 @@ const getIntegrations = (params: DocsParams): string[] => {
const integrations = [];
if (params.isProfilingSelected) {
- integrations.push('Sentry.browserProfilingIntegration()');
+ integrations.push(`Sentry.browserProfilingIntegration()`);
}
if (params.isReplaySelected) {
diff --git a/static/app/gettingStartedDocs/javascript-gatsby/utils.tsx b/static/app/gettingStartedDocs/javascript-gatsby/utils.tsx
index d8426a244ec9..a053cf700c90 100644
--- a/static/app/gettingStartedDocs/javascript-gatsby/utils.tsx
+++ b/static/app/gettingStartedDocs/javascript-gatsby/utils.tsx
@@ -12,11 +12,11 @@ import {tct} from 'sentry/locale';
const getIntegrations = (params: DocsParams): string[] => {
const integrations = [];
if (params.isPerformanceSelected) {
- integrations.push('Sentry.browserTracingIntegration()');
+ integrations.push(`Sentry.browserTracingIntegration()`);
}
if (params.isProfilingSelected) {
- integrations.push('Sentry.browserProfilingIntegration()');
+ integrations.push(`Sentry.browserProfilingIntegration()`);
}
if (params.isReplaySelected) {
diff --git a/static/app/gettingStartedDocs/javascript-nextjs/feedback.tsx b/static/app/gettingStartedDocs/javascript-nextjs/feedback.tsx
index 9b4582f5d053..4e13eb12ae08 100644
--- a/static/app/gettingStartedDocs/javascript-nextjs/feedback.tsx
+++ b/static/app/gettingStartedDocs/javascript-nextjs/feedback.tsx
@@ -59,7 +59,7 @@ export const feedback: OnboardingConfig = {
filename: 'instrumentation-client.(js|ts)',
language: 'javascript',
code: getFeedbackSDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/nextjs";',
+ importStatement: `import * as Sentry from "@sentry/nextjs";`,
dsn: params.dsn.public,
feedbackOptions: params.feedbackOptions,
}),
diff --git a/static/app/gettingStartedDocs/javascript-nextjs/replay.tsx b/static/app/gettingStartedDocs/javascript-nextjs/replay.tsx
index fb1f58b868e2..3226ffcf9140 100644
--- a/static/app/gettingStartedDocs/javascript-nextjs/replay.tsx
+++ b/static/app/gettingStartedDocs/javascript-nextjs/replay.tsx
@@ -41,7 +41,7 @@ export const replay: OnboardingConfig = {
filename: 'instrumentation-client.(js|ts)',
language: 'javascript',
code: getReplaySDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/nextjs";',
+ importStatement: `import * as Sentry from "@sentry/nextjs";`,
dsn: params.dsn.public,
mask: params.replayOptions?.mask,
block: params.replayOptions?.block,
diff --git a/static/app/gettingStartedDocs/javascript-nuxt/feedback.tsx b/static/app/gettingStartedDocs/javascript-nuxt/feedback.tsx
index d11d733de363..6106674dcfdd 100644
--- a/static/app/gettingStartedDocs/javascript-nuxt/feedback.tsx
+++ b/static/app/gettingStartedDocs/javascript-nuxt/feedback.tsx
@@ -50,7 +50,7 @@ export const feedback: OnboardingConfig = {
label: 'JavaScript',
language: 'javascript',
code: getFeedbackSDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/nuxt";',
+ importStatement: `import * as Sentry from "@sentry/nuxt";`,
dsn: params.dsn.public,
feedbackOptions: params.feedbackOptions,
}),
diff --git a/static/app/gettingStartedDocs/javascript-nuxt/replay.tsx b/static/app/gettingStartedDocs/javascript-nuxt/replay.tsx
index 62066ee9e604..d7a934b849f4 100644
--- a/static/app/gettingStartedDocs/javascript-nuxt/replay.tsx
+++ b/static/app/gettingStartedDocs/javascript-nuxt/replay.tsx
@@ -36,7 +36,7 @@ export const replay: OnboardingConfig = {
label: 'JavaScript',
language: 'javascript',
code: getReplaySDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/nuxt";',
+ importStatement: `import * as Sentry from "@sentry/nuxt";`,
dsn: params.dsn.public,
mask: params.replayOptions?.mask,
block: params.replayOptions?.block,
diff --git a/static/app/gettingStartedDocs/javascript-react/utils.tsx b/static/app/gettingStartedDocs/javascript-react/utils.tsx
index 0665ae9ed3d6..20a44d3b6ddc 100644
--- a/static/app/gettingStartedDocs/javascript-react/utils.tsx
+++ b/static/app/gettingStartedDocs/javascript-react/utils.tsx
@@ -43,11 +43,11 @@ const getDynamicParts = (params: DocsParams): string[] => {
const getIntegrations = (params: DocsParams): string[] => {
const integrations = [];
if (params.isPerformanceSelected) {
- integrations.push('Sentry.browserTracingIntegration()');
+ integrations.push(`Sentry.browserTracingIntegration()`);
}
if (params.isProfilingSelected) {
- integrations.push('Sentry.browserProfilingIntegration()');
+ integrations.push(`Sentry.browserProfilingIntegration()`);
}
if (params.isReplaySelected) {
diff --git a/static/app/gettingStartedDocs/javascript-remix/feedback.tsx b/static/app/gettingStartedDocs/javascript-remix/feedback.tsx
index 9a3c55388f9f..73ae84837311 100644
--- a/static/app/gettingStartedDocs/javascript-remix/feedback.tsx
+++ b/static/app/gettingStartedDocs/javascript-remix/feedback.tsx
@@ -51,7 +51,7 @@ export const feedback: OnboardingConfig = {
filename: 'entry.client.tsx',
language: 'javascript',
code: getFeedbackSDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/remix";',
+ importStatement: `import * as Sentry from "@sentry/remix";`,
dsn: params.dsn.public,
feedbackOptions: params.feedbackOptions,
}),
diff --git a/static/app/gettingStartedDocs/javascript-remix/replay.tsx b/static/app/gettingStartedDocs/javascript-remix/replay.tsx
index d4f01179d8ff..cd5b5e79aa0a 100644
--- a/static/app/gettingStartedDocs/javascript-remix/replay.tsx
+++ b/static/app/gettingStartedDocs/javascript-remix/replay.tsx
@@ -38,7 +38,7 @@ export const replay: OnboardingConfig = {
filename: 'entry.client.tsx',
language: 'javascript',
code: getReplaySDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/remix";',
+ importStatement: `import * as Sentry from "@sentry/remix";`,
dsn: params.dsn.public,
mask: params.replayOptions?.mask,
block: params.replayOptions?.block,
diff --git a/static/app/gettingStartedDocs/javascript-solid/utils.tsx b/static/app/gettingStartedDocs/javascript-solid/utils.tsx
index 9677c21d7f2a..5f66f23f73c9 100644
--- a/static/app/gettingStartedDocs/javascript-solid/utils.tsx
+++ b/static/app/gettingStartedDocs/javascript-solid/utils.tsx
@@ -9,11 +9,11 @@ import {getReplayConfigOptions} from 'sentry/components/onboarding/gettingStarte
const getIntegrations = (params: DocsParams): string[] => {
const integrations = [];
if (params.isPerformanceSelected) {
- integrations.push('Sentry.browserTracingIntegration()');
+ integrations.push(`Sentry.browserTracingIntegration()`);
}
if (params.isProfilingSelected) {
- integrations.push('Sentry.browserProfilingIntegration()');
+ integrations.push(`Sentry.browserProfilingIntegration()`);
}
if (params.isReplaySelected) {
diff --git a/static/app/gettingStartedDocs/javascript-solidstart/utils.tsx b/static/app/gettingStartedDocs/javascript-solidstart/utils.tsx
index 36822ac1ca2b..a651614074f6 100644
--- a/static/app/gettingStartedDocs/javascript-solidstart/utils.tsx
+++ b/static/app/gettingStartedDocs/javascript-solidstart/utils.tsx
@@ -9,11 +9,11 @@ import {getReplayConfigOptions} from 'sentry/components/onboarding/gettingStarte
const getIntegrations = (params: DocsParams): string[] => {
const integrations = [];
if (params.isPerformanceSelected) {
- integrations.push('solidRouterBrowserTracingIntegration()');
+ integrations.push(`solidRouterBrowserTracingIntegration()`);
}
if (params.isProfilingSelected) {
- integrations.push('Sentry.browserProfilingIntegration()');
+ integrations.push(`Sentry.browserProfilingIntegration()`);
}
if (params.isReplaySelected) {
@@ -106,7 +106,7 @@ export const installSnippetBlock: ContentBlock = {
{
label: 'pnpm',
language: 'bash',
- code: 'pnpm add @sentry/solidstart',
+ code: `pnpm add @sentry/solidstart`,
},
],
};
diff --git a/static/app/gettingStartedDocs/javascript-svelte/onboarding.tsx b/static/app/gettingStartedDocs/javascript-svelte/onboarding.tsx
index 7f394cd9d7ab..c8a8cceff898 100644
--- a/static/app/gettingStartedDocs/javascript-svelte/onboarding.tsx
+++ b/static/app/gettingStartedDocs/javascript-svelte/onboarding.tsx
@@ -26,7 +26,7 @@ const getVerifySnippet = (params: DocsParams, isVersion5: boolean) => {
const errorCode =
logsCode || metricsCode
? `${logsCode}${metricsCode} throw new Error("This is your first error!");`
- : 'throw new Error("This is your first error!")';
+ : `throw new Error("This is your first error!")`;
return isVersion5
? `
diff --git a/static/app/gettingStartedDocs/javascript-svelte/utils.tsx b/static/app/gettingStartedDocs/javascript-svelte/utils.tsx
index 752d24aa979f..ae664b7b1fa0 100644
--- a/static/app/gettingStartedDocs/javascript-svelte/utils.tsx
+++ b/static/app/gettingStartedDocs/javascript-svelte/utils.tsx
@@ -9,11 +9,11 @@ import {getReplayConfigOptions} from 'sentry/components/onboarding/gettingStarte
const getIntegrations = (params: DocsParams): string[] => {
const integrations = [];
if (params.isPerformanceSelected) {
- integrations.push('Sentry.browserTracingIntegration()');
+ integrations.push(`Sentry.browserTracingIntegration()`);
}
if (params.isProfilingSelected) {
- integrations.push('Sentry.browserProfilingIntegration()');
+ integrations.push(`Sentry.browserProfilingIntegration()`);
}
if (params.isReplaySelected) {
diff --git a/static/app/gettingStartedDocs/javascript-sveltekit/feedback.tsx b/static/app/gettingStartedDocs/javascript-sveltekit/feedback.tsx
index 457019abf8a0..211705944a51 100644
--- a/static/app/gettingStartedDocs/javascript-sveltekit/feedback.tsx
+++ b/static/app/gettingStartedDocs/javascript-sveltekit/feedback.tsx
@@ -50,7 +50,7 @@ export const feedback: OnboardingConfig = {
label: 'JavaScript',
language: 'javascript',
code: getFeedbackSDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/sveltekit";',
+ importStatement: `import * as Sentry from "@sentry/sveltekit";`,
dsn: params.dsn.public,
feedbackOptions: params.feedbackOptions,
}),
diff --git a/static/app/gettingStartedDocs/javascript-sveltekit/replay.tsx b/static/app/gettingStartedDocs/javascript-sveltekit/replay.tsx
index 49eb82d9c4d5..b92b7356d4e4 100644
--- a/static/app/gettingStartedDocs/javascript-sveltekit/replay.tsx
+++ b/static/app/gettingStartedDocs/javascript-sveltekit/replay.tsx
@@ -33,7 +33,7 @@ export const replay: OnboardingConfig = {
label: 'JavaScript',
language: 'javascript',
code: getReplaySDKSetupSnippet({
- importStatement: 'import * as Sentry from "@sentry/sveltekit";',
+ importStatement: `import * as Sentry from "@sentry/sveltekit";`,
dsn: params.dsn.public,
mask: params.replayOptions?.mask,
block: params.replayOptions?.block,
diff --git a/static/app/gettingStartedDocs/javascript-vue/feedback.tsx b/static/app/gettingStartedDocs/javascript-vue/feedback.tsx
index 0e569a08b39f..eca28535c84c 100644
--- a/static/app/gettingStartedDocs/javascript-vue/feedback.tsx
+++ b/static/app/gettingStartedDocs/javascript-vue/feedback.tsx
@@ -47,7 +47,7 @@ export const feedback: OnboardingConfig{integrationName},
}),
diff --git a/static/app/gettingStartedDocs/python/utils.spec.tsx b/static/app/gettingStartedDocs/python/utils.spec.tsx
index a280fea4a05f..ed823304a2ec 100644
--- a/static/app/gettingStartedDocs/python/utils.spec.tsx
+++ b/static/app/gettingStartedDocs/python/utils.spec.tsx
@@ -10,17 +10,17 @@ describe('getPythonInstallCodeBlock', () => {
{
label: 'pip',
language: 'bash',
- code: 'pip install "sentry-sdk"',
+ code: `pip install "sentry-sdk"`,
},
{
label: 'uv',
language: 'bash',
- code: 'uv add "sentry-sdk"',
+ code: `uv add "sentry-sdk"`,
},
{
label: 'poetry',
language: 'bash',
- code: 'poetry add "sentry-sdk"',
+ code: `poetry add "sentry-sdk"`,
},
],
});
@@ -37,17 +37,17 @@ describe('getPythonInstallCodeBlock', () => {
{
label: 'pip',
language: 'bash',
- code: 'pip install --upgrade "sentry-sdk[with-extras]>=2.3.4"',
+ code: `pip install --upgrade "sentry-sdk[with-extras]>=2.3.4"`,
},
{
label: 'uv',
language: 'bash',
- code: 'uv add --upgrade "sentry-sdk[with-extras]>=2.3.4"',
+ code: `uv add --upgrade "sentry-sdk[with-extras]>=2.3.4"`,
},
{
label: 'poetry',
language: 'bash',
- code: 'poetry add "sentry-sdk[with-extras]>=2.3.4"',
+ code: `poetry add "sentry-sdk[with-extras]>=2.3.4"`,
},
],
});
@@ -63,17 +63,17 @@ describe('getPythonInstallCodeBlock', () => {
{
label: 'pip',
language: 'bash',
- code: 'pip install "sentry-sdk" "falcon"',
+ code: `pip install "sentry-sdk" "falcon"`,
},
{
label: 'uv',
language: 'bash',
- code: 'uv add "sentry-sdk" "falcon"',
+ code: `uv add "sentry-sdk" "falcon"`,
},
{
label: 'poetry',
language: 'bash',
- code: 'poetry add "sentry-sdk" "falcon"',
+ code: `poetry add "sentry-sdk" "falcon"`,
},
],
});
diff --git a/static/app/gettingStartedDocs/react-native/utils.tsx b/static/app/gettingStartedDocs/react-native/utils.tsx
index a65a3e8b86a8..c53110d95f4a 100644
--- a/static/app/gettingStartedDocs/react-native/utils.tsx
+++ b/static/app/gettingStartedDocs/react-native/utils.tsx
@@ -10,17 +10,17 @@ export const installCodeBlock: ContentBlock = {
{
label: 'npm',
language: 'bash',
- code: 'npm install @sentry/react-native@latest',
+ code: `npm install @sentry/react-native@latest`,
},
{
label: 'yarn',
language: 'bash',
- code: 'yarn add @sentry/react-native@latest',
+ code: `yarn add @sentry/react-native@latest`,
},
{
label: 'pnpm',
language: 'bash',
- code: 'pnpm add @sentry/react-native@latest',
+ code: `pnpm add @sentry/react-native@latest`,
},
],
};
diff --git a/static/app/gettingStartedDocs/ruby/logs.tsx b/static/app/gettingStartedDocs/ruby/logs.tsx
index 58d3e0e66864..4c17a8f3c8d9 100644
--- a/static/app/gettingStartedDocs/ruby/logs.tsx
+++ b/static/app/gettingStartedDocs/ruby/logs.tsx
@@ -93,7 +93,7 @@ logger.info("Sentry test log from stdlib logger")`,
{
type: 'code',
language: 'ruby',
- code: 'Sentry.logger.info("Test log from %{test_source}", test_source: "Sentry")',
+ code: `Sentry.logger.info("Test log from %{test_source}", test_source: "Sentry")`,
},
],
},
diff --git a/static/app/gettingStartedDocs/unreal/onboarding.tsx b/static/app/gettingStartedDocs/unreal/onboarding.tsx
index aaee846517e7..5d4fc4aa6ab3 100644
--- a/static/app/gettingStartedDocs/unreal/onboarding.tsx
+++ b/static/app/gettingStartedDocs/unreal/onboarding.tsx
@@ -301,7 +301,7 @@ export const onboarding: OnboardingConfig = {
{
type: 'code',
language: 'bash',
- code: 'export SENTRY_UPLOAD_SYMBOLS_AUTOMATICALLY=True',
+ code: `export SENTRY_UPLOAD_SYMBOLS_AUTOMATICALLY=True`,
},
{
type: 'text',
diff --git a/static/app/icons/icons.stories.tsx b/static/app/icons/icons.stories.tsx
index 7f2f91f064a6..10c448c6a4ab 100644
--- a/static/app/icons/icons.stories.tsx
+++ b/static/app/icons/icons.stories.tsx
@@ -1866,7 +1866,7 @@ function IconCard(props: IconCardProps) {
};
snippets.all = `${snippets.import}\n\n${snippets.element}`;
const labels = {
- import: 'import statement',
+ import: `import statement`,
element: props.icon.id,
};
const action: keyof typeof snippets = shift ? 'import' : 'element';
diff --git a/static/app/router/routes.tsx b/static/app/router/routes.tsx
index 24374f502616..5235efa27238 100644
--- a/static/app/router/routes.tsx
+++ b/static/app/router/routes.tsx
@@ -2030,7 +2030,7 @@ function buildRoutes(): RouteObject[] {
},
// Redirect old links to the new mcp landing page
{
- path: 'ai/mcp/',
+ path: `ai/mcp/`,
redirectTo: `/${DOMAIN_VIEW_BASE_URL}/${MCP_LANDING_SUB_PATH}/`,
},
{
@@ -2093,7 +2093,7 @@ function buildRoutes(): RouteObject[] {
},
// Redirect old links to the new agents landing page
{
- path: 'ai/*',
+ path: `ai/*`,
redirectTo: `/${DOMAIN_VIEW_BASE_URL}/${AGENTS_LANDING_SUB_PATH}/`,
},
{
diff --git a/static/app/stories/jsx.tsx b/static/app/stories/jsx.tsx
index b88708d9f7c5..a677c44ca6ee 100644
--- a/static/app/stories/jsx.tsx
+++ b/static/app/stories/jsx.tsx
@@ -19,7 +19,7 @@ export function JSXNode({name, props = {}, children}: JSXNodeProps) {
{'{children}'};
+ return {`{children}`};
}
if (value === null || value === undefined) {
return {`${name}={${value}}`};
diff --git a/static/app/stories/view/storySearch.tsx b/static/app/stories/view/storySearch.tsx
index 0028b23058b4..78a3f9b2827a 100644
--- a/static/app/stories/view/storySearch.tsx
+++ b/static/app/stories/view/storySearch.tsx
@@ -9,7 +9,6 @@ import type {CollectionChildren} from '@react-types/shared';
import {ListBox} from '@sentry/scraps/compactSelect';
import {useHotkeys, Hotkey} from '@sentry/scraps/hotkey';
import {InputGroup} from '@sentry/scraps/input';
-import {Flex} from '@sentry/scraps/layout';
import {Text} from '@sentry/scraps/text';
import {Overlay} from 'sentry/components/overlay';
@@ -210,7 +209,7 @@ function SearchComboBox(props: SearchComboBoxProps) {
onInputChange: setInputValue,
defaultFilter: filter,
shouldCloseOnBlur: true,
- allowsEmptyCollection: true,
+ allowsEmptyCollection: false,
onSelectionChange: handleSelectionChange,
});
@@ -234,51 +233,23 @@ function SearchComboBox(props: SearchComboBoxProps) {
, Q = any export interface RouteContextInterface
, Q = any> {
location: Location ;
routes: PlainRoute[];
diff --git a/static/app/utils/api/useAggregatedQueryKeys.spec.tsx b/static/app/utils/api/useAggregatedQueryKeys.spec.tsx
index 73af0cbfa780..54dbfacca448 100644
--- a/static/app/utils/api/useAggregatedQueryKeys.spec.tsx
+++ b/static/app/utils/api/useAggregatedQueryKeys.spec.tsx
@@ -40,7 +40,7 @@ describe('useAggregatedQueryKeys', () => {
it('should convert multiple buffer calls into one fetch request after a timeout', async () => {
const mockRequest = MockApiClient.addMockResponse({
- url: '/api-tokens/',
+ url: `/api-tokens/`,
body: {
'1111': 5,
'2222': 7,
@@ -61,7 +61,7 @@ describe('useAggregatedQueryKeys', () => {
});
expect(mockRequest).toHaveBeenCalledWith(
- '/api-tokens/',
+ `/api-tokens/`,
expect.objectContaining({
query: expect.arrayContaining(['1111', '2222', '3333']),
})
@@ -70,7 +70,7 @@ describe('useAggregatedQueryKeys', () => {
it('should send a fetch request immediatly if the buffer is full', async () => {
const mockRequest = MockApiClient.addMockResponse({
- url: '/api-tokens/',
+ url: `/api-tokens/`,
});
const {result} = renderHook(useAggregatedQueryKeys, {
@@ -92,7 +92,7 @@ describe('useAggregatedQueryKeys', () => {
it('should return cached data right away, if it exists in the cache', async () => {
const queryClient = makeTestQueryClient();
MockApiClient.addMockResponse({
- url: '/api-tokens/',
+ url: `/api-tokens/`,
body: {
'1111': 5,
'2222': 7,
@@ -139,7 +139,7 @@ describe('useAggregatedQueryKeys', () => {
'1111': 5,
};
MockApiClient.addMockResponse({
- url: '/api-tokens/',
+ url: `/api-tokens/`,
body: mockResponse,
});
@@ -165,7 +165,7 @@ describe('useAggregatedQueryKeys', () => {
it('should separate callsites that have different cacheKeys', async () => {
const wrapper = makeWrapper(makeTestQueryClient());
const mockRequest = MockApiClient.addMockResponse({
- url: '/api-tokens/',
+ url: `/api-tokens/`,
});
const responseReducer1 = jest.fn((prevState: any, response: ApiResult) => {
return {
diff --git a/static/app/utils/demoMode/guides.tsx b/static/app/utils/demoMode/guides.tsx
index 1b537066c87d..8cd5ea128373 100644
--- a/static/app/utils/demoMode/guides.tsx
+++ b/static/app/utils/demoMode/guides.tsx
@@ -79,35 +79,35 @@ export function getDemoModeGuides(): GuidesContent {
title: t('Projects'),
target: 'projects',
description: t(
- 'Create a project for any type of application you want to monitor.'
+ `Create a project for any type of application you want to monitor.`
),
},
{
title: t('Issues'),
target: 'issues',
description: t(
- "Here's a list of what's broken and slow. Sentry automatically groups similar events together into an issue."
+ `Here's a list of what's broken and slow. Sentry automatically groups similar events together into an issue.`
),
},
{
title: t('Performance'),
target: 'performance',
description: t(
- 'Keep a pulse on crash rates, throughput, and latency issues across projects.'
+ `Keep a pulse on crash rates, throughput, and latency issues across projects.`
),
},
{
title: t('Releases'),
target: 'releases',
description: t(
- 'Track the health of every release, see differences between releases from crash analytics to adoption rates.'
+ `Track the health of every release, see differences between releases from crash analytics to adoption rates.`
),
},
{
title: t('Discover'),
target: 'discover',
description: t(
- 'Query and unlock insights into the health of your entire system and get answers to critical business questions all in one place.'
+ `Query and unlock insights into the health of your entire system and get answers to critical business questions all in one place.`
),
nextText: t('Got it'),
},
diff --git a/static/app/utils/discover/eventView.tsx b/static/app/utils/discover/eventView.tsx
index f6f83cb65252..d7855bfc1660 100644
--- a/static/app/utils/discover/eventView.tsx
+++ b/static/app/utils/discover/eventView.tsx
@@ -1275,7 +1275,7 @@ export class EventView {
return {
pathname: makeDiscoverPathname({
- path: '/results/',
+ path: `/results/`,
organization,
}),
query: cloneDeep(output),
diff --git a/static/app/utils/discover/fieldRenderers.spec.tsx b/static/app/utils/discover/fieldRenderers.spec.tsx
index 6df41b1a9c03..ab1f7b057aed 100644
--- a/static/app/utils/discover/fieldRenderers.spec.tsx
+++ b/static/app/utils/discover/fieldRenderers.spec.tsx
@@ -420,7 +420,7 @@ describe('getFieldRenderer', () => {
expect(screen.queryByRole('link')).toHaveAttribute(
'href',
- '/organizations/org-slug/issues/123214/'
+ `/organizations/org-slug/issues/123214/`
);
expect(screen.getByText('SENTRY-T6P')).toBeInTheDocument();
});
diff --git a/static/app/utils/discover/fieldRenderers.tsx b/static/app/utils/discover/fieldRenderers.tsx
index a4d71dc670ea..3631fd0f3115 100644
--- a/static/app/utils/discover/fieldRenderers.tsx
+++ b/static/app/utils/discover/fieldRenderers.tsx
@@ -1193,7 +1193,7 @@ const SPECIAL_FUNCTIONS: SpecialFunctions = {
foo Reading Reading Markdown lists are supported: Markdown code blocks are supported: Test Test Headings & emphasis Links & lists;
- matches: Array
\n'
+ `<div>Hello</div>\n
\n`
);
expect(sanitizedMarked('```tsx\n<div>Hello</div>\n
\n'
+ `<div>Hello</div>\n
\n`
);
expect(sanitizedMarked('```jsx\n<div>Hello</div>\n
\n'
+ `<Component>Hello</Component>\n
\n`
);
});
@@ -111,9 +111,9 @@ describe('marked', () => {
it('single line renderer should not render paragraphs', () => {
expect(singleLineRenderer('foo')).toBe('foo');
expect(sanitizedMarked('foo')).toBe('<Component>Hello</Component>\nfile.py');
+ expect(singleLineRenderer('Reading `file.py`')).toBe(`Reading file.py`);
expect(sanitizedMarked('Reading `file.py`')).toBe(
- 'file.pyfile.py
\n'
+ `const x = 1;\n
\n`
);
});
@@ -232,14 +232,14 @@ describe('marked', () => {
it('renders syntax highlighting via asyncSanitizedMarked', async () => {
const markdown = '```javascript\nconst x = 1;\n```';
expect(await asyncSanitizedMarked(markdown)).toBe(
- 'const x = 1;\n
'
+ `const x = 1;\n
`
);
});
it('renders unknown language code blocks as plaintext via asyncSanitizedMarked', async () => {
const markdown = '```unknown\nconst x = 1;\n```';
expect(await asyncSanitizedMarked(markdown)).toBe(
- 'const x = 1;\n
'
+ `const x = 1;\n
`
);
});
});
diff --git a/static/app/utils/marked/markedText.stories.tsx b/static/app/utils/marked/markedText.stories.tsx
index d14497ad29ff..de768182bb79 100644
--- a/static/app/utils/marked/markedText.stories.tsx
+++ b/static/app/utils/marked/markedText.stories.tsx
@@ -64,7 +64,7 @@ export default Storybook.story('MarkedText', (story, _APIReference) => {
const x = 1;\n