Skip to content

Commit 9410c88

Browse files
authored
Merge branch 'master' into leanderrodrigues/iswf-2529-allow-llm-proxy-to-generate-issue-title-and-description-when
2 parents e9b1ae8 + 2dfa744 commit 9410c88

1,829 files changed

Lines changed: 37349 additions & 25140 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/generate-frontend-forms/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ onSubmit: ({value}) =>
750750

751751
```tsx
752752
<Flex gap="md" justify="end">
753-
<Button onClick={() => form.reset()}>Reset</Button>
753+
<form.ResetButton>Reset</form.ResetButton>
754754
<form.SubmitButton>Save Changes</form.SubmitButton>
755755
</Flex>
756756
```

.agents/skills/generate-migration/SKILL.md

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ sentry django makemigrations <app_name> --empty
3232
3. Run `sentry django sqlmigrate <app_name> <migration_name>` to verify the SQL
3333
4. Apply the migration locally with `sentry django migrate <app_name>` — Sentry's migration framework runs its safety checks on apply, so this catches unsafe ops (missing `is_post_deployment`, unsafe column changes, etc.) before CI does.
3434

35+
When editing a generated migration (e.g. swapping `DeleteModel` for `SafeDeleteModel`), **leave the auto-generated `is_post_deployment` comment block in place**. It documents a non-obvious flag with concrete guidance for future migration authors — useful context, not fluff. Only remove a comment if it's stale or contradicts the code.
36+
3537
## Guidelines
3638

3739
### Adding Columns
@@ -51,11 +53,20 @@ For large tables, set `is_post_deployment = True` on the migration as index crea
5153
3. Replace `RemoveField` with `SafeRemoveField(..., deletion_action=DeletionAction.MOVE_TO_PENDING)`
5254
4. Deploy, then create second migration with `SafeRemoveField(..., deletion_action=DeletionAction.DELETE)`
5355

54-
### Deleting Tables
56+
### Removing a Model (and eventually its table)
57+
58+
Two-phase process — the `historical_silo_assignments` entry must be added in phase 1.
59+
60+
**Phase 1 — Remove the model class (`MOVE_TO_PENDING`)**
5561

5662
1. Remove all code references
5763
2. Replace `DeleteModel` with `SafeDeleteModel(..., deletion_action=DeletionAction.MOVE_TO_PENDING)`
58-
3. Deploy, then create second migration with `SafeDeleteModel(..., deletion_action=DeletionAction.DELETE)`
64+
3. Add the table to `historical_silo_assignments` in `src/sentry/db/router.py` (or `getsentry/db/router.py` for getsentry models). Pick the silo the model used — usually `SiloMode.CELL`.
65+
4. Deploy
66+
67+
**Phase 2 — Drop the table (`DELETE`)**
68+
69+
After phase 1 has deployed, create a second migration with `SafeDeleteModel(..., deletion_action=DeletionAction.DELETE)`. Leave the historical entry in place — the table-drop migration relies on it to resolve the silo.
5970

6071
### Renaming Columns/Tables
6172

