Skip to content

Commit 0d3ffce

Browse files
committed
Merge branch 'master' into txiao/feat/update-seer-explorer-autofix-last-triggered-on-completion
2 parents 383d741 + 956874e commit 0d3ffce

91 files changed

Lines changed: 2005 additions & 954 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.

.agents/skills/cell-architecture/SKILL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ This applies to:
199199
- DB columns: new columns must be nullable or have defaults; don't drop a column in the same deploy that stops writing it; when **renaming a Python field**, set `db_column="old_name"` to avoid a schema migration entirely — the DB column stays unchanged and is safe across rolling deploys
200200
- API response shape changes
201201
- Any data written to outboxes, queues, or caches that may be read by older code
202+
- **Taskworker/Celery task names and kwargs** — the `name=` string is serialized into Kafka/the broker; in-flight tasks carry the old name and old kwarg names. Keep the old name registered during the transition (via `alias=` on `@instrumented_task`, or by keeping the old task as a shim that calls the new function directly). Remove it in a follow-up deploy once the queue has drained.
202203

203204
#### region -> cell Rename
204205

.agents/skills/hybrid-cloud-outboxes/references/signal-receivers.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,13 @@ def process_my_category(object_identifier: int, payload: Any, **kwds: Any) -> No
7979

8080
## Control Outbox Receivers
8181

82-
Control outbox signals include an additional `region_name` argument:
82+
Control outbox signals include an additional `cell_name` argument:
8383

8484
- `sender`: `OutboxCategory` enum value
8585
- `payload`: `dict | None`
8686
- `object_identifier`: `int`
8787
- `shard_identifier`: `int`
88-
- `region_name`: `str` — the target region
88+
- `cell_name`: `str` — the target cell
8989
- `shard_scope`: `int`
9090
- `date_added`: `datetime`
9191
- `scheduled_for`: `datetime`
@@ -100,13 +100,13 @@ from sentry.receivers.outbox import maybe_process_tombstone
100100

101101

