diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index fee2f4e914..1d4da94ad3 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to the **Prowler API** are documented in this file. - Finding groups list and latest endpoints support `sort=delta`, ordering by `new_count` then `changed_count` so groups with the most new findings rank highest [(#10606)](https://github.com/prowler-cloud/prowler/pull/10606) - Finding group resources endpoints (`/finding-groups/{check_id}/resources` and `/finding-groups/latest/{check_id}/resources`) now expose `finding_id` per row, pointing to the most recent matching Finding for each resource. UUIDv7 ordering guarantees `Max(finding__id)` resolves to the latest snapshot [(#10630)](https://github.com/prowler-cloud/prowler/pull/10630) - Handle CIS and CISA SCuBA compliance framework from google workspace [(#10629)](https://github.com/prowler-cloud/prowler/pull/10629) +- Sort support for all finding group counter fields: `pass_muted_count`, `fail_muted_count`, `manual_muted_count`, and all `new_*`/`changed_*` status-mute breakdown counters [(#10655)](https://github.com/prowler-cloud/prowler/pull/10655) ### 🔄 Changed diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 7457f20f4d..2249a041ab 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -17046,6 +17046,57 @@ class TestFindingGroupViewSet: # Descending boolean: True (1) before False (0) assert muted_values == sorted(muted_values, reverse=True) + @pytest.mark.parametrize( + "endpoint_name", ["finding-group-list", "finding-group-latest"] + ) + @pytest.mark.parametrize( + "sort_field", + [ + "pass_muted_count", + "fail_muted_count", + "manual_muted_count", + "new_fail_count", + "new_fail_muted_count", + "new_pass_count", + "new_pass_muted_count", + "new_manual_count", + "new_manual_muted_count", + "changed_fail_count", + "changed_fail_muted_count", + "changed_pass_count", + "changed_pass_muted_count", + "changed_manual_count", + "changed_manual_muted_count", + ], + ) + def test_finding_groups_sort_by_counter_fields( + self, + authenticated_client, + finding_groups_fixture, + endpoint_name, + sort_field, + ): + """All counter fields are accepted as sort parameters (asc and desc).""" + params = {"sort": f"-{sort_field}"} + if endpoint_name == "finding-group-list": + params["filter[inserted_at]"] = TODAY + + response = authenticated_client.get(reverse(endpoint_name), params) + assert response.status_code == status.HTTP_200_OK + data = response.json()["data"] + assert len(data) > 0 + + desc_values = [item["attributes"][sort_field] for item in data] + assert desc_values == sorted(desc_values, reverse=True) + + params["sort"] = sort_field + response = authenticated_client.get(reverse(endpoint_name), params) + assert response.status_code == status.HTTP_200_OK + asc_values = [ + item["attributes"][sort_field] for item in response.json()["data"] + ] + assert asc_values == sorted(asc_values) + @pytest.mark.parametrize( "endpoint_name", ["finding-group-list", "finding-group-latest"] ) diff --git a/api/src/backend/api/v1/views.py b/api/src/backend/api/v1/views.py index 23cfe17f2d..2581ba3f26 100644 --- a/api/src/backend/api/v1/views.py +++ b/api/src/backend/api/v1/views.py @@ -7311,8 +7311,23 @@ class FindingGroupViewSet(BaseRLSViewSet): "pass_count": "pass_count", "manual_count": "manual_count", "muted_count": "muted_count", + "pass_muted_count": "pass_muted_count", + "fail_muted_count": "fail_muted_count", + "manual_muted_count": "manual_muted_count", "new_count": "new_count", + "new_fail_count": "new_fail_count", + "new_fail_muted_count": "new_fail_muted_count", + "new_pass_count": "new_pass_count", + "new_pass_muted_count": "new_pass_muted_count", + "new_manual_count": "new_manual_count", + "new_manual_muted_count": "new_manual_muted_count", "changed_count": "changed_count", + "changed_fail_count": "changed_fail_count", + "changed_fail_muted_count": "changed_fail_muted_count", + "changed_pass_count": "changed_pass_count", + "changed_pass_muted_count": "changed_pass_muted_count", + "changed_manual_count": "changed_manual_count", + "changed_manual_muted_count": "changed_manual_muted_count", "resources_total": "resources_total", "resources_fail": "resources_fail", "first_seen_at": "agg_first_seen_at", @@ -7683,11 +7698,6 @@ class FindingGroupViewSet(BaseRLSViewSet): ) ) - # delta_order is a virtual sort field: expand it to a - # lexicographic ordering by (new_count, changed_count) so groups - # with more new findings rank higher, with changed_count as the - # tie-breaker (preserves the "new > changed" priority used by - # the resources endpoint, but driven by the actual counters). expanded_ordering = [] for field in ordering: if field.lstrip("-") == "delta_order":