diff --git a/airflow/www/static/js/api/useUpstreamDatasetEvents.ts b/airflow/www/static/js/api/useUpstreamDatasetEvents.ts index 995941613cfde..5ba492183ce70 100644 --- a/airflow/www/static/js/api/useUpstreamDatasetEvents.ts +++ b/airflow/www/static/js/api/useUpstreamDatasetEvents.ts @@ -33,7 +33,7 @@ export default function useUpstreamDatasetEvents({ runId }: Props) { const upstreamEventsUrl = ( getMetaValue("upstream_dataset_events_api") || `api/v1/dags/${dagId}/dagRuns/_DAG_RUN_ID_/upstreamDatasetEvents` - ).replace("_DAG_RUN_ID_", runId); + ).replace("_DAG_RUN_ID_", encodeURIComponent(runId)); return axios.get( upstreamEventsUrl ); diff --git a/airflow/www/static/js/dag/details/dagRun/DatasetTriggerEvents.tsx b/airflow/www/static/js/dag/details/dagRun/DatasetTriggerEvents.tsx index 07649d54d9a6f..4e0ed4a95660d 100644 --- a/airflow/www/static/js/dag/details/dagRun/DatasetTriggerEvents.tsx +++ b/airflow/www/static/js/dag/details/dagRun/DatasetTriggerEvents.tsx @@ -17,9 +17,10 @@ * under the License. */ import React, { useMemo } from "react"; -import { Box, Heading, Text } from "@chakra-ui/react"; +import { Box, Text } from "@chakra-ui/react"; import { + CodeCell, DatasetLink, Table, TaskInstanceLink, @@ -55,6 +56,12 @@ const DatasetTriggerEvents = ({ runId }: Props) => { accessor: "timestamp", Cell: TimeCell, }, + { + Header: "Extra", + accessor: "extra", + Cell: CodeCell, + disableSortBy: true, + }, ], [] ); @@ -63,7 +70,9 @@ const DatasetTriggerEvents = ({ runId }: Props) => { return ( - Dataset Events + + Dataset Events + Dataset updates that triggered this DAG run. diff --git a/airflow/www/static/js/dag/details/dagRun/Details.tsx b/airflow/www/static/js/dag/details/dagRun/Details.tsx new file mode 100644 index 0000000000000..c769da72d5c4f --- /dev/null +++ b/airflow/www/static/js/dag/details/dagRun/Details.tsx @@ -0,0 +1,193 @@ +/*! + * 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 React, { useEffect } from "react"; +import { + Flex, + Box, + Button, + Spacer, + Table, + Tbody, + Tr, + Td, + useClipboard, + Text, +} from "@chakra-ui/react"; + +import ReactJson from "react-json-view"; + +import type { DagRun as DagRunType } from "src/types"; +import { SimpleStatus } from "src/dag/StatusBox"; +import { ClipboardText } from "src/components/Clipboard"; +import { formatDuration, getDuration } from "src/datetime_utils"; +import Time from "src/components/Time"; +import RunTypeIcon from "src/components/RunTypeIcon"; + +interface Props { + run: DagRunType; +} + +const formatConf = (conf: string | null | undefined): string => { + if (!conf) { + return ""; + } + return JSON.stringify(JSON.parse(conf), null, 4); +}; + +const DagRunDetails = ({ run }: Props) => { + const { onCopy, setValue, hasCopied } = useClipboard(formatConf(run?.conf)); + + useEffect(() => { + setValue(formatConf(run?.conf)); + }, [run, setValue]); + + if (!run) return null; + const { + state, + runType, + lastSchedulingDecision, + dataIntervalStart, + dataIntervalEnd, + startDate, + endDate, + queuedAt, + externalTrigger, + conf, + confIsJson, + } = run; + + return ( + + + Dag Run Details + +
+ + + + + + + + + + + + + + {startDate && ( + + + + + )} + {lastSchedulingDecision && ( + + + + + )} + {queuedAt && ( + + + + + )} + {startDate && ( + + + + + )} + {endDate && ( + + + + + )} + {dataIntervalStart && dataIntervalEnd && ( + <> + + + + + + + + + + )} + + + + + + + {confIsJson ? ( + + ) : ( + + )} + + +
Status + + + {state || "no status"} + +
Run ID + +
Run type + + {runType} +
Run duration{formatDuration(getDuration(startDate, endDate))}
Last scheduling decision +
Queued at +
Started +
Ended +
Data interval start +
Data interval end +
Externally triggered{externalTrigger ? "True" : "False"}
Run config + + + + + + {conf ?? "None"}
+
+ ); +}; + +export default DagRunDetails; diff --git a/airflow/www/static/js/dag/details/dagRun/index.tsx b/airflow/www/static/js/dag/details/dagRun/index.tsx index 2bbdc5cdb014b..05679a598faa9 100644 --- a/airflow/www/static/js/dag/details/dagRun/index.tsx +++ b/airflow/www/static/js/dag/details/dagRun/index.tsx @@ -16,33 +16,16 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useRef } from "react"; -import { - Flex, - Box, - Button, - Divider, - Spacer, - Table, - Tbody, - Tr, - Td, - useClipboard, -} from "@chakra-ui/react"; - -import ReactJson from "react-json-view"; +import React, { useRef } from "react"; +import { Box } from "@chakra-ui/react"; import { useGridData } from "src/api"; import { getMetaValue, useOffsetTop } from "src/utils"; import type { DagRun as DagRunType } from "src/types"; -import { SimpleStatus } from "src/dag/StatusBox"; -import { ClipboardText } from "src/components/Clipboard"; -import { formatDuration, getDuration } from "src/datetime_utils"; -import Time from "src/components/Time"; -import RunTypeIcon from "src/components/RunTypeIcon"; import NotesAccordion from "src/dag/details/NotesAccordion"; import DatasetTriggerEvents from "./DatasetTriggerEvents"; +import DagRunDetails from "./Details"; const dagId = getMetaValue("dag_id"); @@ -50,13 +33,6 @@ interface Props { runId: DagRunType["runId"]; } -const formatConf = (conf: string | null | undefined): string => { - if (!conf) { - return ""; - } - return JSON.stringify(JSON.parse(conf), null, 4); -}; - const DagRun = ({ runId }: Props) => { const { data: { dagRuns }, @@ -65,27 +41,9 @@ const DagRun = ({ runId }: Props) => { const offsetTop = useOffsetTop(detailsRef); const run = dagRuns.find((dr) => dr.runId === runId); - const { onCopy, setValue, hasCopied } = useClipboard(formatConf(run?.conf)); - - useEffect(() => { - setValue(formatConf(run?.conf)); - }, [run, setValue]); if (!run) return null; - const { - state, - runType, - lastSchedulingDecision, - dataIntervalStart, - dataIntervalEnd, - startDate, - endDate, - queuedAt, - externalTrigger, - conf, - confIsJson, - note, - } = run; + const { runType, note } = run; return ( { overflowY="auto" pb={4} > - - - - - - - - - - - - - - - - - - - {startDate && ( - - - - - )} - {lastSchedulingDecision && ( - - - - - )} - {queuedAt && ( - - - - - )} - {startDate && ( - - - - - )} - {endDate && ( - - - - - )} - {dataIntervalStart && dataIntervalEnd && ( - <> - - - - - - - - - - )} - - - - - - - {confIsJson ? ( - - ) : ( - - )} - - -
Status - - - {state || "no status"} - -
Run ID - -
Run type - - {runType} -
Run duration{formatDuration(getDuration(startDate, endDate))}
Last scheduling decision -
Queued at -
Started -
Ended -
Data interval start -
Data interval end -
Externally triggered{externalTrigger ? "True" : "False"}
Run config - - - - - - {conf ?? "None"}
+ {runType === "dataset_triggered" && ( )} +
); }; diff --git a/airflow/www/static/js/dag/details/taskInstance/DatasetUpdateEvents.tsx b/airflow/www/static/js/dag/details/taskInstance/DatasetUpdateEvents.tsx index 919f4bca8ad7e..ad1ecf9245003 100644 --- a/airflow/www/static/js/dag/details/taskInstance/DatasetUpdateEvents.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/DatasetUpdateEvents.tsx @@ -17,9 +17,10 @@ * under the License. */ import React, { useMemo } from "react"; -import { Box, Heading, Text } from "@chakra-ui/react"; +import { Box, Text } from "@chakra-ui/react"; import { + CodeCell, DatasetLink, Table, TimeCell, @@ -63,6 +64,12 @@ const DatasetUpdateEvents = ({ runId, taskId }: Props) => { accessor: "createdDagruns", Cell: TriggeredRuns, }, + { + Header: "Extra", + accessor: "extra", + Cell: CodeCell, + disableSortBy: true, + }, ], [] ); @@ -70,8 +77,10 @@ const DatasetUpdateEvents = ({ runId, taskId }: Props) => { const data = useMemo(() => datasetEvents, [datasetEvents]); return ( - - Dataset Events + + + Dataset Events + Dataset updates caused by this task instance diff --git a/airflow/www/static/js/dag/details/taskInstance/Details.tsx b/airflow/www/static/js/dag/details/taskInstance/Details.tsx index 2fcbb74a57995..5e78825e5c289 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, Code } from "@chakra-ui/react"; +import { Text, Flex, Table, Tbody, Tr, Td, Code, Box } from "@chakra-ui/react"; import { snakeCase } from "lodash"; import { getGroupAndMapSummary } from "src/utils"; @@ -32,7 +32,6 @@ import type { TaskInstance as GridTaskInstance, TaskState, } from "src/types"; -import DatasetUpdateEvents from "./DatasetUpdateEvents"; interface Props { gridInstance: GridTaskInstance; @@ -48,7 +47,7 @@ const Details = ({ gridInstance, taskInstance, group }: Props) => { const mappedStates = !taskInstance ? gridInstance.mappedStates : undefined; - const { isMapped, tooltip, operator, hasOutletDatasets, triggerRule } = group; + const { isMapped, tooltip, operator, triggerRule } = group; const { totalTasks, childTaskMap } = getGroupAndMapSummary({ group, @@ -82,39 +81,7 @@ const Details = ({ gridInstance, taskInstance, group }: Props) => { const isOverall = (isMapped || isGroup) && "Overall "; return ( - - {!!taskInstance?.trigger && !!taskInstance?.triggererJob && ( - <> - - Triggerer info - -
- - - - - - - - - - - - - - - - - - - - - - -
Trigger class{`${taskInstance?.trigger?.classpath}`}
Trigger ID{`${taskInstance?.trigger?.id}`}
Trigger creation time{`${taskInstance?.trigger?.createdDate}`}
Assigned triggerer{`${taskInstance?.triggererJob?.hostname}`}
Latest triggerer heartbeat{`${taskInstance?.triggererJob?.latestHeartbeat}`}
- - )} - + Task Instance Details @@ -305,10 +272,7 @@ const Details = ({ gridInstance, taskInstance, group }: Props) => { )} - {hasOutletDatasets && ( - - )} - + ); }; diff --git a/airflow/www/static/js/dag/details/taskInstance/TriggererInfo.tsx b/airflow/www/static/js/dag/details/taskInstance/TriggererInfo.tsx new file mode 100644 index 0000000000000..cb093d433745c --- /dev/null +++ b/airflow/www/static/js/dag/details/taskInstance/TriggererInfo.tsx @@ -0,0 +1,65 @@ +/*! + * 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 React from "react"; +import { Text, Table, Tbody, Tr, Td, Box } from "@chakra-ui/react"; + +import type { API } from "src/types"; + +interface Props { + taskInstance?: API.TaskInstance; +} + +const TriggererInfo = ({ taskInstance }: Props) => { + if (!taskInstance?.trigger || !taskInstance?.triggererJob) return null; + + return ( + + + Triggerer info + + + + + + + + + + + + + + + + + + + + + + + + +
Trigger class{`${taskInstance?.trigger?.classpath}`}
Trigger ID{`${taskInstance?.trigger?.id}`}
Trigger creation time{`${taskInstance?.trigger?.createdDate}`}
Assigned triggerer{`${taskInstance?.triggererJob?.hostname}`}
Latest triggerer heartbeat{`${taskInstance?.triggererJob?.latestHeartbeat}`}
+
+ ); +}; + +export default TriggererInfo; diff --git a/airflow/www/static/js/dag/details/taskInstance/index.tsx b/airflow/www/static/js/dag/details/taskInstance/index.tsx index 71f8717dd5494..1b8f0a54ba164 100644 --- a/airflow/www/static/js/dag/details/taskInstance/index.tsx +++ b/airflow/www/static/js/dag/details/taskInstance/index.tsx @@ -28,6 +28,8 @@ import NotesAccordion from "src/dag/details/NotesAccordion"; import TaskNav from "./Nav"; import ExtraLinks from "./ExtraLinks"; import Details from "./Details"; +import DatasetUpdateEvents from "./DatasetUpdateEvents"; +import TriggererInfo from "./TriggererInfo"; const dagId = getMetaValue("dag_id")!; @@ -106,6 +108,10 @@ const TaskInstance = ({ taskId, runId, mapIndex }: Props) => { tryNumber={taskInstance?.tryNumber || gridInstance.tryNumber} /> )} + {group.hasOutletDatasets && ( + + )} +
{ @@ -66,6 +67,12 @@ const Events = ({ datasetId }: { datasetId: number }) => { accessor: "createdDagruns", Cell: TriggeredRuns, }, + { + Header: "Extra", + accessor: "extra", + Cell: CodeCell, + disableSortBy: true, + }, ], [] );