(ui): Add actions and reducer for deleting completed tasks

This commit is contained in:
Ken Hibino
2021-10-18 07:40:48 -07:00
parent f65986db43
commit 4110955aab
6 changed files with 460 additions and 45 deletions

View File

@@ -4,6 +4,7 @@ import {
batchDeleteArchivedTasks,
batchDeleteRetryTasks,
batchDeleteScheduledTasks,
batchDeleteCompletedTasks,
BatchDeleteTasksResponse,
batchArchiveRetryTasks,
batchArchiveScheduledTasks,
@@ -17,9 +18,11 @@ import {
deleteAllArchivedTasks,
deleteAllRetryTasks,
deleteAllScheduledTasks,
deleteAllCompletedTasks,
deleteArchivedTask,
deleteRetryTask,
deleteScheduledTask,
deleteCompletedTask,
archiveAllRetryTasks,
archiveAllScheduledTasks,
archiveRetryTask,
@@ -218,6 +221,21 @@ export const DELETE_ALL_ARCHIVED_TASKS_SUCCESS =
"DELETE_ALL_ARCHIVED_TASKS_SUCCESS";
export const DELETE_ALL_ARCHIVED_TASKS_ERROR =
"DELETE_ALL_ARCHIVED_TASKS_ERROR";
export const DELETE_COMPLETED_TASK_BEGIN = "DELETE_COMPLETED_TASK_BEGIN";
export const DELETE_COMPLETED_TASK_SUCCESS = "DELETE_COMPLETED_TASK_SUCCESS";
export const DELETE_COMPLETED_TASK_ERROR = "DELETE_COMPLETED_TASK_ERROR";
export const DELETE_ALL_COMPLETED_TASKS_BEGIN =
"DELETE_ALL_COMPLETED_TASKS_BEGIN";
export const DELETE_ALL_COMPLETED_TASKS_SUCCESS =
"DELETE_ALL_COMPLETED_TASKS_SUCCESS";
export const DELETE_ALL_COMPLETED_TASKS_ERROR =
"DELETE_ALL_COMPLETED_TASKS_ERROR";
export const BATCH_DELETE_COMPLETED_TASKS_BEGIN =
"BATCH_DELETE_COMPLETED_TASKS_BEGIN";
export const BATCH_DELETE_COMPLETED_TASKS_SUCCESS =
"BATCH_DELETE_COMPLETED_TASKS_SUCCESS";
export const BATCH_DELETE_COMPLETED_TASKS_ERROR =
"BATCH_DELETE_COMPLETED_TASKS_ERROR";
interface GetTaskInfoBeginAction {
type: typeof GET_TASK_INFO_BEGIN;
@@ -933,6 +951,61 @@ interface DeleteAllArchivedTasksErrorAction {
error: string;
}
interface DeleteCompletedTaskBeginAction {
type: typeof DELETE_COMPLETED_TASK_BEGIN;
queue: string;
taskId: string;
}
interface DeleteCompletedTaskSuccessAction {
type: typeof DELETE_COMPLETED_TASK_SUCCESS;
queue: string;
taskId: string;
}
interface DeleteCompletedTaskErrorAction {
type: typeof DELETE_COMPLETED_TASK_ERROR;
queue: string;
taskId: string;
error: string;
}
interface BatchDeleteCompletedTasksBeginAction {
type: typeof BATCH_DELETE_COMPLETED_TASKS_BEGIN;
queue: string;
taskIds: string[];
}
interface BatchDeleteCompletedTasksSuccessAction {
type: typeof BATCH_DELETE_COMPLETED_TASKS_SUCCESS;
queue: string;
payload: BatchDeleteTasksResponse;
}
interface BatchDeleteCompletedTasksErrorAction {
type: typeof BATCH_DELETE_COMPLETED_TASKS_ERROR;
queue: string;
taskIds: string[];
error: string;
}
interface DeleteAllCompletedTasksBeginAction {
type: typeof DELETE_ALL_COMPLETED_TASKS_BEGIN;
queue: string;
}
interface DeleteAllCompletedTasksSuccessAction {
type: typeof DELETE_ALL_COMPLETED_TASKS_SUCCESS;
queue: string;
deleted: number;
}
interface DeleteAllCompletedTasksErrorAction {
type: typeof DELETE_ALL_COMPLETED_TASKS_ERROR;
queue: string;
error: string;
}
// Union of all tasks related action types.
export type TasksActionTypes =
| GetTaskInfoBeginAction
@@ -1054,7 +1127,16 @@ export type TasksActionTypes =
| RunAllArchivedTasksErrorAction
| DeleteAllArchivedTasksBeginAction
| DeleteAllArchivedTasksSuccessAction
| DeleteAllArchivedTasksErrorAction;
| DeleteAllArchivedTasksErrorAction
| DeleteCompletedTaskBeginAction
| DeleteCompletedTaskSuccessAction
| DeleteCompletedTaskErrorAction
| BatchDeleteCompletedTasksBeginAction
| BatchDeleteCompletedTasksSuccessAction
| BatchDeleteCompletedTasksErrorAction
| DeleteAllCompletedTasksBeginAction
| DeleteAllCompletedTasksSuccessAction
| DeleteAllCompletedTasksErrorAction;
export function getTaskInfoAsync(qname: string, id: string) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
@@ -1064,15 +1146,15 @@ export function getTaskInfoAsync(qname: string, id: string) {
dispatch({
type: GET_TASK_INFO_SUCCESS,
payload: response,
})
});
} catch (error) {
console.error("getTaskInfoAsync: ", toErrorStringWithHttpStatus(error));
dispatch({
type: GET_TASK_INFO_ERROR,
error: toErrorString(error),
})
});
}
}
};
}
export function listActiveTasksAsync(
@@ -1210,16 +1292,19 @@ export function listArchivedTasksAsync(
};
}
export function listCompletedTasksAsync(qname: string, pageOpts?: PaginationOptions) {
export function listCompletedTasksAsync(
qname: string,
pageOpts?: PaginationOptions
) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
try {
dispatch({ type: LIST_COMPLETED_TASKS_BEGIN, queue: qname })
dispatch({ type: LIST_COMPLETED_TASKS_BEGIN, queue: qname });
const response = await listCompletedTasks(qname, pageOpts);
dispatch({
type: LIST_COMPLETED_TASKS_SUCCESS,
queue: qname,
payload: response,
})
});
} catch (error) {
console.error(
"listCompletedTasksAsync: ",
@@ -1228,10 +1313,10 @@ export function listCompletedTasksAsync(qname: string, pageOpts?: PaginationOpti
dispatch({
type: LIST_COMPLETED_TASKS_ERROR,
queue: qname,
error: toErrorString(error)
})
error: toErrorString(error),
});
}
}
};
}
export function cancelActiveTaskAsync(queue: string, taskId: string) {
@@ -1444,10 +1529,7 @@ export function deletePendingTaskAsync(queue: string, taskId: string) {
};
}
export function batchDeletePendingTasksAsync(
queue: string,
taskIds: string[]
) {
export function batchDeletePendingTasksAsync(queue: string, taskIds: string[]) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: BATCH_DELETE_PENDING_TASKS_BEGIN, queue, taskIds });
try {
@@ -1987,3 +2069,76 @@ export function runAllArchivedTasksAsync(queue: string) {
}
};
}
export function deleteCompletedTaskAsync(queue: string, taskId: string) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: DELETE_COMPLETED_TASK_BEGIN, queue, taskId });
try {
await deleteCompletedTask(queue, taskId);
dispatch({ type: DELETE_COMPLETED_TASK_SUCCESS, queue, taskId });
} catch (error) {
console.error(
"deleteCompletedTaskAsync: ",
toErrorStringWithHttpStatus(error)
);
dispatch({
type: DELETE_COMPLETED_TASK_ERROR,
error: toErrorString(error),
queue,
taskId,
});
}
};
}
export function batchDeleteCompletedTasksAsync(
queue: string,
taskIds: string[]
) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: BATCH_DELETE_COMPLETED_TASKS_BEGIN, queue, taskIds });
try {
const response = await batchDeleteCompletedTasks(queue, taskIds);
dispatch({
type: BATCH_DELETE_COMPLETED_TASKS_SUCCESS,
queue: queue,
payload: response,
});
} catch (error) {
console.error(
"batchDeleteCompletedTasksAsync: ",
toErrorStringWithHttpStatus(error)
);
dispatch({
type: BATCH_DELETE_COMPLETED_TASKS_ERROR,
error: toErrorString(error),
queue,
taskIds,
});
}
};
}
export function deleteAllCompletedTasksAsync(queue: string) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: DELETE_ALL_COMPLETED_TASKS_BEGIN, queue });
try {
const response = await deleteAllCompletedTasks(queue);
dispatch({
type: DELETE_ALL_COMPLETED_TASKS_SUCCESS,
deleted: response.deleted,
queue,
});
} catch (error) {
console.error(
"deleteAllCompletedTasksAsync: ",
toErrorStringWithHttpStatus(error)
);
dispatch({
type: DELETE_ALL_COMPLETED_TASKS_ERROR,
error: toErrorString(error),
queue,
});
}
};
}

