diff --git a/ui/src/actions/tasksActions.ts b/ui/src/actions/tasksActions.ts index 4eb515a..e663280 100644 --- a/ui/src/actions/tasksActions.ts +++ b/ui/src/actions/tasksActions.ts @@ -19,6 +19,7 @@ import { listScheduledTasks, ListScheduledTasksResponse, PaginationOptions, + runAllDeadTasks, runDeadTask, } from "../api"; import { Dispatch } from "redux"; @@ -61,6 +62,9 @@ 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"; +export const RUN_ALL_DEAD_TASKS_BEGIN = "RUN_ALL_DEAD_TASKS_BEGIN"; +export const RUN_ALL_DEAD_TASKS_SUCCESS = "RUN_ALL_DEAD_TASKS_SUCCESS"; +export const RUN_ALL_DEAD_TASKS_ERROR = "RUN_ALL_DEAD_TASKS_ERROR"; export const DELETE_ALL_DEAD_TASKS_BEGIN = "DELETE_ALL_DEAD_TASKS_BEGIN"; export const DELETE_ALL_DEAD_TASKS_SUCCESS = "DELETE_ALL_DEAD_TASKS_SUCCESS"; export const DELETE_ALL_DEAD_TASKS_ERROR = "DELETE_ALL_DEAD_TASKS_ERROR"; @@ -283,6 +287,22 @@ interface BatchRunDeadTasksErrorAction { error: string; } +interface RunAllDeadTasksBeginAction { + type: typeof RUN_ALL_DEAD_TASKS_BEGIN; + queue: string; +} + +interface RunAllDeadTasksSuccessAction { + type: typeof RUN_ALL_DEAD_TASKS_SUCCESS; + queue: string; +} + +interface RunAllDeadTasksErrorAction { + type: typeof RUN_ALL_DEAD_TASKS_ERROR; + queue: string; + error: string; +} + interface DeleteAllDeadTasksBeginAction { type: typeof DELETE_ALL_DEAD_TASKS_BEGIN; queue: string; @@ -337,6 +357,9 @@ export type TasksActionTypes = | BatchRunDeadTasksBeginAction | BatchRunDeadTasksSuccessAction | BatchRunDeadTasksErrorAction + | RunAllDeadTasksBeginAction + | RunAllDeadTasksSuccessAction + | RunAllDeadTasksErrorAction | DeleteAllDeadTasksBeginAction | DeleteAllDeadTasksSuccessAction | DeleteAllDeadTasksErrorAction; @@ -605,3 +628,20 @@ export function deleteAllDeadTasksAsync(queue: string) { } }; } + +export function runAllDeadTasksAsync(queue: string) { + return async (dispatch: Dispatch) => { + dispatch({ type: RUN_ALL_DEAD_TASKS_BEGIN, queue }); + try { + await runAllDeadTasks(queue); + dispatch({ type: RUN_ALL_DEAD_TASKS_SUCCESS, queue }); + } catch (error) { + console.error("runAllDeadTasksAsync: ", error); + dispatch({ + type: RUN_ALL_DEAD_TASKS_ERROR, + error: `Could not run all dead tasks`, + queue, + }); + } + }; +} diff --git a/ui/src/api.ts b/ui/src/api.ts index 2e85350..b4315e0 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -328,6 +328,13 @@ export async function batchRunDeadTasks( return resp.data; } +export async function runAllDeadTasks(qname: string): Promise { + await axios({ + method: "post", + url: `${BASE_URL}/queues/${qname}/dead_tasks:run_all`, + }); +} + export async function listSchedulerEntries(): Promise { const resp = await axios({ method: "get", diff --git a/ui/src/components/DeadTasksTable.tsx b/ui/src/components/DeadTasksTable.tsx index 56a115f..78857d4 100644 --- a/ui/src/components/DeadTasksTable.tsx +++ b/ui/src/components/DeadTasksTable.tsx @@ -34,6 +34,7 @@ import { deleteAllDeadTasksAsync, listDeadTasksAsync, runDeadTaskAsync, + runAllDeadTasksAsync, } from "../actions/tasksActions"; import TablePaginationActions, { defaultPageSize, @@ -68,7 +69,7 @@ function mapStateToProps(state: AppState) { loading: state.tasks.deadTasks.loading, tasks: state.tasks.deadTasks.data, batchActionPending: state.tasks.deadTasks.batchActionPending, - deleteAllRequestPending: state.tasks.deadTasks.deleteAllRequestPending, + allActionPending: state.tasks.deadTasks.allActionPending, pollInterval: state.settings.pollInterval, }; } @@ -76,6 +77,7 @@ function mapStateToProps(state: AppState) { const mapDispatchToProps = { listDeadTasksAsync, runDeadTaskAsync, + runAllDeadTasksAsync, deleteDeadTaskAsync, deleteAllDeadTasksAsync, batchRunDeadTasksAsync, @@ -128,6 +130,11 @@ function DeadTasksTable(props: Props & ReduxProps) { const handleMenuClose = () => setMenuAnchor(null); + const handleRunAllClick = () => { + props.runAllDeadTasksAsync(queue); + setMenuAnchor(null); + }; + const handleDeleteAllClick = () => { props.deleteAllDeadTasksAsync(queue); setMenuAnchor(null); @@ -177,10 +184,15 @@ function DeadTasksTable(props: Props & ReduxProps) { open={Boolean(menuAnchor)} onClose={handleMenuClose} > - Run All + + Run All + Delete All @@ -258,7 +270,7 @@ function DeadTasksTable(props: Props & ReduxProps) { onDeleteClick={() => { props.deleteDeadTaskAsync(queue, task.key); }} - deleteAllRequestPending={props.deleteAllRequestPending} + allActionPending={props.allActionPending} /> ))} @@ -292,7 +304,7 @@ interface RowProps { onSelectChange: (checked: boolean) => void; onRunClick: () => void; onDeleteClick: () => void; - deleteAllRequestPending: boolean; + allActionPending: boolean; } function Row(props: RowProps) { @@ -331,13 +343,13 @@ function Row(props: RowProps) { diff --git a/ui/src/reducers/queuesReducer.ts b/ui/src/reducers/queuesReducer.ts index e3a50c8..2fef3df 100644 --- a/ui/src/reducers/queuesReducer.ts +++ b/ui/src/reducers/queuesReducer.ts @@ -25,6 +25,7 @@ import { LIST_PENDING_TASKS_SUCCESS, LIST_RETRY_TASKS_SUCCESS, LIST_SCHEDULED_TASKS_SUCCESS, + RUN_ALL_DEAD_TASKS_SUCCESS, RUN_DEAD_TASK_SUCCESS, TasksActionTypes, } from "../actions/tasksActions"; @@ -256,6 +257,24 @@ function queuesReducer( return { ...state, data: newData }; } + case RUN_ALL_DEAD_TASKS_SUCCESS: { + const newData = state.data.map((queueInfo) => { + if (queueInfo.name !== action.queue) { + return queueInfo; + } + return { + ...queueInfo, + currentStats: { + ...queueInfo.currentStats, + pending: + queueInfo.currentStats.pending + queueInfo.currentStats.dead, + dead: 0, + }, + }; + }); + return { ...state, data: newData }; + } + case DELETE_ALL_DEAD_TASKS_SUCCESS: { const newData = state.data.map((queueInfo) => { if (queueInfo.name !== action.queue) { diff --git a/ui/src/reducers/snackbarReducer.ts b/ui/src/reducers/snackbarReducer.ts index dd3864c..09da840 100644 --- a/ui/src/reducers/snackbarReducer.ts +++ b/ui/src/reducers/snackbarReducer.ts @@ -9,6 +9,7 @@ import { DELETE_DEAD_TASK_SUCCESS, DELETE_RETRY_TASK_SUCCESS, DELETE_SCHEDULED_TASK_SUCCESS, + RUN_ALL_DEAD_TASKS_SUCCESS, RUN_DEAD_TASK_SUCCESS, TasksActionTypes, } from "../actions/tasksActions"; @@ -80,10 +81,16 @@ function snackbarReducer( }; } + case RUN_ALL_DEAD_TASKS_SUCCESS: + return { + isOpen: true, + message: "All dead tasks are now pending", + }; + case DELETE_ALL_DEAD_TASKS_SUCCESS: return { isOpen: true, - message: `All dead tasks delete`, + message: "All dead tasks delete", }; default: diff --git a/ui/src/reducers/tasksReducer.ts b/ui/src/reducers/tasksReducer.ts index bc8adac..ae9453c 100644 --- a/ui/src/reducers/tasksReducer.ts +++ b/ui/src/reducers/tasksReducer.ts @@ -39,6 +39,9 @@ import { DELETE_ALL_DEAD_TASKS_BEGIN, DELETE_ALL_DEAD_TASKS_SUCCESS, DELETE_ALL_DEAD_TASKS_ERROR, + RUN_ALL_DEAD_TASKS_BEGIN, + RUN_ALL_DEAD_TASKS_ERROR, + RUN_ALL_DEAD_TASKS_SUCCESS, } from "../actions/tasksActions"; import { ActiveTask, @@ -100,7 +103,7 @@ interface TasksState { deadTasks: { loading: boolean; batchActionPending: boolean; - deleteAllRequestPending: boolean; + allActionPending: boolean; error: string; data: DeadTaskExtended[]; }; @@ -130,7 +133,7 @@ const initialState: TasksState = { deadTasks: { loading: false, batchActionPending: false, - deleteAllRequestPending: false, + allActionPending: false, error: "", data: [], }, @@ -472,31 +475,34 @@ function tasksReducer( }, }; + case RUN_ALL_DEAD_TASKS_BEGIN: case DELETE_ALL_DEAD_TASKS_BEGIN: return { ...state, deadTasks: { ...state.deadTasks, - deleteAllRequestPending: true, + allActionPending: true, }, }; + case RUN_ALL_DEAD_TASKS_SUCCESS: case DELETE_ALL_DEAD_TASKS_SUCCESS: return { ...state, deadTasks: { ...state.deadTasks, - deleteAllRequestPending: false, + allActionPending: false, data: [], }, }; + case RUN_ALL_DEAD_TASKS_ERROR: case DELETE_ALL_DEAD_TASKS_ERROR: return { ...state, deadTasks: { ...state.deadTasks, - deleteAllRequestPending: false, + allActionPending: false, }, };