Repository: ietf-tools/datatracker

Issue:
Search result rows for drafts don't link to reviews specific to the draft
Search result rows on pages like https://datatracker.ietf.org/wg/6man/documents/ show links to review team reviews, such as for opsdir.

Right now, the link shown for the row for draft-ietf-6man-ipv6-alt-mark points to https://datatracker.ietf.org/group/opsdir/

It should (and before the bootstrap5 upgrade, did) point to the actual opsdir review of the draft (or in this case, to the reviw assigment since the review isn't yet complete at https://datatracker.ietf.org/doc/draft-ietf-6man-ipv6-alt-mark/reviewrequest/14700/).

The logic should be the same as what builds the review links at https://datatracker.ietf.org/doc/draft-ietf-6man-ipv6-alt-mark/



Discussion:
So pre-bs5, I think we just showed which team had reviewed something, but **did not** link to the actual review:
https://github.com/ietf-tools/datatracker/blob/e11583a87f492953e9406ca1f5a0b36ca5bafe3c/ietf/templates/doc/search/status_columns.html#L53-L56
But I will fix the link nevertheless.

Resolving patch:
diff --git a/ietf/doc/templatetags/ballot_icon.py b/ietf/doc/templatetags/ballot_icon.py
--- a/ietf/doc/templatetags/ballot_icon.py
+++ b/ietf/doc/templatetags/ballot_icon.py
@@ -204,18 +204,18 @@ def state_age_colored(doc):
             goal1 = 14
             goal2 = 28
         if days > goal2:
-            class_name = "badge bg-danger"
+            class_name = "text-danger"
         elif days > goal1:
-            class_name = "badge bg-warning"
+            class_name = "text-warning"
         else:
-            class_name = "badge bg-success"
+            # don't show a badge when things are in the green; clutters display
+            # class_name = "text-success"
+            return ""
         if days > goal1:
-            title = ' title="Goal is &lt;%d days"' % (goal1,)
+            title = ' title="In state for %d day%s; goal is &lt;%d days."' % (days, 's' if days != 1 else '', goal1,)
         else:
             title = ''
-        return mark_safe('<span class="%s"%s>for %d day%s</span>' % (
-                class_name, title, days,
-                's' if days != 1 else ''))
+        return mark_safe('<span class="%s"><i class="bi bi-hourglass-split"%s></i></span>' % (class_name, title))
     else:
         return ""
 
@@ -233,4 +233,4 @@ def auth48_alert_badge(doc):
     if rfced_state == 'auth48':
         return mark_safe('<span class="badge bg-info" title="AUTH48">AUTH48</span>')
 
-    return ''
\ No newline at end of file
+    return ''
diff --git a/ietf/doc/templatetags/ietf_filters.py b/ietf/doc/templatetags/ietf_filters.py
--- a/ietf/doc/templatetags/ietf_filters.py
+++ b/ietf/doc/templatetags/ietf_filters.py
@@ -719,20 +719,20 @@ def action_holder_badge(action_holder):
     ''
 
     >>> action_holder_badge(DocumentActionHolderFactory(time_added=datetime.datetime.now() - datetime.timedelta(days=16)))
-    '<span class="badge bg-danger" title="Goal is &lt;15 days">for 16 days</span>'
+    '<span class="text-danger"><i class="bi bi-hourglass-split" title="In state for 16 days; goal is &lt;15 days."></i></span>'
 
     >>> action_holder_badge(DocumentActionHolderFactory(time_added=datetime.datetime.now() - datetime.timedelta(days=30)))
-    '<span class="badge bg-danger" title="Goal is &lt;15 days">for 30 days</span>'
+    '<span class="text-danger"><i class="bi bi-hourglass-split" title="In state for 30 days; goal is &lt;15 days."></i></span>'
 
     >>> settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS = old_limit
     """
     age_limit = settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS
     age = (datetime.datetime.now() - action_holder.time_added).days
     if age > age_limit:
-        return mark_safe('<span class="badge bg-danger" title="Goal is &lt;%d days">for %d day%s</span>' % (
-            age_limit,
+        return mark_safe('<span class="text-danger"><i class="bi bi-hourglass-split" title="In state for %d day%s; goal is &lt;%d days."></i></span>' % (
             age,
-            's' if age != 1 else ''))
+            's' if age != 1 else '',
+            age_limit))
     else:
         return ''  # no alert needed
 
diff --git a/ietf/doc/utils_search.py b/ietf/doc/utils_search.py
--- a/ietf/doc/utils_search.py
+++ b/ietf/doc/utils_search.py
@@ -9,6 +9,7 @@
 from ietf.doc.expire import expirable_drafts
 from ietf.doc.utils import augment_docs_and_user_with_user_info
 from ietf.meeting.models import SessionPresentation, Meeting, Session
+from ietf.review.utils import review_assignments_to_list_for_docs
 
 def wrap_value(v):
     return lambda: v
@@ -112,7 +113,7 @@ def fill_in_document_table_attributes(docs, have_telechat_date=False):
 
         if d.get_state_slug() != "rfc":
             d.milestones = [ m for (t, s, v, m) in sorted(((m.time, m.state.slug, m.desc, m) for m in d.groupmilestone_set.all() if m.state_id == "active")) ]
-            d.reviewed_by_teams = sorted(set(r.team.acronym for r in d.reviewrequest_set.filter(state__in=["assigned","accepted","part-completed","completed"]).distinct().select_related('team')))
+            d.review_assignments = review_assignments_to_list_for_docs([d]).get(d.name, [])
 
         e = d.latest_event_cache.get('started_iesg_process', None)
         d.balloting_started = e.time if e else datetime.datetime.min
@@ -255,4 +256,4 @@ def num(i):
             else:
                 d["sort"] = h["key"]
 
-    return (docs, meta)
\ No newline at end of file
+    return (docs, meta)


Test patch:
diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py
--- a/ietf/doc/tests.py
+++ b/ietf/doc/tests.py
@@ -356,8 +356,8 @@ def test_recent_drafts(self):
         self.assertEqual(r.status_code, 200)
         q = PyQuery(r.content)
         self.assertEqual(len(q('td.doc')),3)
-        self.assertEqual(q('td.status span.badge.bg-warning').text(),"for 15 days")
-        self.assertEqual(q('td.status span.badge.bg-danger').text(),"for 29 days")
+        self.assertTrue(q('td.status span.text-warning *[title*="%s"]' % "for 15 days"))
+        self.assertTrue(q('td.status span.text-danger *[title*="%s"]' % "for 29 days"))
         for ah in [draft.action_holders.first() for draft in drafts]:
             self.assertContains(r, escape(ah.name))
 
@@ -1297,8 +1297,8 @@ def test_edit_authors_edit_fields(self):
     @staticmethod
     def _pyquery_select_action_holder_string(q, s):
         """Helper to use PyQuery to find an action holder in the draft HTML"""
-        # selector grabs the action holders heading and finds siblings with a div containing the search string
-        return q('th:contains("Action Holder") ~ td>div:contains("%s")' % s)
+        # selector grabs the action holders heading and finds siblings with a div containing the search string (also in any title attribute)
+        return q('th:contains("Action Holder") ~ td>div:contains("%s"), th:contains("Action Holder") ~ td>div *[title*="%s"]' % (s, s))
 
     @mock.patch.object(Document, 'action_holders_enabled', return_value=False, new_callable=mock.PropertyMock)
     def test_document_draft_hides_action_holders(self, mock_method):
@@ -2852,4 +2852,4 @@ def test_pdfized(self):
             self.should_succeed(dict(name=rfc.name,rev=f'{r:02d}'))
             for ext in ('pdf','txt','html','anythingatall'):
                 self.should_succeed(dict(name=rfc.name,rev=f'{r:02d}',ext=ext))
-        self.should_404(dict(name=rfc.name,rev='02'))
\ No newline at end of file
+        self.should_404(dict(name=rfc.name,rev='02'))