@@ -3175,22 +3175,19 @@ def get_queryset(self):
3175
3175
return Answered_Survey .objects .all ().order_by ("id" )
3176
3176
3177
3177
3178
- # Authorization: object-based (consistent with UI)
3178
+ # Authorization: authenticated
3179
3179
class SimpleMetricsViewSet (
3180
3180
viewsets .ReadOnlyModelViewSet ,
3181
3181
):
3182
3182
3183
3183
"""
3184
3184
Simple metrics API endpoint that provides finding counts by product type
3185
3185
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.
3189
3186
"""
3190
3187
3191
3188
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 ,)
3194
3191
pagination_class = None
3195
3192
3196
3193
@extend_schema (
@@ -3215,19 +3212,16 @@ class SimpleMetricsViewSet(
3215
3212
def list (self , request ):
3216
3213
"""
3217
3214
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.
3223
3215
"""
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
3225
3219
now = timezone .now ()
3226
3220
date_param = request .query_params .get ("date" )
3227
3221
product_type_id = request .query_params .get ("product_type_id" )
3228
3222
3229
3223
if date_param :
3230
- # Enhanced input validation while maintaining consistency with UI behavior
3224
+ # Input validation
3231
3225
if len (date_param ) > 20 :
3232
3226
return Response (
3233
3227
{"error" : "Invalid date parameter length." },
@@ -3246,7 +3240,7 @@ def list(self, request):
3246
3240
# Parse date string with validation
3247
3241
parsed_date = datetime .strptime (date_param , "%Y-%m-%d" )
3248
3242
3249
- # Reasonable date range validation
3243
+ # Date range validation
3250
3244
min_date = datetime (2000 , 1 , 1 )
3251
3245
max_date = datetime .now () + relativedelta (years = 1 )
3252
3246
@@ -3265,83 +3259,36 @@ def list(self, request):
3265
3259
status = status .HTTP_400_BAD_REQUEST ,
3266
3260
)
3267
3261
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
3272
3264
if product_type_id :
3273
3265
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 )
3281
3267
except ValueError :
3282
3268
return Response (
3283
3269
{"error" : "Invalid product_type_id format." },
3284
3270
status = status .HTTP_400_BAD_REQUEST ,
3285
3271
)
3286
3272
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 ,
3328
3284
)
3329
3285
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
+ )
3345
3292
3346
3293
serializer = self .serializer_class (metrics_data , many = True )
3347
3294
return Response (serializer .data )
0 commit comments