UN-3741 [FIX] Show created/modified metadata on adapter and Prompt Studio list pages#2182
Conversation
…tudio list pages Adds an 'Updated <relative time>' hint plus an info icon with hover tooltip (created at, last modified, model / prompt count when present) to the shared ListView widget used by the adapter and Prompt Studio list pages. Backend: expose created_at/modified_at on AdapterListSerializer, and surface the latest prompt modified_at on the Prompt Studio list — prompt edits never touch the CustomTool row, so its own modified_at understates activity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdapter and Prompt Studio list APIs now expose creation and effective modification timestamps. The frontend list view renders update metadata, tooltips, action states, and constrained row layouts. ChangesList metadata flow
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ListView
participant PromptStudioCoreView
participant ToolStudioPrompt
participant CustomToolListSerializer
ListView->>PromptStudioCoreView: Request list data
PromptStudioCoreView->>ToolStudioPrompt: Query latest prompt modified_at
ToolStudioPrompt-->>PromptStudioCoreView: Return latest timestamp
PromptStudioCoreView->>CustomToolListSerializer: Serialize effective metadata
CustomToolListSerializer-->>ListView: Return created_at and modified_at
ListView-->>ListView: Render updated time and metadata tooltip
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
frontend/src/components/widgets/list-view/ListView.jsx (1)
139-144: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the metadata tooltip keyboard-accessible.
Ant Design's
Tooltiprelies on its child element being focusable to support keyboard navigation. Because Ant Design icons render as plain<span>elements by default, they do not receive keyboard focus unless explicitly instructed.Consider adding
tabIndex={0}to the icon so users navigating via keyboard can trigger and read the item metadata.♿ Proposed accessibility fix
<Tooltip title={renderItemMetadata(item)}> <InfoCircleOutlined + tabIndex={0} className="adapters-list-info-icon" aria-label="Creation and modification details" /> </Tooltip>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@frontend/src/components/widgets/list-view/ListView.jsx` around lines 139 - 144, Make the metadata Tooltip keyboard-accessible by adding tabIndex={0} to the InfoCircleOutlined icon used as its child, preserving the existing tooltip content and icon behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@backend/prompt_studio/prompt_studio_core_v2/tests/test_prompt_studio_author.py`:
- Line 104: Update the test setup around the CustomTool and ToolStudioPrompt
timestamp assertions to explicitly backdate the tool via its update operation,
bypassing auto_now and guaranteeing tool.modified_at is earlier than
prompt.modified_at. Keep the strict comparison and endpoint result assertion so
the test verifies the backend selects the prompt timestamp rather than relying
on coincidental row-creation timing.
---
Nitpick comments:
In `@frontend/src/components/widgets/list-view/ListView.jsx`:
- Around line 139-144: Make the metadata Tooltip keyboard-accessible by adding
tabIndex={0} to the InfoCircleOutlined icon used as its child, preserving the
existing tooltip content and icon behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fbba598d-3cd9-478a-909f-fdeb8bbd2d9b
📒 Files selected for processing (6)
backend/adapter_processor_v2/serializers.pybackend/prompt_studio/prompt_studio_core_v2/serializers.pybackend/prompt_studio/prompt_studio_core_v2/tests/test_prompt_studio_author.pybackend/prompt_studio/prompt_studio_core_v2/views.pyfrontend/src/components/widgets/list-view/ListView.cssfrontend/src/components/widgets/list-view/ListView.jsx
|
| Filename | Overview |
|---|---|
| backend/adapter_processor_v2/serializers.py | Adds created_at and modified_at to AdapterListSerializer.Meta.fields — minimal, additive change. |
| backend/adapter_processor_v2/tests/test_adapter_api.py | New test pins that list responses include timestamps and a rename advances modified_at while created_at remains stable; uses backdate+update() to avoid wall-clock dependency. |
| backend/prompt_studio/prompt_studio_v2/models.py | Adds _touch_tool() / save() / delete() overrides to ToolStudioPrompt; each prompt write now fires an extra UPDATE to CustomTool. Bulk queryset writes (update/bulk_create) still bypass the hook — acknowledged in comments. |
| backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py | Fixes sync_prompts to explicitly bump CustomTool.modified_at when the queryset delete removes rows (bypasses _touch_tool()), and correctly extracts only the prompt-row count from the cascade delete result. |
| backend/prompt_studio/prompt_studio_core_v2/views.py | Removes the correlated modified_at subquery (no longer needed since parent row is bumped at source); adds a comment warning that future write paths must bump CustomTool themselves. |
| backend/prompt_studio/prompt_studio_core_v2/tests/test_prompt_studio_author.py | Adds five integration tests covering prompt-write bump, isolation between tools, sync-clear bump, org-context-free bump, and list-endpoint consistency. All use explicit backdate to avoid wall-clock fragility. |
| frontend/src/components/widgets/list-view/ListView.jsx | Decomposes the list-row rendering into renderTitle, renderMeta, and renderActions; adds showModified prop, owner email ellipsis, and info-icon tooltip via renderItemMetadata. |
| frontend/src/helpers/GetStaticData.js | New timeAgo() helper: strict ISO 8601 parsing returns "" for invalid input; clamps to now via moment.min to suppress "in a few seconds" from a fast server clock. |
| frontend/src/components/widgets/list-view/ListView.css | Replaces fixed-width column layout with three-column Flexbox; adds .list-view-modified-container and .list-view-info-icon styles. |
| frontend/src/components/custom-tools/list-of-tools/ListOfTools.jsx | Edit-item update now merges PATCH response over existing item ({...item, ...data}) so list-only fields like prompt_count aren't dropped; adds showModified to the list view. |
| frontend/src/components/custom-tools/view-tools/ViewTools.jsx | Pass-through of new showModified prop to ListView; PropTypes updated accordingly. |
| frontend/src/components/tool-settings/tool-settings/ToolSettings.jsx | Enables the new showModified display on the Adapter list by passing the prop to ViewTools. |
Reviews (11): Last reviewed commit: "UN-3741 [MISC] Make code comments concis..." | Re-trigger Greptile
Restructure ListView rows into three vertically-centered columns (title+description, owner+updated, actions) so the metadata cluster centers against the whole entry instead of hugging the title line. Consistent flex gaps replace the old width-percentage layout; row padding bumped for breathing room. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@frontend/src/components/widgets/list-view/ListView.jsx`:
- Around line 121-126: Update the created-by display expression in ListView to
access the current session email safely when comparing against
item.created_by_email. Use optional chaining on sessionDetails while preserving
the existing “Me”, email, and “-” display behavior.
- Around line 156-184: Update the EditOutlined and ShareAltOutlined onClick
handlers in the list item action rendering to ignore clicks when
item?.is_deprecated is true, while preserving the existing handleEdit and
handleShareClick behavior for active items.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 788871d5-2ed0-4361-98f3-5716c21da620
📒 Files selected for processing (2)
frontend/src/components/widgets/list-view/ListView.cssfrontend/src/components/widgets/list-view/ListView.jsx
Full text remains available via the ellipsis tooltip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ec32691 to
2be21c9
Compare
Reconciles UN-2202 co-owner changes with the UN-3741 list metadata: renderOwnerBadge (membership model, +N co-owners) grafted into the restructured three-column row; serializer keeps both field sets. List test now creates the project via the API so ownership membership rows exist under the new visibility model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…recated actions - Pin the prompt's modified_at to a known future instant in the list test so back-to-back creates can't tie on coarse clocks (Greptile P2) - Block edit/share click handlers on deprecated items to match their disabled styling (CodeRabbit) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ritwik-g
left a comment
There was a problem hiding this comment.
Review — UN-3741 list metadata (no P1s; 3 P2, 3 P3 inline)
Severity: P1 must fix / P2 should fix / P3 optional. Every P2 was independently verified against the code (details in each inline comment).
Checked and cleared:
- Backend annotation/serializer pairing is sound:
CustomToolListSerializeris only returned for thelistaction, andget_querysetalways annotates_last_prompt_modifiedfor it; the datetime comparison is tz-aware-safe; aSerializerMethodFieldshadowing aMeta.fieldsmodel field is valid DRF. - All four ListView consumers (adapters, Prompt Studio, Workflows, Connectors) get both timestamps from their list serializers — no page silently loses the new meta column. The removed CSS classes (
list-view-content,adapters-list-title-container,list-item-desc) have no other references. moment(...)/formattedDateTimeparse DRF's ISO-8601 output cleanly (noDATETIME_FORMAToverride,USE_TZ=True) — no "Invalid date" path.- Prompt reorders / bulk ops do bump
modified_at:BaseModelQuerySetpatchesupdate()/bulk_update()(utils/models/base_model.py:40-54) andToolStudioPromptinherits that manager — so the "honest last modified" holds for those paths too. - No existing tests were modified or deleted; the new test genuinely exercises the list-action → annotation → serializer path and fails without the fix.
- CodeRabbit's "critical"
sessionDetailsrender-crash is a false positive —ListView.jsx:85already usessessionDetails?.emailand this PR doesn't touch that line. The deprecated-icon onClick behavior it flagged as major is pre-existing code moved verbatim, out of this PR's scope.
P3 (not inline): title type — this is customer-requested new metadata display, arguably [FEAT] rather than [FIX] per the release-notes standard; author's call since it also corrects the misleadingly stale modified_at.
ritwik-g
left a comment
There was a problem hiding this comment.
No blocking issues — zero P1s. The three P2s in my review (silently-ignored ?ordering=modified_at, silent stale fallback in get_modified_at, dead centered prop) are latent/hardening/cosmetic and fine as a follow-up. Backend annotation path, all four ListView consumers, and the new test were verified against the PR head.
ritwik-g
left a comment
There was a problem hiding this comment.
Re-approving after re-examining the ordering finding: pre-PR, ?ordering=modified_at only worked for org admins (regular users 500'd on the distinct("tool_id") queryset) and no client sends it — confirmed non-blocking, details in the thread on serializers.py:49. All other findings remain follow-up material (no P1s).
jaseemjaskp
left a comment
There was a problem hiding this comment.
PR Review Toolkit — Code Reviewer, Silent Failure Hunter, Type Design Analyzer, PR Test Analyzer, Comment Analyzer and Code Simplifier. 14 findings below; items already raised by other reviewers are excluded.
The feature is well-motivated and the latest-prompt subquery is the right instinct — the _prompt_count mirroring is clean, the UN-2202 comment in the test documents a genuine trap, and the removed CSS classes were verified unreferenced. Three findings are blocking.
Blocking
-
ListView.jsx:176— the "Updated" block is presence-keyed, so it silently ships to Workflows and Connectors. Both serializers arefields = "__all__"overBaseModel, so both pages gain the label with no backend change and no test. On Workflows the value is structurally stale in exactly the way this PR fixes for Prompt Studio (content lives inToolInstance/WorkflowEndpoint, which never write theWorkflowrow) — a user reconfigures endpoints today and reads "Updated 8 months ago". On Connectors it's bumped by automatic OAuth token refresh (connector_auth_v2/models.py:114), so "Updated 2 minutes ago" appears on connectors nobody touched. Presence of a field isn't evidence of its meaning — make the opt-in explicit. -
views.py:173— the derived timestamp travels backwards on prompt delete.ToolStudioPrompthas no save/delete override and no signals, so deleting the newest prompt drops the tool's reportedmodified_atto the next-newest. The user performs an edit and the row ages by three days. -
serializers.py:74—modified_atmeans two different things on two endpoints.listreturns the derived max; every other action goes throughCustomToolSerializer(fields = "__all__") and returns the raw row. List then GET makes the timestamp jump backwards, with nothing on the wire to distinguish them.
One fix collapses all three, plus the ?ordering=modified_at breakage already reported: bump the parent row at the source (post_save/post_delete on ToolStudioPrompt → CustomTool.objects.filter(pk=...).update(modified_at=now())). modified_at becomes honest for creates, edits and deletes; both serializers agree because there's only one value; ordering works because it's a real column again; and the annotation, the SerializerMethodField and the view↔serializer coupling all disappear. If you'd rather keep deriving, computing the max in SQL via Greatest/Coalesce restores sortability — but note it does not fix the delete case (#2).
Low — a dead click strip through the middle of every row (ListView.jsx:170); the title doesn't ellipsize despite the comment, so long titles wrap and break row uniformity (ListView.css:59); the adapter fields ship with zero coverage of the list endpoint (adapter_processor_v2/serializers.py:173); the new test's single-tool/single-prompt fixture can't detect a lost OuterRef correlation or a flipped sort (test_prompt_studio_author.py:106).
Nits — dead showOwner guard, mixed presence conventions, eagerly-built tooltip, unguarded "Invalid date" rendering, moment/dayjs split, adapters-list-* prefix on generic classes, and a misleading min-width: 0 comment.
Also worth folding into the centered cleanup already raised: ListView.css:40-46 still targets .ant-list-item-meta, which no longer renders anywhere now that List.Item.Meta is gone — both rules are unreachable. (Outside the diff, so no inline comment.)
… Updated label Backend — maintain modified_at at the source instead of deriving it (review: derived max could travel backwards on prompt delete, and gave the same field two meanings across list/detail while breaking ?ordering=modified_at): - ToolStudioPrompt.save()/delete() now bump the parent CustomTool row; the list subquery annotation and SerializerMethodField are gone, so the plain field is honest, identical on every endpoint, and orderable - Tests pin create/edit/delete bumps, cross-tool isolation, and the promptless-project case; adapter list endpoint gains coverage (timestamps present, rename bumps modified_at, created_at stable) Frontend — the Updated label is now explicit opt-in (showModified), passed only by the adapter and Prompt Studio lists; Workflows and Connectors no longer inherit a misleading value by field presence: - New timeAgo helper (strict ISO parse, clamped to now) replaces raw moment usage: malformed input hides the label instead of rendering "Invalid date" - Meta column no longer swallows row clicks; titles ellipsize like descriptions; tooltip fields use one presence convention (!= null) and build lazily; centered prop wired into the row Flex (dead CSS removed); updateList merges PATCH responses so list-only fields survive edits; new CSS classes renamed to the widget's list-view-* prefix; two misleading CSS comments corrected Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…asses model delete sync_prompts clears existing prompts with a queryset delete, which never calls ToolStudioPrompt.delete() — so a prompts-clearing sync with no tool-settings payload left the parent's modified_at stale, violating the invariant the parent-bump establishes (Greptile). The sync now bumps the tool explicitly when rows were deleted; pinned by a test that runs a clearing sync and asserts the bump. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jaseemjaskp
left a comment
There was a problem hiding this comment.
Second pass. The parent-bump-at-source redesign is the right call and the backend tests are genuinely good — test_prompt_bump_only_touches_its_own_tool is the one most people omit, and it's the one that would catch a _touch_tool that lost its pk filter. Three findings that clear the bar; the rest of what I checked came back clean (details below).
Verified clean, so nobody re-treads them: reorder does bump (the bulk_update at prompt_studio_v2/helper.py:112 looks like a bypass, but reorder_prompts_helper finishes with prompt_instance.save() at helper.py:64); import_prompts uses objects.create() so the sync ADD path bumps via save(); the deleted CSS classes (.list-view-content, .adapters-list-title-container, .list-item-desc) have zero references left repo-wide; centered is still live and correctly rewired to the row Flex align; AdapterListSerializer leaks nothing (secrets live in adapter_metadata_b, never in that tuple), and the detail serializer already exposed both fields via fields = "__all__", so list is now a strict subset. No deadlock risk on the N-prompt import loop either — I looked for an inverted lock order and there isn't one.
…unt, scoped claim - _touch_tool and the sync bump now use CustomTool._base_manager: the org-scoped default manager reads a thread-local and would silently match zero rows outside request context; delete() collapsed onto _touch_tool (the FK attname survives super().delete()) so the two paths can't drift - sync_prompts reports the per-model prompt count, not the cascade total that dragged PromptStudioOutputManager rows into "prompts_deleted"; the sync test fixture now carries an output row so the assertion discriminates - List-queryset comment scopes the invariant honestly: only prompt writes bump; ProfileManager/DocumentManager and queryset-level prompt writes are tracked as UN-3759 Review: PR #2182 round 3 (Jaseem) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
chandrasekharan-zipstack
left a comment
There was a problem hiding this comment.
Consider making code comments concise and generic and removing the JIRA dependency if its not truly required
Ticket references belong in commits and PRs, not code — they mean nothing to OSS contributors. Comments and test docstrings now state the behavior generically; the ProfileManager/DocumentManager bump gap stays documented in the list-queryset comment without the tracker pointer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Frontend Lint Report (Biome)✅ All checks passed! No linting or formatting issues found. |
|
Unstract test resultsPer-group results
Critical paths
|
jaseemjaskp
left a comment
There was a problem hiding this comment.
Six-agent PR Review Toolkit pass (code review, silent-failure, type design, tests, comments, simplification) over the backend bump logic and the ListView refactor. No blocking findings — approving.
I verified the load-bearing claims independently rather than trusting them:
- Deletion collector — Django 4.2.30
deletion.pynulls onlymodel._meta.pk.attname(lines 456, 519-521);tool_id_idsurvivessuper().delete(), so the delete-bump works. Comment is accurate. _base_manager/ org scope —CustomToolModelManagermixes inDefaultOrganizationManagerMixin(thread-local scoped); nobase_manager_nameanywhere in the MRO, so_base_manageris a plain unfilteredManager.tool_idis a UUID pk. All three sub-claims hold, andtest_prompt_bump_survives_missing_org_contextgenuinely pins it.- Prompt reorder — flagged by one agent as bypassing the bump via
bulk_update; that is wrong.reorder_prompts_helperends inprompt_instance.save()(prompt_studio_v2/helper.py:64), which fires_touch_tool(). Reorder does bump. per_model.get(ToolStudioPrompt._meta.label, 0)—Collector.delete()keys by_meta.label; the0default is only hit at zero rows. Correct, and the sync test builds a real CASCADE fixture so the assertion actually discriminates 1 from 2.- Backdating helper — works only because
BaseModelQuerySet.updateusessetdefault; had it been an unconditional assign, every>assertion would be vacuous. It is not. Tests are honest. - CSS —
.list-view-content,.adapters-list-title-container,.list-item-deschave zero remaining references;centeredis still honored via the new Flexalign.
Non-blocking notes, no action needed for this PR:
views.py:971(.update(profile_manager=...)on first-profile creation) bypasses the bump, leaving a stale label on that one path. Pre-existing, display-only, self-healing on the next prompt edit, and theviews.py:167-171comment already warns about this class.- Source-bumping is prospective only — existing projects keep their current
modified_atuntil the next prompt write. Worth a thought, but the field never included prompt activity before, so this is strictly an improvement. sync_promptsfires N+2 writes to oneCustomToolrow per sync. Real write amplification, but prompts-per-tool is in the tens and sync is infrequent — not a throughput concern.timeAgo()is untested and the harness exists (vitest, siblinghelpers/requestId.test.js). The strict-parse andmoment.minclamp are exactly the non-obvious bits a future cleanup would strip. Cheap to add whenever.
One thing worth fixing outside the diff: the PR description is stale. It still says the Prompt Studio list returns modified_at as max(tool.modified_at, latest prompt.modified_at) via a correlated subquery, and cites a test test_list_modified_at_reflects_prompt_edits that no longer exists. The implementation moved to source-bumping. Worth updating before merge so the description matches what ships.



What
Updated <relative time>hint next to the owner, plus an info icon whose hover tooltip shows absolute Created / Modified times (browser timezone), the Model (adapters) and Prompt count (Prompt Studio).AdapterListSerializernow returnscreated_at/modified_at; the Prompt Studio list returnsmodified_atasmax(tool.modified_at, latest prompt.modified_at).Why
CustomToolrow, so exposing the rawmodified_atwould show misleadingly stale times on actively-edited projects — hence the latest-prompt subquery.How
ListViewwidget (frontend/src/components/widgets/list-view/), which the adapter, Prompt Studio, Workflows and Connectors pages all render through. Rendering is presence-keyed on the item fields, so pages whose APIs lack a field simply don't show it._prompt_countsubquery.Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)
modified_at, only its value becomes more accurate). The new UI block renders only whenmodified_atis present, so any consumer without the field renders exactly as before. Workflows and Connectors pages (same widget, serializers already return timestamps) gain the same display — verified to follow the same row anatomy.prompt_countfield, which counts all prompt rows including notes (pre-existing semantics, also consumed elsewhere).Database Migrations
BaseModel.created_at/modified_atcolumns).Env Config
Relevant Docs
Related Issues or PRs
Dependencies Versions
momentdependency).Notes on Testing
test_list_modified_at_reflects_prompt_edits(verified red on unpatched code, green with the fix); prompt studio suite 2/2 and adapter suite 22/22 against live Postgres.ruff(pinned 0.3.4) clean,biome checkclean,vite buildclean.ListViewcomponent rendered with representative data at standard width: rows stay single-line; long owner emails ellipsize with a full-address tooltip.Screenshots
_Screenshots to

be attached below (list view + hover tooltips for adapters and Prompt Studio)._
Checklist
I have read and understood the Contribution Guidelines.
🤖 Generated with Claude Code