Skip to content

Commit 61e7ea1

Browse files
committed
Updates to metrics/views.py, api_v2/serailizers.py, utils.py, and HTML templates to deprecate the s0,s1,s2,s3,s4 naming convention
Update api_v2/views.py to have API reuse existing metrics view Fix duplicate PK in dojo/fixtures/unit_limit_reqresp.json
1 parent 2d52b01 commit 61e7ea1

File tree

8 files changed

+684
-257
lines changed

8 files changed

+684
-257
lines changed

dojo/api_v2/serializers.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3112,10 +3112,13 @@ class SimpleMetricsSerializer(serializers.Serializer):
31123112
product_type_id = serializers.IntegerField(read_only=True)
31133113
product_type_name = serializers.CharField(read_only=True)
31143114
Total = serializers.IntegerField(read_only=True)
3115-
S0 = serializers.IntegerField(read_only=True) # Critical
3116-
S1 = serializers.IntegerField(read_only=True) # High
3117-
S2 = serializers.IntegerField(read_only=True) # Medium
3118-
S3 = serializers.IntegerField(read_only=True) # Low
3119-
S4 = serializers.IntegerField(read_only=True) # Info
3115+
3116+
# Severity labels
3117+
critical = serializers.IntegerField(read_only=True)
3118+
high = serializers.IntegerField(read_only=True)
3119+
medium = serializers.IntegerField(read_only=True)
3120+
low = serializers.IntegerField(read_only=True)
3121+
info = serializers.IntegerField(read_only=True)
3122+
31203123
Opened = serializers.IntegerField(read_only=True)
31213124
Closed = serializers.IntegerField(read_only=True)

dojo/api_v2/views.py

Lines changed: 28 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3175,22 +3175,19 @@ def get_queryset(self):
31753175
return Answered_Survey.objects.all().order_by("id")
31763176

31773177

3178-
# Authorization: object-based (consistent with UI)
3178+
# Authorization: authenticated
31793179
class SimpleMetricsViewSet(
31803180
viewsets.ReadOnlyModelViewSet,
31813181
):
31823182

31833183
"""
31843184
Simple metrics API endpoint that provides finding counts by product type
31853185
broken down by severity and month status.
3186-
3187-
This endpoint replicates the logic from the UI's /metrics/simple endpoint
3188-
and uses the same authorization model for consistency.
31893186
"""
31903187

31913188
serializer_class = serializers.SimpleMetricsSerializer
3192-
queryset = Product_Type.objects.none() # Required for consistent auth behavior
3193-
permission_classes = (IsAuthenticated,) # Match pattern used by RoleViewSet
3189+
queryset = Product_Type.objects.none()
3190+
permission_classes = (IsAuthenticated,)
31943191
pagination_class = None
31953192

