Add batch delete button to DeadTasksTable

This commit is contained in:
Ken Hibino
2020-12-13 07:51:40 -08:00
parent 62780e201e
commit ef60304015
7 changed files with 175 additions and 19 deletions

View File

@@ -1,4 +1,6 @@
import {
batchDeleteDeadTasks,
BatchDeleteTasksResponse,
cancelActiveTask,
deleteDeadTask,
deleteRetryTask,
@@ -45,6 +47,10 @@ export const DELETE_RETRY_TASK_ERROR = "DELETE_RETRY_TASK_ERROR";
export const DELETE_DEAD_TASK_BEGIN = "DELETE_DEAD_TASK_BEGIN";
export const DELETE_DEAD_TASK_SUCCESS = "DELETE_DEAD_TASK_SUCCESS";
export const DELETE_DEAD_TASK_ERROR = "DELETE_DEAD_TASK_ERROR";
export const BATCH_DELETE_DEAD_TASKS_BEGIN = "BATCH_DELETE_DEAD_TASKS_BEGIN";
export const BATCH_DELETE_DEAD_TASKS_SUCCESS =
"BATCH_DELETE_DEAD_TASKS_SUCCESS";
export const BATCH_DELETE_DEAD_TASKS_ERROR = "BATCH_DELETE_DEAD_TASKS_ERROR";
interface ListActiveTasksBeginAction {
type: typeof LIST_ACTIVE_TASKS_BEGIN;
@@ -207,6 +213,25 @@ interface DeleteDeadTaskErrorAction {
error: string;
}
interface BatchDeleteDeadTasksBeginAction {
type: typeof BATCH_DELETE_DEAD_TASKS_BEGIN;
queue: string;
taskKeys: string[];
}
interface BatchDeleteDeadTasksSuccessAction {
type: typeof BATCH_DELETE_DEAD_TASKS_SUCCESS;
queue: string;
payload: BatchDeleteTasksResponse;
}
interface BatchDeleteDeadTasksErrorAction {
type: typeof BATCH_DELETE_DEAD_TASKS_ERROR;
queue: string;
taskKeys: string[];
error: string;
}
// Union of all tasks related action types.
export type TasksActionTypes =
| ListActiveTasksBeginAction
@@ -235,7 +260,10 @@ export type TasksActionTypes =
| DeleteRetryTaskErrorAction
| DeleteDeadTaskBeginAction
| DeleteDeadTaskSuccessAction
| DeleteDeadTaskErrorAction;
| DeleteDeadTaskErrorAction
| BatchDeleteDeadTasksBeginAction
| BatchDeleteDeadTasksSuccessAction
| BatchDeleteDeadTasksErrorAction;
export function listActiveTasksAsync(
qname: string,
@@ -422,3 +450,25 @@ export function deleteDeadTaskAsync(queue: string, taskKey: string) {
}
};
}
export function batchDeleteDeadTasksAsync(queue: string, taskKeys: string[]) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: BATCH_DELETE_DEAD_TASKS_BEGIN, queue, taskKeys });
try {
const response = await batchDeleteDeadTasks(queue, taskKeys);
dispatch({
type: BATCH_DELETE_DEAD_TASKS_SUCCESS,
queue: queue,
payload: response,
});
} catch (error) {
console.error("batchDeleteDeadTasksAsync: ", error);
dispatch({
type: BATCH_DELETE_DEAD_TASKS_ERROR,
error: `Could not batch delete tasks: ${taskKeys}`,
queue,
taskKeys,
});
}
};
}

View File

