Add delete task button to RetryTasksTable

This commit is contained in:
Ken Hibino 2020-12-08 21:22:23 -08:00
parent 48c2cda3bf
commit 601a7c8add
6 changed files with 150 additions and 9 deletions

View File

@ -125,6 +125,7 @@ func toPendingTasks(in []*asynq.PendingTask) []*PendingTask {
type ScheduledTask struct { type ScheduledTask struct {
*BaseTask *BaseTask
Key string `json:"key"`
NextProcessAt time.Time `json:"next_process_at"` NextProcessAt time.Time `json:"next_process_at"`
} }
@ -137,6 +138,7 @@ func toScheduledTask(t *asynq.ScheduledTask) *ScheduledTask {
} }
return &ScheduledTask{ return &ScheduledTask{
BaseTask: base, BaseTask: base,
Key: t.Key(),
NextProcessAt: t.NextProcessAt, NextProcessAt: t.NextProcessAt,
} }
} }
@ -151,6 +153,7 @@ func toScheduledTasks(in []*asynq.ScheduledTask) []*ScheduledTask {
type RetryTask struct { type RetryTask struct {
*BaseTask *BaseTask
Key string `json:"key"`
NextProcessAt time.Time `json:"next_process_at"` NextProcessAt time.Time `json:"next_process_at"`
MaxRetry int `json:"max_retry"` MaxRetry int `json:"max_retry"`
Retried int `json:"retried"` Retried int `json:"retried"`
@ -166,6 +169,7 @@ func toRetryTask(t *asynq.RetryTask) *RetryTask {
} }
return &RetryTask{ return &RetryTask{
BaseTask: base, BaseTask: base,
Key: t.Key(),
NextProcessAt: t.NextProcessAt, NextProcessAt: t.NextProcessAt,
MaxRetry: t.MaxRetry, MaxRetry: t.MaxRetry,
Retried: t.Retried, Retried: t.Retried,
@ -183,6 +187,7 @@ func toRetryTasks(in []*asynq.RetryTask) []*RetryTask {
type DeadTask struct { type DeadTask struct {
*BaseTask *BaseTask
Key string `json:"key"`
MaxRetry int `json:"max_retry"` MaxRetry int `json:"max_retry"`
Retried int `json:"retried"` Retried int `json:"retried"`
ErrorMsg string `json:"error_message"` ErrorMsg string `json:"error_message"`
@ -198,6 +203,7 @@ func toDeadTask(t *asynq.DeadTask) *DeadTask {
} }
return &DeadTask{ return &DeadTask{
BaseTask: base, BaseTask: base,
Key: t.Key(),
MaxRetry: t.MaxRetry, MaxRetry: t.MaxRetry,
Retried: t.Retried, Retried: t.Retried,
ErrorMsg: t.ErrorMsg, ErrorMsg: t.ErrorMsg,

View File

@ -1,5 +1,6 @@
import { import {
cancelActiveTask, cancelActiveTask,
deleteRetryTask,
listActiveTasks, listActiveTasks,
ListActiveTasksResponse, ListActiveTasksResponse,
listDeadTasks, listDeadTasks,
@ -33,6 +34,9 @@ export const LIST_DEAD_TASKS_ERROR = "LIST_DEAD_TASKS_ERROR";
export const CANCEL_ACTIVE_TASK_BEGIN = "CANCEL_ACTIVE_TASK_BEGIN"; export const CANCEL_ACTIVE_TASK_BEGIN = "CANCEL_ACTIVE_TASK_BEGIN";
export const CANCEL_ACTIVE_TASK_SUCCESS = "CANCEL_ACTIVE_TASK_SUCCESS"; export const CANCEL_ACTIVE_TASK_SUCCESS = "CANCEL_ACTIVE_TASK_SUCCESS";
export const CANCEL_ACTIVE_TASK_ERROR = "CANCEL_ACTIVE_TASK_ERROR"; export const CANCEL_ACTIVE_TASK_ERROR = "CANCEL_ACTIVE_TASK_ERROR";
export const DELETE_RETRY_TASK_BEGIN = "DELETE_RETRY_TASK_BEGIN";
export const DELETE_RETRY_TASK_SUCCESS = "DELETE_RETRY_TASK_SUCCESS";
export const DELETE_RETRY_TASK_ERROR = "DELETE_RETRY_TASK_ERROR";
interface ListActiveTasksBeginAction { interface ListActiveTasksBeginAction {
type: typeof LIST_ACTIVE_TASKS_BEGIN; type: typeof LIST_ACTIVE_TASKS_BEGIN;
@ -138,6 +142,24 @@ interface CancelActiveTaskErrorAction {
error: string; error: string;
} }
interface DeleteRetryTaskBeginAction {
type: typeof DELETE_RETRY_TASK_BEGIN;
queue: string;
taskKey: string;
}
interface DeleteRetryTaskSuccessAction {
type: typeof DELETE_RETRY_TASK_SUCCESS;
queue: string;
taskKey: string;
}
interface DeleteRetryTaskErrorAction {
type: typeof DELETE_RETRY_TASK_ERROR;
queue: string;
taskKey: string;
error: string;
}
// Union of all tasks related action types. // Union of all tasks related action types.
export type TasksActionTypes = export type TasksActionTypes =
| ListActiveTasksBeginAction | ListActiveTasksBeginAction
@ -157,7 +179,10 @@ export type TasksActionTypes =
| ListDeadTasksErrorAction | ListDeadTasksErrorAction
| CancelActiveTaskBeginAction | CancelActiveTaskBeginAction
| CancelActiveTaskSuccessAction | CancelActiveTaskSuccessAction
| CancelActiveTaskErrorAction; | CancelActiveTaskErrorAction
| DeleteRetryTaskBeginAction
| DeleteRetryTaskSuccessAction
| DeleteRetryTaskErrorAction;
export function listActiveTasksAsync( export function listActiveTasksAsync(
qname: string, qname: string,
@ -290,3 +315,21 @@ export function cancelActiveTaskAsync(queue: string, taskId: string) {
} }
}; };
} }
export function deleteRetryTaskAsync(queue: string, taskKey: string) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: DELETE_RETRY_TASK_BEGIN, queue, taskKey });
try {
await deleteRetryTask(queue, taskKey);
dispatch({ type: DELETE_RETRY_TASK_SUCCESS, queue, taskKey });
} catch (error) {
console.error("deleteRetryTaskAsync: ", error);
dispatch({
type: DELETE_RETRY_TASK_ERROR,
error: `Could not delete task: ${taskKey}`,
queue,
taskKey,
});
}
};
}

View File

@ -79,12 +79,14 @@ export interface PendingTask extends BaseTask {
export interface ScheduledTask extends BaseTask { export interface ScheduledTask extends BaseTask {
id: string; id: string;
key: string;
queue: string; queue: string;
next_process_at: string; next_process_at: string;
} }
export interface RetryTask extends BaseTask { export interface RetryTask extends BaseTask {
id: string; id: string;
key: string;
queue: string; queue: string;
next_process_at: string; next_process_at: string;
max_retry: number; max_retry: number;
@ -94,6 +96,7 @@ export interface RetryTask extends BaseTask {
export interface DeadTask extends BaseTask { export interface DeadTask extends BaseTask {
id: string; id: string;
key: string;
queue: string; queue: string;
max_retry: number; max_retry: number;
retried: number; retried: number;
@ -240,6 +243,16 @@ export async function listDeadTasks(
return resp.data; return resp.data;
} }
export async function deleteRetryTask(
qname: string,
taskKey: string
): Promise<void> {
await axios({
method: "delete",
url: `${BASE_URL}/queues/${qname}/retry_tasks/${taskKey}`,
});
}
export async function listSchedulerEntries(): Promise<ListSchedulerEntriesResponse> { export async function listSchedulerEntries(): Promise<ListSchedulerEntriesResponse> {
const resp = await axios({ const resp = await axios({
method: "get", method: "get",

View File

@ -21,15 +21,18 @@ import Alert from "@material-ui/lab/Alert";
import AlertTitle from "@material-ui/lab/AlertTitle"; import AlertTitle from "@material-ui/lab/AlertTitle";
import SyntaxHighlighter from "react-syntax-highlighter"; import SyntaxHighlighter from "react-syntax-highlighter";
import syntaxHighlightStyle from "react-syntax-highlighter/dist/esm/styles/hljs/github"; import syntaxHighlightStyle from "react-syntax-highlighter/dist/esm/styles/hljs/github";
import { listRetryTasksAsync } from "../actions/tasksActions"; import {
listRetryTasksAsync,
deleteRetryTaskAsync,
} from "../actions/tasksActions";
import { AppState } from "../store"; import { AppState } from "../store";
import { RetryTask } from "../api";
import TablePaginationActions, { import TablePaginationActions, {
defaultPageSize, defaultPageSize,
rowsPerPageOptions, rowsPerPageOptions,
} from "./TablePaginationActions"; } from "./TablePaginationActions";
import { durationBefore } from "../timeutil"; import { durationBefore } from "../timeutil";
import { usePolling } from "../hooks"; import { usePolling } from "../hooks";
import { RetryTaskExtended } from "../reducers/tasksReducer";
const useStyles = makeStyles({ const useStyles = makeStyles({
table: { table: {
@ -45,7 +48,7 @@ function mapStateToProps(state: AppState) {
}; };
} }
const mapDispatchToProps = { listRetryTasksAsync }; const mapDispatchToProps = { listRetryTasksAsync, deleteRetryTaskAsync };
const connector = connect(mapStateToProps, mapDispatchToProps); const connector = connect(mapStateToProps, mapDispatchToProps);
@ -120,7 +123,13 @@ function RetryTasksTable(props: Props & ReduxProps) {
</TableHead> </TableHead>
<TableBody> <TableBody>
{props.tasks.map((task) => ( {props.tasks.map((task) => (
<Row key={task.id} task={task} /> <Row
key={task.id}
task={task}
onDeleteClick={() => {
props.deleteRetryTaskAsync(task.queue, task.key);
}}
/>
))} ))}
</TableBody> </TableBody>
<TableFooter> <TableFooter>
@ -154,7 +163,7 @@ const useRowStyles = makeStyles({
}, },
}); });
function Row(props: { task: RetryTask }) { function Row(props: { task: RetryTaskExtended; onDeleteClick: () => void }) {
const { task } = props; const { task } = props;
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const classes = useRowStyles(); const classes = useRowStyles();
@ -179,7 +188,9 @@ function Row(props: { task: RetryTask }) {
<TableCell>{task.retried}</TableCell> <TableCell>{task.retried}</TableCell>
<TableCell>{task.max_retry}</TableCell> <TableCell>{task.max_retry}</TableCell>
<TableCell> <TableCell>
<Button>Cancel</Button> <Button disabled={task.requestPending} onClick={props.onDeleteClick}>
Delete
</Button>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>

View File

@ -14,6 +14,7 @@ import {
DELETE_QUEUE_SUCCESS, DELETE_QUEUE_SUCCESS,
} from "../actions/queuesActions"; } from "../actions/queuesActions";
import { import {
DELETE_RETRY_TASK_SUCCESS,
LIST_ACTIVE_TASKS_SUCCESS, LIST_ACTIVE_TASKS_SUCCESS,
LIST_DEAD_TASKS_SUCCESS, LIST_DEAD_TASKS_SUCCESS,
LIST_PENDING_TASKS_SUCCESS, LIST_PENDING_TASKS_SUCCESS,
@ -147,6 +148,22 @@ function queuesReducer(
return { ...state, data: newData }; return { ...state, data: newData };
} }
case DELETE_RETRY_TASK_SUCCESS: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {
return queueInfo;
}
return {
...queueInfo,
currentStats: {
...queueInfo.currentStats,
retry: queueInfo.currentStats.retry - 1,
},
};
});
return { ...state, data: newData };
}
default: default:
return state; return state;
} }

View File

@ -18,6 +18,9 @@ import {
CANCEL_ACTIVE_TASK_BEGIN, CANCEL_ACTIVE_TASK_BEGIN,
CANCEL_ACTIVE_TASK_SUCCESS, CANCEL_ACTIVE_TASK_SUCCESS,
CANCEL_ACTIVE_TASK_ERROR, CANCEL_ACTIVE_TASK_ERROR,
DELETE_RETRY_TASK_BEGIN,
DELETE_RETRY_TASK_SUCCESS,
DELETE_RETRY_TASK_ERROR,
} from "../actions/tasksActions"; } from "../actions/tasksActions";
import { import {
ActiveTask, ActiveTask,
@ -37,6 +40,12 @@ export interface ActiveTaskExtended extends ActiveTask {
canceling: boolean; canceling: boolean;
} }
export interface RetryTaskExtended extends RetryTask {
// Indicates that a request has been sent for this
// task and awaiting for a response.
requestPending: boolean;
}
interface TasksState { interface TasksState {
activeTasks: { activeTasks: {
loading: boolean; loading: boolean;
@ -56,7 +65,7 @@ interface TasksState {
retryTasks: { retryTasks: {
loading: boolean; loading: boolean;
error: string; error: string;
data: RetryTask[]; data: RetryTaskExtended[];
}; };
deadTasks: { deadTasks: {
loading: boolean; loading: boolean;
@ -208,7 +217,10 @@ function tasksReducer(
retryTasks: { retryTasks: {
loading: false, loading: false,
error: "", error: "",
data: action.payload.tasks, data: action.payload.tasks.map((task) => ({
...task,
requestPending: false,
})),
}, },
}; };
@ -299,6 +311,45 @@ function tasksReducer(
}, },
}; };
case DELETE_RETRY_TASK_BEGIN:
return {
...state,
retryTasks: {
...state.retryTasks,
data: state.retryTasks.data.map((task) => {
if (task.key !== action.taskKey) {
return task;
}
return { ...task, requestPending: true };
}),
},
};
case DELETE_RETRY_TASK_SUCCESS:
return {
...state,
retryTasks: {
...state.retryTasks,
data: state.retryTasks.data.filter(
(task) => task.key !== action.taskKey
),
},
};
case DELETE_RETRY_TASK_ERROR:
return {
...state,
retryTasks: {
...state.retryTasks,
data: state.retryTasks.data.map((task) => {
if (task.key !== action.taskKey) {
return task;
}
return { ...task, requestPending: false };
}),
},
};
default: default:
return state; return state;
} }