Skip to content

Commit 3c6a541

Browse files
authored
Merge pull request #1114 from alphagov/2100-issue-reports-list
2100: Overhaul Issue reports list page; Add search
2 parents df6e0d7 + 93cfe35 commit 3c6a541

6 files changed

Lines changed: 226 additions & 62 deletions

File tree

accessibility_monitoring_platform/apps/audits/tests/test_views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2566,7 +2566,7 @@ def test_wcag_definition_list_view_shows_all(admin_client):
25662566
["type", "name", "description", "hint", "url_on_w3", "report_boilerplate"],
25672567
)
25682568
def test_wcag_definition_list_view_filters(fieldname, admin_client):
2569-
"""Test WCAG definition list cab be filtered by each field"""
2569+
"""Test WCAG definition list can be filtered by each field"""
25702570
wcag_definition: WcagDefinition = WcagDefinition.objects.create()
25712571
setattr(wcag_definition, fieldname, "helcaraxe")
25722572
wcag_definition.save()

accessibility_monitoring_platform/apps/tech/forms.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,16 @@ class ImportTrelloCommentsForm(forms.Form):
4444
choices=Boolean.choices,
4545
widget=AMPChoiceCheckboxWidget(attrs={"label": "Delete existing data"}),
4646
)
47+
48+
49+
class IssueReportSearchForm(forms.Form):
50+
"""Form for searching for issue reports"""
51+
52+
issue_report_search = AMPCharFieldWide(
53+
widget=forms.TextInput(
54+
attrs={
55+
"class": "govuk-input",
56+
"placeholder": "Search term",
57+
}
58+
)
59+
)

accessibility_monitoring_platform/apps/tech/templates/tech/issue_report_list.html

Lines changed: 86 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
2-
31
{% extends 'base.html' %}
42

53
{% load l10n %}
@@ -13,66 +11,95 @@
1311
<div class="govuk-grid-column-full">
1412
{% include 'common/breadcrumbs.html' %}
1513
<h1 class="govuk-heading-xl amp-margin-bottom-15">{{ sitemap.current_platform_page.get_name }}</h1>
14+
<form method="get" action="{% url sitemap.current_platform_page.url_name %}">
15+
<span class="govuk-visually-hidden">Search Issue reports:</span>
16+
<div class="govuk-grid-row">
17+
<div class="govuk-grid-column-full govuk-form-group">
18+
{{ issue_report_search_form.issue_report_search }}
19+
</div>
20+
</div>
21+
<div class="govuk-grid-row">
22+
<div class="govuk-grid-column-full govuk-button-group">
23+
<input
24+
type="submit"
25+
value="Search"
26+
name="search"
27+
class="govuk-button"
28+
data-module="govuk-button"
29+
/>
30+
<a
31+
href="{% url sitemap.current_platform_page.url_name %}"
32+
class="govuk-link govuk-link--no-visited-state"
33+
>
34+
Clear
35+
</a>
36+
</div>
37+
</div>
38+
</form>
39+
<p class="govuk-body-m">
40+
Displaying {{ paginator.count }}
41+
Issue report{% if paginator.count != 1 %}s{% endif %}.
42+
</p>
1643
{% include "common/pagination_controls.html" %}
17-
<table class="govuk-table">
18-
<tbody class="govuk-table__body">
1944
{% for issue_report in issue_reports %}
20-
<tr class="govuk-table__row">
21-
<td class="govuk-table__cell">
22-
<h2 class="govuk-heading-m">
23-
Issue #{{ issue_report.issue_number }}
24-
</h2>
25-
<p class="govuk-body-m amp-margin-bottom-5">
26-
<b>Page:</b>
27-
{{ issue_report.page_title }}
28-
</p>
29-
<p class="govuk-body-m amp-margin-bottom-5">
30-
<b>URL:</b>
31-
<a href="https://{{ request.get_host }}{{ issue_report.page_url }}"
32-
class="govuk-link govuk-link--no-visited-state">
33-
https://{{ request.get_host }}{{ issue_report.page_url }}</a>
34-
</p>
35-
<p class="govuk-body-m amp-margin-bottom-5">
36-
<b>Reported on:</b>
37-
{{ issue_report.created|amp_date }}
38-
</p>
39-
<p class="govuk-body-m amp-margin-bottom-5">
40-
<b>Reported by:</b>
41-
{{ issue_report.created_by.first_name }}
42-
</p>
43-
{% if issue_report.goal_description %}
44-
<p class="govuk-body-m amp-margin-bottom-5">
45-
<b>User goal:</b>
46-
{{ issue_report.goal_description }}
47-
</p>
48-
{% endif %}
49-
<p class="govuk-body-m amp-margin-bottom-5">
50-
<b>User issue:</b>
51-
{{ issue_report.issue_description }}
52-
</p>
53-
<p class="govuk-body-m amp-margin-bottom-5">
54-
<b>Tech response:</b>
55-
{% if issue_report.notes %}
56-
<i>{{ issue_report.notes }}</i>
57-
{% else %}
58-
None
59-
{% endif %}
60-
</p>
61-
<p class="govuk-body-m amp-margin-bottom-5">
62-
<b>Trello ticket:</b>
63-
{% if issue_report.trello_ticket %}
64-
<a href="{{ issue_report.trello_ticket }}"
65-
class="govuk-link govuk-link--no-visited-state">
66-
{{ issue_report.trello_ticket }}</a>
67-
{% else %}
68-
None
69-
{% endif %}
70-
</p>
71-
</td>
72-
</tr>
45+
<h2 id="issue-{{ issue_report.issue_number }}" class="govuk-heading-m">
46+
Issue #{{ issue_report.issue_number }}
47+
{% if issue_report.complete %}<span class="govuk-visually-hidden">complete</span> &check;{% endif %}
48+
{% if staff_view and user.is_staff %}
49+
<span class="govuk-body-s">
50+
(<a href="{% url 'admin:common_issuereport_change' issue_report.id %}" class="govuk-link govuk-link--no-visited-state"
51+
>admin</a>)
52+
</span>
53+
{% endif %}
54+
</h2>
55+
<p class="govuk-body-m amp-margin-bottom-5">
56+
<b>Page:</b>
57+
{{ issue_report.page_title }}
58+
</p>
59+
<p class="govuk-body-m amp-margin-bottom-5">
60+
<b>URL:</b>
61+
<a href="https://{{ request.get_host }}{{ issue_report.page_url }}"
62+
class="govuk-link govuk-link--no-visited-state">
63+
https://{{ request.get_host }}{{ issue_report.page_url }}</a>
64+
</p>
65+
<p class="govuk-body-m amp-margin-bottom-5">
66+
<b>Reported on:</b>
67+
{{ issue_report.created|amp_date }}
68+
</p>
69+
<p class="govuk-body-m amp-margin-bottom-5">
70+
<b>Reported by:</b>
71+
{{ issue_report.created_by.first_name }}
72+
</p>
73+
{% if issue_report.goal_description %}
74+
<p class="govuk-body-m amp-margin-bottom-5">
75+
<b>User goal:</b>
76+
{{ issue_report.goal_description }}
77+
</p>
78+
{% endif %}
79+
<p class="govuk-body-m amp-margin-bottom-5">
80+
<b>User issue:</b>
81+
{{ issue_report.issue_description }}
82+
</p>
83+
<p class="govuk-body-m amp-margin-bottom-5">
84+
<b>Tech response:</b>
85+
{% if issue_report.notes %}
86+
<i>{{ issue_report.notes }}</i>
87+
{% else %}
88+
None
89+
{% endif %}
90+
</p>
91+
<p class="govuk-body-m amp-margin-bottom-5">
92+
<b>Trello ticket:</b>
93+
{% if issue_report.trello_ticket %}
94+
<a href="{{ issue_report.trello_ticket }}"
95+
class="govuk-link govuk-link--no-visited-state">
96+
{{ issue_report.trello_ticket }}</a>
97+
{% else %}
98+
None
99+
{% endif %}
100+
</p>
101+
<hr class="amp-width-100 amp-margin-bottom-30" />
73102
{% endfor %}
74-
</tbody>
75-
</table>
76103
{% include "common/pagination_controls.html" %}
77104
</div>
78105
</div>

accessibility_monitoring_platform/apps/tech/templates/tech/ref/contents.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ <h2 class="govuk-heading-m">Contents</h2>
5656
<h2 class="govuk-heading-m">Tech team pages</h2>
5757
<ul class="govuk-list">
5858
<li>
59-
<a href="{% url 'tech:issue-reports-list' %}" class="govuk-link govuk-link--no-visited-state">
59+
<a href="{% url 'tech:issue-reports-list' %}{% if user.is_staff %}?staff_view=True{% endif %}" class="govuk-link govuk-link--no-visited-state">
6060
Issue reports</a>
6161
(<a href="{% url 'admin:common_issuereport_changelist' %}?complete__exact=0" class="govuk-link govuk-link--no-visited-state"
6262
>admin</a>)

accessibility_monitoring_platform/apps/tech/tests/test_views.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from pytest_django.asserts import assertContains
1010

1111
from ...audits.models import Audit
12+
from ...common.models import IssueReport
1213
from ...reports.models import Report
1314
from ...simplified.models import SimplifiedCase
1415
from ...users.tests.test_views import VALID_PASSWORD, VALID_USER_EMAIL, create_user
@@ -84,3 +85,73 @@ def test_reference_implementations_page(admin_client):
8485
assert response.status_code == 200
8586

8687
assertContains(response, "Reference implementation")
88+
89+
90+
@pytest.mark.parametrize(
91+
"fieldname",
92+
[
93+
"page_url",
94+
"page_title",
95+
"goal_description",
96+
"issue_description",
97+
"trello_ticket",
98+
"notes",
99+
],
100+
)
101+
def test_issue_report_list_view_filters(fieldname, admin_client):
102+
"""Test Issue reports list can be filtered by each field"""
103+
user: User = User.objects.create()
104+
issue_report: IssueReport = IssueReport.objects.create(created_by=user)
105+
setattr(issue_report, fieldname, "searchstring")
106+
issue_report.save()
107+
108+
response: HttpResponse = admin_client.get(
109+
f"{reverse('tech:issue-reports-list')}?issue_report_search=SearchString"
110+
)
111+
112+
assert response.status_code == 200
113+
114+
assertContains(
115+
response, '<p class="govuk-body-m">Displaying 1 Issue report.</p>', html=True
116+
)
117+
118+
119+
def test_issue_report_list_view_filter_issue_number(admin_client):
120+
"""Test Issue reports list can be filtered by issue number"""
121+
user: User = User.objects.create()
122+
IssueReport.objects.create(created_by=user, issue_number=999)
123+
124+
response: HttpResponse = admin_client.get(
125+
f"{reverse('tech:issue-reports-list')}?issue_report_search=999"
126+
)
127+
128+
assert response.status_code == 200
129+
130+
assertContains(
131+
response, '<p class="govuk-body-m">Displaying 1 Issue report.</p>', html=True
132+
)
133+
134+
135+
@pytest.mark.parametrize(
136+
"fieldname",
137+
[
138+
"first_name",
139+
"last_name",
140+
],
141+
)
142+
def test_issue_report_list_view_filter_user_name(fieldname, admin_client):
143+
"""Test Issue reports list can be filtered by user name"""
144+
user: User = User.objects.create()
145+
setattr(user, fieldname, "Username1")
146+
user.save()
147+
IssueReport.objects.create(created_by=user)
148+
149+
response: HttpResponse = admin_client.get(
150+
f"{reverse('tech:issue-reports-list')}?issue_report_search=Username1"
151+
)
152+
153+
assert response.status_code == 200
154+
155+
assertContains(
156+
response, '<p class="govuk-body-m">Displaying 1 Issue report.</p>', html=True
157+
)

accessibility_monitoring_platform/apps/tech/views.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,25 @@
55

66
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
77
from django.core.exceptions import BadRequest, PermissionDenied
8+
from django.db.models.query import Q, QuerySet
89
from django.http import HttpRequest, HttpResponseRedirect
910
from django.urls import reverse_lazy
1011
from django.views.generic import TemplateView
1112
from django.views.generic.edit import FormView
1213
from django.views.generic.list import ListView
1314

1415
from ..common.models import Boolean, IssueReport
16+
from ..common.utils import get_url_parameters_for_pagination
1517
from ..detailed.models import DetailedCase
1618
from ..mobile.models import MobileCase
1719
from ..reports.models import Report
1820
from ..simplified.models import SimplifiedCase
19-
from .forms import ImportCSVForm, ImportTrelloCommentsForm, PlatformCheckingForm
21+
from .forms import (
22+
ImportCSVForm,
23+
ImportTrelloCommentsForm,
24+
IssueReportSearchForm,
25+
PlatformCheckingForm,
26+
)
2027
from .utils import import_mobile_cases_csv, import_trello_comments
2128

2229
logger = logging.getLogger(__name__)
@@ -82,6 +89,52 @@ class IssueReportListView(ListView):
8289
context_object_name: str = "issue_reports"
8390
paginate_by: int = 10
8491

92+
def get(self, request, *args, **kwargs):
93+
"""Populate filter form"""
94+
if self.request.GET:
95+
self.issue_report_search_form: IssueReportSearchForm = (
96+
IssueReportSearchForm(self.request.GET)
97+
)
98+
self.issue_report_search_form.is_valid()
99+
else:
100+
self.issue_report_search_form = IssueReportSearchForm()
101+
return super().get(request, *args, **kwargs)
102+
103+
def get_queryset(self) -> QuerySet[IssueReport]:
104+
"""Add filters to queryset"""
105+
if self.issue_report_search_form.errors:
106+
return IssueReport.objects.none()
107+
108+
if hasattr(self.issue_report_search_form, "cleaned_data"):
109+
search_str: str | None = self.issue_report_search_form.cleaned_data.get(
110+
"issue_report_search"
111+
)
112+
113+
if search_str:
114+
return IssueReport.objects.filter(
115+
Q(issue_number__icontains=search_str)
116+
| Q(page_url__icontains=search_str)
117+
| Q(page_title__icontains=search_str)
118+
| Q(goal_description__icontains=search_str)
119+
| Q(issue_description__icontains=search_str)
120+
| Q(created_by__first_name__icontains=search_str)
121+
| Q(created_by__last_name__icontains=search_str)
122+
| Q(trello_ticket__icontains=search_str)
123+
| Q(notes__icontains=search_str)
124+
)
125+
126+
return IssueReport.objects.all()
127+
128+
def get_context_data(self, **kwargs: dict[str, Any]) -> dict[str, Any]:
129+
"""Get context data for template rendering"""
130+
context: dict[str, Any] = super().get_context_data(**kwargs)
131+
context["staff_view"] = self.request.GET.get("staff_view")
132+
context["issue_report_search_form"] = self.issue_report_search_form
133+
context["url_parameters"] = get_url_parameters_for_pagination(
134+
request=self.request
135+
)
136+
return context
137+
85138

86139
class ImportCSV(StaffRequiredMixin, FormView):
87140
"""Reset obile Cases data from CSV"""

0 commit comments

Comments
 (0)