feat(customer-analytics): account notes#60250
Conversation
Replaces the "Notebooks" count column in the Accounts table with a row-expansion affordance, matching the activity/explore UX. Expanding fetches the account's notebooks via the existing nested API and renders a compact list of titles linking to each notebook, with the last-modified author and relative timestamp. - New keyed kea logic (accountNotebooksLogic) that lazy-loads notebooks per account on mount - New AccountNotebooksExpansion component handling loading / empty / list states - Wire LemonTable.expandable into AccountsTable, gating the chevron on accounts that actually have linked notebooks Generated-By: PostHog Code Task-Id: d848b1e8-2a74-42ad-abc4-630008cfa895 � Conflicts: � products/customer_analytics/frontend/components/Accounts/AccountsTable.tsx
Drop the rowExpandable gate that hid the chevron whenever an account had no linked notebooks. The expansion already renders an empty state for that case, and matching the /activity/explore UX means the affordance is always present regardless of whether content exists yet. Generated-By: PostHog Code Task-Id: d848b1e8-2a74-42ad-abc4-630008cfa895
…eview table Switch the expanded account row from a bare list to an embedded LemonTable that mirrors the CRM-style "Notes" panel pattern: title with a content preview snippet (truncated from text_content), the creator with their profile picture, and the creation timestamp via TZLabel. Empty content renders an "No content yet" affordance; an account with no notebooks shows the table's empty state. All visual elements come from the design system (LemonTable, Link, ProfilePicture, TZLabel) so it inherits PostHog's typography, spacing, and timezone handling. Generated-By: PostHog Code Task-Id: d848b1e8-2a74-42ad-abc4-630008cfa895
…wn text_content Agents calling the `accounts-notebooks-create` MCP tool almost always supply a plain `text_content` string and skip `content` (the ProseMirror JSON tree), since hand-writing valid ProseMirror is awkward. The result was a notebook that previewed fine in the accounts-table expansion but rendered as a blank page in the standalone NotebookScene, which only reads `content`. Treat `text_content` as Markdown and synthesize a TipTap doc from it in `AccountNotebookViewSet.perform_create` when the caller did not supply a usable `content` tree. Reuse the existing well-tested parser `markdown_to_tiptap_nodes` from `ee/hogai/tools/create_notebook/tiptap.py` (`tach.toml` already allows `products.customer_analytics` to depend on `ee`). Also update the `accounts-notebooks-create` MCP tool description to direct agents to supply Markdown via `text_content` and only set `content` when they already have a ProseMirror tree, which is the more agent-friendly contract. Generated-By: PostHog Code Task-Id: d848b1e8-2a74-42ad-abc4-630008cfa895
…eadcrumbs back to accounts
Opening an account notebook from the accounts table sent the user back to the
global Notebooks list when they clicked the breadcrumb arrow, which is the wrong
mental model — these notebooks live in Customer analytics, not the Notebooks
section.
Add a `parent_resource` field to `NotebookSerializer` that surfaces the
notebook's owning resource (`{type: 'account', id: <uuid>}` for account notes,
`null` for standalone notebooks). The frontend `notebookSceneLogic` uses it to
swap the leading breadcrumb to "Accounts" pointing at the Customer analytics
accounts list whenever the notebook is an account note.
The hand-written `NotebookType` (`frontend/src/scenes/notebooks/types.ts`) is
what `api.notebooks.get` returns, so it's extended directly — no generated-types
regeneration required to compile, and Orval will pick the new field up on the
next `hogli build:openapi` run.
The schema enum starts at `["account"]`; group-owned notebooks already exist in
the model (`RELATED_OBJECTS = ("group", "account")`) and can be added when we
ship a similar UX for them.
Generated-By: PostHog Code
Task-Id: d848b1e8-2a74-42ad-abc4-630008cfa895
|
🎭 Playwright didn't run on this PR — your changes touch code that could affect E2E behavior, but Playwright is opt-in via label now to keep CI cost down. Add the Most PRs don't need this. Real regressions still get caught on master and fix-forward. |
MCP UI Apps size report
|
|
Size Change: +4.58 kB (+0.01%) Total Size: 80.2 MB 📦 View Changed
ℹ️ View Unchanged
|
Prompt To Fix All With AIFix the following 4 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 4
products/notebooks/backend/api/notebook.py:185-191
**N+1 query on notebook list endpoint**
`get_parent_resource` fires one query per notebook because `obj.resources` is a reverse FK relation and the `safely_get_queryset` path only does `select_related("created_by", "last_modified_by", "team")` — no `prefetch_related("resources")`. Every call to `GET /api/projects/{id}/notebooks/` will emit N extra queries for N notebooks in the result page.
### Issue 2 of 4
products/customer_analytics/frontend/components/Accounts/AccountNotebooksExpansion.tsx:77
**Permanent loading spinner on fetch error**
When `loadNotebooks` rejects, kea-loaders sets `notebooksLoading` to `false` but leaves `notebooks` at its initial value of `null`. The condition `notebooks === null || notebooksLoading` then evaluates to `true` indefinitely, so the table shows a loading spinner forever instead of recovering. The toast fires correctly, but the component stays stuck in the loading state.
### Issue 3 of 4
products/customer_analytics/frontend/components/Accounts/accountNotebooksLogic.ts:24-26
**Unused `refresh` action**
The `refresh` action is declared but never connected to any listener (nothing calls `loadNotebooks` when it fires) and is never dispatched from any component. It has no effect and is dead code per the simplicity rule against superfluous parts.
### Issue 4 of 4
products/customer_analytics/backend/api/test_views.py:1128-1129
**Inconsistent import placement in new test methods**
The diff's explicit goal was to promote the `Notebook`/`ResourceNotebook` imports to the top of the class (lines 1009–1115 all had their local imports removed). The five new test methods (`test_create_derives_content_from_markdown_text_content` and its siblings) each re-introduce a local `from products.notebooks.backend.models import Notebook` import, undoing that consolidation. The top-level import already covers `Notebook`, so the local ones are redundant.
Reviews (1): Last reviewed commit: "chore: Convert local imports into global" | Re-trigger Greptile |
|
✅ Visual changes approved by @arthurdedeus — baseline updated in 5 changed, 4 new. |
9 updated Run: 836f1a83-da3b-4011-9dc9-509a3660d032 Co-authored-by: arthurdedeus <54866778+arthurdedeus@users.noreply.github.com>
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub. |
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
Problem
There is no way to review account notes.
Closes #57026
Changes
text_content, but not the markdowncontent(the notebook looked empty in TipTap)How did you test this code?
Unit tests and manually in the UI
👉 Stay up-to-date with PostHog coding conventions for a smoother review.
Publish to changelog?
No
Docs update
No
🤖 Agent context