Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 55 additions & 15 deletions airflow/www/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,16 @@ def get_params(**kwargs):
return urlencode({d: v for d, v in kwargs.items() if v is not None}, True)


def generate_pages(current_page, num_of_pages, search=None, status=None, tags=None, window=7):
def generate_pages(
current_page,
num_of_pages,
search=None,
status=None,
tags=None,
window=7,
sorting_key=None,
sorting_direction=None,
Comment thread
amoghrajesh marked this conversation as resolved.
):
"""
Generates the HTML for a paging component using a similar logic to the paging
auto-generated by Flask managed views. The paging component defines a number of
Expand All @@ -238,6 +247,9 @@ def generate_pages(current_page, num_of_pages, search=None, status=None, tags=No
:param status: 'all', 'active', or 'paused'
:param tags: array of strings of the current filtered tags
:param window: the number of pages to be shown in the paging component (7 default)
: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
:return: the HTML string of the paging component
"""
void_link = "javascript:void(0)"
Expand Down Expand Up @@ -275,9 +287,15 @@ def generate_pages(current_page, num_of_pages, search=None, status=None, tags=No

is_disabled = "disabled" if current_page <= 0 else ""

first_node_link = (
void_link if is_disabled else f"?{get_params(page=0, search=search, status=status, tags=tags)}"
qs = get_params(
page=0,
search=search,
status=status,
tags=tags,
sorting_key=sorting_key,
sorting_direction=sorting_direction,
)
first_node_link = void_link if is_disabled else f"?{qs}"
output.append(
first_node.format(
href_link=first_node_link,
Expand All @@ -287,7 +305,15 @@ def generate_pages(current_page, num_of_pages, search=None, status=None, tags=No

page_link = void_link
if current_page > 0:
page_link = f"?{get_params(page=current_page - 1, search=search, status=status, tags=tags)}"
qs = get_params(
page=current_page - 1,
search=search,
status=status,
tags=tags,
sorting_key=sorting_key,
sorting_direction=sorting_direction,
)
page_link = f"?{qs}"

output.append(previous_node.format(href_link=page_link, disabled=is_disabled))

Expand All @@ -305,30 +331,44 @@ def is_current(current, page):
return page == current

for page in pages:
qs = get_params(
page=page,
search=search,
status=status,
tags=tags,
sorting_key=sorting_key,
sorting_direction=sorting_direction,
)
vals = {
"is_active": "active" if is_current(current_page, page) else "",
"href_link": void_link
if is_current(current_page, page)
else f"?{get_params(page=page, search=search, status=status, tags=tags)}",
"href_link": void_link if is_current(current_page, page) else f"?{qs}",
"page_num": page + 1,
}
output.append(page_node.format(**vals))

is_disabled = "disabled" if current_page >= num_of_pages - 1 else ""

page_link = (
void_link
if current_page >= num_of_pages - 1
else f"?{get_params(page=current_page + 1, search=search, status=status, tags=tags)}"
qs = get_params(
page=current_page + 1,
search=search,
status=status,
tags=tags,
sorting_key=sorting_key,
sorting_direction=sorting_direction,
)
page_link = void_link if current_page >= num_of_pages - 1 else f"?{qs}"

output.append(next_node.format(href_link=page_link, disabled=is_disabled))

last_node_link = (
void_link
if is_disabled
else f"?{get_params(page=last_page, search=search, status=status, tags=tags)}"
qs = get_params(
page=last_page,
search=search,
status=status,
tags=tags,
sorting_key=sorting_key,
sorting_direction=sorting_direction,
)
last_node_link = void_link if is_disabled else f"?{qs}"
output.append(
last_node.format(
href_link=last_node_link,
Expand Down
5 changes: 3 additions & 2 deletions airflow/www/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,8 @@ def _iter_parsed_moved_data_table_names():
search=escape(arg_search_query) if arg_search_query else None,
status=arg_status_filter if arg_status_filter else None,
tags=arg_tags_filter if arg_tags_filter else None,
sorting_key=arg_sorting_key if arg_sorting_key else None,
sorting_direction=arg_sorting_direction if arg_sorting_direction else None,
),
num_runs=num_runs,
tags=tags,
Expand Down Expand Up @@ -3898,8 +3900,7 @@ def audit_log(self, dag_id: str, session=None):
audit_logs_count=audit_logs_count,
page_size=PAGE_SIZE,
paging=wwwutils.generate_pages(
current_page,
num_of_pages,
current_page, num_of_pages, arg_sorting_key, arg_sorting_direction
),
sorting_key=arg_sorting_key,
sorting_direction=arg_sorting_direction,
Expand Down
50 changes: 48 additions & 2 deletions tests/www/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,27 @@


class TestUtils:
def check_generate_pages_html(self, current_page, total_pages, window=7, check_middle=False):
def check_generate_pages_html(
self,
current_page,
total_pages,
window=7,
check_middle=False,
sorting_key=None,
sorting_direction=None,
):
extra_links = 4 # first, prev, next, last
search = "'>\"/><img src=x onerror=alert(1)>"
html_str = utils.generate_pages(current_page, total_pages, search=search)
if sorting_key and sorting_direction:
html_str = utils.generate_pages(
current_page,
total_pages,
search=search,
sorting_key=sorting_key,
sorting_direction=sorting_direction,
)
else:
html_str = utils.generate_pages(current_page, total_pages, search=search)

assert search not in html_str, "The raw search string shouldn't appear in the output"
assert "search=%27%3E%22%2F%3E%3Cimg+src%3Dx+onerror%3Dalert%281%29%3E" in html_str
Expand All @@ -47,10 +64,27 @@ def check_generate_pages_html(self, current_page, total_pages, window=7, check_m

page_items = ulist_items[2:-2]
mid = int(len(page_items) / 2)
all_nodes = []
pages = []

if sorting_key and sorting_direction:
last_page = total_pages - 1

if current_page <= mid or total_pages < window:
pages = list(range(0, min(total_pages, window)))
elif mid < current_page < last_page - mid:
pages = list(range(current_page - mid, current_page + mid + 1))
else:
pages = list(range(total_pages - window, last_page + 1))

pages.append(last_page + 1)
pages.sort(reverse=True if sorting_direction == "desc" else False)

for i, item in enumerate(page_items):
a_node = item.a
href_link = a_node["href"]
node_text = a_node.string
all_nodes.append(node_text)
if node_text == str(current_page + 1):
if check_middle:
assert mid == i
Expand All @@ -62,6 +96,13 @@ def check_generate_pages_html(self, current_page, total_pages, window=7, check_m
assert query["page"] == [str(int(node_text) - 1)]
assert query["search"] == [search]

if sorting_key and sorting_direction:
if pages[0] == 0:
pages = pages[1:]
pages = list(map(lambda x: str(x), pages))

assert pages == all_nodes

def test_generate_pager_current_start(self):
self.check_generate_pages_html(current_page=0, total_pages=6)

Expand All @@ -71,6 +112,11 @@ def test_generate_pager_current_middle(self):
def test_generate_pager_current_end(self):
self.check_generate_pages_html(current_page=38, total_pages=39)

def test_generate_pager_current_start_with_sorting(self):
self.check_generate_pages_html(
current_page=0, total_pages=4, sorting_key="dag_id", sorting_direction="asc"
)

def test_params_no_values(self):
"""Should return an empty string if no params are passed"""
assert "" == utils.get_params()
Expand Down