Skip to content

feat(error_tracking): add native alert CRUD API behind feature flag - #90534

Merged
trunk-io[bot] merged 28 commits into
masterfrom
cat/et-alerts-api
Sep 4, 2026
Merged

trunk-io[bot] merged 28 commits into
masterfrom
cat/et-alerts-api

Conversation

@cat-ph

@cat-ph cat-ph commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Native error tracking alerts (RFC) need a management API before any UI or delivery can exist. Listing today's hog-function alerts means string-matching filter JSON; the new entity gets a real CRUD surface.

Stacked on #90533. Everything is gated behind the per-team error-tracking-native-alerts feature flag, so nothing is reachable for users yet.

Changes

  • New /api/projects/:id/error_tracking/alerts/ CRUD endpoints: an alert with nested destinations, triggers, property filters, and a throttle.
  • Filters compile to HogQL bytecode on save with the same compiler hog functions use, so filter semantics cannot fork between the two planes; uncompilable filters are rejected with a 400.
  • Destination updates replace the destination set wholesale; Slack destinations validate the integration belongs to the team and a channel is configured.
  • Two destinations pointing at the same channel are rejected with a 400 (display-only keys like channel_name do not make them distinct), so delivery can never double-post.
  • Filters and Slack destination config are typed in the OpenAPI schema, so generated clients get real shapes instead of unknown. Slack channel IDs must be JSON strings; numbers are rejected with a 400 instead of being coerced and stored.
  • PUT replaces the whole alert (omitted fields reset to their defaults); PATCH stays partial, and an empty PATCH no longer bumps updated_at.
  • Every endpoint returns 403 unless the team has the error-tracking-native-alerts flag.
  • Serializers read trigger and channel choices from facade contracts, keeping the presentation layer off backend models (hogli product:lint clean).
  • Mechanical: regenerated OpenAPI types (products/error_tracking/frontend/generated/, services/mcp/src/api/generated.ts) and facade/contract plumbing.

How did you test this code?

  • API tests: CRUD roundtrip with nested destinations, flag-off 403s, bytecode compile on create and update, uncompilable-filter rejection, destination replacement, invalid payload matrix (unknown trigger/channel, empty destinations, negative throttle, missing integration/channel), cross-team integration rejection, and per-team isolation.
  • New tests: duplicate destinations rejected on create and update (including a pair differing only by channel_name), empty PATCH leaves updated_at untouched, PUT resets omitted fields, and a parity test pinning contract constants to the model choices. The config-validation cases use a real integration so integration checks cannot mask them.
  • products/error_tracking/backend/tests/api/test_alerts_api.py passed locally; repo-wide mypy and ruff clean; OpenAPI regenerated via hogli build:openapi.

Automatic notifications

  • Publish to changelog?

Docs update

None — flag-gated, docs land with the rollout layer.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

  • Authored with Claude Code. Ported the POC viewset (cat/alerting-v2-poc) and reshaped it for the destination table and filters; the flag gate lives in ViewSet.initial() so every action shares it.
  • Skills invoked: /improving-drf-endpoints, /writing-tests, /writing-user-facing-copy, /writing-pr-descriptions, /stacking-prs.

@cat-ph cat-ph changed the title cat/et alerts api feat(error_tracking): add native alert CRUD API behind feature flag Aug 27, 2026
@github-actions

github-actions Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

✅ Complexity (TypeScript) — clean

Cyclomatic complexity above the limit in changed typescript files (10 for production files, 15 for test files). Warn only: worth simplifying when you next touch these functions.

