From e77bb57a5f97a6d33996dc520cc69739d72128ab Mon Sep 17 00:00:00 2001 From: Steve Wall Date: Tue, 30 Jun 2026 09:22:33 -0600 Subject: [PATCH] fix: relabel finding Asset-tag (AND) filter to v3 Asset vocabulary The v3 Product->Asset relabel (#13155) centralises filter copy in dojo/asset/labels.py, gated by ENABLE_V3_ORGANIZATION_ASSET_RELABEL (default on). The Asset-level AND tag filter on the finding list (test__engagement__product__tags_and) was left with a hardcoded "Product Tags (AND)" label and help text, so it kept the legacy wording even with the relabel enabled -- inconsistent with its siblings "Test Tags (AND)" / "Engagement Tags (AND)" and with the OR variant, whose label is already set dynamically to "Tags (Asset)". Wire it to the edition-aware labels by adding ASSET_FILTERS_TAGS_ASSET_AND_LABEL and ASSET_FILTERS_TAGS_ASSET_AND_HELP to both vocabulary branches, so the field reads "Asset Tags (AND)" in v3 and remains "Product Tags (AND)" in the legacy edition. Adds a regression test asserting the rendered FindingFilter form field. Co-Authored-By: Claude Opus 4.8 --- dojo/asset/labels.py | 6 ++++ dojo/filters.py | 4 +-- unittests/test_filter_asset_tag_labels.py | 38 +++++++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 unittests/test_filter_asset_tag_labels.py diff --git a/dojo/asset/labels.py b/dojo/asset/labels.py index 9061d6b05bf..d634d61ac06 100644 --- a/dojo/asset/labels.py +++ b/dojo/asset/labels.py @@ -68,6 +68,8 @@ class AssetLabelsKeys: ASSET_FILTERS_CSV_TAGS_NOT_HELP = "asset.filters.csv_tags_not_help" ASSET_FILTERS_CSV_LIFECYCLES_LABEL = "asset.filters.csv_lifecycles_label" ASSET_FILTERS_TAGS_ASSET_LABEL = "asset.filters.tags_asset_label" + ASSET_FILTERS_TAGS_ASSET_AND_LABEL = "asset.filters.tags_asset_and_label" + ASSET_FILTERS_TAGS_ASSET_AND_HELP = "asset.filters.tags_asset_and_help" ASSET_FILTERS_TAG_ASSET_LABEL = "asset.filters.tag_asset_label" ASSET_FILTERS_TAG_ASSET_HELP = "asset.filters.tag_asset_help" ASSET_FILTERS_NOT_TAGS_ASSET_LABEL = "asset.filters.not_tags_asset_label" @@ -175,6 +177,8 @@ class AssetLabelsKeys: AssetLabelsKeys.ASSET_FILTERS_CSV_TAGS_NOT_HELP: _("Comma separated list of exact tags not present on Asset"), AssetLabelsKeys.ASSET_FILTERS_CSV_LIFECYCLES_LABEL: _("Comma separated list of exact Asset lifecycles"), AssetLabelsKeys.ASSET_FILTERS_TAGS_ASSET_LABEL: _("Asset Tags"), + AssetLabelsKeys.ASSET_FILTERS_TAGS_ASSET_AND_LABEL: _("Asset Tags (AND)"), + AssetLabelsKeys.ASSET_FILTERS_TAGS_ASSET_AND_HELP: _("Filter Findings by the selected Asset tags (AND logic)"), AssetLabelsKeys.ASSET_FILTERS_TAG_ASSET_LABEL: _("Asset Tag"), AssetLabelsKeys.ASSET_FILTERS_TAG_ASSET_HELP: _("Search for tags on an Asset that are an exact match"), AssetLabelsKeys.ASSET_FILTERS_NOT_TAGS_ASSET_LABEL: _("Not Asset Tags"), @@ -281,6 +285,8 @@ class AssetLabelsKeys: AssetLabelsKeys.ASSET_FILTERS_CSV_TAGS_NOT_HELP: _("Comma separated list of exact tags not present on Product"), AssetLabelsKeys.ASSET_FILTERS_CSV_LIFECYCLES_LABEL: _("Comma separated list of exact Product lifecycles"), AssetLabelsKeys.ASSET_FILTERS_TAGS_ASSET_LABEL: _("Product Tags"), + AssetLabelsKeys.ASSET_FILTERS_TAGS_ASSET_AND_LABEL: _("Product Tags (AND)"), + AssetLabelsKeys.ASSET_FILTERS_TAGS_ASSET_AND_HELP: _("Filter Findings by the selected Product tags (AND logic)"), AssetLabelsKeys.ASSET_FILTERS_TAG_ASSET_LABEL: _("Product Tag"), AssetLabelsKeys.ASSET_FILTERS_TAG_ASSET_HELP: _("Search for tags on an Product that are an exact match"), AssetLabelsKeys.ASSET_FILTERS_NOT_TAGS_ASSET_LABEL: _("Not Product Tags"), diff --git a/dojo/filters.py b/dojo/filters.py index 512d3aa0068..7fb211ee282 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -616,8 +616,8 @@ class FindingTagFilter(DojoFilter): field_name="test__engagement__product__tags__name", to_field_name="name", queryset=Product.tags.tag_model.objects.all().order_by("name"), - help_text="Filter Findings by the selected Product tags (AND logic)", - label="Product Tags (AND)", + help_text=labels.ASSET_FILTERS_TAGS_ASSET_AND_HELP, + label=labels.ASSET_FILTERS_TAGS_ASSET_AND_LABEL, conjoined=True, ) diff --git a/unittests/test_filter_asset_tag_labels.py b/unittests/test_filter_asset_tag_labels.py new file mode 100644 index 00000000000..19eef874e14 --- /dev/null +++ b/unittests/test_filter_asset_tag_labels.py @@ -0,0 +1,38 @@ +from dojo.filters import FindingFilter +from dojo.models import Finding +from unittests.dojo_test_case import DojoTestCase + + +class FindingTagFilterAssetLabelTest(DojoTestCase): + + """ + Regression test for the v3 Product->Asset relabel of the Asset-level AND tag + filter on the finding list. + + The relabel (#13155, gated by ENABLE_V3_ORGANIZATION_ASSET_RELABEL, default on) + moves filter copy into dojo/asset/labels.py. The OR variant + (test__engagement__product__tags) gets its label set dynamically at render time + by get_tags_label_from_model() -> "Tags (Asset)", so it was always fine. But the + AND variant (test__engagement__product__tags_and) is static and was left + hardcoded "Product Tags (AND)", so it rendered the legacy wording even with the + relabel enabled - inconsistent with its siblings "Test Tags (AND)" / + "Engagement Tags (AND)". + + These assert the RENDERED FindingFilter form field (not the static declared + label, which the dynamic setter can override) carries the Asset vocabulary, + which is the default in the test environment. + """ + + def _and_field(self): + # Unscoped finding filter keeps every tag field (the scoped views delete the + # OR field; the AND field is always present). + return FindingFilter(queryset=Finding.objects.none()).form.fields["test__engagement__product__tags_and"] + + def test_asset_and_tag_filter_label_uses_asset_vocabulary(self): + self.assertEqual(str(self._and_field().label), "Asset Tags (AND)") + + def test_asset_and_tag_filter_help_uses_asset_vocabulary(self): + self.assertEqual( + str(self._and_field().help_text), + "Filter Findings by the selected Asset tags (AND logic)", + )