Super Pipelines and display
This commit is contained in:
parent
8ad56e8d38
commit
c05502f15c
8 changed files with 223 additions and 75 deletions
|
@ -8,58 +8,60 @@ const pipelineMapping = [
|
|||
];
|
||||
|
||||
const buildCommon = (jobRequest) => {
|
||||
const { testName } = jobRequest;
|
||||
if (!testName) throw Error("'testName' must be provided!");
|
||||
const command = [baseCommand, suiteEntry, `test=${testName}`];
|
||||
|
||||
const { isTriage, ignore, region, testNames } = jobRequest;
|
||||
const command = [baseCommand, suiteEntry];
|
||||
// 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 { ...jobRequest, command };
|
||||
};
|
||||
|
||||
const buildSingle = (jobReq) => jobReq;
|
||||
|
||||
const buildMarker = (jobReq) => {};
|
||||
|
||||
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}`);
|
||||
}
|
||||
const buildManual = (jobReq) => {
|
||||
const {testNames} = jobReq;
|
||||
if(testNames.length > 1) throw Error("Currently only 1 test can be selected!");
|
||||
|
||||
command.push(`test=${testNames[0]}`);
|
||||
|
||||
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) {
|
||||
const jobReq = buildCommon(jobRequest, id);
|
||||
switch (jobRequest.type) {
|
||||
case "single":
|
||||
return buildSingle(jobReq);
|
||||
case "marker":
|
||||
return buildMarker(jobReq);
|
||||
case "project":
|
||||
return buildProject(jobReq);
|
||||
case "compound":
|
||||
return buildCompound(jobReq, id);
|
||||
default:
|
||||
throw Error("No Job Request Type Specified!");
|
||||
}
|
||||
const {pipeline, testNames, tags } = jobReq;
|
||||
if(pipeline)
|
||||
return buildPipeline(jobReq, id);
|
||||
else if(tags)
|
||||
return buildTags(jobReq);
|
||||
else if(testNames)
|
||||
return buildManual(jobReq); //TODO currently does nothing
|
||||
else throw Error("At least 1 'pipeline or tags or testNames' is required! ");
|
||||
}
|
||||
|
|
|
@ -64,8 +64,7 @@ export default class Initiator {
|
|||
delete triggers[testName].__testDelay;
|
||||
const jobReq = {
|
||||
...jobRequest,
|
||||
pipeline: { ...pipeline, triggers: triggers[testName] },
|
||||
testName,
|
||||
pipeline: {...pipeline, triggers: triggers[testName],__test:testName },
|
||||
};
|
||||
setTimeout(
|
||||
() =>
|
||||
|
|
|
@ -25,14 +25,6 @@ const initialState = {
|
|||
pipelines: [],
|
||||
};
|
||||
|
||||
/*
|
||||
pipelines: [{tracks:[
|
||||
{
|
||||
|
||||
}
|
||||
]}]
|
||||
*/
|
||||
|
||||
const reducer = (state, action) => {
|
||||
// Current Jobs
|
||||
const { jobs, pipelines } = state;
|
||||
|
@ -80,7 +72,7 @@ export const JobProvider = ({ children }) => {
|
|||
return jobFactory({ testNames: ["single"] });
|
||||
}
|
||||
|
||||
function pipelineComponentJob(jobPipeline, testName) {
|
||||
function pipelineComponentJob(jobPipeline, pipelineReq) {
|
||||
const i = new Initiator(url);
|
||||
const jobId = `j${Date.now()}`;
|
||||
const job = {
|
||||
|
@ -88,15 +80,14 @@ export const JobProvider = ({ children }) => {
|
|||
status: jobStatus.PENDING,
|
||||
jobId,
|
||||
isPipeline: true,
|
||||
builderCache: {},
|
||||
initiator: i,
|
||||
pipelineId: jobPipeline.id,
|
||||
branchId: pipelineReq.pipeline.__test
|
||||
};
|
||||
const request = {
|
||||
testName,
|
||||
image: "node",
|
||||
type: "pipeline",
|
||||
name: jobId,
|
||||
pipelineTriggers: "secondary",
|
||||
pipeline: pipelineReq,
|
||||
};
|
||||
|
||||
jobCreate(job);
|
||||
|
@ -113,8 +104,26 @@ export const JobProvider = ({ children }) => {
|
|||
job.status = c === 0 ? jobStatus.OK : jobStatus.ERROR;
|
||||
jobUpdate({ ...job }, jobId);
|
||||
};
|
||||
const onPipelineTrigger = (trigger) => {
|
||||
console.log("Got trigger", trigger);
|
||||
const onPipelineTrigger = (p) => {
|
||||
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(
|
||||
request,
|
||||
|
@ -127,10 +136,16 @@ export const JobProvider = ({ children }) => {
|
|||
|
||||
function pipelineFactory(builderCache) {
|
||||
console.log("Would create pipeline with cache");
|
||||
console.log(builderCache);
|
||||
return pipelineComponentJob(state.pipelines, builderCache.branches[0][0]);
|
||||
// return jobId;
|
||||
/*return jobFactory({testNames: ["primary"]});*/
|
||||
const { tree, branches } = builderCache;
|
||||
const __test = Object.keys(tree)[0];
|
||||
const pipelineReq = {image: "node", pipeline:{__test, triggers: { ...tree[__test] }}}
|
||||
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) {
|
||||
|
|
50
src/views/jobs/JobPipelineBox.jsx
Normal file
50
src/views/jobs/JobPipelineBox.jsx
Normal 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>
|
||||
);
|
||||
}
|
63
src/views/jobs/JobPipelineDisplay.jsx
Normal file
63
src/views/jobs/JobPipelineDisplay.jsx
Normal 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;
|
|
@ -72,7 +72,10 @@ export default function JobView(props) {
|
|||
cb();
|
||||
};
|
||||
|
||||
|
||||
|
||||
function navigateToJobs() {
|
||||
if(job.isPipeline) return navigate(`/qualiteer/jobs#p${job.pipelineId}`)
|
||||
navigate("/qualiteer/jobs");
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,9 @@ import { useLocation, useNavigate } from "react-router-dom";
|
|||
|
||||
import JobContext from "../../ctx/JobContext.jsx";
|
||||
import JobBox from "./JobBox.jsx";
|
||||
import JobPipelineBox from "./JobPipelineBox.jsx";
|
||||
import JobView from "./JobView.jsx";
|
||||
import JobPipelineDisplay from "./JobPipelineDisplay.jsx";
|
||||
import JobBuilder from "./builder/JobBuilder.jsx";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import Box from "@mui/material/Box";
|
||||
|
@ -15,7 +17,11 @@ export default function Jobs() {
|
|||
|
||||
useEffect(() => {
|
||||
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");
|
||||
});
|
||||
|
||||
|
@ -45,8 +51,9 @@ export default function Jobs() {
|
|||
</React.Fragment>
|
||||
) : null}
|
||||
<JobBuilder />
|
||||
{location.hash === "" &&
|
||||
jobState.jobs
|
||||
{location.hash === "" &&(
|
||||
<React.Fragment>
|
||||
{jobState.jobs
|
||||
.filter((j) => !j.isPipeline)
|
||||
.map((v, i) => (
|
||||
<a
|
||||
|
@ -55,10 +62,15 @@ export default function Jobs() {
|
|||
style={{ textDecoration: "none" }}
|
||||
>
|
||||
<JobBox job={v} />
|
||||
</a>
|
||||
))}
|
||||
{jobState.jobs.find((job) => job.name === location.hash.slice(1)) && (
|
||||
<JobView
|
||||
</a>))}
|
||||
{jobState.pipelines.map((p,i)=><a key={i} style={{textDecoration: "none"}} href={`/qualiteer/jobs#p${p.id}`}>
|
||||
<JobPipelineBox pipeline={p}/>
|
||||
</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))}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -12,16 +12,20 @@ const url = process.env.QUALITEER_URL;
|
|||
// Create an initiator and make a job request
|
||||
const primary = new Initiator(url);
|
||||
const job = {
|
||||
type: "compound",
|
||||
testName: "primary",
|
||||
pipeline: {
|
||||
__test: "primary",
|
||||
triggers: {
|
||||
secondary1: {
|
||||
tertiary1: {},
|
||||
tertiary2: { __testDelay: 5000 },
|
||||
__testDelay: 1000,
|
||||
tertiary1: {},
|
||||
tertiary2: {
|
||||
__testDelay: 8000 },
|
||||
},
|
||||
secondary2: {
|
||||
__testDelay: 20000,
|
||||
tertiary3: {
|
||||
__testDelay: 3000 },
|
||||
},
|
||||
secondary2: { tertiary3: { __testDelay: 3000 }, __testDelay: 15000 },
|
||||
},
|
||||
},
|
||||
name: "testing",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue