Fused Frontend and Bakcend

This commit is contained in:
Dunemask 2022-08-09 17:59:36 +00:00
parent 55e0a21f79
commit b7f953e93d
11 changed files with 245 additions and 135 deletions

Binary file not shown.

View file

@ -11,18 +11,18 @@ const buildCommon = (jobRequest) => {
const { isTriage, ignore, region, testNames } = jobRequest;
const command = [baseCommand, suiteEntry];
// Apply Common Flags
command.push(`isTriage=${isTriage}`);
if(ignore && ignore.length > 0) console.log("Would ignore", ignore);
if(region)
command.push(`region=${region}`);
if (isTriage) 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 buildManual = (jobReq) => {
const {testNames} = jobReq;
if(testNames.length > 1) throw Error("Currently only 1 test can be selected!");
const { command, testNames } = jobReq;
if (testNames.length > 1)
throw Error("Currently only 1 test can be selected!");
command.push(`test=${testNames[0]}`);
@ -30,38 +30,32 @@ const buildManual = (jobReq) => {
};
const buildTags = (jobReq) => {
const {command, tags, testNames} = jobReq;
if(testNames && testNames.length > 0){
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"
);
const arg = Buffer.from(JSON.stringify(tags), "utf8").toString("base64");
command.push(`tags=${arg}`);
return {...jobReq, command};
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}`);
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) {
console.log(jobRequest);
const jobReq = buildCommon(jobRequest, id);
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
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! ");
}

View file

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

View file

@ -19,7 +19,7 @@
"build:react": "vite build",
"start": "node dist/app.js",
"start:dev": "nodemon dist/app.js",
"start:dev:replit": "npm run start:react:replit & (sleep 10 && npm run start:dev)",
"start:dev:replit": "npm run start:react:replit & (sleep 30 && npm run start:dev)",
"start:react": "vite preview",
"start:react:replit": "vite --host",
"test": "node tests/index.js",

View file

@ -82,17 +82,18 @@ export const JobProvider = ({ children }) => {
isPipeline: true,
initiator: i,
pipelineId: jobPipeline.id,
branchId: pipelineReq.pipeline.__test
branchId: pipelineReq.pipeline.__test,
};
const request = {
image: "node",
name: jobId,
pipeline: pipelineReq,
...pipelineReq,
};
jobCreate(job);
const onLog = (d) => {
const job = state.jobs.find((j) => j.jobId === jobId);
job.log.push(d);
job.status = jobStatus.ACTIVE;
jobUpdate({ ...job }, jobId);
@ -104,48 +105,86 @@ export const JobProvider = ({ children }) => {
job.status = c === 0 ? jobStatus.OK : jobStatus.ERROR;
jobUpdate({ ...job }, jobId);
};
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
});
}
for (var t in triggers) {
const delay = triggers[t].__testDelay ?? 0;
delete triggers[t].__testDelay;
const jobReq = {
...request,
pipeline: {
triggers: triggers[t],
__test: t,
},
};
jobPipeline.pendingTriggers.push({
testName: t,
timer: setTimeout(
() => pipelineComponentJob(jobPipeline, jobReq),
delay
),
triggerAt: Date.now() + delay,
});
}
};
const started = i.newPipelineJob(
request,
onLog,
onClose,
null,
onPipelineTrigger
);
started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId));
}
function pipelineFactory(builderCache) {
console.log("Would create pipeline with cache");
const { tree, branches } = builderCache;
const { tree, branches, selectedBranches } = builderCache;
const __test = Object.keys(tree)[0];
const pipelineReq = {image: "node", pipeline:{__test, triggers: { ...tree[__test] }}}
const pipelineReq = {
image: "node",
pipeline: { __test, triggers: { ...tree[__test] } },
};
const id = `pij${Date.now()}`;
const pipeline = {id, branches, pendingTriggers: {} };
const {pipelines} = state;
const pipeline = { id, branches, pendingTriggers: [], selectedBranches };
const { pipelines } = state;
pipelines.push(pipeline);
updatePipelines([...pipelines]);
return pipelineComponentJob(pipeline, pipelineReq);
}
function pipelineCancel(pipelineId) {
const pipeline = state.pipelines.find((p) => p.id === pipelineId);
pipeline.pendingTriggers.forEach((t) => clearTimeout(t));
const jobs = state.jobs.filter(
(j) => j.isPipeline && j.pipelineId === pipelineId
);
for (var j of jobs) {
if (j.initiator.sk) j.initiator.sk.close();
j.status = jobStatus.CANCELED;
jobUpdate({ ...j }, j.jobId);
}
}
function pipelineDestroy(pipelineId) {
const pipelineIndex = state.pipelines.findIndex((p) => p.id === pipelineId);
const pipeline = state.pipelines[pipelineIndex];
pipeline.pendingTriggers.forEach((t) => clearTimeout(t));
const jobs = state.jobs.filter(
(j) => j.isPipeline && j.pipelineId === pipelineId
);
for (var j of jobs) {
if (
j.initiator.sk &&
j.status !== jobStatus.OK &&
j.status !== jobStatus.ERROR &&
j.status !== jobStatus.CANCELED
) {
j.initiator.sk.close();
}
jobDelete(j.jobId);
}
state.pipelines.splice(pipelineIndex, 1);
}
function jobCancel(jobId) {
@ -185,11 +224,12 @@ export const JobProvider = ({ children }) => {
};
const request = {
testName: builderCache.testNames[0],
testNames: builderCache.testNames,
image: "node",
type: "single",
name: jobId,
};
console.log(request);
jobCreate(job);
@ -223,6 +263,8 @@ export const JobProvider = ({ children }) => {
jobFactory,
jobCancel,
jobDestroy,
pipelineCancel,
pipelineDestroy,
};
const contextValue = useMemo(() => context, [state, dispatch]);

View file

@ -1,62 +1,114 @@
import React, { useContext } from "react";
import {useNavigate} from "react-router-dom";
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 Box from "@mui/material/Box";
import AppBar from "@mui/material/AppBar";
import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
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";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
function JobPipelineDisplay(props) {
const { back, pipeline } = props;
const {state: jobState} = useContext(JobContext);
const {
state: jobState,
pipelineCancel,
pipelineDestroy,
} = useContext(JobContext);
const navigate = useNavigate();
const pipelineJobs = jobState.jobs.filter((j)=>j.isPipeline && j.pipelineId === pipeline.id);
const pipelineJobs = jobState.jobs.filter(
(j) => j.isPipeline && j.pipelineId === pipeline.id
);
const selectedBranches = () => {
return pipeline.branches.map((b) => {
return b.filter((t) => pipeline.selectedBranches.includes(t));
});
};
const selectJob = (testName) => () =>{
const job = pipelineJobs.find((j)=>j.branchId === testName);
if(!job) return; navigate(`/qualiteer/jobs#${job.jobId}`);
const selectJob = (testName) => () => {
const job = pipelineJobs.find((j) => j.branchId === testName);
if (!job) return;
navigate(`/qualiteer/jobs#${job.jobId}`);
};
function navigateToJobs() {
navigate(`/qualiteer/jobs`);
}
function cancelPipeline() {
pipelineCancel(pipeline.id);
}
function deletePipeline() {
pipelineDestroy(pipeline.id);
}
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)}
<Box>
<AppBar
position="fixed"
sx={{
backgroundColor: "white",
boxShadow: "none",
color: "black",
}}
>
<Toolbar disableGutters />
<Box sx={{ flexGrow: 1, margin: "0 10px" }}>
<Toolbar disableGutters>
<IconButton onClick={navigateToJobs}>
<ArrowBackIcon />
</IconButton>
<Typography variant="h6" sx={{ ml: "auto", mr: "auto" }}>
{pipeline.id}
</Typography>
<IconButton onClick={cancelPipeline}>
<ArrowBackIcon />
</IconButton>
<IconButton onClick={deletePipeline}>
<ArrowBackIcon />
</IconButton>
</Toolbar>
</Box>
</AppBar>
<Toolbar disableGutters />
{selectedBranches().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",
}}
>
<AccordionSummary
style={{
backgroundColor: "rgba(0, 0, 0, .03)",
flexWrap: "wrap",
}}
<Typography
component={"span"}
style={{ wordBreak: "break-word", margin: "auto 0" }}
>
<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>
{test}
</Typography>
<Stack sx={{ ml: "auto" }}>I</Stack>
</AccordionSummary>
</Accordion>
))}
</Box>
</React.Fragment>
))}
</Box>
);
}

