diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f9d71e369..552132119 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -53,6 +53,7 @@ v31.0.0 (next) - Creation date displayed under the project name - Add ability to sort by date and name - Add ability to filter by pipeline type + - Add ability to filter by run status https://github.com/nexB/scancode.io/issues/413 diff --git a/Makefile b/Makefile index fb4f6262b..0ec2cdf86 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,9 @@ PYTHON_EXE?=python3 MANAGE=bin/python manage.py ACTIVATE?=. bin/activate; VIRTUALENV_PYZ=etc/thirdparty/virtualenv.pyz -BLACK_ARGS=--exclude="migrations|data|lib|bin|var" . +BLACK_ARGS=--exclude="migrations|data|lib|bin|var" +PYCODESTYLE_ARGS=--max-line-length=88 \ + --exclude=lib,thirdparty,docs,bin,migrations,settings.py,data,pipelines,var # Do not depend on Python to generate the SECRET_KEY GET_SECRET_KEY=`base64 /dev/urandom | head -c50` # Customize with `$ make envfile ENV_FILE=/etc/scancodeio/.env` @@ -64,11 +66,11 @@ envfile: isort: @echo "-> Apply isort changes to ensure proper imports ordering" - bin/isort . + @${ACTIVATE} isort . black: @echo "-> Apply black code formatter" - bin/black ${BLACK_ARGS} + @${ACTIVATE} black ${BLACK_ARGS} . doc8: @echo "-> Run doc8 validation" @@ -78,11 +80,11 @@ valid: isort black doc8 check: doc8 @echo "-> Run pycodestyle (PEP8) validation" - @${ACTIVATE} pycodestyle --max-line-length=88 --exclude=lib,thirdparty,docs,bin,migrations,settings.py,data,pipelines,var . + @${ACTIVATE} pycodestyle ${PYCODESTYLE_ARGS} . @echo "-> Run isort imports ordering validation" @${ACTIVATE} isort --check-only . @echo "-> Run black validation" - @${ACTIVATE} black --check ${BLACK_ARGS} + @${ACTIVATE} black --check ${BLACK_ARGS} . clean: @echo "-> Clean the Python env" @@ -111,7 +113,7 @@ sqlitedb: @$(MAKE) migrate run: - ${MANAGE} runserver 8001 --noreload --insecure + ${MANAGE} runserver 8001 --insecure --noreload test: @echo "-> Run the test suite" @@ -122,7 +124,7 @@ worker: bump: @echo "-> Bump the version" - bin/bumpver update --no-fetch --patch + @${ACTIVATE} bumpver update --no-fetch --patch docs: rm -rf docs/_build/ diff --git a/scanpipe/filters.py b/scanpipe/filters.py index 36d8edb61..2922c763e 100644 --- a/scanpipe/filters.py +++ b/scanpipe/filters.py @@ -32,6 +32,7 @@ from scanpipe.models import DiscoveredPackage from scanpipe.models import Project from scanpipe.models import ProjectError +from scanpipe.models import Run scanpipe_app = apps.get_app_config("scanpipe") @@ -153,6 +154,18 @@ class ProjectFilterSet(FilterSetUtilsMixin, django_filters.FilterSet): choices=scanpipe_app.get_pipeline_choices(include_blank=False), widget=BulmaDropdownWidget, ) + status = django_filters.ChoiceFilter( + label="Status", + method="filter_run_status", + choices=[ + ("not_started", "Not started"), + ("queued", "Queued"), + ("running", "Running"), + ("succeed", "Success"), + ("failed", "Failure"), + ], + widget=BulmaDropdownWidget, + ) class Meta: model = Project @@ -177,6 +190,14 @@ def __init__(self, data=None, *args, **kwargs): ] ) + def filter_run_status(self, queryset, name, value): + """ + Filter by Run status using the `RunQuerySet` methods. + """ + run_queryset_method = value + run_queryset = getattr(Run.objects, run_queryset_method)() + return queryset.filter(runs__in=run_queryset) + class JSONContainsFilter(django_filters.CharFilter): """ diff --git a/scanpipe/templates/scanpipe/project_list.html b/scanpipe/templates/scanpipe/project_list.html index 6799110eb..3112e35b2 100644 --- a/scanpipe/templates/scanpipe/project_list.html +++ b/scanpipe/templates/scanpipe/project_list.html @@ -21,6 +21,11 @@
{% include 'scanpipe/includes/breadcrumb.html' %} {{ filter.form.is_archived }} + {% if filter.is_active %} + + Clear search and filters + + {% endif %}
New Project @@ -31,6 +36,7 @@
{% include 'scanpipe/includes/filter_dropdown.html' with filter_form_field=filter.form.pipeline only %} + {% include 'scanpipe/includes/filter_dropdown.html' with filter_form_field=filter.form.status only %} {% include 'scanpipe/includes/filter_dropdown.html' with filter_form_field=filter.form.sort only %}
@@ -41,8 +47,8 @@
- {% if filter.form.search.value %} - No Projects found. Clear the search + {% if filter.is_active %} + No Projects found. Clear search and filters {% else %} New Project {% endif %} diff --git a/scanpipe/tests/test_filters.py b/scanpipe/tests/test_filters.py index dcccf7f86..c73bfed7d 100644 --- a/scanpipe/tests/test_filters.py +++ b/scanpipe/tests/test_filters.py @@ -20,17 +20,57 @@ # ScanCode.io is a free software code scanning tool from nexB Inc. and others. # Visit https://github.com/nexB/scancode.io for support and download. +import uuid + from django.test import TestCase +from django.utils import timezone +from scanpipe.filters import ProjectFilterSet from scanpipe.filters import ResourceFilterSet from scanpipe.models import CodebaseResource from scanpipe.models import Project +from scanpipe.models import Run class ScanPipeFiltersTest(TestCase): def setUp(self): self.project1 = Project.objects.create(name="Analysis") + def test_scanpipe_filters_project_filterset_status(self): + now = timezone.now() + not_started = Project.objects.create(name="not_started") + Run.objects.create(project=not_started) + queued = Project.objects.create(name="queued") + Run.objects.create(project=queued, task_id=uuid.uuid4()) + running = Project.objects.create(name="running") + Run.objects.create(project=running, task_start_date=now, task_id=uuid.uuid4()) + succeed = Project.objects.create(name="succeed") + Run.objects.create( + project=succeed, task_start_date=now, task_end_date=now, task_exitcode=0 + ) + failed = Project.objects.create(name="failed") + Run.objects.create( + project=failed, task_start_date=now, task_end_date=now, task_exitcode=1 + ) + + filterset = ProjectFilterSet(data={"status": ""}) + self.assertEqual(6, len(filterset.qs)) + + filterset = ProjectFilterSet(data={"status": "not_started"}) + self.assertEqual([not_started], list(filterset.qs)) + + filterset = ProjectFilterSet(data={"status": "queued"}) + self.assertEqual([queued], list(filterset.qs)) + + filterset = ProjectFilterSet(data={"status": "running"}) + self.assertEqual([running], list(filterset.qs)) + + filterset = ProjectFilterSet(data={"status": "succeed"}) + self.assertEqual([succeed], list(filterset.qs)) + + filterset = ProjectFilterSet(data={"status": "failed"}) + self.assertEqual([failed], list(filterset.qs)) + def test_scanpipe_filters_filter_queryset_empty_values(self): resource1 = CodebaseResource.objects.create( project=self.project1,