31963193
@extend_schema(
@@ -3215,19 +3212,16 @@ class SimpleMetricsViewSet(
32153212
def list(self, request):
32163213
"""
32173214
Retrieve simple metrics data for the requested month grouped by product type.
3218-
3219-
This endpoint replicates the logic from the UI's /metrics/simple endpoint
3220-
and uses the same authorization model for consistency.
3221-
3222-
Performance optimized with database aggregation instead of Python loops.
32233215
"""
3224-
# Parse the date parameter, default to current month (same as UI)
3216+
from dojo.metrics.views import get_simple_metrics_data
3217+
3218+
# Parse the date parameter, default to current month
32253219
now = timezone.now()
32263220
date_param = request.query_params.get("date")
32273221
product_type_id = request.query_params.get("product_type_id")
32283222

32293223
if date_param:
3230-
# Enhanced input validation while maintaining consistency with UI behavior
3224+
# Input validation
32313225
if len(date_param) > 20:
32323226
return Response(
32333227
{"error": "Invalid date parameter length."},
@@ -3246,7 +3240,7 @@ def list(self, request):
32463240
# Parse date string with validation
32473241
parsed_date = datetime.strptime(date_param, "%Y-%m-%d")
32483242

3249-
# Reasonable date range validation
3243+
# Date range validation
32503244
min_date = datetime(2000, 1, 1)
32513245
max_date = datetime.now() + relativedelta(years=1)
32523246

@@ -3265,83 +3259,36 @@ def list(self, request):
32653259
status=status.HTTP_400_BAD_REQUEST,
32663260
)
32673261

3268-
# Get authorized product types (same as UI implementation)
3269-
product_types = get_authorized_product_types(Permissions.Product_Type_View)
3270-
3271-
# Optional filtering by specific product type
3262+
# Optional filtering by specific product type with validation
3263+
parsed_product_type_id = None
32723264
if product_type_id:
32733265
try:
3274-
product_type_id = int(product_type_id)
3275-
product_types = product_types.filter(id=product_type_id)
3276-
if not product_types.exists():
3277-
return Response(
3278-
{"error": "Product type not found or access denied."},
3279-
status=status.HTTP_404_NOT_FOUND,
3280-
)
3266+
parsed_product_type_id = int(product_type_id)
32813267
except ValueError:
32823268
return Response(
32833269
{"error": "Invalid product_type_id format."},
32843270
status=status.HTTP_400_BAD_REQUEST,
32853271
)
32863272

3287-
# Build base filter conditions (same logic as UI)
3288-
base_filter = Q(
3289-
false_p=False,
3290-
duplicate=False,
3291-
out_of_scope=False,
3292-
date__month=now.month,
3293-
date__year=now.year,
3294-
)
3295-
3296-
# Apply verification status filtering if enabled (same as UI)
3297-
if (get_system_setting("enforce_verified_status", True) or
3298-
get_system_setting("enforce_verified_status_metrics", True)):
3299-
base_filter &= Q(verified=True)
3300-
3301-
# Optimize with single aggregated query per product type
3302-
metrics_data = []
3303-
3304-
for pt in product_types:
3305-
# Single aggregated query replacing the Python loop
3306-
metrics = Finding.objects.filter(
3307-
test__engagement__product__prod_type=pt,
3308-
).filter(base_filter).aggregate(
3309-
# Total count
3310-
total=Count("id"),
3311-
3312-
# Count by severity using conditional aggregation
3313-
critical=Count("id", filter=Q(severity="Critical")),
3314-
high=Count("id", filter=Q(severity="High")),
3315-
medium=Count("id", filter=Q(severity="Medium")),
3316-
low=Count("id", filter=Q(severity="Low")),
3317-
info=Count("id", filter=~Q(severity__in=["Critical", "High", "Medium", "Low"])),
3318-
3319-
# Count opened in current month
3320-
opened=Count("id", filter=Q(date__year=now.year, date__month=now.month)),
3321-
3322-
# Count closed in current month
3323-
closed=Count("id", filter=Q(
3324-
mitigated__isnull=False,
3325-
mitigated__year=now.year,
3326-
mitigated__month=now.month,
3327-
)),
3273+
# Get metrics data
3274+
try:
3275+
metrics_data = get_simple_metrics_data(
3276+
now,
3277+
parsed_product_type_id
3278+
)
3279+
except Exception as e:
3280+
logger.error(f"Error retrieving metrics: {e}")
3281+
return Response(
3282+
{"error": "Unable to retrieve metrics data."},
3283+
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
33283284
)
33293285

3330-
# Build the findings summary (same structure as UI)
3331-
findings_broken_out = {
3332-
"product_type_id": pt.id,
3333-
"product_type_name": pt.name, # Always show real name like UI
3334-
"Total": metrics["total"] or 0,
3335-
"S0": metrics["critical"] or 0, # Critical
3336-
"S1": metrics["high"] or 0, # High
3337-
"S2": metrics["medium"] or 0, # Medium
3338-
"S3": metrics["low"] or 0, # Low
3339-
"S4": metrics["info"] or 0, # Info
3340-
"Opened": metrics["opened"] or 0,
3341-
"Closed": metrics["closed"] or 0,
3342-
}
3343-
3344-
metrics_data.append(findings_broken_out)
3286+
# Check if product type was requested but not found
3287+
if parsed_product_type_id and not metrics_data:
3288+
return Response(
3289+
{"error": "Product type not found or access denied."},
3290+
status=status.HTTP_404_NOT_FOUND,
3291+
)
33453292

33463293
serializer = self.serializer_class(metrics_data, many=True)
33473294
return Response(serializer.data)

dojo/fixtures/unit_limit_reqresp.json

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -165,52 +165,6 @@
165165
"hash_code": "c89d25e445b088ba339908f68e15e3177b78d22f3039d1bfea51c4be251bf4e0",
166166
"last_reviewed": null
167167
}
168-
},{
169-
"pk": 8,
170-
"model": "dojo.finding",
171-
"fields": {
172-
"last_reviewed_by": null,
173-
"reviewers": [],
174-
"static_finding": false,
175-
"date": "2017-12-31",
176-
"references": "",
177-
"files": [],
178-
"payload": null,
179-
"under_defect_review": false,
180-
"impact": "High",
181-
"false_p": false,
182-
"verified": false,
183-
"severity": "High",
184-
"title": "DUMMY FINDING WITH REQRESP",
185-
"param": null,
186-
"created": "2017-12-01T00:00:00Z",
187-
"duplicate": false,
188-
"mitigation": "MITIGATION",
189-
"found_by": [
190-
1
191-
],
192-
"numerical_severity": "S0",
193-
"test": 5,
194-
"out_of_scope": false,
195-
"cwe": 1,
196-
"file_path": "",
197-
"duplicate_finding": null,
198-
"description": "TEST finding",
199-
"mitigated_by": null,
200-
"reporter": 2,
201-
"mitigated": null,
202-
"active": false,
203-
"line": 100,
204-
"under_review": false,
205-
"defect_review_requested_by": 2,
206-
"review_requested_by": 2,
207-
"thread_id": 1,
208-
"url": "http://www.example.com",
209-
"notes": [],
210-
"dynamic_finding": false,
211-
"hash_code": "c89d25e445b088ba339908f68e15e3177b78d22f3039d1bfea51c4be251bf4e0",
212-
"last_reviewed": null
213-
}
214168
},{
215169
"pk": 123,
216170
"model": "dojo.burprawrequestresponse",

0 commit comments

Comments
 (0)