@@ -41,6 +41,11 @@ export interface ListSchedulerEntriesResponse {
entries: SchedulerEntry[];
}
export interface BatchDeleteTasksResponse {
deleted_keys: string[];
failed_keys: string[];
}
export interface Queue {
queue: string;
paused: boolean;
@@ -273,6 +278,21 @@ export async function deleteDeadTask(
});
}
export async function batchDeleteDeadTasks(
qname: string,
taskKeys: string[]
): Promise<BatchDeleteTasksResponse> {
const resp = await axios({
method: "post",
url: `${BASE_URL}/queues/${qname}/dead_tasks:batch_delete`,
data: {
task_keys: taskKeys,
},
});
console.log("debug: response:", resp);
return resp.data;
}
export async function listSchedulerEntries(): Promise<ListSchedulerEntriesResponse> {
const resp = await axios({
method: "get",

View File

@@ -28,6 +28,7 @@ import { AppState } from "../store";
import {
listDeadTasksAsync,
deleteDeadTaskAsync,
batchDeleteDeadTasksAsync,
} from "../actions/tasksActions";
import TablePaginationActions, {
defaultPageSize,
@@ -61,11 +62,16 @@ function mapStateToProps(state: AppState) {
return {
loading: state.tasks.deadTasks.loading,
tasks: state.tasks.deadTasks.data,
batchActionPending: state.tasks.deadTasks.batchActionPending,
pollInterval: state.settings.pollInterval,
};
}
const mapDispatchToProps = { listDeadTasksAsync, deleteDeadTaskAsync };
const mapDispatchToProps = {
listDeadTasksAsync,
deleteDeadTaskAsync,
batchDeleteDeadTasksAsync,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
@@ -81,7 +87,7 @@ function DeadTasksTable(props: Props & ReduxProps) {
const classes = useStyles();
const [page, setPage] = useState(0);
const [pageSize, setPageSize] = useState(defaultPageSize);
const [selected, setSelected] = useState<string[]>([]);
const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
const handleChangePage = (
event: React.MouseEvent<HTMLButtonElement> | null,
@@ -99,10 +105,10 @@ function DeadTasksTable(props: Props & ReduxProps) {
const handleSelectAllClick = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.checked) {
const newSelected = props.tasks.map((t) => t.id);
setSelected(newSelected);
const newSelected = props.tasks.map((t) => t.key);
setSelectedKeys(newSelected);
} else {
setSelected([]);
setSelectedKeys([]);
}
};
@@ -132,7 +138,7 @@ function DeadTasksTable(props: Props & ReduxProps) {
];
const rowCount = props.tasks.length;
const numSelected = selected.length;
const numSelected = selectedKeys.length;
return (
<div>
<div className={classes.actionsContainer}>
@@ -147,7 +153,16 @@ function DeadTasksTable(props: Props & ReduxProps) {
>
<Button>Run</Button>
<Button>Kill</Button>
<Button>Delete</Button>
<Button
disabled={props.batchActionPending}
onClick={() =>
props
.batchDeleteDeadTasksAsync(queue, selectedKeys)
.then(() => setSelectedKeys([]))
}
>
Delete
</Button>
</ButtonGroup>
)}
</div>
@@ -176,14 +191,16 @@ function DeadTasksTable(props: Props & ReduxProps) {
<TableBody>
{props.tasks.map((task) => (
<Row
key={task.id}
key={task.key}
task={task}
isSelected={selected.includes(task.id)}
isSelected={selectedKeys.includes(task.key)}
onSelectChange={(checked: boolean) => {
if (checked) {
setSelected(selected.concat(task.id));
setSelectedKeys(selectedKeys.concat(task.key));
} else {
setSelected(selected.filter((id) => id !== task.id));
setSelectedKeys(
selectedKeys.filter((key) => key !== task.key)
);
}
}}
onDeleteClick={() => {

View File

@@ -14,6 +14,7 @@ import {
DELETE_QUEUE_SUCCESS,
} from "../actions/queuesActions";
import {
BATCH_DELETE_DEAD_TASKS_SUCCESS,
DELETE_DEAD_TASK_SUCCESS,
DELETE_RETRY_TASK_SUCCESS,
DELETE_SCHEDULED_TASK_SUCCESS,
@@ -198,6 +199,23 @@ function queuesReducer(
return { ...state, data: newData };
}
case BATCH_DELETE_DEAD_TASKS_SUCCESS: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {
return queueInfo;
}
return {
...queueInfo,
currentStats: {
...queueInfo.currentStats,
dead:
queueInfo.currentStats.dead - action.payload.deleted_keys.length,
},
};
});
return { ...state, data: newData };
}
default:
return state;
}

View File

@@ -3,6 +3,7 @@ import {
SnackbarActionTypes,
} from "../actions/snackbarActions";
import {
BATCH_DELETE_DEAD_TASKS_SUCCESS,
DELETE_DEAD_TASK_SUCCESS,
DELETE_RETRY_TASK_SUCCESS,
DELETE_SCHEDULED_TASK_SUCCESS,
@@ -53,6 +54,14 @@ function snackbarReducer(
message: `Dead task ${action.taskKey} deleted`,
};
case BATCH_DELETE_DEAD_TASKS_SUCCESS: {
const n = action.payload.deleted_keys.length;
return {
isOpen: true,
message: `${n} Dead ${n === 1 ? "task" : "tasks"} deleted`,
};
}
default:
return state;
}

View File

@@ -27,6 +27,9 @@ import {
DELETE_DEAD_TASK_BEGIN,
DELETE_DEAD_TASK_SUCCESS,
DELETE_DEAD_TASK_ERROR,
BATCH_DELETE_DEAD_TASKS_BEGIN,
BATCH_DELETE_DEAD_TASKS_SUCCESS,
BATCH_DELETE_DEAD_TASKS_ERROR,
} from "../actions/tasksActions";
import {
ActiveTask,
@@ -87,6 +90,7 @@ interface TasksState {
};
deadTasks: {
loading: boolean;
batchActionPending: boolean;
error: string;
data: DeadTaskExtended[];
};
@@ -115,6 +119,7 @@ const initialState: TasksState = {
},
deadTasks: {
loading: false,
batchActionPending: false,
error: "",
data: [],
},
@@ -269,6 +274,7 @@ function tasksReducer(
return {
...state,
deadTasks: {
...state.deadTasks,
loading: false,
error: "",
data: action.payload.tasks.map((task) => ({
@@ -452,6 +458,38 @@ function tasksReducer(
},
};
case BATCH_DELETE_DEAD_TASKS_BEGIN:
return {
...state,
deadTasks: {
...state.deadTasks,
batchActionPending: true,
},
};
case BATCH_DELETE_DEAD_TASKS_SUCCESS: {
const newData = state.deadTasks.data.filter(
(task) => !action.payload.deleted_keys.includes(task.key)
);
return {
...state,
deadTasks: {
...state.deadTasks,
batchActionPending: false,
data: newData,
},
};
}
case BATCH_DELETE_DEAD_TASKS_ERROR:
return {
...state,
deadTasks: {
...state.deadTasks,
batchActionPending: false,
},
};
default:
return state;
}