Stable Modification Point

This commit is contained in:
Dunemask 2022-06-22 00:47:19 +00:00
parent d94796173e
commit 468437b5d0
19 changed files with 500 additions and 106 deletions

View file

@ -1,5 +1,7 @@
import { useState, useContext } from "react";
import StoreContext from "../ctx/StoreContext.jsx";
import SilencedBox from "./components/SilencedBox.jsx";
import SilenceDialog from "./components/SilenceDialog.jsx";
import SpeedDial from "@mui/material/SpeedDial";
import SpeedDialAction from "@mui/material/SpeedDialAction";
@ -13,36 +15,78 @@ import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
export default function Alerting() {
const { state: store, updateStore } = useContext(StoreContext);
const {
state: store,
updateStore,
silenceRequest,
} = useContext(StoreContext);
const [alertDialogOpen, setAlertDialogOpen] = useState(false);
const quickAlertClick = () => setAlertDialogOpen(!alertDialogOpen);
const [silenceEntry, setSilenceEntry] = useState({
open: false,
deleteOpen: false,
});
function silenceAlert() {}
const handleClose = (confirmed) => () => {
quickAlertClick();
if (!confirmed) return;
silenceAlert();
const closeSilence = () =>
setSilenceEntry({ ...silenceEntry, open: false, deleteOpen: false });
const handleDeleteClose = (makeRequest) => () => {
const silenceReq = { ...silenceEntry };
closeSilence();
if (!makeRequest) return;
silenceRequest({ ...silenceReq, silencedUntil: null });
};
const handleClose = (silenceReq) => {
closeSilence();
if (!silenceReq) return;
silenceRequest(silenceReq);
};
const quickAlertClick = () => {
setSilenceEntry({ open: true, deleteOpen: false });
};
const editSilence = (silence) => () => {
setSilenceEntry({ ...silence, open: true, deleteOpen: false });
};
const removeSilence = (silence) => () => {
setSilenceEntry({ ...silence, deleteOpen: true, open: false });
};
return (
<div className="alerting">
{store.silenced.map((v, i) => (
<SilencedBox
key={i}
silenceEntry={v}
editSilence={editSilence(v)}
removeSilence={removeSilence(v)}
/>
))}
<Dialog
open={alertDialogOpen}
onClose={handleClose()}
open={silenceEntry.deleteOpen}
onClose={handleDeleteClose()}
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
maxWidth="xs"
>
<DialogTitle>Silence Alert</DialogTitle>
<DialogContent></DialogContent>
<DialogTitle>Resume Alerting</DialogTitle>
<DialogContent>Are you sure you want to resume alerting?</DialogContent>
<DialogActions>
<Button onClick={handleClose()}>Cancel</Button>
<Button onClick={handleClose(true)} autoFocus>
Silence
<Button onClick={handleDeleteClose()}>Cancel</Button>
<Button onClick={handleDeleteClose(true)} autoFocus>
Remove
</Button>
</DialogActions>
</Dialog>
<SilenceDialog
keepMounted
open={silenceEntry.open}
onClose={handleClose}
silence={silenceEntry}
/>
<SpeedDial
ariaLabel="Silence Alert"
sx={{ position: "fixed", bottom: 16, right: 16 }}

View file

@ -1,6 +1,7 @@
import { useEffect, useContext } from "react";
import StoreContext from "../ctx/StoreContext.jsx";
import JobContext from "../ctx/JobContext.jsx";
import CatalogBox from "./components/CatalogBox.jsx";
import TextField from "@mui/material/TextField";
@ -35,6 +36,9 @@ export default function Catalog() {
clearOnUnmount
/>
<h6>{store.catalogSearch}</h6>
{store.catalog.map((v, i) => (
<CatalogBox key={i} catalogTest={v} />
))}
</div>
);
}

View file

@ -1,6 +1,7 @@
import { useState, useContext } from "react";
import StoreContext from "../ctx/StoreContext.jsx";
import JobContext from "../ctx/JobContext.jsx";
import SilenceDialog from "./components/SilenceDialog.jsx";
import SpeedDial from "@mui/material/SpeedDial";
import SpeedDialAction from "@mui/material/SpeedDialAction";
@ -20,9 +21,27 @@ import FailingBox from "./components/FailingBox.jsx";
export default function Failing() {
const { state: jobState, retryAll, activeJobStates } = useContext(JobContext);
const { state: store, updateStore } = useContext(StoreContext);
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 });
};
/* TODO
for(var j of activeJobStates()){
const failingTest = failing.find((f)=>f.name===j.testName);
@ -32,6 +51,7 @@ const failingTest = failing.find((f)=>f.name===j.testName);
const [retryAllOpen, setRetryAllOpen] = useState(false);
const retryAllClick = () => setRetryAllOpen(!retryAllOpen);
const handleClose = (confirmed) => () => {
retryAllClick();
if (!confirmed) return;
@ -41,7 +61,7 @@ const failingTest = failing.find((f)=>f.name===j.testName);
return (
<div className="failing">
{failing.map((v, i) => (
<FailingBox key={i} failingTest={v} />
<FailingBox key={i} failingTest={v} silenceClick={editSilence(v)} />
))}
<Dialog
@ -63,7 +83,12 @@ const failingTest = failing.find((f)=>f.name===j.testName);
</Button>
</DialogActions>
</Dialog>
<SilenceDialog
keepMounted
open={silenceEntry.open}
onClose={handleSilenceClose}
silence={silenceEntry}
/>
<SpeedDial
ariaLabel="Retry All"
sx={{ position: "fixed", bottom: 16, right: 16 }}

View file

@ -1,6 +1,15 @@
import { useState, useContext } from "react";
import { useState, useContext, useEffect } from "react";
import StoreContext from "../ctx/StoreContext.jsx";
import JobContext from "../ctx/JobContext.jsx";
import JobBox from "./components/JobBox.jsx";
import JobTestSelector from "./components/JobTestSelector.jsx";
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 DialogTitle from "@mui/material/DialogTitle";
import ClickAwayListener from "@mui/material/ClickAwayListener";
import SpeedDial from "@mui/material/SpeedDial";
@ -20,20 +29,50 @@ export default function Jobs() {
} = useContext(JobContext);
const { state: store, updateStore } = useContext(StoreContext);
const [quickOpen, setQuickOpen] = useState(false);
const quickOpenClick = () => setQuickOpen(!quickOpen);
const quickOpenClose = () => setQuickOpen(false);
const [jobDialogOpen, setJobDialogOpen] = useState(false);
const actions = [
{ name: "Suite", icon: <ViewCarouselIcon /> },
{ name: "Compound", icon: <ViewColumnIcon /> },
{ name: "Manual", icon: <PageviewIcon /> },
];
const quickOpenClick = (e) => {
e.preventDefault();
e.stopPropagation();
if(!store.simplifiedControls) return setQuickOpen(!quickOpen);
setJobDialogOpen(true);
}
const quickOpenClose = () => setQuickOpen(false);
const handleClickOpen = () => setJobDialogOpen(true);
const handleClose = () => setJobDialogOpen(false);
const [queued, setQueued] = useState([]);
useEffect(() => {
}, [jobState.jobs]);
return (
<div className="jobs">
{jobState.jobs.map((v, i) => (
<JobBox key={i} job={v} />
))}
<Dialog open={jobDialogOpen} onClose={handleClose} maxWidth="xs">
<DialogTitle>New Job</DialogTitle>
<DialogContent>
<span>Some Selectors</span>
<JobTestSelector queued={queued} availableTests={store.catalog} setQueued={setQueued} />
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleClose} autoFocus>
Start
</Button>
</DialogActions>
</Dialog>
<ClickAwayListener onClickAway={quickOpenClose}>
<SpeedDial
ariaLabel="New Job"
@ -47,6 +86,7 @@ export default function Jobs() {
key={action.name}
icon={action.icon}
tooltipTitle={action.name}
onClick={handleClickOpen}
/>
))}
</SpeedDial>

View file

@ -29,7 +29,7 @@ export default function Settings(props) {
const optionSettings = {
region: {
title: "Region",
options: [],
options: regions,
current: store.defaultRegion,
onSelect: (r) => updateStore({ defaultRegion: r }),
},
@ -106,7 +106,7 @@ export default function Settings(props) {
/>
</ListItem>
<ListItem button divider>
<ListItem button divider onClick={handleToggle("simplifiedControls")}>
<ListItemText primary="Simplified Controls" />
<Switch
edge="end"
@ -115,7 +115,7 @@ export default function Settings(props) {
/>
</ListItem>
<ListItem button divider>
<ListItem button divider onClick={handleToggle("focusJob")}>
<ListItemText primary="Focus New Jobs" />
<Switch
edge="end"
@ -124,6 +124,17 @@ export default function Settings(props) {
/>
</ListItem>
<ListItem button divider onClick={handleToggle("logAppDetails")}>
<ListItemText primary="Log App Details" />
<Switch
edge="end"
onChange={handleToggle("logAppDetails")}
checked={store.logAppDetails}
/>
</ListItem>
<MultiOptionDialog
id="multi-options-menu"
keepMounted

View file

@ -0,0 +1,49 @@
import { useState, useEffect } from "react";
import Box from "@mui/material/Box";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import Checkbox from "@mui/material/Checkbox";
export default function JobManualSelector(props){
const {availableTests} = props;
const [queued, setQueued] = useState([]);
useEffect(()=>{
},[availableTests]);
const queueTest = (test) => () => {
const q = [...queued];
const testIndex = q.indexOf(test);
if(testIndex === -1) q.push(test);
else q.splice(testIndex, 1);
setQueued(q);
};
return (
<Box style={{ overflow: "auto", maxHeight: 250 }}>
<List>
{availableTests.map((v, i) => (
<ListItem
key={i}
secondaryAction={<Checkbox edge="end" checked={queued.includes(v)} />}
disablePadding
onClick={queueTest(v)}
>
<ListItemButton key={i}>
<ListItemText
primary={
<span>
{v.class}#<strong>{v.name}</strong>
</span>
}
style={{ wordBreak: "break-word" }}
/>
</ListItemButton>
</ListItem>
))}
</List>
</Box>
);
}

View file

@ -0,0 +1,25 @@
import { useState, useEffect } from "react";
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 DialogTitle from "@mui/material/DialogTitle";
export default function JobTestSelector(props){
const {jobDialogOpen, handleClose, dialogTitle, testSelector} = props;
return (
<Dialog open={jobDialogOpen} onClose={handleClose} maxWidth="xs">
<DialogTitle>{dialogTitle}</DialogTitle>
<DialogContent>
{testSelector}
</DialogContent>
<DialogActions>
<Button onClick={handleClose}>Cancel</Button>
<Button onClick={handleClose} autoFocus>
Start
</Button>
</DialogActions>
</Dialog>
);
}

View file

@ -9,7 +9,7 @@ import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
import DeleteIcon from "@mui/icons-material/Delete";
import NotificationsIcon from "@mui/icons-material/Notifications";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
@ -21,41 +21,45 @@ export default function CatalogBox(props) {
name: testName,
class: testClass,
repo: testRepo,
isCompound,
type: testType
isCompound,
type: testType,
} = catalogTest;
const { state: store, updateStore } = useContext(StoreContext);
const { state: jobState, jobBuilder} = useContext(JobContext);
const { state: jobState, jobBuilder } = useContext(JobContext);
const [open, setOpen] = useState(false);
const toggleOpen = () => setOpen(!open);
function Actions() {
return (
<React.Fragment>
<IconButton aria-label="play" component="span" color="primary">
<NotificationsIcon />
</IconButton>
<IconButton color="error" aria-label="delete" component="span">
<DeleteIcon />
<IconButton color="success" aria-label="play" component="span">
<PlayArrowIcon />
</IconButton>
</React.Fragment>
);
}
return (
<Accordion expanded={open} disableGutters={true} onChange={toggleOpen}
square>
<Accordion
expanded={open}
disableGutters={true}
onChange={toggleOpen}
square
>
<AccordionSummary
style={{
backgroundColor: "rgba(0, 0, 0, .03)",
flexWrap: "wrap",
}}
>
<Typography component={"span"} style={{ wordBreak: "break-word" }}>
<Typography
component={"span"}
style={{ wordBreak: "break-word", margin: "auto 0" }}
>
{`${testClass}#`}
<Box fontWeight="bold" display="inline">
{testName}
@ -77,10 +81,12 @@ export default function CatalogBox(props) {
>
<Actions />
</Stack>
<AccordionDetails>
<Typography>{JSON.stringify(catalogTest)}</Typography>
</AccordionDetails>
</AccordionSummary>
<AccordionDetails>
<Typography component={"span"} style={{ wordBreak: "break-word" }}>
{JSON.stringify(catalogTest)}
</Typography>
</AccordionDetails>
</Accordion>
);
}

View file

@ -27,10 +27,8 @@ export default function CatalogSearch(props) {
placeholder="Search Catalog"
inputProps={{ "aria-label": `search catalog` }}
onChange={onChange}
fullWidth
/>
<IconButton type="submit" sx={{ p: "10px" }} aria-label="search">
<SearchIcon />
</IconButton>
<Divider sx={{ height: 26, m: 0.5 }} orientation="vertical" />
<IconButton
sx={{ p: "10px", mr: 0.5 }}

View file

@ -27,7 +27,7 @@ import Box from "@mui/material/Box";
const stopPropagation = (e) => e.stopPropagation() && e.preventDefault();
export default function FailingBox(props) {
const { failingTest } = props;
const { failingTest, silenceClick } = props;
const {
class: testClass,
@ -92,6 +92,7 @@ export default function FailingBox(props) {
aria-label="silence"
component="span"
color={silencedUntil ? "primary" : "default"}
onClick={silenceClick}
>
<NotificationsIcon />
</IconButton>
@ -152,14 +153,18 @@ export default function FailingBox(props) {
direction="row"
sx={{
ml: "auto",
display: { xs: "none", sm: "none", md: "flex", lg: "flex" },
mb: "auto",
mt: "auto",
display: { xs: "none", sm: "none", md: "block", lg: "block" },
}}
>
<Actions />
</Stack>
</AccordionSummary>
<AccordionDetails>
<Typography>{failedMessage}</Typography>
<Typography component={"span"} style={{ wordBreak: "break-word" }}>
{failedMessage}
</Typography>
</AccordionDetails>
</Accordion>
);

View file

@ -0,0 +1,67 @@
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 IconButton from "@mui/material/IconButton";
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 Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
export default function JobBox(props) {
const { job } = props;
const { name, status } = job;
function jobIcon() {
switch (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 />;
}
}
return (
<Accordion expanded={false} disableGutters={true} square>
<AccordionSummary
style={{
backgroundColor: "rgba(0, 0, 0, .03)",
flexWrap: "wrap",
}}
>
<Typography
component={"span"}
style={{ wordBreak: "break-word", margin: "auto 0" }}
>
{name}
</Typography>
<Stack sx={{ ml: "auto" }}>
<IconButton aria-label="retry" component="span">
{jobIcon()}
</IconButton>
</Stack>
</AccordionSummary>
</Accordion>
);
}

View file

@ -0,0 +1,47 @@
import { useState, useEffect } from "react";
import Box from "@mui/material/Box";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText";
import Checkbox from "@mui/material/Checkbox";
export default function JobTestSelector(props){
const {availableTests, queued, setQueued} = props;
useEffect(()=>{},[availableTests]);
const queueTest = (test) => () => {
const q = [...queued];
const testIndex = q.indexOf(test);
if(testIndex === -1) q.push(test);
else q.splice(testIndex, 1);
setQueued(q);
};
return (
<Box style={{ overflow: "auto", maxHeight: 250 }}>
<List>
{availableTests.map((v, i) => (
<ListItem
key={i}
secondaryAction={<Checkbox edge="end" checked={queued.includes(v)} />}
disablePadding
onClick={queueTest(v)}
>
<ListItemButton key={i}>
<ListItemText
primary={
<span>
{v.class}#<strong>{v.name}</strong>
</span>
}
style={{ wordBreak: "break-word" }}
/>
</ListItemButton>
</ListItem>
))}
</List>
</Box>
);
}

View file

@ -1,4 +1,4 @@
import { useState, useContext } from "react";
import { useState, useContext, useEffect } from "react";
import StoreContext from "../../ctx/StoreContext.jsx";
import Button from "@mui/material/Button";
@ -6,6 +6,7 @@ import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
import Dialog from "@mui/material/Dialog";
import TextField from "@mui/material/TextField";
export default function SilenceDialog(props) {
const { silence, open, onClose } = props;
@ -18,14 +19,15 @@ export default function SilenceDialog(props) {
const { state: store, updateStore } = useContext(StoreContext);
const upsertSilence = () => {
console.log("Would upsert silence", silenceEntry);
};
const handleCancel = () => onClose();
const handleOk = () => onClose(silenceEntry);
const updateSilence = (silenceType) => (e) => {
silenceEntry[silenceType] = e.target.value;
setSilenceEntry({ ...silenceEntry });
};
return (
<Dialog
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
@ -34,7 +36,30 @@ export default function SilenceDialog(props) {
>
<DialogTitle>Silence Alert</DialogTitle>
<DialogContent>
<span>{JSON.stringify(silenceEntry)}</span>
<TextField
fullWidth
label="Test Name"
variant="standard"
defaultValue={silence.name ?? ""}
onChange={updateSilence("name")}
margin="normal"
/>
<TextField
fullWidth
label="Test Method"
variant="standard"
defaultValue={silence.method ?? ""}
onChange={updateSilence("method")}
margin="normal"
/>
<TextField
fullWidth
label="Test Class"
variant="standard"
defaultValue={silence.class ?? ""}
onChange={updateSilence("class")}
margin="normal"
/>
</DialogContent>
<DialogActions>
<Button autoFocus onClick={handleCancel}>

View file

@ -7,12 +7,12 @@ import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
import DeleteIcon from "@mui/icons-material/Delete";
import NotificationsIcon from "@mui/icons-material/Notifications";
import EditIcon from "@mui/icons-material/Edit";
import Stack from "@mui/material/Stack";
export default function SilencingBox(props) {
const { silenceEntry } = props;
const { silenceEntry, editSilence, removeSilence } = props;
const {
name: testName,
@ -27,10 +27,20 @@ export default function SilencingBox(props) {
function Actions() {
return (
<React.Fragment>
<IconButton aria-label="modify" component="span" color="primary">
<NotificationsIcon />
<IconButton
aria-label="modify"
component="span"
color="primary"
onClick={editSilence}
>
<EditIcon />
</IconButton>
<IconButton color="error" aria-label="delete" component="span">
<IconButton
color="error"
aria-label="delete"
component="span"
onClick={removeSilence}
>
<DeleteIcon />
</IconButton>
</React.Fragment>
@ -64,7 +74,9 @@ export default function SilencingBox(props) {
direction="row"
sx={{
ml: "auto",
display: { xs: "none", sm: "none", md: "flex", lg: "flex" },
mb: "auto",
mt: "auto",
display: { xs: "none", sm: "none", md: "block", lg: "block" },
}}
>
<Actions />