⚠️ Bundle size — 🔺 +733 B (+0.0%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 68.16 MiB · 🔺 +733 B (+0.0%)

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

✅ Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.43 MiB · 22 files no change ███░░░░░░░ 31.6% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.85 MiB · 3,249 files 🔺 +242 B (+0.0%) █████████░ 91.1% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
307.0 KiB ../node_modules/.pnpm/posthog-js@1.422.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
263.5 KiB ../node_modules/.pnpm/posthog-js@1.422.5_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.js
257.0 KiB src/taxonomy/core-filter-definitions-by-group.json
154.2 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
104.5 KiB src/lib/api.ts
95.2 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

✅ Toolbar bundle — eager 2.26 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.26 MiB · 18 files 🔺 +246 B (+0.0%) ████░░░░░░ 39.5% of 5.72 MiB
Deferred (lazy) 2.12 MiB · 45 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
751.7 KiB dist/toolbar/toolbar-app-JNIXJPZH.css
590.3 KiB dist/toolbar/chunk-chunk-LNNTXXLR.js
484.7 KiB dist/toolbar/chunk-chunk-OTPWDCQE.js
134.1 KiB dist/toolbar/chunk-chunk-AVJ3KMOD.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
71.3 KiB dist/toolbar/toolbar-app-W36IVMP2.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-CPO5F3IA.js
21.0 KiB dist/toolbar/chunk-chunk-XYMVXILC.js
6.8 KiB dist/toolbar/chunk-chunk-DV7IWQNF.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

✅ Dist folder size — 🔺 +34.9 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1448.82 MiB · 🔺 +34.9 KiB (+0.0%)

ℹ️ MCP UI apps size — 33 app(s), 17662.9 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 600.0 KB 195.2 KB
action 458.2 KB 195.2 KB
action-list 564.9 KB 195.2 KB
cohort 457.2 KB 195.2 KB
cohort-list 563.8 KB 195.2 KB
email-template 457.0 KB 195.2 KB
error-details 472.9 KB 195.2 KB
error-issue 457.9 KB 195.2 KB
error-issue-list 564.7 KB 195.2 KB
experiment 562.0 KB 195.2 KB
experiment-list 565.6 KB 195.2 KB
experiment-results 567.1 KB 195.2 KB
feature-flag 567.6 KB 195.2 KB
feature-flag-list 571.4 KB 195.2 KB
feature-flag-testing 461.4 KB 195.2 KB
inline-scan 457.7 KB 195.2 KB
insight-actors 563.0 KB 195.2 KB
invite-email-preview 456.4 KB 195.2 KB
llm-costs 560.0 KB 195.2 KB
session-recording 459.0 KB 195.2 KB
survey 458.8 KB 195.2 KB
survey-global-stats 562.7 KB 195.2 KB
survey-list 565.5 KB 195.2 KB
survey-stats 562.7 KB 195.2 KB
trace-span 457.6 KB 195.2 KB
trace-span-list 564.7 KB 195.2 KB
vision-observation-list 563.9 KB 195.2 KB
workflow 457.5 KB 195.2 KB
workflow-list 564.2 KB 195.2 KB
loops-review 461.9 KB 195.2 KB
query-results 755.2 KB 195.2 KB
render-ui 838.3 KB 195.2 KB
visual-review-snapshots 462.0 KB 195.2 KB
⚠️ Backend coverage — 97.0% of changed backend lines covered — 18 uncovered

🧪 Backend test coverage

Patch coverage — changed backend lines (products + core): ███████████████████░ 97.0% (663 / 681)

File Patch Uncovered changed lines
products/error_tracking/backend/logic/lifecycle_events.py 90.0% 49–50, 69, 136, 139
products/error_tracking/backend/logic/alerts.py 92.4% 39, 42–43, 122, 136, 178, 205, 218, 220
products/error_tracking/backend/presentation/views/alerts.py 97.0% 180, 186, 222
products/error_tracking/backend/models.py 98.0% 939

🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 33572047202 -n patch-coverage), or the coverage-data block at the end of this comment.

Per-product line coverage (touched products)
Product Coverage Lines
platform_features ██░░░░░░░░░░░░░░░░░░ 12.1% 7 / 58
warehouse_sources_queue ██████░░░░░░░░░░░░░░ 29.4% 91 / 309
demo ███████████░░░░░░░░░ 56.9% 1,518 / 2,666
autoresearch ████████████░░░░░░░░ 62.5% 5 / 8
data_tools █████████████░░░░░░░ 66.2% 90 / 136
ai_gateway ███████████████░░░░░ 75.0% 9 / 12
batch_exports ████████████████░░░░ 79.8% 19,679 / 24,669
apm █████████████████░░░ 83.3% 1,234 / 1,481
signals █████████████████░░░ 84.6% 33,339 / 39,413
cdp █████████████████░░░ 85.2% 4,310 / 5,056
wizard █████████████████░░░ 86.7% 1,090 / 1,257
data_modeling █████████████████░░░ 86.7% 9,654 / 11,133
notebooks ██████████████████░░ 87.7% 11,202 / 12,770
product_tours ██████████████████░░ 87.9% 1,303 / 1,482
data_warehouse ██████████████████░░ 88.6% 12,782 / 14,425
engineering_analytics ██████████████████░░ 89.0% 7,707 / 8,656
cohorts ██████████████████░░ 89.1% 7,370 / 8,273
business_knowledge ██████████████████░░ 89.1% 4,440 / 4,984
dashboards ██████████████████░░ 89.6% 6,807 / 7,598
tasks ██████████████████░░ 90.0% 63,212 / 70,240
streamlit_apps ██████████████████░░ 90.2% 2,500 / 2,773
exports ██████████████████░░ 90.5% 8,717 / 9,637
links ██████████████████░░ 90.6% 183 / 202
managed_warehouse ██████████████████░░ 90.7% 8,563 / 9,436
alerts ██████████████████░░ 90.9% 5,182 / 5,700
visual_review ██████████████████░░ 90.9% 6,880 / 7,567
conversations ██████████████████░░ 91.0% 20,910 / 22,973
canvas ██████████████████░░ 91.0% 4,913 / 5,397
mcp_analytics ██████████████████░░ 91.2% 4,374 / 4,795
error_tracking ██████████████████░░ 91.7% 13,309 / 14,506
notifications ██████████████████░░ 92.2% 1,101 / 1,194
early_access_features ███████████████████░ 92.5% 1,323 / 1,430
managed_migrations ███████████████████░ 92.6% 1,556 / 1,681
slack_app ███████████████████░ 92.8% 12,354 / 13,315
stamphog ███████████████████░ 92.9% 5,775 / 6,216
web_analytics ███████████████████░ 93.1% 18,597 / 19,966
context_layer ███████████████████░ 93.3% 2,573 / 2,758
surveys ███████████████████░ 93.4% 6,051 / 6,482
reminders ███████████████████░ 93.4% 468 / 501
mcp_store ███████████████████░ 93.4% 7,609 / 8,144
posthog_ai ███████████████████░ 93.5% 1,372 / 1,468
marketing_analytics ███████████████████░ 93.5% 17,322 / 18,526
ai_observability ███████████████████░ 93.7% 17,988 / 19,194
approvals ███████████████████░ 93.7% 3,492 / 3,725
billing_alerts ███████████████████░ 93.9% 2,078 / 2,212
workflows ███████████████████░ 94.1% 11,042 / 11,739
legal_documents ███████████████████░ 94.1% 1,970 / 2,093
review_hog ███████████████████░ 94.3% 9,982 / 10,583
endpoints ███████████████████░ 94.4% 8,866 / 9,396
tracing ███████████████████░ 94.4% 2,678 / 2,837
access_control ███████████████████░ 94.9% 5,828 / 6,139
customer_analytics ███████████████████░ 95.0% 17,702 / 18,639
annotations ███████████████████░ 95.2% 795 / 835
product_analytics ███████████████████░ 95.3% 19,292 / 20,252
replay_vision ███████████████████░ 95.4% 26,086 / 27,355
experiments ███████████████████░ 95.5% 29,271 / 30,660
actions ███████████████████░ 95.5% 706 / 739
skills ███████████████████░ 95.6% 5,403 / 5,651
messaging ███████████████████░ 95.8% 3,636 / 3,795
logs ███████████████████░ 95.9% 13,495 / 14,077
growth ███████████████████░ 95.9% 7,644 / 7,969
feature_flags ███████████████████░ 96.1% 19,585 / 20,382
revenue_analytics ███████████████████░ 96.3% 1,841 / 1,911
user_interviews ███████████████████░ 96.5% 2,639 / 2,735
warehouse_sources ███████████████████░ 97.2% 406,037 / 417,835
data_quality ███████████████████░ 97.5% 4,103 / 4,209
analytics_platform ████████████████████ 98.1% 2,286 / 2,330
data_catalog ████████████████████ 98.3% 3,234 / 3,289
metrics ████████████████████ 98.3% 3,505 / 3,564
pulse ████████████████████ 98.4% 2,017 / 2,049
live_debugger ████████████████████ 99.2% 613 / 618
field_notes ████████████████████ 99.4% 158 / 159

Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.

✅ Django migration risk — migration analysis complete

We've analyzed your migrations for potential risks.

