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
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { columns } from "./columns";

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

Expand Down Expand Up @@ -62,40 +62,42 @@ const ActionAccordion = ({ affectedTasks, note, setNote }: Props) => {
</Accordion.ItemContent>
</Accordion.Item>
) : undefined}
<Accordion.Item key="note" value="note">
<Accordion.ItemTrigger>
<Text fontWeight="bold">Note</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%"
{note === undefined ? undefined : (
<Accordion.Item key="note" value="note">
<Accordion.ItemTrigger>
<Text fontWeight="bold">Note</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">Add a note...</Text>
)}
</Editable.Preview>
<Editable.Textarea
data-testid="notes-input"
height="200px"
overflowY="auto"
placeholder="Add a note..."
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">Add a note...</Text>
)}
</Editable.Preview>
<Editable.Textarea
data-testid="notes-input"
height="200px"
overflowY="auto"
placeholder="Add a note..."
resize="none"
/>
</Editable.Root>
</Accordion.ItemContent>
</Accordion.Item>
)}
</Accordion.Root>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@
import { Box, useDisclosure } from "@chakra-ui/react";
import { CgRedo } from "react-icons/cg";

import type { TaskInstanceResponse } from "openapi/requests/types.gen";
import type { TaskActionProps } from "src/components/MarkAs/utils";
import ActionButton from "src/components/ui/ActionButton";

import ClearTaskInstanceDialog from "./ClearTaskInstanceDialog";
import { ClearTaskInstanceDialog } from "./ClearTaskInstanceDialog";

type Props = {
readonly taskInstance: TaskInstanceResponse;
readonly taskActionProps: TaskActionProps;
readonly withText?: boolean;
};

