Skip to content

feat(dashboards): add tile groups and badges to the dashboard API and MCP - #114810

Closed
pauldambra wants to merge 5 commits into
masterfrom
posthog/tile-groups-backend
Closed

pauldambra wants to merge 5 commits into
masterfrom
posthog/tile-groups-backend

Conversation

@pauldambra

@pauldambra pauldambra commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Problem

An agent that compares dashboard tiles cannot mark which tile in a set won, or give the set a name. The answer stays buried in tile titles.

This is layer 1 of 2, split out of #114783. Nothing draws these fields yet; layer 2 (#114811) adds the UI. Refs #114760.

Changes

  • A tile can now carry a group_key and a badge (winner or cheeky-hog). The dashboard PATCH and the MCP update tools set them.
  • A dashboard can name each group with customization.group_titles. Keys are stripped like tile keys. Blank keys, duplicate keys and more than 100 titles are rejected.
  • Duplicating a whole dashboard keeps group_key and badge, and the titles come with it. Copying or moving one tile to another dashboard drops its group_key and keeps the badge, because a group belongs to the tile and its dashboard.
  • A group can hold more than one winner. The API help text says so.
  • The migration adds two nullable columns with no default, so it needs no table rewrite and can run before the code. It is the riskiest part to review.
  • Mechanical: generated API and MCP types, MCP tool schema snapshots, the tile SQL snapshot and the resource transfer field snapshot.

No UI changes in this layer, so there are no screenshots.

How did you test this code?

Test rationale: The group title tests catch a padded title key that saves but never matches a tile, and the rejection cases catch blank, duplicate and over-limit input. The existing group title test only covered the length check.

  • Group title tests in test_dashboard.py: padded keys match, old padded keys read back stripped, four rejection cases.
  • The DashboardTile field snapshot and the MCP tool schema snapshots cover the new fields.
  • Not run: the full backend suite and the OpenAPI codegen locally. CI covers both.

Release status

  • No feature flag controls this change

Docs update

None. The MCP tool descriptions carry the new fields.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Agent: Claude Code, Sonnet 5 (main loop and runners) and Opus 5.5 (one review and one frontend fix runner)


Created with PostHog Desktop

… MCP

Dashboard tiles get an optional group_key and a badge (winner or cheeky-hog).
A dashboard can give each group key a title through customization.group_titles.
The columns are nullable, so the migration is safe to run before the code.
The dashboard PATCH and the MCP tools can set all three. Nothing draws them yet.

Generated-By: PostHog Desktop
Task-Id: a0588627-3b8c-4fc5-b331-716af1994715
@pauldambra pauldambra self-assigned this Oct 9, 2026
@trunk-io

trunk-io Bot commented Oct 9, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

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

@posthog

posthog Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

🦔 PostHog Review (standard) reviewed this pull request

Nothing worth raising.

@github-actions

github-actions Bot commented Oct 9, 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.

✅ Duplication (Python) — clean

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.

✅ 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.

⚠️ Bundle size — 🔺 +1.3 KiB (+0.0%)

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

Total: 75.52 MiB · 🔺 +1.3 KiB (+0.0%)

