This is an automated email from the ASF dual-hosted git repository.

bbovenzi pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 2846242adad Show states of task groups as stacked cards in Grid view 
(#75442)
2846242adad is described below

commit 2846242adad612b53c71ea304997e863ace61ce1
Author: Zhen-Lun (Kevin) Hong <[email protected]>
AuthorDate: Fri Oct 23 22:54:49 2026 +0800

    Show states of task groups as stacked cards in Grid view (#75442)
    
    * Show task groups as stacked cards in the Grid view
    
    * Test failed task groups in the Grid stacked card
---
 .../ui/src/layouts/Details/Grid/GridTI.test.tsx    | 25 ++++++++++++++++++++++
 .../airflow/ui/src/layouts/Details/Grid/GridTI.tsx |  9 ++++----
 2 files changed, 30 insertions(+), 4 deletions(-)

diff --git 
a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.test.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.test.tsx
index ad9a51bcd96..0c6d46f92c8 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.test.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.test.tsx
@@ -53,6 +53,7 @@ const buildStackShadow = (backCardState: string) =>
 type RenderGridTIOptions = {
   readonly hasNote?: boolean;
   readonly instance?: LightGridTaskInstanceSummary;
+  readonly isGroup?: boolean;
   readonly isMapped?: boolean;
   readonly runId?: string;
   readonly taskId?: string;
@@ -63,6 +64,7 @@ const renderGridTI = (
   {
     hasNote,
     instance = taskInstance,
+    isGroup,
     isMapped,
     runId = SELECTED_RUN_ID,
     taskId = "selected_task",
@@ -79,6 +81,7 @@ const renderGridTI = (
                   dagId="example_dag"
                   hasNote={hasNote}
                   instance={{ ...instance, task_id: taskId }}
+                  isGroup={isGroup}
                   isMapped={isMapped}
                   label={taskId}
                   runId={runId}
@@ -146,6 +149,28 @@ describe("GridTI", () => {
     },
   );
 
+  it.each<{
+    backCardState: string;
+    childStates: Record<string, number>;
+    primaryState: LightGridTaskInstanceSummary["state"];
+  }>([
+    { backCardState: "skipped", childStates: { skipped: 2, success: 1 }, 
primaryState: "success" },
+    { backCardState: "success", childStates: { success: 3 }, primaryState: 
"success" },
+    {
+      backCardState: "upstream_failed",
+      childStates: { failed: 1, success: 1, upstream_failed: 2 },
+      primaryState: "failed",
+    },
+    { backCardState: "success", childStates: { failed: 2, success: 3 }, 
primaryState: "failed" },
+  ])("stacks task group cells like mapped tasks", ({ backCardState, 
childStates, primaryState }) => {
+    
renderGridTI(`/dags/example_dag/runs/${SELECTED_RUN_ID}/tasks/selected_task`, {
+      instance: { ...taskInstance, child_states: childStates, state: 
primaryState },
+      isGroup: true,
+    });
+
+    
expect(screen.getByTestId("task-state-badge").style.boxShadow).toBe(buildStackShadow(backCardState));
+  });
+
   it("shows a single card for unmapped tasks", () => {
     
renderGridTI(`/dags/example_dag/runs/${SELECTED_RUN_ID}/tasks/selected_task`, {
       instance: { ...taskInstance, child_states: { skipped: 2, success: 1 } },
diff --git a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.tsx 
b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.tsx
index 5bc78375e0e..fbfb1860c6a 100644
--- a/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.tsx
+++ b/airflow-core/src/airflow/ui/src/layouts/Details/Grid/GridTI.tsx
@@ -87,13 +87,14 @@ export const GridTI = ({
   const isSelectedRow = selectedTaskId === taskId || selectedGroupId === 
taskId;
   const isSelectedTaskInstance = selectedRunId === runId && isSelectedRow;
   const selectedOutlineColor = SELECTED_TASK_OUTLINE_COLOR[colorMode];
+  const isStacked = Boolean(isMapped) || Boolean(isGroup);
   const frontCardState = instance.state ?? "none";
-  const backCardState = isMapped
+  const backCardState = isStacked
     ? (sortStateEntries(instance.child_states).find(([state]) => state !== 
frontCardState)?.[0] ??
       frontCardState)
     : undefined;
-  const badgeSize = `${isMapped ? TASK_BAR_HEIGHT_PX - 1 : 
TASK_BAR_HEIGHT_PX}px`;
-  const stackPadding = isMapped ? `${BACK_CARD_OFFSET_PX}px` : undefined;
+  const badgeSize = `${isStacked ? TASK_BAR_HEIGHT_PX - 1 : 
TASK_BAR_HEIGHT_PX}px`;
+  const stackPadding = isStacked ? `${BACK_CARD_OFFSET_PX}px` : undefined;
 
   return (
     <Flex
@@ -123,7 +124,7 @@ export const GridTI = ({
           borderRadius={4}
           display="flex"
           outlineColor={isSelectedTaskInstance ? selectedOutlineColor : 
undefined}
-          outlineOffset={isMapped ? "0px" : "1px"}
+          outlineOffset={isStacked ? "0px" : "1px"}
           outlineStyle={isSelectedTaskInstance ? "solid" : undefined}
           outlineWidth={isSelectedTaskInstance ? "2px" : undefined}
           pb={stackPadding}

Reply via email to