Implement in-browser task search

This commit is contained in:
Andy Bao 2022-05-17 19:19:28 -07:00
parent 1597dac66e
commit 2ec7a68d4a
9 changed files with 1016 additions and 237 deletions

View File

@ -60,9 +60,11 @@ import {
runAggregatingTask,
archiveAggregatingTask,
ListAggregatingTasksResponse,
Queue,
} from "../api";
import { Dispatch } from "redux";
import { toErrorString, toErrorStringWithHttpStatus } from "../utils";
import { AppState } from "../store";
// List of tasks related action types.
export const GET_TASK_INFO_BEGIN = "GET_TASK_INFO_BEGIN";
@ -89,6 +91,11 @@ export const LIST_COMPLETED_TASKS_ERROR = "LIST_COMPLETED_TASKS_ERROR";
export const LIST_AGGREGATING_TASKS_BEGIN = "LIST_AGGREGATING_TASKS_BEGIN";
export const LIST_AGGREGATING_TASKS_SUCCESS = "LIST_AGGREGATING_TASKS_SUCCESS";
export const LIST_AGGREGATING_TASKS_ERROR = "LIST_AGGREGATING_TASKS_ERROR";
export const FILTER_TASKS_BEGIN = "FILTER_TASKS_BEGIN";
export const FILTER_TASKS_PROGRESS = "FILTER_TASKS_PROGRESS";
export const FILTER_TASKS_SUCCESS = "FILTER_TASKS_SUCCESS";
export const FILTER_TASKS_ERROR = "FILTER_TASKS_ERROR";
export const FILTER_TASKS_CANCEL = "FILTER_TASKS_CANCEL";
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";
@ -429,6 +436,30 @@ interface ListAggregatingTasksErrorAction {
error: string; // error description
}
interface FilterTasksBeginAction {
type: typeof FILTER_TASKS_BEGIN;
}
interface FilterTasksProgressAction {
type: typeof FILTER_TASKS_PROGRESS;
processedTasks: number;
filterResults: TaskInfo[];
newStats: Queue;
}
interface FilterTasksSuccessAction {
type: typeof FILTER_TASKS_SUCCESS;
}
interface FilterTasksErrorAction {
type: typeof FILTER_TASKS_ERROR;
error: string;
}
interface FilterTasksCancelAction {
type: typeof FILTER_TASKS_CANCEL;
}
interface CancelActiveTaskBeginAction {
type: typeof CANCEL_ACTIVE_TASK_BEGIN;
queue: string;
@ -1291,6 +1322,11 @@ export type TasksActionTypes =
| ListAggregatingTasksBeginAction
| ListAggregatingTasksSuccessAction
| ListAggregatingTasksErrorAction
| FilterTasksBeginAction
| FilterTasksProgressAction
| FilterTasksSuccessAction
| FilterTasksErrorAction
| FilterTasksCancelAction
| CancelActiveTaskBeginAction
| CancelActiveTaskSuccessAction
| CancelActiveTaskErrorAction
@ -1446,14 +1482,42 @@ export function getTaskInfoAsync(qname: string, id: string) {
};
}
function getFilterResults(
state: AppState,
qname: string,
pageOpts?: PaginationOptions
): ListTasksResponse | null {
const filterOp = state.tasks.filterOp;
if (filterOp == null || !filterOp.done) return null;
const curQueueStats = state.queues.data.find((it) => it.name === qname);
if (curQueueStats == null) return null;
const size = pageOpts?.size ?? 20;
const page = pageOpts?.page ?? 1;
const start = (page - 1) * size;
const end = start + size;
const results = filterOp.result.slice(start, end);
return {
tasks: results,
stats: curQueueStats.currentStats,
};
}
export function listActiveTasksAsync(
qname: string,
pageOpts?: PaginationOptions
) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
return async (
dispatch: Dispatch<TasksActionTypes>,
getState: () => AppState
) => {
dispatch({ type: LIST_ACTIVE_TASKS_BEGIN, queue: qname });
try {
const response = await listActiveTasks(qname, pageOpts);
const response =
getFilterResults(getState(), qname, pageOpts) ??
(await listActiveTasks(qname, pageOpts));
dispatch({
type: LIST_ACTIVE_TASKS_SUCCESS,
queue: qname,
@ -1477,10 +1541,15 @@ export function listPendingTasksAsync(
qname: string,
pageOpts?: PaginationOptions
) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
return async (
dispatch: Dispatch<TasksActionTypes>,
getState: () => AppState
) => {
dispatch({ type: LIST_PENDING_TASKS_BEGIN, queue: qname });
try {
const response = await listPendingTasks(qname, pageOpts);
const response =
getFilterResults(getState(), qname, pageOpts) ??
(await listPendingTasks(qname, pageOpts));
dispatch({
type: LIST_PENDING_TASKS_SUCCESS,
queue: qname,
@ -1504,10 +1573,15 @@ export function listScheduledTasksAsync(
qname: string,
pageOpts?: PaginationOptions
) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
return async (
dispatch: Dispatch<TasksActionTypes>,
getState: () => AppState
) => {
dispatch({ type: LIST_SCHEDULED_TASKS_BEGIN, queue: qname });
try {
const response = await listScheduledTasks(qname, pageOpts);
const response =
getFilterResults(getState(), qname, pageOpts) ??
(await listScheduledTasks(qname, pageOpts));
dispatch({
type: LIST_SCHEDULED_TASKS_SUCCESS,
queue: qname,
@ -1531,10 +1605,15 @@ export function listRetryTasksAsync(
qname: string,
pageOpts?: PaginationOptions
) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
return async (
dispatch: Dispatch<TasksActionTypes>,
getState: () => AppState
) => {
dispatch({ type: LIST_RETRY_TASKS_BEGIN, queue: qname });
try {
const response = await listRetryTasks(qname, pageOpts);
const response =
getFilterResults(getState(), qname, pageOpts) ??
(await listRetryTasks(qname, pageOpts));
dispatch({
type: LIST_RETRY_TASKS_SUCCESS,
queue: qname,
@ -1558,10 +1637,15 @@ export function listArchivedTasksAsync(
qname: string,
pageOpts?: PaginationOptions
) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
return async (
dispatch: Dispatch<TasksActionTypes>,
getState: () => AppState
) => {
dispatch({ type: LIST_ARCHIVED_TASKS_BEGIN, queue: qname });
try {
const response = await listArchivedTasks(qname, pageOpts);
const response =
getFilterResults(getState(), qname, pageOpts) ??
(await listArchivedTasks(qname, pageOpts));
dispatch({
type: LIST_ARCHIVED_TASKS_SUCCESS,
queue: qname,
@ -1585,10 +1669,15 @@ export function listCompletedTasksAsync(
qname: string,
pageOpts?: PaginationOptions
) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
return async (
dispatch: Dispatch<TasksActionTypes>,
getState: () => AppState
) => {
try {
dispatch({ type: LIST_COMPLETED_TASKS_BEGIN, queue: qname });
const response = await listCompletedTasks(qname, pageOpts);
const response =
getFilterResults(getState(), qname, pageOpts) ??
(await listCompletedTasks(qname, pageOpts));
dispatch({
type: LIST_COMPLETED_TASKS_SUCCESS,
queue: qname,
@ -1613,6 +1702,7 @@ export function listAggregatingTasksAsync(
gname: string,
pageOpts?: PaginationOptions
) {
// TODO Add filter support
return async (dispatch: Dispatch<TasksActionTypes>) => {
try {
dispatch({
@ -1642,6 +1732,160 @@ export function listAggregatingTasksAsync(
};
}
export interface TasksFilter {
payloadQuery?: string;
resultQuery?: string;
payloadRegex?: RegExp;
resultRegex?: RegExp;
customJs?: string;
resultLimit?: number;
}
function parseIfJson(str: string) {
try {
return JSON.parse(str);
} catch (e) {
return str;
}
}
function evalCustomJsFilter(js: string, task: TaskInfo): any {
/* eslint-disable @typescript-eslint/no-unused-vars */
const {
id,
queue,
type,
state,
start_time,
max_retry,
retried,
last_failed_at,
error_message,
next_process_at,
timeout_seconds,
deadline,
group,
completed_at,
ttl_seconds,
is_orphaned,
} = task;
// Parse payload and result into JSON for convenience
const payload = parseIfJson(task.payload);
const result = parseIfJson(task.result);
/* eslint-enable @typescript-eslint/no-unused-vars */
return eval(js); // eslint-disable-line no-eval
}
function filterTask(filter: TasksFilter, task: TaskInfo): boolean {
if (
filter.payloadQuery != null &&
!task.payload.includes(filter.payloadQuery)
) {
return false;
}
if (filter.resultQuery != null && !task.result.includes(filter.resultQuery)) {
return false;
}
if (
filter.payloadRegex != null &&
task.payload.match(filter.payloadRegex) == null
) {
return false;
}
if (
filter.resultRegex != null &&
task.result.match(filter.resultRegex) == null
) {
return false;
}
if (filter.customJs != null) {
let customJsResult;
try {
customJsResult = evalCustomJsFilter(filter.customJs, task);
} catch (e) {
console.error(
"Custom JS filter error:",
e,
"task:",
task,
"skipping task."
);
return false;
}
// Custom JS can return anything so sanitize output into a boolean
return !!customJsResult;
} else {
return true;
}
}
export function filterTasksAsync(
filter: TasksFilter,
fetchTasks: (
page?: number // page number (1 being the first page)
) => Promise<ListTasksResponse>
) {
return async (
dispatch: Dispatch<TasksActionTypes>,
getState: () => AppState
) => {
dispatch({ type: FILTER_TASKS_BEGIN });
let page = 1;
let processed = 0;
let finished = false;
do {
// Check if operation was cancelled, if so, return
if (getState().tasks.filterOp == null) return;
// Fetch next page
let response;
try {
response = await fetchTasks(page);
} catch (error) {
dispatch({
type: FILTER_TASKS_ERROR,
error: toErrorString(error),
});
return;
}
page++;
// Process page
const filterResults: TaskInfo[] = [];
for (const task of response.tasks) {
if (filterTask(filter, task)) {
filterResults.push(task);
processed++;
if (filter.resultLimit != null && processed >= filter.resultLimit) {
finished = true;
break;
}
}
}
// Update state
dispatch({
type: FILTER_TASKS_PROGRESS,
processedTasks: response.tasks.length,
newStats: response.stats,
filterResults,
});
if (response.tasks.length === 0) finished = true;
} while (!finished);
dispatch({
type: FILTER_TASKS_SUCCESS,
});
};
}
export function cancelFilterTasks() {
return {
type: FILTER_TASKS_CANCEL,
};
}
export function cancelActiveTaskAsync(queue: string, taskId: string) {
return async (dispatch: Dispatch<TasksActionTypes>) => {
dispatch({ type: CANCEL_ACTIVE_TASK_BEGIN, queue, taskId });

View File

@ -0,0 +1,165 @@
import React, {
ChangeEventHandler,
KeyboardEventHandler,
useState,
} from "react";
import Dialog from "@material-ui/core/Dialog";
import DialogTitle from "@material-ui/core/DialogTitle";
import TextField from "@material-ui/core/TextField";
import DialogContent from "@material-ui/core/DialogContent";
import DialogActions from "@material-ui/core/DialogActions";
import Button from "@material-ui/core/Button";
import { TasksFilter } from "../actions/tasksActions";
import DialogContentText from "@material-ui/core/DialogContentText";
interface TasksFilterDialogProps {
open: boolean;
onClose?: () => void;
onFilter?: (filter: TasksFilter) => void;
}
function TasksFilterDialog(props: TasksFilterDialogProps) {
const [customJs, setCustomJs] = useState("");
const [payloadQuery, setPayloadQuery] = useState("");
const [resultQuery, setResultQuery] = useState("");
const [payloadRegex, setPayloadRegex] = useState("");
const [resultRegex, setResultRegex] = useState("");
const [resultLimitStr, setResultLimitStr] = useState("");
const [resultLimit, setResultLimit] = useState(-1);
const [resultLimitError, setResultLimitError] = useState<string | null>(null);
const buildFilter = () => {
if (resultLimitError != null) return null;
const filter: TasksFilter = {};
if (payloadQuery.trim().length > 0) filter.payloadQuery = payloadQuery;
if (resultQuery.trim().length > 0) filter.resultQuery = resultQuery;
if (payloadRegex.trim().length > 0)
filter.payloadRegex = RegExp(payloadRegex);
if (resultRegex.trim().length > 0) filter.resultRegex = RegExp(resultRegex);
if (customJs.trim().length > 0) filter.customJs = customJs;
if (resultLimit >= 0) filter.resultLimit = resultLimit;
return filter;
};
const handleRunFilter = () => {
const filter = buildFilter();
if (filter != null) {
if (props.onClose != null) props.onClose();
if (props.onFilter != null) props.onFilter(filter);
}
};
const handleKeyDown: KeyboardEventHandler = (event) => {
if (event.key === "Enter") {
handleRunFilter();
}
};
const handleResultLimitChange: ChangeEventHandler<HTMLInputElement> = (
event
) => {
const value = event.target.value;
setResultLimitStr(value);
const trimmedValue = value.trim();
if (trimmedValue.length === 0) {
setResultLimit(-1);
setResultLimitError(null);
} else {
const parsed = parseInt(trimmedValue, 10);
if (isNaN(parsed) || parsed < 0) {
setResultLimit(-1);
setResultLimitError("Please enter a valid positive number.");
} else {
setResultLimit(parsed);
setResultLimitError(null);
}
}
};
return (
<Dialog open={props.open} onClose={props.onClose}>
<DialogTitle>Filter tasks</DialogTitle>
<DialogContent>
<DialogContentText>
<p style={{ marginTop: 0 }}>
Filter results are stored in browser memory, please ensure that your
filter does not return too many results.
</p>
<p>
This feature may not behave correctly if the payload or result are
truncated due to <code>MaxPayloadLength</code> or{" "}
<code>MaxResultLength</code> being misconfigured.
</p>
</DialogContentText>
<TextField
label="Payload keyword"
placeholder="Search for tasks with payloads containing the specified text"
value={payloadQuery}
fullWidth
variant="filled"
onChange={(e) => setPayloadQuery(e.target.value)}
onKeyDown={handleKeyDown}
/>
<TextField
label="Result keyword"
placeholder="Search for tasks with results containing the specified text"
value={resultQuery}
fullWidth
variant="filled"
onChange={(e) => setResultQuery(e.target.value)}
onKeyDown={handleKeyDown}
/>
<TextField
label="Payload regex"
placeholder="Search for tasks with payloads matching the specified regex"
value={payloadRegex}
fullWidth
variant="filled"
onChange={(e) => setPayloadRegex(e.target.value)}
onKeyDown={handleKeyDown}
/>
<TextField
label="Query regex"
placeholder="Search for tasks with results matching the specified regex"
value={resultRegex}
fullWidth
variant="filled"
onChange={(e) => setResultRegex(e.target.value)}
onKeyDown={handleKeyDown}
/>
<TextField
label="JavaScript expression"
placeholder={
"Example:\npayload.auth.user_id === 'f094d054-64c0-42a3-a413-3f5a4eae0088'\n\nAvailable variables: " +
"id, queue, type, state, start_time, max_retry, retried, last_failed_at, error_message, next_process_at, " +
"timeout_seconds, deadline, group, completed_at, ttl_seconds, is_orphaned, payload, result"
}
value={customJs}
multiline
minRows={7}
fullWidth
variant="filled"
onChange={(e) => setCustomJs(e.target.value)}
/>
<TextField
label={"Limit number of results"}
placeholder={"Leave blank for no limit"}
variant="filled"
fullWidth
value={resultLimitStr}
onKeyDown={handleKeyDown}
onChange={handleResultLimitChange}
error={resultLimitError != null}
helperText={resultLimitError}
/>
</DialogContent>
<DialogActions>
<Button onClick={props.onClose}>Cancel</Button>
<Button onClick={handleRunFilter}>Run</Button>
</DialogActions>
</Dialog>
);
}
export default TasksFilterDialog;

View File

@ -0,0 +1,55 @@
import DialogTitle from "@material-ui/core/DialogTitle";
import React from "react";
import DialogActions from "@material-ui/core/DialogActions";
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import DialogContent from "@material-ui/core/DialogContent";
import LinearProgress from "@material-ui/core/LinearProgress";
import { AppState } from "../store";
import { connect, ConnectedProps } from "react-redux";
import DialogContentText from "@material-ui/core/DialogContentText";
import { cancelFilterTasks } from "../actions/tasksActions";
function mapStateToProps(state: AppState) {
const filterOp = state.tasks.filterOp;
return {
open: filterOp != null && !filterOp.done,
processedTasks: filterOp?.processedTasks ?? 0,
matches: filterOp?.result?.length ?? 0,
};
}
const mapDispatchToProps = {
cancelFilterTasks,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
type ReduxProps = ConnectedProps<typeof connector>;
interface TasksFilterDialogProps extends ReduxProps {
totalTasks: number;
}
function TasksFilterDialog(props: TasksFilterDialogProps) {
let progress;
if (props.totalTasks > 0) progress = props.processedTasks / props.totalTasks;
else progress = 0;
return (
<Dialog open={props.open} fullWidth>
<DialogTitle>Running filter</DialogTitle>
<DialogContent>
<DialogContentText>
Processed {props.processedTasks} of {props.totalTasks} total tasks,{" "}
{props.matches} matches so far.
</DialogContentText>
<LinearProgress variant="determinate" value={progress * 100} />
</DialogContent>
<DialogActions>
<Button onClick={props.cancelFilterTasks}>Cancel</Button>
</DialogActions>
</Dialog>
);
}
export default connector(TasksFilterDialog);

View File

@ -26,6 +26,18 @@ import { TaskInfoExtended } from "../reducers/tasksReducer";
import { TableColumn } from "../types/table";
import { PaginationOptions } from "../api";
import { TaskState } from "../types/taskState";
import TasksFilterProgressDialog from "./TasksFilterProgressDialog";
import { AppState } from "../store";
import { connect, ConnectedProps } from "react-redux";
function mapStateToProps(state: AppState) {
return {
filterEnabled: state.tasks.filterOp?.done ?? false,
};
}
const connector = connect(mapStateToProps);
type ReduxProps = ConnectedProps<typeof connector>;
const useStyles = makeStyles((theme) => ({
table: {
@ -43,7 +55,7 @@ const useStyles = makeStyles((theme) => ({
},
}));
interface Props {
interface Props extends ReduxProps {
queue: string; // name of the queue.
totalTaskCount: number; // totoal number of tasks in the given state.
taskState: TaskState;
@ -75,12 +87,15 @@ interface Props {
renderRow: (rowProps: RowProps) => JSX.Element;
}
export default function TasksTable(props: Props) {
function TasksTable(props: Props) {
const { pollInterval, listTasks, queue, pageSize } = props;
const classes = useStyles();
const [page, setPage] = useState(0);
const [selectedIds, setSelectedIds] = useState<string[]>([]);
const [activeTaskId, setActiveTaskId] = useState<string>("");
const [lastFilterEnabled, setLastFilterEnabled] = useState(
props.filterEnabled
);
const handlePageChange = (
event: React.MouseEvent<HTMLButtonElement> | null,
@ -123,6 +138,8 @@ export default function TasksTable(props: Props) {
}
let allActions = [];
if (!props.filterEnabled) {
// Protect against using all actions while filter enabled
if (props.deleteAllTasks) {
allActions.push({
label: "Delete All",
@ -151,6 +168,7 @@ export default function TasksTable(props: Props) {
disabled: props.allActionPending,
});
}
}
let batchActions = [];
if (props.batchDeleteTasks) {
@ -193,6 +211,12 @@ export default function TasksTable(props: Props) {
usePolling(fetchData, pollInterval);
if (props.filterEnabled !== lastFilterEnabled) {
setLastFilterEnabled(props.filterEnabled);
setPage(0);
fetchData();
}
if (props.error.length > 0) {
return (
<Alert severity="error" className={classes.alert}>
@ -320,6 +344,7 @@ export default function TasksTable(props: Props) {
</TableFooter>
</Table>
</TableContainer>
<TasksFilterProgressDialog totalTasks={props.totalTaskCount} />
</div>
);
}
@ -376,3 +401,5 @@ export interface RowProps {
onActionCellEnter: () => void;
onActionCellLeave: () => void;
}
export default connector(TasksTable);

View File

@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { connect, ConnectedProps } from "react-redux";
import { makeStyles } from "@material-ui/core/styles";
import Typography from "@material-ui/core/Typography";
@ -18,6 +18,24 @@ import { queueDetailsPath, taskDetailsPath } from "../paths";
import { QueueInfo } from "../reducers/queuesReducer";
import { AppState } from "../store";
import { isDarkTheme } from "../theme";
import { Divider } from "@material-ui/core";
import FilterListIcon from "@material-ui/icons/FilterList";
import TasksFilterDialog from "./TasksFilterDialog";
import {
listActiveTasks,
listArchivedTasks,
listCompletedTasks,
listPendingTasks,
listRetryTasks,
listScheduledTasks,
ListTasksResponse,
PaginationOptions,
} from "../api";
import {
cancelFilterTasks,
filterTasksAsync,
TasksFilter,
} from "../actions/tasksActions";
interface TabPanelProps {
children?: React.ReactNode;
@ -65,10 +83,18 @@ function mapStatetoProps(state: AppState, ownProps: Props) {
failed: 0,
timestamp: "n/a",
};
return { currentStats };
const filterOp = state.tasks.filterOp;
const filterActive = filterOp != null;
const filterCount = filterOp?.done === true ? filterOp?.result?.length : null;
return { currentStats, filterActive, filterCount };
}
const connector = connect(mapStatetoProps);
const mapDispatchToProps = {
filterTasksAsync,
cancelFilterTasks,
};
const connector = connect(mapStatetoProps, mapDispatchToProps);
type ReduxProps = ConnectedProps<typeof connector>;
@ -146,24 +172,92 @@ const useStyles = makeStyles((theme) => ({
}));
function TasksTableContainer(props: Props & ReduxProps) {
const { currentStats } = props;
const { currentStats, cancelFilterTasks } = props;
const classes = useStyles();
const history = useHistory();
const chips = [
{ key: "active", label: "Active", count: currentStats.active },
{ key: "pending", label: "Pending", count: currentStats.pending },
{
key: "active",
label: "Active",
count: currentStats.active,
fetcher: listActiveTasks,
},
{
key: "pending",
label: "Pending",
count: currentStats.pending,
fetcher: listPendingTasks,
},
{
key: "aggregating",
label: "Aggregating",
count: currentStats.aggregating,
fetcher: null, // TODO Support aggregating table
},
{
key: "scheduled",
label: "Scheduled",
count: currentStats.scheduled,
fetcher: listScheduledTasks,
},
{
key: "retry",
label: "Retry",
count: currentStats.retry,
fetcher: listRetryTasks,
},
{
key: "archived",
label: "Archived",
count: currentStats.archived,
fetcher: listArchivedTasks,
},
{
key: "completed",
label: "Completed",
count: currentStats.completed,
fetcher: listCompletedTasks,
},
{ key: "scheduled", label: "Scheduled", count: currentStats.scheduled },
{ key: "retry", label: "Retry", count: currentStats.retry },
{ key: "archived", label: "Archived", count: currentStats.archived },
{ key: "completed", label: "Completed", count: currentStats.completed },
];
const [searchQuery, setSearchQuery] = useState<string>("");
const [filterModalOpen, setFilterModalOpen] = useState(false);
useEffect(() => {
// Clear filter when the user selects a different task status
cancelFilterTasks();
return () => {
// Clear filter when leaving the page
cancelFilterTasks();
};
}, [props.selected, cancelFilterTasks]);
const taskPageFetcher = (
fetcherApi: (
qname: string,
pageOpts?: PaginationOptions
) => Promise<ListTasksResponse>
) => {
const qname = props.queue;
return async (page?: number) => {
return fetcherApi(qname, {
page,
size: 10_000, // TODO Choose a value here intelligently?
});
};
};
const dispatchFilterAction = (filter: TasksFilter) => {
const fetcherApi = chips.find((c) => c.key === props.selected)?.fetcher;
if (fetcherApi == null) {
console.error(
"Failed to find fetcher API for selected task type:",
props.selected
);
return;
}
props.filterTasksAsync(filter, taskPageFetcher(fetcherApi));
};
return (
<Paper variant="outlined" className={classes.container}>
@ -187,6 +281,25 @@ function TasksTableContainer(props: Props & ReduxProps) {
/>
))}
</div>
<Divider orientation="vertical" variant="middle" flexItem />
<div>
{chips.find((c) => c.key === props.selected)?.fetcher && (
<Chip
icon={<FilterListIcon />}
label="Filter"
onClick={() => setFilterModalOpen(true)}
onDelete={
props.filterActive ? props.cancelFilterTasks : undefined
}
color={props.filterActive ? "primary" : "default"}
/>
)}
<TasksFilterDialog
open={filterModalOpen}
onClose={() => setFilterModalOpen(false)}
onFilter={dispatchFilterAction}
/>
</div>
<div className={classes.searchbar}>
<div className={classes.search}>
<div className={classes.searchIcon}>
@ -219,13 +332,13 @@ function TasksTableContainer(props: Props & ReduxProps) {
<TabPanel value="active" selected={props.selected}>
<ActiveTasksTable
queue={props.queue}
totalTaskCount={currentStats.active}
totalTaskCount={props.filterCount ?? currentStats.active}
/>
</TabPanel>
<TabPanel value="pending" selected={props.selected}>
<PendingTasksTable
queue={props.queue}
totalTaskCount={currentStats.pending}
totalTaskCount={props.filterCount ?? currentStats.pending}
/>
</TabPanel>
<TabPanel value="aggregating" selected={props.selected}>
@ -234,25 +347,25 @@ function TasksTableContainer(props: Props & ReduxProps) {
<TabPanel value="scheduled" selected={props.selected}>
<ScheduledTasksTable
queue={props.queue}
totalTaskCount={currentStats.scheduled}
totalTaskCount={props.filterCount ?? currentStats.scheduled}
/>
</TabPanel>
<TabPanel value="retry" selected={props.selected}>
<RetryTasksTable
queue={props.queue}
totalTaskCount={currentStats.retry}
totalTaskCount={props.filterCount ?? currentStats.retry}
/>
</TabPanel>
<TabPanel value="archived" selected={props.selected}>
<ArchivedTasksTable
queue={props.queue}
totalTaskCount={currentStats.archived}
totalTaskCount={props.filterCount ?? currentStats.archived}
/>
</TabPanel>
<TabPanel value="completed" selected={props.selected}>
<CompletedTasksTable
queue={props.queue}
totalTaskCount={currentStats.completed}
totalTaskCount={props.filterCount ?? currentStats.completed}
/>
</TabPanel>
</Paper>

View File

@ -0,0 +1,149 @@
import {
ARCHIVE_AGGREGATING_TASK_SUCCESS,
ARCHIVE_PENDING_TASK_SUCCESS,
ARCHIVE_RETRY_TASK_SUCCESS,
ARCHIVE_SCHEDULED_TASK_SUCCESS,
BATCH_ARCHIVE_AGGREGATING_TASKS_SUCCESS,
BATCH_ARCHIVE_PENDING_TASKS_SUCCESS,
BATCH_ARCHIVE_RETRY_TASKS_SUCCESS,
BATCH_ARCHIVE_SCHEDULED_TASKS_SUCCESS,
BATCH_DELETE_AGGREGATING_TASKS_SUCCESS,
BATCH_DELETE_ARCHIVED_TASKS_SUCCESS,
BATCH_DELETE_COMPLETED_TASKS_SUCCESS,
BATCH_DELETE_PENDING_TASKS_SUCCESS,
BATCH_DELETE_RETRY_TASKS_SUCCESS,
BATCH_DELETE_SCHEDULED_TASKS_SUCCESS,
BATCH_RUN_AGGREGATING_TASKS_SUCCESS,
BATCH_RUN_ARCHIVED_TASKS_SUCCESS,
BATCH_RUN_RETRY_TASKS_SUCCESS,
BATCH_RUN_SCHEDULED_TASKS_SUCCESS,
DELETE_AGGREGATING_TASK_SUCCESS,
DELETE_ARCHIVED_TASK_SUCCESS,
DELETE_COMPLETED_TASK_SUCCESS,
DELETE_PENDING_TASK_SUCCESS,
DELETE_RETRY_TASK_SUCCESS,
DELETE_SCHEDULED_TASK_SUCCESS,
FILTER_TASKS_BEGIN,
FILTER_TASKS_CANCEL,
FILTER_TASKS_PROGRESS,
FILTER_TASKS_SUCCESS,
RUN_AGGREGATING_TASK_SUCCESS,
RUN_ARCHIVED_TASK_SUCCESS,
RUN_RETRY_TASK_SUCCESS,
RUN_SCHEDULED_TASK_SUCCESS,
TasksActionTypes,
} from "../actions/tasksActions";
import { TasksState } from "./tasksReducer";
import { TaskInfo } from "../api";
function modifyFilterResults(
state: TasksState,
action: (tasks: TaskInfo[]) => TaskInfo[]
): TasksState {
const filterOp = state.filterOp;
if (filterOp == null || !filterOp.done) return state;
return {
...state,
filterOp: {
...filterOp,
result: action(filterOp.result),
},
};
}
export function filterReducer(
state: TasksState,
action: TasksActionTypes
): TasksState {
switch (action.type) {
case FILTER_TASKS_BEGIN:
return {
...state,
filterOp: {
done: false,
processedTasks: 0,
result: [],
},
};
case FILTER_TASKS_PROGRESS: {
const filterOp = state.filterOp;
if (filterOp == null) return state;
return {
...state,
filterOp: {
done: false,
processedTasks: filterOp.processedTasks + action.processedTasks,
result: filterOp.result.concat(action.filterResults),
},
};
}
case FILTER_TASKS_SUCCESS: {
const filterOp = state.filterOp;
if (filterOp == null) return state;
return {
...state,
filterOp: {
...filterOp,
done: true,
},
};
}
case FILTER_TASKS_CANCEL: {
const { filterOp, ...newState } = state;
return newState;
}
// Replicate actions made to queues to filter results
case ARCHIVE_PENDING_TASK_SUCCESS:
case DELETE_PENDING_TASK_SUCCESS:
case DELETE_COMPLETED_TASK_SUCCESS:
case RUN_SCHEDULED_TASK_SUCCESS:
case ARCHIVE_SCHEDULED_TASK_SUCCESS:
case DELETE_SCHEDULED_TASK_SUCCESS:
case RUN_AGGREGATING_TASK_SUCCESS:
case ARCHIVE_AGGREGATING_TASK_SUCCESS:
case DELETE_AGGREGATING_TASK_SUCCESS:
case RUN_RETRY_TASK_SUCCESS:
case ARCHIVE_RETRY_TASK_SUCCESS:
case DELETE_RETRY_TASK_SUCCESS:
case RUN_ARCHIVED_TASK_SUCCESS:
case DELETE_ARCHIVED_TASK_SUCCESS:
return modifyFilterResults(state, (tasks) =>
tasks.filter((task) => task.id !== action.taskId)
);
case BATCH_ARCHIVE_PENDING_TASKS_SUCCESS:
case BATCH_ARCHIVE_SCHEDULED_TASKS_SUCCESS:
case BATCH_ARCHIVE_AGGREGATING_TASKS_SUCCESS:
case BATCH_ARCHIVE_RETRY_TASKS_SUCCESS:
return modifyFilterResults(state, (tasks) =>
tasks.filter((task) => !action.payload.archived_ids.includes(task.id))
);
case BATCH_DELETE_COMPLETED_TASKS_SUCCESS:
case BATCH_DELETE_PENDING_TASKS_SUCCESS:
case BATCH_DELETE_SCHEDULED_TASKS_SUCCESS:
case BATCH_DELETE_AGGREGATING_TASKS_SUCCESS:
case BATCH_DELETE_RETRY_TASKS_SUCCESS:
case BATCH_DELETE_ARCHIVED_TASKS_SUCCESS:
return modifyFilterResults(state, (tasks) =>
tasks.filter((task) => !action.payload.deleted_ids.includes(task.id))
);
case BATCH_RUN_SCHEDULED_TASKS_SUCCESS:
case BATCH_RUN_AGGREGATING_TASKS_SUCCESS:
case BATCH_RUN_RETRY_TASKS_SUCCESS:
case BATCH_RUN_ARCHIVED_TASKS_SUCCESS:
return modifyFilterResults(state, (tasks) =>
tasks.filter((task) => !action.payload.pending_ids.includes(task.id))
);
default:
return state;
}
}

View File

@ -3,44 +3,64 @@ import {
LIST_GROUPS_SUCCESS,
} from "../actions/groupsActions";
import {
LIST_QUEUES_SUCCESS,
LIST_QUEUES_BEGIN,
QueuesActionTypes,
PAUSE_QUEUE_BEGIN,
PAUSE_QUEUE_SUCCESS,
PAUSE_QUEUE_ERROR,
RESUME_QUEUE_BEGIN,
RESUME_QUEUE_ERROR,
RESUME_QUEUE_SUCCESS,
DELETE_QUEUE_BEGIN,
DELETE_QUEUE_ERROR,
DELETE_QUEUE_SUCCESS,
LIST_QUEUES_BEGIN,
LIST_QUEUES_ERROR,
LIST_QUEUES_SUCCESS,
PAUSE_QUEUE_BEGIN,
PAUSE_QUEUE_ERROR,
PAUSE_QUEUE_SUCCESS,
QueuesActionTypes,
RESUME_QUEUE_BEGIN,
RESUME_QUEUE_ERROR,
RESUME_QUEUE_SUCCESS,
} from "../actions/queuesActions";
import {
BATCH_DELETE_ARCHIVED_TASKS_SUCCESS,
BATCH_DELETE_RETRY_TASKS_SUCCESS,
BATCH_DELETE_SCHEDULED_TASKS_SUCCESS,
ARCHIVE_AGGREGATING_TASK_SUCCESS,
ARCHIVE_ALL_AGGREGATING_TASKS_SUCCESS,
ARCHIVE_ALL_PENDING_TASKS_SUCCESS,
ARCHIVE_ALL_RETRY_TASKS_SUCCESS,
ARCHIVE_ALL_SCHEDULED_TASKS_SUCCESS,
ARCHIVE_PENDING_TASK_SUCCESS,
ARCHIVE_RETRY_TASK_SUCCESS,
ARCHIVE_SCHEDULED_TASK_SUCCESS,
BATCH_ARCHIVE_AGGREGATING_TASKS_SUCCESS,
BATCH_ARCHIVE_PENDING_TASKS_SUCCESS,
BATCH_ARCHIVE_RETRY_TASKS_SUCCESS,
BATCH_ARCHIVE_SCHEDULED_TASKS_SUCCESS,
BATCH_DELETE_AGGREGATING_TASKS_SUCCESS,
BATCH_DELETE_ARCHIVED_TASKS_SUCCESS,
BATCH_DELETE_COMPLETED_TASKS_SUCCESS,
BATCH_DELETE_PENDING_TASKS_SUCCESS,
BATCH_DELETE_RETRY_TASKS_SUCCESS,
BATCH_DELETE_SCHEDULED_TASKS_SUCCESS,
BATCH_RUN_AGGREGATING_TASKS_SUCCESS,
BATCH_RUN_ARCHIVED_TASKS_SUCCESS,
BATCH_RUN_RETRY_TASKS_SUCCESS,
BATCH_RUN_SCHEDULED_TASKS_SUCCESS,
DELETE_AGGREGATING_TASK_SUCCESS,
DELETE_ALL_AGGREGATING_TASKS_SUCCESS,
DELETE_ALL_ARCHIVED_TASKS_SUCCESS,
DELETE_ALL_COMPLETED_TASKS_SUCCESS,
DELETE_ALL_PENDING_TASKS_SUCCESS,
DELETE_ALL_RETRY_TASKS_SUCCESS,
DELETE_ALL_SCHEDULED_TASKS_SUCCESS,
DELETE_ARCHIVED_TASK_SUCCESS,
DELETE_COMPLETED_TASK_SUCCESS,
DELETE_PENDING_TASK_SUCCESS,
DELETE_RETRY_TASK_SUCCESS,
DELETE_SCHEDULED_TASK_SUCCESS,
ARCHIVE_ALL_RETRY_TASKS_SUCCESS,
ARCHIVE_ALL_SCHEDULED_TASKS_SUCCESS,
ARCHIVE_RETRY_TASK_SUCCESS,
ARCHIVE_SCHEDULED_TASK_SUCCESS,
FILTER_TASKS_PROGRESS,
LIST_ACTIVE_TASKS_SUCCESS,
LIST_AGGREGATING_TASKS_SUCCESS,
LIST_ARCHIVED_TASKS_SUCCESS,
LIST_PENDING_TASKS_SUCCESS,
LIST_RETRY_TASKS_SUCCESS,
LIST_SCHEDULED_TASKS_SUCCESS,
RUN_AGGREGATING_TASK_SUCCESS,
RUN_ALL_AGGREGATING_TASKS_SUCCESS,
RUN_ALL_ARCHIVED_TASKS_SUCCESS,
RUN_ALL_RETRY_TASKS_SUCCESS,
RUN_ALL_SCHEDULED_TASKS_SUCCESS,
@ -48,25 +68,6 @@ import {
RUN_RETRY_TASK_SUCCESS,
RUN_SCHEDULED_TASK_SUCCESS,
TasksActionTypes,
ARCHIVE_PENDING_TASK_SUCCESS,
DELETE_PENDING_TASK_SUCCESS,
BATCH_ARCHIVE_PENDING_TASKS_SUCCESS,
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,
DELETE_ALL_AGGREGATING_TASKS_SUCCESS,
ARCHIVE_ALL_AGGREGATING_TASKS_SUCCESS,
RUN_ALL_AGGREGATING_TASKS_SUCCESS,
BATCH_DELETE_AGGREGATING_TASKS_SUCCESS,
BATCH_RUN_AGGREGATING_TASKS_SUCCESS,
BATCH_ARCHIVE_AGGREGATING_TASKS_SUCCESS,
DELETE_AGGREGATING_TASK_SUCCESS,
RUN_AGGREGATING_TASK_SUCCESS,
ARCHIVE_AGGREGATING_TASK_SUCCESS,
LIST_AGGREGATING_TASKS_SUCCESS,
} from "../actions/tasksActions";
import { Queue } from "../api";
@ -190,6 +191,17 @@ function queuesReducer(
return { ...state, data: newData };
}
case FILTER_TASKS_PROGRESS: {
const newData = state.data
.filter((queueInfo) => queueInfo.name !== action.newStats.queue)
.concat({
name: action.newStats.queue,
currentStats: action.newStats,
requestPending: false,
});
return { ...state, data: newData };
}
case RUN_AGGREGATING_TASK_SUCCESS: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {

View File

@ -1,166 +1,167 @@
import {
LIST_ACTIVE_TASKS_BEGIN,
LIST_ACTIVE_TASKS_SUCCESS,
LIST_ACTIVE_TASKS_ERROR,
TasksActionTypes,
LIST_PENDING_TASKS_BEGIN,
LIST_PENDING_TASKS_SUCCESS,
LIST_PENDING_TASKS_ERROR,
LIST_SCHEDULED_TASKS_BEGIN,
LIST_SCHEDULED_TASKS_SUCCESS,
LIST_SCHEDULED_TASKS_ERROR,
LIST_RETRY_TASKS_BEGIN,
LIST_RETRY_TASKS_SUCCESS,
LIST_RETRY_TASKS_ERROR,
LIST_ARCHIVED_TASKS_BEGIN,
LIST_ARCHIVED_TASKS_SUCCESS,
LIST_ARCHIVED_TASKS_ERROR,
LIST_COMPLETED_TASKS_BEGIN,
LIST_COMPLETED_TASKS_SUCCESS,
LIST_COMPLETED_TASKS_ERROR,
CANCEL_ACTIVE_TASK_BEGIN,
CANCEL_ACTIVE_TASK_SUCCESS,
CANCEL_ACTIVE_TASK_ERROR,
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_ARCHIVED_TASK_BEGIN,
DELETE_ARCHIVED_TASK_SUCCESS,
DELETE_ARCHIVED_TASK_ERROR,
BATCH_DELETE_ARCHIVED_TASKS_BEGIN,
BATCH_DELETE_ARCHIVED_TASKS_SUCCESS,
BATCH_DELETE_ARCHIVED_TASKS_ERROR,
RUN_ARCHIVED_TASK_BEGIN,
RUN_ARCHIVED_TASK_SUCCESS,
RUN_ARCHIVED_TASK_ERROR,
BATCH_RUN_ARCHIVED_TASKS_BEGIN,
BATCH_RUN_ARCHIVED_TASKS_ERROR,
BATCH_RUN_ARCHIVED_TASKS_SUCCESS,
DELETE_ALL_ARCHIVED_TASKS_BEGIN,
DELETE_ALL_ARCHIVED_TASKS_SUCCESS,
DELETE_ALL_ARCHIVED_TASKS_ERROR,
RUN_ALL_ARCHIVED_TASKS_BEGIN,
RUN_ALL_ARCHIVED_TASKS_ERROR,
RUN_ALL_ARCHIVED_TASKS_SUCCESS,
BATCH_DELETE_RETRY_TASKS_ERROR,
BATCH_RUN_RETRY_TASKS_ERROR,
BATCH_DELETE_RETRY_TASKS_SUCCESS,
BATCH_RUN_RETRY_TASKS_SUCCESS,
BATCH_DELETE_RETRY_TASKS_BEGIN,
BATCH_RUN_RETRY_TASKS_BEGIN,
DELETE_ALL_RETRY_TASKS_ERROR,
RUN_ALL_RETRY_TASKS_ERROR,
DELETE_ALL_RETRY_TASKS_SUCCESS,
RUN_ALL_RETRY_TASKS_SUCCESS,
DELETE_ALL_RETRY_TASKS_BEGIN,
RUN_ALL_RETRY_TASKS_BEGIN,
BATCH_DELETE_SCHEDULED_TASKS_ERROR,
BATCH_RUN_SCHEDULED_TASKS_ERROR,
BATCH_DELETE_SCHEDULED_TASKS_SUCCESS,
BATCH_RUN_SCHEDULED_TASKS_SUCCESS,
BATCH_DELETE_SCHEDULED_TASKS_BEGIN,
BATCH_RUN_SCHEDULED_TASKS_BEGIN,
DELETE_ALL_SCHEDULED_TASKS_ERROR,
RUN_ALL_SCHEDULED_TASKS_ERROR,
DELETE_ALL_SCHEDULED_TASKS_SUCCESS,
RUN_ALL_SCHEDULED_TASKS_SUCCESS,
DELETE_ALL_SCHEDULED_TASKS_BEGIN,
RUN_ALL_SCHEDULED_TASKS_BEGIN,
RUN_RETRY_TASK_BEGIN,
RUN_RETRY_TASK_SUCCESS,
RUN_RETRY_TASK_ERROR,
RUN_SCHEDULED_TASK_BEGIN,
RUN_SCHEDULED_TASK_SUCCESS,
RUN_SCHEDULED_TASK_ERROR,
ARCHIVE_SCHEDULED_TASK_BEGIN,
ARCHIVE_SCHEDULED_TASK_SUCCESS,
ARCHIVE_SCHEDULED_TASK_ERROR,
ARCHIVE_AGGREGATING_TASK_BEGIN,
ARCHIVE_AGGREGATING_TASK_ERROR,
ARCHIVE_AGGREGATING_TASK_SUCCESS,
ARCHIVE_ALL_AGGREGATING_TASKS_BEGIN,
ARCHIVE_ALL_AGGREGATING_TASKS_ERROR,
ARCHIVE_ALL_AGGREGATING_TASKS_SUCCESS,
ARCHIVE_ALL_PENDING_TASKS_BEGIN,
ARCHIVE_ALL_PENDING_TASKS_ERROR,
ARCHIVE_ALL_PENDING_TASKS_SUCCESS,
ARCHIVE_ALL_RETRY_TASKS_BEGIN,
ARCHIVE_ALL_RETRY_TASKS_ERROR,
ARCHIVE_ALL_RETRY_TASKS_SUCCESS,
ARCHIVE_ALL_SCHEDULED_TASKS_BEGIN,
ARCHIVE_ALL_SCHEDULED_TASKS_SUCCESS,
ARCHIVE_ALL_SCHEDULED_TASKS_ERROR,
ARCHIVE_ALL_SCHEDULED_TASKS_SUCCESS,
ARCHIVE_PENDING_TASK_BEGIN,
ARCHIVE_PENDING_TASK_ERROR,
ARCHIVE_PENDING_TASK_SUCCESS,
ARCHIVE_RETRY_TASK_BEGIN,
ARCHIVE_RETRY_TASK_ERROR,
ARCHIVE_RETRY_TASK_SUCCESS,
ARCHIVE_SCHEDULED_TASK_BEGIN,
ARCHIVE_SCHEDULED_TASK_ERROR,
ARCHIVE_SCHEDULED_TASK_SUCCESS,
BATCH_ARCHIVE_AGGREGATING_TASKS_BEGIN,
BATCH_ARCHIVE_AGGREGATING_TASKS_ERROR,
BATCH_ARCHIVE_AGGREGATING_TASKS_SUCCESS,
BATCH_ARCHIVE_PENDING_TASKS_BEGIN,
BATCH_ARCHIVE_PENDING_TASKS_ERROR,
BATCH_ARCHIVE_PENDING_TASKS_SUCCESS,
BATCH_ARCHIVE_RETRY_TASKS_BEGIN,
BATCH_ARCHIVE_RETRY_TASKS_ERROR,
BATCH_ARCHIVE_RETRY_TASKS_SUCCESS,
BATCH_ARCHIVE_SCHEDULED_TASKS_BEGIN,
BATCH_ARCHIVE_SCHEDULED_TASKS_ERROR,
BATCH_ARCHIVE_SCHEDULED_TASKS_SUCCESS,
ARCHIVE_RETRY_TASK_BEGIN,
ARCHIVE_RETRY_TASK_SUCCESS,
ARCHIVE_RETRY_TASK_ERROR,
ARCHIVE_ALL_RETRY_TASKS_BEGIN,
ARCHIVE_ALL_RETRY_TASKS_SUCCESS,
ARCHIVE_ALL_RETRY_TASKS_ERROR,
BATCH_ARCHIVE_RETRY_TASKS_SUCCESS,
BATCH_ARCHIVE_RETRY_TASKS_BEGIN,
BATCH_ARCHIVE_RETRY_TASKS_ERROR,
BATCH_CANCEL_ACTIVE_TASKS_BEGIN,
BATCH_CANCEL_ACTIVE_TASKS_SUCCESS,
BATCH_CANCEL_ACTIVE_TASKS_ERROR,
CANCEL_ALL_ACTIVE_TASKS_BEGIN,
CANCEL_ALL_ACTIVE_TASKS_SUCCESS,
CANCEL_ALL_ACTIVE_TASKS_ERROR,
ARCHIVE_PENDING_TASK_BEGIN,
DELETE_PENDING_TASK_BEGIN,
ARCHIVE_PENDING_TASK_SUCCESS,
DELETE_PENDING_TASK_SUCCESS,
ARCHIVE_PENDING_TASK_ERROR,
DELETE_PENDING_TASK_ERROR,
ARCHIVE_ALL_PENDING_TASKS_BEGIN,
DELETE_ALL_PENDING_TASKS_BEGIN,
ARCHIVE_ALL_PENDING_TASKS_SUCCESS,
DELETE_ALL_PENDING_TASKS_SUCCESS,
ARCHIVE_ALL_PENDING_TASKS_ERROR,
DELETE_ALL_PENDING_TASKS_ERROR,
BATCH_ARCHIVE_PENDING_TASKS_BEGIN,
BATCH_DELETE_PENDING_TASKS_BEGIN,
BATCH_ARCHIVE_PENDING_TASKS_SUCCESS,
BATCH_DELETE_PENDING_TASKS_SUCCESS,
BATCH_ARCHIVE_PENDING_TASKS_ERROR,
BATCH_DELETE_PENDING_TASKS_ERROR,
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_CANCEL_ACTIVE_TASKS_SUCCESS,
BATCH_DELETE_AGGREGATING_TASKS_BEGIN,
BATCH_DELETE_AGGREGATING_TASKS_ERROR,
BATCH_DELETE_AGGREGATING_TASKS_SUCCESS,
BATCH_DELETE_ARCHIVED_TASKS_BEGIN,
BATCH_DELETE_ARCHIVED_TASKS_ERROR,
BATCH_DELETE_ARCHIVED_TASKS_SUCCESS,
BATCH_DELETE_COMPLETED_TASKS_BEGIN,
BATCH_DELETE_COMPLETED_TASKS_ERROR,
BATCH_DELETE_COMPLETED_TASKS_SUCCESS,
LIST_AGGREGATING_TASKS_BEGIN,
LIST_AGGREGATING_TASKS_SUCCESS,
LIST_AGGREGATING_TASKS_ERROR,
DELETE_ALL_AGGREGATING_TASKS_BEGIN,
DELETE_ALL_AGGREGATING_TASKS_SUCCESS,
DELETE_ALL_AGGREGATING_TASKS_ERROR,
ARCHIVE_ALL_AGGREGATING_TASKS_BEGIN,
ARCHIVE_ALL_AGGREGATING_TASKS_SUCCESS,
ARCHIVE_ALL_AGGREGATING_TASKS_ERROR,
RUN_ALL_AGGREGATING_TASKS_BEGIN,
RUN_ALL_AGGREGATING_TASKS_SUCCESS,
RUN_ALL_AGGREGATING_TASKS_ERROR,
BATCH_ARCHIVE_AGGREGATING_TASKS_BEGIN,
BATCH_DELETE_PENDING_TASKS_BEGIN,
BATCH_DELETE_PENDING_TASKS_ERROR,
BATCH_DELETE_PENDING_TASKS_SUCCESS,
BATCH_DELETE_RETRY_TASKS_BEGIN,
BATCH_DELETE_RETRY_TASKS_ERROR,
BATCH_DELETE_RETRY_TASKS_SUCCESS,
BATCH_DELETE_SCHEDULED_TASKS_BEGIN,
BATCH_DELETE_SCHEDULED_TASKS_ERROR,
BATCH_DELETE_SCHEDULED_TASKS_SUCCESS,
BATCH_RUN_AGGREGATING_TASKS_BEGIN,
BATCH_DELETE_AGGREGATING_TASKS_BEGIN,
BATCH_RUN_AGGREGATING_TASKS_ERROR,
BATCH_ARCHIVE_AGGREGATING_TASKS_ERROR,
BATCH_DELETE_AGGREGATING_TASKS_ERROR,
BATCH_DELETE_AGGREGATING_TASKS_SUCCESS,
BATCH_RUN_AGGREGATING_TASKS_SUCCESS,
BATCH_ARCHIVE_AGGREGATING_TASKS_SUCCESS,
RUN_AGGREGATING_TASK_BEGIN,
ARCHIVE_AGGREGATING_TASK_BEGIN,
BATCH_RUN_ARCHIVED_TASKS_BEGIN,
BATCH_RUN_ARCHIVED_TASKS_ERROR,
BATCH_RUN_ARCHIVED_TASKS_SUCCESS,
BATCH_RUN_RETRY_TASKS_BEGIN,
BATCH_RUN_RETRY_TASKS_ERROR,
BATCH_RUN_RETRY_TASKS_SUCCESS,
BATCH_RUN_SCHEDULED_TASKS_BEGIN,
BATCH_RUN_SCHEDULED_TASKS_ERROR,
BATCH_RUN_SCHEDULED_TASKS_SUCCESS,
CANCEL_ACTIVE_TASK_BEGIN,
CANCEL_ACTIVE_TASK_ERROR,
CANCEL_ACTIVE_TASK_SUCCESS,
CANCEL_ALL_ACTIVE_TASKS_BEGIN,
CANCEL_ALL_ACTIVE_TASKS_ERROR,
CANCEL_ALL_ACTIVE_TASKS_SUCCESS,
DELETE_AGGREGATING_TASK_BEGIN,
RUN_AGGREGATING_TASK_ERROR,
ARCHIVE_AGGREGATING_TASK_ERROR,
DELETE_AGGREGATING_TASK_ERROR,
RUN_AGGREGATING_TASK_SUCCESS,
ARCHIVE_AGGREGATING_TASK_SUCCESS,
DELETE_AGGREGATING_TASK_SUCCESS,
DELETE_ALL_AGGREGATING_TASKS_BEGIN,
DELETE_ALL_AGGREGATING_TASKS_ERROR,
DELETE_ALL_AGGREGATING_TASKS_SUCCESS,
DELETE_ALL_ARCHIVED_TASKS_BEGIN,
DELETE_ALL_ARCHIVED_TASKS_ERROR,
DELETE_ALL_ARCHIVED_TASKS_SUCCESS,
DELETE_ALL_COMPLETED_TASKS_BEGIN,
DELETE_ALL_COMPLETED_TASKS_ERROR,
DELETE_ALL_COMPLETED_TASKS_SUCCESS,
DELETE_ALL_PENDING_TASKS_BEGIN,
DELETE_ALL_PENDING_TASKS_ERROR,
DELETE_ALL_PENDING_TASKS_SUCCESS,
DELETE_ALL_RETRY_TASKS_BEGIN,
DELETE_ALL_RETRY_TASKS_ERROR,
DELETE_ALL_RETRY_TASKS_SUCCESS,
DELETE_ALL_SCHEDULED_TASKS_BEGIN,
DELETE_ALL_SCHEDULED_TASKS_ERROR,
DELETE_ALL_SCHEDULED_TASKS_SUCCESS,
DELETE_ARCHIVED_TASK_BEGIN,
DELETE_ARCHIVED_TASK_ERROR,
DELETE_ARCHIVED_TASK_SUCCESS,
DELETE_COMPLETED_TASK_BEGIN,
DELETE_COMPLETED_TASK_ERROR,
DELETE_COMPLETED_TASK_SUCCESS,
DELETE_PENDING_TASK_BEGIN,
DELETE_PENDING_TASK_ERROR,
DELETE_PENDING_TASK_SUCCESS,
DELETE_RETRY_TASK_BEGIN,
DELETE_RETRY_TASK_ERROR,
DELETE_RETRY_TASK_SUCCESS,
DELETE_SCHEDULED_TASK_BEGIN,
DELETE_SCHEDULED_TASK_ERROR,
DELETE_SCHEDULED_TASK_SUCCESS,
GET_TASK_INFO_BEGIN,
GET_TASK_INFO_ERROR,
GET_TASK_INFO_SUCCESS,
LIST_ACTIVE_TASKS_BEGIN,
LIST_ACTIVE_TASKS_ERROR,
LIST_ACTIVE_TASKS_SUCCESS,
LIST_AGGREGATING_TASKS_BEGIN,
LIST_AGGREGATING_TASKS_ERROR,
LIST_AGGREGATING_TASKS_SUCCESS,
LIST_ARCHIVED_TASKS_BEGIN,
LIST_ARCHIVED_TASKS_ERROR,
LIST_ARCHIVED_TASKS_SUCCESS,
LIST_COMPLETED_TASKS_BEGIN,
LIST_COMPLETED_TASKS_ERROR,
LIST_COMPLETED_TASKS_SUCCESS,
LIST_PENDING_TASKS_BEGIN,
LIST_PENDING_TASKS_ERROR,
LIST_PENDING_TASKS_SUCCESS,
LIST_RETRY_TASKS_BEGIN,
LIST_RETRY_TASKS_ERROR,
LIST_RETRY_TASKS_SUCCESS,
LIST_SCHEDULED_TASKS_BEGIN,
LIST_SCHEDULED_TASKS_ERROR,
LIST_SCHEDULED_TASKS_SUCCESS,
RUN_AGGREGATING_TASK_BEGIN,
RUN_AGGREGATING_TASK_ERROR,
RUN_AGGREGATING_TASK_SUCCESS,
RUN_ALL_AGGREGATING_TASKS_BEGIN,
RUN_ALL_AGGREGATING_TASKS_ERROR,
RUN_ALL_AGGREGATING_TASKS_SUCCESS,
RUN_ALL_ARCHIVED_TASKS_BEGIN,
RUN_ALL_ARCHIVED_TASKS_ERROR,
RUN_ALL_ARCHIVED_TASKS_SUCCESS,
RUN_ALL_RETRY_TASKS_BEGIN,
RUN_ALL_RETRY_TASKS_ERROR,
RUN_ALL_RETRY_TASKS_SUCCESS,
RUN_ALL_SCHEDULED_TASKS_BEGIN,
RUN_ALL_SCHEDULED_TASKS_ERROR,
RUN_ALL_SCHEDULED_TASKS_SUCCESS,
RUN_ARCHIVED_TASK_BEGIN,
RUN_ARCHIVED_TASK_ERROR,
RUN_ARCHIVED_TASK_SUCCESS,
RUN_RETRY_TASK_BEGIN,
RUN_RETRY_TASK_ERROR,
RUN_RETRY_TASK_SUCCESS,
RUN_SCHEDULED_TASK_BEGIN,
RUN_SCHEDULED_TASK_ERROR,
RUN_SCHEDULED_TASK_SUCCESS,
TasksActionTypes,
} from "../actions/tasksActions";
import { TaskInfo } from "../api";
import { filterReducer } from "./filterReducer";
export interface TaskInfoExtended extends TaskInfo {
// Indicates that a request has been sent for this
@ -174,7 +175,7 @@ export interface TaskInfoExtended extends TaskInfo {
canceling?: boolean;
}
interface TasksState {
export interface TasksState {
activeTasks: {
loading: boolean;
batchActionPending: boolean;
@ -230,6 +231,12 @@ interface TasksState {
error: string;
data?: TaskInfo;
};
filterOp?: {
done: boolean;
processedTasks: number;
result: TaskInfo[];
error?: string;
};
}
const initialState: TasksState = {
@ -293,6 +300,7 @@ function tasksReducer(
state = initialState,
action: TasksActionTypes
): TasksState {
state = filterReducer(state, action);
switch (action.type) {
case GET_TASK_INFO_BEGIN:
return {

View File

@ -32,6 +32,12 @@ export type AppState = ReturnType<typeof rootReducer>;
const store = configureStore({
reducer: rootReducer,
preloadedState,
middleware: (getDefaultMiddleware) =>
// Disable debug middleware for tasks.filterOp as it may contain large state
getDefaultMiddleware({
immutableCheck: { ignoredPaths: ["tasks.filterOp"] },
serializableCheck: { ignoredPaths: ["tasks.filterOp"] },
}),
});
export default store;