This is an automated email from the ASF dual-hosted git repository.

pierrejeambrun pushed a commit to branch v2-11-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v2-11-test by this push:
     new a54d5392ce6 fix(55017): DAG List Pagination with 'Failed' Filter by 
Preserving lastrun Parameter Across Pages (#64472)
a54d5392ce6 is described below

commit a54d5392ce629acffed9359f8b06236415426bfb
Author: Mingjie Zhao <[email protected]>
AuthorDate: Thu Apr 2 23:42:38 2026 +0800

    fix(55017): DAG List Pagination with 'Failed' Filter by Preserving lastrun 
Parameter Across Pages (#64472)
    
    * fix(55017): DAG List Pagination with 'Failed' Filter by Preserving 
lastrun Parameter Across Pages
    
    * Add tests
    
    * Update docstring
    
    ---------
    
    Co-authored-by: Mingjie Zhao <[email protected]>
    Co-authored-by: Jarek Potiuk <[email protected]>
---
 airflow/www/utils.py    | 14 +++++++++++---
 airflow/www/views.py    |  1 +
 tests/www/test_utils.py | 19 +++++++++++++++++++
 3 files changed, 31 insertions(+), 3 deletions(-)

diff --git a/airflow/www/utils.py b/airflow/www/utils.py
index bccc597c8de..a1bb7a9c2eb 100644
--- a/airflow/www/utils.py
+++ b/airflow/www/utils.py
@@ -262,6 +262,7 @@ def generate_pages(
     window=7,
     sorting_key=None,
     sorting_direction=None,
+    lastrun=None,
 ):
     """
     Generate the HTML for a paging component.
@@ -272,9 +273,10 @@ def generate_pages(
     and keeps the current one in the middle of the pager component. When in 
the last pages,
     the pages won't scroll and just keep moving until the last page. Pager 
also contains
     <first, previous, ..., next, last> pages.
-    This component takes into account custom parameters such as search, 
status, and tags
-    which could be added to the pages link in order to maintain the state 
between
-    client and server. It also allows to make a bookmark on a specific paging 
state.
+    This component takes into account custom parameters such as search, 
status, tags,
+    sorting_key, sorting_direction, and lastrun which could be added to the 
pages link
+    in order to maintain the state between client and server. It also allows 
to make
+    a bookmark on a specific paging state.
 
     :param current_page: the current page number, 0-indexed
     :param num_of_pages: the total number of pages
@@ -285,6 +287,7 @@ def generate_pages(
     :param sorting_key: the sorting key selected for dags, None indicates that 
sorting is not needed/provided
     :param sorting_direction: direction of sorting, 'asc' or 'desc',
     None indicates that sorting is not needed/provided
+    :param lastrun: the lastrun filter, 'running', 'failed', or None
     :return: the HTML string of the paging component
     """
     void_link = "javascript:void(0)"
@@ -329,6 +332,7 @@ def generate_pages(
         tags=tags,
         sorting_key=sorting_key,
         sorting_direction=sorting_direction,
+        lastrun=lastrun,
     )
     first_node_link = void_link if is_disabled else f"?{qs}"
     output.append(
@@ -347,6 +351,7 @@ def generate_pages(
             tags=tags,
             sorting_key=sorting_key,
             sorting_direction=sorting_direction,
+            lastrun=lastrun,
         )
         page_link = f"?{qs}"
 
@@ -373,6 +378,7 @@ def generate_pages(
             tags=tags,
             sorting_key=sorting_key,
             sorting_direction=sorting_direction,
+            lastrun=lastrun,
         )
         vals = {
             "is_active": "active" if is_current(current_page, page) else "",
@@ -390,6 +396,7 @@ def generate_pages(
         tags=tags,
         sorting_key=sorting_key,
         sorting_direction=sorting_direction,
+        lastrun=lastrun,
     )
     page_link = void_link if current_page >= num_of_pages - 1 else f"?{qs}"
 
@@ -402,6 +409,7 @@ def generate_pages(
         tags=tags,
         sorting_key=sorting_key,
         sorting_direction=sorting_direction,
+        lastrun=lastrun,
     )
     last_node_link = void_link if is_disabled else f"?{qs}"
     output.append(
diff --git a/airflow/www/views.py b/airflow/www/views.py
index 284733857e8..59726b95f6d 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -1123,6 +1123,7 @@ class Airflow(AirflowBaseView):
                 tags=arg_tags_filter or None,
                 sorting_key=arg_sorting_key or None,
                 sorting_direction=arg_sorting_direction or None,
+                lastrun=arg_lastrun_filter or None,
             ),
             num_runs=num_runs,
             tags=tags,
diff --git a/tests/www/test_utils.py b/tests/www/test_utils.py
index b947e268dfd..6ef6fe974ea 100644
--- a/tests/www/test_utils.py
+++ b/tests/www/test_utils.py
@@ -142,6 +142,21 @@ class TestUtils:
             current_page=0, total_pages=4, sorting_key="dag_id", 
sorting_direction="asc"
         )
 
+    @pytest.mark.parametrize("lastrun", ["failed", "running"])
+    def test_generate_pager_preserves_lastrun_in_page_links(self, lastrun):
+        """Pagination hrefs must keep lastrun so filters survive page changes 
(e.g. failed DAGs)."""
+        html_str = utils.generate_pages(
+            current_page=1,
+            num_of_pages=4,
+            lastrun=lastrun,
+        )
+        dom = BeautifulSoup(html_str, "html.parser")
+        for a in dom.find_all("a", href=True):
+            href = a["href"]
+            if href.startswith("?"):
+                query = parse_qs(href[1:])
+                assert query.get("lastrun") == [lastrun], href
+
     def test_params_no_values(self):
         """Should return an empty string if no params are passed"""
         assert "" == utils.get_params()
@@ -164,6 +179,10 @@ class TestUtils:
             "status": ["active"],
         } == parse_qs(query)
 
+    def test_params_lastrun(self):
+        query = utils.get_params(page=2, lastrun="failed")
+        assert parse_qs(query) == {"page": ["2"], "lastrun": ["failed"]}
+
     def test_params_escape(self):
         assert 
"search=%27%3E%22%2F%3E%3Cimg+src%3Dx+onerror%3Dalert%281%29%3E" == 
utils.get_params(
             search="'>\"/><img src=x onerror=alert(1)>"

Reply via email to