File Size Δ vs base
posthog-app/_parent/products/review_hog/frontend/CodeReviewScene.js 109.3 KiB 🟢 -4.7 KiB (-4.1%)
posthog-app/_parent/products/autoresearch/frontend/AutoresearchPipelineScene.js 93.0 KiB 🔺 +4.6 KiB (+5.2%)

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.67 MiB · 23 files 🔺 +299 B (+0.0%) █████████░ 90.6% 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.82 MiB · 675 files 🔺 +967 B (+0.0%) █████████░ 94.8% of 4.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
7.78 MiB · 2,463 files 🔺 +1.7 KiB (+0.0%) █████████░ 93.3% of 8.34 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
9.92 MiB · 3,517 files 🔺 +1.5 KiB (+0.0%) █████████░ 90.8% of 10.92 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
7.80 MiB · 2,473 files 🔺 +1.7 KiB (+0.0%) █████████░ 90.8% of 8.58 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
9.53 MiB · 3,361 files 🔺 +1.5 KiB (+0.0%) █████████░ 90.6% of 10.51 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
12.33 MiB · 4,192 files 🔺 +1.5 KiB (+0.0%) ████████░░ 78.4% of 15.72 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ 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
🟢 products/dashboards/frontend/widgets/previews/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 zod/v4/locales/de.js stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/project-homepage/today/TodayReportPage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/queries/Query/Query.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/activity/explore/EventsScene.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
839 B src/scenes/ChunkLoadErrorBoundary.tsx
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
317.7 KiB ../node_modules/.pnpm/posthog-js@1.438.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
220.8 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
99.6 KiB src/lib/api.ts
93.6 KiB src/products.tsx
69.0 KiB src/lib/lemon-ui/icons/icons.tsx
40.7 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
29.0 KiB ../node_modules/.pnpm/zod@4.3.6/node_modules/zod/v4/core/schemas.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
317.7 KiB ../node_modules/.pnpm/posthog-js@1.438.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
280.8 KiB src/taxonomy/core-filter-definitions-by-group.json
220.8 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
112.1 KiB ../packages/quill/packages/quill/dist/index.js
99.6 KiB src/lib/api.ts
93.6 KiB src/products.tsx
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
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
317.7 KiB ../node_modules/.pnpm/posthog-js@1.438.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
280.8 KiB src/taxonomy/core-filter-definitions-by-group.json
220.8 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
181.9 KiB src/queries/validators.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
112.1 KiB ../packages/quill/packages/quill/dist/index.js
99.6 KiB src/lib/api.ts
93.6 KiB src/products.tsx
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
Size File
317.7 KiB ../node_modules/.pnpm/posthog-js@1.438.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
280.8 KiB src/taxonomy/core-filter-definitions-by-group.json
220.8 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
112.1 KiB ../packages/quill/packages/quill/dist/index.js
99.6 KiB src/lib/api.ts
93.6 KiB src/products.tsx
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
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
317.7 KiB ../node_modules/.pnpm/posthog-js@1.438.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
280.8 KiB src/taxonomy/core-filter-definitions-by-group.json
220.8 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
181.9 KiB src/queries/validators.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
112.1 KiB ../packages/quill/packages/quill/dist/index.js
99.6 KiB src/lib/api.ts
93.6 KiB src/products.tsx
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
317.7 KiB ../node_modules/.pnpm/posthog-js@1.438.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
316.0 KiB ../node_modules/.pnpm/posthog-js@1.438.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
280.8 KiB src/taxonomy/core-filter-definitions-by-group.json
220.8 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
181.9 KiB src/queries/validators.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
112.1 KiB ../packages/quill/packages/quill/dist/index.js
99.6 KiB src/lib/api.ts
93.6 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.24 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.24 MiB · 19 files 🔺 +967 B (+0.0%) ████░░░░░░ 39.2% of 5.72 MiB
Deferred (lazy) 2.19 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
865.5 KiB dist/toolbar/toolbar-app-NMNGBAOU.css
671.0 KiB dist/toolbar/chunk-chunk-DHA6A5DR.js
259.5 KiB dist/toolbar/chunk-chunk-DS74BCMD.js
138.9 KiB dist/toolbar/chunk-chunk-XAFTTSIR.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
75.2 KiB dist/toolbar/toolbar-app-46OTW7GA.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
35.6 KiB dist/toolbar/chunk-chunk-VW3VMDUI.js
21.7 KiB dist/toolbar/chunk-chunk-AKM7UIVI.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 — 🔺 +100.3 KiB (+0.0%)

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

Total: 1006.83 MiB · 🔺 +100.3 KiB (+0.0%)

ℹ️ MCP UI apps size — 32 app(s), 17214.8 KB JS

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

