286 lines
8.4 KiB
JavaScript
286 lines
8.4 KiB
JavaScript
import React, { useState, useContext } from "react";
|
|
import { useNavigate } from "react-router-dom";
|
|
import { usePipelineMappings } from "../../Queries.jsx";
|
|
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";
|
|
|
|
import {
|
|
selectBranch,
|
|
asTree,
|
|
asBranches,
|
|
as1d,
|
|
} from "../../../lib/jobs/pipelines.js";
|
|
|
|
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,
|
|
isPipeline,
|
|
jobStatus: testJobStatus,
|
|
job,
|
|
pipeline,
|
|
} = failingTest;
|
|
|
|
const navigate = useNavigate();
|
|
const { data: pipelineMappings, isLoading } = usePipelineMappings();
|
|
const { jobFactory } = 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 retryPipelineTest = () => {
|
|
const primaries = pipelineMappings.filter((m) => m.includes(testName));
|
|
const builderCache = {
|
|
branches: asBranches(primaries),
|
|
tree: asTree(primaries),
|
|
selectedBranches: as1d(primaries),
|
|
isTriage: true,
|
|
};
|
|
const pipeline = jobFactory(builderCache);
|
|
if (store.focusJob) navigate(`/qualiteer/jobs#p${pipeline.id}`);
|
|
};
|
|
|
|
const retryTest = () => {
|
|
if (isPipeline) return retryPipelineTest();
|
|
const jobId = jobFactory({ testNames: [testName], isTriage: true });
|
|
if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
|
|
};
|
|
|
|
const navigateToJob = () => {
|
|
if (pipeline) return navigate(`/qualiteer/jobs#p${pipeline.id}`);
|
|
navigate(`/qualiteer/jobs#${job.jobId}`);
|
|
};
|
|
|
|
const jobOnClick = () => {
|
|
if (pipeline) return navigateToJob;
|
|
if (!job) return retryTest;
|
|
switch (job.status) {
|
|
case jobStatus.OK:
|
|
return navigateToJob;
|
|
case jobStatus.ERROR:
|
|
return retryTest;
|
|
case jobStatus.PENDING:
|
|
return navigateToJob;
|
|
case jobStatus.ACTIVE:
|
|
return navigateToJob;
|
|
case jobStatus.CANCELED:
|
|
return navigateToJob;
|
|
case jobStatus.QUEUED:
|
|
return navigateToJob;
|
|
default:
|
|
return retryTest;
|
|
}
|
|
};
|
|
|
|
function jobIcon() {
|
|
if (pipeline && pipeline.isCanceled)
|
|
return <DoNotDisturbIcon color="warning" />;
|
|
if (pipeline) return <ViewColumnIcon color="secondary" />;
|
|
if (!job) return <ReplayIcon />;
|
|
switch (job.status) {
|
|
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>
|
|
{isPipeline && <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>
|
|
);
|
|
}
|