Repository: apache/airflow

Issue:
Graph View is empty when Operator has multiline string in args (v2.0)
Airflow v2.0b3
 Kubernetes v1.19.3

Discovered issue while testing KubernetesPodOperator (haven't tested with other operator).
If I create a multiline string using """ """", add some variables inside (Jinja templating), then use this string as an argument to KubernetesPodOperator:
- In Graph View DAG is not visible (just gray area where it should be a digraph);
- in browser's web console i see the following error:

`Uncaught TypeError: node is undefined
    preProcessGraph http://localhost:8080/static/dist/dagre-d3.min.js:103
    preProcessGraph http://localhost:8080/static/dist/dagre-d3.min.js:103
    fn http://localhost:8080/static/dist/dagre-d3.min.js:103
    call http://localhost:8080/static/dist/d3.min.js:3
    draw http://localhost:8080/graph?dag_id=mydag&execution_date=mydate
    expand_group http://localhost:8080/graph?dag_id=mydag&execution_date=mydate
    <anonymous> http://localhost:8080/graph?dag_id=mydag&execution_date=mydate`

Tree view works without issues in this case. The DAG succeeds.


Discussion:
Thanks for opening your first issue here! Be sure to follow the issue template!

@ielizarov Can you post your DAG please for us to replicate the exact error?
Thanks @ielizarov 
@kaxil My bad, it seems I forgot to add "dag=dag" to this particular operator, and it led to this error. 
Perhaps there should be more clear behavior (i.e. DAG should still be displayed in this case)?
Yes, even without the `dag=dag` it shouldn't be able to cause a crash in the webserver like this
@yuqian90 / @houqp  If you have some time can you help looking into this issue, it looks like it has something to do with the TaskGroup change in graph.html after some debugging.

Here is the DAG (notice the 2nd task does is not directly assigned to a DAG but because of the dependency of the other task it works fine on Tree View). It used to work on Graph view too until the TaskGroup change in `airflow/www/templates/airflow/graph.html` file:

Error:

![image](https://user-images.githubusercontent.com/8811558/101227639-7de0b100-3690-11eb-99b6-10228a835f7d.png)

Tree View:

![image](https://user-images.githubusercontent.com/8811558/101227652-8638ec00-3690-11eb-8305-37ad0650848c.png)


DAG:

```python
"""Demo DAG"""
from datetime import timedelta
from airflow import DAG
from airflow.contrib.operators.kubernetes_pod_operator import KubernetesPodOperator
from airflow.utils.dates import days_ago
args = {
    'owner': 'airflow',
}
dag = DAG(
    dag_id='demo_test',
    default_args=args,
    schedule_interval='0 0 * * *',
    start_date=days_ago(2),
    dagrun_timeout=timedelta(minutes=60),
    tags=['demo'],
    params={
        "pod_op_image": "localhost:5000/my-image:latest",
        "pod_op_image_pull_policy": "IfNotPresent",
    },
)
env_params = {
    "TEST_ENV_VAR": "TEST ENV VAR",
}
normal_op = KubernetesPodOperator(
    dag=dag,
    name='normal_op',
    task_id='normal_op',
    namespace='airflow',
    image="{{ dag_run.conf['pod_op_image'] }}",
    image_pull_policy="{{ dag_run.conf['pod_op_image_pull_policy'] }}",
    env_vars=env_params,
    cmds=["/bin/bash", "-c"],
    arguments=["echo $TEST_ENV_VAR"],
)
#below is templated multi-string var which is used as argument further
templated_command = """
        echo "{{ var.value.TEST_VAR}}"
        echo "{{ params.my_param }}"
    """
templated_op = KubernetesPodOperator(
    name='templated_op',
    task_id='templated_op',
    namespace='airflow',
    image="{{ dag_run.conf['pod_op_image'] }}",
    image_pull_policy="{{ dag_run.conf['pod_op_image_pull_policy'] }}",
    cmds=["bash", "-c"],
    arguments=[templated_command],
    params={'my_param': 'Passed_Parameter'},
)
normal_op >> templated_op

```

Graph View:
![image](https://user-images.githubusercontent.com/8811558/101227675-9bae1600-3690-11eb-8217-45b436ebd5fe.png)


Same DAG on 1.10.14:

![image](https://user-images.githubusercontent.com/8811558/101227779-ffd0da00-3690-11eb-8457-e51d99085a59.png)


Resolving patch:
diff --git a/airflow/models/baseoperator.py b/airflow/models/baseoperator.py
--- a/airflow/models/baseoperator.py
+++ b/airflow/models/baseoperator.py
@@ -1206,11 +1206,17 @@ def _set_relatives(
             )
 
         if dag and not self.has_dag():
+            # If this task does not yet have a dag, add it to the same dag as the other task and
+            # put it in the dag's root TaskGroup.
             self.dag = dag
+            self.dag.task_group.add(self)
 
         for task in task_list:
             if dag and not task.has_dag():
+                # If the other task does not yet have a dag, add it to the same dag as this task and
+                # put it in the dag's root TaskGroup.
                 task.dag = dag
+                task.dag.task_group.add(task)
             if upstream:
                 task.add_only_new(task.get_direct_relative_ids(upstream=False), self.task_id)
                 self.add_only_new(self._upstream_task_ids, task.task_id)


Test patch:
diff --git a/tests/utils/test_task_group.py b/tests/utils/test_task_group.py
--- a/tests/utils/test_task_group.py
+++ b/tests/utils/test_task_group.py
@@ -559,3 +559,20 @@ def test_duplicate_group_id():
             _ = DummyOperator(task_id="task1")
             with TaskGroup("group1"):
                 _ = DummyOperator(task_id="upstream_join_id")
+
+
+def test_task_without_dag():
+    """
+    Test that if a task doesn't have a DAG when it's being set as the relative of another task which
+    has a DAG, the task should be added to the root TaskGroup of the other task's DAG.
+    """
+    dag = DAG(dag_id='test_task_without_dag', start_date=pendulum.parse("20200101"))
+    op1 = DummyOperator(task_id='op1', dag=dag)
+    op2 = DummyOperator(task_id='op2')
+    op3 = DummyOperator(task_id="op3")
+    op1 >> op2
+    op3 >> op2
+
+    assert op1.dag == op2.dag == op3.dag
+    assert dag.task_group.children.keys() == {"op1", "op2", "op3"}
+    assert dag.task_group.children.keys() == dag.task_dict.keys()