Summary: 1 Safe | 0 Needs Review | 0 Blocked

✅ Safe

Brief or no lock, backwards compatible

error_tracking.0031_errortrackingalert_errortrackingalertdestination_and_more
  └─ #1 ✅ CreateModel
     Creating new table is safe
     model: ErrorTrackingAlert
  └─ #2 ✅ CreateModel
     Creating new table is safe
     model: ErrorTrackingAlertDestination
  └─ #3 ✅ CreateModel
     Creating new table is safe
     model: ErrorTrackingAlertThread
  │
  └──> ℹ️  INFO:
       ℹ️  Skipped operations on newly created tables (empty tables
       don't cause lock contention).

Last updated: 2026-09-01 23:42 UTC (34bb4c4)

@trunk-io

trunk-io Bot commented Aug 27, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

@cat-ph
cat-ph force-pushed the cat/et-alerts-api branch from 6b17c04 to e2db263 Compare August 27, 2026 23:28
@cat-ph
cat-ph force-pushed the cat/et-alerts-api branch from e2db263 to a1682c1 Compare August 28, 2026 12:16
@cat-ph
cat-ph force-pushed the cat/et-alerts-api branch from a1682c1 to a18c80e Compare August 28, 2026 21:58
@cat-ph
cat-ph force-pushed the cat/et-alerts-api branch from a18c80e to ddc5bdb Compare August 28, 2026 23:38
@cat-ph
cat-ph force-pushed the cat/et-alerts-api branch from ddc5bdb to 3f479a7 Compare August 28, 2026 23:42
@cat-ph
cat-ph force-pushed the cat/et-alerts-api branch 3 times, most recently from 48a3dd0 to ddda004 Compare August 31, 2026 00:07
@cat-ph
cat-ph force-pushed the cat/et-alerts-api branch from d670c0c to d5b6286 Compare August 31, 2026 00:54
@cat-ph
cat-ph marked this pull request as ready for review August 31, 2026 18:23
@cat-ph
cat-ph requested review from a team, ablaszkiewicz and hpouillot August 31, 2026 18:23
@github-actions

github-actions Bot commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

🦔 Hogbox preview · ❌ build failed

The preview didn't come up for commit 34bb4c4. See the build log for the failing step. It'll retry on the next push.

Previews are optional and never block merging. A failure here is often a hogland or tailnet hiccup rather than anything in your PR, so the check stays green and this comment is the status.

@greptile-apps

greptile-apps Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
products/error_tracking/backend/logic/alerts.py:209-210
**Canonical team rejects child integrations**

When an alert uses a Slack integration owned by a child environment, validation compares the integration's `team_id` with the canonical parent team ID, causing the valid same-project destination to be rejected with a 400 response.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "refactor(error_tracking): derive alert c..." | Re-trigger Greptile

cat-ph and others added 12 commits September 2, 2026 02:36
The alert, destination, and thread models add reverse accessors to Team,
User, and Integration; the cross-boundary reverse-accessor ratchet forbids
new ones. Nothing reads them, so related_name='+' seals all five. The
unmerged 0031 migration is updated in place.
- serializers read trigger/channel choices from facade contracts instead of backend models (presentation-layer boundary)
- reject identical duplicate destinations on create and update
- type alert filters and Slack destination config in the schema and regenerate the API clients
- PUT replaces the whole alert (omitted fields reset to defaults); an empty PATCH no longer touches updated_at
…nums

Replaces the duplicated contract constants (and their parity test) with
tuples derived in the alerts facade, so the values cannot drift.
Base automatically changed from cat/et-alerts-models to master September 2, 2026 14:18
@trunk-io

trunk-io Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

😎 Stack merged successfully - details.

@trunk-io
trunk-io Bot merged commit 3292137 into master Sep 4, 2026
270 checks passed
@trunk-io
trunk-io Bot deleted the cat/et-alerts-api branch September 4, 2026 14:18
@deployment-status-posthog

deployment-status-posthog Bot commented Sep 4, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-09-04 15:00 UTC Run
prod-us ✅ Deployed 2026-09-04 15:21 UTC Run
prod-eu ✅ Deployed 2026-09-04 15:20 UTC Run

This branch had an error being deployed

1 failed deployment
preview-pr-90534 — 34bb4c43 Deployed Sep 1, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants