Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions airflow-core/src/airflow/ui/public/i18n/locales/en/dags.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -73,7 +74,9 @@ const ClearTaskInstanceConfirmationDialog = ({
const [isReady, setIsReady] = useState(false);

const handleConfirm = useCallback(() => {
if (onConfirm) onConfirm();
if (onConfirm) {
onConfirm();
}
onClose();
}, [onConfirm, onClose]);

Expand All @@ -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();
Expand All @@ -109,28 +111,25 @@ const ClearTaskInstanceConfirmationDialog = ({
<Dialog.Header>
<VStack align="start" gap={4}>
<Dialog.Title>
<Icon color="tomato" size="lg" pr="2">
<Icon color="tomato" pr="2" size="lg">
<GoAlertFill />
</Icon>
{translate("dags:runAndTaskActions.confirmationDialog.title")}
</Dialog.Title>
<Dialog.Description>
{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",
})}
</>
)}
</Dialog.Description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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;

Expand Down Expand Up @@ -111,90 +111,91 @@ const ClearTaskInstanceDialog = ({ onClose: onCloseDialog, open: openDialog, tas

return (
<>
<Dialog.Root lazyMount onOpenChange={onCloseDialog} open={openDialog ? !open : false} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
<VStack align="start" gap={4}>
<Heading size="xl">
<strong>
{translate("dags:runAndTaskActions.clear.title", {
type: translate("taskInstance_one"),
})}
:
</strong>{" "}
{taskInstance.task_display_name} <Time datetime={taskInstance.start_date} />
</Heading>
</VStack>
</Dialog.Header>

<Dialog.CloseTrigger />

<Dialog.Body width="full">
<Flex justifyContent="center">
<SegmentedControl
defaultValues={["downstream"]}
multiple
onChange={setSelectedOptions}
options={[
{
disabled: taskInstance.logical_date === null,
label: translate("dags:runAndTaskActions.options.past"),
value: "past",
},
{
disabled: taskInstance.logical_date === null,
label: translate("dags:runAndTaskActions.options.future"),
value: "future",
},
{
label: translate("dags:runAndTaskActions.options.upstream"),
value: "upstream",
},
{
label: translate("dags:runAndTaskActions.options.downstream"),
value: "downstream",
},
{
label: translate("dags:runAndTaskActions.options.onlyFailed"),
value: "onlyFailed",
},
]}
/>
</Flex>
<ActionAccordion affectedTasks={affectedTasks} note={note} setNote={setNote} />
<Flex
{...(shouldShowBundleVersionOption ? { alignItems: "center" } : {})}
justifyContent={shouldShowBundleVersionOption ? "space-between" : "end"}
mt={3}
>
{shouldShowBundleVersionOption ? (
<Dialog.Root lazyMount onOpenChange={onCloseDialog} open={openDialog ? !open : false} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
<VStack align="start" gap={4}>
<Heading size="xl">
<strong>
{translate("dags:runAndTaskActions.clear.title", {
type: translate("taskInstance_one"),
})}
:
</strong>{" "}
{taskInstance.task_display_name} <Time datetime={taskInstance.start_date} />
</Heading>
</VStack>
</Dialog.Header>

<Dialog.CloseTrigger />

<Dialog.Body width="full">
<Flex justifyContent="center">
<SegmentedControl
defaultValues={["downstream"]}
multiple
onChange={setSelectedOptions}
options={[
{
disabled: taskInstance.logical_date === null,
label: translate("dags:runAndTaskActions.options.past"),
value: "past",
},
{
disabled: taskInstance.logical_date === null,
label: translate("dags:runAndTaskActions.options.future"),
value: "future",
},
{
label: translate("dags:runAndTaskActions.options.upstream"),
value: "upstream",
},
{
label: translate("dags:runAndTaskActions.options.downstream"),
value: "downstream",
},
{
label: translate("dags:runAndTaskActions.options.onlyFailed"),
value: "onlyFailed",
},
]}
/>
</Flex>
<ActionAccordion affectedTasks={affectedTasks} note={note} setNote={setNote} />
<Flex
{...(shouldShowBundleVersionOption ? { alignItems: "center" } : {})}
justifyContent={shouldShowBundleVersionOption ? "space-between" : "end"}
mt={3}
>
{shouldShowBundleVersionOption ? (
<Checkbox
checked={runOnLatestVersion}
onCheckedChange={(event) => setRunOnLatestVersion(Boolean(event.checked))}
>
{translate("dags:runAndTaskActions.options.runOnLatestVersion")}
</Checkbox>
) : undefined}
<Checkbox
checked={runOnLatestVersion}
onCheckedChange={(event) => 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")}
</Checkbox>
) : undefined}
<Checkbox
checked={preventRunningTask}
onCheckedChange={(event) => setPreventRunningTask(Boolean(event.checked))}
style={{ marginRight: "auto"}}
>
{translate("dags:runAndTaskActions.options.preventRunningTasks")}
</Checkbox>
<Button
colorPalette="brand"
disabled={affectedTasks.total_entries === 0}
loading={isPending || isPendingPatchDagRun}
onClick={onOpen}
>
<CgRedo /> {translate("modal.confirm")}
</Button>
</Flex>
</Dialog.Body>
</Dialog.Content>
</Dialog.Root>
{open ? <ClearTaskInstanceConfirmationDialog
<Button
colorPalette="brand"
disabled={affectedTasks.total_entries === 0}
loading={isPending || isPendingPatchDagRun}
onClick={onOpen}
>
<CgRedo /> {translate("modal.confirm")}
</Button>
</Flex>
</Dialog.Body>
</Dialog.Content>
</Dialog.Root>
{open ? (
<ClearTaskInstanceConfirmationDialog
dagDetails={{
dagId,
dagRunId,
Expand Down Expand Up @@ -236,7 +237,8 @@ const ClearTaskInstanceDialog = ({ onClose: onCloseDialog, open: openDialog, tas
}}
open={open}
preventRunningTask={preventRunningTask}
/> : null}
/>
) : null}
</>
);
};
Expand Down
35 changes: 15 additions & 20 deletions airflow-core/src/airflow/ui/src/queries/useClearTaskInstances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import { useQueryClient } from "@tanstack/react-query";
import { useTranslation } from "react-i18next";


import {
UseDagRunServiceGetDagRunKeyFn,
useDagRunServiceGetDagRunsKey,
Expand All @@ -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,
Expand All @@ -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 },
Expand Down Expand Up @@ -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,
});
};
2 changes: 1 addition & 1 deletion airflow-core/src/airflow/ui/src/utils/datetimeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down