Upgrades people!

This commit is contained in:
Dunemask 2022-08-09 04:29:10 +00:00
parent f84234150f
commit 8ad56e8d38
40 changed files with 483 additions and 379 deletions

View file

@ -1,5 +1,5 @@
import { useContext, useState } from "react";
import {useCurrentlyFailing} from "../Queries.jsx";
import { useCurrentlyFailing } from "../Queries.jsx";
import JobContext from "../ctx/JobContext.jsx";
import StoreContext from "../ctx/StoreContext.jsx";
@ -24,18 +24,22 @@ 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 SentimentSatisfiedAltIcon from '@mui/icons-material/SentimentSatisfiedAlt';
import SentimentSatisfiedAltIcon from "@mui/icons-material/SentimentSatisfiedAlt";
const drawerWidth = 250;
export default function Navbar(props) {
const { state: jobState } = useContext(JobContext);
const { state: store } = useContext(StoreContext);
const {isLoading, data: failing} = useCurrentlyFailing();
const { isLoading, data: failing } = useCurrentlyFailing();
const pages = store.pages;
const icons = [
<Badge badgeContent={(failing ?? []).length} color="error">
{(failing ?? []).length > 0 ? <WarningIcon />: <SentimentSatisfiedAltIcon/>}
{(failing ?? []).length > 0 ? (
<WarningIcon />
) : (
<SentimentSatisfiedAltIcon />
)}
</Badge>,
<NotificationsIcon />,
<Badge badgeContent={jobState.jobs.length} color="primary">

View file

@ -33,7 +33,9 @@ export default function Views() {
<Route
exact
path="/"
element={<Navigate to={`/qualiteer/${store.defaultPage}`} replace />}
element={
<Navigate to={`/qualiteer/${store.defaultPage}`} replace />
}
/>
<Route path="/qualiteer/failing" element={<Failing />} />
<Route path="/qualiteer/alerting" element={<Alerting />} />

View file

@ -1,5 +1,5 @@
import React, { useState, useContext } from "react";
import {useSilencedAlerts} from "../../Queries.jsx";
import { useSilencedAlerts } from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx";
import SilencedBox from "./SilencedBox.jsx";
import SilenceDialog from "./SilenceDialog.jsx";
@ -18,13 +18,9 @@ import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box";
export default function Alerting() {
const {
updateStore,
silenceRequest,
} = useContext(StoreContext);
const { updateStore, silenceRequest } = useContext(StoreContext);
const {isLoading, data: silenced} = useSilencedAlerts();
const { isLoading, data: silenced } = useSilencedAlerts();
const [silenceEntry, setSilenceEntry] = useState({
open: false,
@ -61,36 +57,41 @@ const {isLoading, data: silenced} = useSilencedAlerts();
return (
<div className="alerting">
{isLoading? null : silenced.map((v, i) => (
<SilencedBox
key={i}
silenceEntry={v}
editSilence={editSilence(v)}
removeSilence={removeSilence(v)}
/>
))}
{(silenced ?? []).length === 0? (
<React.Fragment>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{flexFlow: "wrap"}}
>
{isLoading
? null
: silenced.map((v, i) => (
<SilencedBox
key={i}
silenceEntry={v}
editSilence={editSilence(v)}
removeSilence={removeSilence(v)}
/>
))}
<Typography variant="h4">No alerts silenced! </Typography> </Box>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{flexFlow: "wrap"}}
> <Typography variant="h5">Click the '+' to create a new one!
</Typography>
</Box>
{(silenced ?? []).length === 0 ? (
<React.Fragment>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{ flexFlow: "wrap" }}
>
<Typography variant="h4">No alerts silenced! </Typography>{" "}
</Box>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{ flexFlow: "wrap" }}
>
{" "}
<Typography variant="h5">
Click the '+' to create a new one!
</Typography>
</Box>
</React.Fragment>
): null}
) : null}
<Dialog
open={silenceEntry.deleteOpen}
onClose={handleDeleteClose()}

View file

@ -3,13 +3,12 @@ import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext from "../../ctx/JobContext.jsx";
import CatalogBox from "./CatalogBox.jsx";
import CatalogSearch from "./CatalogSearch.jsx";
import {useCatalogTests} from "../../Queries.jsx";
import { useCatalogTests } from "../../Queries.jsx";
import TextField from "@mui/material/TextField";
export default function Catalog() {
const { state: store, updateStore } = useContext(StoreContext);
const {isLoading, data: tests} = useCatalogTests();
const { isLoading, data: tests } = useCatalogTests();
const handleSearchChange = (e) =>
updateStore({ catalogSearch: e.target.value });
@ -29,9 +28,9 @@ export default function Catalog() {
clearOnUnmount
/>
<h6>{store.catalogSearch}</h6>
{isLoading ? null : tests.map((v, i) => (
<CatalogBox key={i} catalogTest={v} />
))}
{isLoading
? null
: tests.map((v, i) => <CatalogBox key={i} catalogTest={v} />)}
</div>
);
}

View file

@ -29,7 +29,7 @@ export default function CatalogBox(props) {
const { state: store, updateStore } = useContext(StoreContext);
const { state: jobState, jobFactory} = useContext(JobContext);
const { state: jobState, jobFactory } = useContext(JobContext);
const [open, setOpen] = useState(false);
@ -38,7 +38,7 @@ export default function CatalogBox(props) {
const runTest = (e) => {
e.preventDefault();
e.stopPropagation();
const jobId = jobFactory({testNames: [testName]});
const jobId = jobFactory({ testNames: [testName] });
if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
};

View file

@ -1,6 +1,6 @@
import { useState, useContext } from "react";
import { useNavigate } from "react-router-dom";
import {useCurrentlyFailing, useSilencedAlerts} from "../../Queries.jsx";
import { useCurrentlyFailing, useSilencedAlerts } from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext from "../../ctx/JobContext.jsx";
import SilenceDialog from "../alerting/SilenceDialog.jsx";
@ -28,8 +28,8 @@ export default function Failing() {
updateStore,
silenceRequest,
} = useContext(StoreContext);
const {isLoading, data: failing} = useCurrentlyFailing();
const {isSilencedLoading, data:silencedAlerts} = useSilencedAlerts();
const { isLoading, data: failing } = useCurrentlyFailing();
const { isSilencedLoading, data: silencedAlerts } = useSilencedAlerts();
const [silenceEntry, setSilenceEntry] = useState({ open: false });
const closeSilence = () => setSilenceEntry({ ...silenceEntry, open: false });
@ -58,22 +58,28 @@ export default function Failing() {
const failingTestsWithJobs = () => {
const silences = silencedAlerts ?? [];
for(var test of failing){
if(test.isCompound) continue;
const job = jobState.jobs.find((j)=>j.builderCache.testNames.includes(test.name))
if(job) test.job = job;
const silence = silences.find((s)=>s.name === test.name || s.class === test.class)
for (var test of failing) {
if (test.isCompound) continue;
const job = jobState.jobs.find((j) =>
j.builderCache.testNames.includes(test.name)
);
if (job) test.job = job;
const silence = silences.find(
(s) => s.name === test.name || s.class === test.class
);
if(silence) test.silencedUntil = silence;
if (silence) test.silencedUntil = silence;
}
return failing;
}
};
return (
<div className="failing">
{isLoading? null :failingTestsWithJobs().map((v, i) => (
<FailingBox key={i} failingTest={v} silenceClick={editSilence(v)} />
))}
{isLoading
? null
: failingTestsWithJobs().map((v, i) => (
<FailingBox key={i} failingTest={v} silenceClick={editSilence(v)} />
))}
<Dialog
open={retryAllOpen}
@ -100,25 +106,21 @@ export default function Failing() {
onClose={handleSilenceClose}
silence={silenceEntry}
/>
{(failing ?? []).length === 0? ( <Box
display="flex"
alignItems="center"
justifyContent="center"
>
<Typography variant="h4">No tests failing!</Typography>
</Box>
): null}
{(failing ?? []).length === 0 ? (
<Box display="flex" alignItems="center" justifyContent="center">
<Typography variant="h4">No tests failing!</Typography>
</Box>
) : null}
{(failing ?? []).length ===0? null :(
<SpeedDial
ariaLabel="Retry All"
sx={{ position: "fixed", bottom: 16, right: 16 }}
icon={<ReplayIcon />}
onClick={retryAllClick}
open={false}
/>)}
{(failing ?? []).length === 0 ? null : (
<SpeedDial
ariaLabel="Retry All"
sx={{ position: "fixed", bottom: 16, right: 16 }}
icon={<ReplayIcon />}
onClick={retryAllClick}
open={false}
/>
)}
</div>
);
}

View file

@ -48,7 +48,7 @@ export default function FailingBox(props) {
failedMessage,
isCompound,
jobStatus: testJobStatus,
job
job,
} = failingTest;
const navigate = useNavigate();
@ -78,12 +78,12 @@ export default function FailingBox(props) {
}
const retryTest = () => {
const jobId = jobFactory({testNames: [testName], isTriage: true});
const jobId = jobFactory({ testNames: [testName], isTriage: true });
if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
};
const jobOnClick = () => {
if(!job) return retryTest;
if (!job) return retryTest;
switch (job.status) {
case jobStatus.OK:
return null;
@ -103,7 +103,7 @@ export default function FailingBox(props) {
};
function jobIcon() {
if(!job) return <ReplayIcon />;
if (!job) return <ReplayIcon />;
switch (job.status) {
case jobStatus.OK:
return <CheckIcon color="success" />;

View file

@ -21,13 +21,11 @@ import DoNotDisturbIcon from "@mui/icons-material/DoNotDisturb";
import DeleteIcon from "@mui/icons-material/Delete";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
export default function JobView(props) {
const navigate = useNavigate();
const { job } = props;
const { jobFactory, jobCancel, jobDestroy } = useContext(JobContext);
const {state: store} = useContext(StoreContext);
const { state: store } = useContext(StoreContext);
const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl);
const handleClick = (event) => {
@ -51,8 +49,8 @@ export default function JobView(props) {
}
function retryJob() {
const jobId = jobFactory(job.builderCache);
if(store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
const jobId = jobFactory(job.builderCache);
if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
}
function downloadLog() {
@ -60,12 +58,11 @@ export default function JobView(props) {
download(`${job.jobId}.txt`, job.log.join("\n"));
}
function cancelJob(){
function cancelJob() {
jobCancel(job.jobId);
}
function deleteJob(){
function deleteJob() {
jobDestroy(job.jobId);
navigateToJobs();
}
@ -107,7 +104,7 @@ export default function JobView(props) {
<Toolbar disableGutters />
<JobLogView log={job.log} status={job.status} />
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={menuSelect(downloadLog)}>
<MenuItem onClick={menuSelect(downloadLog)}>
<ListItemIcon>
<DownloadIcon fontSize="small" />
</ListItemIcon>
@ -115,16 +112,27 @@ export default function JobView(props) {
</MenuItem>
<MenuItem onClick={menuSelect(retryJob)}>
<ListItemIcon>
{job.status === jobStatus.OK || job.status === jobStatus.ERROR ? <ReplayIcon fontSize="small" /> : <PlayArrowIcon fontSize="small"/>}
{job.status === jobStatus.OK || job.status === jobStatus.ERROR ? (
<ReplayIcon fontSize="small" />
) : (
<PlayArrowIcon fontSize="small" />
)}
</ListItemIcon>
<ListItemText> {job.status === jobStatus.ERROR? "Retry" : "Duplicate"}</ListItemText>
<ListItemText>
{" "}
{job.status === jobStatus.ERROR ? "Retry" : "Duplicate"}
</ListItemText>
</MenuItem>
{job.status === jobStatus.OK || job.status === jobStatus.ERROR || job.status === jobStatus.CANCELED? null : <MenuItem onClick={menuSelect(cancelJob)}>
<ListItemIcon>
<DoNotDisturbIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Cancel</ListItemText>
</MenuItem>}
{job.status === jobStatus.OK ||
job.status === jobStatus.ERROR ||
job.status === jobStatus.CANCELED ? null : (
<MenuItem onClick={menuSelect(cancelJob)}>
<ListItemIcon>
<DoNotDisturbIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Cancel</ListItemText>
</MenuItem>
)}
<MenuItem onClick={menuSelect(deleteJob)}>
<ListItemIcon>
<DeleteIcon fontSize="small" />

View file

@ -21,37 +21,42 @@ export default function Jobs() {
return (
<div className="jobs">
{jobState.jobs.length === 0? (
<React.Fragment>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{flexFlow: "wrap"}}
>
<Typography variant="h4">No jobs found! </Typography> </Box>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{flexFlow: "wrap"}}
> <Typography variant="h5">Click the '+' to start a new one!
</Typography>
</Box>
{jobState.jobs.length === 0 ? (
<React.Fragment>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{ flexFlow: "wrap" }}
>
<Typography variant="h4">No jobs found! </Typography>{" "}
</Box>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{ flexFlow: "wrap" }}
>
{" "}
<Typography variant="h5">
Click the '+' to start a new one!
</Typography>
</Box>
</React.Fragment>
): null}
) : null}
<JobBuilder />
{location.hash === "" &&
jobState.jobs.filter((j)=>!j.isPipeline).map((v, i) => (
<a
key={i}
href={`/qualiteer/jobs#${v.name}`}
style={{ textDecoration: "none" }}
>
<JobBox job={v} />
</a>
))}
jobState.jobs
.filter((j) => !j.isPipeline)
.map((v, i) => (
<a
key={i}
href={`/qualiteer/jobs#${v.name}`}
style={{ textDecoration: "none" }}
>
<JobBox job={v} />
</a>
))}
{jobState.jobs.find((job) => job.name === location.hash.slice(1)) && (
<JobView
job={jobState.jobs.find((job) => job.name === location.hash.slice(1))}

View file

@ -6,7 +6,6 @@ import DialogContent from "@mui/material/DialogContent";
function PipelineConfirm(props) {
const { cache, back, start } = props;
return (
<React.Fragment>
<DialogContent>

View file

@ -1,5 +1,5 @@
import React, { useContext } from "react";
import {usePipelineMappings} from "../../../Queries.jsx";
import { usePipelineMappings } from "../../../Queries.jsx";
import Button from "@mui/material/Button";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
@ -11,27 +11,27 @@ import Stack from "@mui/material/Stack";
function PipelineSelector(props) {
const { cache, setCache, cancel, next } = props;
const {isLoading, data: pipelineMappings} = usePipelineMappings();
const { isLoading, data: pipelineMappings } = usePipelineMappings();
const primaryMappings = () =>{
if(isLoading) return {};
const primaryMappings = () => {
if (isLoading) return {};
const primaryMappings = {};
for (var pm of pipelineMappings) {
if (!(pm[0] in primaryMappings)) primaryMappings[pm[0]] = [];
primaryMappings[pm[0]].push(pm);
}
return primaryMappings;
for (var pm of pipelineMappings) {
if (!(pm[0] in primaryMappings)) primaryMappings[pm[0]] = [];
primaryMappings[pm[0]].push(pm);
}
return primaryMappings;
};
const selectPrimary = (primarySelectedMappings) => () => {
setCache({ primarySelectedMappings });
};
const clickNext = () =>{
if(!cache.primarySelectedMappings ) return console.log("No mapping selected")
next()
}
const clickNext = () => {
if (!cache.primarySelectedMappings)
return console.log("No mapping selected");
next();
};
return (
<React.Fragment>
@ -53,11 +53,17 @@ function PipelineSelector(props) {
<Typography
component={"span"}
style={{ wordBreak: "break-word", margin: "auto 0" }}
color={(cache.primarySelectedMappings ?? [[]])[0][0] ===k ? "primary": null}
color={
(cache.primarySelectedMappings ?? [[]])[0][0] === k
? "primary"
: null
}
>
{k}
</Typography>
<Stack sx={{ ml: "auto" }}>{(primaryMappings[k] ?? []).length}</Stack>
<Stack sx={{ ml: "auto" }}>
{(primaryMappings[k] ?? []).length}
</Stack>
</AccordionSummary>
</Accordion>
))}

View file

@ -8,40 +8,48 @@ import AccordionSummary from "@mui/material/AccordionSummary";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack";
import {selectBranch, asTree, asBranches, as1d} from "../../../../lib/jobs/pipelines.js";
import {
selectBranch,
asTree,
asBranches,
as1d,
} from "../../../../lib/jobs/pipelines.js";
function PipelineTrackSelector(props) {
const { cache, setCache, back, next } = props;
const {primarySelectedMappings: primaries} = cache;
const { primarySelectedMappings: primaries } = cache;
const addTrack = (test) => {
const tracks = cache.tracks ?? []; tracks.push(selectBranch(primaries, test));
setCache({ ...cache, tracks});
const tracks = cache.tracks ?? [];
tracks.push(selectBranch(primaries, test));
setCache({ ...cache, tracks });
};
const removeTrack = (test) => {
const {tracks} = cache;
for(var i in tracks){
const { tracks } = cache;
for (var i in tracks) {
const index = tracks[i].indexOf(test);
if(index===-1) continue;
tracks[i] = tracks[i].slice(0,index);
if (index === -1) continue;
tracks[i] = tracks[i].slice(0, index);
}
setCache({ ...cache, tracks});
setCache({ ...cache, tracks });
};
const selectTrack = (test) => () => {
if(as1d(cache.tracks).includes(test)) return removeTrack(test);
if (as1d(cache.tracks).includes(test)) return removeTrack(test);
addTrack(test);
};
const getColor = (test) => as1d(cache.tracks).includes(test)
? "primary"
: null;
const getColor = (test) =>
as1d(cache.tracks).includes(test) ? "primary" : null;
const nextClick = () => {
setCache({...cache, branches: asBranches(primaries), tree: asTree(cache.tracks)});
setCache({
...cache,
branches: asBranches(primaries),
tree: asTree(cache.tracks),
});
next();
}
};
return (
<React.Fragment>