Create DeleteQueueConfirmationDialog component

This commit is contained in:
Ken Hibino 2020-11-28 08:34:12 -08:00
parent 0d94eaaee0
commit 753970471f
7 changed files with 196 additions and 48 deletions

View File

@ -91,7 +91,10 @@ func main() {
fs := &staticFileServer{staticPath: "ui/build", indexPath: "index.html"}
router.PathPrefix("/").Handler(fs)
handler := cors.Default().Handler(router)
c := cors.New(cors.Options{
AllowedMethods: []string{"GET", "POST", "DELETE"},
})
handler := c.Handler(router)
srv := &http.Server{
Handler: handler,

View File

@ -1,4 +1,5 @@
import {
deleteQueue,
getQueue,
GetQueueResponse,
listQueues,
@ -14,6 +15,9 @@ export const LIST_QUEUES_SUCCESS = "LIST_QUEUES_SUCCESS";
export const GET_QUEUE_BEGIN = "GET_QUEUE_BEGIN";
export const GET_QUEUE_SUCCESS = "GET_QUEUE_SUCCESS";
export const GET_QUEUE_ERROR = "GET_QUEUE_ERROR";
export const DELETE_QUEUE_BEGIN = "DELETE_QUEUE_BEGIN";
export const DELETE_QUEUE_SUCCESS = "DELETE_QUEUE_SUCCESS";
export const DELETE_QUEUE_ERROR = "DELETE_QUEUE_ERROR";
export const PAUSE_QUEUE_BEGIN = "PAUSE_QUEUE_BEGIN";
export const PAUSE_QUEUE_SUCCESS = "PAUSE_QUEUE_SUCCESS";
export const PAUSE_QUEUE_ERROR = "PAUSE_QUEUE_ERROR";
@ -47,6 +51,22 @@ interface GetQueueErrorAction {
error: string; // error description
}
interface DeleteQueueBeginAction {
type: typeof DELETE_QUEUE_BEGIN;
queue: string; // name of the queue
}
interface DeleteQueueSuccessAction {
type: typeof DELETE_QUEUE_SUCCESS;
queue: string; // name of the queue
}
interface DeleteQueueErrorAction {
type: typeof DELETE_QUEUE_ERROR;
queue: string; // name of the queue
error: string; // error description
}
interface PauseQueueBeginAction {
type: typeof PAUSE_QUEUE_BEGIN;
queue: string; // name of the queue
@ -86,6 +106,9 @@ export type QueuesActionTypes =
| GetQueueBeginAction
| GetQueueSuccessAction
| GetQueueErrorAction
| DeleteQueueBeginAction
| DeleteQueueSuccessAction
| DeleteQueueErrorAction
| PauseQueueBeginAction
| PauseQueueSuccessAction
| PauseQueueErrorAction
@ -115,7 +138,8 @@ export function getQueueAsync(qname: string) {
queue: qname,
payload: response,
});
} catch {
} catch (error) {
console.error(error);
dispatch({
type: GET_QUEUE_ERROR,
queue: qname,
@ -125,6 +149,30 @@ export function getQueueAsync(qname: string) {
};
}
export function deleteQueueAsync(qname: string) {
return async (dispatch: Dispatch<QueuesActionTypes>) => {
dispatch({
type: DELETE_QUEUE_BEGIN,
queue: qname,
});
try {
await deleteQueue(qname);
// FIXME: this action doesn't get dispatched when server stalls
dispatch({
type: DELETE_QUEUE_SUCCESS,
queue: qname,
});
} catch (error) {
console.error(error);
dispatch({
type: DELETE_QUEUE_ERROR,
queue: qname,
error: `Could not delete queue: ${qname}`,
});
}
};
}
export function pauseQueueAsync(qname: string) {
return async (dispatch: Dispatch<QueuesActionTypes>) => {
dispatch({ type: PAUSE_QUEUE_BEGIN, queue: qname });

View File

@ -118,6 +118,13 @@ export async function getQueue(qname: string): Promise<GetQueueResponse> {
return resp.data;
}
export async function deleteQueue(qname: string): Promise<void> {
await axios({
method: "delete",
url: `${BASE_URL}/queues/${qname}`,
});
}
export async function pauseQueue(qname: string): Promise<void> {
await axios({
method: "post",

View File

@ -0,0 +1,101 @@
import React from "react";
import { connect, ConnectedProps } from "react-redux";
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import { Queue } from "../api";
import { AppState } from "../store";
import { deleteQueueAsync } from "../actions/queuesActions";
interface Props {
queue: Queue | null; // queue to delete
onClose: () => void;
}
function mapStateToProps(state: AppState, ownProps: Props) {
let requestPending = false;
if (ownProps.queue !== null) {
const q = state.queues.data.find((q) => q.name === ownProps.queue?.queue);
if (q !== undefined) {
requestPending = q.requestPending;
}
}
return {
requestPending,
};
}
const connector = connect(mapStateToProps, { deleteQueueAsync });
type ReduxProps = ConnectedProps<typeof connector>;
function DeleteQueueConfirmationDialog(props: Props & ReduxProps) {
const handleDeleteClick = () => {
if (!props.queue) {
return;
}
props.deleteQueueAsync(props.queue.queue);
props.onClose();
};
return (
<Dialog
open={props.queue !== null}
onClose={props.onClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
{props.queue !== null &&
(props.queue.size > 0 ? (
<>
<DialogTitle id="alert-dialog-title">
Queue is not empty
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
You are trying to delete a non-emtpy queue "{props.queue.queue}
". Please empty the queue first before deleting.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={props.onClose} color="primary">
OK
</Button>
</DialogActions>
</>
) : (
<>
<DialogTitle id="alert-dialog-title">
Are you sure you want to delete "{props.queue.queue}"?
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
You can't undo this action.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button
onClick={props.onClose}
disabled={props.requestPending}
color="primary"
>
Cancel
</Button>
<Button
onClick={handleDeleteClick}
disabled={props.requestPending}
color="primary"
autoFocus
>
Delete
</Button>
</DialogActions>
</>
))}
</Dialog>
);
}
export default connector(DeleteQueueConfirmationDialog);

View File

@ -2,12 +2,6 @@ import React, { useState } from "react";
import clsx from "clsx";
import { Link } from "react-router-dom";
import { makeStyles } from "@material-ui/core/styles";
import Button from "@material-ui/core/Button";
import Dialog from "@material-ui/core/Dialog";
import DialogActions from "@material-ui/core/DialogActions";
import DialogContent from "@material-ui/core/DialogContent";
import DialogContentText from "@material-ui/core/DialogContentText";
import DialogTitle from "@material-ui/core/DialogTitle";
import Table from "@material-ui/core/Table";
import TableBody from "@material-ui/core/TableBody";
import TableCell from "@material-ui/core/TableCell";
@ -21,6 +15,7 @@ import PauseCircleFilledIcon from "@material-ui/icons/PauseCircleFilled";
import PlayCircleFilledIcon from "@material-ui/icons/PlayCircleFilled";
import DeleteIcon from "@material-ui/icons/Delete";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import DeleteQueueConfirmationDialog from "./DeleteQueueConfirmationDialog";
import { Queue } from "../api";
import { queueDetailsPath } from "../paths";
@ -53,13 +48,14 @@ const useStyles = makeStyles((theme) => ({
}));
interface QueueWithMetadata extends Queue {
pauseRequestPending: boolean; // indicates pause/resume request is pending for the queue.
requestPending: boolean; // indicates pause/resume/delete request is pending for the queue.
}
interface Props {
queues: QueueWithMetadata[];
onPauseClick: (qname: string) => Promise<void>;
onResumeClick: (qname: string) => Promise<void>;
onDeleteClick: (qname: string) => Promise<void>;
}
enum SortBy {
@ -118,7 +114,9 @@ export default function QueuesOverviewTable(props: Props) {
const [sortBy, setSortBy] = useState<SortBy>(SortBy.Queue);
const [sortDir, setSortDir] = useState<SortDirection>(SortDirection.Asc);
const [activeRowIndex, setActiveRowIndex] = useState<number>(-1);
const [queueToDelete, setQueueToDelete] = useState<Queue | null>(null);
const [queueToDelete, setQueueToDelete] = useState<QueueWithMetadata | null>(
null
);
const total = getAggregateCounts(props.queues);
const createSortClickHandler = (sortKey: SortBy) => (e: React.MouseEvent) => {
@ -274,7 +272,7 @@ export default function QueuesOverviewTable(props: Props) {
<IconButton
color="secondary"
onClick={() => props.onResumeClick(q.queue)}
disabled={q.pauseRequestPending}
disabled={q.requestPending}
>
<PlayCircleFilledIcon />
</IconButton>
@ -282,7 +280,7 @@ export default function QueuesOverviewTable(props: Props) {
<IconButton
color="primary"
onClick={() => props.onPauseClick(q.queue)}
disabled={q.pauseRequestPending}
disabled={q.requestPending}
>
<PauseCircleFilledIcon />
</IconButton>
@ -330,34 +328,10 @@ export default function QueuesOverviewTable(props: Props) {
</TableFooter>
</Table>
</TableContainer>
<Dialog
open={queueToDelete !== null}
<DeleteQueueConfirmationDialog
onClose={handleDialogClose}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
{queueToDelete !== null && (
<>
<DialogTitle id="alert-dialog-title">
Are you sure you want to delete "{queueToDelete.queue}"?
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
All of the tasks in the queue will be deleted. You can't undo
this action.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogClose} color="primary">
Cancel
</Button>
<Button onClick={handleDialogClose} color="primary" autoFocus>
Delete
</Button>
</DialogActions>
</>
)}
</Dialog>
queue={queueToDelete}
/>
</React.Fragment>
);
}

View File

@ -9,6 +9,9 @@ import {
RESUME_QUEUE_ERROR,
RESUME_QUEUE_SUCCESS,
GET_QUEUE_SUCCESS,
DELETE_QUEUE_BEGIN,
DELETE_QUEUE_ERROR,
DELETE_QUEUE_SUCCESS,
} from "../actions/queuesActions";
import {
LIST_ACTIVE_TASKS_SUCCESS,
@ -29,7 +32,7 @@ export interface QueueInfo {
name: string; // name of the queue.
currentStats: Queue;
history: DailyStat[];
pauseRequestPending: boolean; // indicates pause/resume action is pending on this queue
requestPending: boolean; // indicates pause/resume/delete action is pending on this queue
}
const initialState: QueuesState = { data: [], loading: false };
@ -51,7 +54,7 @@ function queuesReducer(
name: q.queue,
currentStats: q,
history: [],
pauseRequestPending: false,
requestPending: false,
})),
};
@ -62,21 +65,29 @@ function queuesReducer(
name: action.queue,
currentStats: action.payload.current,
history: action.payload.history,
pauseRequestPending: false,
requestPending: false,
});
return { ...state, data: newData };
case DELETE_QUEUE_BEGIN:
case PAUSE_QUEUE_BEGIN:
case RESUME_QUEUE_BEGIN: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {
return queueInfo;
}
return { ...queueInfo, pauseRequestPending: true };
return { ...queueInfo, requestPending: true };
});
return { ...state, data: newData };
}
case DELETE_QUEUE_SUCCESS: {
const newData = state.data.filter(
(queueInfo) => queueInfo.name !== action.queue
);
return { ...state, data: newData };
}
case PAUSE_QUEUE_SUCCESS: {
const newData = state.data.map((queueInfo) => {
if (queueInfo.name !== action.queue) {
@ -84,7 +95,7 @@ function queuesReducer(
}
return {
...queueInfo,
pauseRequestPending: false,
requestPending: false,
currentStats: { ...queueInfo.currentStats, paused: true },
};
});
@ -98,13 +109,14 @@ function queuesReducer(
}
return {
...queueInfo,
pauseRequestPending: false,
requestPending: false,
currentStats: { ...queueInfo.currentStats, paused: false },
};
});
return { ...state, data: newData };
}
case DELETE_QUEUE_ERROR:
case PAUSE_QUEUE_ERROR:
case RESUME_QUEUE_ERROR: {
const newData = state.data.map((queueInfo) => {
@ -113,7 +125,7 @@ function queuesReducer(
}
return {
...queueInfo,
pauseRequestPending: false,
requestPending: false,
};
});
return { ...state, data: newData };
@ -130,7 +142,7 @@ function queuesReducer(
name: action.queue,
currentStats: action.payload.stats,
history: [],
pauseRequestPending: false,
requestPending: false,
});
return { ...state, data: newData };
}

View File

@ -10,6 +10,7 @@ import {
listQueuesAsync,
pauseQueueAsync,
resumeQueueAsync,
deleteQueueAsync,
} from "../actions/queuesActions";
import { AppState } from "../store";
import QueueSizeChart from "../components/QueueSizeChart";
@ -56,7 +57,7 @@ function mapStateToProps(state: AppState) {
loading: state.queues.loading,
queues: state.queues.data.map((q) => ({
...q.currentStats,
pauseRequestPending: q.pauseRequestPending,
requestPending: q.requestPending,
})),
pollInterval: state.settings.pollInterval,
};
@ -66,6 +67,7 @@ const mapDispatchToProps = {
listQueuesAsync,
pauseQueueAsync,
resumeQueueAsync,
deleteQueueAsync,
};
const connector = connect(mapStateToProps, mapDispatchToProps);
@ -171,6 +173,7 @@ function DashboardView(props: Props) {
queues={queues}
onPauseClick={props.pauseQueueAsync}
onResumeClick={props.resumeQueueAsync}
onDeleteClick={props.deleteQueueAsync}
/>
</Paper>
</Grid>