.agents/skills/lint-new/SKILL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ ruleTester.run('$ARGUMENTS', $RULE_NAME, {
9797
Run tests:
9898

9999
```bash
100-
CI=true pnpm test "static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts"
100+
pnpm test-ci "static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts"
101101
```
102102

103103
## Autofix Guidance
@@ -165,7 +165,7 @@ Add to `eslint.config.ts` inside the `name: 'plugin/@sentry/scraps'` block:
165165
### 3. Verify
166166

167167
```bash
168-
CI=true pnpm test "static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts"
168+
pnpm test-ci "static/eslint/eslintPluginScraps/src/rules/$ARGUMENTS.spec.ts"
169169
```
170170

171171
## Extending an Existing Rule

.agents/skills/migrate-frontend-forms/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ function SlugForm({project}: {project: Project}) {
483483
</Alert>
484484

485485
<Flex gap="sm" justify="end">
486-
<Button onClick={() => form.reset()}>Cancel</Button>
486+
<form.ResetButton>Reset</form.ResetButton>
487487
<form.SubmitButton>Save</form.SubmitButton>
488488
</Flex>
489489
</form.AppForm>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: 'Early Devservices'
2+
description: 'Starts devservices in the background so image pulls overlap with venv setup'
3+
inputs:
4+
mode:
5+
description: 'devservices mode (must match the mode passed to setup-sentry)'
6+
required: true
7+
timeout-minutes:
8+
description: 'Maximum minutes for devservices up'
9+
required: false
10+
default: '10'
11+
12+
runs:
13+
using: 'composite'
14+
steps:
15+
- uses: astral-sh/setup-uv@884ad927a57e558e7a70b92f2bccf9198a4be546 # v6
16+
with:
17+
version: '0.9.28'
18+
enable-cache: false
19+
20+
- name: Start devservices in background
21+
shell: bash --noprofile --norc -euo pipefail {0}
22+
env:
23+
TIMEOUT_MINUTES: ${{ inputs.timeout-minutes }}
24+
MODE: ${{ inputs.mode }}
25+
run: |
26+
DS_VERSION=$(python3 -c "
27+
import tomllib
28+
with open('uv.lock', 'rb') as f:
29+
lock = tomllib.load(f)
30+
for pkg in lock['package']:
31+
if pkg['name'] == 'devservices':
32+
print(pkg['version'])
33+
break
34+
")
35+
echo "Installing devservices==${DS_VERSION}"
36+
uv venv /tmp/ds-venv --python python3 -q
37+
uv pip install --python /tmp/ds-venv/bin/python -q \
38+
--index-url https://pypi.devinfra.sentry.io/simple \
39+
"devservices==${DS_VERSION}"
40+
(set +e; timeout "${TIMEOUT_MINUTES}m" /tmp/ds-venv/bin/devservices up --mode "$MODE"; echo $? > /tmp/ds-exit) \
41+
> /tmp/ds.log 2>&1 &
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#!/usr/bin/env python3
2+
"""Wait for the background devservices process started by the setup-devservices action.
3+
4+
Usage: wait.py [timeout_seconds]
5+
6+
Reads: /tmp/ds-exit, /tmp/ds.log (written by the setup-devservices action)
7+
Writes: $GITHUB_ENV (DJANGO_LIVE_TEST_SERVER_ADDRESS)
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import json
13+
import os
14+
import subprocess
15+
import sys
16+
import time
17+
from pathlib import Path
18+
19+
DS_EXIT = Path("/tmp/ds-exit")
20+
DS_LOG = Path("/tmp/ds.log")
21+
TIMEOUT = 300
22+
23+
24+
def log(msg: str) -> None:
25+
print(msg, flush=True)
26+
27+
28+
def docker(*args: str) -> subprocess.CompletedProcess[str]:
29+
return subprocess.run(["docker", *args], capture_output=True, text=True)
30+
31+
32+
def stream_log(pos: int) -> int:
33+
"""Print any new content written to DS_LOG since pos. Returns the new position."""
34+
if not DS_LOG.exists():
35+
return pos
36+
with DS_LOG.open() as f:
37+
f.seek(pos)
38+
chunk = f.read()
39+
if chunk:
40+
sys.stdout.write(chunk)
41+
sys.stdout.flush()
42+
return f.tell()
43+
44+
45+
def container_inspect_dump() -> None:
46+
ids = docker("ps", "-aq").stdout.split()
47+
if not ids:
48+
return
49+
50+
r = docker("inspect", *ids)
51+
if r.returncode != 0:
52+
return
53+
54+
containers = json.loads(r.stdout)
55+
for c in containers:
56+
name = c["Name"].lstrip("/")
57+
status = c["State"]["Status"]
58+
health = c["State"].get("Health")
59+
health_status = health["Status"] if health else "n/a"
60+
log(f"{name} status={status} health={health_status}")
61+
62+
log("")
63+
for c in containers:
64+
health = c["State"].get("Health")
65+
if not health or health["Status"] == "healthy":
66+
continue
67+
name = c["Name"].lstrip("/")
68+
log(f"--- {name} last health check ---")
69+
for entry in health.get("Log", []):
70+
log(f" exit={entry['ExitCode']} {entry['Output'].strip()}")
71+
72+
73+
def wait(timeout: int = TIMEOUT) -> None:
74+
start = time.monotonic()
75+
log_pos = 0
76+
77+
while not DS_EXIT.exists():
78+
elapsed = time.monotonic() - start
79+
if elapsed > timeout:
80+
log_pos = stream_log(log_pos)
81+
log(f"::error::Timed out waiting for devservices after {timeout}s")
82+
log("--- container health on timeout ---")
83+
container_inspect_dump()
84+
sys.exit(1)
85+
log_pos = stream_log(log_pos)
86+
time.sleep(2)
87+
88+
# Drain any remaining log output.
89+
stream_log(log_pos)
90+
91+
rc = int(DS_EXIT.read_text().strip())
92+
if rc != 0:
93+
log(f"::error::devservices up failed (exit {rc})")
94+
log("--- container health on failure ---")
95+
container_inspect_dump()
96+
sys.exit(1)
97+
98+
r = docker(
99+
"network",
100+
"inspect",
101+
"bridge",
102+
"--format",
103+
"{{(index .IPAM.Config 0).Gateway}}",
104+
)
105+
if r.returncode != 0:
106+
log(f"::error::docker network inspect bridge failed: {r.stderr.strip()}")
107+
sys.exit(1)
108+
gateway = r.stdout.strip()
109+
github_env = os.environ.get("GITHUB_ENV")
110+
if github_env:
111+
with open(github_env, "a") as f:
112+
f.write(f"DJANGO_LIVE_TEST_SERVER_ADDRESS={gateway}\n")
113+
114+
r2 = docker("ps", "-a")
115+
if r2.stdout.strip():
116+
log(r2.stdout.strip())
117+
118+
119+
if __name__ == "__main__":
120+
wait(int(sys.argv[1]) if len(sys.argv) > 1 else TIMEOUT)

.github/file-filters.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ acceptance: &acceptance
108108
- '.github/workflows/acceptance.yml'
109109
- '.github/actions/collect-test-data/**'
110110

111+
# Used in `getsentry-dispatch.js` to decide whether to run getsentry's
112+
# acceptance suite against a sentry PR. Scoped to gsApp and gsAdmin
113+
# frontend changes (both have acceptance tests in getsentry).
114+
gsapp: &gsapp
115+
- added|deleted|modified: 'static/{gsApp,gsAdmin}/**/*.{ts,tsx}'
116+
111117
api_docs: &api_docs
112118
- *backend_all
113119
- 'api-docs/**'

0 commit comments

Comments
 (0)