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 @@ +