feat(users): add UserPersonalization side table with avatar_url - #72086
feat(users): add UserPersonalization side table with avatar_url#72086adboio wants to merge 2 commits into
Conversation
New "Profile picture" row in General settings (flag-gated): preview via
UserAvatar, set/change with an https image URL, and remove. Saves to
the PostHog account through PATCH /api/users/@me/ {avatar_url} (new
updateCurrentUserAvatar client method; backend field lands in
PostHog/posthog#72086). UserAvatar/useUserAvatar props widen to a
structural AvatarUser so the full user object works too.
Generated-By: PostHog Code
Task-Id: 66aecb98-c55b-4516-a7aa-42c53cfa8bd2
ec9706d to
fffc818
Compare
🤖 CI report✅ Bundle size — no changeUncompressed size of every built Total: 64.22 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 budgetHow 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
🟢 Largest files eagerly shipped from
|
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 762 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 281.3 KiB | ../node_modules/.pnpm/posthog-js@1.404.0/node_modules/posthog-js/dist/rrweb.js |
| 267.7 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 235.5 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 223.2 KiB | ../node_modules/.pnpm/posthog-js@1.404.0/node_modules/posthog-js/dist/module.js |
| 164.0 KiB | src/queries/validators.js |
| 154.3 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 |
| 105.8 KiB | src/lib/api.ts |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 93.2 KiB | ../packages/quill/packages/quill/dist/index.js |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479
✅ Dist folder size — 🔺 +1.1 KiB (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1341.50 MiB · 🔺 +1.1 KiB (+0.0%)
⚠️ Backend snapshots — 1 updated (1 modified, 0 added, 0 deleted)
Query snapshots: Backend query snapshots updated
Changes: 1 snapshots (1 modified, 0 added, 0 deleted)
What this means:
- Query snapshots have been automatically updated to match current output
- These changes reflect modifications to database queries or schema
Next steps:
- Review the query changes to ensure they're intentional
- If unexpected, investigate what caused the query to change
⚠️ MCP snapshots — 2 updated (2 modified, 0 added, 0 deleted)
Snapshots: MCP unit test snapshots updated
Changes: 2 snapshots (2 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:
posthog/migrations/1259_userpersonalization.py
BEGIN;
--
-- Create model UserPersonalization
--
CREATE TABLE "posthog_userpersonalization" ("id" uuid NOT NULL PRIMARY KEY, "avatar_url" varchar(800) NULL, "created_at" timestamp with time zone NOT NULL, "updated_at" timestamp with time zone NOT NULL, "user_id" integer NOT NULL UNIQUE);
--
-- Add FOREIGN KEY constraint posthog_userpersonalization_user_id_fk on userpersonalization (NOT VALID)
--
ALTER TABLE "posthog_userpersonalization" ADD CONSTRAINT "posthog_userpersonalization_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "posthog_user" ("id") DEFERRABLE INITIALLY DEFERRED NOT VALID;
COMMIT;posthog/migrations/1260_validate_userpersonalization_fk.py
BEGIN;
--
-- Validate constraint posthog_userpersonalization_user_id_fk on userpersonalization
--
SET lock_timeout = 0;
SET statement_timeout = 0;
ALTER TABLE "posthog_userpersonalization" VALIDATE CONSTRAINT "posthog_userpersonalization_user_id_fk";
COMMIT;Last updated: 2026-07-17 23:54 UTC (1f8bcfa)
✅ Django migration risk — migration analysis complete
We've analyzed your migrations for potential risks.
Summary: 0 Safe | 2 Needs Review | 0 Blocked
⚠️ Needs Review
May have performance impact
posthog.1259_userpersonalization
└─ #1 ✅ CreateModel
Creating new table is safe
model: UserPersonalization
└─ #2 ⚠️ AddForeignKeyNotValid: Unknown operation type: AddForeignKeyNotValid
│
└──> ℹ️ INFO:
ℹ️ Skipped operations on newly created tables (empty tables
don't cause lock contention).
posthog.1260_validate_userpersonalization_fk
└─ #1 ⚠️ ValidateForeignKey: Unknown operation type: ValidateForeignKey
Last updated: 2026-07-17 23:54 UTC (1f8bcfa)
f592fe7 to
39fe807
Compare
8ff71ae to
8768442
Compare
Store the profile picture on a new UserPersonalization side table (one row per user) instead of a column on the hot posthog_user table. Readable and writable (https-only) via /api/users/@me/, and exposed on the org members endpoint through a select_related so teammates' avatars ride the members list without touching UserBasic. The user FK is declared db_constraint=False, added NOT VALID in 1259, and validated in 1260, so no migration blocks posthog_user traffic. Registered in the user-scoped IDOR semgrep rules.
8768442 to
bf55fff
Compare
Problem
PostHog apps have no real profile photos for users — every avatar surface falls back to Gravatar (which most accounts don't have) or initials. PostHog Code is adding a generic
UserAvatarcomponent that reads a profile photo off the API, but there's nowhere to store one. An earlier revision of this PR added a column toposthog_user; that's a hot table, so the field now lives on a side table instead and the core user model is untouched.Changes
UserPersonalizationmodel: one row per user (OneToOneField), currently holdingavatar_url, with room for future per-user appearance/profile preferences. Follows the existing user-keyed side-table pattern (UserProductList,UserScenePersonalisation,UserHomeSettings).avatar_urlreadable and writable (https-only) on/api/users/@me/— writes upsert the side row,posthog_useris never written.OrganizationMemberSerializer.avatar_url) via aselect_relatedon the shared members queryset, so clients resolve teammates' avatars from the members list.UserBasicSerializeris deliberately untouched — no payload changes ripple through other endpoints, and no SQL snapshots move.CREATE TABLE; no hot-table acknowledgment or coordinated deploy needed.Stacked follow-up: #72092 backfills staff avatars from posthog.com/people. PostHog Code consumes this in PostHog/code#3549.
How did you test this code?
Automated tests added (not run locally — this environment has no dev stack, so CI is the arbiter):
posthog/api/test/test_user_avatar_url.py— set/remove via PATCH@me(asserting the side row), default-null read without a row, https-only rejection, and exposure on the org members list. Catches the upsert path breaking, the field becoming read-only, or the members exposure regressing.Not run locally:
hogli build:openapiand mypy — expecting the CI bots/checks to flag anything needed.Automatic notifications
Docs update
N/A — API reference flows from serializer
help_text.🤖 Agent context
Human-driven (agent-assisted) — requested by Adam Bowker in the project-bluebird channel (PostHog Code); reworked from a
posthog_usercolumn to a side table at his direction.Created with PostHog Code