Add delete button for scheduled and dead tasks

This commit is contained in:
Ken Hibino 2020-12-09 06:56:44 -08:00
parent 601a7c8add
commit ce978f4516
7 changed files with 303 additions and 18 deletions

View File

@ -1,6 +1,8 @@
import {
cancelActiveTask,
deleteDeadTask,
deleteRetryTask,
deleteScheduledTask,
listActiveTasks,
ListActiveTasksResponse,
listDeadTasks,
@ -34,9 +36,15 @@ 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_SUCCESS = "CANCEL_ACTIVE_TASK_SUCCESS";
export const CANCEL_ACTIVE_TASK_ERROR = "CANCEL_ACTIVE_TASK_ERROR";
export const DELETE_SCHEDULED_TASK_BEGIN = "DELETE_SCHEDULED_TASK_BEGIN";
export const DELETE_SCHEDULED_TASK_SUCCESS = "DELETE_SCHEDULED_TASK_SUCCESS";
export const DELETE_SCHEDULED_TASK_ERROR = "DELETE_SCHEDULED_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";
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";
interface ListActiveTasksBeginAction {
type: typeof LIST_ACTIVE_TASKS_BEGIN;
@ -142,6 +150,25 @@ interface CancelActiveTaskErrorAction {
error: string;
}
interface DeleteScheduledTaskBeginAction {
type: typeof DELETE_SCHEDULED_TASK_BEGIN;
queue: string;
taskKey: string;
}
interface DeleteScheduledTaskSuccessAction {
type: typeof DELETE_SCHEDULED_TASK_SUCCESS;
queue: string;
taskKey: string;
}
interface DeleteScheduledTaskErrorAction {
type: typeof DELETE_SCHEDULED_TASK_ERROR;
queue: string;
taskKey: string;
error: string;
}
interface DeleteRetryTaskBeginAction {
type: typeof DELETE_RETRY_TASK_BEGIN;
queue: string;
@ -160,6 +187,26 @@ interface DeleteRetryTaskErrorAction {
taskKey: string;
error: string;
}
interface DeleteDeadTaskBeginAction {
type: typeof DELETE_DEAD_TASK_BEGIN;
queue: string;
taskKey: string;
}
interface DeleteDeadTaskSuccessAction {
type: typeof DELETE_DEAD_TASK_SUCCESS;
queue: string;
taskKey: string;
}
interface DeleteDeadTaskErrorAction {
type: typeof DELETE_DEAD_TASK_ERROR;
queue: string;
taskKey: string;
error: string;
}
// Union of all tasks related action types.
export type TasksActionTypes =
| ListActiveTasksBeginAction
@ -180,9 +227,15 @@ export type TasksActionTypes =
| CancelActiveTaskBeginAction
| CancelActiveTaskSuccessAction
| CancelActiveTaskErrorAction
| DeleteScheduledTaskBeginAction
| DeleteScheduledTaskSuccessAction
| DeleteScheduledTaskErrorAction
| DeleteRetryTaskBeginAction
| DeleteRetryTaskSuccessAction
| DeleteRetryTaskErrorAction;
| DeleteRetryTaskErrorAction
| DeleteDeadTaskBeginAction
| DeleteDeadTaskSuccessAction
| DeleteDeadTaskErrorAction;
export function listActiveTasksAsync(
qname: string,
@ -316,6 +369,24 @@ export function cancelActiveTaskAsync(queue: string, taskId: string) {
};
}
export function deleteScheduledTaskAsync(queue: string, taskKey: string) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: DELETE_SCHEDULED_TASK_BEGIN, queue, taskKey });
try {
await deleteScheduledTask(queue, taskKey);
dispatch({ type: DELETE_SCHEDULED_TASK_SUCCESS, queue, taskKey });
} catch (error) {
console.error("deleteScheduledTaskAsync: ", error);
dispatch({
type: DELETE_SCHEDULED_TASK_ERROR,
error: `Could not delete task: ${taskKey}`,
queue,
taskKey,
});
}
};
}
export function deleteRetryTaskAsync(queue: string, taskKey: string) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: DELETE_RETRY_TASK_BEGIN, queue, taskKey });
@ -333,3 +404,21 @@ export function deleteRetryTaskAsync(queue: string, taskKey: string) {
}
};
}
export function deleteDeadTaskAsync(queue: string, taskKey: string) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: DELETE_DEAD_TASK_BEGIN, queue, taskKey });
try {
await deleteDeadTask(queue, taskKey);
dispatch({ type: DELETE_DEAD_TASK_SUCCESS, queue, taskKey });
} catch (error) {
console.error("deleteDeadTaskAsync: ", error);
dispatch({
type: DELETE_DEAD_TASK_ERROR,
error: `Could not delete task: ${taskKey}`,
queue,
taskKey,
});
}
};
}

View File

