feat(product-analytics): prepopulate the home tab with a dashboard template - #106651
GeneralistDev wants to merge 6 commits into
Conversation
…mplate
Replace the Insights Home tab's compact-list widgets with a real, editable PostHog dashboard, prepopulated from a template chosen by what the team builds (SaaS, e-commerce, mobile app, or the existing generic template).
- Adds `TeamHomeTabDashboard`, a Team extension model separate from `Team.primary_dashboard`, exposed as `home_tab_dashboard` on the team/project serializers.
- Seeds three new GLOBAL dashboard templates ("SaaS product", "E-commerce", "Mobile app") via a data migration, built from automatically captured pageview/screen events.
- Adds a template picker (`HomeTabTemplatePicker`) shown until a team picks a template; afterwards the Home tab renders the created dashboard via the existing `Dashboard` component.
- Removes the now-dead RecentlyViewed/FiringAlerts/ActiveUsers/Trending/NewEvents/Activity widgets and their logics.
Skills invoked: /django-migrations, /improving-drf-endpoints, /adopting-generated-api-types, /writing-kea-logics, /writing-ui-components, /writing-user-facing-copy, /writing-tests, /writing-code-comments, /writing-pr-descriptions.
Generated-By: PostHog Desktop
Task-Id: 0aa0698f-585a-4ae9-9e72-04665643a7dd
Generated-By: PostHog Desktop Task-Id: 0aa0698f-585a-4ae9-9e72-04665643a7dd
…rd-template Generated-By: PostHog Desktop Task-Id: 0aa0698f-585a-4ae9-9e72-04665643a7dd
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
🤖 CI report
|
| Function | Location | Complexity | Limit |
|---|---|---|---|
DashboardScene |
frontend/src/scenes/dashboard/Dashboard.tsx:101 |
29 | 10 |
DashboardItems |
frontend/src/scenes/dashboard/DashboardItems.tsx:82 |
27 | 10 |
refreshDashboardItems |
frontend/src/scenes/dashboard/dashboardLogic.tsx:4066 |
27 | 10 |
<anonymous> |
frontend/src/scenes/dashboard/DashboardItems.tsx:514 |
22 | 10 |
saveEditModeChanges |
frontend/src/scenes/dashboard/dashboardLogic.tsx:1630 |
19 | 10 |
recording_domains |
frontend/src/scenes/team-activity/teamActivityDescriber.tsx:211 |
19 | 10 |
updateWidgetTile |
frontend/src/scenes/dashboard/dashboardLogic.tsx:1879 |
16 | 10 |
copyToDashboard |
frontend/src/scenes/dashboard/dashboardLogic.tsx:1806 |
15 | 10 |
session_recording_masking_config |
frontend/src/scenes/team-activity/teamActivityDescriber.tsx:266 |
14 | 10 |
moveToDashboardSuccess |
frontend/src/scenes/dashboard/dashboardLogic.tsx:3804 |
12 | 10 |
<anonymous> |
frontend/src/scenes/dashboard/dashboardLogic.tsx:5096 |
11 | 10 |
⚠️ Duplication (Python) — 1 new duplicated block (worst 100 tokens)
New Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.
| First copy | Second copy | Lines | Tokens |
|---|---|---|---|
posthog/api/project.py:688 |
posthog/api/project.py:799 |
50 | 100 |
✅ Duplication (TypeScript) — clean
New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.
🚨 Comment density — 7% of added code lines are comments (26 of 373)
This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out.
Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See .agents/skills/writing-code-comments/SKILL.md for the house rules.
Files with the most added comment lines:
| File | Comment lines | Added lines |
|---|---|---|
frontend/src/scenes/saved-insights/homeTabLogic.ts |
5 | 79 |
posthog/api/project.py |
4 | 15 |
products/dashboards/backend/api/test/test_dashboard_templates.py |
4 | 28 |
frontend/src/scenes/dashboard/dashboardLogic.tsx |
3 | 9 |
products/dashboards/backend/models/team_home_tab_dashboard.py |
3 | 10 |
frontend/src/scenes/team-activity/teamActivityDescriber.tsx |
2 | 3 |
posthog/api/team.py |
2 | 20 |
frontend/src/scenes/dashboard/Dashboard.tsx |
1 | 17 |
This check does not block merging. It updates on every push and clears when the share drops.
⚠️ Bundle size — 🔺 +757.4 KiB (+1.1%)
Uncompressed size of every built .js bundle, compared against the base branch.
Total: 69.55 MiB · 🔺 +757.4 KiB (+1.1%)
| File | Size | Δ vs base |
|---|---|---|
render-query/src/render-query/render-query.js |
20.88 MiB | 🔺 +667.7 KiB (+3.2%) |
posthog-app/_parent/products/alerts/frontend/views/InsightAlerts.js |
13.1 KiB | 🔺 +3.4 KiB (+34.5%) |
posthog-app/_parent/products/ai_observability/frontend/evaluations/AIObservabilityEvaluationsScene.js |
50.4 KiB | 🔺 +1.7 KiB (+3.5%) |
posthog-app/_parent/products/ai_observability/frontend/prompts/LLMPromptScene.js |
55.5 KiB | 🔺 +1.7 KiB (+3.1%) |
posthog-app/_parent/products/ai_observability/frontend/tags/AIObservabilityTagScene.js |
40.3 KiB | 🔺 +1.7 KiB (+4.4%) |
posthog-app/_parent/products/ai_observability/frontend/tags/AIObservabilityTagsScene.js |
20.3 KiB | 🔺 +1.7 KiB (+9.1%) |
posthog-app/_parent/products/groups/frontend/pages/Groups.js |
16.5 KiB | 🔺 +1.7 KiB (+11.4%) |
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScannersScene.js |
91.9 KiB | 🔺 +1.7 KiB (+1.9%) |
posthog-app/src/queries/nodes/WebVitals/WebVitals.js |
19.7 KiB | 🔺 +1.7 KiB (+9.4%) |
posthog-app/src/queries/Query/Query.js |
13.5 KiB | 🔺 +1.7 KiB (+14.4%) |
posthog-app/src/scenes/activity/explore/EventsScene.js |
15.6 KiB | 🔺 +1.7 KiB (+12.2%) |
posthog-app/src/scenes/activity/explore/SessionsScene.js |
16.9 KiB | 🔺 +1.7 KiB (+11.1%) |
posthog-app/src/scenes/debug/DebugScene.js |
32.0 KiB | 🔺 +1.7 KiB (+5.6%) |
posthog-app/src/scenes/insights/InsightQuickStart/InsightQuickStart.js |
18.3 KiB | 🔺 +1.7 KiB (+10.2%) |
posthog-app/src/scenes/web-analytics/SessionAttributionExplorer/SessionAttributionExplorerScene.js |
19.3 KiB | 🔺 +1.7 KiB (+9.6%) |
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyTemplatesScene/CustomerJourneyTemplatesScene.js |
20.1 KiB | 🔺 +1.7 KiB (+9.2%) |
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerJourneyBuilderScene/CustomerJourneyBuilderScene.js |
14.7 KiB | 🔺 +1.7 KiB (+13.0%) |
posthog-app/src/scenes/marketing-analytics/MarketingAnalyticsScene.js |
139.4 KiB | 🔺 +1.7 KiB (+1.2%) |
posthog-app/src/scenes/saved-insights/SavedInsights.js |
13.6 KiB | 🔺 +1.7 KiB (+14.0%) |
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/SearchSessionRecordingsWidget.js |
14.0 KiB | 🔺 +1.7 KiB (+13.4%) |
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ReplayScanner.js |
55.5 KiB | 🔺 +1.7 KiB (+3.1%) |
posthog-app/src/scenes/hog-functions/HogFunctionScene.js |
58.4 KiB | 🔺 +1.7 KiB (+2.9%) |
posthog-app/src/lib/components/ActivityLog/describers.js |
168.5 KiB | 🔺 +1.7 KiB (+1.0%) |
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/components/ScannerScoutsTab.js |
30.8 KiB | 🔺 +1.6 KiB (+5.6%) |
posthog-app/src/scenes/data-management/definition/DefinitionView.js |
38.9 KiB | 🔺 +1.6 KiB (+4.4%) |
posthog-app/_parent/products/endpoints/frontend/EndpointsScene.js |
40.6 KiB | 🔺 +1.6 KiB (+4.2%) |
posthog-app/_parent/products/ai_observability/frontend/AIObservabilitySessionScene.js |
14.2 KiB | 🔺 +1.6 KiB (+12.7%) |
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/CreateInsightWidget.js |
14.0 KiB | 🔺 +1.6 KiB (+12.8%) |
posthog-app/_parent/products/posthog_ai/frontend/components/tool/widgets/QueryWidget.js |
14.1 KiB | 🔺 +1.6 KiB (+12.8%) |
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/components/SidePanelRunnerImpl.js |
19.2 KiB | 🔺 +1.6 KiB (+9.0%) |
posthog-app/_parent/products/signals/frontend/inbox/components/detail/ReportAiPanel.js |
16.8 KiB | 🔺 +1.6 KiB (+10.5%) |
posthog-app/src/layout/panel-layout/navbar/tabs/NavTabChat.js |
22.9 KiB | 🔺 +1.6 KiB (+7.5%) |
posthog-app/_parent/products/persons/frontend/pages/PersonsScene.js |
18.6 KiB | 🔺 +1.6 KiB (+9.1%) |
posthog-app/_parent/products/posthog_ai/frontend/scenes/TaskTracker/TaskTracker.js |
24.1 KiB | 🔺 +1.6 KiB (+6.9%) |
posthog-app/_parent/products/mcp_analytics/frontend/MCPAnalyticsScene.js |
178.6 KiB | 🔺 +1.6 KiB (+0.9%) |
posthog-app/src/scenes/cohorts/Cohort.js |
46.7 KiB | 🔺 +1.5 KiB (+3.3%) |
posthog-app/_parent/products/early_access_features/frontend/EarlyAccessFeature.js |
13.9 KiB | 🔺 +1.4 KiB (+11.5%) |
posthog-app/_parent/products/actions/frontend/pages/Action.js |
14.0 KiB | 🔺 +1.4 KiB (+11.0%) |
posthog-app/src/scenes/product-tours/ProductTour.js |
182.9 KiB | 🔺 +1.4 KiB (+0.8%) |
posthog-app/src/scenes/web-analytics/WebAnalyticsScene.js |
229.4 KiB | 🔺 +1.3 KiB (+0.6%) |
posthog-app/_parent/products/endpoints/frontend/EndpointScene.js |
64.1 KiB | 🔺 +1.3 KiB (+2.1%) |
posthog-app/_parent/products/signals/frontend/inbox/InboxScene.js |
462.8 KiB | 🔺 +1.2 KiB (+0.3%) |
posthog-app/src/scenes/experiments/SharedMetrics/SharedMetric.js |
21.8 KiB | 🔺 +1.2 KiB (+6.0%) |
posthog-app/_parent/products/business_knowledge/frontend/scenes/BusinessKnowledgeSettingsScene.js |
18.0 KiB | 🔺 +1.2 KiB (+7.1%) |
posthog-app/_parent/products/conversations/frontend/scenes/settings/SupportSettingsScene.js |
17.2 KiB | 🔺 +1.2 KiB (+7.5%) |
posthog-app/_parent/products/customer_analytics/frontend/scenes/CustomerAnalyticsConfigurationScene/CustomerAnalyticsConfigurationScene.js |
18.8 KiB | 🔺 +1.2 KiB (+6.8%) |
posthog-app/_parent/products/replay_vision/frontend/replay_scanners/ScannerEditorScene.js |
63.3 KiB | 🔺 +1.2 KiB (+1.9%) |
posthog-app/src/scenes/settings/SettingsMap.js |
17.1 KiB | 🔺 +1.2 KiB (+7.5%) |
posthog-app/src/scenes/settings/SettingsScene.js |
20.7 KiB | 🔺 +1.2 KiB (+6.1%) |
posthog-app/src/scenes/surveys/Survey.js |
14.5 KiB | 🔺 +1.2 KiB (+9.0%) |
posthog-app/_parent/products/replay_vision/frontend/observations/ObservationRecording.js |
18.3 KiB | 🔺 +1.1 KiB (+6.6%) |
posthog-app/src/scenes/session-recordings/detail/SessionRecordingDetail.js |
18.7 KiB | 🔺 +1.1 KiB (+6.4%) |
posthog-app/src/scenes/session-recordings/file-playback/SessionRecordingFilePlaybackScene.js |
21.3 KiB | 🔺 +1.1 KiB (+5.6%) |
posthog-app/src/scenes/session-recordings/kiosk/SessionRecordingsKiosk.js |
26.6 KiB | 🔺 +1.1 KiB (+4.4%) |
posthog-app/src/scenes/session-recordings/player/modal/SessionPlayerModal.js |
19.3 KiB | 🔺 +1.1 KiB (+6.2%) |
posthog-app/src/scenes/sessions/SessionProfileScene.js |
31.6 KiB | 🔺 +1.1 KiB (+3.7%) |
posthog-app/_parent/products/logs/frontend/LogsScene.js |
49.9 KiB | 🔺 +1.1 KiB (+2.2%) |
posthog-app/src/scenes/session-recordings/SessionRecordings.js |
17.9 KiB | 🔺 +1.1 KiB (+6.5%) |
posthog-app/src/scenes/session-recordings/settings/SessionRecordingsSettingsScene.js |
18.8 KiB | 🔺 +1.1 KiB (+6.2%) |
posthog-app/_parent/products/groups/frontend/pages/Group.js |
33.3 KiB | 🟢 -1.1 KiB (-3.1%) |
posthog-app/_parent/products/persons/frontend/pages/PersonScene.js |
42.7 KiB | 🟢 -1.1 KiB (-2.4%) |
posthog-app/src/scenes/notebooks/NotebookCanvasScene.js |
22.9 KiB | 🟢 -1.1 KiB (-4.4%) |
posthog-app/src/scenes/notebooks/NotebookPanel/NotebookPanel.js |
24.7 KiB | 🟢 -1.1 KiB (-4.1%) |
posthog-app/src/scenes/notebooks/NotebookScene.js |
30.3 KiB | 🟢 -1.1 KiB (-3.4%) |
posthog-app/_parent/products/ai_observability/frontend/AIObservabilityScene.js |
185.9 KiB | 🟢 -1.1 KiB (-0.6%) |
posthog-app/src/scenes/dashboard/Dashboard.js |
13.5 KiB | 🟢 -1.1 KiB (-7.3%) |
posthog-app/_parent/products/conversations/frontend/scenes/ticket/SupportTicketScene.js |
67.9 KiB | 🔺 +1.0 KiB (+1.5%) |
posthog-app/_parent/products/error_tracking/frontend/posthogAi/ErrorTrackingWidget.js |
18.0 KiB | 🔺 +1.0 KiB (+6.1%) |
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.57 MiB · 22 files | 🟢 -417 B (-0.0%) | █████████░ 85.1% 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.57 MiB · 628 files | 🟢 -420 B (-0.0%) | █████████░ 88.6% of 4.03 MiB |
authenticated shell (every logged-in page)src/scenes/AuthenticatedShell.tsx |
7.26 MiB · 2,297 files | 🔺 +30 B (+0.0%) | █████████░ 87.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.13_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 267.6 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.4 KiB | src/lib/api.ts |
| 84.9 KiB | src/products.tsx |
| 69.1 KiB | src/lib/lemon-ui/icons/icons.tsx |
| 63.9 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.3 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.13_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs |
| 271.7 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 267.6 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.4 KiB | src/lib/api.ts |
| 98.5 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 |
| 84.9 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.37 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.37 MiB · 19 files | 🟢 -475 B (-0.0%) | ████░░░░░░ 41.4% 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 |
|---|---|
| 789.8 KiB | dist/toolbar/toolbar-app-BJ4B4DT7.css |
| 650.5 KiB | dist/toolbar/chunk-chunk-J2XTBSOD.js |
| 483.6 KiB | dist/toolbar/chunk-chunk-M677G2Z5.js |
| 138.3 KiB | dist/toolbar/chunk-chunk-AZCJQJSR.js |
| 131.8 KiB | dist/toolbar/chunk-chunk-FDH2IBXT.js |
| 75.2 KiB | dist/toolbar/toolbar-app-6ANSB5D6.js |
| 69.0 KiB | dist/toolbar/chunk-chunk-TSAL54PB.js |
| 35.6 KiB | dist/toolbar/chunk-chunk-47FXT3NK.js |
| 21.0 KiB | dist/toolbar/chunk-chunk-EOQOZYXZ.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 — 🔺 +4.11 MiB (+0.4%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 947.18 MiB · 🔺 +4.11 MiB (+0.4%)
⚠️ MCP snapshots — 1 updated (1 modified, 0 added, 0 deleted)
Snapshots: MCP unit test snapshots updated
Changes: 1 snapshots (1 modified, 0 added, 0 deleted)
What this means:
- Snapshots have been automatically updated to match current output
Next steps:
- Review the changes to ensure they're intentional
- If unexpected, investigate what caused the output to change
⚠️ Django migration SQL — 2 new migrations to review
We've detected new migrations on this PR. Review the SQL output for each migration:
products/dashboards/backend/migrations/0022_teamhometabdashboard.py
/opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/infi/clickhouse_orm/__init__.py:1: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
__import__("pkg_resources").declare_namespace(__name__)
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;
--
-- Create model TeamHomeTabDashboard
--
CREATE TABLE "dashboards_teamhometabdashboard" ("team_id" integer NOT NULL PRIMARY KEY, "dashboard_id" bigint NULL);
ALTER TABLE "dashboards_teamhometabdashboard" ADD CONSTRAINT "dashboards_teamhomet_team_id_ac87bd9d_fk_posthog_t" FOREIGN KEY ("team_id") REFERENCES "posthog_team" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "dashboards_teamhometabdashboard" ADD CONSTRAINT "dashboards_teamhomet_dashboard_id_a02f96ea_fk_posthog_d" FOREIGN KEY ("dashboard_id") REFERENCES "posthog_dashboard" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "dashboards_teamhometabdashboard_dashboard_id_a02f96ea" ON "dashboards_teamhometabdashboard" ("dashboard_id");
COMMIT;products/dashboards/backend/migrations/0023_home_tab_dashboard_templates.py
/opt/hostedtoolcache/Python/3.13.13/x64/lib/python3.13/site-packages/infi/clickhouse_orm/__init__.py:1: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
__import__("pkg_resources").declare_namespace(__name__)
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;
--
-- Raw Python operation
--
-- THIS OPERATION CANNOT BE WRITTEN AS SQL
COMMIT;Last updated: 2026-09-25 14:18 UTC (c7271b6)
❌ Django migration risk — blocked migration detected
We've analyzed your migrations for potential risks.
Summary: 0 Safe | 1 Needs Review | 1 Blocked
❌ Blocked
Causes locks or breaks compatibility
dashboards.0022_teamhometabdashboard
│ └─ #1 ✅ CreateModel
│ Creating new table is safe
│ model: TeamHomeTabDashboard
│
└──> ℹ️ INFO:
ℹ️ Skipped operations on newly created tables (empty tables
don't cause lock contention).
│
└──> �[91m📋 POSTHOG POLICY VIOLATIONS:�[0m
❌ BLOCKED: CreateModel adds a ForeignKey to "posthog_team" - this
table is read on virtually every request. Creating the FK
constraint takes a SHARE ROW EXCLUSIVE lock on the referenced
parent (posthog_team), which conflicts with the ROW EXCLUSIVE
lock every INSERT/UPDATE/DELETE on it holds. Under write traffic
the lock request queues, lock_timeout cancels it, and each
bin/migrate retry repeats the stall. This has blocked deploys.
Two options: (a) db_constraint=False on the ForeignKey - emits no
FK constraint and takes NO lock on the parent at all (app-level
enforcement only). This is the only truly lock-free path. (b) For
a real database constraint, declare the FK with
db_constraint=False, then add it back as a DB constraint via
posthog.migration_helpers.AddForeignKeyNotValid in a later
migration and ValidateForeignKey after that. NOT VALID still
takes a *brief* SHARE ROW EXCLUSIVE lock on the parent for the
metadata add (it skips the row scan), so it shrinks the lock
window but does not eliminate it; VALIDATE then runs lock-free on
the parent. If this FK genuinely must lock posthog_team on add,
add "dashboards.0022_teamhometabdashboard" to posthog/management/
migration_analysis/hot_table_acknowledged_migrations.txt to
accept the risk, and coordinate the deploy with #team-
infrastructure for a low-traffic window. See https://github.com/P
ostHog/posthog/blob/master/docs/published/handbook/engineering/sa
fe-django-migrations.md#foreign-keys-to-hot-tables
⚠️ Needs Review
May have performance impact
dashboards.0023_home_tab_dashboard_templates
└─ #1 ⚠️ RunPython: RunPython data migration needs review for performance
📚 How to Deploy These Changes Safely
RunPython:
Use batching for large data migrations:
- Use
.iterator()to avoid loading all rows into memory - Use
.bulk_update()instead of saving individual objects - Batch size: 1,000-10,000 rows per batch
- Add pauses between batches
- Consider background jobs for very large updates (millions of rows)
See the migration safety guide
Last updated: 2026-09-25 14:18 UTC (c7271b6)
|
[High risk] Adds database schema and API fields for home tab dashboard configuration. The PR should not merge until Home-tab edits can be saved and dashboard creation reliably persists the team link. Reviews (1) · Last reviewed commit: "Merge branch 'master' into posthog/produ..." |
| use_template: templateKey, | ||
| pinned: true, | ||
| }) | ||
| actions.updateCurrentTeam({ home_tab_dashboard: dashboard.id }) |
There was a problem hiding this comment.
Team update finishes too late Dashboard creation completes without waiting for the request that links it to the team. If that request fails, the Home tab remains unconfigured while a pinned dashboard is left behind. If it is slow, the picker becomes clickable again and can create duplicates. Keep creation pending until the team update succeeds, and handle update failures.
Prompt To Fix With AI
This is a comment left during a code review.
Path: frontend/src/scenes/saved-insights/homeTabLogic.ts
Line: 77
Comment:
**Team update finishes too late** Dashboard creation completes without waiting for the request that links it to the team. If that request fails, the Home tab remains unconfigured while a pinned dashboard is left behind. If it is slow, the picker becomes clickable again and can create duplicates. Keep creation pending until the team update succeeds, and handle update failures.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| def remove_home_tab_dashboard_templates(apps, schema_editor): | ||
| DashboardTemplate = apps.get_model("dashboards", "DashboardTemplate") | ||
| DashboardTemplate.objects.filter(template_name__in=HOME_TAB_TEMPLATE_NAMES, team=None).delete() |
There was a problem hiding this comment.
Rollback deletes existing templates If a global template already has one of these names, the forward migration leaves it untouched, but the reverse migration deletes it. A rollback could therefore remove a template this migration did not create. Limit reversal to rows known to have been seeded here.
Prompt To Fix With AI
This is a comment left during a code review.
Path: products/dashboards/backend/migrations/0023_home_tab_dashboard_templates.py
Line: 344-346
Comment:
**Rollback deletes existing templates** If a global template already has one of these names, the forward migration leaves it untouched, but the reverse migration deletes it. A rollback could therefore remove a template this migration did not create. Limit reversal to rows known to have been seeded here.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| if "home_tab_dashboard" in validated_data: | ||
| team.home_tab_dashboard = validated_data.pop("home_tab_dashboard") |
There was a problem hiding this comment.
Home selection lacks audit trail This update saves
home_tab_dashboard on an extension and removes it before the Project and Team change checks run. A request that only changes this setting produces no activity entry, so its history cannot be traced there. Record the extension change explicitly. The Team serializer has the same gap.
Prompt To Fix With AI
This is a comment left during a code review.
Path: posthog/api/project.py
Line: 1231-1232
Comment:
**Home selection lacks audit trail** This update saves `home_tab_dashboard` on an extension and removes it before the Project and Team change checks run. A request that only changes this setting produces no activity entry, so its history cannot be traced there. Record the extension change explicitly. The Team serializer has the same gap.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| access_control: true, | ||
| week_start_day: true, | ||
| primary_dashboard: true, | ||
| home_tab_dashboard: true, |
There was a problem hiding this comment.
Create tool advertises rejected field The project-create tool offers
home_tab_dashboard, but the backend rejects it whenever there is no existing project instance. A client that supplies the advertised field receives a validation error instead of creating the project. Remove it from the create schema while retaining it for updates.
Prompt To Fix With AI
This is a comment left during a code review.
Path: services/mcp/src/tools/generated/core.ts
Line: 162
Comment:
**Create tool advertises rejected field** The project-create tool offers `home_tab_dashboard`, but the backend rejects it whenever there is no existing project instance. A client that supplies the advertised field receives a validation error instead of creating the project. Remove it from the create schema while retaining it for updates.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository: PostHog/posthog/.coderabbit.yaml Review profile: QUIET Plan: Enterprise Run ID: 📒 Files selected for processing (1)
Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughThe changes add a team-level Home tab dashboard setting and expose it through team and project APIs. A migration adds three global dashboard templates. The Home tab now renders the configured dashboard or offers template selection, which creates and assigns a dashboard. Dashboard consumers can require explicit edit mode. The previous Home tab widgets and their supporting logic are removed. Tests cover dashboard assignment, ownership validation, extension storage, and template application. Priority: ⬇️ Low Merge Risk: 🟡 Moderate · up to Dashboard selection can leave an unassigned dashboard if saving the team setting fails, and deleting a selected dashboard can leave Home pointing to it. Resolve these paths before merging. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to The new Home tab keeps dashboard access within the selected team, but creating, linking, and deleting its dashboard can leave the team’s Home tab in a state that does not recover automatically. No cross-team data exposure was established. 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: 2
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
🟡 Other comments (4)
posthog/api/team.py-2236-2240 (1)
2236-2240: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
home_tab_dashboardchanges are not recorded in the activity log, in either serializer.Both
update()methods assign the new dashboard through thehome_tab_dashboardproperty setter and pop the key fromvalidated_data, but neither logs the change afterward.home_tab_dashboardis a@propertybacked by theTeamHomeTabDashboardextension, not a model field, so it never appears ininstance.__dict__/team.__dict__. Each method's generic diff at the end ofupdate()only compares values captured from__dict__, so it cannot detect this change. Every other extension-backed setting in both files (revenue_analytics_config,marketing_analytics_config,customer_analytics_config,workflows_config,feature_flag_policy_config) is routed through a dedicated helper that explicitly captures the diff.home_tab_dashboardhas no equivalent call in either file, so changing a team's Home tab dashboard leaves no audit trail.
posthog/api/team.py#L2236-L2240: afterinstance.home_tab_dashboard = validated_data.pop("home_tab_dashboard"), capture the old/new dashboard IDs and callself._capture_diff(instance, "home_tab_dashboard", old_id, new_id), mirroring_update_revenue_analytics_config.posthog/api/project.py#L1229-L1233: afterteam.home_tab_dashboard = validated_data.pop("home_tab_dashboard"), capture the old/new dashboard IDs and callcapture_team_config_diff(team, "home_tab_dashboard", old_id, new_id, context=config_context), mirroringupdate_team_revenue_analytics_config.frontend/src/scenes/saved-insights/HomeTab.tsx-29-31 (1)
29-31: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the editable dashboard placement for the Home dashboard.
DashboardPlacement.BuiltinskipsDashboardHeader, which mounts the customize and add-tile controls. Builtin mode also excludes the dashboard edit bar and edit menu. AlthoughcanEditDashboardis true for users with Editor access, they cannot enter dashboard edit mode from Home.Suggested fix
- return <Dashboard id={String(currentTeam.home_tab_dashboard)} placement={DashboardPlacement.Builtin} /> + return <Dashboard id={String(currentTeam.home_tab_dashboard)} placement={DashboardPlacement.Dashboard} />products/dashboards/backend/api/test/test_dashboard_templates.py-1807-1812 (1)
1807-1812: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winNarrow the nullable JSON fields before use.
DashboardTemplate.tilesandInsight.queryare nullable JSON fields. The repository-wide mypy check can rejectlen(template.tiles)and"kind" in tile.insight.queryunderstrict_optional.Proposed fix
- self.assertEqual(dashboard.tiles.count(), len(template.tiles)) + assert template.tiles is not None + self.assertEqual(dashboard.tiles.count(), len(template.tiles)) insight_tiles = [tile for tile in dashboard.tiles.all() if tile.insight_id is not None] self.assertTrue(insight_tiles, "Expected at least one insight tile") for tile in insight_tiles: assert tile.insight is not None + assert tile.insight.query is not None self.assertIn("kind", tile.insight.query)frontend/src/scenes/saved-insights/HomeTabTemplatePicker.tsx-12-17 (1)
12-17: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winShow an error toast when Home tab dashboard creation fails.
createHomeTabDashboardcan fail because of a network/server error or a missing migrated template. TheProduct analyticsoption has a hardcoded fallback, but the other three options can raiseAttributeErrorwhen their global rows are deleted.createHomeTabDashboardFailureonly clears the selection, so the picker remains retryable but gives no failure feedback. Handle that action with an error toast.Suggested fix
-import { MakeLogicType, connect, kea, path, reducers } from 'kea' +import { MakeLogicType, connect, kea, listeners, path, reducers } from 'kea' import { loaders } from 'kea-loaders' import posthog from 'posthog-js' +import { lemonToast } from '`@posthog/lemon-ui`' + @@ reducers({ @@ }), + listeners(() => ({ + createHomeTabDashboardFailure: () => { + lemonToast.error('Failed to set up your Home tab dashboard. Please try again.') + }, + })), loaders(({ values, actions }) => ({
🧹 Nitpick comments (1)
posthog/api/test/test_team.py (1)
921-942: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winParameterize the shared dashboard tests while preserving field-specific checks.
The four tests share most of their flow, so the applicable guidance favors combining them. They are not identical, however. Preserve the primary-dashboard assertions, home-dashboard preservation check, and the primary-dashboard setup in the invalid-dashboard case.
Suggested parameterized refactor
- def test_update_primary_dashboard(self): - d = Dashboard.objects.create(name="Test", team=self.team) - - # Can set it - response = self.client.patch("/api/environments/@current/", {"primary_dashboard": d.id}) - response_data = response.json() - - self.assertEqual(response.status_code, status.HTTP_200_OK, response.json()) - self.assertEqual(response_data["name"], self.team.name) - self.assertEqual(response_data["primary_dashboard"], d.id) - - def test_cant_set_primary_dashboard_to_another_teams_dashboard(self): - self.team.primary_dashboard_id = None # Remove the default primary dashboard from the picture - self.team.save() - - team_2 = Team.objects.create(organization=self.organization, name="Default project") - d = Dashboard.objects.create(name="Test", team=team_2) - - response = self.client.patch("/api/environments/@current/", {"primary_dashboard": d.id}) - self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual( - response.json(), - self.validation_error_response("Dashboard does not belong to this team.", attr="primary_dashboard"), - ) - - def test_update_home_tab_dashboard(self): - d = Dashboard.objects.create(name="Home tab dashboard", team=self.team) - - response = self.client.patch("/api/environments/@current/", {"home_tab_dashboard": d.id}) - response_data = response.json() - - self.assertEqual(response.status_code, status.HTTP_200_OK, response.json()) - self.assertEqual(response_data["home_tab_dashboard"], d.id) - # Setting home_tab_dashboard must not disturb the unrelated primary_dashboard field - self.assertEqual(response_data["primary_dashboard"], self.team.primary_dashboard_id) - - def test_cant_set_home_tab_dashboard_to_another_teams_dashboard(self): - team_2 = Team.objects.create(organization=self.organization, name="Default project") - d = Dashboard.objects.create(name="Test", team=team_2) - - response = self.client.patch("/api/environments/@current/", {"home_tab_dashboard": d.id}) + `@parameterized.expand`( + [ + ("primary_dashboard", "Test", True), + ("home_tab_dashboard", "Home tab dashboard", False), + ] + ) + def test_update_dashboard(self, dashboard_field, dashboard_name, is_primary_dashboard): + d = Dashboard.objects.create(name=dashboard_name, team=self.team) + response = self.client.patch("/api/environments/@current/", {dashboard_field: d.id}) + response_data = response.json() + + self.assertEqual(response.status_code, status.HTTP_200_OK, response.json()) + self.assertEqual(response_data[dashboard_field], d.id) + if is_primary_dashboard: + self.assertEqual(response_data["name"], self.team.name) + else: + self.assertEqual(response_data["primary_dashboard"], self.team.primary_dashboard_id) + + `@parameterized.expand`( + [ + ("primary_dashboard", True), + ("home_tab_dashboard", False), + ] + ) + def test_cant_set_dashboard_to_another_teams_dashboard(self, dashboard_field, clear_primary_dashboard): + if clear_primary_dashboard: + self.team.primary_dashboard_id = None + self.team.save() + + team_2 = Team.objects.create(organization=self.organization, name="Default project") + d = Dashboard.objects.create(name="Test", team=team_2) + + response = self.client.patch("/api/environments/@current/", {dashboard_field: d.id}) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual( response.json(), - self.validation_error_response("Dashboard does not belong to this team.", attr="home_tab_dashboard"), + self.validation_error_response("Dashboard does not belong to this team.", attr=dashboard_field), )
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: PostHog/posthog/.coderabbit.yaml
Review profile: QUIET
Plan: Enterprise
Run ID: da544628-172d-450a-9576-7f15bb7b0a4d
⛔ Files ignored due to path filters (3)
frontend/src/generated/core/api.schemas.tsis excluded by!**/generated/**services/mcp/src/generated/core/api.tsis excluded by!**/generated/**services/mcp/src/tools/generated/core.tsis excluded by!**/generated/**
📒 Files selected for processing (34)
.github/scripts/check-idor-model-coverage.pyfrontend/src/lib/api.mock.tsfrontend/src/scenes/saved-insights/ActiveUsers.tsxfrontend/src/scenes/saved-insights/Activity.tsxfrontend/src/scenes/saved-insights/AlertRow.tsxfrontend/src/scenes/saved-insights/EventRow.tsxfrontend/src/scenes/saved-insights/FiringAlerts.tsxfrontend/src/scenes/saved-insights/HomeTab.tsxfrontend/src/scenes/saved-insights/HomeTabTemplatePicker.tsxfrontend/src/scenes/saved-insights/InsightRow.tsxfrontend/src/scenes/saved-insights/NewEvents.tsxfrontend/src/scenes/saved-insights/PersonRow.tsxfrontend/src/scenes/saved-insights/RecentlyViewed.tsxfrontend/src/scenes/saved-insights/SavedInsights.stories.tsxfrontend/src/scenes/saved-insights/Trending.tsxfrontend/src/scenes/saved-insights/activeUsersLogic.tsfrontend/src/scenes/saved-insights/homeTabLogic.tsfrontend/src/scenes/saved-insights/newEventsLogic.tsfrontend/src/scenes/saved-insights/trendingInsightsLogic.tsfrontend/src/scenes/team-activity/teamActivityDescriber.tsxfrontend/src/types.tsposthog/api/project.pyposthog/api/team.pyposthog/api/test/test_team.pyposthog/models/team/team.pyposthog/models/team/test/test_extensions.pyproducts/dashboards/backend/api/test/test_dashboard_templates.pyproducts/dashboards/backend/migrations/0022_teamhometabdashboard.pyproducts/dashboards/backend/migrations/0023_home_tab_dashboard_templates.pyproducts/dashboards/backend/migrations/max_migration.txtproducts/dashboards/backend/models/__init__.pyproducts/dashboards/backend/models/team_home_tab_dashboard.pyservices/mcp/src/api/generated.tsservices/mcp/tests/unit/__snapshots__/tool-schemas/project-settings-update.json
💤 Files with no reviewable changes (13)
- frontend/src/scenes/saved-insights/PersonRow.tsx
- frontend/src/scenes/saved-insights/RecentlyViewed.tsx
- frontend/src/scenes/saved-insights/ActiveUsers.tsx
- frontend/src/scenes/saved-insights/EventRow.tsx
- frontend/src/scenes/saved-insights/Activity.tsx
- frontend/src/scenes/saved-insights/Trending.tsx
- frontend/src/scenes/saved-insights/AlertRow.tsx
- frontend/src/scenes/saved-insights/FiringAlerts.tsx
- frontend/src/scenes/saved-insights/newEventsLogic.ts
- frontend/src/scenes/saved-insights/InsightRow.tsx
- frontend/src/scenes/saved-insights/activeUsersLogic.ts
- frontend/src/scenes/saved-insights/trendingInsightsLogic.ts
- frontend/src/scenes/saved-insights/NewEvents.tsx
Included review availability: Your plan provides up to 12 included reviews per hour; 3 remain after this review.
| if (currentTeam?.home_tab_dashboard) { | ||
| return <Dashboard id={String(currentTeam.home_tab_dashboard)} placement={DashboardPlacement.Builtin} /> | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🔎 Supported by static analysis
🏁 Script executed:
#!/bin/bash
rg -n -C5 'home_tab_dashboard' posthog/api/team.py posthog/api/project.pyRepository: PostHog/posthog
Length of output: 9197
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- changed file ---'
sed -n '1,180p' frontend/src/scenes/saved-insights/HomeTab.tsx
printf '%s\n' '--- dashboard and home-tab references ---'
rg -n -C6 'class Dashboard|def delete|deleted\s*=|home_tab_dashboard|TeamHomeTabDashboard|class Dashboard' posthog/models posthog/api frontend/src/scenes/saved-insights frontend/src | head -n 500
printf '%s\n' '--- relevant diff ---'
git diff --stat d5d656b206f2363bf52ed988950e160e22cfc479 7c8c97d4de0564de83f3f7e7bdd743780e64a189 -- frontend/src/scenes/saved-insights/HomeTab.tsx
git diff d5d656b206f2363bf52ed988950e160e22cfc479 7c8c97d4de0564de83f3f7e7bdd743780e64a189 -- frontend/src/scenes/saved-insights/HomeTab.tsxRepository: PostHog/posthog
Length of output: 38566
🏁 Script executed:
#!/bin/bash
set -e
sed -n '1,180p' frontend/src/scenes/saved-insights/HomeTab.tsx
printf '%s\n' '--- model and extension bindings ---'
rg -n -C6 'home_tab_dashboard|TeamHomeTabDashboard|class Dashboard|def delete|deleted\s*=' posthog frontend/src/scenes/saved-insights frontend/src | head -n 500
printf '%s\n' '--- reviewed diff ---'
git diff --stat d5d656b206f2363bf52ed988950e160e22cfc479 7c8c97d4de0564de83f3f7e7bdd743780e64a189 -- frontend/src/scenes/saved-insights/HomeTab.tsx
git diff d5d656b206f2363bf52ed988950e160e22cfc479 7c8c97d4de0564de83f3f7e7bdd743780e64a189 -- frontend/src/scenes/saved-insights/HomeTab.tsxRepository: PostHog/posthog
Length of output: 41138
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- extension model ---'
sed -n '1,180p' products/dashboards/backend/models/team_home_tab_dashboard.py
printf '%s\n' '--- dashboard declarations and deletion/read filters ---'
rg -l 'class Dashboard' posthog products | head -n 20
rg -n -C5 'class Dashboard|deleted\s*=\s*models|deleted=True|filter\(.*deleted|exclude\(deleted' products/dashboards/backend posthog/models | head -n 300
printf '%s\n' '--- Dashboard component ---'
fd -i '^Dashboard\\.(tsx|ts)$' frontend/src/scenes/dashboard frontend/src | head -n 20Repository: PostHog/posthog
Length of output: 29196
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- extension model ---'
sed -n '1,180p' products/dashboards/backend/models/team_home_tab_dashboard.py
printf '%s\n' '--- dashboard declarations and deletion/read filters ---'
rg -l 'class Dashboard' posthog products | head -n 20
rg -n -C5 'class Dashboard|deleted\s*=\s*models|deleted=True|filter\(.*deleted|exclude\(deleted' products/dashboards/backend posthog/models | head -n 300
printf '%s\n' '--- Dashboard component ---'
fd -i '^Dashboard\.(tsx|ts)$' frontend/src/scenes/dashboard frontend/src | head -n 20Repository: PostHog/posthog
Length of output: 28008
🏁 Script executed:
#!/bin/bash
set -e
printf '%s\n' '--- dashboard API bindings ---'
rg -n -C8 'class .*Dashboard.*ViewSet|queryset\s*=\s*Dashboard|Dashboard\.objects|deleted' products/dashboards/backend/api products/dashboards/backend/facade | head -n 320
printf '%s\n' '--- Dashboard component bindings ---'
rg -n -C8 'dashboard(Loading|Error|NotFound)|not.?found|error|useValues|dashboardLogic|dashboardsModel|dashboardData' frontend/src/scenes/dashboard/Dashboard.tsx frontend/src/scenes/dashboard | head -n 320Repository: PostHog/posthog
Length of output: 41683
Clear a deleted Home tab dashboard.
deleteDashboard soft-deletes the dashboard by setting deleted: true. The SET_NULL rule on TeamHomeTabDashboard.dashboard does not run. The team can therefore keep exposing the deleted dashboard ID. HomeTab then stays on the <Dashboard> branch instead of rendering HomeTabTemplatePicker.
Clear home_tab_dashboard when the dashboard is soft-deleted, or serialize the deleted dashboard as null.
| const dashboard = await dashboardsCreate(String(values.currentTeamId), { | ||
| name: label, | ||
| use_template: templateKey, | ||
| pinned: true, | ||
| }) | ||
| actions.updateCurrentTeam({ home_tab_dashboard: dashboard.id }) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '1,105p' frontend/src/scenes/saved-insights/homeTabLogic.ts
rg -n 'updateCurrentTeam:|updateCurrentTeam\(' frontend/src/scenes/teamLogic.ts frontend/src/scenes/team* frontend/src 2>/dev/null | head -65Repository: PostHog/posthog
Length of output: 12078
🏁 Script executed:
sed -n '270,325p' frontend/src/scenes/teamLogic.tsx
sed -n '375,435p' frontend/src/scenes/teamLogic.tsx
rg -n -C 8 'homeTabDashboard|createHomeTabDashboard|home_tab_dashboard' frontend/src/scenes/saved-insights frontend/src/scenes/teamLogic.test.ts frontend/src/scenes/teamLogic.tsxRepository: PostHog/posthog
Length of output: 25917
🏁 Script executed:
sed -n '425,475p' frontend/src/scenes/teamLogic.tsx
sed -n '1,120p' frontend/src/scenes/saved-insights/HomeTab.tsx
sed -n '1,90p' frontend/src/scenes/saved-insights/HomeTabTemplatePicker.tsx
rg -n -C 5 'asyncActions|updateCurrentTeam' frontend/src/scenes/teamLogic.tsx frontend/src/scenes/saved-insights/homeTabLogic.tsRepository: PostHog/posthog
Length of output: 13618
🏁 Script executed:
sed -n '330,375p' frontend/src/scenes/teamLogic.tsx
sed -n '620,690p' frontend/src/scenes/teamLogic.tsxRepository: PostHog/posthog
Length of output: 5474
🏁 Script executed:
sed -n '470,625p' frontend/src/scenes/teamLogic.tsxRepository: PostHog/posthog
Length of output: 9626
Await the team update before reporting success.
actions.updateCurrentTeam(...) dispatches the team loader but does not await its PATCH. If the PATCH fails, the home-tab loader can still return the pinned dashboard successfully. The team remains without home_tab_dashboard, so the picker appears again. A retry can create another unassigned dashboard.
Use the async action and add cleanup or an atomic operation if retries must not leave unassigned dashboards.
Suggested fix
- actions.updateCurrentTeam({ home_tab_dashboard: dashboard.id })
+ await teamLogic.asyncActions.updateCurrentTeam({ home_tab_dashboard: dashboard.id })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const dashboard = await dashboardsCreate(String(values.currentTeamId), { | |
| name: label, | |
| use_template: templateKey, | |
| pinned: true, | |
| }) | |
| actions.updateCurrentTeam({ home_tab_dashboard: dashboard.id }) | |
| const dashboard = await dashboardsCreate(String(values.currentTeamId), { | |
| name: label, | |
| use_template: templateKey, | |
| pinned: true, | |
| }) | |
| await teamLogic.asyncActions.updateCurrentTeam({ home_tab_dashboard: dashboard.id }) |
…to home tab dashboard Adds a hedgehog illustration to the "What does your product do?" picker, and makes the Home tab's created dashboard require an explicit "Customize" action to enter layout edit mode rather than entering it implicitly when a tile is dragged or resized. Generated-By: PostHog Desktop Task-Id: 0aa0698f-585a-4ae9-9e72-04665643a7dd
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to GitHub limitations.
🟡 Minor · Make the rollback non-destructive. · 0023_home_tab_dashboard_templates.py:325-356
products/dashboards/backend/migrations/0023_home_tab_dashboard_templates.py:325-356
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick winMake the rollback non-destructive.
If a matching global template exists before migration 0023,
get_or_createreuses it. The reverse callback then deletes that pre-existing row by name. Use a no-op reverse callback or track migration-owned rows explicitly.Suggested fix
-def remove_home_tab_dashboard_templates(apps, schema_editor): - DashboardTemplate = apps.get_model("dashboards", "DashboardTemplate") - DashboardTemplate.objects.filter(template_name__in=HOME_TAB_TEMPLATE_NAMES, team=None).delete() - - class Migration(migrations.Migration): @@ - migrations.RunPython(create_home_tab_dashboard_templates, remove_home_tab_dashboard_templates), + migrations.RunPython(create_home_tab_dashboard_templates, migrations.RunPython.noop),
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository: PostHog/posthog/.coderabbit.yaml
Review profile: QUIET
Plan: Enterprise
Run ID: 28238b94-642b-4781-9cf8-eea15cb9df3f
📒 Files selected for processing (5)
frontend/src/scenes/dashboard/Dashboard.tsxfrontend/src/scenes/dashboard/DashboardItems.tsxfrontend/src/scenes/dashboard/dashboardLogic.tsxfrontend/src/scenes/saved-insights/HomeTab.tsxfrontend/src/scenes/saved-insights/HomeTabTemplatePicker.tsx
Included review availability: Your plan provides up to 12 included reviews per hour; 10 remain after this review.
|
👋 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. |
…emplate Clears `home_tab_dashboard` so the "what does your product do?" picker reappears, without a shell command. Gated on `preflight.is_debug` so it never shows outside local dev / E2E testing. Generated-By: PostHog Desktop Task-Id: 0aa0698f-585a-4ae9-9e72-04665643a7dd
Problem
The Insights "Home" tab shows six compact-list widgets (recently viewed, firing alerts, active users, trending, new events, activity) with no way to customize or act on them. A user landing there gets a fixed dashboard-adjacent summary instead of a real, editable dashboard.
Changes
home_tab_dashboard.Dashboard/DashboardItemscomponents gained an opt-inrequireExplicitEditModeprop for this (default off everywhere else, so no other dashboard consumer's behavior changes).home_tab_dashboardis a new field onTeam/Project, separate fromTeam.primary_dashboard(used by the unrelated project homepage). It lives on a newTeamHomeTabDashboardextension table, followingposthog/models/team/README.md.This is a separate approach from the still-open #105851 ("use a shared notebook for home"), scoped only to the product-analytics Insights Home tab. It does not touch
frontend/src/scenes/project-homepage/,Team.primary_dashboard, or Django onboarding.How did you test this code?
Backend, run via
hogli testequivalents locally:posthog/api/test/test_team.pyandposthog/api/test/test_project.py(shared factory covers both/api/environments/and/api/projects/):test_update_home_tab_dashboardandtest_cant_set_home_tab_dashboard_to_another_teams_dashboard— guard the exact bug found while building this: the generic passthroughsave(update_fields=[...])loop would raiseFieldDoesNotExistfor a property-backed field unless it is special-cased, and it needs the same cross-team validation asprimary_dashboard.posthog/models/team/test/test_extensions.py::TestHomeTabDashboardProperty— theTeam.home_tab_dashboardgetter/setter round-trips through the extension table and never touchesprimary_dashboard.products/dashboards/backend/api/test/test_dashboard_templates.py::TestHomeTabDashboardTemplates— a smoke test per new template (parameterized) thatcreate_dashboard_from_templatesucceeds and produces tiles with well-formed queries, so a JSON typo in the migration fails here instead of as a 500 when a customer picks the template.posthog/api/test/test_team.py,posthog/api/test/test_project.py,posthog/models/team/test/,products/dashboards/backend/api/test/test_dashboard_templates.py,posthog/api/test/dashboards/test_dashboard.py.uv run mypy --cache-fine-grained .andpnpm --filter=@posthog/frontend typescript:check: clean.Frontend: rendered
Scenes-App/Saved Insights→HomeandHomeWithDashboardstories in a headless Storybook (Playwright/Chromium) at 1300px and 520px; screenshots above. For the explicit-edit-mode change, also clicked through the flow in Storybook with an editor-permission dashboard: confirmed "Customize" is the only way into layout edit mode (dragging/resizing a tile no longer auto-enters it), and that Save/Cancel appear once in edit mode. Manual QA in the running app was not done in this sandbox.Not run:
hogli migrations:check's async-migrations step fails in this sandbox on a freshly-created dev DB (unrelated legacy async migrations 0001-0007 show as pending) —migrate --check,migrate_clickhouse --check, andmakemigrations --check --dry-runall pass, and the two new migrations were applied and exercised directly.Release status
Automatic notifications
Docs update
None — no existing doc's scope covers the product-analytics Home tab specifically.
🤖 Agent context
Autonomy: Fully autonomous
Agent: Claude Code, Sonnet 5
Implemented from a detailed design brief covering the backend extension model, the data migration for the three templates, and the frontend picker/wiring. Skills invoked:
/django-migrations,/improving-drf-endpoints,/adopting-generated-api-types,/writing-kea-logics,/writing-ui-components,/writing-user-facing-copy,/writing-tests,/writing-code-comments,/writing-pr-descriptions,/running-ci-preflight.During implementation, two deviations from the literal brief were needed for the feature to work end to end:
posthog/api/team.py, butteamLogic.updateCurrentTeam()actually PATCHes/api/projects/{id}, served byProjectBackwardCompatSerializerinposthog/api/project.py. That serializer needed the matchinghome_tab_dashboardfield,team_passthrough_fieldsentry, and the same extension-popping fix inupdate(), or the picker's save would silently no-op.Teamdirectly; that produces a real circular import once the model is registered inproducts/dashboards/backend/models/__init__.py(pulled in transitively from insideTeam's own module body viaposthog.helpers.dashboard_templates). Switched to Django's string-based FK references ("posthog.Team","dashboards.Dashboard"), the documented pattern inproducts/README.mdfor this exact situation.A follow-up request added the hedgehog illustration and the explicit-edit-mode requirement described above.
POSTHOG_TASK_RUN_ID/CIwere set for this session, so the local CodeRabbit review pass was skipped per repo instructions.