View File

@@ -5,7 +5,9 @@ import queryString from "query-string";
// the static file server.
// In developement, we assume that the API server is listening on port 8080.
const BASE_URL =
process.env.NODE_ENV === "production" ? `${window.ROOT_PATH}/api` : `http://localhost:8080${window.ROOT_PATH}/api`;
process.env.NODE_ENV === "production"
? `${window.ROOT_PATH}/api`
: `http://localhost:8080${window.ROOT_PATH}/api`;
export interface ListQueuesResponse {
queues: Queue[];
@@ -244,7 +246,7 @@ export interface Queue {
scheduled: number;
retry: number;
archived: number;
completed: number,
completed: number;
processed: number;
failed: number;
timestamp: string;
@@ -333,7 +335,7 @@ export interface CompletedTask extends BaseTask {
retried: number;
completed_at: string;
result: string;
ttl_seconds: number
ttl_seconds: number;
}
export interface ServerInfo {
@@ -415,7 +417,10 @@ export async function listQueueStats(): Promise<ListQueueStatsResponse> {
return resp.data;
}
export async function getTaskInfo(qname: string, id: string): Promise<TaskInfo> {
export async function getTaskInfo(
qname: string,
id: string
): Promise<TaskInfo> {
const url = `${BASE_URL}/queues/${qname}/tasks/${id}`;
const resp = await axios({
method: "get",
@@ -530,16 +535,19 @@ export async function listArchivedTasks(
return resp.data;
}
export async function listCompletedTasks(qname: string, pageOpts?: PaginationOptions): Promise<ListCompletedTasksResponse> {
let url = `${BASE_URL}/queues/${qname}/completed_tasks`
export async function listCompletedTasks(
qname: string,
pageOpts?: PaginationOptions
): Promise<ListCompletedTasksResponse> {
let url = `${BASE_URL}/queues/${qname}/completed_tasks`;
if (pageOpts) {
url += `?${queryString.stringify(pageOpts)}`
url += `?${queryString.stringify(pageOpts)}`;
}
const resp = await axios({
method: "get",
url,
})
return resp.data
});
return resp.data;
}
export async function archivePendingTask(
@@ -864,6 +872,40 @@ export async function runAllArchivedTasks(qname: string): Promise<void> {
});
}
export async function deleteCompletedTask(
qname: string,
taskId: string
): Promise<void> {
await axios({
method: "delete",
url: `${BASE_URL}/queues/${qname}/completed_tasks/${taskId}`,
});
}
export async function batchDeleteCompletedTasks(
qname: string,
taskIds: string[]
): Promise<BatchDeleteTasksResponse> {
const resp = await axios({
method: "post",
url: `${BASE_URL}/queues/${qname}/completed_tasks:batch_delete`,
data: {
task_ids: taskIds,
},
});
return resp.data;
}
export async function deleteAllCompletedTasks(
qname: string
): Promise<DeleteAllTasksResponse> {
const resp = await axios({
method: "delete",
url: `${BASE_URL}/queues/${qname}/completed_tasks:delete_all`,
});
return resp.data;
}
export async function listServers(): Promise<ListServersResponse> {
const resp = await axios({
method: "get",

View File

@@ -20,7 +20,12 @@ import Alert from "@material-ui/lab/Alert";
import AlertTitle from "@material-ui/lab/AlertTitle";
import SyntaxHighlighter from "./SyntaxHighlighter";
import { AppState } from "../store";
import { listCompletedTasksAsync } from "../actions/tasksActions";
import {
listCompletedTasksAsync,
deleteAllCompletedTasksAsync,
deleteCompletedTaskAsync,
batchDeleteCompletedTasksAsync,
} from "../actions/tasksActions";
import TablePaginationActions, {
rowsPerPageOptions,
} from "./TablePaginationActions";
@@ -67,6 +72,9 @@ function mapStateToProps(state: AppState) {
const mapDispatchToProps = {
listCompletedTasksAsync,
deleteCompletedTaskAsync,
deleteAllCompletedTasksAsync,
batchDeleteCompletedTasksAsync,
taskRowsPerPageChange,
};
@@ -109,6 +117,16 @@ function CompletedTasksTable(props: Props & ReduxProps) {
}
};
const handleDeleteAllClick = () => {
props.deleteAllCompletedTasksAsync(queue);
};
const handleBatchDeleteClick = () => {
props
.batchDeleteCompletedTasksAsync(queue, selectedIds)
.then(() => setSelectedIds([]));
};
const fetchData = useCallback(() => {
const pageOpts = { page: page + 1, size: pageSize };
listCompletedTasksAsync(queue, pageOpts);
@@ -153,18 +171,14 @@ function CompletedTasksTable(props: Props & ReduxProps) {
{
tooltip: "Delete",
icon: <DeleteIcon />,
onClick: () => {
/* TODO */
},
onClick: handleBatchDeleteClick,
disabled: props.batchActionPending,
},
]}
menuItemActions={[
{
label: "Delete All",
onClick: () => {
/* TODO */
},
onClick: handleDeleteAllClick,
disabled: props.allActionPending,
},
]}
@@ -218,7 +232,7 @@ function CompletedTasksTable(props: Props & ReduxProps) {
}
}}
onDeleteClick={() => {
// props.deleteCompletedTaskAsync(queue, task.id);
props.deleteCompletedTaskAsync(queue, task.id);
}}
allActionPending={props.allActionPending}
onActionCellEnter={() => setActiveTaskId(task.id)}

View File

@@ -50,6 +50,9 @@ import {
BATCH_DELETE_PENDING_TASKS_SUCCESS,
ARCHIVE_ALL_PENDING_TASKS_SUCCESS,
DELETE_ALL_PENDING_TASKS_SUCCESS,
DELETE_COMPLETED_TASK_SUCCESS,
DELETE_ALL_COMPLETED_TASKS_SUCCESS,
BATCH_DELETE_COMPLETED_TASKS_SUCCESS,
} from "../actions/tasksActions";
import { Queue } from "../api";
@@ -550,8 +553,7 @@ function queuesReducer(
queueInfo.currentStats.pending +
action.payload.archived_ids.length,
retry:
queueInfo.currentStats.retry -
action.payload.archived_ids.length,
queueInfo.currentStats.retry - action.payload.archived_ids.length,
},
};
});
@@ -647,6 +649,23 @@ function queuesReducer(
return { ...state, data: newData };
}
case DELETE_COMPLETED_TASK_SUCCESS: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {
return queueInfo;
}
return {
...queueInfo,
currentStats: {
...queueInfo.currentStats,
size: queueInfo.currentStats.size - 1,
completed: queueInfo.currentStats.completed - 1,
},
};
});
return { ...state, data: newData };
}
case BATCH_RUN_ARCHIVED_TASKS_SUCCESS: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {
@@ -688,6 +707,26 @@ function queuesReducer(
return { ...state, data: newData };
}
case BATCH_DELETE_COMPLETED_TASKS_SUCCESS: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {
return queueInfo;
}
return {
...queueInfo,
currentStats: {
...queueInfo.currentStats,
size:
queueInfo.currentStats.size - action.payload.deleted_ids.length,
completed:
queueInfo.currentStats.completed -
action.payload.deleted_ids.length,
},
};
});
return { ...state, data: newData };
}
case RUN_ALL_ARCHIVED_TASKS_SUCCESS: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {
@@ -723,6 +762,23 @@ function queuesReducer(
return { ...state, data: newData };
}
case DELETE_ALL_COMPLETED_TASKS_SUCCESS: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {
return queueInfo;
}
return {
...queueInfo,
currentStats: {
...queueInfo.currentStats,
size: queueInfo.currentStats.size - action.deleted,
completed: 0,
},
};
});
return { ...state, data: newData };
}
default:
return state;
}

