fix: dispatch metrics update for collection parent on project delete#6067
Conversation
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
Up to standards ✅🟢 Issues
|
84b6b6a to
6f830f0
Compare
|
Looks like that when a project is updated, for example when the parent is changes, the metrics aren't always updated as the parent is not always fetched from the DB resutling in an "always NONE" value for collection logic. Committed a possible fix for that. |
874fd69 to
732e3fd
Compare
There was a problem hiding this comment.
Pull request overview
Fixes stale metrics on parent collection projects when deleting child projects via the bulk delete endpoint by dispatching ProjectMetricsUpdateEvent for impacted parents after the SQL deletion completes.
Changes:
- Collect UUIDs of parent collection projects affected by bulk project deletion (excluding parents included in the delete set).
- Dispatch
ProjectMetricsUpdateEventfor those parents after the bulk deletion transaction. - Add unit tests to verify event dispatch behavior for parent collections (and non-dispatch when parent is also deleted).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/main/java/org/dependencytrack/persistence/ProjectQueryManager.java |
Collects impacted parent collection UUIDs and dispatches metrics update events post bulk-delete transaction. |
src/test/java/org/dependencytrack/persistence/ProjectQueryManagerTest.java |
Adds tests covering metrics update event dispatch for collection parents during bulk delete. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
fce1cdb to
fef6694
Compare
…lete When child projects are deleted via deleteProjectsByUUIDs, their parent collection project's metrics are now recalculated. Previously the stale aggregated metrics (vuln counts, risk scores, etc.) would persist on the collection project indefinitely after all children were removed. The single-delete path (recursivelyDelete) already handled this correctly; this aligns the bulk-delete path with the same behaviour. Signed-off-by: Valentijn Scholten <valentijnscholten@gmail.com>
Signed-off-by: Valentijn Scholten <valentijnscholten@gmail.com>
…gerTest.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: valentijnscholten <valentijnscholten@gmail.com> Signed-off-by: Valentijn Scholten <valentijnscholten@gmail.com>
recursivelyDelete captures project.getParent() before any deletions, but collectionLogic is not in the default fetch group. After numerous pm.deletePersistentAll calls modify PM state, the subsequent lazy load of collectionLogic on the parent silently returns null (normalised to NONE), so the ProjectMetricsUpdateEvent for the collection parent is never dispatched and stale metrics persist indefinitely. Fix by reloading the parent with the METRICS_UPDATE fetch group (which includes collectionLogic) before the deletion cascade begins. Signed-off-by: Valentijn Scholten <valentijnscholten@gmail.com>
…n bulk delete Signed-off-by: Valentijn Scholten <valentijnscholten@gmail.com>
fef6694 to
2e3bbdf
Compare
Problem
In my DT instance I am observing that collection projects without any children retain their metrics. Although I will probably update my integration scripts to remove these empty projects, I also think DT should handle this correctly w.r.t. the metrics.
When child projects are deleted, any parent collection project retains stale metrics indefinitely — showing non-zero vulnerability counts, risk scores, etc. even after the collection is empty.
Root cause
Two separate bugs, both in
ProjectQueryManager:1.
recursivelyDelete(single delete path)recursivelyDeletecapturesproject.getParent()before the cascade, butcollectionLogicis@PersistentwithoutdefaultFetchGroup = "true"— it is not eagerly loaded. By the timeparent.getCollectionLogic()is checked at the end of the method, the PM state has been heavily modified by cascadingpm.deletePersistentAllcalls (all joining the same outer transaction fromProjectResource). The lazy load silently fails,getCollectionLogic()normalisesnulltoNONE, the condition is false, andProjectMetricsUpdateEventis never dispatched for the parent collection.2.
deleteProjectsByUUIDs(bulk delete path)Uses bulk SQL to remove projects but never dispatches
ProjectMetricsUpdateEventfor parent collection projects at all.Fix
recursivelyDelete: Reload the parent with theMETRICS_UPDATEfetch group (which explicitly includescollectionLogic) before the deletion cascade begins, so the field is in memory and does not depend on lazy loading through a modified PM state.deleteProjectsByUUIDs: Before the SQL deletion runs, collect the UUIDs of any parent collection projects whose children are being deleted (excluding parents that are themselves in the delete set). After the transaction completes, dispatchProjectMetricsUpdateEventfor each of them.Why it does not self-correct
One might expect the periodic portfolio metrics recalculation to fix this automatically, but it explicitly skips collection projects (
PortfolioMetricsUpdateTask:179):This exclusion is intentional — collection project numbers are derived from their children, not counted independently at portfolio level, to avoid double-counting. The side effect is that a collection project's own
ProjectMetricsrecord is only updated when aProjectMetricsUpdateEventis explicitly dispatched for it.This exclusion is doubtful as I think collection projects also need to have their metrics updated periodically. But it's not straightforward to fix as projects can be nested multiple levels deep.
Tests
Three new tests added to
ProjectQueryManagerTest, following the existingMockedStatic<Event>+ArgumentCaptorpattern:testRecursivelyDeleteDispatchesMetricsUpdateForCollectionParent— verifies that single-deleting a child dispatches a metrics update event for the collection parent.testDeleteProjectsByUUIDsDispatchesMetricsUpdateForCollectionParent— verifies that bulk-deleting a child dispatches a metrics update event for the collection parent.testDeleteProjectsByUUIDsDoesNotDispatchMetricsUpdateWhenParentAlsoDeleted— verifies that no spurious event is dispatched when the parent is also included in the same delete batch.Disclaimer
Claude Code was used in the process of creating this PR.