Refactored frontend
This commit is contained in:
parent
37613e4de1
commit
6386294887
24 changed files with 54 additions and 529 deletions
94
src/views/failing/Failing.jsx
Normal file
94
src/views/failing/Failing.jsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { useState, useContext } from "react";
|
||||
import StoreContext from "../../ctx/StoreContext.jsx";
|
||||
import JobContext from "../../ctx/JobContext.jsx";
|
||||
import SilenceDialog from "../alerting/SilenceDialog.jsx";
|
||||
import FailingBox from "./FailingBox.jsx";
|
||||
|
||||
import SpeedDial from "@mui/material/SpeedDial";
|
||||
import SpeedDialAction from "@mui/material/SpeedDialAction";
|
||||
import SpeedDialIcon from "@mui/material/SpeedDialIcon";
|
||||
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
|
||||
import ReplayIcon from "@mui/icons-material/Replay";
|
||||
|
||||
|
||||
export default function Failing() {
|
||||
const { state: jobState, retryAll, activeJobStates } = useContext(JobContext);
|
||||
|
||||
const {
|
||||
state: store,
|
||||
updateStore,
|
||||
silenceRequest,
|
||||
} = useContext(StoreContext);
|
||||
const { failing } = store;
|
||||
|
||||
const [silenceEntry, setSilenceEntry] = useState({ open: false });
|
||||
|
||||
const closeSilence = () => setSilenceEntry({ ...silenceEntry, open: false });
|
||||
|
||||
const handleSilenceClose = (silenceReq) => {
|
||||
closeSilence();
|
||||
if (!silenceReq) return;
|
||||
silenceRequest(silenceReq);
|
||||
};
|
||||
|
||||
const editSilence = (silence) => () => {
|
||||
setSilenceEntry({ ...silence, open: true });
|
||||
};
|
||||
|
||||
const [retryAllOpen, setRetryAllOpen] = useState(false);
|
||||
const retryAllClick = () => setRetryAllOpen(!retryAllOpen);
|
||||
|
||||
const handleClose = (confirmed) => () => {
|
||||
retryAllClick();
|
||||
if (!confirmed) return;
|
||||
retryAll(store.failing);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="failing">
|
||||
{failing.map((v, i) => (
|
||||
<FailingBox key={i} failingTest={v} silenceClick={editSilence(v)} />
|
||||
))}
|
||||
|
||||
<Dialog
|
||||
open={retryAllOpen}
|
||||
onClose={handleClose()}
|
||||
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
>
|
||||
<DialogTitle>Retry all failing tests?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
This will create x jobs and run y tests
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose()}>Cancel</Button>
|
||||
<Button onClick={handleClose(true)} autoFocus>
|
||||
Yes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<SilenceDialog
|
||||
keepMounted
|
||||
open={silenceEntry.open}
|
||||
onClose={handleSilenceClose}
|
||||
silence={silenceEntry}
|
||||
/>
|
||||
<SpeedDial
|
||||
ariaLabel="Retry All"
|
||||
sx={{ position: "fixed", bottom: 16, right: 16 }}
|
||||
icon={<ReplayIcon />}
|
||||
onClick={retryAllClick}
|
||||
open={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
247
src/views/failing/FailingBox.jsx
Normal file
247
src/views/failing/FailingBox.jsx
Normal file
|
@ -0,0 +1,247 @@
|
|||
import React, { useState, useContext } from "react";
|
||||
import StoreContext from "../../ctx/StoreContext.jsx";
|
||||
import JobContext, { jobStatus } from "../../ctx/JobContext.jsx";
|
||||
|
||||
import Accordion from "@mui/material/Accordion";
|
||||
import AccordionDetails from "@mui/material/AccordionDetails";
|
||||
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||
import ReplayIcon from "@mui/icons-material/Replay";
|
||||
import PhotoCameraIcon from "@mui/icons-material/PhotoCamera";
|
||||
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
||||
import PendingIcon from "@mui/icons-material/Pending";
|
||||
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||
import DoNotDisturbIcon from "@mui/icons-material/DoNotDisturb";
|
||||
|
||||
import Badge from "@mui/material/Badge";
|
||||
import Stack from "@mui/material/Stack";
|
||||
import Box from "@mui/material/Box";
|
||||
|
||||
const stopPropagation = (e) => e.stopPropagation() && e.preventDefault();
|
||||
|
||||
export default function FailingBox(props) {
|
||||
const { failingTest, silenceClick } = props;
|
||||
|
||||
const {
|
||||
class: testClass,
|
||||
name: testName,
|
||||
timestamp,
|
||||
silencedUntil,
|
||||
type,
|
||||
dailyFails,
|
||||
screenshot: screenshotUrl,
|
||||
recentResults,
|
||||
failedMessage,
|
||||
isCompound,
|
||||
jobStatus: testJobStatus,
|
||||
} = failingTest;
|
||||
|
||||
const { state: jobState, retrySingle } = useContext(JobContext);
|
||||
|
||||
const { state: store, updateStore, removeFailure } = useContext(StoreContext);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const toggleOpen = () => setOpen(!open);
|
||||
|
||||
const [removeOpen, setRemoveOpen] = useState(false);
|
||||
const removeClick = () => setRemoveOpen(!removeOpen);
|
||||
|
||||
const handleRemoveClose = (confirmed) => (e) => {
|
||||
stopPropagation(e);
|
||||
setRemoveOpen(false);
|
||||
if (!confirmed) return;
|
||||
removeFailure(failingTest);
|
||||
};
|
||||
|
||||
function badgeColor() {
|
||||
if (dailyFails === 1) return "primary";
|
||||
else if (dailyFails === 2) return "secondary";
|
||||
else if (dailyFails < 6) return "warning";
|
||||
return "error";
|
||||
}
|
||||
|
||||
const retryTest = () => retrySingle(failingTest);
|
||||
|
||||
const jobOnClick = () => {
|
||||
switch (testJobStatus) {
|
||||
case jobStatus.OK:
|
||||
return null;
|
||||
case jobStatus.ERROR:
|
||||
return retryTest;
|
||||
case jobStatus.PENDING:
|
||||
return null;
|
||||
case jobStatus.ACTIVE:
|
||||
return null;
|
||||
case jobStatus.CANCELED:
|
||||
return retryTest;
|
||||
case jobStatus.QUEUED:
|
||||
return null;
|
||||
default:
|
||||
return retryTest;
|
||||
}
|
||||
};
|
||||
|
||||
function jobIcon() {
|
||||
switch (testJobStatus) {
|
||||
case jobStatus.OK:
|
||||
return <CheckIcon color="success" />;
|
||||
case jobStatus.ERROR:
|
||||
return <ClearIcon color="error" />;
|
||||
case jobStatus.PENDING:
|
||||
return <PendingIcon color="info" />;
|
||||
case jobStatus.ACTIVE:
|
||||
return <VisibilityIcon color="primary" />;
|
||||
case jobStatus.CANCELED:
|
||||
return <DoNotDisturbIcon color="warning" />;
|
||||
case jobStatus.QUEUED:
|
||||
return <ViewColumnIcon color="secondary" />;
|
||||
default:
|
||||
return <ReplayIcon />;
|
||||
}
|
||||
}
|
||||
|
||||
function Actions() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<a href={screenshotUrl}>
|
||||
<IconButton aria-label="photo" component="span">
|
||||
<PhotoCameraIcon />
|
||||
</IconButton>
|
||||
</a>
|
||||
|
||||
<IconButton aria-label="retry" component="span" onClick={jobOnClick()}>
|
||||
{jobIcon()}
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
aria-label="silence"
|
||||
component="span"
|
||||
color={silencedUntil ? "primary" : "default"}
|
||||
onClick={silenceClick}
|
||||
>
|
||||
<NotificationsIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
color="error"
|
||||
aria-label="delete"
|
||||
component="span"
|
||||
onClick={removeClick}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion
|
||||
expanded={open}
|
||||
onChange={toggleOpen}
|
||||
disableGutters={true}
|
||||
square
|
||||
>
|
||||
<AccordionSummary
|
||||
style={{
|
||||
backgroundColor: "rgba(0, 0, 0, .03)",
|
||||
flexWrap: "wrap",
|
||||
}}
|
||||
>
|
||||
<Badge
|
||||
sx={{ mr: 2 }}
|
||||
color={badgeColor()}
|
||||
badgeContent={dailyFails}
|
||||
anchorOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
}}
|
||||
></Badge>
|
||||
<Badge
|
||||
sx={{ mr: 2 }}
|
||||
style={{ whiteSpace: "nowrap", left: "3.125rem" }}
|
||||
badgeContent={new Date(timestamp).toLocaleTimeString("en-US")}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "left",
|
||||
}}
|
||||
></Badge>
|
||||
<Dialog
|
||||
open={removeOpen}
|
||||
onClose={handleRemoveClose()}
|
||||
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
>
|
||||
<DialogTitle>Remove failure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
This will remove 1 test from the database
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleRemoveClose()}>Cancel</Button>
|
||||
<Button onClick={handleRemoveClose(true)} autoFocus>
|
||||
Yes
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Typography component={"span"} style={{ wordBreak: "break-word" }}>
|
||||
{`${testClass}#`}
|
||||
<Box fontWeight="bold" display="inline">
|
||||
{testName}{" "}
|
||||
</Box>
|
||||
<br />
|
||||
<div>
|
||||
<span className="recent-results">
|
||||
{recentResults.map(
|
||||
(v, i) =>
|
||||
(v && <CheckIcon key={i} color="success" />) || (
|
||||
<ClearIcon key={i} color="error" />
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
{isCompound && <ViewColumnIcon />}
|
||||
</div>
|
||||
</Typography>
|
||||
|
||||
<Stack
|
||||
onClick={stopPropagation}
|
||||
sx={{ ml: "auto", display: { md: "none", lg: "none", xl: "none" } }}
|
||||
>
|
||||
<Actions />
|
||||
</Stack>
|
||||
<Stack
|
||||
onClick={stopPropagation}
|
||||
direction="row"
|
||||
sx={{
|
||||
ml: "auto",
|
||||
mb: "auto",
|
||||
mt: "auto",
|
||||
whiteSpace: "nowrap",
|
||||
display: { xs: "none", sm: "none", md: "block", lg: "block" },
|
||||
}}
|
||||
>
|
||||
<Actions />
|
||||
</Stack>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Typography component={"span"} style={{ wordBreak: "break-word" }}>
|
||||
{failedMessage}
|
||||
</Typography>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue