This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun 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 b1a3d3bc209 Unify grid and graph view tooltips with dates, duration,
and child states (#62119)
b1a3d3bc209 is described below
commit b1a3d3bc209ae5c00742a85e53dc7b55904744c4
Author: Nathan Hadfield <[email protected]>
AuthorDate: Fri Feb 20 13:38:22 2026 +0000
Unify grid and graph view tooltips with dates, duration, and child states
(#62119)
Both grid and graph views now use the shared TaskInstanceTooltip component,
showing consistent information: task ID, state, dates, duration, and child
state breakdown. Previously the grid view used a basic tooltip without
duration or child states, and the graph view was missing dates entirely
because the API was overriding them with null for leaf tasks.
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../airflow/api_fastapi/core_api/routes/ui/grid.py | 6 +-
.../airflow/ui/src/components/Graph/TaskNode.tsx | 2 +
.../ui/src/components/Graph/reactflowUtils.ts | 1 +
.../ui/src/components/Graph/useGraphLayout.ts | 1 +
.../ui/src/components/TaskInstanceTooltip.test.tsx | 144 +++++++++++++++++++++
.../ui/src/components/TaskInstanceTooltip.tsx | 123 +++++++++++-------
.../airflow/ui/src/layouts/Details/Grid/GridTI.tsx | 84 +++++-------
.../src/airflow/ui/src/utils/datetimeUtils.test.ts | 7 +
.../src/airflow/ui/src/utils/datetimeUtils.ts | 14 +-
.../api_fastapi/core_api/routes/ui/test_grid.py | 32 ++---
10 files changed, 289 insertions(+), 125 deletions(-)
diff --git a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
index 694f12e0294..9c7436ba462 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/ui/grid.py
@@ -401,8 +401,6 @@ def get_grid_ti_summaries(
yielded_task_ids.add(node["task_id"])
if node["type"] == "task":
node["child_states"] = None
- node["min_start_date"] = None
- node["max_end_date"] = None
yield node
# For good history: add synthetic leaf nodes for task_ids that have
TIs in this run
@@ -418,10 +416,8 @@ def get_grid_ti_summaries(
"type": "task",
"parent_id": None,
**agg,
- # Align with leaf behavior
+ # Leaf tasks have no children
"child_states": None,
- "min_start_date": None,
- "max_end_date": None,
}
task_instances = list(get_node_sumaries())
diff --git a/airflow-core/src/airflow/ui/src/components/Graph/TaskNode.tsx
b/airflow-core/src/airflow/ui/src/components/Graph/TaskNode.tsx
index a81fd9b773d..330528a4d82 100644
--- a/airflow-core/src/airflow/ui/src/components/Graph/TaskNode.tsx
+++ b/airflow-core/src/airflow/ui/src/components/Graph/TaskNode.tsx
@@ -43,6 +43,7 @@ export const TaskNode = ({
operator,
setupTeardownType,
taskInstance,
+ tooltip,
width = 0,
},
id,
@@ -89,6 +90,7 @@ export const TaskNode = ({
placement: "top-start",
}}
taskInstance={taskInstance}
+ tooltip={isGroup ? tooltip : undefined}
>
<Flex
// Alternate background color for nested open groups
diff --git a/airflow-core/src/airflow/ui/src/components/Graph/reactflowUtils.ts
b/airflow-core/src/airflow/ui/src/components/Graph/reactflowUtils.ts
index f64951f861b..d73eec056ee 100644
--- a/airflow-core/src/airflow/ui/src/components/Graph/reactflowUtils.ts
+++ b/airflow-core/src/airflow/ui/src/components/Graph/reactflowUtils.ts
@@ -37,6 +37,7 @@ export type CustomNodeProps = {
operator?: string | null;
setupTeardownType?: NodeResponse["setup_teardown_type"];
taskInstance?: LightGridTaskInstanceSummary;
+ tooltip?: string | null;
type: string;
width?: number;
};
diff --git a/airflow-core/src/airflow/ui/src/components/Graph/useGraphLayout.ts
b/airflow-core/src/airflow/ui/src/components/Graph/useGraphLayout.ts
index 83b415ad141..a340ddd47a6 100644
--- a/airflow-core/src/airflow/ui/src/components/Graph/useGraphLayout.ts
+++ b/airflow-core/src/airflow/ui/src/components/Graph/useGraphLayout.ts
@@ -219,6 +219,7 @@ const generateElkGraph = ({
layoutOptions: direction === "RIGHT" ? { "elk.portConstraints":
"FIXED_SIDE" } : undefined,
operator: node.operator,
setupTeardownType: node.setup_teardown_type,
+ tooltip: node.tooltip,
type: node.type,
width,
};
diff --git
a/airflow-core/src/airflow/ui/src/components/TaskInstanceTooltip.test.tsx
b/airflow-core/src/airflow/ui/src/components/TaskInstanceTooltip.test.tsx
new file mode 100644
index 00000000000..bd2758c8bce
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/TaskInstanceTooltip.test.tsx
@@ -0,0 +1,144 @@
+/* eslint-disable unicorn/no-null */
+
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import "@testing-library/jest-dom";
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+
+import type { LightGridTaskInstanceSummary } from "openapi/requests/types.gen";
+import { Wrapper } from "src/utils/Wrapper";
+
+import TaskInstanceTooltip from "./TaskInstanceTooltip";
+
+describe("TaskInstanceTooltip", () => {
+ it("renders children directly when both taskInstance and tooltip are
undefined", () => {
+ render(
+ <TaskInstanceTooltip>
+ <span data-testid="child">Child content</span>
+ </TaskInstanceTooltip>,
+ { wrapper: Wrapper },
+ );
+
+ expect(screen.getByTestId("child")).toBeInTheDocument();
+ // Should not render a tooltip trigger wrapper
+ expect(screen.queryByRole("tooltip")).toBeNull();
+ });
+
+ it("shows state and dates for LightGridTaskInstanceSummary with both dates",
() => {
+ const taskInstance: LightGridTaskInstanceSummary = {
+ child_states: null,
+ max_end_date: "2025-01-01T01:00:00Z",
+ min_start_date: "2025-01-01T00:00:00Z",
+ state: "success",
+ task_display_name: "My Task",
+ task_id: "my_task",
+ };
+
+ render(
+ <TaskInstanceTooltip open taskInstance={taskInstance}>
+ <span>trigger</span>
+ </TaskInstanceTooltip>,
+ { wrapper: Wrapper },
+ );
+
+ expect(screen.getByText(/state/iu)).toBeInTheDocument();
+ expect(screen.getByText(/startDate/iu)).toBeInTheDocument();
+ expect(screen.getByText(/endDate/iu)).toBeInTheDocument();
+ expect(screen.getByText(/duration/iu)).toBeInTheDocument();
+ });
+
+ it("shows only start date when max_end_date is null", () => {
+ const taskInstance: LightGridTaskInstanceSummary = {
+ child_states: null,
+ max_end_date: null,
+ min_start_date: "2025-01-01T00:00:00Z",
+ state: "running",
+ task_display_name: "My Task",
+ task_id: "my_task",
+ };
+
+ render(
+ <TaskInstanceTooltip open taskInstance={taskInstance}>
+ <span>trigger</span>
+ </TaskInstanceTooltip>,
+ { wrapper: Wrapper },
+ );
+
+ expect(screen.getByText(/state/iu)).toBeInTheDocument();
+ expect(screen.getByText(/startDate/iu)).toBeInTheDocument();
+ expect(screen.queryByText(/endDate/iu)).toBeNull();
+ expect(screen.queryByText(/duration/iu)).toBeNull();
+ });
+
+ it("shows no dates when min_start_date is null", () => {
+ const taskInstance: LightGridTaskInstanceSummary = {
+ child_states: null,
+ max_end_date: null,
+ min_start_date: null,
+ state: "queued",
+ task_display_name: "My Task",
+ task_id: "my_task",
+ };
+
+ render(
+ <TaskInstanceTooltip open taskInstance={taskInstance}>
+ <span>trigger</span>
+ </TaskInstanceTooltip>,
+ { wrapper: Wrapper },
+ );
+
+ expect(screen.getByText(/state/iu)).toBeInTheDocument();
+ expect(screen.queryByText(/startDate/iu)).toBeNull();
+ expect(screen.queryByText(/endDate/iu)).toBeNull();
+ });
+
+ it("shows tooltip text when only tooltip prop is provided", () => {
+ render(
+ <TaskInstanceTooltip open tooltip="My group description">
+ <span>trigger</span>
+ </TaskInstanceTooltip>,
+ { wrapper: Wrapper },
+ );
+
+ expect(screen.getByText("My group description")).toBeInTheDocument();
+ });
+
+ it("shows both tooltip text and task instance data", () => {
+ const taskInstance: LightGridTaskInstanceSummary = {
+ child_states: { success: 3 },
+ max_end_date: "2025-01-01T02:00:00Z",
+ min_start_date: "2025-01-01T00:00:00Z",
+ state: "success",
+ task_display_name: "Group Task",
+ task_id: "group_task",
+ };
+
+ render(
+ <TaskInstanceTooltip open taskInstance={taskInstance} tooltip="Task
Group Info">
+ <span>trigger</span>
+ </TaskInstanceTooltip>,
+ { wrapper: Wrapper },
+ );
+
+ expect(screen.getByText("Task Group Info")).toBeInTheDocument();
+ expect(screen.getAllByText(/state/iu).length).toBeGreaterThan(0);
+ expect(screen.getByText(/startDate/iu)).toBeInTheDocument();
+ });
+});
diff --git a/airflow-core/src/airflow/ui/src/components/TaskInstanceTooltip.tsx
b/airflow-core/src/airflow/ui/src/components/TaskInstanceTooltip.tsx
index 6dbec7a6b4b..a375252f420 100644
--- a/airflow-core/src/airflow/ui/src/components/TaskInstanceTooltip.tsx
+++ b/airflow-core/src/airflow/ui/src/components/TaskInstanceTooltip.tsx
@@ -26,78 +26,107 @@ import type {
} from "openapi/requests/types.gen";
import Time from "src/components/Time";
import { Tooltip, type TooltipProps } from "src/components/ui";
-import { renderDuration, sortStateEntries } from "src/utils";
+import { getDuration, renderDuration, sortStateEntries } from "src/utils";
type Props = {
readonly taskInstance?: LightGridTaskInstanceSummary |
TaskInstanceHistoryResponse | TaskInstanceResponse;
+ readonly tooltip?: string | null;
} & Omit<TooltipProps, "content">;
-const TaskInstanceTooltip = ({ children, positioning, taskInstance, ...rest }:
Props) => {
+const TaskInstanceTooltip = ({ children, positioning, taskInstance, tooltip,
...rest }: Props) => {
const { t: translate } = useTranslation("common");
+ const hasTooltip = tooltip !== undefined && tooltip !== null;
+
const childEntries =
taskInstance !== undefined && "child_states" in taskInstance &&
taskInstance.child_states !== null
? sortStateEntries(taskInstance.child_states)
: [];
- return taskInstance === undefined ? (
+ return taskInstance === undefined && !hasTooltip ? (
children
) : (
<Tooltip
{...rest}
content={
- <Box>
- <Text>
- {translate("state")}:{" "}
- {taskInstance.state
- ? translate(`common:states.${taskInstance.state}`)
- : translate("common:states.no_status")}
- </Text>
- {childEntries.length > 0 ? (
- <VStack align="start" gap={1} mt={1}>
- {childEntries.map(([state, count]) => (
- <HStack gap={2} key={state}>
- <Box
- bg={`${state}.solid`}
- border="1px solid"
- borderColor="border.emphasized"
- borderRadius="2px"
- height="10px"
- width="10px"
- />
- <Text fontSize="xs">
- {count} {translate(`common:states.${state}`)}
- </Text>
- </HStack>
- ))}
- </VStack>
- ) : undefined}
- {"dag_run_id" in taskInstance ? (
- <Text>
- {translate("runId")}: {taskInstance.dag_run_id}
- </Text>
- ) : undefined}
- {"start_date" in taskInstance ? (
+ <VStack align="start" gap={1}>
+ {hasTooltip ? <Text fontWeight="bold">{tooltip}</Text> : undefined}
+ {taskInstance ? (
<>
- {taskInstance.try_number > 1 && (
- <Text>
- {translate("tryNumber")}: {taskInstance.try_number}
- </Text>
- )}
- <Text>
- {translate("startDate")}: <Time
datetime={taskInstance.start_date} />
- </Text>
<Text>
- {translate("endDate")}: <Time datetime={taskInstance.end_date}
/>
+ {translate("taskId")}: {taskInstance.task_id}
</Text>
<Text>
- {translate("duration")}:
{renderDuration(taskInstance.duration)}
+ {translate("state")}:{" "}
+ {taskInstance.state
+ ? translate(`common:states.${taskInstance.state}`)
+ : translate("common:states.no_status")}
</Text>
+ {"dag_run_id" in taskInstance ? (
+ <Text>
+ {translate("runId")}: {taskInstance.dag_run_id}
+ </Text>
+ ) : undefined}
+ {"start_date" in taskInstance ? (
+ <>
+ {taskInstance.try_number > 1 && (
+ <Text>
+ {translate("tryNumber")}: {taskInstance.try_number}
+ </Text>
+ )}
+ <Text>
+ {translate("startDate")}: <Time
datetime={taskInstance.start_date} />
+ </Text>
+ <Text>
+ {translate("endDate")}: <Time
datetime={taskInstance.end_date} />
+ </Text>
+ <Text>
+ {translate("duration")}:
{renderDuration(taskInstance.duration)}
+ </Text>
+ </>
+ ) : undefined}
+ {"min_start_date" in taskInstance && taskInstance.min_start_date
!== null ? (
+ <>
+ <Text>
+ {translate("startDate")}: <Time
datetime={taskInstance.min_start_date} />
+ </Text>
+ {taskInstance.max_end_date !== null && (
+ <>
+ <Text>
+ {translate("endDate")}: <Time
datetime={taskInstance.max_end_date} />
+ </Text>
+ <Text>
+ {translate("duration")}:{" "}
+ {getDuration(taskInstance.min_start_date,
taskInstance.max_end_date, false)}
+ </Text>
+ </>
+ )}
+ </>
+ ) : undefined}
+ {childEntries.length > 0 ? (
+ <VStack align="start" gap={1} ps={2}>
+ {childEntries.map(([state, count]) => (
+ <HStack gap={2} key={state}>
+ <Box
+ bg={`${state}.solid`}
+ border="1px solid"
+ borderColor="border.emphasized"
+ borderRadius="2px"
+ height="10px"
+ width="10px"
+ />
+ <Text fontSize="xs">
+ {count} {translate(`common:states.${state}`)}
+ </Text>
+ </HStack>
+ ))}
+ </VStack>
+ ) : undefined}
</>
) : undefined}
- </Box>
+ </VStack>
}
- key={taskInstance.task_id}
+ key={taskInstance?.task_id}
portalled
positioning={{
offset: {
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 dd5280ea9be..1a4b1413f21 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
@@ -16,14 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Badge, Flex } from "@chakra-ui/react";
-import { useTranslation } from "react-i18next";
+import { Badge, Box, Flex } from "@chakra-ui/react";
import { Link, useLocation, useParams, useSearchParams } from
"react-router-dom";
import type { LightGridTaskInstanceSummary } from "openapi/requests/types.gen";
-import { BasicTooltip } from "src/components/BasicTooltip";
import { StateIcon } from "src/components/StateIcon";
-import Time from "src/components/Time";
+import TaskInstanceTooltip from "src/components/TaskInstanceTooltip";
import { useHover } from "src/context/hover";
import { buildTaskInstanceUrl } from "src/utils/links";
@@ -41,7 +39,6 @@ type Props = {
export const GridTI = ({ dagId, instance, isGroup, isMapped, onClick, runId,
taskId }: Props) => {
const { hoveredTaskId, setHoveredTaskId } = useHover();
const { groupId: selectedGroupId, taskId: selectedTaskId } = useParams();
- const { t: translate } = useTranslation();
const location = useLocation();
const [searchParams] = useSearchParams();
@@ -83,56 +80,35 @@ export const GridTI = ({ dagId, instance, isGroup,
isMapped, onClick, runId, tas
py={0}
transition="background-color 0.2s"
>
- <BasicTooltip
- content={
- <>
- {translate("taskId")}: {taskId}
- <br />
- {translate("state")}:{" "}
- {instance.state
- ? translate(`common:states.${instance.state}`)
- : translate("common:states.no_status")}
- {instance.min_start_date !== null && (
- <>
- <br />
- {translate("startDate")}: <Time
datetime={instance.min_start_date} />
- </>
- )}
- {instance.max_end_date !== null && (
- <>
- <br />
- {translate("endDate")}: <Time datetime={instance.max_end_date}
/>
- </>
- )}
- </>
- }
- >
- <Link
- id={`grid-${runId}-${taskId}`}
- onClick={onClick}
- replace
- to={{
- pathname: taskUrl,
- search: redirectionSearch,
- }}
- >
- <Badge
- alignItems="center"
- borderRadius={4}
- colorPalette={instance.state ?? "none"}
- data-testid="task-state-badge"
- display="flex"
- height="14px"
- justifyContent="center"
- minH={0}
- p={0}
- variant="solid"
- width="14px"
+ <TaskInstanceTooltip openDelay={500} positioning={{ placement: "bottom"
}} taskInstance={instance}>
+ <Box as="span" display="inline-block">
+ <Link
+ id={`grid-${runId}-${taskId}`}
+ onClick={onClick}
+ replace
+ to={{
+ pathname: taskUrl,
+ search: redirectionSearch,
+ }}
>
- <StateIcon size={10} state={instance.state} />
- </Badge>
- </Link>
- </BasicTooltip>
+ <Badge
+ alignItems="center"
+ borderRadius={4}
+ colorPalette={instance.state ?? "none"}
+ data-testid="task-state-badge"
+ display="flex"
+ height="14px"
+ justifyContent="center"
+ minH={0}
+ p={0}
+ variant="solid"
+ width="14px"
+ >
+ <StateIcon size={10} state={instance.state} />
+ </Badge>
+ </Link>
+ </Box>
+ </TaskInstanceTooltip>
</Flex>
);
};
diff --git a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.test.ts
b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.test.ts
index c8faa00b0c9..0f9ba62c67c 100644
--- a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.test.ts
+++ b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.test.ts
@@ -49,6 +49,13 @@ describe("getDuration", () => {
expect(getDuration(start, end)).toBe("02:30:00");
});
+ it("omits milliseconds when withMilliseconds is false", () => {
+ const start = "2024-03-14T10:00:00.000Z";
+ const end = "2024-03-14T10:00:05.511Z";
+
+ expect(getDuration(start, end, false)).toBe("00:00:05");
+ });
+
it("handles small, null or undefined values", () => {
// eslint-disable-next-line unicorn/no-null
expect(getDuration(null, null)).toBe(undefined);
diff --git a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
index ca251b8575a..1fe6b65ef88 100644
--- a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
+++ b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
@@ -47,10 +47,18 @@ export const renderDuration = (
: dayjs.duration(durationSeconds, "seconds").format("D[d]HH:mm:ss");
};
-export const getDuration = (startDate?: string | null, endDate?: string |
null) => {
- const seconds = dayjs.duration(dayjs(endDate ?? undefined).diff(startDate ??
undefined)).asSeconds();
+export const getDuration = (
+ startDate?: string | null,
+ endDate?: string | null,
+ withMilliseconds: boolean = true,
+) => {
+ if (startDate === undefined || startDate === null || endDate === undefined
|| endDate === null) {
+ return undefined;
+ }
+
+ const seconds = dayjs.duration(dayjs(endDate).diff(startDate)).asSeconds();
- return renderDuration(seconds);
+ return renderDuration(seconds, withMilliseconds);
};
export const formatDate = (
diff --git
a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
index e8ccafaa32d..fd67e7e3267 100644
--- a/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
+++ b/airflow-core/tests/unit/api_fastapi/core_api/routes/ui/test_grid.py
@@ -698,24 +698,24 @@ class TestGetGridDataEndpoint:
"task_id": "t1",
"task_display_name": "t1",
"child_states": None,
- "max_end_date": None,
- "min_start_date": None,
+ "max_end_date": "2025-03-02T00:00:00Z",
+ "min_start_date": "2025-03-01T23:59:58Z",
},
{
"state": "success",
"task_id": "t2",
"task_display_name": "t2",
"child_states": None,
- "max_end_date": None,
- "min_start_date": None,
+ "max_end_date": "2025-03-02T00:00:02Z",
+ "min_start_date": "2025-03-02T00:00:00Z",
},
{
"state": "success",
"task_id": "t7",
"task_display_name": "t7",
"child_states": None,
- "max_end_date": None,
- "min_start_date": None,
+ "max_end_date": "2025-03-02T00:00:04Z",
+ "min_start_date": "2025-03-02T00:00:02Z",
},
{
"child_states": {"success": 4},
@@ -730,8 +730,8 @@ class TestGetGridDataEndpoint:
"task_id": "task_group-1.t6",
"task_display_name": "task_group-1.t6",
"child_states": None,
- "max_end_date": None,
- "min_start_date": None,
+ "max_end_date": "2025-03-02T00:00:06Z",
+ "min_start_date": "2025-03-02T00:00:04Z",
},
{
"child_states": {"success": 3},
@@ -746,24 +746,24 @@ class TestGetGridDataEndpoint:
"task_id": "task_group-1.task_group-2.t3",
"task_display_name": "task_group-1.task_group-2.t3",
"child_states": None,
- "max_end_date": None,
- "min_start_date": None,
+ "max_end_date": "2025-03-02T00:00:08Z",
+ "min_start_date": "2025-03-02T00:00:06Z",
},
{
"state": "success",
"task_id": "task_group-1.task_group-2.t4",
"task_display_name": "task_group-1.task_group-2.t4",
"child_states": None,
- "max_end_date": None,
- "min_start_date": None,
+ "max_end_date": "2025-03-02T00:00:10Z",
+ "min_start_date": "2025-03-02T00:00:08Z",
},
{
"state": "success",
"task_id": "task_group-1.task_group-2.t5",
"task_display_name": "task_group-1.task_group-2.t5",
"child_states": None,
- "max_end_date": None,
- "min_start_date": None,
+ "max_end_date": "2025-03-02T00:00:12Z",
+ "min_start_date": "2025-03-02T00:00:10Z",
},
],
}
@@ -812,8 +812,8 @@ class TestGetGridDataEndpoint:
"task_id": "mapped_task_group.subtask",
"task_display_name": "mapped_task_group.subtask",
"child_states": None,
- "max_end_date": None,
- "min_start_date": None,
+ "max_end_date": "2024-12-30T01:02:03Z",
+ "min_start_date": "2024-12-30T01:00:00Z",
},
{
"state": "success",