commonlib: add Sites Tree Get Info context menu#7328
Conversation
Implements the long-standing request from zaproxy/zaproxy#3738 to surface a quick "what does this branch of the Sites tree actually contain" view without the user having to walk it manually. Lives in the commonlib add-on per the guidance from @thc202 on the closed zaproxy core PR — see the discussion on zaproxy/zaproxy#3738. The menu adds three pieces of info, computed on demand by walking the subtree rooted at the right-clicked node: - Total descendant count. - Most-recently-added descendant timestamp (from HistoryReference.getTimeSentMillis()). - Source-type breakdown via HrefTypeInfo.getFromType(int) — so the user can tell at a glance whether a branch came from passive proxying, the spider, AJAX spider, fuzzing, etc. This is the "how the newest node was found" slice of the original ask, but rolled up across the whole subtree so it's useful at any level. Walking on demand rather than maintaining counters per node is the right tradeoff here: the menu is only triggered explicitly, and even a 10k-node branch walks in well under a second. Avoids the wasted work of incrementally maintaining stats for a feature that's rarely used per session. Wiring follows the same pattern as the existing GenerateFixPromptMenu in this add-on: registered in ExtensionCommonlib.hook() inside the hasView() guard so headless mode is unaffected. Six new keys in Messages.properties (menu, title, body, lastadded.unknown, source.unknown, sources.none) plus a CHANGELOG entry under Unreleased. Tests: SitesTreeInfoMenuUnitTest covers the pure-data helpers (formatSources sort order, count formatting, separator semantics, SubtreeSummary defaults). The Swing dialog and the SiteNode tree walk itself need ZAP's full Sites tree initialised, so those are covered by manual verification rather than unit tests — same shape as the existing GenerateFixPromptMenu. Verified: ./gradlew :addOns:commonlib:test passes, :assemble succeeds, :spotlessJavaCheck clean.
|
Great job! No new security vulnerabilities introduced in this pull requestUse @Checkmarx to interact with Checkmarx PR Assistant. |
Two inline comments on the first review:
- Drop the explicit MessageFormat.format and use the
`Constant.messages.getString(key, args...)` overload directly.
The Constant.messages helper is already MessageFormat-backed,
so the manual wrap was redundant. Same i18n behaviour, two
fewer imports.
- Rewrite the CHANGELOG entry as user-facing rather than
dev-relevant. The internal HrefTypeInfo reference belongs in
the Javadoc (where it already is), not in a release note that
end users read. Adopted the wording @kingthorin suggested
verbatim, including the issue cross-link format.
|
Pushed the two inline fixes — On the EDT question — happy to add a SwingWorker if you'd rather, but I think we're already safe as-is:
If you'd still rather move the walk off the EDT, I can wrap |
|
It's preferable not tag in commit messages since that might end up generating unwanted notifications, in any case we all receive notifications of pushes and comments already. |
|
Pushed the CHANGELOG fix — "context menu item", link dropped. Noted on the commit-message style; I'll keep tags out of future commits. |
| import org.zaproxy.zap.view.popup.PopupMenuItemSiteNodeContainer; | ||
|
|
||
| /** | ||
| * Right-click menu item on the Sites tree that summarizes the selected node's subtree. |
There was a problem hiding this comment.
It's a context menu not right-cilck menu.
| DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z").withZone(ZoneId.systemDefault()); | ||
|
|
||
| public SitesTreeInfoMenu() { | ||
| super(Constant.messages.getString("commonlib.sitestree.info.menu"), true); |
There was a problem hiding this comment.
You are allowing multiple selections which means multiple popups in succession, was that on purpose?
| @Override | ||
| protected boolean isButtonEnabledForSiteNode(SiteNode siteNode) { | ||
| // Always available — even a leaf node has a meaningful (if trivial) summary. | ||
| return siteNode != null; | ||
| } |
There was a problem hiding this comment.
The siteNode is never null, the whole method should be removed.
| if (siteNode == null || !View.isInitialised()) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
The siteNode is never null here either, the view check is redundant this is a UI component.
| formatSources(summary.sourceCounts)); | ||
| JOptionPane.showMessageDialog( | ||
| View.getSingleton().getMainFrame(), | ||
| message, |
There was a problem hiding this comment.
Use ZapLabel to allow the user to copy the info shown.
| // HrefTypeInfo.toString() returns the localized display name (the | ||
| // internal `name` field has no getter), so it's the canonical way | ||
| // to read the type label. | ||
| String label = info != null ? info.toString() : null; | ||
| if (label == null || label.isEmpty()) { | ||
| label = Constant.messages.getString("commonlib.sitestree.info.source.unknown"); | ||
| } |
There was a problem hiding this comment.
The info is never null, while the label can be it's not in practice, this whole logic can be removed.
| summary.sourceCounts.merge(label, 1, Integer::sum); | ||
| } | ||
|
|
||
| Enumeration<javax.swing.tree.TreeNode> children = node.children(); |
There was a problem hiding this comment.
Do import, why LLMs like to do this.
| class SitesTreeInfoMenuUnitTest { | ||
|
|
||
| @Test | ||
| void formatSourcesSortsByCountDescThenLabelAsc() { |
There was a problem hiding this comment.
It would be preferable to follow the existing naming conventions.
|
|
||
| ## Unreleased | ||
| ### Added | ||
| - A "Get Info" context menu item on the Sites tree that summarizes the selected node's subtree (total node count, most-recent addition, breakdown by source type). |
| /** | ||
| * Unit tests for the pure-data helpers on {@link SitesTreeInfoMenu}. The Swing dialog and the | ||
| * SiteNode tree-walk are exercised by manual verification because they require ZAP's full Sites | ||
| * tree to be initialised; the helpers below are decoupled enough to test in isolation. | ||
| */ | ||
| class SitesTreeInfoMenuUnitTest { |
There was a problem hiding this comment.
This is no correct, it does not require full sites tree.

Implements the long-standing request from zaproxy/zaproxy#3738 — surface a quick "what does this branch of the Sites tree actually contain" view without the user having to walk it manually.
Re-targeted here on @thc202's guidance on the original PR (zaproxy/zaproxy#9323): commonlib add-on, leveraging core's
org.zaproxy.zap.view.HrefTypeInfofor the source-type breakdown.What it does
Right-clicking any node on the Sites tree → "Get Info" pops a dialog with three pieces of info, computed on demand by walking the subtree rooted at that node:
HistoryReference.getTimeSentMillis()).HrefTypeInfo.getFromType(int)— Proxy, Spider, AJAX Spider, Fuzzer, etc. Sorted by count desc with deterministic tiebreaks. This is the "how the newest node was found" slice of the original ask, but rolled up across the whole subtree so it's useful at any level.Design choices worth flagging
HrefTypeInfo.toString()for the label. The internalnamefield has no public getter;toString()returns the localized display name, which is what we want here. Documented inline.HistoryReference(e.g. a synthetic parent) is counted in the node total but contributes nothing to the source histogram — the histogram sums to ≤ totalNodes, never above.Wiring
Same shape as the existing
GenerateFixPromptMenuin this add-on: registered inExtensionCommonlib.hook()inside thehasView()guard so headless mode is unaffected. Six new keys inMessages.properties(menu, title, body, lastadded.unknown, source.unknown, sources.none). CHANGELOG entry under Unreleased / Added.Tests
SitesTreeInfoMenuUnitTestcovers the pure-data helpers —formatSourcessort order (count desc → label asc), count formatting, separator semantics,SubtreeSummarydefaults. The Swing dialog and theSiteNodetree walk itself need ZAP's full Sites tree initialised, so those are covered by manual verification rather than unit tests, same shape as howGenerateFixPromptMenuis verified.Test plan
:testgreen:assembleclean:spotlessJavaCheckclean