diff --git a/src/Navbar.jsx b/src/Navbar.jsx new file mode 100644 index 0000000..8b55628 --- /dev/null +++ b/src/Navbar.jsx @@ -0,0 +1,147 @@ +import { useContext, useState } from "react"; +import StoreContext from "./ctx/StoreContext.jsx"; +import JobContext from "./ctx/JobContext.jsx"; + +import { + Routes, + Route, + Link, + BrowserRouter, + Navigate, + useLocation, +} from "react-router-dom"; +import AppBar from "@mui/material/AppBar"; +import Badge, { BadgeProps } from "@mui/material/Badge"; +import { styled } from "@mui/material/styles"; +import Box from "@mui/material/Box"; +import Toolbar from "@mui/material/Toolbar"; +import IconButton from "@mui/material/IconButton"; +import Typography from "@mui/material/Typography"; +import MenuIcon from "@mui/icons-material/Menu"; +import Avatar from "@mui/material/Avatar"; +import Drawer from "@mui/material/Drawer"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import List from "@mui/material/List"; +import ListItemButton from "@mui/material/ListItemButton"; +import NotificationsIcon from "@mui/icons-material/Notifications"; +import WorkIcon from "@mui/icons-material/Work"; +import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; +import SettingsIcon from "@mui/icons-material/Settings"; +import WarningIcon from "@mui/icons-material/Warning"; +import InfoIcon from "@mui/icons-material/Info"; +// Import Pages +import Failing from "./views/Failing.jsx"; +import Alerting from "./views/Alerting.jsx"; +import Jobs from "./views/Jobs.jsx"; +import Catalog from "./views/Catalog.jsx"; +import Settings from "./views/Settings.jsx"; +import About from "./views/About.jsx"; + +const drawerWidth = 240; + +export default function Navbar(props) { + const { state: jobState } = useContext(JobContext); + const { state: store } = useContext(StoreContext); + const { inModal } = props; + const pages = store.pages; + const icons = [ + + + , + , + + + , + , + , + , + ]; + + const location = useLocation(); + const [drawerOpen, setDrawer] = useState(false); + + const toggleDrawer = () => setDrawer(!drawerOpen); + const closeDrawer = () => setDrawer(false); + + const reloadPage = () => window.location.reload(false); + + const SideBadge = styled(Badge)(({ theme }) => ({ + "& .MuiBadge-badge": { + right: -6, + top: 10, + padding: "0 4px", + }, + })); + + const navHeader = () => { + const pathStr = + location.pathname.charAt(1).toUpperCase() + location.pathname.slice(2); + if (location.pathname !== "/failing") return pathStr; + return ( + + {pathStr} + + ); + }; + + const drawerIndex = (isDrawer) => (theme) => + theme.zIndex.modal + 2 - (isDrawer ? 1 : 0); + + return ( + + + + + + + + + + + {pages.map((text, index) => ( + + {icons[index]} + + + ))} + + + + + {navHeader()} + + + + + + ); +} diff --git a/src/Views.jsx b/src/Views.jsx index 1325909..5f2fe54 100644 --- a/src/Views.jsx +++ b/src/Views.jsx @@ -1,6 +1,7 @@ import { useContext, useState } from "react"; import StoreContext from "./ctx/StoreContext.jsx"; import JobContext from "./ctx/JobContext.jsx"; +import Navbar from "./Navbar.jsx"; import { Routes, @@ -44,106 +45,9 @@ export default function Views() { const { state: jobState } = useContext(JobContext); const { state: store } = useContext(StoreContext); - const pages = ["failing", "alerting", "jobs", "catalog", "settings", "about"]; - const icons = [ - - - , - , - - - , - , - , - , - ]; - - const location = useLocation(); - const [drawerOpen, setDrawer] = useState(false); - - const toggleDrawer = () => setDrawer(!drawerOpen); - const closeDrawer = () => setDrawer(false); - - const reloadPage = () => window.location.reload(false); - - const SideBadge = styled(Badge)(({ theme }) => ({ - "& .MuiBadge-badge": { - right: -6, - top: 10, - padding: "0 4px", - }, - })); - - const navHeader = () => { - const pathStr = - location.pathname.charAt(1).toUpperCase() + location.pathname.slice(2); - if (location.pathname !== "/failing") return pathStr; - return ( - - {pathStr} - - ); - }; - return (
- theme.zIndex.drawer + 1 }} - > - - - - - - - - - - {pages.map((text, index) => ( - - {icons[index]} - - - ))} - - - - - {navHeader()} - - - - - + @@ -153,7 +57,7 @@ export default function Views() { } /> } /> } /> - } /> + } /> } /> diff --git a/src/ctx/JobContext.jsx b/src/ctx/JobContext.jsx index aa3be32..0631b08 100644 --- a/src/ctx/JobContext.jsx +++ b/src/ctx/JobContext.jsx @@ -100,8 +100,13 @@ export const JobProvider = ({ children }) => { }); } - function jobBuilder() { - mockRun(); + function jobBuilder(tests) { + if (!Array.isArray(tests)) throw Error("Error from within JobContext.jsx"); + console.log("Would run tests", tests); + } + + function retrySingle(test) { + console.log("Would retry test", test); } const context = { @@ -111,6 +116,8 @@ export const JobProvider = ({ children }) => { jobCreate, jobDelete, retryAll, + retrySingle, + jobBuilder, activeJobStates, }; const contextValue = useMemo(() => context, [state, dispatch]); diff --git a/src/ctx/StoreContext.jsx b/src/ctx/StoreContext.jsx index 87725dc..c1e921c 100644 --- a/src/ctx/StoreContext.jsx +++ b/src/ctx/StoreContext.jsx @@ -23,16 +23,16 @@ const catalogMock = new Array(10).fill(0).map((v, i) => ({ type: i % 3 ? "api" : "ui", })); -const failingMock = new Array(10).fill(0).map((v, i) => ({ +const failingMock = new Array(12).fill(0).map((v, i) => ({ class: `SomeTestClass${i % 2 ? i - 1 : i / 2}`, name: `TestThatDoesOneThing${i + 1}`, - timestamp: `2022-05-10T16:${2 + i}:33.810Z`, + timestamp: `2022-05-10T16:0${i < 10 ? i : i - 10}:33.810Z`, method: `SomeMethod`, - silencedUntil: i % 4 ? null : `2022-05-10T16:${2 + i}:33.810Z`, + silencedUntil: i % 4 ? null : `2022-05-10T16:0${i}:33.810Z`, frequency: "1hour", type: i % 3 ? "api" : "ui", dailyFails: i + 1, - screenshot: "https://example.com", + screenshot: "https://picsum.photos/1920/1080", recentResults: [1, 0, 0, 1, 0], isCompound: i % 5 ? false : true, failedMessage: `Some Test FailureMessage ${i}`, @@ -57,6 +57,7 @@ const failingMock = new Array(10).fill(0).map((v, i) => ({ })); const initialState = { + pages: ["failing", "alerting", "jobs", "catalog", "settings", "about"], intervals: [], catalog: catalogMock, failing: failingMock, @@ -85,14 +86,28 @@ export const StoreProvider = ({ children }) => { const [state, dispatch] = useReducer(reducer, initialState); function silenceRequest(silenceInfo) { - const req = {name: silenceInfo.name ?? "*", class: silenceInfo.class ?? "*", method: silenceInfo.method ?? "*", silencedUntil: silenceInfo.silencedUntil }; + const req = { + name: silenceInfo.name ?? "*", + class: silenceInfo.class ?? "*", + method: silenceInfo.method ?? "*", + silencedUntil: silenceInfo.silencedUntil, + }; console.log("Would upsert silence", req); } + function removeFailure(failure) { + const req = { + name: failure.name, + class: failure.class, + }; + console.log("Would remove failure", req); + } + const context = { state, dispatch, silenceRequest, + removeFailure, updateStore: (store) => dispatch({ type: ACTIONS.UPDATE, store }), }; const contextValue = useMemo(() => context, [state, dispatch]); diff --git a/src/views/Failing.jsx b/src/views/Failing.jsx index 04359ec..3c299e3 100644 --- a/src/views/Failing.jsx +++ b/src/views/Failing.jsx @@ -42,13 +42,6 @@ export default function Failing() { setSilenceEntry({ ...silence, open: true }); }; - /* TODO -for(var j of activeJobStates()){ -const failingTest = failing.find((f)=>f.name===j.testName); - if(!failingTest) continue; - failingTest.jobStatus= j.status; - }*/ - const [retryAllOpen, setRetryAllOpen] = useState(false); const retryAllClick = () => setRetryAllOpen(!retryAllOpen); diff --git a/src/views/Jobs.jsx b/src/views/Jobs.jsx index dab3953..cffd3b6 100644 --- a/src/views/Jobs.jsx +++ b/src/views/Jobs.jsx @@ -8,7 +8,7 @@ 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 Toolbar from "@mui/material/Toolbar"; import DialogTitle from "@mui/material/DialogTitle"; import ClickAwayListener from "@mui/material/ClickAwayListener"; @@ -24,8 +24,7 @@ export default function Jobs() { const { state: jobState, dispatch: jobDispatch, - jobUpdate, - jobCreate, + jobBuilder, } = useContext(JobContext); const { state: store, updateStore } = useContext(StoreContext); @@ -37,21 +36,25 @@ export default function Jobs() { { name: "Compound", icon: }, { name: "Manual", icon: }, ]; - + const quickOpenClick = (e) => { e.preventDefault(); e.stopPropagation(); - if(!store.simplifiedControls) return setQuickOpen(!quickOpen); + 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]); + useEffect(() => {}, [jobState.jobs]); + + const handleClose = (confirmed) => () => { + setJobDialogOpen(false); + if (!confirmed) return; + jobBuilder(queued); + }; return (
@@ -59,15 +62,20 @@ export default function Jobs() { ))} - + + New Job Some Selectors - + - - + diff --git a/src/views/Settings.jsx b/src/views/Settings.jsx index d11cc52..fde36bc 100644 --- a/src/views/Settings.jsx +++ b/src/views/Settings.jsx @@ -15,7 +15,6 @@ import Typography from "@mui/material/Typography"; export default function Settings(props) { const { state: store, updateStore } = useContext(StoreContext); const { regions } = store; - const { pages } = props; const defaultDialog = { title: "", @@ -35,7 +34,7 @@ export default function Settings(props) { }, defaultPage: { title: "Default Page", - options: pages, + options: store.pages, current: store.defaultPage, onSelect: (p) => updateStore({ defaultPage: p }), }, @@ -133,8 +132,6 @@ export default function Settings(props) { /> - - { - },[availableTests]); + useEffect(() => {}, [availableTests]); const queueTest = (test) => () => { const q = [...queued]; const testIndex = q.indexOf(test); - if(testIndex === -1) q.push(test); + if (testIndex === -1) q.push(test); else q.splice(testIndex, 1); setQueued(q); }; - - return ( - - - {availableTests.map((v, i) => ( - } - disablePadding - onClick={queueTest(v)} - > - - - {v.class}#{v.name} - - } - style={{ wordBreak: "break-word" }} - /> - - - ))} - - - ); -} \ No newline at end of file + + return ( + + + {availableTests.map((v, i) => ( + + } + disablePadding + onClick={queueTest(v)} + > + + + {v.class}#{v.name} + + } + style={{ wordBreak: "break-word" }} + /> + + + ))} + + + ); +} diff --git a/src/views/alt-comps/JobTestSelector.jsx b/src/views/alt-comps/JobTestSelector.jsx index 58009b8..05a3408 100644 --- a/src/views/alt-comps/JobTestSelector.jsx +++ b/src/views/alt-comps/JobTestSelector.jsx @@ -6,20 +6,18 @@ 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 ( - - {dialogTitle} - - {testSelector} - - - - - - - ); -} \ No newline at end of file +export default function JobTestSelector(props) { + const { jobDialogOpen, handleClose, dialogTitle, testSelector } = props; + return ( + + {dialogTitle} + {testSelector} + + + + + + ); +} diff --git a/src/views/components/CatalogBox.jsx b/src/views/components/CatalogBox.jsx index a308609..618d18d 100644 --- a/src/views/components/CatalogBox.jsx +++ b/src/views/components/CatalogBox.jsx @@ -8,7 +8,6 @@ import AccordionSummary from "@mui/material/AccordionSummary"; import Typography from "@mui/material/Typography"; import IconButton from "@mui/material/IconButton"; -import DeleteIcon from "@mui/icons-material/Delete"; import PlayArrowIcon from "@mui/icons-material/PlayArrow"; import Box from "@mui/material/Box"; @@ -33,10 +32,21 @@ export default function CatalogBox(props) { const toggleOpen = () => setOpen(!open); + const runTest = (e) => { + e.preventDefault(); + e.stopPropagation(); + jobBuilder([catalogTest]); + }; + function Actions() { return ( - + diff --git a/src/views/components/FailingBox.jsx b/src/views/components/FailingBox.jsx index 227892c..773503d 100644 --- a/src/views/components/FailingBox.jsx +++ b/src/views/components/FailingBox.jsx @@ -7,6 +7,13 @@ 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"; @@ -43,14 +50,23 @@ export default function FailingBox(props) { jobStatus: testJobStatus, } = failingTest; - const { state: jobState, retryTest, retryJobStatus } = useContext(JobContext); + const { state: jobState, retrySingle } = useContext(JobContext); - const { state: store, updateStore } = useContext(StoreContext); + 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"; @@ -80,9 +96,11 @@ export default function FailingBox(props) { function Actions() { return ( - - - + + + + + {jobIcon()} @@ -96,7 +114,12 @@ export default function FailingBox(props) { > - + @@ -125,21 +148,52 @@ export default function FailingBox(props) { horizontal: "left", }} > + + + Remove failure? + + + This will remove 1 test from the database + + + + + + + + {`${testClass}#`} {testName}{" "}
- - {recentResults.map( - (v, i) => - (v && ) || ( - - ) - )} - - {isCompound && } +
+ + {recentResults.map( + (v, i) => + (v && ) || ( + + ) + )} + + {isCompound && } +
diff --git a/src/views/components/JobTestSelector.jsx b/src/views/components/JobTestSelector.jsx index 715f62a..cf6dbce 100644 --- a/src/views/components/JobTestSelector.jsx +++ b/src/views/components/JobTestSelector.jsx @@ -6,42 +6,44 @@ 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]); +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); + if (testIndex === -1) q.push(test); else q.splice(testIndex, 1); setQueued(q); }; - - return ( - - - {availableTests.map((v, i) => ( - } - disablePadding - onClick={queueTest(v)} - > - - - {v.class}#{v.name} - - } - style={{ wordBreak: "break-word" }} - /> - - - ))} - - - ); -} \ No newline at end of file + + return ( + + + {availableTests.map((v, i) => ( + + } + disablePadding + onClick={queueTest(v)} + > + + + {v.class}#{v.name} + + } + style={{ wordBreak: "break-word" }} + /> + + + ))} + + + ); +} diff --git a/src/views/components/MultiOptionDialog.jsx b/src/views/components/MultiOptionDialog.jsx index c209702..9d6bd4e 100644 --- a/src/views/components/MultiOptionDialog.jsx +++ b/src/views/components/MultiOptionDialog.jsx @@ -1,5 +1,7 @@ import { useState, useRef, useEffect } from "react"; +import useMediaQuery from "@mui/material/useMediaQuery"; +import { useTheme } from "@mui/material/styles"; import Button from "@mui/material/Button"; import DialogTitle from "@mui/material/DialogTitle"; import DialogContent from "@mui/material/DialogContent"; @@ -8,12 +10,14 @@ import Dialog from "@mui/material/Dialog"; import RadioGroup from "@mui/material/RadioGroup"; import Radio from "@mui/material/Radio"; import FormControlLabel from "@mui/material/FormControlLabel"; +import Toolbar from "@mui/material/Toolbar"; export default function MultiOptionDialog(props) { const { dialog: dialogProp, onClose, open, ...other } = props; const [value, setValue] = useState(dialogProp.current); const [dialog, setDialog] = useState(dialogProp); - + const theme = useTheme(); + const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); const radioGroupRef = useRef(null); useEffect(() => { @@ -35,12 +39,17 @@ export default function MultiOptionDialog(props) { return ( + {dialog.title} { @@ -30,10 +34,16 @@ export default function SilenceDialog(props) { return ( + Silence Alert