diff --git a/airflow/www/templates/airflow/dags.html b/airflow/www/templates/airflow/dags.html
index 1a0ebc01f44ba..561aa8d783650 100644
--- a/airflow/www/templates/airflow/dags.html
+++ b/airflow/www/templates/airflow/dags.html
@@ -103,13 +103,11 @@
DAGs
- {% set last_run = dag.get_last_dagrun(include_externally_triggered=True) %}
- {% if last_run and last_run.execution_date %}
-
- {{ last_run.execution_date.strftime("%Y-%m-%d %H:%M") }}
-
-
- {% endif %}
+
+
+
+
+
|
@@ -309,6 +307,24 @@ DAGs
}
});
});
+ d3.json("{{ url_for('airflow.last_dagruns') }}", function(error, json) {
+ for(var safe_dag_id in json) {
+ dag_id = json[safe_dag_id].dag_id;
+ last_run = json[safe_dag_id].last_run;
+ g = d3.select('div#last-run-' + safe_dag_id)
+
+ g.selectAll('a')
+ .attr("href", "{{ url_for('airflow.graph') }}?dag_id=" + dag_id + "&execution_date=" + last_run)
+ .text(last_run);
+
+ g.selectAll('span')
+ .attr("data-original-title", "Start Date: " + last_run)
+ .style('display', null);
+
+ g.selectAll(".loading-last-run").remove();
+ }
+ d3.selectAll(".loading-last-run").remove();
+ });
d3.json("{{ url_for('airflow.dag_stats') }}", function(error, json) {
for(var dag_id in json) {
states = json[dag_id];
diff --git a/airflow/www/views.py b/airflow/www/views.py
index 1596d86148cf3..e0b4097159ff4 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -668,6 +668,26 @@ def task_stats(self, session=None):
})
return wwwutils.json_response(payload)
+ @expose('/last_dagruns')
+ @login_required
+ @provide_session
+ def last_dagruns(self, session=None):
+ DagRun = models.DagRun
+
+ dags_to_latest_runs = dict(session.query(
+ DagRun.dag_id, sqla.func.max(DagRun.execution_date).label('execution_date'))
+ .group_by(DagRun.dag_id).all())
+
+ payload = {}
+ for dag in dagbag.dags.values():
+ if dag.dag_id in dags_to_latest_runs and dags_to_latest_runs[dag.dag_id]:
+ payload[dag.safe_dag_id] = {
+ 'dag_id': dag.dag_id,
+ 'last_run': dags_to_latest_runs[dag.dag_id].strftime("%Y-%m-%d %H:%M")
+ }
+
+ return wwwutils.json_response(payload)
+
@expose('/code')
@login_required
@provide_session
diff --git a/airflow/www_rbac/templates/airflow/dags.html b/airflow/www_rbac/templates/airflow/dags.html
index fb157b8b3062e..c1e32726451a3 100644
--- a/airflow/www_rbac/templates/airflow/dags.html
+++ b/airflow/www_rbac/templates/airflow/dags.html
@@ -103,13 +103,11 @@ DAGs
- {% set last_run = dag.get_last_dagrun(include_externally_triggered=True) %}
- {% if last_run and last_run.execution_date %}
-
- {{ last_run.execution_date.strftime("%Y-%m-%d %H:%M") }}
-
-
- {% endif %}
+
+
+
+
+
|
@@ -309,6 +307,24 @@ DAGs
}
});
});
+ d3.json("{{ url_for('Airflow.last_dagruns') }}", function(error, json) {
+ for(var safe_dag_id in json) {
+ dag_id = json[safe_dag_id].dag_id;
+ last_run = json[safe_dag_id].last_run;
+ g = d3.select('div#last-run-' + safe_dag_id)
+
+ g.selectAll('a')
+ .attr("href", "{{ url_for('Airflow.graph') }}?dag_id=" + dag_id + "&execution_date=" + last_run)
+ .text(last_run);
+
+ g.selectAll('span')
+ .attr("data-original-title", "Start Date: " + last_run)
+ .style('display', null);
+
+ g.selectAll(".loading-last-run").remove();
+ }
+ d3.selectAll(".loading-last-run").remove();
+ });
d3.json("{{ url_for('Airflow.dag_stats') }}", function(error, json) {
for(var dag_id in json) {
states = json[dag_id];
diff --git a/airflow/www_rbac/views.py b/airflow/www_rbac/views.py
index a69af7d53e8ee..3a8020fb2324c 100644
--- a/airflow/www_rbac/views.py
+++ b/airflow/www_rbac/views.py
@@ -410,6 +410,33 @@ def task_stats(self, session=None):
})
return wwwutils.json_response(payload)
+ @expose('/last_dagruns')
+ @has_access
+ @provide_session
+ def last_dagruns(self, session=None):
+ DagRun = models.DagRun
+
+ filter_dag_ids = appbuilder.sm.get_accessible_dag_ids()
+
+ if not filter_dag_ids:
+ return
+
+ dags_to_latest_runs = dict(session.query(
+ DagRun.dag_id, sqla.func.max(DagRun.execution_date).label('execution_date'))
+ .group_by(DagRun.dag_id).all())
+
+ payload = {}
+ for dag in dagbag.dags.values():
+ dag_accessible = 'all_dags' in filter_dag_ids or dag.dag_id in filter_dag_ids
+ if (dag_accessible and dag.dag_id in dags_to_latest_runs and
+ dags_to_latest_runs[dag.dag_id]):
+ payload[dag.safe_dag_id] = {
+ 'dag_id': dag.dag_id,
+ 'last_run': dags_to_latest_runs[dag.dag_id].strftime("%Y-%m-%d %H:%M")
+ }
+
+ return wwwutils.json_response(payload)
+
@expose('/code')
@has_dag_access(can_dag_read=True)
@has_access
diff --git a/tests/core.py b/tests/core.py
index 07891003559d4..b0ee4da2ae736 100644
--- a/tests/core.py
+++ b/tests/core.py
@@ -1851,17 +1851,6 @@ def test_index(self):
self.assertIn("DAGs", resp_html)
self.assertIn("example_bash_operator", resp_html)
- # The HTML should contain data for the last-run. A link to the specific run,
- # and the text of the date.
- url = "/admin/airflow/graph?" + urlencode({
- "dag_id": self.dag_python.dag_id,
- "execution_date": self.dagrun_python.execution_date,
- }).replace("&", "&")
- self.assertIn(url, resp_html)
- self.assertIn(
- self.dagrun_python.execution_date.strftime("%Y-%m-%d %H:%M"),
- resp_html)
-
def test_query(self):
response = self.app.get('/admin/queryview/')
self.assertIn("Ad Hoc Query", response.data.decode('utf-8'))
@@ -2005,7 +1994,9 @@ def test_dag_views(self):
response = self.app.get(
'/admin/airflow/task_stats')
self.assertIn("example_bash_operator", response.data.decode('utf-8'))
-
+ response = self.app.get(
+ '/admin/airflow/last_dagruns')
+ self.assertIn("example_python_operator", response.data.decode('utf-8'))
response = self.app.post("/admin/airflow/success", data=dict(
task_id="print_the_context",
dag_id="example_python_operator",