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

Reply via email to