App JS CSS
debug 597.9 KB 203.4 KB
action 454.2 KB 203.4 KB
action-list 564.3 KB 203.4 KB
cohort 453.2 KB 203.4 KB
cohort-list 563.3 KB 203.4 KB
email-template 453.0 KB 203.4 KB
error-details 469.7 KB 203.4 KB
error-issue 454.6 KB 203.4 KB
error-issue-list 564.9 KB 203.4 KB
experiment 561.4 KB 203.4 KB
experiment-list 565.0 KB 203.4 KB
experiment-results 566.4 KB 203.4 KB
feature-flag 566.9 KB 203.4 KB
feature-flag-list 570.6 KB 203.4 KB
feature-flag-testing 457.4 KB 203.4 KB
inline-scan 453.7 KB 203.4 KB
insight-actors 562.4 KB 203.4 KB
llm-costs 559.4 KB 203.4 KB
session-recording 455.4 KB 203.4 KB
survey 454.8 KB 203.4 KB
survey-global-stats 562.0 KB 203.4 KB
survey-list 565.0 KB 203.4 KB
survey-stats 562.0 KB 203.4 KB
trace-span 453.6 KB 203.4 KB
trace-span-list 564.2 KB 203.4 KB
vision-observation-list 563.4 KB 203.4 KB
workflow 453.5 KB 203.4 KB
workflow-list 563.6 KB 203.4 KB
loops-review 457.9 KB 203.4 KB
query-results 789.9 KB 203.4 KB
render-ui 873.5 KB 203.4 KB
visual-review-snapshots 458.0 KB 203.4 KB
ℹ️ MCP agent API — agent-facing tool changes

What this PR changes for agents, from the tool schema snapshots and tool definitions.

Tools changed (2):

Tool Params Scopes Annotations Schema chars
dashboard-create +group_titles 3,314 -> 3,671
dashboard-update +group_titles description changed 19,697 -> 20,827
⚠️ Django migration SQL — 1 new migration to review

We've detected new migrations on this PR. Review the SQL output for each migration:

products/dashboards/backend/migrations/0026_dashboardtile_group_key_badge.py

/home/runner/work/tmp/tool_cache/Python/3.14.7/x64/lib/python3.14/site-packages/anyio/from_thread.py:119: SyntaxWarning: 'return' in a 'finally' block
  return result
