Added display for triggers

This commit is contained in:
Dunemask 2022-10-17 08:00:03 -04:00
parent bc7e0767d6
commit bb934ee859
13 changed files with 154 additions and 28 deletions

View file

@ -10,7 +10,7 @@ CREATE TABLE catalog (
created TIMESTAMP NOT NULL DEFAULT now(), created TIMESTAMP NOT NULL DEFAULT now(),
mr varchar(255) DEFAULT NULL, mr varchar(255) DEFAULT NULL,
tags varchar(255)[] DEFAULT NULL, tags varchar(255)[] DEFAULT NULL,
crons varchar(127) DEFAULT NULL, crons varchar(127)[] DEFAULT NULL,
env varchar(31)[] DEFAULT NULL, env varchar(31)[] DEFAULT NULL,
regions varchar(15)[] DEFAULT NULL, regions varchar(15)[] DEFAULT NULL,
triggers varchar(255)[] DEFAULT NULL, triggers varchar(255)[] DEFAULT NULL,

7
package-lock.json generated
View file

@ -15,7 +15,6 @@
"dotenv": "^16.0.2", "dotenv": "^16.0.2",
"express": "^4.18.1", "express": "^4.18.1",
"figlet": "^1.5.2", "figlet": "^1.5.2",
"lodash": "^4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"path": "^0.12.7", "path": "^0.12.7",
"pg-promise": "^10.12.0", "pg-promise": "^10.12.0",
@ -4053,7 +4052,8 @@
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
}, },
"node_modules/lodash.defaults": { "node_modules/lodash.defaults": {
"version": "4.2.0", "version": "4.2.0",
@ -9241,7 +9241,8 @@
"lodash": { "lodash": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
}, },
"lodash.defaults": { "lodash.defaults": {
"version": "4.2.0", "version": "4.2.0",

View file

@ -9,6 +9,7 @@ import ViewColumnIcon from "@mui/icons-material/ViewColumn";
import PendingIcon from "@mui/icons-material/Pending"; import PendingIcon from "@mui/icons-material/Pending";
import VisibilityIcon from "@mui/icons-material/Visibility"; import VisibilityIcon from "@mui/icons-material/Visibility";
import DoNotDisturbIcon from "@mui/icons-material/DoNotDisturb"; import DoNotDisturbIcon from "@mui/icons-material/DoNotDisturb";
import TimerIcon from "@mui/icons-material/Timer";
import ReplayIcon from "@mui/icons-material/Replay"; import ReplayIcon from "@mui/icons-material/Replay";
function statusIcon(status) { function statusIcon(status) {
@ -25,6 +26,8 @@ function statusIcon(status) {
return <DoNotDisturbIcon color="warning" />; return <DoNotDisturbIcon color="warning" />;
case jobStatus.QUEUED: case jobStatus.QUEUED:
return <ViewColumnIcon color="secondary" />; return <ViewColumnIcon color="secondary" />;
case jobStatus.TIMER:
return <TimerIcon color="primary" />;
default: default:
return <ReplayIcon />; return <ReplayIcon />;
} }
@ -42,6 +45,7 @@ export function useJobExtra() {
function pipelineIcon(pl) { function pipelineIcon(pl) {
const jobStatuses = pipelineJobs(pl).map(({ status }) => status); const jobStatuses = pipelineJobs(pl).map(({ status }) => status);
if (pl.pendingTriggers.length > 0) return statusIcon(jobStatus.TIMER);
if (jobStatuses.includes(jobStatus.ERROR)) if (jobStatuses.includes(jobStatus.ERROR))
return statusIcon(jobStatus.ERROR); return statusIcon(jobStatus.ERROR);
if (jobStatuses.includes(jobStatus.ACTIVE)) if (jobStatuses.includes(jobStatus.ACTIVE))

View file

@ -37,15 +37,26 @@ export function usePipelineCore() {
const onPipelineTrigger = (p) => { const onPipelineTrigger = (p) => {
const { triggers } = p; const { triggers } = p;
for (var t in triggers) { for (const t in triggers) {
if (t === "__testDelay") continue; if (t === "__testDelay") continue;
const delay = triggers[t].__testDelay ?? 0; const delay = triggers[t].__testDelay ?? 0;
delete triggers[t].__testDelay; delete triggers[t].__testDelay;
const plTrigger = { ...p, triggers: triggers[t], __test: t }; const plTrigger = { ...p, triggers: triggers[t], __test: t };
const jobReq = { ...plReq, pipeline: plTrigger }; const jobReq = { ...plReq, pipeline: plTrigger };
const timer = setTimeout(() => pipelineJob(pl, jobReq), delay);
const triggerAt = Date.now() + delay; const triggerAt = Date.now() + delay;
pl.pendingTriggers.push({ testName: t, timer, triggerAt }); const testName = t;
function removeTrigger() {
const i = pl.pendingTriggers.findIndex((pt) => pt.testName === t);
if (i < 0) return;
pl.pendingTriggers.splice(i, 1);
}
function launchTrigger() {
pipelineJob(pl, jobReq);
removeTrigger();
}
const doTrigger = { removeTrigger, launchTrigger };
const timer = setTimeout(launchTrigger, delay);
pl.pendingTriggers.push({ testName, timer, triggerAt, ...doTrigger });
} }
}; };
const started = initiator.newPipelineJob( const started = initiator.newPipelineJob(

View file

@ -5,5 +5,6 @@ export const jobStatus = {
CANCELED: "c", CANCELED: "c",
ACTIVE: "a", ACTIVE: "a",
ERROR: "e", ERROR: "e",
TIMER: "t",
}; };
export const socketUrl = "/"; export const socketUrl = "/";

View file

@ -73,7 +73,9 @@ export default function Alerting() {
justifyContent="center" justifyContent="center"
sx={{ flexFlow: "wrap" }} sx={{ flexFlow: "wrap" }}
> >
<Typography variant="h4">No alerts silenced! </Typography>{" "} <Typography variant="h4" sx={{ textAlign: "center" }}>
No alerts silenced!{" "}
</Typography>
</Box> </Box>
<Box <Box
display="flex" display="flex"
@ -81,8 +83,7 @@ export default function Alerting() {
justifyContent="center" justifyContent="center"
sx={{ flexFlow: "wrap" }} sx={{ flexFlow: "wrap" }}
> >
{" "} <Typography variant="h5" sx={{ textAlign: "center" }}>
<Typography variant="h5">
Click the '+' to create a new one! Click the '+' to create a new one!
</Typography> </Typography>
</Box> </Box>

View file

@ -19,6 +19,8 @@ import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import { asTree, asBranches, as1d } from "@qltr/util/pipelines.js"; import { asTree, asBranches, as1d } from "@qltr/util/pipelines.js";
import CatalogItemDetails from "./CatalogItemDetails.jsx";
export default function CatalogBox(props) { export default function CatalogBox(props) {
const { catalogTest } = props; const { catalogTest } = props;
@ -129,9 +131,7 @@ export default function CatalogBox(props) {
</Stack> </Stack>
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<Typography component={"span"} style={{ wordBreak: "break-word" }}> <CatalogItemDetails catalogTest={catalogTest} />
{"Test info"}
</Typography>
</AccordionDetails> </AccordionDetails>
</Accordion> </Accordion>
); );

View file

@ -0,0 +1,45 @@
import Typography from "@mui/material/Typography";
export default function CatalogItemDetails(props) {
const { catalogTest } = props;
const {
name: testName,
class: testClass,
isPipeline,
description,
image,
crons,
regions,
env,
tags,
projects,
} = catalogTest;
return (
<Typography component={"div"} style={{ wordBreak: "break-word" }}>
<div sx={{ display: "inline-flex" }}>
<span style={{ fontWeight: "bold" }}>Image: </span>
{image}
</div>
<div sx={{ display: "inline-flex" }}>
<span style={{ fontWeight: "bold" }}>Env: </span>
{env.join(", ")}
</div>
<div sx={{ display: "inline-flex" }}>
<span style={{ fontWeight: "bold" }}>Regions: </span>
{regions.join(", ")}
</div>
<div sx={{ display: "inline-flex" }}>
<span style={{ fontWeight: "bold" }}>Crons: </span>
{JSON.stringify(crons)}
</div>
<div sx={{ display: "inline-flex" }}>
<span style={{ fontWeight: "bold" }}>Projects: </span>
{projects.join(", ")}
</div>
<div style={{ fontStyle: "italic" }}>
{description ?? "No Description Provided!"}
</div>
</Typography>
);
}

View file

@ -90,7 +90,9 @@ export default function Failing() {
<FailingRetry failing={failsLoading ? [] : failing} /> <FailingRetry failing={failsLoading ? [] : failing} />
{!failsLoading && failing.length === 0 && ( {!failsLoading && failing.length === 0 && (
<Box display="flex" alignItems="center" justifyContent="center"> <Box display="flex" alignItems="center" justifyContent="center">
<Typography variant="h4">No tests failing!</Typography> <Typography variant="h4" sx={{ textAlign: "center" }}>
No tests failing!
</Typography>
</Box> </Box>
)} )}
{!failsLoading && {!failsLoading &&

View file

@ -1,5 +1,6 @@
import React, { useContext } from "react"; import React, { useContext, useState, useEffect } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import moment from "moment";
import { useJobCore, jobStatus } from "@qltr/jobcore"; import { useJobCore, jobStatus } from "@qltr/jobcore";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
@ -38,17 +39,36 @@ function JobPipelineDisplay(props) {
const nav = useNavigate(); const nav = useNavigate();
const [anchorEl, setAnchorEl] = React.useState(null); const [time, setTime] = useState(Date.now());
const [anchorEl, setAnchorEl] = useState(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const handleClick = (event) => setAnchorEl(event.currentTarget); const handleClick = (event) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null); const handleClose = () => setAnchorEl(null);
const branches = selectedPipelineBranches(pipeline);
const pipelineTriggers = branches.map(({ name }) =>
pipeline.pendingTriggers.find(({ testName }) => testName === name)
);
useEffect(() => {
const interval = setInterval(() => setTime(Date.now()), 1000);
return () => clearInterval(interval);
}, []);
const selectJob = (testName) => () => { const selectJob = (testName) => () => {
const pt = pipeline.pendingTriggers.find(
({ testName }) => testName === name
);
if (pt) return selectTimer(pt);
const job = findPipelineJobByTestName(pipeline, testName); const job = findPipelineJobByTestName(pipeline, testName);
if (!job) return; if (!job) return;
toJob(job.jobId); toJob(job.jobId);
}; };
const selectTimer = (pt) => {
console.log("Selected timer:", pt);
};
function cancelPipeline() { function cancelPipeline() {
pipelineCancel(pipeline.id); pipelineCancel(pipeline.id);
} }
@ -69,12 +89,22 @@ function JobPipelineDisplay(props) {
} }
function boxIcon(name) { function boxIcon(name) {
if (pipeline.pendingTriggers.find(({ testName }) => testName === name))
return jobIcon({ status: jobStatus.TIMER });
if (pipeline.isCanceled) return <DoNotDisturbIcon color="warning" />; if (pipeline.isCanceled) return <DoNotDisturbIcon color="warning" />;
const job = findPipelineJobByTestName(pipeline, name); const job = findPipelineJobByTestName(pipeline, name);
if (!job) return <ViewColumnIcon color="secondary" />; if (!job) return <ViewColumnIcon color="secondary" />;
return jobIcon(job); return jobIcon(job);
} }
function timerDisplay(name) {
const pt = pipeline.pendingTriggers.find(
({ testName }) => testName === name
);
if (!pt) return;
return moment(moment(pt.triggerAt).diff(moment())).format("mm:ss");
}
return ( return (
<Box> <Box>
<AppBar <AppBar
@ -91,7 +121,18 @@ function JobPipelineDisplay(props) {
<IconButton onClick={() => nav(-1)}> <IconButton onClick={() => nav(-1)}>
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButton>
<Typography variant="h6" sx={{ ml: "auto", mr: "auto" }}> <Typography
variant="h6"
component="span"
sx={{
ml: "auto",
mr: "auto",
textOverflow: "ellipsis",
display: "block",
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
{pipeline.id} {pipeline.id}
</Typography> </Typography>
<IconButton onClick={handleClick}> <IconButton onClick={handleClick}>
@ -101,7 +142,7 @@ function JobPipelineDisplay(props) {
</Box> </Box>
</AppBar> </AppBar>
<Toolbar disableGutters /> <Toolbar disableGutters />
{selectedPipelineBranches(pipeline).map((track, i) => ( {branches.map((track, i) => (
<React.Fragment key={i}> <React.Fragment key={i}>
<Typography variant="h6">{i + 1}</Typography> <Typography variant="h6">{i + 1}</Typography>
<Box> <Box>
@ -125,11 +166,19 @@ function JobPipelineDisplay(props) {
> >
{test.name} {test.name}
</Typography> </Typography>
<Stack sx={{ ml: "auto" }}> <div style={{ marginLeft: "auto", display: "inline-flex" }}>
<IconButton aria-label="retry" component="span"> <Typography
{boxIcon(test.name)} component={"span"}
</IconButton> style={{ wordBreak: "break-word", margin: "auto 0" }}
</Stack> >
{timerDisplay(test.name)}
</Typography>
<Stack sx={{ ml: "auto" }}>
<IconButton aria-label="retry" component="span">
{boxIcon(test.name)}
</IconButton>
</Stack>
</div>
</AccordionSummary> </AccordionSummary>
</Accordion> </Accordion>
))} ))}

View file

@ -91,7 +91,7 @@ export default function JobPipelinePendingView(props) {
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButton>
<Typography variant="h6" component="span" sx={{ ml: "auto" }}> <Typography variant="h6" component="span" sx={{ ml: "auto" }}>
{job.name} {job.jobId}
</Typography> </Typography>
{job.isPipeline && ( {job.isPipeline && (
<IconButton> <IconButton>

View file

@ -91,8 +91,18 @@ export default function JobView(props) {
<IconButton onClick={() => nav(-1)}> <IconButton onClick={() => nav(-1)}>
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButton>
<Typography variant="h6" component="span" sx={{ ml: "auto" }}> <Typography
{job.name} variant="h6"
component="span"
sx={{
ml: "auto",
textOverflow: "ellipsis",
display: "block",
whiteSpace: "nowrap",
overflow: "hidden",
}}
>
{job.jobId}
</Typography> </Typography>
{job.isPipeline && ( {job.isPipeline && (
<IconButton> <IconButton>

View file

@ -55,7 +55,9 @@ export default function Jobs() {
justifyContent="center" justifyContent="center"
sx={{ flexFlow: "wrap" }} sx={{ flexFlow: "wrap" }}
> >
<Typography variant="h4">No jobs found!</Typography> <Typography variant="h4" sx={{ textAlign: "center" }}>
No jobs found!
</Typography>
</Box> </Box>
<Box <Box
display="flex" display="flex"
@ -63,7 +65,7 @@ export default function Jobs() {
justifyContent="center" justifyContent="center"
sx={{ flexFlow: "wrap" }} sx={{ flexFlow: "wrap" }}
> >
<Typography variant="h5"> <Typography variant="h5" sx={{ textAlign: "center" }}>
Click the '+' to start a new one! Click the '+' to start a new one!
</Typography> </Typography>
</Box> </Box>