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 3d35fe602c Add more task details from rest api (#37394)
3d35fe602c is described below
commit 3d35fe602ce4c033e47dbe3a4d67f673a14f48d9
Author: Brent Bovenzi <[email protected]>
AuthorDate: Tue Feb 13 15:29:53 2024 -0500
Add more task details from rest api (#37394)
---
airflow/www/static/js/api/useTaskInstance.ts | 18 +--
airflow/www/static/js/dag/details/index.tsx | 12 +-
.../static/js/dag/details/taskInstance/Details.tsx | 147 +++++++++++++++++----
.../js/dag/details/taskInstance/ExtraLinks.tsx | 5 +-
.../static/js/dag/details/taskInstance/index.tsx | 42 +++---
5 files changed, 152 insertions(+), 72 deletions(-)
diff --git a/airflow/www/static/js/api/useTaskInstance.ts
b/airflow/www/static/js/api/useTaskInstance.ts
index 2b2818bc21..84d44ca02d 100644
--- a/airflow/www/static/js/api/useTaskInstance.ts
+++ b/airflow/www/static/js/api/useTaskInstance.ts
@@ -18,25 +18,18 @@
*/
import axios, { AxiosResponse } from "axios";
-import type { API, TaskInstance } from "src/types";
+import type { API } from "src/types";
import { useQuery } from "react-query";
import { useAutoRefresh } from "src/context/autorefresh";
import { getMetaValue } from "src/utils";
import type { SetOptional } from "type-fest";
-/* GridData.TaskInstance and API.TaskInstance are not compatible at the moment.
- * Remove this function when changing the api response for grid_data_url to
comply
- * with API.TaskInstance.
- */
-const convertTaskInstance = (ti: API.TaskInstance) =>
- ({ ...ti, runId: ti.dagRunId } as TaskInstance);
-
const taskInstanceApi = getMetaValue("task_instance_api");
interface Props
extends SetOptional<API.GetMappedTaskInstanceVariables, "mapIndex"> {
- enabled: boolean;
+ enabled?: boolean;
}
const useTaskInstance = ({
@@ -61,15 +54,10 @@ const useTaskInstance = ({
return useQuery(
["taskInstance", dagId, dagRunId, taskId, mapIndex],
- () =>
- axios.get<AxiosResponse, API.TaskInstance>(url, {
- headers: { Accept: "text/plain" },
- }),
+ () => axios.get<AxiosResponse, API.TaskInstance>(url),
{
- placeholderData: {},
refetchInterval: isRefreshOn && (autoRefreshInterval || 1) * 1000,
enabled,
- select: convertTaskInstance,
}
);
};
diff --git a/airflow/www/static/js/dag/details/index.tsx
b/airflow/www/static/js/dag/details/index.tsx
index 918fa7c804..3b058dab42 100644
--- a/airflow/www/static/js/dag/details/index.tsx
+++ b/airflow/www/static/js/dag/details/index.tsx
@@ -227,7 +227,11 @@ const Details = ({
<MarkInstanceAs
taskId={taskId}
runId={runId}
- state={instance?.state}
+ state={
+ !instance?.state || instance?.state === "none"
+ ? undefined
+ : instance.state
+ }
isGroup={isGroup}
isMapped={isMapped}
mapIndex={mapIndex}
@@ -348,7 +352,11 @@ const Details = ({
mapIndex={mapIndex}
executionDate={run?.executionDate}
tryNumber={instance?.tryNumber}
- state={instance?.state}
+ state={
+ !instance?.state || instance?.state === "none"
+ ? undefined
+ : instance.state
+ }
/>
</TabPanel>
)}
diff --git a/airflow/www/static/js/dag/details/taskInstance/Details.tsx
b/airflow/www/static/js/dag/details/taskInstance/Details.tsx
index 5cf1c5cfb9..2fcbb74a57 100644
--- a/airflow/www/static/js/dag/details/taskInstance/Details.tsx
+++ b/airflow/www/static/js/dag/details/taskInstance/Details.tsx
@@ -18,7 +18,7 @@
*/
import React from "react";
-import { Text, Flex, Table, Tbody, Tr, Td, Divider } from "@chakra-ui/react";
+import { Text, Flex, Table, Tbody, Tr, Td, Code } from "@chakra-ui/react";
import { snakeCase } from "lodash";
import { getGroupAndMapSummary } from "src/utils";
@@ -26,32 +26,29 @@ import { getDuration, formatDuration } from
"src/datetime_utils";
import { SimpleStatus } from "src/dag/StatusBox";
import Time from "src/components/Time";
import { ClipboardText } from "src/components/Clipboard";
-import type { Task, TaskInstance, TaskState } from "src/types";
-import useTaskInstance from "src/api/useTaskInstance";
+import type {
+ API,
+ Task,
+ TaskInstance as GridTaskInstance,
+ TaskState,
+} from "src/types";
import DatasetUpdateEvents from "./DatasetUpdateEvents";
interface Props {
- instance: TaskInstance;
+ gridInstance: GridTaskInstance;
+ taskInstance?: API.TaskInstance;
group: Task;
- dagId: string;
}
-const Details = ({ instance, group, dagId }: Props) => {
+const Details = ({ gridInstance, taskInstance, group }: Props) => {
const isGroup = !!group.children;
const summary: React.ReactNode[] = [];
- const { taskId, runId, startDate, endDate, state, mappedStates, mapIndex } =
- instance;
+ const { taskId, runId, startDate, endDate, state } = gridInstance;
- const { isMapped, tooltip, operator, hasOutletDatasets, triggerRule } =
group;
+ const mappedStates = !taskInstance ? gridInstance.mappedStates : undefined;
- const { data: apiTI } = useTaskInstance({
- dagId,
- dagRunId: runId,
- taskId,
- mapIndex,
- enabled: !isGroup && !isMapped,
- });
+ const { isMapped, tooltip, operator, hasOutletDatasets, triggerRule } =
group;
const { totalTasks, childTaskMap } = getGroupAndMapSummary({
group,
@@ -83,37 +80,44 @@ const Details = ({ instance, group, dagId }: Props) => {
state &&
["success", "failed", "upstream_failed", "skipped"].includes(state);
const isOverall = (isMapped || isGroup) && "Overall ";
+
return (
<Flex flexWrap="wrap" justifyContent="space-between">
- {state === "deferred" && (
+ {!!taskInstance?.trigger && !!taskInstance?.triggererJob && (
<>
- <Text as="strong">Triggerer info</Text>
- <Divider my={2} />
+ <Text as="strong" mb={3}>
+ Triggerer info
+ </Text>
<Table variant="striped" mb={3}>
<Tbody>
<Tr>
<Td>Trigger class</Td>
- <Td>{`${apiTI?.trigger?.classpath}`}</Td>
+ <Td>{`${taskInstance?.trigger?.classpath}`}</Td>
+ </Tr>
+ <Tr>
+ <Td>Trigger ID</Td>
+ <Td>{`${taskInstance?.trigger?.id}`}</Td>
</Tr>
<Tr>
<Td>Trigger creation time</Td>
- <Td>{`${apiTI?.trigger?.createdDate}`}</Td>
+ <Td>{`${taskInstance?.trigger?.createdDate}`}</Td>
</Tr>
<Tr>
<Td>Assigned triggerer</Td>
- <Td>{`${apiTI?.triggererJob?.hostname}`}</Td>
+ <Td>{`${taskInstance?.triggererJob?.hostname}`}</Td>
</Tr>
<Tr>
<Td>Latest triggerer heartbeat</Td>
- <Td>{`${apiTI?.triggererJob?.latestHeartbeat}`}</Td>
+ <Td>{`${taskInstance?.triggererJob?.latestHeartbeat}`}</Td>
</Tr>
</Tbody>
</Table>
</>
)}
- <Text as="strong">Task Instance Details</Text>
- <Divider my={2} />
+ <Text as="strong" mb={3}>
+ Task Instance Details
+ </Text>
<Table variant="striped">
<Tbody>
{tooltip && (
@@ -167,10 +171,16 @@ const Details = ({ instance, group, dagId }: Props) => {
</Text>
</Td>
</Tr>
- {mapIndex !== undefined && (
+ {taskInstance?.mapIndex !== undefined && (
<Tr>
<Td>Map Index</Td>
- <Td>{mapIndex}</Td>
+ <Td>{taskInstance.mapIndex}</Td>
+ </Tr>
+ )}
+ {!!taskInstance?.tryNumber && (
+ <Tr>
+ <Td>Try Number</Td>
+ <Td>{taskInstance.tryNumber}</Td>
</Tr>
)}
{operator && (
@@ -210,6 +220,89 @@ const Details = ({ instance, group, dagId }: Props) => {
</Td>
</Tr>
)}
+ {!!taskInstance?.pid && (
+ <Tr>
+ <Td>Process ID (PID)</Td>
+ <Td>
+ <ClipboardText value={taskInstance.pid.toString()} />
+ </Td>
+ </Tr>
+ )}
+ {!!taskInstance?.hostname && (
+ <Tr>
+ <Td>Hostname</Td>
+ <Td>
+ <ClipboardText value={taskInstance.hostname} />
+ </Td>
+ </Tr>
+ )}
+ {taskInstance?.renderedFields && (
+ <>
+ {Object.keys(taskInstance.renderedFields).map((key) => {
+ const renderedFields = taskInstance.renderedFields as Record<
+ string,
+ unknown
+ >;
+ if (renderedFields[key]) {
+ return (
+ <Tr key={key}>
+ <Td>{key}</Td>
+ <Td>
+ <Code fontSize="md">
+ {JSON.stringify(renderedFields[key])}
+ </Code>
+ </Td>
+ </Tr>
+ );
+ }
+ return null;
+ })}
+ </>
+ )}
+ {!!taskInstance?.pool && (
+ <Tr>
+ <Td>Pool</Td>
+ <Td>{taskInstance.pool}</Td>
+ </Tr>
+ )}
+ {!!taskInstance?.poolSlots && (
+ <Tr>
+ <Td>Pool Slots</Td>
+ <Td>{taskInstance.poolSlots}</Td>
+ </Tr>
+ )}
+ {!!taskInstance?.executorConfig && (
+ <Tr>
+ <Td>Executor Config</Td>
+ <Td>
+ <Code fontSize="md">{taskInstance.executorConfig}</Code>
+ </Td>
+ </Tr>
+ )}
+ {!!taskInstance?.unixname && (
+ <Tr>
+ <Td>Unix Name</Td>
+ <Td>{taskInstance.unixname}</Td>
+ </Tr>
+ )}
+ {!!taskInstance?.maxTries && (
+ <Tr>
+ <Td>Max Tries</Td>
+ <Td>{taskInstance.maxTries}</Td>
+ </Tr>
+ )}
+ {!!taskInstance?.queue && (
+ <Tr>
+ <Td>Queue</Td>
+ <Td>{taskInstance.queue}</Td>
+ </Tr>
+ )}
+ {!!taskInstance?.priorityWeight && (
+ <Tr>
+ <Td>Priority Weight</Td>
+ <Td>{taskInstance.priorityWeight}</Td>
+ </Tr>
+ )}
</Tbody>
</Table>
{hasOutletDatasets && (
diff --git a/airflow/www/static/js/dag/details/taskInstance/ExtraLinks.tsx
b/airflow/www/static/js/dag/details/taskInstance/ExtraLinks.tsx
index a1f88e9623..1cd4c450c3 100644
--- a/airflow/www/static/js/dag/details/taskInstance/ExtraLinks.tsx
+++ b/airflow/www/static/js/dag/details/taskInstance/ExtraLinks.tsx
@@ -54,10 +54,9 @@ const ExtraLinks = ({
url && /^(?:[a-z]+:)?\/\//.test(url);
return (
- <Box mb={3}>
+ <Box my={3}>
<Text as="strong">Extra Links</Text>
- <Divider my={2} />
- <Flex flexWrap="wrap">
+ <Flex flexWrap="wrap" mt={3}>
{links.map(({ name, url }) => (
<Button
key={name}
diff --git a/airflow/www/static/js/dag/details/taskInstance/index.tsx
b/airflow/www/static/js/dag/details/taskInstance/index.tsx
index bb5f63fb5b..71f8717dd5 100644
--- a/airflow/www/static/js/dag/details/taskInstance/index.tsx
+++ b/airflow/www/static/js/dag/details/taskInstance/index.tsx
@@ -22,7 +22,7 @@ import { Box } from "@chakra-ui/react";
import { useGridData, useTaskInstance } from "src/api";
import { getMetaValue, getTask, useOffsetTop } from "src/utils";
-import type { DagRun, TaskInstance as TaskInstanceType } from "src/types";
+import type { DagRun, TaskInstance as GridTaskInstance } from "src/types";
import NotesAccordion from "src/dag/details/NotesAccordion";
import TaskNav from "./Nav";
@@ -34,7 +34,7 @@ const dagId = getMetaValue("dag_id")!;
interface Props {
taskId: string;
runId: DagRun["runId"];
- mapIndex: TaskInstanceType["mapIndex"];
+ mapIndex: GridTaskInstance["mapIndex"];
}
const TaskInstance = ({ taskId, runId, mapIndex }: Props) => {
@@ -56,19 +56,16 @@ const TaskInstance = ({ taskId, runId, mapIndex }: Props)
=> {
const isGroup = !!children;
const isGroupOrMappedTaskSummary = isGroup || isMappedTaskSummary;
- const { data: mappedTaskInstance } = useTaskInstance({
+ const { data: taskInstance } = useTaskInstance({
dagId,
dagRunId: runId,
taskId,
mapIndex,
- enabled: isMapIndexDefined,
+ enabled: (!isGroup && !isMapped) || isMapIndexDefined,
});
+ const gridInstance = group?.instances.find((ti) => ti.runId === runId);
- const instance = isMapIndexDefined
- ? mappedTaskInstance
- : group?.instances.find((ti) => ti.runId === runId);
-
- if (!group || !run || !instance) return null;
+ if (!group || !run || !gridInstance) return null;
const { executionDate } = run;
@@ -94,31 +91,26 @@ const TaskInstance = ({ taskId, runId, mapIndex }: Props)
=> {
dagId={dagId}
runId={runId}
taskId={taskId}
- mapIndex={instance.mapIndex}
- initialValue={instance.note}
- key={dagId + runId + taskId + instance.mapIndex}
- />
- )}
- {isMapped && group.extraLinks && isMapIndexDefined && (
- <ExtraLinks
- taskId={taskId}
- dagId={dagId}
- mapIndex={mapIndex}
- executionDate={executionDate}
- extraLinks={group?.extraLinks}
- tryNumber={instance.tryNumber}
+ mapIndex={gridInstance.mapIndex}
+ initialValue={gridInstance.note}
+ key={dagId + runId + taskId + gridInstance.mapIndex}
/>
)}
- {!isMapped && group.extraLinks && (
+ {!!group.extraLinks?.length && !isGroupOrMappedTaskSummary && (
<ExtraLinks
taskId={taskId}
dagId={dagId}
+ mapIndex={isMapped && isMapIndexDefined ? mapIndex : undefined}
executionDate={executionDate}
extraLinks={group?.extraLinks}
- tryNumber={instance.tryNumber}
+ tryNumber={taskInstance?.tryNumber || gridInstance.tryNumber}
/>
)}
- <Details instance={instance} group={group} dagId={dagId} />
+ <Details
+ gridInstance={gridInstance}
+ taskInstance={taskInstance}
+ group={group}
+ />
</Box>
);
};