Skip to content
Closed
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
13 changes: 13 additions & 0 deletions airflow-core/src/airflow/ui/public/i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@
"requiredActions": "Required Actions",
"xcoms": "XComs"
},
"bulkAction": {
"delete": {
"taskInstance": {
"success": {
"description": "{{count}} task instances deleted successfully. Keys: {{keys}}",
"title": "Delete Task Instances Request Successful"
},
"warning": "This will remove all metadata related to the Task Instances, including task instance history and audit record."
}
}
},
"collapseAllExtra": "Collapse all extra json",
"collapseDetailsPanel": "Collapse Details Panel",
"createdAssetEvent_one": "Created Asset Event",
Expand Down Expand Up @@ -74,6 +85,7 @@
"dagWarnings": "Dag warnings/errors",
"defaultToGraphView": "Default to graph view",
"defaultToGridView": "Default to grid view",
"delete": "Delete",
"direction": "Direction",
"docs": {
"documentation": "Documentation",
Expand Down Expand Up @@ -183,6 +195,7 @@
"users": "Users"
},
"selectLanguage": "Select Language",
"selected": "Selected",
"showDetailsPanel": "Show Details Panel",
"signedInAs": "Signed in as",
"source": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@
"requiredActions": "待回應的任務實例",
"xcoms": "XComs"
},
"bulkAction": {
"delete": {
"taskInstance": {
"success": {
"description": "已成功刪除 {{count}} 個任務實例。鍵:{{keys}}",
"title": "已提交刪除任務實例請求"
},
"warning": "這將刪除所有與任務實例相關的系統資料,包括歷史記錄和審計日誌。"
}
}
},
"collapseDetailsPanel": "收起詳細資訊",
"createdAssetEvent_one": "已建立資源事件",
"createdAssetEvent_other": "已建立資源事件",
Expand Down Expand Up @@ -73,6 +84,7 @@
"dagWarnings": "Dag 警告 / 錯誤",
"defaultToGraphView": "預設使用圖形視圖",
"defaultToGridView": "預設使用網格視圖",
"delete": "刪除",
"direction": "書寫方向",
"docs": {
"documentation": "文件",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import { getColumns } from "./columns";

type Props = {
readonly affectedTasks?: TaskInstanceCollectionResponse;
readonly note: DAGRunResponse["note"];
readonly setNote: (value: string) => void;
readonly note?: DAGRunResponse["note"];
readonly setNote?: (value: string) => void;
};

// Table is in memory, pagination and sorting are disabled.
Expand Down Expand Up @@ -72,40 +72,42 @@ const ActionAccordion = ({ affectedTasks, note, setNote }: Props) => {
</Accordion.ItemContent>
</Accordion.Item>
) : undefined}
<Accordion.Item key="note" value="note">
<Accordion.ItemTrigger>
<Text fontWeight="bold">{translate("note.label")}</Text>
</Accordion.ItemTrigger>
<Accordion.ItemContent>
<Editable.Root
onChange={(event: ChangeEvent<HTMLInputElement>) => setNote(event.target.value)}
value={note ?? ""}
>
<Editable.Preview
_hover={{ backgroundColor: "transparent" }}
alignItems="flex-start"
as={VStack}
gap="0"
height="200px"
overflowY="auto"
width="100%"
{setNote && note !== undefined ? (
<Accordion.Item key="note" value="note">
<Accordion.ItemTrigger>
<Text fontWeight="bold">{translate("note.label")}</Text>
</Accordion.ItemTrigger>
<Accordion.ItemContent>
<Editable.Root
onChange={(event: ChangeEvent<HTMLInputElement>) => setNote(event.target.value)}
value={note ?? ""}
>
{Boolean(note) ? (
<ReactMarkdown>{note}</ReactMarkdown>
) : (
<Text color="fg.subtle">{translate("note.placeholder")}</Text>
)}
</Editable.Preview>
<Editable.Textarea
data-testid="notes-input"
height="200px"
overflowY="auto"
placeholder={translate("note.placeholder")}
resize="none"
/>
</Editable.Root>
</Accordion.ItemContent>
</Accordion.Item>
<Editable.Preview
_hover={{ backgroundColor: "transparent" }}
alignItems="flex-start"
as={VStack}
gap="0"
height="200px"
overflowY="auto"
width="100%"
>
{Boolean(note) ? (
<ReactMarkdown>{note}</ReactMarkdown>
) : (
<Text color="fg.subtle">{translate("note.placeholder")}</Text>
)}
</Editable.Preview>
<Editable.Textarea
data-testid="notes-input"
height="200px"
overflowY="auto"
placeholder={translate("note.placeholder")}
resize="none"
/>
</Editable.Root>
</Accordion.ItemContent>
</Accordion.Item>
) : undefined}
</Accordion.Root>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/*!
* 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 { useDisclosure } from "@chakra-ui/react";
import type { TaskInstanceResponse, TaskInstanceState } from "openapi-gen/requests/types.gen";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { MdArrowDropDown } from "react-icons/md";

import { StateBadge } from "src/components/StateBadge";
import { Menu, Tooltip } from "src/components/ui";
import ActionButton from "src/components/ui/ActionButton";

import PatchTaskInstancesDialog from "./MarkTaskInstancesAsDialog";

type Props = {
readonly clearSelections: () => void;
readonly dagId: string;
readonly dagRunId: string;
readonly patchKeys: Array<TaskInstanceResponse>;
};

const MarkTaskInstancesAsButton = ({ clearSelections, dagId, dagRunId, patchKeys }: Props) => {
const { onClose, onOpen, open } = useDisclosure();
const [selectedState, setSelectedState] = useState<TaskInstanceState>("success");

const allowedStates: Array<TaskInstanceState> = ["success", "failed"];

const { t: translate } = useTranslation();

if (patchKeys.length === 0) {
return undefined;
}

const type = translate("common:taskInstance_other");
const patchButtonText = translate("dags:runAndTaskActions.markAs.button", { type });

return (
<>
<Menu.Root positioning={{ gutter: 0, placement: "bottom" }}>
<Menu.Trigger asChild>
<ActionButton
actionName={patchButtonText}
colorPalette="blue"
flexDirection="row-reverse"
icon={<MdArrowDropDown />}
text={patchButtonText}
variant="outline"
withText
/>
</Menu.Trigger>
<Menu.Content>
{allowedStates.map((menuState) => {
const content = translate(
`dags:runAndTaskActions.markAs.buttonTooltip.${menuState === "success" ? "success" : "failed"}`,
);

return (
<Tooltip closeDelay={100} content={content} key={menuState} openDelay={100}>
<Menu.Item
asChild
key={menuState}
onClick={() => {
setSelectedState(menuState);
onOpen();
}}
value={menuState}
>
<StateBadge my={1} state={menuState}>
{translate(`common:states.${menuState}`)}
</StateBadge>
</Menu.Item>
</Tooltip>
);
})}
</Menu.Content>
</Menu.Root>
<PatchTaskInstancesDialog
clearSelections={clearSelections}
dagId={dagId}
dagRunId={dagRunId}
onClose={onClose}
open={open}
patchKeys={patchKeys}
selectedState={selectedState}
/>
</>
);
};

export default MarkTaskInstancesAsButton;
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*!
* 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 { Button, Flex, Heading, VStack } from "@chakra-ui/react";
import type {
TaskInstanceCollectionResponse,
TaskInstanceResponse,
TaskInstanceState,
} from "openapi-gen/requests/types.gen";
import { useState } from "react";
import { useTranslation } from "react-i18next";

import { ActionAccordion } from "src/components/ActionAccordion";
import { StateBadge } from "src/components/StateBadge";
import { Dialog } from "src/components/ui/Dialog";
import { useBulkPatchTaskInstances } from "src/queries/useBulkPatchTaskInstances";

type Props = {
readonly clearSelections: () => void;
readonly dagId: string;
readonly dagRunId: string;
readonly onClose: () => void;
readonly open: boolean;
readonly patchKeys: Array<TaskInstanceResponse>;
readonly selectedState: TaskInstanceState;
};

const MarkTaskInstancesAsDialog = ({
clearSelections,
dagId,
dagRunId,
onClose,
open,
patchKeys,
selectedState,
}: Props) => {
const [note, setNote] = useState<string | null>();

const { isPending, patchTaskInstances } = useBulkPatchTaskInstances({
dagId,
dagRunId,
onSuccessConfirm: () => {
clearSelections();
onClose();
},
});
const { t: translate } = useTranslation();

const affectedTasks = {
task_instances: patchKeys,
total_entries: patchKeys.length,
} as TaskInstanceCollectionResponse;

const handlePatch = (state: TaskInstanceState) => {
const actionValue = state === "failed" ? "set_failed" : "set_success";

patchTaskInstances(patchKeys, actionValue, { note });
onClose();
};

return (
<Dialog.Root lazyMount onOpenChange={onClose} open={open} size="xl">
<Dialog.Content backdrop>
<Dialog.Header>
<VStack align="start" gap={4}>
<Heading size="xl">
<strong>
{translate("dags:runAndTaskActions.markAs.title", {
state: selectedState,
type: translate("common:taskInstance_other"),
})}
:
</strong>{" "}
<StateBadge state={selectedState} />
</Heading>
</VStack>
</Dialog.Header>

<Dialog.CloseTrigger />

<Dialog.Body width="full">
<ActionAccordion affectedTasks={affectedTasks} note={note} setNote={setNote} />
<Flex justifyContent="end" mt={3}>
<Button colorPalette="blue" loading={isPending} onClick={() => handlePatch(selectedState)}>
{translate("modal.confirm")}
</Button>
</Flex>
</Dialog.Body>
Comment on lines +92 to +103

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are missing the selector,

Image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it's fine for now since we use the 'bulk' endpoint.

</Dialog.Content>
</Dialog.Root>
);
};

export default MarkTaskInstancesAsDialog;
Loading
Loading