Compare commits

...

2 Commits

Author SHA1 Message Date
Adrián Jesús Peña Rodríguez b82d7318ce chore: update changelog 2026-04-09 12:52:30 +02:00
Adrián Jesús Peña Rodríguez fdff780624 feat: add status to sort param 2026-04-09 12:49:48 +02:00
3 changed files with 77 additions and 0 deletions
+1
View File
@@ -11,6 +11,7 @@ All notable changes to the **Prowler API** are documented in this file.
- `VALKEY_SCHEME`, `VALKEY_USERNAME`, and `VALKEY_PASSWORD` environment variables to configure Celery broker TLS/auth connection details for Valkey/ElastiCache [(#10420)](https://github.com/prowler-cloud/prowler/pull/10420)
- `Vercel` provider support [(#10190)](https://github.com/prowler-cloud/prowler/pull/10190)
- 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 groups list and latest endpoints support `sort=status`, ordering by aggregated status with the FAIL > PASS > MUTED priority [(#10628)](https://github.com/prowler-cloud/prowler/pull/10628)
### 🔄 Changed
+62
View File
@@ -16872,6 +16872,68 @@ class TestFindingGroupViewSet:
asc_keys = [delta_key(item) for item in response.json()["data"]]
assert asc_keys == sorted(asc_keys)
@pytest.mark.parametrize(
"endpoint_name", ["finding-group-list", "finding-group-latest"]
)
def test_finding_groups_sort_by_status(
self,
authenticated_client,
finding_groups_fixture,
endpoint_name,
):
"""Sort by status orders by aggregated status (FAIL > PASS > MUTED)."""
status_order = {"FAIL": 3, "PASS": 2, "MUTED": 1}
# Descending: FAIL groups first, then PASS
params = {"sort": "-status"}
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_statuses = [item["attributes"]["status"] for item in data]
desc_keys = [status_order[s] for s in desc_statuses]
assert desc_keys == sorted(desc_keys, reverse=True)
# Ascending: PASS groups first, then FAIL
params["sort"] = "status"
response = authenticated_client.get(reverse(endpoint_name), params)
assert response.status_code == status.HTTP_200_OK
asc_statuses = [
item["attributes"]["status"] for item in response.json()["data"]
]
asc_keys = [status_order[s] for s in asc_statuses]
assert asc_keys == sorted(asc_keys)
@pytest.mark.parametrize(
"endpoint_name", ["finding-group-list", "finding-group-latest"]
)
def test_finding_groups_sort_by_status_includes_muted(
self,
authenticated_client,
finding_groups_fixture,
endpoint_name,
):
"""When include_muted is set, MUTED groups participate in status sort."""
status_order = {"FAIL": 3, "PASS": 2, "MUTED": 1}
params = {"sort": "status", "filter[include_muted]": "true"}
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"]
statuses = [item["attributes"]["status"] for item in data]
assert "MUTED" in statuses
assert statuses[0] == "MUTED"
keys = [status_order[s] for s in statuses]
assert keys == sorted(keys)
def test_finding_groups_latest_ignores_date_filters(
self, authenticated_client, finding_groups_fixture
):
+14
View File
@@ -7219,6 +7219,7 @@ class FindingGroupViewSet(BaseRLSViewSet):
"check_id": "check_id",
"check_title": "check_title",
"severity": "severity_order",
"status": "status_order",
"delta": "delta_order",
"fail_count": "fail_count",
"pass_count": "pass_count",
@@ -7570,6 +7571,19 @@ class FindingGroupViewSet(BaseRLSViewSet):
sort_param, self._FINDING_GROUP_SORT_MAP
)
if ordering:
# status_order is annotated on demand so groups can be sorted
# by their aggregated status (FAIL > PASS > MUTED), mirroring
# the priority used in _post_process_aggregation.
if any(field.lstrip("-") == "status_order" for field in ordering):
aggregated_queryset = aggregated_queryset.annotate(
status_order=Case(
When(fail_count__gt=0, then=Value(3)),
When(pass_count__gt=0, then=Value(2)),
default=Value(1),
output_field=IntegerField(),
)
)
# 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