View file

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

View file

@ -18,11 +18,11 @@ export default function Jobs() {
useEffect(() => {
const jobName = location.hash.slice(1);
const pipelineId = jobName.slice(1);
if(!jobName || !pipelineId) return;
const hasJob = jobState.pipelines.find((p)=>p.id ===pipelineId);
if (!jobName || !pipelineId) return;
const hasJob = jobState.pipelines.find((p) => p.id === pipelineId);
const hasPipeline = jobState.jobs.find((job) => job.name === jobName);
if(hasPipeline || hasJob) return;
if(jobName || pipelineId) navigate("/qualiteer/jobs");
if (hasPipeline || hasJob) return;
if (jobName || pipelineId) navigate("/qualiteer/jobs");
});
return (
@ -51,29 +51,46 @@ export default function Jobs() {
</React.Fragment>
) : null}
<JobBuilder />
{location.hash === "" &&(
<React.Fragment>
{jobState.jobs
.filter((j) => !j.isPipeline)
.map((v, i) => (
{location.hash === "" && (
<React.Fragment>
{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.pipelines.map((p, i) => (
<a
key={i}
href={`/qualiteer/jobs#${v.name}`}
style={{ textDecoration: "none" }}
href={`/qualiteer/jobs#p${p.id}`}
>
<JobBox job={v} />
</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"? jobState.pipelines.find((p)=>p.id===location.hash.slice(2)) && (<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))}
/>
<JobPipelineBox pipeline={p} />
</a>
))}
</React.Fragment>
)}
{location.hash[1] === "p"
? jobState.pipelines.find((p) => p.id === location.hash.slice(2)) && (
<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)
)}
/>
)}
</div>
);
}

View file

@ -47,6 +47,7 @@ function PipelineTrackSelector(props) {
...cache,
branches: asBranches(primaries),
tree: asTree(cache.tracks),
selectedBranches: as1d(cache.tracks),
});
next();
};

View file

@ -57,7 +57,7 @@ const runTests = () => {
liveIndicator();
setTimeout(() => {
const status = runTests();
pipeline.testData = status.pipelineData;
if (pipeline) pipeline.testData = status.pipelineData;
const testResult = {
...status,
name: test,

View file

@ -19,12 +19,14 @@ const job = {
__testDelay: 1000,
tertiary1: {},
tertiary2: {
__testDelay: 8000 },
__testDelay: 8000,
},
},
secondary2: {
__testDelay: 20000,
tertiary3: {
__testDelay: 3000 },
__testDelay: 3000,
},
},
},
},