/home/runner/work/tmp/tool_cache/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-10-10T10:27:03.411457Z [error    ] Path must be a valid database or directory containing databases. [posthog.exceptions_capture] pid=24687 tid=139741633211264
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 "/home/runner/work/tmp/tool_cache/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.
/home/runner/work/tmp/tool_cache/Python/3.14.7/x64/lib/python3.14/site-packages/sshtunnel.py:1040: SyntaxWarning: 'return' in a 'finally' block
  return (ssh_host,
/home/runner/work/tmp/tool_cache/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;
--
-- Add field badge to dashboardtile
--
ALTER TABLE "posthog_dashboardtile" ADD COLUMN "badge" varchar(20) NULL;
--
-- Add field group_key to dashboardtile
--
ALTER TABLE "posthog_dashboardtile" ADD COLUMN "group_key" varchar(100) NULL;
COMMIT;

Last updated: 2026-10-10 10:27 UTC (228c382)

✅ 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

dashboards.0026_dashboardtile_group_key_badge
  └─ #1 ✅ AddField
     Adding nullable field requires brief lock
     model: dashboardtile, field: badge
  └─ #2 ✅ AddField
     Adding nullable field requires brief lock
     model: dashboardtile, field: group_key

📚 How to Deploy These Changes Safely

AddField:

This operation acquires a brief lock but doesn't rewrite the table.

Deployment uses lock timeouts with automatic retries, so lock contention will cause retries rather than connection pile-up.

Last updated: 2026-10-10 10:28 UTC (228c382)

ℹ️ ClickHouse migration SQL — 1 migration(s)

ClickHouse migration SQL per cloud environment

  • unset
    • all
      ALTER TABLE flag_evaluations_mv MODIFY QUERY
      SELECT
          uuid,
          event,
          properties,
          timestamp,
          team_id,
          distinct_id,
          created_at,
          person_id,
          -- inserted_at is the time this view processes the row, as in the native-JSON
          -- events MV. The sync_feature_flag_last_called checkpoint and the deletion
          -- sweeps need a row that ClickHouse consumes after their cutoff to fall after
          -- it. The Kafka message time is earlier by the consumer lag. The Distributed
          -- forward and the replication happen after this stamp, so those readers still
          -- need a buffer.
          now64() AS inserted_at,
          _timestamp,
          _offset,
          _partition
      FROM posthog_test.kafka_flag_evaluations
  • US, EU, DEV
    • medium
      ALTER TABLE flag_evaluations_mv MODIFY QUERY
      SELECT
          uuid,
          event,
          properties,
          timestamp,
          team_id,
          distinct_id,
          created_at,
          person_id,
          -- inserted_at is the time this view processes the row, as in the native-JSON
          -- events MV. The sync_feature_flag_last_called checkpoint and the deletion
          -- sweeps need a row that ClickHouse consumes after their cutoff to fall after
          -- it. The Kafka message time is earlier by the consumer lag. The Distributed
          -- forward and the replication happen after this stamp, so those readers still
          -- need a buffer.
          now64() AS inserted_at,
          _timestamp,
          _offset,
          _partition
      FROM posthog_test.kafka_flag_evaluations

@posthog

posthog Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

Comment thread products/dashboards/backend/api/dashboard.py
Comment thread products/dashboards/backend/api/dashboard.py Outdated
…group keys

Address review findings on the tile groups API.

- Load the widget with the tile when a PATCH marks widget tiles, so each marked tile no longer costs a query
- Reject NUL characters in group keys and group title keys with a 400, not a 500
- Validate every tile badge and group key before any write, so a bad value cannot leave earlier tiles saved

Generated-By: PostHog Desktop
Task-Id: a0588627-3b8c-4fc5-b331-716af1994715
@trunk-io

trunk-io Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

Comment thread products/dashboards/backend/api/dashboard.py Outdated

pauldambra commented Oct 9, 2026 •

Copy link
Copy Markdown
Member Author

Note

🤖 Automated comment by QA Swarm — not written by a human

Review of this layer by one stronger-model reviewer (opus) or a cheap first pass (sonnet). The same code was reviewed in #114783.

Verdict: ✅ APPROVE (round 2 @ cd34523)

No blocking findings. Every finding from round 1 and from the repo bot is fixed. One item is left for a person.

Key findings

  • 🟡 Fixed The widget loaded once per marked widget tile. The tile now loads with its widget.
  • 🟡 Fixed A NUL character in a group key or title key returned a 500. It now returns a 400.
  • 🟡 Fixed A bad badge or group key returned a 400 after earlier tiles and the group titles were saved. Every tile in the payload, including tiles that edit text, a button or a widget, is now checked before any write.
  • 🟢 Fixed Copying or moving a single tile to another dashboard used to keep its group_key but not the title, so the tile could join an unrelated group. It now drops the key and keeps the badge. Whole-dashboard duplication keeps both.
  • ⚪ NIT, not changed badge has blank=True, so the generated read type allows an empty string that the API rejects. Dropping it changes the migration and regenerates the API types.

Checked and clean

Migration 0026 (nullable, no default, no hot table), edit permission and tenancy on the PATCH path, partial updates, clearing a badge with null, copy and transfer of both fields, the analytics event carries no free text, and the MCP text.

Previous rounds (1)

round 1 @ c1c19dc: APPROVE, with the widget query, NUL key and partial save findings fixed in that round. The partial save fix missed tiles that also edit content, which round 2 closed.


Automated by QA Swarm — not a human review

The early check only covered tiles that edit display fields.
A tile that also edited text, a button or a widget could carry a bad badge or group key, and the 400 came after the dashboard and the content were saved.
Every tile in the payload is now checked before any write.

Generated-By: PostHog Desktop
Task-Id: a0588627-3b8c-4fc5-b331-716af1994715
…oved

A group belongs to a tile and its dashboard, and the group titles live on the dashboard.
Copying or moving one tile to another dashboard breaks that pairing, so the tile arrives without a group key.
Whole dashboard duplication keeps the key, because the titles are copied with the dashboard.
The badge is kept in every case.

Generated-By: PostHog Desktop
Task-Id: a0588627-3b8c-4fc5-b331-716af1994715
…ts types

The API only clears a badge with null, but blank=True made the generated types accept an empty string.
The column is new and unmerged, so the migration is edited in place.
Generated API and MCP types are regenerated.

Generated-By: PostHog Desktop
Task-Id: a0588627-3b8c-4fc5-b331-716af1994715
@pauldambra
pauldambra removed this pull request from stack #114812 October 10, 2026 10:10

Copy link
Copy Markdown
Member Author

Note

🤖 Automated comment by PR Shepherd — not written by a human

Closing this PR. The same commits now live in #114783, which keeps its link to the report on the issue. Layer 2 is #114811, now on top of #114783.

@pauldambra pauldambra closed this Oct 10, 2026
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.

1 participant