This is an automated email from the ASF dual-hosted git repository.
bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new c0157e6a3c Support for sorting DAGs in the web UI (#22671)
c0157e6a3c is described below
commit c0157e6a3c6129b143a30954d53e7f49ed4d74f6
Author: pierrejeambrun <[email protected]>
AuthorDate: Sat Apr 9 17:21:00 2022 +0200
Support for sorting DAGs in the web UI (#22671)
* Add sort + small test
* clean code
* Remove useless forgotten macro, fix nullslast for mysql
* Changes following code review
* Remove nullslast
* Changes desc syntax
---
airflow/www/templates/airflow/dags.html | 34 ++++++++++++++++++++++++++++++---
airflow/www/views.py | 18 ++++++++++-------
tests/www/views/test_views_home.py | 19 +++++++++++++++++-
3 files changed, 60 insertions(+), 11 deletions(-)
diff --git a/airflow/www/templates/airflow/dags.html
b/airflow/www/templates/airflow/dags.html
index f062a57ded..ba0e35f8c1 100644
--- a/airflow/www/templates/airflow/dags.html
+++ b/airflow/www/templates/airflow/dags.html
@@ -21,6 +21,34 @@
{% from 'appbuilder/loading_dots.html' import loading_dots %}
{% from 'airflow/_messages.html' import show_message %}
+{%- macro sortable_column(display_name, attribute_name) -%}
+ {% set curr_ordering_direction = (request.args.get('sorting_direction',
'desc')) %}
+ {% set new_ordering_direction = ('asc' if (request.args.get('sorting_key')
!= attribute_name or curr_ordering_direction == 'desc') else 'desc') %}
+ <a href="{{ url_for('Airflow.index',
+ status=request.args.get('status', 'all'),
+ search=request.args.get('search', None),
+ tags=request.args.get('tags', None),
+ sorting_key=attribute_name,
+ sorting_direction=new_ordering_direction
+ ) }}"
+ class="js-tooltip"
+ role="tooltip"
+ title="Sort by {{ new_ordering_direction }} {{ attribute_name }}."
+ >
+ {{ display_name }}
+
+ <span class="material-icons" aria-hidden="true"
aria-describedby="sorting-tip-{{ display_name }}">
+ {% if curr_ordering_direction == 'desc' and
request.args.get('sorting_key') == attribute_name %}
+ expand_more
+ {% elif curr_ordering_direction == 'asc' and
request.args.get('sorting_key') == attribute_name %}
+ expand_less
+ {% else %}
+ unfold_more
+ {% endif %}
+ </span>
+ </a>
+{%- endmacro -%}
+
{% block page_title %}
{% if search_query %}"{{ search_query }}" - {% endif %}DAGs - {{
appbuilder.app_name }}
{% endblock %}
@@ -122,8 +150,8 @@
<th width="12">
<span class="material-icons text-muted js-tooltip" title="Use
this toggle to pause/unpause a DAG. The scheduler won't schedule new tasks
instances for a paused DAG. Tasks already running at pause time won't be
affected.">info</span>
</th>
- <th>DAG</th>
- <th>Owner</th>
+ <th>{{ sortable_column("DAG", "dag_id") }}</th>
+ <th>{{ sortable_column("Owner", "owners") }}</th>
<th>Runs
<span class="material-icons text-muted js-tooltip"
aria-hidden="true" title="Status of all previous DAG runs.">info</span>
</th>
@@ -131,7 +159,7 @@
<th style="width:180px;">Last Run
<span class="material-icons text-muted js-tooltip"
aria-hidden="true" title="Date/Time of the latest Dag Run.">info</span>
</th>
- <th style="width:180px;">Next Run
+ <th style="width:180px;">{{ sortable_column("Next Run",
"next_dagrun") }}
<span class="material-icons text-muted js-tooltip"
aria-hidden="true" title="Expected Date/Time of the next Dag Run.">info</span>
</th>
<th>Recent Tasks
diff --git a/airflow/www/views.py b/airflow/www/views.py
index edc2de32c6..f9cebf90b7 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -686,6 +686,8 @@ class Airflow(AirflowBaseView):
arg_search_query = request.args.get('search')
arg_tags_filter = request.args.getlist('tags')
arg_status_filter = request.args.get('status')
+ arg_sorting_key = request.args.get('sorting_key', 'dag_id')
+ arg_sorting_direction = request.args.get('sorting_direction',
default='asc')
if request.args.get('reset_tags') is not None:
flask_session[FILTER_TAGS_COOKIE] = None
@@ -756,13 +758,13 @@ class Airflow(AirflowBaseView):
current_dags = all_dags
num_of_all_dags = all_dags_count
- dags = (
- current_dags.order_by(DagModel.dag_id)
- .options(joinedload(DagModel.tags))
- .offset(start)
- .limit(dags_per_page)
- .all()
- )
+ sort_column = DagModel.__table__.c.get(arg_sorting_key)
+ if sort_column is not None:
+ if arg_sorting_direction == 'desc':
+ sort_column = sort_column.desc()
+ current_dags = current_dags.order_by(sort_column)
+
+ dags =
current_dags.options(joinedload(DagModel.tags)).offset(start).limit(dags_per_page).all()
user_permissions = g.user.perms
all_dags_editable = (permissions.ACTION_CAN_EDIT,
permissions.RESOURCE_DAG) in user_permissions
can_create_dag_run = (
@@ -890,6 +892,8 @@ class Airflow(AirflowBaseView):
status_count_active=status_count_active,
status_count_paused=status_count_paused,
tags_filter=arg_tags_filter,
+ sorting_key=arg_sorting_key,
+ sorting_direction=arg_sorting_direction,
)
@expose('/dag_stats', methods=['POST'])
diff --git a/tests/www/views/test_views_home.py
b/tests/www/views/test_views_home.py
index 470643ccde..1d4c126e5d 100644
--- a/tests/www/views/test_views_home.py
+++ b/tests/www/views/test_views_home.py
@@ -117,7 +117,7 @@ def client_single_dag(app, user_single_dag):
)
-TEST_FILTER_DAG_IDS = ['filter_test_1', 'filter_test_2']
+TEST_FILTER_DAG_IDS = ['filter_test_1', 'filter_test_2', 'a_first_dag_id_asc']
def _process_file(file_path, session):
@@ -251,3 +251,20 @@ def test_audit_log_view(user_client, working_dags):
url = 'audit_log?dag_id=filter_test_1'
resp = user_client.get(url, follow_redirects=True)
check_content_in_response('Dag Audit Log', resp)
+
+
[email protected](
+ "url, lower_key, greater_key",
+ [
+ ("home?status=all", "a_first_dag_id_asc", "filter_test_1"),
+ ("home?status=all&sorting_key=dag_id&sorting_direction=asc",
"filter_test_1", "filter_test_2"),
+ ("home?status=all&sorting_key=dag_id&sorting_direction=desc",
"filter_test_2", "filter_test_1"),
+ ],
+ ids=["no_order_provided", "ascending_order_on_dag_id",
"descending_order_on_dag_id"],
+)
+def test_sorting_home_view(url, lower_key, greater_key, user_client,
working_dags):
+ resp = user_client.get(url, follow_redirects=True)
+ resp_html = resp.data.decode('utf-8')
+ lower_index = resp_html.find(lower_key)
+ greater_index = resp_html.find(greater_key)
+ assert lower_index < greater_index