diff --git a/scanpipe/api/serializers.py b/scanpipe/api/serializers.py index 1063c8dc5..86997b644 100644 --- a/scanpipe/api/serializers.py +++ b/scanpipe/api/serializers.py @@ -33,6 +33,7 @@ from scanpipe.models import Project from scanpipe.models import ProjectError from scanpipe.models import Run +from scanpipe.pipes import count_group_by scanpipe_app_config = apps.get_app_config("scanpipe") @@ -126,8 +127,8 @@ class Meta: ] def get_codebase_resources_summary(self, project): - statuses = project.codebaseresources.values_list("status", flat=True) - return dict(Counter(statuses)) + queryset = project.codebaseresources.all() + return count_group_by(queryset, "status") def get_discovered_package_summary(self, project): base_qs = project.discoveredpackages diff --git a/scanpipe/management/commands/status.py b/scanpipe/management/commands/status.py index 0002f57ca..554f3c36f 100644 --- a/scanpipe/management/commands/status.py +++ b/scanpipe/management/commands/status.py @@ -25,6 +25,7 @@ from scanpipe.models import CodebaseResource from scanpipe.models import DiscoveredPackage from scanpipe.models import ProjectError +from scanpipe.pipes import count_group_by class Command(ProjectCommand, RunStatusCommandMixin): @@ -33,23 +34,30 @@ class Command(ProjectCommand, RunStatusCommandMixin): def handle(self, *args, **options): super().handle(*args, **options) - status = [ + message = [ f"Project: {self.project.name}", f"Create date: {self.project.created_date.strftime('%b %d %Y %H:%M')}", f"Work directory: {self.project.work_directory}", - "\nDatabase:", + "\n", + "Database:", ] for model_class in [CodebaseResource, DiscoveredPackage, ProjectError]: - object_count = model_class.objects.project(self.project).count() - status.append(f" - {model_class.__name__}: {object_count}") + queryset = model_class.objects.project(self.project) + message.append(f" - {model_class.__name__}: {queryset.count()}") + + if model_class == CodebaseResource: + status_summary = count_group_by(queryset, "status") + for status, count in status_summary.items(): + status = status or "(no status)" + message.append(f" - {status}: {count}") runs = self.project.runs.all() if runs: - status.append("\nPipelines:") + message.append("\nPipelines:") for run in runs: status_code = self.get_run_status_code(run) - status.append(f" [{status_code}] {run.pipeline}") + message.append(f" [{status_code}] {run.pipeline}") - for line in status: + for line in message: self.stdout.write(line) diff --git a/scanpipe/pipes/__init__.py b/scanpipe/pipes/__init__.py index 413341a38..66154d718 100644 --- a/scanpipe/pipes/__init__.py +++ b/scanpipe/pipes/__init__.py @@ -25,6 +25,7 @@ from functools import partial from pathlib import Path +from django.db.models import Count from django.forms import model_to_dict from commoncode import fileutils @@ -315,3 +316,17 @@ def filename_now(sep="-"): """ now = datetime.now().isoformat(sep=sep, timespec="seconds") return now.replace(":", sep) + + +def count_group_by(queryset, field_name): + """ + Return a summary of all existing values for the provided `field_name` on the + `queryset`, including the count of each entry, as a dict. + """ + counts = ( + queryset.values(field_name) + .annotate(count=Count(field_name)) + .order_by(field_name) + ) + + return {entry.get(field_name): entry.get("count") for entry in counts}