const ClearTaskInstanceButton = ({ taskInstance, withText = true }: Props) => {
const ClearTaskInstanceButton = ({ taskActionProps, withText = true }: Props) => {
const { onClose, onOpen, open } = useDisclosure();

return (
Expand All @@ -43,7 +43,7 @@ const ClearTaskInstanceButton = ({ taskInstance, withText = true }: Props) => {
/>

{open ? (
<ClearTaskInstanceDialog onClose={onClose} open={open} taskInstance={taskInstance} />
<ClearTaskInstanceDialog onClose={onClose} open={open} taskActionProps={taskActionProps} />
) : undefined}
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { Flex, Heading, VStack } from "@chakra-ui/react";
import { useState } from "react";
import { CgRedo } from "react-icons/cg";

import type { TaskInstanceResponse } from "openapi/requests/types.gen";
import { ActionAccordion } from "src/components/ActionAccordion";
import type { TaskActionProps } from "src/components/MarkAs/utils";
import Time from "src/components/Time";
import { Button, Dialog } from "src/components/ui";
import SegmentedControl from "src/components/ui/SegmentedControl";
Expand All @@ -32,16 +32,23 @@ import { usePatchTaskInstance } from "src/queries/usePatchTaskInstance";
type Props = {
readonly onClose: () => void;
readonly open: boolean;
readonly taskInstance: TaskInstanceResponse;
readonly taskActionProps: TaskActionProps;
};

const ClearTaskInstanceDialog = ({ onClose, open, taskInstance }: Props) => {
const taskId = taskInstance.task_id;
const mapIndex = taskInstance.map_index;

const dagId = taskInstance.dag_id;
const dagRunId = taskInstance.dag_run_id;

export const ClearTaskInstanceDialog = ({
onClose,
open,
taskActionProps: {
dagId,
dagRunId,
logicalDate,
mapIndex = -1,
note: taskNote,
startDate,
taskDisplayName,
taskId,
},
}: Props) => {
const { isPending, mutate } = useClearTaskInstances({
dagId,
dagRunId,
Expand All @@ -56,7 +63,8 @@ const ClearTaskInstanceDialog = ({ onClose, open, taskInstance }: Props) => {
const upstream = selectedOptions.includes("upstream");
const downstream = selectedOptions.includes("downstream");

const [note, setNote] = useState<string | null>(taskInstance.note);
// eslint-disable-next-line unicorn/no-null
const [note, setNote] = useState<string | null>(taskNote ?? null);
const { isPending: isPendingPatchDagRun, mutate: mutatePatchTaskInstance } = usePatchTaskInstance({
dagId,
dagRunId,
Expand Down Expand Up @@ -91,8 +99,7 @@ const ClearTaskInstanceDialog = ({ onClose, open, taskInstance }: Props) => {
<Dialog.Header>
<VStack align="start" gap={4}>
<Heading size="xl">
<strong>Clear Task Instance:</strong> {taskInstance.task_display_name}{" "}
<Time datetime={taskInstance.start_date} />
<strong>Clear Task Instance:</strong> {taskDisplayName ?? taskId} <Time datetime={startDate} />
</Heading>
</VStack>
</Dialog.Header>
Expand All @@ -105,15 +112,19 @@ const ClearTaskInstanceDialog = ({ onClose, open, taskInstance }: Props) => {
multiple
onChange={setSelectedOptions}
options={[
{ disabled: taskInstance.logical_date === null, label: "Past", value: "past" },
{ disabled: taskInstance.logical_date === null, label: "Future", value: "future" },
{ disabled: !Boolean(logicalDate), label: "Past", value: "past" },
{ disabled: !Boolean(logicalDate), label: "Future", value: "future" },
{ label: "Upstream", value: "upstream" },
{ label: "Downstream", value: "downstream" },
{ label: "Only Failed", value: "onlyFailed" },
]}
/>
</Flex>
<ActionAccordion affectedTasks={affectedTasks} note={note} setNote={setNote} />
<ActionAccordion
affectedTasks={affectedTasks}
note={taskNote === undefined ? undefined : note}
Copy link
Contributor

Choose a reason for hiding this comment

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

I briefly thought about if it would make sense to keep the notes fied and if all are cleared then the note is applied to all mapped tasks?
But not critical / blocking. Can also be adjusted in future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Mark state as and setting a note are the same patch endpoint which doesn't work right now.

Copy link
Member

Choose a reason for hiding this comment

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

If I remember correctly you need to pass a tuple somewhere.

I’ll double check tomorrow. Otherwise I think we should fix the backend

Copy link
Member

Choose a reason for hiding this comment

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

Oh that’s the bulk patch ? Anyway I need to check the code tomorrow

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool. I'll hold off. It would be great to have this all at once instead of refactoring again later.

Copy link
Member

Choose a reason for hiding this comment

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

Added to my todo list, I'll work on the backend part soon.

setNote={setNote}
/>
<Flex justifyContent="end" mt={3}>
<Button
colorPalette="blue"
Expand All @@ -133,7 +144,7 @@ const ClearTaskInstanceDialog = ({ onClose, open, taskInstance }: Props) => {
task_ids: [taskId],
},
});
if (note !== taskInstance.note) {
if (taskNote !== undefined && note !== taskNote) {
mutatePatchTaskInstance({
dagId,
dagRunId,
Expand All @@ -152,5 +163,3 @@ const ClearTaskInstanceDialog = ({ onClose, open, taskInstance }: Props) => {
</Dialog.Root>
);
};

export default ClearTaskInstanceDialog;
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import { Box, useDisclosure } from "@chakra-ui/react";
import { useState } from "react";
import { MdArrowDropDown } from "react-icons/md";

import type { TaskInstanceResponse, TaskInstanceState } from "openapi/requests/types.gen";
import type { TaskInstanceState } from "openapi/requests/types.gen";
import type { TaskActionProps } from "src/components/MarkAs/utils";
import { StateBadge } from "src/components/StateBadge";
import { Menu } from "src/components/ui";
import ActionButton from "src/components/ui/ActionButton";
Expand All @@ -29,11 +30,12 @@ import { allowedStates } from "../utils";
import MarkTaskInstanceAsDialog from "./MarkTaskInstanceAsDialog";

type Props = {
readonly taskInstance: TaskInstanceResponse;
readonly state?: TaskInstanceState | null;
readonly taskActionProps: TaskActionProps;
readonly withText?: boolean;
};

const MarkTaskInstanceAsButton = ({ taskInstance, withText = true }: Props) => {
const MarkTaskInstanceAsButton = ({ state: taskState, taskActionProps, withText = true }: Props) => {
const { onClose, onOpen, open } = useDisclosure();

const [state, setState] = useState<TaskInstanceState>("success");
Expand All @@ -54,10 +56,10 @@ const MarkTaskInstanceAsButton = ({ taskInstance, withText = true }: Props) => {
{allowedStates.map((menuState) => (
<Menu.Item
asChild
disabled={taskInstance.state === menuState}
disabled={taskState === menuState}
key={menuState}
onClick={() => {
if (taskInstance.state !== menuState) {
if (taskState !== menuState) {
setState(menuState);
onOpen();
}
Expand All @@ -73,7 +75,12 @@ const MarkTaskInstanceAsButton = ({ taskInstance, withText = true }: Props) => {
</Menu.Root>

{open ? (
<MarkTaskInstanceAsDialog onClose={onClose} open={open} state={state} taskInstance={taskInstance} />
<MarkTaskInstanceAsDialog
onClose={onClose}
open={open}
state={state}
taskActionProps={taskActionProps}
/>
) : undefined}
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import { Flex, Heading, VStack } from "@chakra-ui/react";
import { useState } from "react";

import type { TaskInstanceResponse, TaskInstanceState } from "openapi/requests/types.gen";
import type { TaskInstanceState } from "openapi/requests/types.gen";
import { ActionAccordion } from "src/components/ActionAccordion";
import { StateBadge } from "src/components/StateBadge";
import Time from "src/components/Time";
Expand All @@ -28,27 +28,39 @@ import SegmentedControl from "src/components/ui/SegmentedControl";
import { usePatchTaskInstance } from "src/queries/usePatchTaskInstance";
import { usePatchTaskInstanceDryRun } from "src/queries/usePatchTaskInstanceDryRun";

import type { TaskActionProps } from "../utils";

type Props = {
readonly onClose: () => void;
readonly open: boolean;
readonly state: TaskInstanceState;
readonly taskInstance: TaskInstanceResponse;
readonly taskActionProps: TaskActionProps;
};

const MarkTaskInstanceAsDialog = ({ onClose, open, state, taskInstance }: Props) => {
const dagId = taskInstance.dag_id;
const dagRunId = taskInstance.dag_run_id;
const taskId = taskInstance.task_id;
const mapIndex = taskInstance.map_index;

const MarkTaskInstanceAsDialog = ({
onClose,
open,
state,
taskActionProps: {
dagId,
dagRunId,
logicalDate,
mapIndex = -1,
note: taskNote,
startDate,
taskDisplayName,
taskId,
},
}: Props) => {
const [selectedOptions, setSelectedOptions] = useState<Array<string>>([]);

const past = selectedOptions.includes("past");
const future = selectedOptions.includes("future");
const upstream = selectedOptions.includes("upstream");
const downstream = selectedOptions.includes("downstream");

const [note, setNote] = useState<string | null>(taskInstance.note);
// eslint-disable-next-line unicorn/no-null
const [note, setNote] = useState<string | null>(taskNote ?? null);

const { isPending, mutate } = usePatchTaskInstance({
dagId,
Expand Down Expand Up @@ -86,8 +98,8 @@ const MarkTaskInstanceAsDialog = ({ onClose, open, state, taskInstance }: Props)
<Dialog.Header>
<VStack align="start" gap={4}>
<Heading size="xl">
<strong>Mark Task Instance as {state}:</strong> {taskInstance.task_display_name}{" "}
<Time datetime={taskInstance.start_date} /> <StateBadge state={state} />
<strong>Mark Task Instance as {state}:</strong> {taskDisplayName ?? taskId}{" "}
<Time datetime={startDate} /> <StateBadge state={state} />
</Heading>
</VStack>
</Dialog.Header>
Expand All @@ -100,8 +112,8 @@ const MarkTaskInstanceAsDialog = ({ onClose, open, state, taskInstance }: Props)
multiple
onChange={setSelectedOptions}
options={[
{ disabled: taskInstance.logical_date === null, label: "Past", value: "past" },
{ disabled: taskInstance.logical_date === null, label: "Future", value: "future" },
{ disabled: !Boolean(logicalDate), label: "Past", value: "past" },
{ disabled: !Boolean(logicalDate), label: "Future", value: "future" },
{ label: "Upstream", value: "upstream" },
{ label: "Downstream", value: "downstream" },
]}
Expand Down
11 changes: 11 additions & 0 deletions airflow-core/src/airflow/ui/src/components/MarkAs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,14 @@
import type { DAGRunPatchStates } from "openapi/requests/types.gen";

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

export type TaskActionProps = {
dagId: string;
dagRunId: string;
logicalDate?: string | null;
mapIndex?: number;
note?: string | null;
startDate: string | null;
taskDisplayName?: string;
taskId: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,18 @@ export const TaskLogPreview = ({
<Time datetime={taskInstance.run_after} ml={1} />
</Box>
<Flex gap={1}>
<ClearTaskInstanceButton taskInstance={taskInstance} withText={false} />
<ClearTaskInstanceButton
taskActionProps={{
dagId: taskInstance.dag_id,
dagRunId: taskInstance.dag_run_id,
mapIndex: taskInstance.map_index,
note: taskInstance.note,
startDate: taskInstance.start_date,
taskDisplayName: taskInstance.task_display_name,
taskId: taskInstance.task_id,
}}
withText={false}
/>
<Link asChild color="fg.info" fontSize="sm">
<RouterLink to={getTaskInstanceLink(taskInstance)}>View full logs</RouterLink>
</Link>
Expand Down
Loading