Super Pipelines and display

This commit is contained in:
Dunemask 2022-08-09 14:07:53 +00:00
parent 8ad56e8d38
commit c05502f15c
8 changed files with 223 additions and 75 deletions

View file

@ -8,58 +8,60 @@ const pipelineMapping = [
]; ];
const buildCommon = (jobRequest) => { const buildCommon = (jobRequest) => {
const { testName } = jobRequest; const { isTriage, ignore, region, testNames } = jobRequest;
if (!testName) throw Error("'testName' must be provided!"); const command = [baseCommand, suiteEntry];
const command = [baseCommand, suiteEntry, `test=${testName}`];
// Apply Common Flags // Apply Common Flags
command.push("isRetry=false"); command.push(`isTriage=${isTriage}`);
if(ignore && ignore.length > 0) console.log("Would ignore", ignore);
if(region)
command.push(`region=${region}`);
// Return new request // Return new request
return { ...jobRequest, command }; return { ...jobRequest, command };
}; };
const buildSingle = (jobReq) => jobReq; const buildManual = (jobReq) => {
const {testNames} = jobReq;
if(testNames.length > 1) throw Error("Currently only 1 test can be selected!");
const buildMarker = (jobReq) => {}; command.push(`test=${testNames[0]}`);
const buildProject = (jobReq) => {};
const pipelineMaxLife = (testName) => {
const pipelines = pipelineMapping
.filter((m) => m.pipeline.find((t) => t.name === testName))
.map((m) => m.pipeline);
return Math.max(pipelines.map((p) => p.length)) + 1;
};
const buildCompound = (jobReq, socketId) => {
const { testName, command } = jobReq;
const { pipeline } = jobReq;
if (pipeline) {
pipeline.dashboardSocketId = socketId;
const pipelineArg = Buffer.from(JSON.stringify(pipeline), "utf8").toString(
"base64"
);
command.push(`pipeline=${pipelineArg}`);
}
return { ...jobReq, command }; return { ...jobReq, command };
}; };
function nextCompound(previousTest) {} const buildTags = (jobReq) => {
const {command, tags, testNames} = jobReq;
if(testNames && testNames.length > 0){
return console.log("Would run tags as manual");
}
const arg = Buffer.from(JSON.stringify(tags), "utf8").toString(
"base64"
);
command.push(`tags=${arg}`);
return {...jobReq, command};
};
const buildPipeline = (jobReq, socketId) => {
const { command, pipeline } = jobReq;
const {__test: test} = pipeline;
if(!test) throw Error("__test is required for pipeline jobs!");
pipeline.dashboardSocketId = socketId;
const arg = Buffer.from(JSON.stringify(pipeline), "utf8").toString(
"base64"
);
command.push(`pipeline=${arg}`);
command.push(`test=${test}`);
return { ...jobReq, command };
};
export default function jobBuilder(jobRequest, id) { export default function jobBuilder(jobRequest, id) {
const jobReq = buildCommon(jobRequest, id); const jobReq = buildCommon(jobRequest, id);
switch (jobRequest.type) { const {pipeline, testNames, tags } = jobReq;
case "single": if(pipeline)
return buildSingle(jobReq); return buildPipeline(jobReq, id);
case "marker": else if(tags)
return buildMarker(jobReq); return buildTags(jobReq);
case "project": else if(testNames)
return buildProject(jobReq); return buildManual(jobReq); //TODO currently does nothing
case "compound": else throw Error("At least 1 'pipeline or tags or testNames' is required! ");
return buildCompound(jobReq, id);
default:
throw Error("No Job Request Type Specified!");
}
} }

View file