@ -243,6 +243,16 @@ export async function listDeadTasks(
return resp.data;
}
export async function deleteScheduledTask(
qname: string,
taskKey: string
): Promise<void> {
await axios({
method: "delete",
url: `${BASE_URL}/queues/${qname}/scheduled_tasks/${taskKey}`,
});
}
export async function deleteRetryTask(
qname: string,
taskKey: string
@ -253,6 +263,16 @@ export async function deleteRetryTask(
});
}
export async function deleteDeadTask(
qname: string,
taskKey: string
): Promise<void> {
await axios({
method: "delete",
url: `${BASE_URL}/queues/${qname}/dead_tasks/${taskKey}`,
});
}
export async function listSchedulerEntries(): Promise<ListSchedulerEntriesResponse> {
const resp = await axios({
method: "get",

View File

@ -22,14 +22,17 @@ import AlertTitle from "@material-ui/lab/AlertTitle";
import SyntaxHighlighter from "react-syntax-highlighter";
import syntaxHighlightStyle from "react-syntax-highlighter/dist/esm/styles/hljs/github";
import { AppState } from "../store";
import { listDeadTasksAsync } from "../actions/tasksActions";
import { DeadTask } from "../api";
import {
listDeadTasksAsync,
deleteDeadTaskAsync,
} from "../actions/tasksActions";
import TablePaginationActions, {
defaultPageSize,
rowsPerPageOptions,
} from "./TablePaginationActions";
import { timeAgo } from "../timeutil";
import { usePolling } from "../hooks";
import { DeadTaskExtended } from "../reducers/tasksReducer";
const useStyles = makeStyles({
table: {
@ -53,7 +56,7 @@ function mapStateToProps(state: AppState) {
};
}
const mapDispatchToProps = { listDeadTasksAsync };
const mapDispatchToProps = { listDeadTasksAsync, deleteDeadTaskAsync };
const connector = connect(mapStateToProps, mapDispatchToProps);
@ -126,7 +129,13 @@ function DeadTasksTable(props: Props & ReduxProps) {
</TableHead>
<TableBody>
{props.tasks.map((task) => (
<Row key={task.id} task={task} />
<Row
key={task.id}
task={task}
onDeleteClick={() => {
props.deleteDeadTaskAsync(queue, task.key);
}}
/>
))}
</TableBody>
<TableFooter>
@ -152,7 +161,12 @@ function DeadTasksTable(props: Props & ReduxProps) {
);
}
function Row(props: { task: DeadTask }) {
interface RowProps {
task: DeadTaskExtended;
onDeleteClick: () => void;
}
function Row(props: RowProps) {
const { task } = props;
const [open, setOpen] = React.useState(false);
const classes = useRowStyles();
@ -175,7 +189,9 @@ function Row(props: { task: DeadTask }) {
<TableCell>{timeAgo(task.last_failed_at)}</TableCell>
<TableCell>{task.error_message}</TableCell>
<TableCell>
<Button>Cancel</Button>
<Button onClick={props.onDeleteClick} disabled={task.requestPending}>
Delete
</Button>
</TableCell>
</TableRow>
<TableRow>

View File

@ -163,7 +163,12 @@ const useRowStyles = makeStyles({
},
});
function Row(props: { task: RetryTaskExtended; onDeleteClick: () => void }) {
interface RowProps {
task: RetryTaskExtended;
onDeleteClick: () => void;
}
function Row(props: RowProps) {
const { task } = props;
const [open, setOpen] = React.useState(false);
const classes = useRowStyles();

View File

@ -21,15 +21,18 @@ import Alert from "@material-ui/lab/Alert";
import AlertTitle from "@material-ui/lab/AlertTitle";
import SyntaxHighlighter from "react-syntax-highlighter";
import syntaxHighlightStyle from "react-syntax-highlighter/dist/esm/styles/hljs/github";
import { listScheduledTasksAsync } from "../actions/tasksActions";
import {
listScheduledTasksAsync,
deleteScheduledTaskAsync,
} from "../actions/tasksActions";
import { AppState } from "../store";
import { ScheduledTask } from "../api";
import TablePaginationActions, {
defaultPageSize,
rowsPerPageOptions,
} from "./TablePaginationActions";
import { durationBefore } from "../timeutil";
import { usePolling } from "../hooks";
import { ScheduledTaskExtended } from "../reducers/tasksReducer";
const useStyles = makeStyles({
table: {
@ -45,7 +48,10 @@ function mapStateToProps(state: AppState) {
};
}
const mapDispatchToProps = { listScheduledTasksAsync };
const mapDispatchToProps = {
listScheduledTasksAsync,
deleteScheduledTaskAsync,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
@ -117,7 +123,13 @@ function ScheduledTasksTable(props: Props & ReduxProps) {
</TableHead>
<TableBody>
{props.tasks.map((task) => (
<Row key={task.id} task={task} />
<Row
key={task.id}
task={task}
onDeleteClick={() => {
props.deleteScheduledTaskAsync(queue, task.key);
}}
/>
))}
</TableBody>
<TableFooter>
@ -151,7 +163,12 @@ const useRowStyles = makeStyles({
},
});
function Row(props: { task: ScheduledTask }) {
interface RowProps {
task: ScheduledTaskExtended;
onDeleteClick: () => void;
}
function Row(props: RowProps) {
const { task } = props;
const [open, setOpen] = React.useState(false);
const classes = useRowStyles();
@ -173,7 +190,9 @@ function Row(props: { task: ScheduledTask }) {
<TableCell>{task.type}</TableCell>
<TableCell>{durationBefore(task.next_process_at)}</TableCell>
<TableCell>
<Button>Cancel</Button>
<Button onClick={props.onDeleteClick} disabled={task.requestPending}>
Delete
</Button>
</TableCell>
</TableRow>
<TableRow>

View File

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

View File

@ -21,6 +21,12 @@ import {
DELETE_RETRY_TASK_BEGIN,
DELETE_RETRY_TASK_SUCCESS,
DELETE_RETRY_TASK_ERROR,
DELETE_SCHEDULED_TASK_BEGIN,
DELETE_SCHEDULED_TASK_SUCCESS,
DELETE_SCHEDULED_TASK_ERROR,
DELETE_DEAD_TASK_BEGIN,
DELETE_DEAD_TASK_SUCCESS,
DELETE_DEAD_TASK_ERROR,
} from "../actions/tasksActions";
import {
ActiveTask,
@ -40,12 +46,24 @@ export interface ActiveTaskExtended extends ActiveTask {
canceling: boolean;
}
export interface ScheduledTaskExtended extends ScheduledTask {
// Indicates that a request has been sent for this
// task and awaiting for a response.
requestPending: boolean;
}
export interface RetryTaskExtended extends RetryTask {
// Indicates that a request has been sent for this
// task and awaiting for a response.
requestPending: boolean;
}
export interface DeadTaskExtended extends DeadTask {
// Indicates that a request has been sent for this
// task and awaiting for a response.
requestPending: boolean;
}
interface TasksState {
activeTasks: {
loading: boolean;
@ -60,7 +78,7 @@ interface TasksState {
scheduledTasks: {
loading: boolean;
error: string;
data: ScheduledTask[];
data: ScheduledTaskExtended[];
};
retryTasks: {
loading: boolean;
@ -70,7 +88,7 @@ interface TasksState {
deadTasks: {
loading: boolean;
error: string;
data: DeadTask[];
data: DeadTaskExtended[];
};
}
@ -187,7 +205,10 @@ function tasksReducer(
scheduledTasks: {
loading: false,
error: "",
data: action.payload.tasks,
data: action.payload.tasks.map((task) => ({
...task,
requestPending: false,
})),
},
};
@ -250,7 +271,10 @@ function tasksReducer(
deadTasks: {
loading: false,
error: "",
data: action.payload.tasks,
data: action.payload.tasks.map((task) => ({
...task,
requestPending: false,
})),
},
};
@ -311,6 +335,45 @@ function tasksReducer(
},
};
case DELETE_SCHEDULED_TASK_BEGIN:
return {
...state,
scheduledTasks: {
...state.scheduledTasks,
data: state.scheduledTasks.data.map((task) => {
if (task.key !== action.taskKey) {
return task;
}
return { ...task, requestPending: true };
}),
},
};
case DELETE_SCHEDULED_TASK_SUCCESS:
return {
...state,
scheduledTasks: {
...state.scheduledTasks,
data: state.scheduledTasks.data.filter(
(task) => task.key !== action.taskKey
),
},
};
case DELETE_SCHEDULED_TASK_ERROR:
return {
...state,
scheduledTasks: {
...state.scheduledTasks,
data: state.scheduledTasks.data.map((task) => {
if (task.key !== action.taskKey) {
return task;
}
return { ...task, requestPending: false };
}),
},
};
case DELETE_RETRY_TASK_BEGIN:
return {
...state,
@ -350,6 +413,45 @@ function tasksReducer(
},
};
case DELETE_DEAD_TASK_BEGIN:
return {
...state,
deadTasks: {
...state.deadTasks,
data: state.deadTasks.data.map((task) => {
if (task.key !== action.taskKey) {
return task;
}
return { ...task, requestPending: true };
}),
},
};
case DELETE_DEAD_TASK_SUCCESS:
return {
...state,
deadTasks: {
...state.deadTasks,
data: state.deadTasks.data.filter(
(task) => task.key !== action.taskKey
),
},
};
case DELETE_DEAD_TASK_ERROR:
return {
...state,
deadTasks: {
...state.deadTasks,
data: state.deadTasks.data.map((task) => {
if (task.key !== action.taskKey) {
return task;
}
return { ...task, requestPending: false };
}),
},
};
default:
return state;
}