Repository: ietf-tools/datatracker

Issue:
Add editorial stream to meeting materials view
### Description

With #6027, `rsab` and `rswg` are listed under **Editorial stream** but at the moment meeting materials view lists the `rsab` and `rswg` under [**Other Miscellaneous other sessions**](https://datatracker.ietf.org/meeting/117/materials/#other).

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)
cc slide proposer on upload email
### Description

When proposing slides for a meeting an email is sent to the chairs, responsible AD, etc.  But the proposer has only the confirmation text at the top of the resulting page to know that slides were successfully proposed.

One simple thing might be to cc the email address(es) associated with the datatracker login that proposed the slides on the email informing leadership of same.

### Code of Conduct

- [X] I agree to follow the [IETF's Code of Conduct](https://github.com/ietf-tools/.github/blob/main/CODE_OF_CONDUCT.md)


Discussion:

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
@@ -212,9 +212,9 @@ def state_age_colored(doc):
             goal1 = 14
             goal2 = 28
         if days > goal2:
-            class_name = "bg-danger"
+            class_name = "text-bg-danger"
         elif days > goal1:
-            class_name = "bg-warning"
+            class_name = "text-bg-warning"
         else:
             # don't show a badge when things are in the green; clutters display
             # class_name = "text-success"
@@ -247,6 +247,6 @@ def auth48_alert_badge(doc):
 
     rfced_state = doc.get_state_slug('draft-rfceditor')
     if rfced_state == 'auth48':
-        return mark_safe('<span class="badge rounded-pill bg-info" title="AUTH48">AUTH48</span>')
+        return mark_safe('<span class="badge rounded-pill text-bg-info" title="AUTH48">AUTH48</span>')
 
     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
@@ -704,10 +704,10 @@ def action_holder_badge(action_holder):
     ''
 
     >>> action_holder_badge(DocumentActionHolderFactory(time_added=timezone.now() - datetime.timedelta(days=16)))
-    '<span class="badge rounded-pill bg-danger" title="In state for 16 days; goal is &lt;15 days."><i class="bi bi-clock-fill"></i> 16</span>'
+    '<span class="badge rounded-pill text-bg-danger" title="In state for 16 days; goal is &lt;15 days."><i class="bi bi-clock-fill"></i> 16</span>'
 
     >>> action_holder_badge(DocumentActionHolderFactory(time_added=timezone.now() - datetime.timedelta(days=30)))
-    '<span class="badge rounded-pill bg-danger" title="In state for 30 days; goal is &lt;15 days."><i class="bi bi-clock-fill"></i> 30</span>'
+    '<span class="badge rounded-pill text-bg-danger" title="In state for 30 days; goal is &lt;15 days."><i class="bi bi-clock-fill"></i> 30</span>'
 
     >>> settings.DOC_ACTION_HOLDER_AGE_LIMIT_DAYS = old_limit
     """
@@ -715,7 +715,7 @@ def action_holder_badge(action_holder):
     age = (timezone.now() - action_holder.time_added).days
     if age > age_limit:
         return mark_safe(
-            '<span class="badge rounded-pill bg-danger" title="In state for %d day%s; goal is &lt;%d days."><i class="bi bi-clock-fill"></i> %d</span>'
+            '<span class="badge rounded-pill text-bg-danger" title="In state for %d day%s; goal is &lt;%d days."><i class="bi bi-clock-fill"></i> %d</span>'
             % (age, "s" if age != 1 else "", age_limit, age)
         )
     else:
@@ -842,3 +842,36 @@ def is_valid_url(url):
     except ValidationError:
         return False
     return True
+
+
+@register.filter
+def badgeify(blob):
+    """
+    Add an appropriate bootstrap badge around "text", based on its contents.
+    """
+    config = [
+        (r"rejected|not ready", "danger", "x-lg"),
+        (r"complete|accepted|ready", "success", ""),
+        (r"has nits|almost ready", "info", "info-lg"),
+        (r"has issues", "warning", "exclamation-lg"),
+        (r"assigned", "info", "person-plus-fill"),
+        (r"will not review|overtaken by events|withdrawn", "secondary", "dash-lg"),
+    ]
+    text = str(blob)
+
+    for pattern, color, icon in config:
+        if re.search(pattern, text, flags=re.IGNORECASE):
+            # Shorten the badge text
+            text = re.sub(r"with ", "w/", text, flags=re.IGNORECASE)
+            text = re.sub(r"document", "doc", text, flags=re.IGNORECASE)
+            text = re.sub(r"will not", "won't", text, flags=re.IGNORECASE)
+
+            return mark_safe(
+                f"""
+                <span class="badge rounded-pill text-bg-{color}">
+                    <i class="bi bi-{icon}"></i> {text.capitalize()}
+                </span>
+                """
+            )
+
+    return text
diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py
--- a/ietf/doc/views_doc.py
+++ b/ietf/doc/views_doc.py
@@ -542,6 +542,7 @@ def document_main(request, name, rev=None, document_html=False):
                 html = doc.html_body()
                 if request.COOKIES.get("pagedeps") == "inline":
                     js = Path(finders.find("ietf/js/document_html.js")).read_text()
+                    js += Path(finders.find("ietf/js/theme.js")).read_text()
                     css = Path(finders.find("ietf/css/document_html_inline.css")).read_text()
                     if html:
                         css += Path(finders.find("ietf/css/document_html_txt.css")).read_text()


Test patch:
diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py
--- a/ietf/doc/tests.py
+++ b/ietf/doc/tests.py
@@ -445,8 +445,8 @@ def test_recent_drafts(self):
         self.assertEqual(r.status_code, 200)
         q = PyQuery(r.content)
         self.assertEqual(len(q('td.doc')),3)
-        self.assertTrue(q('td.status span.bg-warning[title*="%s"]' % "for 15 days"))
-        self.assertTrue(q('td.status span.bg-danger[title*="%s"]' % "for 29 days"))
+        self.assertTrue(q('td.status span.text-bg-warning[title*="%s"]' % "for 15 days"))
+        self.assertTrue(q('td.status span.text-bg-danger[title*="%s"]' % "for 29 days"))
         for ah in [draft.action_holders.first() for draft in drafts]:
             self.assertContains(r, escape(ah.name))
 
diff --git a/ietf/group/tests_info.py b/ietf/group/tests_info.py
--- a/ietf/group/tests_info.py
+++ b/ietf/group/tests_info.py
@@ -472,13 +472,13 @@ def test_nonactive_group_badges(self):
         r = self.client.get(url)
         self.assertEqual(r.status_code,200)
         q = PyQuery(r.content)
-        self.assertEqual(q('.badge.bg-warning').text(),"Concluded WG")
+        self.assertEqual(q('.badge.text-bg-warning').text(),"Concluded WG")
         replaced_group = GroupFactory(state_id='replaced')
         url = urlreverse("ietf.group.views.history",kwargs={'acronym':replaced_group.acronym})
         r = self.client.get(url)
         self.assertEqual(r.status_code,200)
         q = PyQuery(r.content)
-        self.assertEqual(q('.badge.bg-warning').text(),"Replaced WG")
+        self.assertEqual(q('.badge.text-bg-warning').text(),"Replaced WG")
 
 
 class GroupEditTests(TestCase):
diff --git a/ietf/nomcom/tests.py b/ietf/nomcom/tests.py
--- a/ietf/nomcom/tests.py
+++ b/ietf/nomcom/tests.py
@@ -1485,7 +1485,7 @@ def test_feedback_index_badges(self):
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 4 )
+        self.assertEqual( len(q('.text-bg-success')), 4 )
 
         f = self.nc.feedback_set.first()
         f.time = self.hour_ago
@@ -1495,20 +1495,20 @@ def test_feedback_index_badges(self):
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 3 )
+        self.assertEqual( len(q('.text-bg-success')), 3 )
 
         FeedbackLastSeen.objects.update(time=self.second_from_now)
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 1 )
+        self.assertEqual( len(q('.text-bg-success')), 1 )
 
         TopicFeedbackLastSeen.objects.create(reviewer=self.member,topic=self.topic)
         TopicFeedbackLastSeen.objects.update(time=self.second_from_now)
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 0 )
+        self.assertEqual( len(q('.text-bg-success')), 0 )
 
     def test_feedback_nominee_badges(self):
         url = reverse('ietf.nomcom.views.view_feedback_nominee', kwargs={'year':self.nc.year(), 'nominee_id':self.nominee.id})
@@ -1517,7 +1517,7 @@ def test_feedback_nominee_badges(self):
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 3 )
+        self.assertEqual( len(q('.text-bg-success')), 3 )
 
         f = self.nc.feedback_set.first()
         f.time = self.hour_ago
@@ -1527,13 +1527,13 @@ def test_feedback_nominee_badges(self):
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 2 )
+        self.assertEqual( len(q('.text-bg-success')), 2 )
 
         FeedbackLastSeen.objects.update(time=self.second_from_now)
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 0 )
+        self.assertEqual( len(q('.text-bg-success')), 0 )
 
     def test_feedback_topic_badges(self):
         url = reverse('ietf.nomcom.views.view_feedback_topic', kwargs={'year':self.nc.year(), 'topic_id':self.topic.id})
@@ -1542,7 +1542,7 @@ def test_feedback_topic_badges(self):
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 1 )
+        self.assertEqual( len(q('.text-bg-success')), 1 )
 
         f = self.topic.feedback_set.first()
         f.time = self.hour_ago
@@ -1552,13 +1552,13 @@ def test_feedback_topic_badges(self):
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 0 )
+        self.assertEqual( len(q('.text-bg-success')), 0 )
 
         TopicFeedbackLastSeen.objects.update(time=self.second_from_now)
         response = self.client.get(url)
         self.assertEqual(response.status_code,200)
         q = PyQuery(response.content)
-        self.assertEqual( len(q('.bg-success')), 0 )
+        self.assertEqual( len(q('.text-bg-success')), 0 )
 
 class NewActiveNomComTests(TestCase):