Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#2645] Filter cases by status #1331

Merged
merged 1 commit into from
Aug 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/open_inwoner/cms/cases/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,26 @@ class CaseContactForm(forms.Form):
widget=forms.Textarea(attrs={"rows": "5"}),
required=True,
)


class CaseFilterForm(forms.Form):
def __init__(
self,
status_freqs: dict[str, int],
status_initial: list[str] | None = None,
*args,
**kwargs,
):
super().__init__(*args, **kwargs)

self.fields["status"].choices = (
(status, f"{status} ({frequency})")
for status, frequency in status_freqs.items()
)
self.fields["status"].initial = status_initial or []

status = forms.MultipleChoiceField(
label=_("Filter by status"),
widget=forms.CheckboxSelectMultiple,
choices=dict(),
)
143 changes: 104 additions & 39 deletions src/open_inwoner/cms/cases/views/cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
import logging
from dataclasses import dataclass

from django.http import HttpRequest
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView

from furl import furl
from view_breadcrumbs import BaseBreadcrumbMixin
from zgw_consumers.concurrent import parallel

Expand All @@ -20,11 +22,15 @@
from open_inwoner.utils.mixins import PaginationMixin
from open_inwoner.utils.views import CommonPageMixin

from ..forms import CaseFilterForm
from .mixins import CaseAccessMixin, CaseLogMixin, OuterCaseAccessMixin

logger = logging.getLogger(__name__)


SUBMISSION_STATUS_OPEN = _("Openstaande aanvraag")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double checking: will this value also be translated in the submissions themselves?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Translation in the template was indeed missing; I've added it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I didn't even think of that, but 👍 .

I actually meant this snippet:

 open_submissions = (
                open_submissions if SUBMISSION_STATUS_OPEN in statuses else []
            )

I was concerned whether there is a situation where SUBMISSION_STATUS_OPEN is translated but statsuses is not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The statuses are retrieved in openzaak/api_models.py and indeed not translated, since we don't have control over what statuses are returned. We can translate the "status" for open submissions since it is our own. Do you think it would be better to leave it untranslated as well for consistency?



@dataclass(frozen=True)
class ZaakWithApiGroup(UniformCase):
zaak: Zaak
Expand All @@ -38,6 +44,60 @@ def process_data(self) -> dict:
return {**self.zaak.process_data(), "api_group": self.api_group}


class CaseListService:
def __init__(self, request: HttpRequest):
self.request = request

def get_cases_for_api_group(self, group: ZGWApiGroupConfig):
raw_cases = group.zaken_client.fetch_cases(
**get_user_fetch_parameters(self.request)
)
preprocessed_cases = preprocess_data(raw_cases, group)
return preprocessed_cases

def get_cases(self) -> list[ZaakWithApiGroup]:
all_api_groups = list(ZGWApiGroupConfig.objects.all())

with parallel() as executor:
futures = [
executor.submit(self.get_cases_for_api_group, group)
for group in all_api_groups
]

cases_with_api_group = []
for task in concurrent.futures.as_completed(futures):
try:
group_for_task = all_api_groups[futures.index(task)]
for row in task.result():
cases_with_api_group.append(
ZaakWithApiGroup(zaak=row, api_group=group_for_task)
)
except BaseException:
logger.exception("Error fetching and pre-processing cases")

# Ensure stable sorting for pagination and testing purposes
cases_with_api_group.sort(key=lambda c: all_api_groups.index(c.api_group))

return cases_with_api_group

def get_submissions(self):
subs = fetch_open_submissions(self.request.user.bsn)
subs.sort(key=lambda sub: sub.datum_laatste_wijziging, reverse=True)

return subs

def get_case_status_frequencies(self):
cases = self.get_cases()
submissions = self.get_submissions()

case_statuses = [case.zaak.status_text for case in cases]

# add static text for open submissions
case_statuses += [SUBMISSION_STATUS_OPEN for submission in submissions]

return {status: case_statuses.count(status) for status in case_statuses}


class OuterCaseListView(
OuterCaseAccessMixin, CommonPageMixin, BaseBreadcrumbMixin, TemplateView
):
Expand All @@ -57,7 +117,12 @@ def page_title(self):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)

context["hxget"] = reverse("cases:cases_content")
statuses = self.request.GET.getlist("status")

f_url = furl(reverse("cases:cases_content"))
f_url.args.addlist("status", statuses)

context["hxget"] = f_url.url
return context


