This is an automated email from the ASF dual-hosted git repository.
jscheffl 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 9ce71fd2270 Add additional task instance attributes to task instance's
details section (#68378)
9ce71fd2270 is described below
commit 9ce71fd2270f634760b150324360bf2ee6bfa812
Author: Jayachandra Kasarla <[email protected]>
AuthorDate: Mon Jun 15 03:02:46 2026 +0530
Add additional task instance attributes to task instance's details section
(#68378)
* Add additional task instance attributes to Airflow 3 UI task instance
details page
* Add task instance attributes to task details page
* added task instance id and filtered out kwargs from trigger
* renamed Task Instance ID to ID in common.json
* Added RenderedJsonField component to render JSON in the details page
---------
Co-authored-by: Jayachandra Kasarla <[email protected]>
---
.../airflow/ui/public/i18n/locales/en/common.json | 4 ++
.../airflow/ui/src/pages/TaskInstance/Details.tsx | 63 +++++++++++++++++++++-
2 files changed, 66 insertions(+), 1 deletion(-)
diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
index c949d619b6c..cc27c1faf54 100644
--- a/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
+++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
@@ -285,10 +285,12 @@
"taskGroup_other": "Task Groups",
"taskId": "Task ID",
"taskInstance": {
+ "additionalAttributes": "Additional Task Instance Attributes",
"dagVersion": "Dag Version",
"executor": "Executor",
"executorConfig": "Executor Config",
"hostname": "Hostname",
+ "id": "ID",
"maxTries": "Max Tries",
"pid": "PID",
"pool": "Pool",
@@ -298,11 +300,13 @@
"queuedWhen": "Queued At",
"renderedMapIndex": "Rendered Map Index",
"scheduledWhen": "Scheduled At",
+ "trigger": "Trigger",
"triggerer": {
"assigned": "Assigned triggerer",
"class": "Trigger class",
"createdAt": "Trigger creation time",
"id": "Trigger ID",
+ "job": "Triggerer Job",
"latestHeartbeat": "Latest triggerer heartbeat",
"title": "Triggerer Info"
},
diff --git a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Details.tsx
b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Details.tsx
index 646fc36cdd9..6b5eccf7f5d 100644
--- a/airflow-core/src/airflow/ui/src/pages/TaskInstance/Details.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/TaskInstance/Details.tsx
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-import { Box, Flex, HStack, Table } from "@chakra-ui/react";
+import { Box, Flex, Heading, HStack, Table } from "@chakra-ui/react";
+import type { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { useParams, useSearchParams } from "react-router-dom";
@@ -25,6 +26,7 @@ import {
useTaskInstanceServiceGetTaskInstanceTryDetails,
} from "openapi/queries";
import { DagVersionDetails } from "src/components/DagVersionDetails";
+import RenderedJsonField from "src/components/RenderedJsonField";
import { StateBadge } from "src/components/StateBadge";
import { TaskTrySelect } from "src/components/TaskTrySelect";
import Time from "src/components/Time";
@@ -84,6 +86,40 @@ export const Details = () => {
},
);
+ const renderValue = (value: unknown): ReactNode => {
+ if (value === null || value === undefined || value === "") {
+ return translate("common:none", { defaultValue: "None" });
+ }
+
+ if (typeof value === "object") {
+ return <RenderedJsonField content={value} />;
+ }
+
+ if (typeof value === "string") {
+ return value;
+ }
+
+ if (typeof value === "number" || typeof value === "boolean" || typeof
value === "bigint") {
+ return value.toString();
+ }
+
+ return translate("common:none", { defaultValue: "None" });
+ };
+
+ // omit kwargs from trigger
+ const triggerWithoutKwargs = taskInstance?.trigger
+ ? (({ kwargs, ...rest }) => rest)(taskInstance.trigger)
+ : undefined;
+
+ const rawTaskInstanceDetails: Array<{ label: string; value: unknown }> = [
+ { label: translate("taskInstance.id"), value: taskInstance?.id },
+ { label: translate("tryNumber"), value: tryInstance?.try_number },
+ { label: translate("taskInstance.maxTries"), value: tryInstance?.max_tries
},
+ { label: translate("dagId"), value: tryInstance?.dag_id },
+ { label: translate("taskInstance.trigger"), value: triggerWithoutKwargs },
+ { label: translate("taskInstance.triggerer.job"), value:
taskInstance?.triggerer_job },
+ ];
+
return (
<Box p={2}>
{taskInstance === undefined || tryNumber === undefined ||
taskInstance.try_number <= 1 ? (
@@ -225,6 +261,31 @@ export const Details = () => {
</Table.Row>
</Table.Body>
</Table.Root>
+ <Box mt={6}>
+ <Heading as="h3" mb={3} size="sm">
+ {translate("taskInstance.additionalAttributes")}
+ </Heading>
+
+ <Table.Root striped>
+ <Table.Body>
+ {rawTaskInstanceDetails.map(({ label, value }) => {
+ const isObject = typeof value === "object" && value !== null;
+
+ return (
+ <Table.Row key={label}>
+ <Table.Cell>{label}</Table.Cell>
+ <Table.Cell
+ fontFamily={isObject ? undefined : "mono"}
+ whiteSpace={isObject ? undefined : "pre-wrap"}
+ >
+ {renderValue(value)}
+ </Table.Cell>
+ </Table.Row>
+ );
+ })}
+ </Table.Body>
+ </Table.Root>
+ </Box>
</Box>
);
};