Add per-user finding bookmarks and /me/queue/ API#4
Merged
Conversation
Backs the watercolor FE's "saved" heart, Queue screen, and nav badge.
- New `FindingBookmark` model (user + finding, unique pair, two indexes).
- New endpoints under `/api/v2/`:
- `GET /bookmarks/` list user's saved findings
- `POST /bookmarks/` idempotent save (201 / 200)
- `DELETE /bookmarks/{finding_id}/` idempotent un-save (204)
- `GET /bookmarks/count/` cheap badge count
- `GET /me/queue/` bookmarked ∪ reviewers-of-me, deduped
- `FindingCardSerializer` slim payload (id, title, severity, cvss_score,
age_days, scanner, sla_pct, due_in, tags, assignees, status, saved).
- Authorization reuses the fork's `get_authorized_findings(Finding_View)`
so stale bookmarks on now-inaccessible findings silently drop out.
- Migration `0265_findingbookmark` is additive only — no `ALTER` on
`finding`, no data backfill, reversible.
- Admin registration for support / debugging.
- DRF tests cover the four verification cases from the PRD: idempotent
create / list / delete, cross-product 403, stale-bookmark drop, and
union dedup.
PRD: https://github.com/tactivos/security_pipeline/blob/main/docs/agent-work/prd-fe-bookmarks-and-user-queue-api.md
Deviation from PRD: the upstream `Finding` model has no `assigned_to` FK;
assignment is modeled as `Finding.reviewers` (M2M to user). The queue
endpoint unions reviewers-of-me instead, and the card payload exposes
`assignees` as an array. The PRD's "Open Questions" section already
anticipated multi-assignee, so this is the natural mapping.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ruff `--fix` autofixes (docstring D2xx formatting, import sorting, unused noqas) plus seven manual touch-ups: - dojo/api_v2/serializers.py: replace bare `except: pass` in `FindingCardSerializer.get_scanner` with a `logger.debug` (S110). - dojo/api_v2/views.py: replace ambiguous Unicode `×` and `∪` in docstrings / OpenAPI summaries with ASCII (RUF001/RUF002); convert truthy-string membership check to a set literal (PLR6201); collapse the queue-union if/else to a ternary (SIM108). - unittests/test_apiv2_bookmarks.py: drop the unused local binding before `return` in `_make_finding` (RET504). `ruff check .` now passes locally on this branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ngs-and-queue-api
PR #3 (dashboard aggregations) landed `0265_categorymapping` first, so this branch's migration is renumbered `0265_findingbookmark` -> `0266_findingbookmark` and its `dependencies` now points at `0265_categorymapping` to keep the Django migration graph linear. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the BE primitives the watercolor FE needs for the saved-heart, the Queue screen, and the nav-bar saved-count badge. Implements the PRD at https://github.com/tactivos/security_pipeline/blob/main/docs/agent-work/prd-fe-bookmarks-and-user-queue-api.md.
What ships
FindingBookmarkmodel (user + finding, unique pair, two named indexes; reversible additive migration0265_findingbookmark)./api/v2/:GET /bookmarks/— paged list of the requesting user's saved findings.POST /bookmarks/— idempotent save. 201 on first save, 200 if it already exists, 403 if the user can't access the finding.IntegrityErroron concurrent POSTs is caught and treated as "already exists".DELETE /bookmarks/{finding_id}/— idempotent un-save (204 either way).GET /bookmarks/count/— single indexed COUNT(*) for the nav badge.GET /me/queue/— union of bookmarked + reviewers-of-me, deduped, withorder_byin {severity, cvss, age, due_in} andassigned_only=trueskipping the bookmark side for the Home "Picked for you" rail.FindingCardSerializerslim card payload: id, title, severity, cve/cwe, cvss_score, created_at, age_days, scanner, sla_pct, due_in, tags, assignees, status, saved.get_authorized_findings(Permissions.Finding_View)so stale bookmarks on now-inaccessible findings silently drop out of list/queue/count results without deleting the row.FindingBookmarkfor support.unittests/test_apiv2_bookmarks.pycovering the four PRD verification cases.@extend_schemaannotations sodrf_spectacularpicks up the new endpoints; schema not regenerated (will pick up on nextmanage.py spectacular).Deviations from the PRD
Finding.assigned_todoes not exist on this fork. Upstream DefectDojo models assignment asFinding.reviewers(M2M to user). I implemented/me/queue/asbookmarked ∪ findings.reviewers__contains=meand exposedassigneesas an array of avatar chip objects on the card serializer. The PRD's "Open Questions" section already anticipated multi-assignee, so this is the natural mapping rather than a workaround.LimitOffsetPagination; the PRD specifies?page=&page_size=. I attached a per-viewsetPageNumberPaginationsubclass (BookmarkPagination) withpage_size=12,max_page_size=100, andpage_size_query_param=page_sizeso the two new viewsets match the PRD wire format without disturbing the rest of the API.DELETE /bookmarks/{finding_id}/useslookup_field = "finding_id"instead ofpk, since the FE only knows the finding id it wants to un-save and bookmark row ids are internal.Test plan
manage.py migrateapplies0265_findingbookmarkcleanly on a fresh DB.manage.py migrate dojo 0264rolls it back cleanly.manage.py test unittests.test_apiv2_bookmarkspasses (4 verification cases + count/list/assigned_only).manage.py spectacular --file schema.ymlproduces a schema that includes/api/v2/bookmarks/,/api/v2/bookmarks/count/, and/api/v2/me/queue/with response shapes.Product_Memberrow for a product, then GET/me/queue/: bookmarked findings from that product no longer appear; theFindingBookmarkrow is still in the DB.Notes for reviewer
BookmarkPagination, the helper_SEVERITY_RANK, and theFindingCardAssigneeSerializerchip palette are intentionally local to this feature — the rest of the API still uses limit/offset and the existingFindingSerializer.assignees.color_hexis a deterministic palette pick keyed onusernameso the FE can render avatar chips without a second API round-trip. If the FE ever wants this driven from user profile data instead, swap the palette for a profile field.🤖 Generated with Claude Code