@@ -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
31793179class 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 )
0 commit comments