Skip to content

Commit 87fd225

Browse files
rszwajkosjd78
authored andcommitted
🐛 Add task actions to task manager drawer (konveyor#2004)
Resolves: konveyor#1972 --------- Signed-off-by: Radoslaw Szwajkowski <[email protected]>
1 parent 9ca674b commit 87fd225

File tree

4 files changed

+108
-44
lines changed

4 files changed

+108
-44
lines changed

client/src/app/components/task-manager/TaskManagerDrawer.tsx

+60-4
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@ import React, { forwardRef, useMemo, useState } from "react";
22
import { Link } from "react-router-dom";
33
import dayjs from "dayjs";
44
import {
5+
Dropdown,
6+
DropdownItem,
7+
DropdownList,
58
EmptyState,
69
EmptyStateActions,
710
EmptyStateBody,
811
EmptyStateFooter,
912
EmptyStateHeader,
1013
EmptyStateIcon,
1114
EmptyStateVariant,
15+
MenuToggle,
16+
MenuToggleElement,
1217
NotificationDrawer,
1318
NotificationDrawerBody,
1419
NotificationDrawerHeader,
@@ -18,15 +23,16 @@ import {
1823
NotificationDrawerListItemHeader,
1924
Tooltip,
2025
} from "@patternfly/react-core";
21-
import { CubesIcon } from "@patternfly/react-icons";
26+
import { CubesIcon, EllipsisVIcon } from "@patternfly/react-icons";
2227
import { css } from "@patternfly/react-styles";
2328

24-
import { TaskState } from "@app/api/models";
29+
import { Task, TaskState } from "@app/api/models";
2530
import { useTaskManagerContext } from "./TaskManagerContext";
2631
import { useServerTasks } from "@app/queries/tasks";
2732

2833
import "./TaskManagerDrawer.css";
2934
import { TaskStateIcon } from "../Icons";
35+
import { useTaskActions } from "@app/pages/tasks/useTaskActions";
3036

3137
/** A version of `Task` specific for the task manager drawer components */
3238
interface TaskManagerTask {
@@ -47,6 +53,9 @@ interface TaskManagerTask {
4753
applicationId: number;
4854
applicationName: string;
4955
preemptEnabled: boolean;
56+
57+
// full object to be used with library functions
58+
_: Task;
5059
}
5160

5261
const PAGE_SIZE = 20;
@@ -61,6 +70,9 @@ export const TaskManagerDrawer: React.FC<TaskManagerDrawerProps> = forwardRef(
6170
const { tasks } = useTaskManagerData();
6271

6372
const [expandedItems, setExpandedItems] = useState<number[]>([]);
73+
const [taskWithExpandedActions, setTaskWithExpandedAction] = useState<
74+
number | boolean
75+
>(false);
6476

6577
const closeDrawer = () => {
6678
setIsExpanded(!isExpanded);
@@ -109,6 +121,10 @@ export const TaskManagerDrawer: React.FC<TaskManagerDrawerProps> = forwardRef(
109121
: expandedItems.filter((i) => i !== task.id)
110122
);
111123
}}
124+
actionsExpanded={task.id === taskWithExpandedActions}
125+
onActionsExpandToggle={(flag: boolean) =>
126+
setTaskWithExpandedAction(flag && task.id)
127+
}
112128
/>
113129
))}
114130
</NotificationDrawerList>
@@ -130,13 +146,22 @@ const TaskItem: React.FC<{
130146
task: TaskManagerTask;
131147
expanded: boolean;
132148
onExpandToggle: (expand: boolean) => void;
133-
}> = ({ task, expanded, onExpandToggle }) => {
149+
actionsExpanded: boolean;
150+
onActionsExpandToggle: (expand: boolean) => void;
151+
}> = ({
152+
task,
153+
expanded,
154+
onExpandToggle,
155+
actionsExpanded,
156+
onActionsExpandToggle,
157+
}) => {
134158
const starttime = dayjs(task.started ?? task.createTime);
135159
const title = expanded
136160
? `${task.id} (${task.addon})`
137161
: `${task.id} (${task.addon}) - ${task.applicationName} - ${
138162
task.priority ?? 0
139163
}`;
164+
const taskActionItems = useTaskActions(task._);
140165

141166
return (
142167
<NotificationDrawerListItem
@@ -153,7 +178,36 @@ const TaskItem: React.FC<{
153178
title={title}
154179
icon={<TaskStateToIcon taskState={task.state} />}
155180
>
156-
{/* Put the item's action menu here */}
181+
<Dropdown
182+
onSelect={() => onActionsExpandToggle(false)}
183+
isOpen={actionsExpanded}
184+
onOpenChange={() => onActionsExpandToggle(false)}
185+
popperProps={{ position: "right" }}
186+
toggle={(toggleRef: React.Ref<MenuToggleElement>) => (
187+
<MenuToggle
188+
ref={toggleRef}
189+
isExpanded={actionsExpanded}
190+
isDisabled={taskActionItems.every(({ isDisabled }) => isDisabled)}
191+
onClick={() => onActionsExpandToggle(!actionsExpanded)}
192+
variant="plain"
193+
aria-label={`Actions for task ${task.name}`}
194+
>
195+
<EllipsisVIcon aria-hidden="true" />
196+
</MenuToggle>
197+
)}
198+
>
199+
<DropdownList>
200+
{taskActionItems.map(({ title, onClick, isDisabled }) => (
201+
<DropdownItem
202+
key={title}
203+
onClick={onClick}
204+
isDisabled={isDisabled}
205+
>
206+
{title}
207+
</DropdownItem>
208+
))}
209+
</DropdownList>
210+
</Dropdown>
157211
</NotificationDrawerListItemHeader>
158212
{expanded ? (
159213
<NotificationDrawerListItemBody
@@ -217,6 +271,8 @@ const useTaskManagerData = () => {
217271
applicationName: task.application.name,
218272
preemptEnabled: task?.policy?.preemptEnabled ?? false,
219273

274+
_: task,
275+
220276
// TODO: Add any checks that could be needed later...
221277
// - isCancelable (does the current user own the task? other things to check?)
222278
// - isPreemptionToggleAllowed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React, { FC } from "react";
2+
3+
import { Task } from "@app/api/models";
4+
import { ActionsColumn } from "@patternfly/react-table";
5+
import { useTaskActions } from "./useTaskActions";
6+
7+
export const TaskActionColumn: FC<{ task: Task }> = ({ task }) => {
8+
const actions = useTaskActions(task);
9+
return <ActionsColumn items={actions} />;
10+
};

client/src/app/pages/tasks/tasks-page.tsx

+3-38
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,7 @@ import {
1313
ToolbarContent,
1414
ToolbarItem,
1515
} from "@patternfly/react-core";
16-
import {
17-
Table,
18-
Tbody,
19-
Th,
20-
Thead,
21-
Tr,
22-
Td,
23-
ActionsColumn,
24-
} from "@patternfly/react-table";
16+
import { Table, Tbody, Th, Thead, Tr, Td } from "@patternfly/react-table";
2517
import { CubesIcon } from "@patternfly/react-icons";
2618

2719
import { FilterToolbar, FilterType } from "@app/components/FilterToolbar";
@@ -46,9 +38,9 @@ import { Task } from "@app/api/models";
4638
import { IconWithLabel, TaskStateIcon } from "@app/components/Icons";
4739
import { ManageColumnsToolbar } from "../applications/applications-table/components/manage-columns-toolbar";
4840
import dayjs from "dayjs";
49-
import { useTaskActions } from "./useTaskActions";
5041
import { formatPath } from "@app/utils/utils";
5142
import { Paths } from "@app/Paths";
43+
import { TaskActionColumn } from "./TaskActionColumn";
5244

5345
export const TasksPage: React.FC = () => {
5446
const { t } = useTranslation();
@@ -205,9 +197,6 @@ export const TasksPage: React.FC = () => {
205197
filterToolbarProps.setFilterValues({});
206198
};
207199

208-
const { cancelTask, togglePreemption, canCancel, canTogglePreemption } =
209-
useTaskActions();
210-
211200
const toCells = ({
212201
id,
213202
application,
@@ -335,31 +324,7 @@ export const TasksPage: React.FC = () => {
335324
isActionCell
336325
id={`row-actions-${task.id}`}
337326
>
338-
<ActionsColumn
339-
items={[
340-
{
341-
title: t("actions.cancel"),
342-
isDisabled: !canCancel(task.state),
343-
onClick: () => cancelTask(task.id),
344-
},
345-
{
346-
title: task.policy?.preemptEnabled
347-
? t("actions.disablePreemption")
348-
: t("actions.enablePreemption"),
349-
isDisabled: !canTogglePreemption(task.state),
350-
onClick: () => togglePreemption(task),
351-
},
352-
{
353-
title: t("actions.taskDetails"),
354-
onClick: () =>
355-
history.push(
356-
formatPath(Paths.taskDetails, {
357-
taskId: task.id,
358-
})
359-
),
360-
},
361-
]}
362-
/>
327+
<TaskActionColumn task={task} />
363328
</Td>
364329
</TableRowContentWithControls>
365330
</Tr>

client/src/app/pages/tasks/useTaskActions.tsx

+35-2
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,17 @@ import {
77
import { Task, TaskState } from "@app/api/models";
88
import { NotificationsContext } from "@app/components/NotificationsContext";
99
import { useTranslation } from "react-i18next";
10+
import { useHistory } from "react-router-dom";
11+
import { formatPath } from "@app/utils/utils";
12+
import { Paths } from "@app/Paths";
1013

1114
const canCancel = (state: TaskState = "No task") =>
1215
!["Succeeded", "Failed", "Canceled"].includes(state);
1316

1417
const canTogglePreemption = (state: TaskState = "No task") =>
1518
!["Succeeded", "Failed", "Canceled", "Running"].includes(state);
1619

17-
export const useTaskActions = () => {
20+
const useAsyncTaskActions = () => {
1821
const { t } = useTranslation();
1922
const { pushNotification } = React.useContext(NotificationsContext);
2023
const { mutate: cancelTask } = useCancelTaskMutation(
@@ -55,5 +58,35 @@ export const useTaskActions = () => {
5558
},
5659
});
5760

58-
return { cancelTask, togglePreemption, canCancel, canTogglePreemption };
61+
return { cancelTask, togglePreemption };
62+
};
63+
64+
export const useTaskActions = (task: Task) => {
65+
const { cancelTask, togglePreemption } = useAsyncTaskActions();
66+
const { t } = useTranslation();
67+
const history = useHistory();
68+
69+
return [
70+
{
71+
title: t("actions.cancel"),
72+
isDisabled: !canCancel(task.state),
73+
onClick: () => cancelTask(task.id),
74+
},
75+
{
76+
title: task.policy?.preemptEnabled
77+
? t("actions.disablePreemption")
78+
: t("actions.enablePreemption"),
79+
isDisabled: !canTogglePreemption(task.state),
80+
onClick: () => togglePreemption(task),
81+
},
82+
{
83+
title: t("actions.taskDetails"),
84+
onClick: () =>
85+
history.push(
86+
formatPath(Paths.taskDetails, {
87+
taskId: task.id,
88+
})
89+
),
90+
},
91+
];
5992
};

0 commit comments

Comments
 (0)