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)>"