diff --git a/airflow/macros/hive.py b/airflow/macros/hive.py index b147316d67..d3636db41e 100644 --- a/airflow/macros/hive.py +++ b/airflow/macros/hive.py @@ -1,7 +1,7 @@ from airflow import settings -from airflow.hooks.hive_hook import HiveHook def max_partition( + from airflow.hooks.hive_hook import HiveHook table, schema="default", hive_dbid=settings.HIVE_DEFAULT_DBID): hh = HiveHook(hive_dbid=hive_dbid) return hh.max_partition(schema=schema, table=table) diff --git a/airflow/models.py b/airflow/models.py index 8308334f58..92582533ec 100644 --- a/airflow/models.py +++ b/airflow/models.py @@ -16,7 +16,6 @@ from sqlalchemy import ( ) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship -from airflow import macros from airflow.executors import DEFAULT_EXECUTOR from airflow import settings from airflow import utils @@ -407,6 +406,7 @@ class TaskInstance(Base): logging.info(msg.format(self=self)) try: if not mark_success: + from airflow import macros jinja_context = { 'ti': self, 'execution_date': self.execution_date, diff --git a/airflow/utils.py b/airflow/utils.py index 675edf2079..e52faa3dcf 100644 --- a/airflow/utils.py +++ b/airflow/utils.py @@ -32,10 +32,10 @@ def validate_key(k, max_length=250): elif len(k) > max_length: raise Exception("The key has to be less than {0} characters".format( max_length)) - elif not re.match(r'^[A-Za-z0-9_]+$', k): + elif not re.match(r'^[A-Za-z0-9_-]+$', k): raise Exception( - "The key has to be made of alphanumeric characters and " - "undersairflows exclusively") + "The key has to be made of alphanumeric characters, dashes " + "and underscores exclusively") else: return True diff --git a/airflow/www/app.py b/airflow/www/app.py index 954a0e0da6..ee8dc8527a 100644 --- a/airflow/www/app.py +++ b/airflow/www/app.py @@ -7,7 +7,7 @@ from flask import Flask, url_for, Markup, Blueprint, redirect, flash from flask.ext.admin import Admin, BaseView, expose, AdminIndexView from flask.ext.admin.contrib.sqla import ModelView from flask import request -from wtforms import Form, DateTimeField +from wtforms import Form, DateTimeField, SelectField from pygments import highlight from pygments.lexers import PythonLexer @@ -41,6 +41,15 @@ app.jinja_env.add_extension("chartkick.ext.charts") class DateTimeForm(Form): execution_date = DateTimeField("Execution date") +class GraphForm(Form): + execution_date = DateTimeField("Execution date") + arrange = SelectField("Layout", choices=( + ('LR', "Left->Right"), + ('RL', "Right->Left"), + ('TB', "Top->Bottom"), + ('BT', "Bottom->Top"), + )) + @app.route('/') def index(): @@ -101,6 +110,28 @@ class Airflow(BaseView): return self.render( 'admin/code.html', html_code=html_code, dag=dag, title=title) + @expose('/task') + def task(self): + dag_id = request.args.get('dag_id') + task_id = request.args.get('task_id') + dag = dagbag.dags[dag_id] + task = dag.get_task(task_id) + + s = '' + for attr_name in dir(task): + if not attr_name.startswith('_'): + attr = getattr(task, attr_name) + if type(attr) != type(self.task): + s += attr_name + ': ' + str(attr) + '\n' + + s = s.replace('<', '<') + s = s.replace('>', '>') + html_code = "
" + s + "
"
+ title = "Task Details for {task_id}".format(**locals())
+
+ return self.render(
+ 'admin/code.html', html_code=html_code, dag=dag, title=title)
+
@expose('/tree')
def tree(self):
dag_id = request.args.get('dag_id')
@@ -205,6 +236,7 @@ class Airflow(BaseView):
def graph(self):
session = settings.Session()
dag_id = request.args.get('dag_id')
+ arrange = request.args.get('arrange', "LR")
dag = dagbag.dags[dag_id]
nodes = []
@@ -234,7 +266,7 @@ class Airflow(BaseView):
else:
dttm = dag.latest_execution_date or datetime.now().date()
- form = DateTimeForm(data={'execution_date': dttm})
+ form = GraphForm(data={'execution_date': dttm, 'arrange': arrange})
task_instances = {
ti.task_id: utils.alchemy_to_dict(ti)
@@ -252,6 +284,7 @@ class Airflow(BaseView):
dag=dag,
form=form,
execution_date=dttm.isoformat(),
+ arrange=arrange,
task_instances=json.dumps(task_instances, indent=2),
tasks=json.dumps(tasks, indent=2),
nodes=json.dumps(nodes, indent=2),
diff --git a/airflow/www/static/graph.css b/airflow/www/static/graph.css
index bd9ada1a6e..8f94f264df 100644
--- a/airflow/www/static/graph.css
+++ b/airflow/www/static/graph.css
@@ -15,7 +15,7 @@ g.node.running rect{
g.node.failed rect {
stroke: red;
}
-input#execution_date {
+input, select {
margin-bottom: 0px;
}
div#svg_container {
diff --git a/airflow/www/static/main.css b/airflow/www/static/main.css
index fcbc578058..9a4308f7a2 100644
--- a/airflow/www/static/main.css
+++ b/airflow/www/static/main.css
@@ -30,3 +30,12 @@ input#execution_date {
.modal{
margin-top:5000px;
}
+pre {
+ overflow: auto;
+ word-wrap: normal;
+ white-space: pre;
+}
+pre code {
+ overflow-wrap: normal;
+ white-space: pre;
+}
diff --git a/airflow/www/templates/admin/dag.html b/airflow/www/templates/admin/dag.html
index 231e4f921d..6106666fb2 100644
--- a/airflow/www/templates/admin/dag.html
+++ b/airflow/www/templates/admin/dag.html
@@ -58,6 +58,9 @@
+