View File

@@ -36,6 +36,9 @@ import {
BATCH_DELETE_PENDING_TASKS_SUCCESS,
ARCHIVE_ALL_PENDING_TASKS_SUCCESS,
DELETE_ALL_PENDING_TASKS_SUCCESS,
DELETE_COMPLETED_TASK_SUCCESS,
DELETE_ALL_COMPLETED_TASKS_SUCCESS,
BATCH_DELETE_COMPLETED_TASKS_SUCCESS,
} from "../actions/tasksActions";
interface SnackbarState {
@@ -285,6 +288,25 @@ function snackbarReducer(
message: "All archived tasks deleted",
};
case DELETE_COMPLETED_TASK_SUCCESS:
return {
isOpen: true,
message: `Completed task deleted`,
};
case DELETE_ALL_COMPLETED_TASKS_SUCCESS:
return {
isOpen: true,
message: "All completed tasks deleted",
};
case BATCH_DELETE_COMPLETED_TASKS_SUCCESS:
const n = action.payload.deleted_ids.length;
return {
isOpen: true,
message: `${n} completed ${n === 1 ? "task" : "tasks"} deleted`,
};
default:
return state;
}

View File

@@ -120,6 +120,15 @@ import {
GET_TASK_INFO_BEGIN,
GET_TASK_INFO_ERROR,
GET_TASK_INFO_SUCCESS,
DELETE_COMPLETED_TASK_BEGIN,
DELETE_COMPLETED_TASK_ERROR,
DELETE_COMPLETED_TASK_SUCCESS,
DELETE_ALL_COMPLETED_TASKS_BEGIN,
DELETE_ALL_COMPLETED_TASKS_ERROR,
DELETE_ALL_COMPLETED_TASKS_SUCCESS,
BATCH_DELETE_COMPLETED_TASKS_BEGIN,
BATCH_DELETE_COMPLETED_TASKS_ERROR,
BATCH_DELETE_COMPLETED_TASKS_SUCCESS,
} from "../actions/tasksActions";
import {
ActiveTask,
@@ -213,12 +222,12 @@ interface TasksState {
allActionPending: boolean;
error: string;
data: CompletedTaskExtended[];
}
};
taskInfo: {
loading: boolean;
error: string;
data?: TaskInfo;
},
};
}
const initialState: TasksState = {
@@ -267,7 +276,7 @@ const initialState: TasksState = {
taskInfo: {
loading: false,
error: "",
}
},
};
function tasksReducer(
@@ -282,16 +291,16 @@ function tasksReducer(
...state.taskInfo,
loading: true,
},
}
};
case GET_TASK_INFO_ERROR:
return {
...state,
taskInfo: {
loading: false,
error: action.error,
},
};
return {
...state,
taskInfo: {
loading: false,
error: action.error,
},
};
case GET_TASK_INFO_SUCCESS:
return {
@@ -508,6 +517,123 @@ function tasksReducer(
},
};
case DELETE_COMPLETED_TASK_BEGIN:
return {
...state,
completedTasks: {
...state.completedTasks,
data: state.completedTasks.data.map((task) => {
if (task.id !== action.taskId) {
return task;
}
return { ...task, requestPending: true };
}),
},
};
case DELETE_COMPLETED_TASK_SUCCESS:
return {
...state,
completedTasks: {
...state.completedTasks,
data: state.completedTasks.data.filter(
(task) => task.id !== action.taskId
),
},
};
case DELETE_COMPLETED_TASK_ERROR:
return {
...state,
completedTasks: {
...state.completedTasks,
data: state.completedTasks.data.map((task) => {
if (task.id !== action.taskId) {
return task;
}
return { ...task, requestPending: false };
}),
},
};
case DELETE_ALL_COMPLETED_TASKS_BEGIN:
return {
...state,
completedTasks: {
...state.completedTasks,
allActionPending: true,
},
};
case DELETE_ALL_COMPLETED_TASKS_SUCCESS:
return {
...state,
completedTasks: {
...state.completedTasks,
allActionPending: false,
data: [],
},
};
case DELETE_ALL_COMPLETED_TASKS_ERROR:
return {
...state,
completedTasks: {
...state.completedTasks,
allActionPending: false,
},
};
case BATCH_DELETE_COMPLETED_TASKS_BEGIN:
return {
...state,
completedTasks: {
...state.completedTasks,
batchActionPending: true,
data: state.completedTasks.data.map((task) => {
if (!action.taskIds.includes(task.id)) {
return task;
}
return {
...task,
requestPending: true,
};
}),
},
};
case BATCH_DELETE_COMPLETED_TASKS_SUCCESS: {
const newData = state.completedTasks.data.filter(
(task) => !action.payload.deleted_ids.includes(task.id)
);
return {
...state,
completedTasks: {
...state.completedTasks,
batchActionPending: false,
data: newData,
},
};
}
case BATCH_DELETE_COMPLETED_TASKS_ERROR:
return {
...state,
completedTasks: {
...state.completedTasks,
batchActionPending: false,
data: state.completedTasks.data.map((task) => {
if (!action.taskIds.includes(task.id)) {
return task;
}
return {
...task,
requestPending: false,
};
}),
},
};
case CANCEL_ACTIVE_TASK_BEGIN: {
const newData = state.activeTasks.data.map((task) => {
if (task.id !== action.taskId) {