diff --git a/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json b/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json index aef5d3f65eea0..5ea7f9c74f4ec 100644 --- a/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json +++ b/airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json @@ -34,6 +34,10 @@ "error": "Failed to clear {{type}}", "title": "Clear {{type}}" }, + "confirmationDialog": { + "description": "Task is currently in a {{state}} state started by user {{user}} at {{time}}. \nThe user is unable to clear this task until it is done running or a user unchecks the \"Prevent rerun of running tasks\" option in the clear task dialog.", + "title": "Cannot Clear Task Instance" + }, "delete": { "button": "Delete {{type}}", "dialog": { @@ -61,14 +65,10 @@ "future": "Future", "onlyFailed": "Clear only failed tasks", "past": "Past", + "preventRunningTasks": "Prevent rerun if task is running", "queueNew": "Queue up new tasks", "runOnLatestVersion": "Run with latest bundle version", - "upstream": "Upstream", - "preventRunningTasks": "Prevent rerun if task is running" - }, - "confirmationDialog": { - "title": "Cannot Clear Task Instance", - "description": "Task is currently in a {{state}} state started by user {{user}} at {{time}}. \nThe user is unable to clear this task until it is done running or a user unchecks the \"Prevent rerun of running tasks\" option in the clear task dialog." + "upstream": "Upstream" } }, "search": { diff --git a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceConfirmationDialog.tsx b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceConfirmationDialog.tsx index a76a2ae778fe6..0a34c7335b108 100644 --- a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceConfirmationDialog.tsx +++ b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceConfirmationDialog.tsx @@ -16,10 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { useEffect, useState, useCallback } from "react"; import { VStack, Icon, Text, Spinner } from "@chakra-ui/react"; -import { GoAlertFill } from "react-icons/go"; +import { useEffect, useState, useCallback } from "react"; import { useTranslation } from "react-i18next"; +import { GoAlertFill } from "react-icons/go"; + import { Button, Dialog } from "src/components/ui"; import { useClearTaskInstancesDryRun } from "src/queries/useClearTaskInstancesDryRun"; import { getRelativeTime } from "src/utils/datetimeUtils"; @@ -73,7 +74,9 @@ const ClearTaskInstanceConfirmationDialog = ({ const [isReady, setIsReady] = useState(false); const handleConfirm = useCallback(() => { - if (onConfirm) onConfirm(); + if (onConfirm) { + onConfirm(); + } onClose(); }, [onConfirm, onClose]); @@ -83,8 +86,7 @@ const ClearTaskInstanceConfirmationDialog = ({ useEffect(() => { if (!isFetching && open && data) { - const isInTriggeringState = - taskCurrentState === "queued" || taskCurrentState === "scheduled"; + const isInTriggeringState = taskCurrentState === "queued" || taskCurrentState === "scheduled"; if (!preventRunningTask || !isInTriggeringState) { handleConfirm(); @@ -109,7 +111,7 @@ const ClearTaskInstanceConfirmationDialog = ({ - + {translate("dags:runAndTaskActions.confirmationDialog.title")} @@ -117,20 +119,17 @@ const ClearTaskInstanceConfirmationDialog = ({ {taskInstances.length > 0 && ( <> - {translate( - "dags:runAndTaskActions.confirmationDialog.description", - { - state: taskCurrentState, - time: - firstInstance?.start_date !== null && firstInstance?.start_date !== undefined - ? getRelativeTime(firstInstance.start_date) - : undefined, - user: - (firstInstance?.unixname?.trim().length ?? 0) > 0 - ? firstInstance?.unixname - : "unknown user", - } - )} + {translate("dags:runAndTaskActions.confirmationDialog.description", { + state: taskCurrentState, + time: + firstInstance?.start_date !== null && firstInstance?.start_date !== undefined + ? getRelativeTime(firstInstance.start_date) + : undefined, + user: + (firstInstance?.unixname?.trim().length ?? 0) > 0 + ? firstInstance?.unixname + : "unknown user", + })} )} diff --git a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx index fa4e85a3bf0d2..a2d5bc4b05adb 100644 --- a/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx +++ b/airflow-core/src/airflow/ui/src/components/Clear/TaskInstance/ClearTaskInstanceDialog.tsx @@ -30,6 +30,7 @@ import SegmentedControl from "src/components/ui/SegmentedControl"; import { useClearTaskInstances } from "src/queries/useClearTaskInstances"; import { useClearTaskInstancesDryRun } from "src/queries/useClearTaskInstancesDryRun"; import { usePatchTaskInstance } from "src/queries/usePatchTaskInstance"; + import ClearTaskInstanceConfirmationDialog from "./ClearTaskInstanceConfirmationDialog"; type Props = { @@ -44,7 +45,6 @@ const ClearTaskInstanceDialog = ({ onClose: onCloseDialog, open: openDialog, tas const { t: translate } = useTranslation(); const { onClose, onOpen, open } = useDisclosure(); - const dagId = taskInstance.dag_id; const dagRunId = taskInstance.dag_run_id; @@ -111,90 +111,91 @@ const ClearTaskInstanceDialog = ({ onClose: onCloseDialog, open: openDialog, tas return ( <> - - - - - - - {translate("dags:runAndTaskActions.clear.title", { - type: translate("taskInstance_one"), - })} - : - {" "} - {taskInstance.task_display_name} - - - - - - - - - - - - {shouldShowBundleVersionOption ? ( + + + + + + + {translate("dags:runAndTaskActions.clear.title", { + type: translate("taskInstance_one"), + })} + : + {" "} + {taskInstance.task_display_name} + + + + + + + + + + + + {shouldShowBundleVersionOption ? ( + setRunOnLatestVersion(Boolean(event.checked))} + > + {translate("dags:runAndTaskActions.options.runOnLatestVersion")} + + ) : undefined} setRunOnLatestVersion(Boolean(event.checked))} + checked={preventRunningTask} + onCheckedChange={(event) => setPreventRunningTask(Boolean(event.checked))} + style={{ marginRight: "auto" }} > - {translate("dags:runAndTaskActions.options.runOnLatestVersion")} + {translate("dags:runAndTaskActions.options.preventRunningTasks")} - ) : undefined} - setPreventRunningTask(Boolean(event.checked))} - style={{ marginRight: "auto"}} - > - {translate("dags:runAndTaskActions.options.preventRunningTasks")} - - - - - - - {open ? + {translate("modal.confirm")} + + + + + + {open ? ( + : null} + /> + ) : null} ); }; diff --git a/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts b/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts index a04db2fb31dd8..ce9822a63ef38 100644 --- a/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts +++ b/airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts @@ -19,7 +19,6 @@ import { useQueryClient } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; - import { UseDagRunServiceGetDagRunKeyFn, useDagRunServiceGetDagRunsKey, @@ -29,12 +28,12 @@ import { UseGridServiceGetGridTiSummariesKeyFn, useGridServiceGetGridTiSummariesKey, } from "openapi/queries"; +import type { ApiError } from "openapi/requests"; import type { ClearTaskInstancesBody, TaskInstanceCollectionResponse } from "openapi/requests/types.gen"; import { toaster } from "src/components/ui"; import { useClearTaskInstancesDryRunKey } from "./useClearTaskInstancesDryRun"; import { usePatchTaskInstanceDryRunKey } from "./usePatchTaskInstanceDryRun"; -import type { ApiError } from "openapi/requests"; export const useClearTaskInstances = ({ dagId, @@ -57,31 +56,27 @@ export const useClearTaskInstances = ({ const apiError = error as ApiError; description = typeof apiError.message === "string" ? apiError.message : ""; - const apiErrorWithDetail = apiError as unknown as { detail?: unknown }; - detail = - typeof apiErrorWithDetail.body.detail === "string" - ? apiErrorWithDetail.body.detail - : ""; - - if ( detail.includes("AirflowClearRunningTaskException") === true ) { - description = detail - } + const apiErrorWithDetail = apiError as unknown as { body?: { detail?: unknown } }; + detail = typeof apiErrorWithDetail.body?.detail === "string" ? apiErrorWithDetail.body.detail : ""; + + if (detail.includes("AirflowClearRunningTaskException")) { + description = detail; + } } else { // Fallback for completely unknown errors - description = translate("common:error.defaultMessage") + description = translate("common:error.defaultMessage"); } toaster.create({ - description: description, - title: translate("dags:runAndTaskActions.clear.error", { - type: translate("common:taskInstance_one"), - }), - type: "error", - }); + description, + title: translate("dags:runAndTaskActions.clear.error", { + type: translate("common:taskInstance_one"), + }), + type: "error", + }); }; - const onSuccess = async ( _: TaskInstanceCollectionResponse, variables: { dagId: string; requestBody: ClearTaskInstancesBody }, @@ -133,6 +128,6 @@ export const useClearTaskInstances = ({ onSuccess, // This function uses the mutation function of React // For showing the error toast immediately, set retry to 0 - retry: 0 + retry: 0, }); }; diff --git a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts index dc4eea6731fb7..14d357b9a5741 100644 --- a/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts +++ b/airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts @@ -18,8 +18,8 @@ */ import dayjs from "dayjs"; import dayjsDuration from "dayjs/plugin/duration"; -import tz from "dayjs/plugin/timezone"; import relativeTime from "dayjs/plugin/relativeTime"; +import tz from "dayjs/plugin/timezone"; dayjs.extend(dayjsDuration); dayjs.extend(relativeTime);