diff --git a/airflow/www/static/js/dag/InstanceTooltip.test.tsx b/airflow/www/static/js/dag/InstanceTooltip.test.tsx
index d2a4b92d38cc1..c52a465663665 100644
--- a/airflow/www/static/js/dag/InstanceTooltip.test.tsx
+++ b/airflow/www/static/js/dag/InstanceTooltip.test.tsx
@@ -40,12 +40,18 @@ describe("Test Task InstanceTooltip", () => {
test("Displays a normal task", () => {
const { getByText, queryByText } = render(
,
{ wrapper: Wrapper }
);
+ expect(getByText("Trigger Rule: all_failed")).toBeDefined();
expect(getByText("Status: success")).toBeDefined();
expect(queryByText("Contains a note")).toBeNull();
expect(getByText("Duration: 00:00:00")).toBeDefined();
diff --git a/airflow/www/static/js/dag/InstanceTooltip.tsx b/airflow/www/static/js/dag/InstanceTooltip.tsx
index f49b55a4fcdda..8fb021e30d9b7 100644
--- a/airflow/www/static/js/dag/InstanceTooltip.tsx
+++ b/airflow/www/static/js/dag/InstanceTooltip.tsx
@@ -83,6 +83,7 @@ const InstanceTooltip = ({
>
)}
+ {group.triggerRule && Trigger Rule: {group.triggerRule}}
{note && Contains a note}
);
diff --git a/airflow/www/static/js/dag/details/taskInstance/Details.tsx b/airflow/www/static/js/dag/details/taskInstance/Details.tsx
index 942910879390e..3e3b9dc881459 100644
--- a/airflow/www/static/js/dag/details/taskInstance/Details.tsx
+++ b/airflow/www/static/js/dag/details/taskInstance/Details.tsx
@@ -42,7 +42,7 @@ const Details = ({ instance, group, dagId }: Props) => {
const { taskId, runId, startDate, endDate, state, mappedStates, mapIndex } =
instance;
- const { isMapped, tooltip, operator, hasOutletDatasets } = group;
+ const { isMapped, tooltip, operator, hasOutletDatasets, triggerRule } = group;
const { data: apiTI } = useTaskInstance({
dagId,
@@ -168,6 +168,12 @@ const Details = ({ instance, group, dagId }: Props) => {
diff --git a/airflow/www/static/js/types/index.ts b/airflow/www/static/js/types/index.ts
index dc45f79460158..94e0e60526e18 100644
--- a/airflow/www/static/js/types/index.ts
+++ b/airflow/www/static/js/types/index.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import * as API from "./api-generated";
+import type * as API from "./api-generated";
type RunState = "success" | "running" | "queued" | "failed";
@@ -96,6 +96,7 @@ interface Task {
isMapped?: boolean;
operator?: string;
hasOutletDatasets?: boolean;
+ triggerRule?: API.TriggerRule;
}
type RunOrdering = (
diff --git a/airflow/www/views.py b/airflow/www/views.py
index 8b7e53f748c8d..ca8adac50bb6e 100644
--- a/airflow/www/views.py
+++ b/airflow/www/views.py
@@ -119,7 +119,7 @@
from airflow.utils.session import NEW_SESSION, create_session, provide_session
from airflow.utils.state import State, TaskInstanceState
from airflow.utils.strings import to_boolean
-from airflow.utils.task_group import MappedTaskGroup, task_group_to_dict
+from airflow.utils.task_group import MappedTaskGroup, TaskGroup, task_group_to_dict
from airflow.utils.timezone import td_format, utcnow
from airflow.version import version
from airflow.www import auth, utils as wwwutils
@@ -297,7 +297,7 @@ def dag_to_grid(dag: DagModel, dag_runs: Sequence[DagRun], session: Session):
grouped_tis = {task_id: list(tis) for task_id, tis in itertools.groupby(query, key=lambda ti: ti.task_id)}
def task_group_to_grid(item, grouped_tis, *, is_parent_mapped: bool):
- if isinstance(item, AbstractOperator):
+ if not isinstance(item, TaskGroup):
def _get_summary(task_instance):
return {
@@ -363,6 +363,7 @@ def set_overall_state(record):
"is_mapped": isinstance(item, MappedOperator) or is_parent_mapped,
"has_outlet_datasets": any(isinstance(i, Dataset) for i in (item.outlets or [])),
"operator": item.operator_name,
+ "trigger_rule": item.trigger_rule,
}
# Task Group
diff --git a/tests/www/views/test_views_grid.py b/tests/www/views/test_views_grid.py
index 79e9401f684f0..171b48211f547 100644
--- a/tests/www/views/test_views_grid.py
+++ b/tests/www/views/test_views_grid.py
@@ -108,6 +108,7 @@ def test_no_runs(admin_client, dag_without_runs):
"is_mapped": False,
"label": "task1",
"operator": "EmptyOperator",
+ "trigger_rule": "all_success",
},
{
"children": [
@@ -119,6 +120,7 @@ def test_no_runs(admin_client, dag_without_runs):
"is_mapped": True,
"label": "subtask2",
"operator": "MockOperator",
+ "trigger_rule": "all_success",
}
],
"is_mapped": True,
@@ -137,6 +139,7 @@ def test_no_runs(admin_client, dag_without_runs):
"is_mapped": True,
"label": "mapped",
"operator": "MockOperator",
+ "trigger_rule": "all_success",
}
],
"id": "group",
@@ -255,6 +258,7 @@ def test_one_run(admin_client, dag_with_runs: list[DagRun], session):
"is_mapped": False,
"label": "task1",
"operator": "EmptyOperator",
+ "trigger_rule": "all_success",
},
{
"children": [
@@ -283,6 +287,7 @@ def test_one_run(admin_client, dag_with_runs: list[DagRun], session):
"is_mapped": True,
"label": "subtask2",
"operator": "MockOperator",
+ "trigger_rule": "all_success",
}
],
"is_mapped": True,
@@ -335,6 +340,7 @@ def test_one_run(admin_client, dag_with_runs: list[DagRun], session):
"is_mapped": True,
"label": "mapped",
"operator": "MockOperator",
+ "trigger_rule": "all_success",
},
],
"id": "group",
@@ -398,6 +404,7 @@ def _expected_task_details(task_id, has_outlet_datasets):
"is_mapped": False,
"label": task_id,
"operator": "EmptyOperator",
+ "trigger_rule": "all_success",
}
assert resp.status_code == 200, resp.json
|