diff --git a/airflow/example_dags/tutorial.py b/airflow/example_dags/tutorial.py index 518c801ab7439..09d6ca3f1a658 100644 --- a/airflow/example_dags/tutorial.py +++ b/airflow/example_dags/tutorial.py @@ -97,6 +97,7 @@ You can document your task using the attributes `doc_md` (markdown), `doc` (plain text), `doc_rst`, `doc_json`, `doc_yaml` which gets rendered in the UI's Task Instance Details page. + ![img](http://montcs.bloomu.edu/~bobmon/Semesters/2012-01/491/import%20soul.png) """ ) diff --git a/airflow/models/baseoperator.py b/airflow/models/baseoperator.py index 8bda785845a6c..eacea646c8231 100644 --- a/airflow/models/baseoperator.py +++ b/airflow/models/baseoperator.py @@ -278,6 +278,21 @@ class derived from this one results in the creation of a task object, :param do_xcom_push: if True, an XCom is pushed containing the Operator's result :type do_xcom_push: bool + :param doc: Add documentation or notes to your Task objects that is visible in + Task Instance details View in the Webserver + :type doc: str + :param doc_md: Add documentation (in Markdown format) or notes to your Task objects + that is visible in Task Instance details View in the Webserver + :type doc_md: str + :param doc_rst: Add documentation (in RST format) or notes to your Task objects + that is visible in Task Instance details View in the Webserver + :type doc_rst: str + :param doc_json: Add documentation (in JSON format) or notes to your Task objects + that is visible in Task Instance details View in the Webserver + :type doc_json: str + :param doc_yaml: Add documentation (in YAML format) or notes to your Task objects + that is visible in Task Instance details View in the Webserver + :type doc_yaml: str """ # For derived classes to define which fields will get jinjaified @@ -381,6 +396,11 @@ def __init__( inlets: Optional[Any] = None, outlets: Optional[Any] = None, task_group: Optional["TaskGroup"] = None, + doc: Optional[str] = None, + doc_md: Optional[str] = None, + doc_json: Optional[str] = None, + doc_yaml: Optional[str] = None, + doc_rst: Optional[str] = None, **kwargs, ): from airflow.models.dag import DagContext @@ -486,6 +506,12 @@ def __init__( self.executor_config = executor_config or {} self.do_xcom_push = do_xcom_push + self.doc_md = doc_md + self.doc_json = doc_json + self.doc_yaml = doc_yaml + self.doc_rst = doc_rst + self.doc = doc + # Private attributes self._upstream_task_ids: Set[str] = set() self._downstream_task_ids: Set[str] = set() diff --git a/airflow/serialization/schema.json b/airflow/serialization/schema.json index 0fbe20fb23eff..3bc11ee9a9cee 100644 --- a/airflow/serialization/schema.json +++ b/airflow/serialization/schema.json @@ -168,7 +168,12 @@ "type": "array", "items": { "type": "string" }, "uniqueItems": true - } + }, + "doc": { "type": "string" }, + "doc_md": { "type": "string" }, + "doc_json": { "type": "string" }, + "doc_yaml": { "type": "string" }, + "doc_rst": { "type": "string" } }, "additionalProperties": true }, diff --git a/airflow/www/utils.py b/airflow/www/utils.py index afd94c649502d..ad53436f38d1d 100644 --- a/airflow/www/utils.py +++ b/airflow/www/utils.py @@ -321,7 +321,7 @@ def render(obj, lexer): return out -def wrapped_markdown(s, css_class=None): +def wrapped_markdown(s, css_class='rich_doc'): """Convert a Markdown string to HTML.""" if s is None: return None diff --git a/airflow/www/views.py b/airflow/www/views.py index f0116b3ff4ed5..5f4c8c556b209 100644 --- a/airflow/www/views.py +++ b/airflow/www/views.py @@ -1220,7 +1220,7 @@ def task(self): # Color coding the special attributes that are code special_attrs_rendered = {} for attr_name in wwwutils.get_attr_renderer(): - if hasattr(task, attr_name): + if getattr(task, attr_name, None) is not None: source = getattr(task, attr_name) special_attrs_rendered[attr_name] = wwwutils.get_attr_renderer()[attr_name](source) diff --git a/docs/apache-airflow/concepts.rst b/docs/apache-airflow/concepts.rst index 2637b780c5acf..3de060b6445a3 100644 --- a/docs/apache-airflow/concepts.rst +++ b/docs/apache-airflow/concepts.rst @@ -1394,8 +1394,8 @@ Documentation & Notes ===================== It's possible to add documentation or notes to your DAGs & task objects that -become visible in the web interface ("Graph View" & "Tree View" for DAGs, "Task Details" for -tasks). There are a set of special task attributes that get rendered as rich +become visible in the web interface ("Graph View" & "Tree View" for DAGs, "Task Instance Details" +for tasks). There are a set of special task attributes that get rendered as rich content if defined: ========== ================ @@ -1430,7 +1430,7 @@ to the related tasks in Airflow. """ This content will get rendered as markdown respectively in the "Graph View" and -"Task Details" pages. +"Task Instance Details" pages. .. _jinja-templating: diff --git a/tests/serialization/test_dag_serialization.py b/tests/serialization/test_dag_serialization.py index 55d2c5a41b4f5..e447751f7ef0f 100644 --- a/tests/serialization/test_dag_serialization.py +++ b/tests/serialization/test_dag_serialization.py @@ -79,6 +79,7 @@ }, "is_paused_upon_creation": False, "_dag_id": "simple_dag", + "doc_md": "### DAG Tutorial Documentation", "fileloc": None, "tasks": [ { @@ -110,6 +111,7 @@ } }, }, + "doc_md": "### Task Tutorial Documentation", }, { "task_id": "custom_task", @@ -170,6 +172,7 @@ def make_simple_dag(): start_date=datetime(2019, 8, 1), is_paused_upon_creation=False, access_control={"test_role": {permissions.ACTION_CAN_READ, permissions.ACTION_CAN_EDIT}}, + doc_md="### DAG Tutorial Documentation", ) as dag: CustomOperator(task_id='custom_task') BashOperator( @@ -177,6 +180,7 @@ def make_simple_dag(): bash_command='echo {{ task.task_id }}', owner='airflow', executor_config={"pod_override": executor_config_pod}, + doc_md="### Task Tutorial Documentation", ) return {'simple_dag': dag} @@ -853,6 +857,11 @@ def test_no_new_fields_added_to_base_operator(self): '_upstream_task_ids': set(), 'depends_on_past': False, 'do_xcom_push': True, + 'doc': None, + 'doc_json': None, + 'doc_md': None, + 'doc_rst': None, + 'doc_yaml': None, 'email': None, 'email_on_failure': True, 'email_on_retry': True, diff --git a/tests/www/test_utils.py b/tests/www/test_utils.py index 5ced73a6a7700..f4e50d9480c8a 100644 --- a/tests/www/test_utils.py +++ b/tests/www/test_utils.py @@ -240,7 +240,7 @@ def test_wrapped_markdown_with_table(self): ) assert ( - '
\n\n\n\n' + '
Job
\n\n\n\n' '\n\n\n\n\n\n\n\n\n' '
JobDuration
ETL' '14m
' @@ -255,4 +255,4 @@ def test_wrapped_markdown_with_indented_lines(self): """ ) - assert '

header

\n

1st line\n2nd line

' == rendered + assert '

header

\n

1st line\n2nd line

' == rendered