feat(customer-analytics): rename announcement api, models and mcp tools to shoutouts - #108431
Conversation
…ls to shoutouts Rename the Announcement and AnnouncementDelivery models, the logic module, the viewset and its route, the facade contracts, the send task and the announcements-* MCP tools to shoutout names. The migration changes Django state only. The tables, the foreign key column, the index and the constraint keep their old names, so the deploy runs no DDL. The send task registers under customer_analytics.send_shoutout. The old customer_analytics.send_announcement name stays registered until queued tasks drain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: 87cd43f9-33a9-4151-b7c7-ec5b0ad4294c
🤖 CI report
|
| File | Comment lines | Added lines |
|---|---|---|
products/customer_analytics/backend/test/test_shoutout.py |
9 | 39 |
products/customer_analytics/backend/facade/api.py |
1 | 34 |
products/customer_analytics/backend/models/shoutout.py |
1 | 4 |
products/customer_analytics/backend/models/shoutout_delivery.py |
1 | 10 |
products/customer_analytics/backend/presentation/views/shoutouts.py |
1 | 35 |
products/customer_analytics/backend/tasks/tasks.py |
1 | 13 |
products/customer_analytics/backend/test/test_send_shoutout.py |
1 | 116 |
This check does not block merging. It updates on every push and clears when the share drops.
✅ Bundle size — no change
Uncompressed size of every built .js bundle, compared against the base branch.
Total: 68.92 MiB · no change
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.58 MiB · 22 files | no change | █████████░ 85.8% of 1.84 MiB |
logged-out boot: index + App + bootApp (preloaded by every page, including /login)src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts |
3.52 MiB · 629 files | no change | █████████░ 87.3% of 4.03 MiB |
authenticated shell (every logged-in page)src/scenes/AuthenticatedShell.tsx |
7.35 MiB · 2,339 files | 🟢 -16 B (-0.0%) | █████████░ 88.1% of 8.34 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/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/layout/navigation-3000/navigationLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/dashboard/dashboardLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/lemon-ui/LemonMarkdown/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/RichContentEditor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/CodeSnippet/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 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
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts 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
🟢 [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/index.tsx |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 854 B | src/scenes/ChunkLoadErrorBoundary.tsx |
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
| Size | File |
|---|---|
| 301.8 KiB | ../node_modules/.pnpm/posthog-js@1.434.14_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 216.9 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 |
| 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 |
| 100.5 KiB | src/lib/api.ts |
| 88.5 KiB | src/products.tsx |
| 69.4 KiB | src/lib/lemon-ui/icons/icons.tsx |
| 40.1 KiB | src/lib/utils/eventUsageLogic.ts |
| 38.7 KiB | ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js |
| 33.9 KiB | ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js |
| 28.4 KiB | src/scenes/scenes.ts |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 301.8 KiB | ../node_modules/.pnpm/posthog-js@1.434.14_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 272.2 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 216.9 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 |
| 153.7 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 |
| 100.5 KiB | src/lib/api.ts |
| 98.8 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 |
| 88.5 KiB | src/products.tsx |
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.16 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.16 MiB · 19 files | no change | ████░░░░░░ 37.8% of 5.72 MiB |
| Deferred (lazy) | 2.10 MiB · 44 files | no change | n/a — loads on demand |
Loader dist/toolbar.js |
1.2 KiB | no change | █░░░░░░░░░ 6.0% of 19.5 KiB |
Largest eagerly-shipped chunks
| Size | File |
|---|---|
| 805.4 KiB | dist/toolbar/toolbar-app-5UT2PX3W.css |
| 651.7 KiB | dist/toolbar/chunk-chunk-SEZBMT4M.js |
| 259.4 KiB | dist/toolbar/chunk-chunk-RK3L52NC.js |
| 138.3 KiB | dist/toolbar/chunk-chunk-YMECCIGG.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-FDH2IBXT.js |
| 75.2 KiB | dist/toolbar/toolbar-app-SN7ERW5L.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-TSAL54PB.js |
| 35.6 KiB | dist/toolbar/chunk-chunk-A6ER72L5.js |
| 21.0 KiB | dist/toolbar/chunk-chunk-E6HGNIFN.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 — 🟢 -870 B (-0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 947.32 MiB · 🟢 -870 B (-0.0%)
ℹ️ MCP UI apps size — 33 app(s), 17633.1 KB JS
Built size of each MCP UI app (main.js + styles.css).
| App | JS | CSS |
|---|---|---|
| debug | 597.9 KB | 199.2 KB |
| action | 454.1 KB | 199.2 KB |
| action-list | 564.2 KB | 199.2 KB |
| cohort | 453.1 KB | 199.2 KB |
| cohort-list | 563.2 KB | 199.2 KB |
| email-template | 452.9 KB | 199.2 KB |
| error-details | 469.6 KB | 199.2 KB |
| error-issue | 454.5 KB | 199.2 KB |
| error-issue-list | 564.8 KB | 199.2 KB |
| experiment | 561.3 KB | 199.2 KB |
| experiment-list | 564.9 KB | 199.2 KB |
| experiment-results | 566.3 KB | 199.2 KB |
| feature-flag | 566.8 KB | 199.2 KB |
| feature-flag-list | 570.5 KB | 199.2 KB |
| feature-flag-testing | 457.3 KB | 199.2 KB |
| inline-scan | 453.6 KB | 199.2 KB |
| insight-actors | 562.3 KB | 199.2 KB |
| invite-email-preview | 452.3 KB | 199.2 KB |
| llm-costs | 559.3 KB | 199.2 KB |
| session-recording | 455.3 KB | 199.2 KB |
| survey | 454.7 KB | 199.2 KB |
| survey-global-stats | 561.9 KB | 199.2 KB |
| survey-list | 564.9 KB | 199.2 KB |
| survey-stats | 561.9 KB | 199.2 KB |
| trace-span | 453.5 KB | 199.2 KB |
| trace-span-list | 564.1 KB | 199.2 KB |
| vision-observation-list | 563.3 KB | 199.2 KB |
| workflow | 453.4 KB | 199.2 KB |
| workflow-list | 563.5 KB | 199.2 KB |
| loops-review | 457.8 KB | 199.2 KB |
| query-results | 774.1 KB | 199.2 KB |
| render-ui | 858.1 KB | 199.2 KB |
| visual-review-snapshots | 457.9 KB | 199.2 KB |
⚠️ Django migration SQL — 1 new migration to review
We've detected new migrations on this PR. Review the SQL output for each migration:
products/customer_analytics/backend/migrations/0059_rename_announcement_to_shoutout.py
/opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/anyio/from_thread.py:119: SyntaxWarning: 'return' in a 'finally' block
return result
/opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/structlog/stdlib.py:1166: UserWarning: Remove `format_exc_info` from your processor chain if you want pretty exceptions.
ed = p(logger, meth_name, ed) # type: ignore[arg-type]
2026-09-29T15:05:05.046596Z [error ] Path must be a valid database or directory containing databases. [posthog.exceptions_capture] pid=8175 tid=140114836024192
Traceback (most recent call last):
File "/home/runner/work/posthog/posthog/posthog/geoip.py", line 15, in <module>
geoip: Optional[GeoIP2] = GeoIP2(cache=8)
~~~~~~^^^^^^^^^
File "/opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/django/contrib/gis/geoip2.py", line 116, in __init__
raise GeoIP2Exception(
"Path must be a valid database or directory containing databases."
)
django.contrib.gis.geoip2.GeoIP2Exception: Path must be a valid database or directory containing databases.
/opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/sshtunnel.py:1040: SyntaxWarning: 'return' in a 'finally' block
return (ssh_host,
/opt/hostedtoolcache/Python/3.14.7/x64/lib/python3.14/site-packages/langchain_core/_api/deprecation.py:27: UserWarning: Core Pydantic V1 functionality isn't compatible with Python 3.14 or greater.
from pydantic.v1.fields import FieldInfo as FieldInfoV1
System check identified some issues:
WARNINGS:
?: (axes.W001) You are using the django-axes cache handler for login attempt tracking. Your cache configuration is however invalid and will not work correctly with django-axes. This can leave security holes in your login systems as attempts are not tracked correctly. Reconfigure settings.AXES_CACHE and settings.CACHES per django-axes configuration documentation.
?: (staticfiles.W004) The directory '/home/runner/work/posthog/posthog/frontend/dist' in the STATICFILES_DIRS setting does not exist.
BEGIN;
--
-- Custom state/database change combination
--
-- (no-op)
COMMIT;Last updated: 2026-09-29 15:05 UTC (8fd9b20)
✅ 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
customer_analytics.0059_rename_announcement_to_shoutout
└─ #1 ✅ SeparateDatabaseAndState: Only state operations (no database changes)
Last updated: 2026-09-29 15:05 UTC (8fd9b20)
…amed model Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Generated-By: PostHog Desktop Task-Id: 87cd43f9-33a9-4151-b7c7-ec5b0ad4294c
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. 📝 WalkthroughWalkthroughCustomer Analytics models, backend APIs, frontend integrations, and MCP tools now use shoutout names instead of announcement names. The migration updates model state without database operations and retains existing table and column names. The backend creates shoutouts and schedules delivery after commit. Delivery processing updates per-channel results and overall status. Priority: ⬇️ Low Merge Risk: 🔵 Low · up to This is a rename of the Customer Analytics announcements feature to shoutouts, with the database schema preserved. One concern remains before merge: the send confirmation may not show the destination channels. The generated MCP types may also differ slightly from the actual API responses. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to The renamed interfaces retain the visible access and destination controls, but delivery compatibility is one-way: older queued tasks can run on new workers, while the reverse is not established. A mixed-version deployment or rollback could leave customer messages unsent. Retained concerns
Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 1✅ Passed checks (1 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
🟡 Other comments (2)
products/customer_analytics/mcp/tools.yaml-1283-1285 (1)
1283-1285: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winShow the destination channels in the confirmation message.
The confirmation message interpolates
{message}but not{channels}.prepareConfirmedActionreturns that message and instructs the model to surface it. Unless a separate UI adds the channels, the user can confirm an irreversible Slack send without seeing its destinations. Include the signed channel list in the confirmation display.services/mcp/src/api/generated.ts-67105-67105 (1)
67105-67105: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winRepresent a deleted creator as null.
The shoutout model permits
created_by = null. The facade passes that null through to the response, butShoutout.created_byrequiresUserBasic. Change the generated response contract toUserBasic | nullso consumers must handle shoutouts whose creator was deleted. (raw.githubusercontent.com)
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: PostHog/posthog/.coderabbit.yaml
Review profile: QUIET
Plan: Enterprise
Run ID: 2086be8e-f7d8-42fc-a438-bd552ab1e644
⛔ Files ignored due to path filters (5)
products/customer_analytics/frontend/generated/api.schemas.tsis excluded by!**/generated/**products/customer_analytics/frontend/generated/api.tsis excluded by!**/generated/**products/customer_analytics/frontend/generated/api.zod.tsis excluded by!**/generated/**services/mcp/src/generated/customer_analytics/api.tsis excluded by!**/generated/**services/mcp/src/tools/generated/customer_analytics.tsis excluded by!**/generated/**
📒 Files selected for processing (30)
.semgrep/rules/security/idor-team-scoped-models.yamlposthog/test/repo_invariants/test_pagination_contract.pyproducts/customer_analytics/backend/COMPROMISES.mdproducts/customer_analytics/backend/constants.pyproducts/customer_analytics/backend/facade/api.pyproducts/customer_analytics/backend/facade/constants.pyproducts/customer_analytics/backend/facade/contracts.pyproducts/customer_analytics/backend/logic/shoutouts.pyproducts/customer_analytics/backend/migrations/0059_rename_announcement_to_shoutout.pyproducts/customer_analytics/backend/migrations/max_migration.txtproducts/customer_analytics/backend/models/__init__.pyproducts/customer_analytics/backend/models/shoutout.pyproducts/customer_analytics/backend/models/shoutout_delivery.pyproducts/customer_analytics/backend/presentation/views/shoutouts.pyproducts/customer_analytics/backend/routes.pyproducts/customer_analytics/backend/tasks/__init__.pyproducts/customer_analytics/backend/tasks/tasks.pyproducts/customer_analytics/backend/test/test_announcement.pyproducts/customer_analytics/backend/test/test_send_announcement.pyproducts/customer_analytics/backend/test/test_send_shoutout.pyproducts/customer_analytics/backend/test/test_shoutout.pyproducts/customer_analytics/backend/test/test_shoutouts.pyproducts/customer_analytics/frontend/components/Shoutouts/ShoutoutsTabContent.test.tsxproducts/customer_analytics/frontend/components/Shoutouts/ShoutoutsTabContent.tsxproducts/customer_analytics/frontend/components/Shoutouts/shoutoutsLogic.test.tsproducts/customer_analytics/frontend/components/Shoutouts/shoutoutsLogic.tsproducts/customer_analytics/mcp/tools.yamlservices/mcp/schema/generated-tool-definitions.jsonservices/mcp/schema/tool-definitions-all.jsonservices/mcp/src/api/generated.ts
💤 Files with no reviewable changes (2)
- products/customer_analytics/backend/test/test_send_announcement.py
- products/customer_analytics/backend/test/test_announcement.py
Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 9 remain after this review.
| /** Slack channel IDs to send to. Each must be a channel the SupportHog bot is a member of; names are resolved server-side. */ | ||
| channels: string[]; |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Remove channels from the shoutout response type.
The endpoint marks channels write-only, so list, retrieve, and create responses omit it. The required Shoutout.channels field tells MCP consumers that every response contains channel IDs. Keep channels in the create-request type, and regenerate a response type without it. (raw.githubusercontent.com)
|
👋 Visual changes detected for this PR. Review and approve in PostHog Visual Review If these changes are unexpected, they may be caused by a flaky test or a broken snapshot on master. Don't approve — rerun the job or wait for a fix. Install the Visual Review Chrome extension to see visual review results at the top of your pull requests. |
Problem
announcements-*names for a feature that the UI calls shoutouts.Closes #108348
Origin
Changes
announcements-list,-retrieve,-createand-channels-listbecomeshoutouts-*, with the tool copy updated. Agents that call the old names get a missing-tool error./api/projects/:id/announcements/becomes/api/projects/:id/shoutouts/. The frontend uses the new generatedshoutouts*client andShoutout*Apitypes. No UI change.AnnouncementandAnnouncementDeliverybecomeShoutoutandShoutoutDelivery. The logic module, viewset, serializers, facade functions and contracts get the same rename.customer_analytics.send_shoutout. Acustomer_analytics.send_announcementalias stays registered so tasks queued before the deploy still send.test_pagination_contract.pyand the IDOR semgrep rule list the new names.Note
Migration
0059changes Django state only (sqlmigrateprints a no-op). The tables, theannouncement_idcolumn, the index and the unique constraint keep their old names throughdb_tableanddb_column. Old and new pods read the same schema during the deploy. A real table rename would break the old pods mid-deploy.Warning
Order with #102862: that PR edits the same announcement files and adds its own migration. It is in the merge queue, so the proposed order is #102862 first, then this stack rebases onto it and applies its
send_aschanges under the shoutout names. This PR stays a draft until that order is agreed.Two names stay as they were on purpose:
customer analytics announcement createdkeeps its name, so existing insights on it keep working.How did you test this code?
test_facade.pyandtest_pagination_contract.pyran locally against the dev database.test_send_shoutout.pynow runs its delivery and retry-config cases against both task names. It catches an alias that is registered under the wrong name or with inert retries, which would drop sends queued before the deploy.makemigrations --checkshows no state drift.sqlmigrate customer_analytics 0059emits no SQL.@posthog/quillpackage, and none of those errors touch the changed files. Semgrep is not installed here, so the IDOR rule edit relies on CI.Release status
Automatic notifications
Docs update
None. No doc under
docs/covers this feature.🤖 Agent context
Autonomy: Fully autonomous
Agent: Claude Code, claude-opus-5-5
/django-migrations,/writing-tests,/writing-pr-descriptions.RenameModelwith table and column renames. It would break old pods during the deploy.Created with PostHog Desktop from this inbox report.
🤖 Generated with Claude Code