Added display for triggers
This commit is contained in:
parent
bc7e0767d6
commit
bb934ee859
13 changed files with 154 additions and 28 deletions
|
@ -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
7
package-lock.json
generated
|
@ -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",
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 = "/";
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
45
src/views/catalog/CatalogItemDetails.jsx
Normal file
45
src/views/catalog/CatalogItemDetails.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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 &&
|
||||||
|
|
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue