Skip to content

Commit 49d0acc

Browse files
committed
Only show unique checks
Commit e5c641f optimized fetching of checks when displaying a patch by prefetching these checks ahead of time. Unfortunately we missed that this should exclude older versions of checks for a given context. Make the code used to generate the unique checks generic and allow us to use that as a filter to the checks provided to the template, restoring the correct behavior. Conflicts: patchwork/tests/test_detail.py NOTE(stephenfin): Conflicts are due to some differences in imports, however, we also need to drop some usages of f-strings since Patchwork 2.x supported Python 2.x also. We also need to specify an explicit decode value for some strings since Python 2.x defaults to 'ascii', not 'utf-8'. Signed-off-by: Stephen Finucane <stephen@that.guru> Closes: #398 Fixes: e5c641f ("Optimise fetching checks when displaying a patch") (cherry picked from commit a43d6ac) (cherry picked from commit f4ff605)
1 parent c99a933 commit 49d0acc

File tree

3 files changed

+89
-43
lines changed

3 files changed

+89
-43
lines changed

patchwork/models.py

Lines changed: 48 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -516,50 +516,13 @@ def is_editable(self, user):
516516
return True
517517
return False
518518

519-
@property
520-
def combined_check_state(self):
521-
"""Return the combined state for all checks.
522-
523-
Generate the combined check's state for this patch. This check
524-
is one of the following, based on the value of each unique
525-
check:
526-
527-
* failure, if any context's latest check reports as failure
528-
* warning, if any context's latest check reports as warning
529-
* pending, if there are no checks, or a context's latest
530-
Check reports as pending
531-
* success, if latest checks for all contexts reports as
532-
success
533-
"""
534-
state_names = dict(Check.STATE_CHOICES)
535-
states = [check.state for check in self.checks]
536-
537-
if not states:
538-
return state_names[Check.STATE_PENDING]
539-
540-
for state in [Check.STATE_FAIL, Check.STATE_WARNING,
541-
Check.STATE_PENDING]: # order sensitive
542-
if state in states:
543-
return state_names[state]
544-
545-
return state_names[Check.STATE_SUCCESS]
546-
547-
@property
548-
def checks(self):
549-
"""Return the list of unique checks.
550-
551-
Generate a list of checks associated with this patch for each
552-
type of Check. Only "unique" checks are considered,
553-
identified by their 'context' field. This means, given n
554-
checks with the same 'context', the newest check is the only
555-
one counted regardless of its value. The end result will be a
556-
association of types to number of unique checks for said
557-
type.
558-
"""
519+
@staticmethod
520+
def filter_unique_checks(checks):
521+
"""Filter the provided checks to generate the unique list."""
559522
unique = {}
560523
duplicates = []
561524

562-
for check in self.check_set.all():
525+
for check in checks:
563526
ctx = check.context
564527
user = check.user_id
565528

@@ -586,7 +549,50 @@ def checks(self):
586549
# prefetch_related.) So, do it 'by hand' in Python. We can
587550
# also be confident that this won't be worse, seeing as we've
588551
# just iterated over self.check_set.all() *anyway*.
589-
return [c for c in self.check_set.all() if c.id not in duplicates]
552+
return [c for c in checks if c.id not in duplicates]
553+
554+
@property
555+
def checks(self):
556+
"""Return the list of unique checks.
557+
558+
Generate a list of checks associated with this patch for each
559+
type of Check. Only "unique" checks are considered,
560+
identified by their 'context' field. This means, given n
561+
checks with the same 'context', the newest check is the only
562+
one counted regardless of its value. The end result will be a
563+
association of types to number of unique checks for said
564+
type.
565+
"""
566+
return self.filter_unique_checks(self.check_set.all())
567+
568+
@property
569+
def combined_check_state(self):
570+
"""Return the combined state for all checks.
571+
572+
Generate the combined check's state for this patch. This check
573+
is one of the following, based on the value of each unique
574+
check:
575+
576+
* failure, if any context's latest check reports as failure
577+
* warning, if any context's latest check reports as warning
578+
* pending, if there are no checks, or a context's latest check reports
579+
as pending
580+
* success, if latest checks for all contexts reports as success
581+
"""
582+
state_names = dict(Check.STATE_CHOICES)
583+
states = [check.state for check in self.checks]
584+
585+
if not states:
586+
return state_names[Check.STATE_PENDING]
587+
588+
# order sensitive
589+
for state in (
590+
Check.STATE_FAIL, Check.STATE_WARNING, Check.STATE_PENDING,
591+
):
592+
if state in states:
593+
return state_names[state]
594+
595+
return state_names[Check.STATE_SUCCESS]
590596

591597
@property
592598
def check_count(self):

patchwork/tests/test_detail.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@
33
#
44
# SPDX-License-Identifier: GPL-2.0-or-later
55

6+
from datetime import datetime as dt
7+
from datetime import timedelta
8+
69
from django.test import TestCase
710
from django.urls import reverse
811

12+
from patchwork.models import Check
13+
from patchwork.tests.utils import create_check
914
from patchwork.tests.utils import create_comment
1015
from patchwork.tests.utils import create_cover
1116
from patchwork.tests.utils import create_patch
1217
from patchwork.tests.utils import create_project
18+
from patchwork.tests.utils import create_user
1319

1420

1521
class CoverLetterViewTest(TestCase):
@@ -156,6 +162,38 @@ def test_invalid_patch_id(self):
156162
response = self.client.get(requested_url)
157163
self.assertEqual(response.status_code, 404)
158164

165+
def test_patch_with_checks(self):
166+
user = create_user()
167+
patch = create_patch()
168+
check_a = create_check(
169+
patch=patch, user=user, context='foo', state=Check.STATE_FAIL,
170+
date=(dt.utcnow() - timedelta(days=1)))
171+
create_check(
172+
patch=patch, user=user, context='foo', state=Check.STATE_SUCCESS)
173+
check_b = create_check(
174+
patch=patch, user=user, context='bar', state=Check.STATE_PENDING)
175+
requested_url = reverse(
176+
'patch-detail',
177+
kwargs={
178+
'project_id': patch.project.linkname,
179+
'msgid': patch.url_msgid,
180+
},
181+
)
182+
response = self.client.get(requested_url)
183+
184+
# the response should contain checks
185+
self.assertContains(response, '<h2>Checks</h2>')
186+
187+
# and it should only show the unique checks
188+
self.assertEqual(
189+
1, response.content.decode('utf-8').count(
190+
'<td>%s/%s</td>' % (check_a.user, check_a.context)
191+
))
192+
self.assertEqual(
193+
1, response.content.decode('utf-8').count(
194+
'<td>%s/%s</td>' % (check_b.user, check_b.context)
195+
))
196+
159197

160198
class CommentRedirectTest(TestCase):
161199

patchwork/views/patch.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ def patch_detail(request, project_id, msgid):
123123
related_different_project = []
124124

125125
context['comments'] = comments
126-
context['checks'] = patch.check_set.all().select_related('user')
126+
context['checks'] = Patch.filter_unique_checks(
127+
patch.check_set.all().select_related('user'),
128+
)
127129
context['submission'] = patch
128130
context['patchform'] = form
129131
context['createbundleform'] = createbundleform

0 commit comments

Comments
 (0)