Repository: ietf-tools/datatracker

Issue:
Can't filter for just IETF meetings
### Description

Can't filter for just IETF meetings on https://datatracker.ietf.org/meeting/upcoming

### 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/meeting/helpers.py b/ietf/meeting/helpers.py
--- a/ietf/meeting/helpers.py
+++ b/ietf/meeting/helpers.py
@@ -256,6 +256,10 @@ def __init__(self, *, single_category=False, **kwargs):
         self.special_filters = None
         if self._use_legacy_keywords():
             self.extra_labels += ('Plenary',)  # need this when not using session purpose
+        self.manual_extra_labels = set()
+
+    def add_extra_filter(self, kw):
+        self.manual_extra_labels.add(kw)
 
     def get_non_area_keywords(self):
         """Get list of any 'non-area' (aka 'special') keywords
@@ -436,13 +440,13 @@ def _legacy_non_group_filters(self, sessions):
     def _extra_filters(self):
         """Get list of filters corresponding to self.extra_labels"""
         item_source = self.assignments or self.sessions or []
-        candidates = set(self.extra_labels)
+        candidates = set(self.extra_labels).union(self.manual_extra_labels)
         return self._filter_column(
             label=None,
             keyword=None,
             children=[
                 self._filter_entry(label=label, keyword=xslugify(label), toggled_by=[], is_bof=False)
-                for label in candidates if any(
+                for label in candidates if label in self.manual_extra_labels or any(
                     # Keep only those that will affect at least one session
                     [label.lower() in item.filter_keywords for item in item_source]
                 )]
diff --git a/ietf/meeting/views.py b/ietf/meeting/views.py
--- a/ietf/meeting/views.py
+++ b/ietf/meeting/views.py
@@ -3456,6 +3456,10 @@ def upcoming(request):
     # Set up for agenda filtering - only one filter_category here
     AgendaKeywordTagger(sessions=interim_sessions).apply()
     filter_organizer = AgendaFilterOrganizer(sessions=interim_sessions, single_category=True)
+    # Allow filtering to show only IETF Meetings. This adds a button labeled "IETF Meetings" to the
+    # "Other" column of the filter UI. When enabled, this adds the keyword "ietf-meetings" to the "show"
+    # filter list. The IETF meetings are explicitly labeled with this keyword in upcoming.html.
+    filter_organizer.add_extra_filter('IETF Meetings')
 
     entries = list(ietf_meetings)
     entries.extend(list(interim_sessions))
@@ -3544,9 +3548,14 @@ def upcoming_ical(request):
             a.session = sessions.get(a.session_id) or a.session
             a.session.ical_status = ical_session_status(a)
 
-    # handle IETFs separately
-    ietfs = [m for m in meetings if m.type_id == 'ietf']
-    preprocess_meeting_important_dates(ietfs)
+    # Handle IETFs separately. Manually apply the 'ietf-meetings' filter.
+    if filter_params is None or (
+            'ietf-meetings' in filter_params['show'] and 'ietf-meetings' not in filter_params['hide']
+    ):
+        ietfs = [m for m in meetings if m.type_id == 'ietf']
+        preprocess_meeting_important_dates(ietfs)
+    else:
+        ietfs = []
 
     meeting_vtz = {meeting.vtimezone() for meeting in meetings}
     meeting_vtz.discard(None)


Test patch:
diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py
--- a/ietf/meeting/tests_js.py
+++ b/ietf/meeting/tests_js.py
@@ -1076,9 +1076,9 @@ def advance_month():
                     unexpected.add(entry.text)
             advance_month()
 
-        self.assertEqual(seen, visible_meetings, "Expected calendar entries not shown.")
-        self.assertEqual(not_visible, set(), "Hidden calendar entries for expected interim meetings.")
-        self.assertEqual(unexpected, set(), "Unexpected calendar entries visible")
+        self.assertCountEqual(seen, visible_meetings, "Expected calendar entries not shown.")
+        self.assertCountEqual(not_visible, set(), "Hidden calendar entries for expected interim meetings.")
+        self.assertCountEqual(unexpected, set(), "Unexpected calendar entries visible")
 
     def do_upcoming_view_filter_test(self, querystring, visible_meetings=()):
         self.login()
@@ -1152,102 +1152,85 @@ def test_upcoming_view_default(self):
         ietf_meetings = set(self.all_ietf_meetings())
         self.do_upcoming_view_filter_test('', ietf_meetings.union(self.displayed_interims()))
 
+    def test_upcoming_view_show_ietf_meetings(self):
+        self.do_upcoming_view_filter_test('?show=ietf-meetings', self.all_ietf_meetings())
+
     def test_upcoming_view_filter_show_group(self):
         # Show none
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?show=', ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=')
 
         # Show one
-        self.do_upcoming_view_filter_test('?show=mars', 
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['mars'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=mars', self.displayed_interims(groups=['mars']))
 
         # Show two
-        self.do_upcoming_view_filter_test('?show=mars,ames', 
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['mars', 'ames'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=mars,ames',self.displayed_interims(groups=['mars', 'ames']))
+
+        # Show two plus ietf-meetings
+        self.do_upcoming_view_filter_test(
+            '?show=ietf-meetings,mars,ames',
+            set(self.all_ietf_meetings()).union(self.displayed_interims(groups=['mars', 'ames']))
+        )
 
     def test_upcoming_view_filter_show_area(self):
         mars = Group.objects.get(acronym='mars')
         area = mars.parent
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?show=%s' % area.acronym,
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['mars', 'ames'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=%s' % area.acronym, self.displayed_interims(groups=['mars', 'ames']))
 
     def test_upcoming_view_filter_show_type(self):
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?show=plenary',
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['sg'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=plenary', self.displayed_interims(groups=['sg']))
 
     def test_upcoming_view_filter_hide_group(self):
         mars = Group.objects.get(acronym='mars')
         area = mars.parent
 
         # Without anything shown, should see only ietf meetings
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?hide=mars', ietf_meetings)
+        self.do_upcoming_view_filter_test('?hide=mars')
 
         # With group shown
-        self.do_upcoming_view_filter_test('?show=ames,mars&hide=mars',
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['ames'])
-                                          ))
+        self.do_upcoming_view_filter_test('?show=ames,mars&hide=mars', self.displayed_interims(groups=['ames']))
         # With area shown
-        self.do_upcoming_view_filter_test('?show=%s&hide=mars' % area.acronym, 
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['ames'])
-                                          ))
-
+        self.do_upcoming_view_filter_test('?show=%s&hide=mars' % area.acronym, self.displayed_interims(groups=['ames']))
         # With type shown
-        self.do_upcoming_view_filter_test('?show=plenary&hide=sg',
-                                          ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=plenary&hide=sg')
 
     def test_upcoming_view_filter_hide_area(self):
         mars = Group.objects.get(acronym='mars')
         area = mars.parent
 
-        # Without anything shown, should see only ietf meetings
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?hide=%s' % area.acronym, ietf_meetings)
+        # Without anything shown, should see nothing
+        self.do_upcoming_view_filter_test('?hide=%s' % area.acronym)
 
         # With area shown
-        self.do_upcoming_view_filter_test('?show=%s&hide=%s' % (area.acronym, area.acronym),
-                                          ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=%s&hide=%s' % (area.acronym, area.acronym))
 
         # With group shown
-        self.do_upcoming_view_filter_test('?show=mars&hide=%s' % area.acronym, ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=mars&hide=%s' % area.acronym)
 
         # With type shown
-        self.do_upcoming_view_filter_test('?show=regular&hide=%s' % area.acronym, ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=regular&hide=%s' % area.acronym)
 
-    def test_upcoming_view_filter_hide_type(self):
-        mars = Group.objects.get(acronym='mars')
-        area = mars.parent
+        # with IETF meetings shown
+        self.do_upcoming_view_filter_test('?show=ietf-meetings,hide=%s' % area.acronym, self.all_ietf_meetings())
 
-        # Without anything shown, should see only ietf meetings
-        ietf_meetings = set(self.all_ietf_meetings())
-        self.do_upcoming_view_filter_test('?hide=regular', ietf_meetings)
+    def test_upcoming_view_filter_hide_type(self):
+        # Without anything shown, should see nothing
+        self.do_upcoming_view_filter_test('?hide=regular')
 
         # With group shown
-        self.do_upcoming_view_filter_test('?show=mars&hide=regular', ietf_meetings)
+        self.do_upcoming_view_filter_test('?show=mars&hide=regular')
 
         # With type shown
-        self.do_upcoming_view_filter_test('?show=plenary,regular&hide=%s' % area.acronym, 
-                                          ietf_meetings.union(
-                                              self.displayed_interims(groups=['sg'])
-                                          ))
+        self.do_upcoming_view_filter_test(
+            '?show=plenary,regular&hide=regular',
+            self.displayed_interims(groups=['sg'])
+        )
+
+        # With interim-meetings shown
+        self.do_upcoming_view_filter_test('?show=plenary,regular&hide=regular', self.displayed_interims(groups=['sg']))
 
     def test_upcoming_view_filter_whitespace(self):
         """Whitespace in filter lists should be ignored"""
-        meetings = set(self.all_ietf_meetings())
-        meetings.update(self.displayed_interims(groups=['mars']))
-        self.do_upcoming_view_filter_test('?show=mars , ames &hide=   ames', meetings)
+        self.do_upcoming_view_filter_test('?show=mars , ames &hide=   ames', self.displayed_interims(groups=['mars']))
 
     def test_upcoming_view_time_zone_selection(self):
         def _assert_interim_tz_correct(sessions, tz):
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -4507,8 +4507,16 @@ def test_upcoming_ical_filter(self):
         # Just a quick check of functionality - details tested by test_js.InterimTests
         make_meeting_test_data(create_interims=True)
         url = urlreverse("ietf.meeting.views.upcoming_ical")
+
         r = self.client.get(url + '?show=mars')
+        self.assertEqual(r.status_code, 200)
+        assert_ical_response_is_valid(self, r,
+                                      expected_event_summaries=[
+                                          'mars - Martian Special Interest Group',
+                                      ],
+                                      expected_event_count=1)
 
+        r = self.client.get(url + '?show=mars,ietf-meetings')
         self.assertEqual(r.status_code, 200)
         assert_ical_response_is_valid(self, r,
                                       expected_event_summaries=[