Expand All @@ -75,48 +140,42 @@ class InnerCaseListView(
def page_title(self):
return _("Mijn aanvragen")

def get_cases_for_api_group(self, group: ZGWApiGroupConfig):
raw_cases = group.zaken_client.fetch_cases(
**get_user_fetch_parameters(self.request)
)
preprocessed_cases = preprocess_data(raw_cases, group)
return preprocessed_cases

def get_cases(self) -> list[ZaakWithApiGroup]:
all_api_groups = list(ZGWApiGroupConfig.objects.all())
with parallel() as executor:
futures = [
executor.submit(self.get_cases_for_api_group, group)
for group in all_api_groups
]

cases = []
for task in concurrent.futures.as_completed(futures):
try:
group_for_task = all_api_groups[futures.index(task)]
for row in task.result():
cases.append(
ZaakWithApiGroup(zaak=row, api_group=group_for_task)
)
except BaseException:
logger.exception("Error fetching and pre-processing cases")

# Ensure stable sorting for pagination and testing purposes
cases.sort(key=lambda c: all_api_groups.index(c.api_group))
return cases

def get_submissions(self):
subs = fetch_open_submissions(self.request.user.bsn)
subs.sort(key=lambda sub: sub.datum_laatste_wijziging, reverse=True)
return subs

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
config = OpenZaakConfig.get_solo()
case_service = CaseListService(self.request)
form = None

# update ctx with open submissions, cases, form for filtering
open_submissions: list[UniformCase] = case_service.get_submissions()
preprocessed_cases: list[UniformCase] = case_service.get_cases()

statuses = self.request.GET.getlist("status")
# GET.getlist returns [''] if no query params are passed, hence we test by GET.get
if self.request.GET.get("status"):
form = CaseFilterForm(
status_freqs=case_service.get_case_status_frequencies(),
status_initial=statuses,
data={"status": statuses},
)

# error message to user would be unhelpful;
# we pass silently over invalid input and send a ping to Sentry
if not form.is_valid():
form.errors["status"] = []
logger.error(
"Invalid data (%s) for case filtering by %s",
self.request.GET,
self.request.user,
)

open_submissions = (
open_submissions if SUBMISSION_STATUS_OPEN in statuses else []
)
preprocessed_cases = [
case for case in preprocessed_cases if case.zaak.status_text in statuses
]

# update ctx with submissions + cases
open_submissions: list[UniformCase] = self.get_submissions()
preprocessed_cases: list[UniformCase] = self.get_cases()
paginator_dict = self.paginate_with_context(
[*open_submissions, *preprocessed_cases]
)
Expand All @@ -127,7 +186,13 @@ def get_context_data(self, **kwargs):

self.log_access_cases(case_dicts)

context["form"] = form or CaseFilterForm(
status_freqs=case_service.get_case_status_frequencies(),
status_initial=statuses,
)

# other data
context["hxget"] = reverse("cases:cases_content")
context["title_text"] = config.title_text

return context
22 changes: 13 additions & 9 deletions src/open_inwoner/openzaak/api_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,15 @@ def _format_zaak_identificatie(self) -> str:
def identification(self) -> str:
return self._format_zaak_identificatie()

def process_data(self) -> dict:
"""
Prepare data for template
"""

status_text = glom_multiple(
@property
def status_text(self) -> str:
_status_text = glom_multiple(
self,
("status.statustype.statustekst", "status.statustype.omschrijving"),
default="",
)
if self.einddatum and self.resultaat:
result_text = glom_multiple(
_status_text = glom_multiple(
self,
(
"resultaat.resultaattype.naam",
Expand All @@ -90,15 +87,22 @@ def process_data(self) -> dict:
),
default="",
)
status_text = result_text or status_text or _("No data available")
_status_text = _status_text or _("No data available")

return _status_text

def process_data(self) -> dict:
"""
Prepare data for template
"""

return {
"identification": self.identification,
"uuid": str(self.uuid),
"start_date": self.startdatum,
"end_date": getattr(self, "einddatum", None),
"description": self.zaaktype.omschrijving,
"current_status": status_text,
"current_status": self.status_text,
"zaaktype_config": getattr(self, "zaaktype_config", None),
"statustype_config": getattr(self, "statustype_config", None),
"case_type": "Zaak",
Expand Down
92 changes: 92 additions & 0 deletions src/open_inwoner/openzaak/tests/test_api_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from django.test import TestCase
from django.utils.translation import gettext as _

from zgw_consumers.api_models.base import factory

from open_inwoner.openzaak.api_models import Zaak


class ZaakAPIModelTest(TestCase):
def setUp(self):
self.zaak_data = {
"url": "",
"identificatie": "",
"bronorganisatie": "",
"omschrijving": "",
"zaaktype": "",
"registratiedatum": "2024-08-04",
"startdatum": "2024-08-04",
"vertrouwelijkheidaanduiding": "",
"status": {
"statustype": {
"statustekst": "",
"omschrijving": "",
}
},
"einddatum": None,
"resultaat": {
"resultaattype": {
"naam": "",
"omschrijving": "",
"omschrijving_generiek": "",
"resultaattypeomschrijving": "",
},
},
}

def test_status_text_no_result(self):
zaak_statustype = self.zaak_data["status"]["statustype"]

zaak_statustype["statustekst"] = "test statustekst"
zaak_statustype["omschrijving"] = "test omschrijving"

case = factory(Zaak, data=self.zaak_data)

self.assertEqual(case.status_text, "test statustekst")

case.status["statustype"]["statustekst"] = ""

self.assertEqual(case.status_text, "test omschrijving")

def test_status_text_with_result(self):
self.zaak_data["status"]["statustype"]["statustekst"] = "test statustekst"
self.zaak_data["einddatum"] = "2024-08-06"

resultaattype = self.zaak_data["resultaat"]["resultaattype"]

resultaattype["naam"] = "test naam"
resultaattype["omschrijving"] = "test omschrijving"
resultaattype["omschrijving_generiek"] = "test omschrijving_generiek"
resultaattype["resultaattypeomschrijving"] = "test resultaattypeomschrijving"

case = factory(Zaak, data=self.zaak_data)

self.assertEqual(case.status_text, "test naam")

case.resultaat["resultaattype"]["naam"] = ""

self.assertEqual(case.status_text, "test omschrijving")

case.resultaat["resultaattype"]["omschrijving"] = ""

self.assertEqual(case.status_text, "test omschrijving_generiek")

case.resultaat["resultaattype"]["omschrijving_generiek"] = ""

self.assertEqual(case.status_text, "test resultaattypeomschrijving")

def test_status_text_with_result_but_no_end_data(self):
self.zaak_data["status"]["statustype"]["statustekst"] = "test statustekst"

resultaattype = self.zaak_data["resultaat"]["resultaattype"]

resultaattype["naam"] = "test naam"

case = factory(Zaak, data=self.zaak_data)

self.assertEqual(case.status_text, "test statustekst")

def test_status_text_default(self):
case = factory(Zaak, data=self.zaak_data)

self.assertEqual(case.status_text, _("No data available"))
Loading
Loading