From 2b0e912dcd34a81030f4e2517bc3c41a1a92b97f Mon Sep 17 00:00:00 2001 From: Ahmed Gouda Date: Mon, 4 Aug 2025 18:18:22 +0300 Subject: [PATCH 1/4] Fix duplicated metrics --- .../internal/ordering/project_health_metrics.py | 9 +++++++-- .../ordering/project_health_metrics_test.py | 7 +++---- .../src/app/projects/dashboard/metrics/page.tsx | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/backend/apps/owasp/api/internal/ordering/project_health_metrics.py b/backend/apps/owasp/api/internal/ordering/project_health_metrics.py index f52680af02..3fc5f51509 100644 --- a/backend/apps/owasp/api/internal/ordering/project_health_metrics.py +++ b/backend/apps/owasp/api/internal/ordering/project_health_metrics.py @@ -1,6 +1,6 @@ """OWASP Project Health Metrics Ordering.""" -from strawberry import auto +import strawberry from strawberry_django import order_type from apps.owasp.models.project_health_metrics import ProjectHealthMetrics @@ -10,4 +10,9 @@ class ProjectHealthMetricsOrder: """Ordering for Project Health Metrics.""" - score: auto + score: strawberry.auto + # We need to order by another field in case of equal scores + # to ensure unique metrics in pagination. + # because SQL returns random order if no order is specified. + # We didn't do this ordering in the model since we order already in the query. + project__name: strawberry.auto diff --git a/backend/tests/apps/owasp/api/internal/ordering/project_health_metrics_test.py b/backend/tests/apps/owasp/api/internal/ordering/project_health_metrics_test.py index b03d7948ba..784e3ca5cd 100644 --- a/backend/tests/apps/owasp/api/internal/ordering/project_health_metrics_test.py +++ b/backend/tests/apps/owasp/api/internal/ordering/project_health_metrics_test.py @@ -15,12 +15,11 @@ def test_order_fields(self): order_fields = { field.name for field in ProjectHealthMetricsOrder.__strawberry_definition__.fields } - expected_fields = {"score"} + expected_fields = {"score", "project__name"} assert expected_fields == order_fields def test_order_by(self): """Test ordering by score.""" - order_instance = ProjectHealthMetricsOrder( - score="DESC", - ) + order_instance = ProjectHealthMetricsOrder(score="DESC", project__name="ASC") assert order_instance.score == "DESC" + assert order_instance.project__name == "ASC" diff --git a/frontend/src/app/projects/dashboard/metrics/page.tsx b/frontend/src/app/projects/dashboard/metrics/page.tsx index 2b4273c5da..1bc90ea2f4 100644 --- a/frontend/src/app/projects/dashboard/metrics/page.tsx +++ b/frontend/src/app/projects/dashboard/metrics/page.tsx @@ -99,7 +99,12 @@ const MetricsPage: FC = () => { variables: { filters, pagination: { offset: 0, limit: PAGINATION_LIMIT }, - ordering, + ordering: [ + ordering, + { + ['project_Name']: 'ASC', + }, + ], }, }) @@ -246,7 +251,12 @@ const MetricsPage: FC = () => { variables: { filters, pagination: newPagination, - ordering, + ordering: [ + ordering, + { + ['project_Name']: 'ASC', + }, + ], }, updateQuery: (prev, { fetchMoreResult }) => { if (!fetchMoreResult) return prev From d73b3a80c513944dda95d00245edec59742b9536 Mon Sep 17 00:00:00 2001 From: Ahmed Gouda Date: Mon, 4 Aug 2025 18:43:15 +0300 Subject: [PATCH 2/4] Remove inactive projects metrics --- .../owasp/api/internal/filters/project_health_metrics.py | 5 +++++ .../api/internal/filters/project_health_metrics_test.py | 5 +++-- frontend/src/app/projects/dashboard/metrics/page.tsx | 7 +++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/backend/apps/owasp/api/internal/filters/project_health_metrics.py b/backend/apps/owasp/api/internal/filters/project_health_metrics.py index 2e7726cb49..0cd2cd10ac 100644 --- a/backend/apps/owasp/api/internal/filters/project_health_metrics.py +++ b/backend/apps/owasp/api/internal/filters/project_health_metrics.py @@ -20,3 +20,8 @@ class ProjectHealthMetricsFilter: def level(self, value: str, prefix: str): """Filter by project level.""" return Q(project__level=ProjectLevel(value)) if value else Q() + + @strawberry_django.filter_field + def is_active(self, *, value: bool, prefix: str): + """Filter by active projects.""" + return Q(project__is_active=value) if value else Q() diff --git a/backend/tests/apps/owasp/api/internal/filters/project_health_metrics_test.py b/backend/tests/apps/owasp/api/internal/filters/project_health_metrics_test.py index c9c86633da..723249ad8c 100644 --- a/backend/tests/apps/owasp/api/internal/filters/project_health_metrics_test.py +++ b/backend/tests/apps/owasp/api/internal/filters/project_health_metrics_test.py @@ -16,11 +16,12 @@ def test_filter_fields(self): filter_fields = { field.name for field in ProjectHealthMetricsFilter.__strawberry_definition__.fields } - expected_fields = {"score", "level"} + expected_fields = {"score", "level", "is_active"} assert expected_fields.issubset(filter_fields) def test_filtering(self): """Test filtering by project level and score.""" - filter_instance = ProjectHealthMetricsFilter(level="flagship", score=50) + filter_instance = ProjectHealthMetricsFilter(level="flagship", score=50, is_active=True) assert filter_instance.level == ProjectLevel.FLAGSHIP assert filter_instance.score == 50 + assert filter_instance.is_active is True diff --git a/frontend/src/app/projects/dashboard/metrics/page.tsx b/frontend/src/app/projects/dashboard/metrics/page.tsx index 1bc90ea2f4..43d1876290 100644 --- a/frontend/src/app/projects/dashboard/metrics/page.tsx +++ b/frontend/src/app/projects/dashboard/metrics/page.tsx @@ -52,7 +52,9 @@ const MetricsPage: FC = () => { }, } - let currentFilters = {} + let currentFilters = { + isActive: true, + } let currentOrdering = { score: 'DESC', } @@ -62,6 +64,7 @@ const MetricsPage: FC = () => { const currentFilterKeys = [] if (healthFilter) { currentFilters = { + ...currentFilters, ...healthFiltersMapping[healthFilter], } currentFilterKeys.push(healthFilter) @@ -181,7 +184,7 @@ const MetricsPage: FC = () => { } else { newParams.delete('health') newParams.delete('level') - newFilters = {} + newFilters = { isActive: true } } setFilters(newFilters) setActiveFilters( From cc9b6a40b15ac888a898fdd73f67dba1a84da274 Mon Sep 17 00:00:00 2001 From: Ahmed Gouda Date: Tue, 5 Aug 2025 01:43:19 +0300 Subject: [PATCH 3/4] Apply suggestions --- .../owasp/api/internal/filters/project_health_metrics.py | 5 ----- backend/apps/owasp/models/project_health_metrics.py | 3 ++- .../api/internal/filters/project_health_metrics_test.py | 5 ++--- frontend/src/app/projects/dashboard/metrics/page.tsx | 7 ++----- 4 files changed, 6 insertions(+), 14 deletions(-) diff --git a/backend/apps/owasp/api/internal/filters/project_health_metrics.py b/backend/apps/owasp/api/internal/filters/project_health_metrics.py index 0cd2cd10ac..2e7726cb49 100644 --- a/backend/apps/owasp/api/internal/filters/project_health_metrics.py +++ b/backend/apps/owasp/api/internal/filters/project_health_metrics.py @@ -20,8 +20,3 @@ class ProjectHealthMetricsFilter: def level(self, value: str, prefix: str): """Filter by project level.""" return Q(project__level=ProjectLevel(value)) if value else Q() - - @strawberry_django.filter_field - def is_active(self, *, value: bool, prefix: str): - """Filter by active projects.""" - return Q(project__is_active=value) if value else Q() diff --git a/backend/apps/owasp/models/project_health_metrics.py b/backend/apps/owasp/models/project_health_metrics.py index 66f3adfe8a..36a88c23ea 100644 --- a/backend/apps/owasp/models/project_health_metrics.py +++ b/backend/apps/owasp/models/project_health_metrics.py @@ -169,7 +169,8 @@ def get_latest_health_metrics() -> models.QuerySet["ProjectHealthMetrics"]: ProjectHealthMetrics.objects.filter(project=models.OuterRef("project")) .order_by("-nest_created_at") .values("nest_created_at")[:1] - ) + ), + project__is_active=True, ) @staticmethod diff --git a/backend/tests/apps/owasp/api/internal/filters/project_health_metrics_test.py b/backend/tests/apps/owasp/api/internal/filters/project_health_metrics_test.py index 723249ad8c..c9c86633da 100644 --- a/backend/tests/apps/owasp/api/internal/filters/project_health_metrics_test.py +++ b/backend/tests/apps/owasp/api/internal/filters/project_health_metrics_test.py @@ -16,12 +16,11 @@ def test_filter_fields(self): filter_fields = { field.name for field in ProjectHealthMetricsFilter.__strawberry_definition__.fields } - expected_fields = {"score", "level", "is_active"} + expected_fields = {"score", "level"} assert expected_fields.issubset(filter_fields) def test_filtering(self): """Test filtering by project level and score.""" - filter_instance = ProjectHealthMetricsFilter(level="flagship", score=50, is_active=True) + filter_instance = ProjectHealthMetricsFilter(level="flagship", score=50) assert filter_instance.level == ProjectLevel.FLAGSHIP assert filter_instance.score == 50 - assert filter_instance.is_active is True diff --git a/frontend/src/app/projects/dashboard/metrics/page.tsx b/frontend/src/app/projects/dashboard/metrics/page.tsx index 43d1876290..1bc90ea2f4 100644 --- a/frontend/src/app/projects/dashboard/metrics/page.tsx +++ b/frontend/src/app/projects/dashboard/metrics/page.tsx @@ -52,9 +52,7 @@ const MetricsPage: FC = () => { }, } - let currentFilters = { - isActive: true, - } + let currentFilters = {} let currentOrdering = { score: 'DESC', } @@ -64,7 +62,6 @@ const MetricsPage: FC = () => { const currentFilterKeys = [] if (healthFilter) { currentFilters = { - ...currentFilters, ...healthFiltersMapping[healthFilter], } currentFilterKeys.push(healthFilter) @@ -184,7 +181,7 @@ const MetricsPage: FC = () => { } else { newParams.delete('health') newParams.delete('level') - newFilters = { isActive: true } + newFilters = {} } setFilters(newFilters) setActiveFilters( From 9ce072faa7835e616f703acf55879904c4ed35f1 Mon Sep 17 00:00:00 2001 From: Arkadii Yakovets Date: Wed, 6 Aug 2025 12:13:14 -0700 Subject: [PATCH 4/4] Update comment --- .../owasp/api/internal/ordering/project_health_metrics.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/apps/owasp/api/internal/ordering/project_health_metrics.py b/backend/apps/owasp/api/internal/ordering/project_health_metrics.py index 3fc5f51509..7728c61645 100644 --- a/backend/apps/owasp/api/internal/ordering/project_health_metrics.py +++ b/backend/apps/owasp/api/internal/ordering/project_health_metrics.py @@ -11,8 +11,9 @@ class ProjectHealthMetricsOrder: """Ordering for Project Health Metrics.""" score: strawberry.auto + # We need to order by another field in case of equal scores # to ensure unique metrics in pagination. - # because SQL returns random order if no order is specified. - # We didn't do this ordering in the model since we order already in the query. + # The ORM returns random ordered query set if no order is specified. + # We don't do ordering in the model since we order already in the query. project__name: strawberry.auto