@ -64,8 +64,7 @@ export default class Initiator {
delete triggers[testName].__testDelay; delete triggers[testName].__testDelay;
const jobReq = { const jobReq = {
...jobRequest, ...jobRequest,
pipeline: { ...pipeline, triggers: triggers[testName] }, pipeline: {...pipeline, triggers: triggers[testName],__test:testName },
testName,
}; };
setTimeout( setTimeout(
() => () =>

View file

@ -25,14 +25,6 @@ const initialState = {
pipelines: [], pipelines: [],
}; };
/*
pipelines: [{tracks:[
{
}
]}]
*/
const reducer = (state, action) => { const reducer = (state, action) => {
// Current Jobs // Current Jobs
const { jobs, pipelines } = state; const { jobs, pipelines } = state;
@ -80,7 +72,7 @@ export const JobProvider = ({ children }) => {
return jobFactory({ testNames: ["single"] }); return jobFactory({ testNames: ["single"] });
} }
function pipelineComponentJob(jobPipeline, testName) { function pipelineComponentJob(jobPipeline, pipelineReq) {
const i = new Initiator(url); const i = new Initiator(url);
const jobId = `j${Date.now()}`; const jobId = `j${Date.now()}`;
const job = { const job = {
@ -88,15 +80,14 @@ export const JobProvider = ({ children }) => {
status: jobStatus.PENDING, status: jobStatus.PENDING,
jobId, jobId,
isPipeline: true, isPipeline: true,
builderCache: {},
initiator: i, initiator: i,
pipelineId: jobPipeline.id,
branchId: pipelineReq.pipeline.__test
}; };
const request = { const request = {
testName,
image: "node", image: "node",
type: "pipeline",
name: jobId, name: jobId,
pipelineTriggers: "secondary", pipeline: pipelineReq,
}; };
jobCreate(job); jobCreate(job);
@ -113,8 +104,26 @@ export const JobProvider = ({ children }) => {
job.status = c === 0 ? jobStatus.OK : jobStatus.ERROR; job.status = c === 0 ? jobStatus.OK : jobStatus.ERROR;
jobUpdate({ ...job }, jobId); jobUpdate({ ...job }, jobId);
}; };
const onPipelineTrigger = (trigger) => { const onPipelineTrigger = (p) => {
console.log("Got trigger", trigger); const { triggers } = p;
for (var t in triggers) {
const delay = triggers[t].__testDelay ?? 0;
delete triggers[t].__testDelay;
const jobReq = {
...request,
pipeline: {
...pipeline,
triggers: triggers[t],__test:t
},
};
jobPipeline.pendingTriggers[t].push(
{
testName: t,
timer: setTimeout(() => pipelineComponentJob(jobPipeline, jobReq),delay),
triggerAt: Date.now() + delay
});
}
}; };
const started = i.newPipelineJob( const started = i.newPipelineJob(
request, request,
@ -127,10 +136,16 @@ export const JobProvider = ({ children }) => {
function pipelineFactory(builderCache) { function pipelineFactory(builderCache) {
console.log("Would create pipeline with cache"); console.log("Would create pipeline with cache");
console.log(builderCache); const { tree, branches } = builderCache;
return pipelineComponentJob(state.pipelines, builderCache.branches[0][0]); const __test = Object.keys(tree)[0];
// return jobId; const pipelineReq = {image: "node", pipeline:{__test, triggers: { ...tree[__test] }}}
/*return jobFactory({testNames: ["primary"]});*/ const id = `pij${Date.now()}`;
const pipeline = {id, branches, pendingTriggers: {} };
const {pipelines} = state;
pipelines.push(pipeline);
updatePipelines([...pipelines]);
return pipelineComponentJob(pipeline, pipelineReq);
} }
function jobCancel(jobId) { function jobCancel(jobId) {

View file

@ -0,0 +1,50 @@
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 JobPipelineBox(props) {
const { pipeline } = props;
function jobIcon() {
return <ViewColumnIcon />;
}
return (
<Accordion expanded={false} disableGutters={true} square>
<AccordionSummary
style={{
backgroundColor: "rgba(0, 0, 0, .03)",
flexWrap: "wrap",
}}
>
<Typography
component={"span"}
style={{ wordBreak: "break-word", margin: "auto 0" }}
>
{pipeline.id}
</Typography>
<Stack sx={{ ml: "auto" }}>
<IconButton aria-label="" component="span">
{jobIcon()}
</IconButton>
</Stack>
</AccordionSummary>
</Accordion>
);
}

View file

@ -0,0 +1,63 @@
import React, { useContext } from "react";
import {useNavigate} from "react-router-dom";
import JobContext from "../../ctx/JobContext.jsx";
import Button from "@mui/material/Button";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack";
function JobPipelineDisplay(props) {
const { back, pipeline } = props;
const {state: jobState} = useContext(JobContext);
const navigate = useNavigate();
const pipelineJobs = jobState.jobs.filter((j)=>j.isPipeline && j.pipelineId === pipeline.id);
const selectJob = (testName) => () =>{
const job = pipelineJobs.find((j)=>j.branchId === testName);
if(!job) return; navigate(`/qualiteer/jobs#${job.jobId}`);
}
return (
<React.Fragment>
<h3>{}</h3>
{pipeline.branches.map((track, i) => (
<React.Fragment key={i}>
<Typography variant="h6">{i + 1}</Typography>
<Box>
{track.map((test, j) => (
<Accordion
expanded={false}
disableGutters={true}
square
key={j}
onClick={selectJob(test)}
>
<AccordionSummary
style={{
backgroundColor: "rgba(0, 0, 0, .03)",
flexWrap: "wrap",
}}
>
<Typography
component={"span"}
style={{ wordBreak: "break-word", margin: "auto 0" }}
>
{test}
</Typography>
<Stack sx={{ ml: "auto" }}>I</Stack>
</AccordionSummary>
</Accordion>
))}
</Box>
</React.Fragment>
))}
</React.Fragment>
);
}
export default JobPipelineDisplay;

View file

@ -72,7 +72,10 @@ export default function JobView(props) {
cb(); cb();
}; };
function navigateToJobs() { function navigateToJobs() {
if(job.isPipeline) return navigate(`/qualiteer/jobs#p${job.pipelineId}`)
navigate("/qualiteer/jobs"); navigate("/qualiteer/jobs");
} }

View file

@ -3,7 +3,9 @@ import { useLocation, useNavigate } from "react-router-dom";
import JobContext from "../../ctx/JobContext.jsx"; import JobContext from "../../ctx/JobContext.jsx";
import JobBox from "./JobBox.jsx"; import JobBox from "./JobBox.jsx";
import JobPipelineBox from "./JobPipelineBox.jsx";
import JobView from "./JobView.jsx"; import JobView from "./JobView.jsx";
import JobPipelineDisplay from "./JobPipelineDisplay.jsx";
import JobBuilder from "./builder/JobBuilder.jsx"; import JobBuilder from "./builder/JobBuilder.jsx";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
@ -15,7 +17,11 @@ export default function Jobs() {
useEffect(() => { useEffect(() => {
const jobName = location.hash.slice(1); const jobName = location.hash.slice(1);
if (!jobName || jobState.jobs.find((job) => job.name === jobName)) return; const pipelineId = jobName.slice(1);
const noPipeline = !jobName || jobState.pipelines.find((p)=>p.id ===pipelineId);
const noJob = !jobName || jobState.jobs.find((job) => job.name === jobName)
if(!noPipeline || !noJob) return;
navigate("/qualiteer/jobs"); navigate("/qualiteer/jobs");
}); });
@ -45,8 +51,9 @@ export default function Jobs() {
</React.Fragment> </React.Fragment>
) : null} ) : null}
<JobBuilder /> <JobBuilder />
{location.hash === "" && {location.hash === "" &&(
jobState.jobs <React.Fragment>
{jobState.jobs
.filter((j) => !j.isPipeline) .filter((j) => !j.isPipeline)
.map((v, i) => ( .map((v, i) => (
<a <a
@ -55,10 +62,15 @@ export default function Jobs() {
style={{ textDecoration: "none" }} style={{ textDecoration: "none" }}
> >
<JobBox job={v} /> <JobBox job={v} />
</a> </a>))}
))} {jobState.pipelines.map((p,i)=><a key={i} style={{textDecoration: "none"}} href={`/qualiteer/jobs#p${p.id}`}>
{jobState.jobs.find((job) => job.name === location.hash.slice(1)) && ( <JobPipelineBox pipeline={p}/>
<JobView </a>)}
</React.Fragment>)
}
{ location.hash[1] === "p"? <JobPipelineDisplay pipeline={jobState.pipelines.find((p)=>p.id===location.hash.slice(2))}/> :
jobState.jobs.find((job) => job.name === location.hash.slice(1)) && (<JobView
job={jobState.jobs.find((job) => job.name === location.hash.slice(1))} job={jobState.jobs.find((job) => job.name === location.hash.slice(1))}
/> />
)} )}

View file

@ -12,16 +12,20 @@ const url = process.env.QUALITEER_URL;
// Create an initiator and make a job request // Create an initiator and make a job request
const primary = new Initiator(url); const primary = new Initiator(url);
const job = { const job = {
type: "compound",
testName: "primary",
pipeline: { pipeline: {
__test: "primary",
triggers: { triggers: {
secondary1: { secondary1: {
tertiary1: {},
tertiary2: { __testDelay: 5000 },
__testDelay: 1000, __testDelay: 1000,
tertiary1: {},
tertiary2: {
__testDelay: 8000 },
},
secondary2: {
__testDelay: 20000,
tertiary3: {
__testDelay: 3000 },
}, },
secondary2: { tertiary3: { __testDelay: 3000 }, __testDelay: 15000 },
}, },
}, },
name: "testing", name: "testing",