You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by bb...@apache.org on 2022/08/05 14:42:05 UTC

[airflow] branch main updated: Show dataset-triggered next run details (#25549)

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 45e5150714 Show dataset-triggered next run details (#25549)
45e5150714 is described below

commit 45e5150714e0a5a8e82e3fa6d0b337b92cbeb067
Author: Brent Bovenzi <br...@gmail.com>
AuthorDate: Fri Aug 5 15:41:49 2022 +0100

    Show dataset-triggered next run details (#25549)
    
    * add dataset-triggered next run details modal
    
    * use correct url
    
    * add tooltip telling users to click
    
    * use macro for modal
    
    * fix datasets url
---
 airflow/www/static/css/main.css                    |  5 ++
 airflow/www/static/js/dag.js                       |  6 ++
 airflow/www/static/js/dags.js                      |  7 +++
 airflow/www/static/js/openDatasetModal.js          | 67 ++++++++++++++++++++++
 airflow/www/templates/airflow/dag.html             | 20 ++++++-
 airflow/www/templates/airflow/dags.html            | 18 +++++-
 .../templates/airflow/dataset_next_run_modal.html  | 56 ++++++++++++++++++
 7 files changed, 175 insertions(+), 4 deletions(-)

diff --git a/airflow/www/static/css/main.css b/airflow/www/static/css/main.css
index e735b7b9bb..2557f521e6 100644
--- a/airflow/www/static/css/main.css
+++ b/airflow/www/static/css/main.css
@@ -473,3 +473,8 @@ details summary {
   max-height: 300px;
   overflow-y: auto;
 }
+
+.next-dataset-triggered:hover {
+  cursor: pointer;
+  background-color: #cbcbcb;
+}
diff --git a/airflow/www/static/js/dag.js b/airflow/www/static/js/dag.js
index c6d7f4223f..d4e5dc2255 100644
--- a/airflow/www/static/js/dag.js
+++ b/airflow/www/static/js/dag.js
@@ -21,6 +21,7 @@
 
 import { getMetaValue } from './utils';
 import { approxTimeFromNow, formatDateTime } from './datetime_utils';
+import openDatasetModal from './openDatasetModal';
 
 function updateQueryStringParameter(uri, key, value) {
   const re = new RegExp(`([?&])${key}=.*?(&|$)`, 'i');
@@ -399,3 +400,8 @@ $('#next-run').on('mouseover', () => {
     return newTitle;
   });
 });
+
+$('.next-dataset-triggered').on('click', (e) => {
+  const summary = $(e.target).data('summary');
+  openDatasetModal(dagId, summary || '');
+});
diff --git a/airflow/www/static/js/dags.js b/airflow/www/static/js/dags.js
index bdf52b6da9..c6817d038e 100644
--- a/airflow/www/static/js/dags.js
+++ b/airflow/www/static/js/dags.js
@@ -23,6 +23,7 @@
 import { getMetaValue } from './utils';
 import tiTooltip from './task_instances';
 import { approxTimeFromNow, formatDateTime } from './datetime_utils';
+import openDatasetModal from './openDatasetModal';
 
 const DAGS_INDEX = getMetaValue('dags_index');
 const ENTER_KEY_CODE = 13;
@@ -537,3 +538,9 @@ $('#auto_refresh').change(() => {
   }
   startOrStopRefresh();
 });
+
+$('.next-dataset-triggered').on('click', (e) => {
+  const dagId = $(e.target).data('dag-id');
+  const summary = $(e.target).data('summary');
+  if (dagId) openDatasetModal(dagId, summary || '');
+});
diff --git a/airflow/www/static/js/openDatasetModal.js b/airflow/www/static/js/openDatasetModal.js
new file mode 100644
index 0000000000..6e6c824e1d
--- /dev/null
+++ b/airflow/www/static/js/openDatasetModal.js
@@ -0,0 +1,67 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/* global $, isoDateToTimeEl, document */
+
+import { getMetaValue } from './utils';
+
+function openDatasetModal(dagId, summary) {
+  const datasetsUrl = getMetaValue('datasets_url');
+  let nextRunUrl = getMetaValue('next_run_datasets_url');
+  $('#datasets_tbody').empty();
+  $('#datasets_error').hide();
+  $('#datasetNextRunModal').modal({});
+  $('#dag_id').text(dagId);
+  $('#next_run_summary').text(summary);
+  $('#datasets-loading-dots').css('display', 'inline-block');
+  if (dagId) {
+    if (nextRunUrl.includes('__DAG_ID__')) {
+      nextRunUrl = nextRunUrl.replace('__DAG_ID__', dagId);
+    }
+    $.get(nextRunUrl)
+      .done(
+        (datasets) => {
+          datasets.forEach((d) => {
+            const row = document.createElement('tr');
+
+            const uriCell = document.createElement('td');
+            const datasetLink = document.createElement('a');
+            datasetLink.href = `${datasetsUrl}?dataset_id=${d.id}`;
+            datasetLink.innerText = d.uri;
+            uriCell.append(datasetLink);
+
+            const timeCell = document.createElement('td');
+            if (d.created_at) timeCell.append(isoDateToTimeEl(d.created_at));
+
+            row.append(uriCell);
+            row.append(timeCell);
+            $('#datasets-loading-dots').hide();
+            $('#datasets_tbody').append(row);
+          });
+        },
+      ).fail((response, textStatus, err) => {
+        $('#datasets-loading-dots').hide();
+        const description = (response.responseJSON && response.responseJSON.error) || 'Something went wrong.';
+        $('#datasets_error_msg').text(`${textStatus}: ${err} ${description}`);
+        $('#datasets_error').show();
+      });
+  }
+}
+
+export default openDatasetModal;
diff --git a/airflow/www/templates/airflow/dag.html b/airflow/www/templates/airflow/dag.html
index 7501eeb295..20a2f06e54 100644
--- a/airflow/www/templates/airflow/dag.html
+++ b/airflow/www/templates/airflow/dag.html
@@ -18,6 +18,7 @@
 #}
 
 {% extends base_template %}
+{% from 'airflow/dataset_next_run_modal.html' import dataset_next_run_modal %}
 {% from 'appbuilder/dag_docs.html' import dag_docs %}
 
 {% block page_title %}{{ dag.dag_id }} - {{ appbuilder.app_name }}{% endblock %}
@@ -54,8 +55,10 @@
   <meta name="success_url" content="{{ url_for('Airflow.success') }}">
   <meta name="confirm_url" content="{{ url_for('Airflow.confirm') }}">
   <meta name="grid_data_url" content="{{ url_for('Airflow.grid_data') }}">
+  <meta name="next_run_datasets_url" content="{{ url_for('Airflow.next_run_datasets', dag_id=dag.dag_id) }}">
   <meta name="run_url" content="{{ url_for('Airflow.run') }}">
   <meta name="grid_url" content="{{ url_for('Airflow.grid', dag_id=dag.dag_id) }}">
+  <meta name="datasets_url" content="{{ url_for('Airflow.datasets') }}">
   <meta name="grid_url_no_root" content="{{ url_for('Airflow.grid', dag_id=dag.dag_id, num_runs=num_runs_arg, base_date=base_date_arg) }}">
   <meta name="dag_details_url" content="{{ url_for('Airflow.dag_details', dag_id=dag.dag_id) }}">
   <meta name="graph_url" content="{{ url_for('Airflow.graph', dag_id=dag.dag_id, root=root) }}">
@@ -134,9 +137,18 @@
         </p>
       {% endif %}
       {% if dag_model is defined and dag_model.schedule_interval is defined and dag_model.schedule_interval == 'Dataset' %}
-        <p class="label label-default" style="margin-left: 5px">
-          Next Run: {{ dag_model.get_dataset_triggered_next_run_info() }}
-        </p>
+        <span
+          class="js-tooltip"
+          title="Click to see dataset details."
+        >
+          <p
+            class="label label-default next-dataset-triggered"
+            style="margin-left: 5px;"
+            data-summary="{{ dag_model.get_dataset_triggered_next_run_info() }}"
+          >
+            Next Run: {{ dag_model.get_dataset_triggered_next_run_info() }}
+          </p>
+        </span>
       {% endif %}
     </h4>
   </div>
@@ -431,6 +443,8 @@
       </div>
     </div>
   </div>
+  <!-- Modal for dataset-triggered next run -->
+  {{ dataset_next_run_modal(id='dataset-next-run-modal') }}
 {% endblock %}
 {% block tail %}
   {{ super() }}
diff --git a/airflow/www/templates/airflow/dags.html b/airflow/www/templates/airflow/dags.html
index df4439318c..32ed7e9f8a 100644
--- a/airflow/www/templates/airflow/dags.html
+++ b/airflow/www/templates/airflow/dags.html
@@ -19,6 +19,7 @@
 
 {% extends base_template %}
 {% from 'appbuilder/loading_dots.html' import loading_dots %}
+{% from 'airflow/dataset_next_run_modal.html' import dataset_next_run_modal %}
 {% from 'airflow/_messages.html' import show_message %}
 
 {%- macro sortable_column(display_name, attribute_name) -%}
@@ -68,6 +69,8 @@
   <meta name="dag_stats_url" content="{{ url_for('Airflow.dag_stats') }}">
   <meta name="task_stats_url" content="{{ url_for('Airflow.task_stats') }}">
   <meta name="grid_url" content="{{ url_for('Airflow.grid', dag_id='__DAG_ID__') }}">
+  <meta name="datasets_url" content="{{ url_for('Airflow.datasets') }}">
+  <meta name="next_run_datasets_url" content="{{ url_for('Airflow.next_run_datasets', dag_id='__DAG_ID__') }}">
 {% endblock %}
 
 {% block head_css %}
@@ -290,7 +293,18 @@
             </td>
             <td class="text-nowrap">
               {% if dag.dag_id in dataset_triggered_next_run_info %}
-                {{ dataset_triggered_next_run_info[dag.dag_id] }}
+                <span
+                  class="js-tooltip"
+                  title="Click to see dataset details."
+                >
+                  <div
+                    class="label label-default next-dataset-triggered"
+                    data-dag-id="{{ dag.dag_id }}"
+                    data-summary="{{ dataset_triggered_next_run_info[dag.dag_id] }}"
+                  >
+                    {{ dataset_triggered_next_run_info[dag.dag_id] }}
+                  </div>
+                </span>
               {% endif %}
               {% if dag.next_dagrun is not none %}
                 <time datetime="{{ dag.next_dagrun }}">{{ dag.next_dagrun }}</time>
@@ -408,6 +422,8 @@
     <div class="tooltip-arrow"></div>
     <div class="tooltip-inner"></div>
   </div>
+  <!-- Modal for dataset-triggered next run -->
+  {{ dataset_next_run_modal(id='dataset-next-run-modal') }}
 {% endblock %}
 
 {% block tail %}
diff --git a/airflow/www/templates/airflow/dataset_next_run_modal.html b/airflow/www/templates/airflow/dataset_next_run_modal.html
new file mode 100644
index 0000000000..df5f1466c8
--- /dev/null
+++ b/airflow/www/templates/airflow/dataset_next_run_modal.html
@@ -0,0 +1,56 @@
+{#
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+ #}
+
+{% from 'appbuilder/loading_dots.html' import loading_dots %}
+
+{% macro dataset_next_run_modal(id=None, classes=None) %}
+  <div class="modal fade" id="datasetNextRunModal" tabindex="-1" role="dialog" aria-labelledby="datasetNextRunModalLabel" aria-hidden="true">
+    <div class="modal-dialog">
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
+          <h4 class="modal-title" id="datasetNextRunModalLabel">
+            <span class="text-muted">Datasets needed to trigger the next run for</span> <span id="dag_id"></span>
+          </h4>
+        </div>
+        <div class="modal-body">
+          <p id="next_run_summary"></p>
+          {{ loading_dots(id='datasets-loading-dots', classes='refresh-loading') }}
+          <div id="datasets_error" style="display: none; margin-top: 10px;" class="alert alert-danger" role="alert">
+            <span class="material-icons" aria-hidden="true">error</span>
+            <span id="datasets_error_msg">Oops.</span>
+          </div>
+          <table class="table table-striped table-bordered table-hover dataset-events">
+            <thead>
+              <tr>
+                <th>Dataset URI</th>
+                <th>Timestamp</th>
+              </tr>
+            </thead>
+            <tbody id="datasets_tbody">
+            </tbody>
+          </table>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+        </div>
+      </div>
+    </div>
+  </div>
+{% endmacro %}