From 468437b5d036a99ba39f6405f65e0a5e29d01d0d Mon Sep 17 00:00:00 2001 From: Dunemask Date: Wed, 22 Jun 2022 00:47:19 +0000 Subject: [PATCH] Stable Modification Point --- package-lock.json | 24 +----- package.json | 2 +- src/Views.jsx | 43 ++++++----- src/ctx/JobContext.jsx | 26 ++++++- src/ctx/StoreContext.jsx | 31 +++++++- src/views/Alerting.jsx | 74 +++++++++++++++---- src/views/Catalog.jsx | 4 + src/views/Failing.jsx | 31 +++++++- src/views/Jobs.jsx | 50 +++++++++++-- src/views/Settings.jsx | 17 ++++- src/views/alt-comps/JobManualSelector.jsx | 49 ++++++++++++ src/views/alt-comps/JobTestSelector.jsx | 25 +++++++ src/views/components/CatalogBox.jsx | 40 +++++----- src/views/components/CatalogSearch.jsx | 4 +- src/views/components/FailingBox.jsx | 11 ++- src/views/components/JobBox.jsx | 67 +++++++++++++++++ src/views/components/JobTestSelector.jsx | 47 ++++++++++++ src/views/components/SilenceDialog.jsx | 37 ++++++++-- .../{SilencingBox.jsx => SilencedBox.jsx} | 24 ++++-- 19 files changed, 500 insertions(+), 106 deletions(-) create mode 100644 src/views/alt-comps/JobManualSelector.jsx create mode 100644 src/views/alt-comps/JobTestSelector.jsx create mode 100644 src/views/components/JobBox.jsx create mode 100644 src/views/components/JobTestSelector.jsx rename src/views/components/{SilencingBox.jsx => SilencedBox.jsx} (75%) diff --git a/package-lock.json b/package-lock.json index 1479e7e..4e8ced2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "path": "^0.12.7", "pg-promise": "^10.11.1", "postgres-migrations": "^5.3.0", + "rabbiteer": "gitlab:Dunemask/rabbiteer", "react-router-dom": "^6.3.0", "socket.io": "^4.4.1", "socket.io-client": "^4.4.1", @@ -9289,20 +9290,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -26662,13 +26649,6 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -31327,7 +31307,7 @@ }, "rabbiteer": { "version": "git+ssh://git@gitlab.com/Dunemask/rabbiteer.git#6bb6cc26ca7ea7f4eb934529305ebb8ae0b26369", - "from": "rabbiteer@gitlab:Dunemask/rabbiteer", + "from": "rabbiteer@git+https://gitlab.com/Dunemask/rabbiteer.git", "optional": true, "requires": { "amqplib": "^0.8.0" diff --git a/package.json b/package.json index 9e49ce7..5648d3b 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "start:dev": "nodemon dist/app.js", "start:dev:replit": "npm run start:dev & npm run start:react:replit", "start:react": "react-scripts start", - "start:react:replit": "DANGEROUSLY_DISABLE_HOST_CHECK=true node --max-old-space-size=512 node_modules/.bin/react-scripts start", + "start:react:replit": "CHOKIDAR_USEPOLLING=true CHOKIDAR_INTERVAL=8000 DANGEROUSLY_DISABLE_HOST_CHECK=true node node_modules/.bin/react-scripts start", "test": "node tests/index.js", "test:api": "node tests/api.js", "test:dev": "nodemon tests/index.js" diff --git a/src/Views.jsx b/src/Views.jsx index e3a3227..1325909 100644 --- a/src/Views.jsx +++ b/src/Views.jsx @@ -1,5 +1,7 @@ import { useContext, useState } from "react"; -import * as React from "react"; +import StoreContext from "./ctx/StoreContext.jsx"; +import JobContext from "./ctx/JobContext.jsx"; + import { Routes, Route, @@ -36,25 +38,28 @@ import Catalog from "./views/Catalog.jsx"; import Settings from "./views/Settings.jsx"; import About from "./views/About.jsx"; -const pages = ["failing", "alerting", "jobs", "catalog", "settings", "about"]; -const icons = [ - - - , - , - - - , - , - , - , -]; - const drawerWidth = 240; 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] = React.useState(false); + const [drawerOpen, setDrawer] = useState(false); const toggleDrawer = () => setDrawer(!drawerOpen); const closeDrawer = () => setDrawer(false); @@ -74,7 +79,11 @@ export default function Views() { location.pathname.charAt(1).toUpperCase() + location.pathname.slice(2); if (location.pathname !== "/failing") return pathStr; return ( - + {pathStr} ); diff --git a/src/ctx/JobContext.jsx b/src/ctx/JobContext.jsx index 7109205..aa3be32 100644 --- a/src/ctx/JobContext.jsx +++ b/src/ctx/JobContext.jsx @@ -15,9 +15,16 @@ const ACTIONS = { UPDATE: "u", DELETE: "d", }; +/**/ +const jobMock = Object.values(jobStatus).map((v, i) => ({ + name: `Job${i + 1}`, + test: `someTestName${i + 1}`, + status: v, + exitcode: 0, +})); const initialState = { - jobs: [], + jobs: jobMock, }; /* { @@ -80,7 +87,22 @@ export const JobProvider = ({ children }) => { console.log("Would return all active job states"); } - function jobBuilder() {} + function mockRun() { + dispatch({ + job: { + name: "Job1", + test: "someTestName", + log: [], + status: jobStatus.OK, + exitcode: 0, + }, + action: ACTIONS.CREATE, + }); + } + + function jobBuilder() { + mockRun(); + } const context = { state, diff --git a/src/ctx/StoreContext.jsx b/src/ctx/StoreContext.jsx index aad6aca..87725dc 100644 --- a/src/ctx/StoreContext.jsx +++ b/src/ctx/StoreContext.jsx @@ -7,10 +7,27 @@ const ACTIONS = { UPDATE: "u", }; +const silencedMock = new Array(10).fill(0).map((v, i) => ({ + name: `Test${i + 1}`, + class: `SomeTestClass${i % 2 ? i - 1 : i / 2}`, + method: "someMethod", + id: Date.now(), + silencedUntil: `2022-05-10T16:${2 + i}:33.810Z`, +})); + +const catalogMock = new Array(10).fill(0).map((v, i) => ({ + name: `Test${i + 1}`, + class: `SomeTestClass${i % 2 ? i - 1 : i / 2}`, + repo: "Repo", + isCompound: i % 5 ? false : true, + type: i % 3 ? "api" : "ui", +})); + const failingMock = new Array(10).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`, + method: `SomeMethod`, silencedUntil: i % 4 ? null : `2022-05-10T16:${2 + i}:33.810Z`, frequency: "1hour", type: i % 3 ? "api" : "ui", @@ -41,12 +58,14 @@ const failingMock = new Array(10).fill(0).map((v, i) => ({ const initialState = { intervals: [], - catalog: [], - failing: failngMock, + catalog: catalogMock, + failing: failingMock, + silenced: silencedMock, regions: [], catalogSearch: "", focusJob: false, - simplifiedControls: false, + simplifiedControls: true, + logAppDetails: true, defaultRegion: "us", // Local Store defaultPage: "failing", // Local Store }; @@ -65,9 +84,15 @@ const reducer = (state, action) => { 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 }; + console.log("Would upsert silence", req); + } + const context = { state, dispatch, + silenceRequest, updateStore: (store) => dispatch({ type: ACTIONS.UPDATE, store }), }; const contextValue = useMemo(() => context, [state, dispatch]); diff --git a/src/views/Alerting.jsx b/src/views/Alerting.jsx index 1730a57..431b03f 100644 --- a/src/views/Alerting.jsx +++ b/src/views/Alerting.jsx @@ -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 (
+ {store.silenced.map((v, i) => ( + + ))} - Silence Alert - + Resume Alerting + Are you sure you want to resume alerting? - - + + +
{store.catalogSearch}
+ {store.catalog.map((v, i) => ( + + ))}
); } diff --git a/src/views/Failing.jsx b/src/views/Failing.jsx index 533a9ad..04359ec 100644 --- a/src/views/Failing.jsx +++ b/src/views/Failing.jsx @@ -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 (
{failing.map((v, i) => ( - + ))} f.name===j.testName); - + setQuickOpen(!quickOpen); - const quickOpenClose = () => setQuickOpen(false); + const [jobDialogOpen, setJobDialogOpen] = useState(false); const actions = [ { name: "Suite", icon: }, { name: "Compound", icon: }, { name: "Manual", icon: }, ]; + + 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 (
+ {jobState.jobs.map((v, i) => ( + + ))} + + + New Job + + Some Selectors + + + + + + + + ))} diff --git a/src/views/Settings.jsx b/src/views/Settings.jsx index 4861ee6..d11cc52 100644 --- a/src/views/Settings.jsx +++ b/src/views/Settings.jsx @@ -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) { /> - + - + + + + + + + + { + },[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 ( + + + {availableTests.map((v, i) => ( + } + disablePadding + onClick={queueTest(v)} + > + + + {v.class}#{v.name} + + } + style={{ wordBreak: "break-word" }} + /> + + + ))} + + + ); +} \ No newline at end of file diff --git a/src/views/alt-comps/JobTestSelector.jsx b/src/views/alt-comps/JobTestSelector.jsx new file mode 100644 index 0000000..58009b8 --- /dev/null +++ b/src/views/alt-comps/JobTestSelector.jsx @@ -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 ( + + {dialogTitle} + + {testSelector} + + + + + + + ); +} \ No newline at end of file diff --git a/src/views/components/CatalogBox.jsx b/src/views/components/CatalogBox.jsx index 483d953..a308609 100644 --- a/src/views/components/CatalogBox.jsx +++ b/src/views/components/CatalogBox.jsx @@ -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 ( - - - - - + + ); } return ( - + - + {`${testClass}#`} {testName} @@ -77,10 +81,12 @@ export default function CatalogBox(props) { > - - {JSON.stringify(catalogTest)} - + + + {JSON.stringify(catalogTest)} + + ); } diff --git a/src/views/components/CatalogSearch.jsx b/src/views/components/CatalogSearch.jsx index d0e9c26..58b4115 100644 --- a/src/views/components/CatalogSearch.jsx +++ b/src/views/components/CatalogSearch.jsx @@ -27,10 +27,8 @@ export default function CatalogSearch(props) { placeholder="Search Catalog" inputProps={{ "aria-label": `search catalog` }} onChange={onChange} + fullWidth /> - - - 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} > @@ -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" }, }} > - {failedMessage} + + {failedMessage} + ); diff --git a/src/views/components/JobBox.jsx b/src/views/components/JobBox.jsx new file mode 100644 index 0000000..bb97db4 --- /dev/null +++ b/src/views/components/JobBox.jsx @@ -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 ; + case jobStatus.ERROR: + return ; + case jobStatus.PENDING: + return ; + case jobStatus.ACTIVE: + return ; + case jobStatus.CANCELED: + return ; + case jobStatus.QUEUED: + return ; + default: + return ; + } + } + + return ( + + + + {name} + + + + {jobIcon()} + + + + + ); +} diff --git a/src/views/components/JobTestSelector.jsx b/src/views/components/JobTestSelector.jsx new file mode 100644 index 0000000..715f62a --- /dev/null +++ b/src/views/components/JobTestSelector.jsx @@ -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 ( + + + {availableTests.map((v, i) => ( + } + disablePadding + onClick={queueTest(v)} + > + + + {v.class}#{v.name} + + } + style={{ wordBreak: "break-word" }} + /> + + + ))} + + + ); +} \ No newline at end of file diff --git a/src/views/components/SilenceDialog.jsx b/src/views/components/SilenceDialog.jsx index cae9a5e..c2353a8 100644 --- a/src/views/components/SilenceDialog.jsx +++ b/src/views/components/SilenceDialog.jsx @@ -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 ( Silence Alert - {JSON.stringify(silenceEntry)} + + +