diff --git a/ui/src/actions/tasksActions.ts b/ui/src/actions/tasksActions.ts index ea9b50e..57caf27 100644 --- a/ui/src/actions/tasksActions.ts +++ b/ui/src/actions/tasksActions.ts @@ -1,4 +1,5 @@ import { + cancelActiveTask, listActiveTasks, ListActiveTasksResponse, listDeadTasks, @@ -29,6 +30,9 @@ export const LIST_RETRY_TASKS_ERROR = "LIST_RETRY_TASKS_ERROR"; export const LIST_DEAD_TASKS_BEGIN = "LIST_DEAD_TASKS_BEGIN"; export const LIST_DEAD_TASKS_SUCCESS = "LIST_DEAD_TASKS_SUCCESS"; 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"; interface ListActiveTasksBeginAction { type: typeof LIST_ACTIVE_TASKS_BEGIN; @@ -115,6 +119,25 @@ interface ListDeadTasksErrorAction { error: string; // error description } +interface CancelActiveTaskBeginAction { + type: typeof CANCEL_ACTIVE_TASK_BEGIN; + queue: string; + taskId: string; +} + +interface CancelActiveTaskSuccessAction { + type: typeof CANCEL_ACTIVE_TASK_SUCCESS; + queue: string; + taskId: string; +} + +interface CancelActiveTaskErrorAction { + type: typeof CANCEL_ACTIVE_TASK_ERROR; + queue: string; + taskId: string; + error: string; +} + // Union of all tasks related action types. export type TasksActionTypes = | ListActiveTasksBeginAction @@ -131,7 +154,10 @@ export type TasksActionTypes = | ListRetryTasksErrorAction | ListDeadTasksBeginAction | ListDeadTasksSuccessAction - | ListDeadTasksErrorAction; + | ListDeadTasksErrorAction + | CancelActiveTaskBeginAction + | CancelActiveTaskSuccessAction + | CancelActiveTaskErrorAction; export function listActiveTasksAsync( qname: string, @@ -247,3 +273,20 @@ export function listDeadTasksAsync( } }; } + +export function cancelActiveTaskAsync(queue: string, taskId: string) { + return async (dispatch: Dispatch) => { + dispatch({ type: CANCEL_ACTIVE_TASK_BEGIN, queue, taskId }); + try { + await cancelActiveTask(queue, taskId); + dispatch({ type: CANCEL_ACTIVE_TASK_SUCCESS, queue, taskId }); + } catch { + dispatch({ + type: CANCEL_ACTIVE_TASK_ERROR, + error: `Could not cancel task: ${taskId}`, + queue, + taskId, + }); + } + }; +} diff --git a/ui/src/api.ts b/ui/src/api.ts index 19d40a1..4e31479 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -170,6 +170,16 @@ export async function listActiveTasks( return resp.data; } +export async function cancelActiveTask( + qname: string, + taskId: string +): Promise { + await axios({ + method: "post", + url: `${BASE_URL}/queues/${qname}/active_tasks/${taskId}/cancel`, + }); +} + export async function listPendingTasks( qname: string, pageOpts?: PaginationOptions diff --git a/ui/src/reducers/tasksReducer.ts b/ui/src/reducers/tasksReducer.ts index ad5cbed..c1a112f 100644 --- a/ui/src/reducers/tasksReducer.ts +++ b/ui/src/reducers/tasksReducer.ts @@ -15,6 +15,9 @@ import { LIST_DEAD_TASKS_BEGIN, LIST_DEAD_TASKS_SUCCESS, LIST_DEAD_TASKS_ERROR, + CANCEL_ACTIVE_TASK_BEGIN, + CANCEL_ACTIVE_TASK_SUCCESS, + CANCEL_ACTIVE_TASK_ERROR, } from "../actions/tasksActions"; import { ActiveTask, @@ -24,11 +27,21 @@ import { ScheduledTask, } from "../api"; +interface ActiveTaskExtended extends ActiveTask { + // Indicates that a request has been sent for this + // task and awaiting for a response. + requestPending: boolean; + + // Incidates that a cancelation signal has been + // published for this task. + canceling: boolean; +} + interface TasksState { activeTasks: { loading: boolean; error: string; - data: ActiveTask[]; + data: ActiveTaskExtended[]; }; pendingTasks: { loading: boolean; @@ -101,7 +114,11 @@ function tasksReducer( activeTasks: { loading: false, error: "", - data: action.payload.tasks, + data: action.payload.tasks.map((task) => ({ + ...task, + canceling: false, + requestPending: false, + })), }, }; @@ -235,6 +252,53 @@ function tasksReducer( }, }; + case CANCEL_ACTIVE_TASK_BEGIN: { + const newData = state.activeTasks.data.map((task) => { + if (task.id !== action.taskId) { + return task; + } + return { ...task, requestPending: true }; + }); + return { + ...state, + activeTasks: { + ...state.activeTasks, + data: newData, + }, + }; + } + + case CANCEL_ACTIVE_TASK_SUCCESS: { + const newData = state.activeTasks.data.map((task) => { + if (task.id !== action.taskId) { + return task; + } + return { ...task, requestPending: false, canceling: true }; + }); + return { + ...state, + activeTasks: { + ...state.activeTasks, + data: newData, + }, + }; + } + + case CANCEL_ACTIVE_TASK_ERROR: + const newData = state.activeTasks.data.map((task) => { + if (task.id !== action.taskId) { + return task; + } + return { ...task, requestPending: false }; + }); + return { + ...state, + activeTasks: { + ...state.activeTasks, + data: newData, + }, + }; + default: return state; }