102102
@receiver(process_control_outbox, sender=OutboxCategory.MY_CATEGORY)
103-
def process_my_category(object_identifier: int, region_name: str, **kwds: Any) -> None:
103+
def process_my_category(object_identifier: int, cell_name: str, **kwds: Any) -> None:
104104
if (instance := maybe_process_tombstone(
105-
MyModel, object_identifier, cell_name=region_name
105+
MyModel, object_identifier, cell_name=cell_name
106106
)) is None:
107107
return
108108
# Replicate to the specific cell
109-
my_cell_service.sync(cell_name=region_name, data=serialize(instance))
109+
my_cell_service.sync(cell_name=cell_name, data=serialize(instance))
110110
```
111111

112112
### Template: Control Pure-RPC Receiver
@@ -118,7 +118,7 @@ For categories where the receiver makes an RPC call without looking up a model:
118118
def process_my_category(
119119
payload: Mapping[str, Any], shard_identifier: int, **kwds: Any
120120
) -> None:
121-
my_region_service.do_something(
121+
my_cell_service.do_something(
122122
organization_id=shard_identifier,
123123
data=payload["data"],
124124
)
@@ -144,4 +144,4 @@ The tombstone system drives `HybridCloudForeignKey` cascade deletes across silos
144144

145145
**When to use**: Any receiver that needs to distinguish between "object was created/updated" and "object was deleted". Not needed for payload-only categories (audit logs, IP events) where the payload carries all necessary data.
146146

147-
**`region_name` parameter**: Pass `region_name` for control outbox receivers (tombstone goes to the cell). Omit for cell outbox receivers (tombstone goes to control).
147+
**`cell_name` parameter**: Pass `cell_name` for control outbox receivers (tombstone goes to the cell). Omit for cell outbox receivers (tombstone goes to control).

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,7 @@ tests/sentry/api/endpoints/test_organization_attribute_mappings.py @get
779779

780780
# Span buffer + process-segments are co-owned by streaming platform and vis for now.
781781
/src/sentry/spans/ @getsentry/data-browsing @getsentry/streaming-platform
782+
/src/sentry/scripts/spans/ @getsentry/data-browsing @getsentry/streaming-platform
782783
/tests/sentry/spans/ @getsentry/data-browsing @getsentry/streaming-platform
783784

784785
# Streaming platform
@@ -805,6 +806,7 @@ tests/sentry/api/endpoints/test_organization_attribute_mappings.py @get
805806
/static/app/utils/theme/ @getsentry/design-engineering
806807
/static/app/components/commandPalette/ @getsentry/design-engineering
807808
/static/app/components/core/ @getsentry/design-engineering
809+
/static/app/components/dnd/ @getsentry/design-engineering
808810
/static/app/components/pageFilters/ @getsentry/design-engineering
809811
/static/app/icons/ @getsentry/design-engineering
810812
/static/app/stories/ @getsentry/design-engineering

.github/codeowners-coverage-baseline.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ static/app/components/discover/transactionsList.spec.tsx
552552
static/app/components/discover/transactionsList.tsx
553553
static/app/components/discover/transactionsTable.tsx
554554
static/app/components/discoverButton.tsx
555+
static/app/components/dnd/dragReorderButton.tsx
555556
static/app/components/dropdownButton.tsx
556557
static/app/components/dropdownMenu/footer.tsx
557558
static/app/components/dropdownMenu/index.spec.tsx

.github/workflows/frontend-optional.yml

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@ jobs:
2525
testable_rules_changed: ${{ steps.changes.outputs.testable_rules_changed }}
2626
typecheckable_rules_changed: ${{ steps.changes.outputs.typecheckable_rules_changed }}
2727
frontend_all: ${{ steps.changes.outputs.frontend_all }}
28+
merge_base: ${{ steps.merge_base.outputs.merge_base }}
2829
steps:
2930
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
31+
with:
32+
fetch-depth: 100
3033

3134
- name: Check for frontend file changes
3235
uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
@@ -36,6 +39,88 @@ jobs:
3639
filters: .github/file-filters.yml
3740
list-files: shell
3841

42+
# On PRs, HEAD is the merge commit; its parents (HEAD^1, HEAD^2) are base and head.
43+
# Merge base of those two is what Jest --changedSince needs.
44+
# If merge base can't be computed or non-frontend files changed, output is empty
45+
# and the optional Jest job will be skipped entirely.
46+
- name: Get merge base for changedSince
47+
id: merge_base
48+
run: |
49+
MERGE_BASE=$(git merge-base HEAD^1 HEAD^2 2>/dev/null) || true
50+
if [ -n "$MERGE_BASE" ]; then
51+
CHANGED=$(git diff --name-only "$MERGE_BASE" HEAD^2)
52+
if echo "$CHANGED" | grep -qvE '^static/'; then
53+
echo "Non-frontend file changed — skipping optional Jest"
54+
MERGE_BASE=""
55+
else
56+
echo "Merge base: $MERGE_BASE (Jest will use --changedSince)"
57+
fi
58+
else
59+
echo "Could not compute merge base — skipping optional Jest"
60+
fi
61+
echo "merge_base=${MERGE_BASE:-}" >> "$GITHUB_OUTPUT"
62+
63+
# This job intentionally mirrors `frontend-jest-tests` in frontend.yml.
64+
# Our intent is to try it out for a few weeks and see if it's stable.
65+
frontend-jest-tests-changed-only:
66+
if: >-
67+
needs.files-changed.outputs.merge_base != '' &&
68+
(needs.files-changed.outputs.testable_rules_changed == 'true' || needs.files-changed.outputs.testable_modified == 'true')
69+
needs: [files-changed]
70+
name: Jest
71+
# If you change the runs-on image, you must also change the runner in jest-balance.yml
72+
# so that the balancer runs in the same environment as the tests.
73+
runs-on: ubuntu-24.04
74+
timeout-minutes: 30
75+
strategy:
76+
# This helps not having to run multiple jobs because one fails, thus, reducing resource usage
77+
# and reducing the risk that one of many runs would turn red again (read: intermittent tests)
78+
fail-fast: false
79+
matrix:
80+
# XXX: When updating this, make sure you also update CI_NODE_TOTAL.
81+
instance: [0, 1, 2, 3]
82+
83+
steps:
84+
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
85+
name: Checkout sentry
86+
with:
87+
# PRs need history so we can compute merge base for Jest --changedSince.
88+
# 100 is an arbitrary depth that will get most reasonable PRs' commits.
89+
fetch-depth: ${{ github.event_name == 'pull_request' && '100' || '1' }}
90+
91+
- uses: ./.github/actions/setup-node-pnpm
92+
93+
- name: Download jest-balance.json
94+
id: download-artifact
95+
uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11
96+
with:
97+
workflow: 38531594 # jest-balancer.yml
98+
workflow_conclusion: success # The conclusion of the workflow we're looking for
99+
branch: master # The branch we're looking for
100+
name: jest-balance.json # Artifact name
101+
name_is_regexp: false
102+
path: tests/js/test-balancer/ # Directory where to extract artifact(s), defaults to the current directory
103+
search_artifacts: true # Search for the last workflow run whose stored the artifact we're looking for
104+
if_no_artifact_found: warn # Can be one of: "fail", "warn", "ignore"
105+
106+
- name: jest
107+
env:
108+
GITHUB_PR_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
109+
GITHUB_PR_REF: ${{ github.event.pull_request.head.ref || github.ref }}
110+
# XXX: CI_NODE_TOTAL must be hardcoded to the length of strategy.matrix.instance.
111+
# Otherwise, if there are other things in the matrix, using strategy.job-total
112+
# wouldn't be correct.
113+
CI_NODE_TOTAL: 4
114+
CI_NODE_INDEX: ${{ matrix.instance }}
115+
# Disable testing-library from printing out any of of the DOM to
116+
# stdout. No one actually looks through this in CI, they're just
117+
# going to run it locally.
118+
#
119+
# This quiets up the logs quite a bit.
120+
DEBUG_PRINT_LIMIT: 0
121+
MERGE_BASE: ${{ needs.files-changed.outputs.merge_base }}
122+
run: pnpm run test-ci --forceExit
123+
39124
typescript-native:
40125
if: needs.files-changed.outputs.frontend_all == 'true'
41126
needs: files-changed

eslint.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,7 @@ export default typescript.config([
928928
name: 'files/jest related',
929929
files: [
930930
'tests/js/jest-pegjs-transform.js',
931+
'tests/js/sentry-test/jest-environment.js',
931932
'tests/js/sentry-test/mocks/*',
932933
'tests/js/sentry-test/loadFixtures.ts',
933934
'tests/js/setup.ts',

jest.config.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,18 @@ let JEST_TESTS: string[] | undefined;
6969
// to reexec itself here
7070
if (CI && !process.env.JEST_LIST_TESTS_INNER) {
7171
try {
72-
const stdout = execFileSync('pnpm', ['exec', 'jest', '--listTests', '--json'], {
72+
const listTestArguments = ['exec', 'jest', '--listTests', '--json'];
73+
74+
if (process.env.MERGE_BASE) {
75+
console.log('MERGE_BASE detected:', process.env.MERGE_BASE);
76+
listTestArguments.push(
77+
'--changedSince',
78+
process.env.MERGE_BASE,
79+
'--passWithNoTests'
80+
);
81+
}
82+
83+
const stdout = execFileSync('pnpm', listTestArguments, {
7384
stdio: 'pipe',
7485
encoding: 'utf-8',
7586
env: {...process.env, JEST_LIST_TESTS_INNER: '1'},
@@ -108,6 +119,10 @@ function getTestsForGroup(
108119
allTests: ReadonlyArray<string>,
109120
testStats: Record<string, number>
110121
): string[] {
122+
if (allTests.length === 0) {
123+
return [];
124+
}
125+
111126
const speculatedSuiteDuration = Object.values(testStats).reduce((a, b) => a + b, 0);
112127
const targetDuration = speculatedSuiteDuration / nodeTotal;
113128

@@ -122,8 +137,13 @@ function getTestsForGroup(
122137
const tests = new Map<string, number>();
123138
const SUITE_P50_DURATION_MS = 1500;
124139

140+
const allTestsSet = new Set(allTests);
141+
125142
// First, iterate over all of the tests we have stats for.
126143
Object.entries(testStats).forEach(([test, duration]) => {
144+
if (!allTestsSet.has(test)) {
145+
return;
146+
}
127147
if (duration <= 0) {
128148
throw new Error(`Test duration is <= 0 for ${test}`);
129149
}
@@ -199,8 +219,8 @@ function getTestsForGroup(
199219
}
200220
}
201221

202-
if (!groups[nodeIndex]) {
203-
throw new Error(`No tests found for node ${nodeIndex}`);
222+
if (!groups[nodeIndex]?.length) {
223+
return ['<rootDir>/__no_tests_for_this_shard__'];
204224
}
205225
return groups[nodeIndex].map(test => `<rootDir>/${test}`);
206226
}
@@ -285,6 +305,7 @@ const config: Config.InitialOptions = {
285305
// window/cookies state.
286306
'@sentry/toolbar': '<rootDir>/tests/js/sentry-test/mocks/sentryToolbarMock.js',
287307
},
308+
passWithNoTests: !!process.env.MERGE_BASE,
288309
setupFiles: [
289310
'<rootDir>/static/app/utils/silence-react-unsafe-warnings.ts',
290311
'jest-canvas-mock',
@@ -333,8 +354,7 @@ const config: Config.InitialOptions = {
333354
*/
334355
clearMocks: true,
335356

336-
// To disable the sentry jest integration, set this to 'jsdom'
337-
testEnvironment: '@sentry/jest-environment/jsdom',
357+
testEnvironment: '<rootDir>/tests/js/sentry-test/jest-environment.js',
338358
testEnvironmentOptions: {
339359
globalsCleanup: 'on',
340360
sentryConfig: {

0 commit comments

Comments
 (0)