diff --git a/bin/executor b/bin/executor index 72bb3dd..000a93a 100755 Binary files a/bin/executor and b/bin/executor differ diff --git a/lib/jobs/job-builder.js b/lib/jobs/job-builder.js index 9df5c47..17fbc8f 100644 --- a/lib/jobs/job-builder.js +++ b/lib/jobs/job-builder.js @@ -11,57 +11,51 @@ 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]}`); - + return { ...jobReq, command }; }; 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! "); } diff --git a/lib/sockets/clients/Initiator.js b/lib/sockets/clients/Initiator.js index c0aca30..943793a 100644 --- a/lib/sockets/clients/Initiator.js +++ b/lib/sockets/clients/Initiator.js @@ -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( () => diff --git a/package.json b/package.json index ae38fc5..20456ca 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/ctx/JobContext.jsx b/src/ctx/JobContext.jsx index 1bb3df3..7b70a81 100644 --- a/src/ctx/JobContext.jsx +++ b/src/ctx/JobContext.jsx @@ -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]); diff --git a/src/views/jobs/JobPipelineDisplay.jsx b/src/views/jobs/JobPipelineDisplay.jsx index ff33eb8..00600a2 100644 --- a/src/views/jobs/JobPipelineDisplay.jsx +++ b/src/views/jobs/JobPipelineDisplay.jsx @@ -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 selectJob = (testName) => () =>{ - const job = pipelineJobs.find((j)=>j.branchId === testName); - if(!job) return; navigate(`/qualiteer/jobs#${job.jobId}`); + 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}`); + }; + + function navigateToJobs() { + navigate(`/qualiteer/jobs`); } - + + function cancelPipeline() { + pipelineCancel(pipeline.id); + } + + function deletePipeline() { + pipelineDestroy(pipeline.id); + } + return ( - -

{}

- {pipeline.branches.map((track, i) => ( - - {i + 1} - - {track.map((test, j) => ( - + + + + + + + + + {pipeline.id} + + + + + + + + + + + + {selectedBranches().map((track, i) => ( + + {i + 1} + + {track.map((test, j) => ( + + - - - {test} - - I - - - ))} - - - ))} - + {test} + + I + + + ))} + +
+ ))} + ); } diff --git a/src/views/jobs/JobView.jsx b/src/views/jobs/JobView.jsx index 55182e1..94e95ef 100644 --- a/src/views/jobs/JobView.jsx +++ b/src/views/jobs/JobView.jsx @@ -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"); } diff --git a/src/views/jobs/Jobs.jsx b/src/views/jobs/Jobs.jsx index 62eb282..fd6d2f5 100644 --- a/src/views/jobs/Jobs.jsx +++ b/src/views/jobs/Jobs.jsx @@ -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() { ) : null} - {location.hash === "" &&( - - {jobState.jobs - .filter((j) => !j.isPipeline) - .map((v, i) => ( + {location.hash === "" && ( + + {jobState.jobs + .filter((j) => !j.isPipeline) + .map((v, i) => ( + + + + ))} + {jobState.pipelines.map((p, i) => ( - - ))} - {jobState.pipelines.map((p,i)=> - - )} - ) - } - - { location.hash[1] === "p"? jobState.pipelines.find((p)=>p.id===location.hash.slice(2)) && (p.id===location.hash.slice(2))}/>) : - jobState.jobs.find((job) => job.name === location.hash.slice(1)) && ( job.name === location.hash.slice(1))} - /> + + + ))} + )} + + {location.hash[1] === "p" + ? jobState.pipelines.find((p) => p.id === location.hash.slice(2)) && ( + p.id === location.hash.slice(2) + )} + /> + ) + : jobState.jobs.find((job) => job.name === location.hash.slice(1)) && ( + job.name === location.hash.slice(1) + )} + /> + )} ); } diff --git a/src/views/jobs/builder/PipelineTrackSelector.jsx b/src/views/jobs/builder/PipelineTrackSelector.jsx index 1683c08..11c84c4 100644 --- a/src/views/jobs/builder/PipelineTrackSelector.jsx +++ b/src/views/jobs/builder/PipelineTrackSelector.jsx @@ -47,6 +47,7 @@ function PipelineTrackSelector(props) { ...cache, branches: asBranches(primaries), tree: asTree(cache.tracks), + selectedBranches: as1d(cache.tracks), }); next(); }; diff --git a/tests/assets/suite/runner.js b/tests/assets/suite/runner.js index 114dc9d..481d9e1 100644 --- a/tests/assets/suite/runner.js +++ b/tests/assets/suite/runner.js @@ -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, diff --git a/tests/index.js b/tests/index.js index f5be35d..b2dbb1a 100644 --- a/tests/index.js +++ b/tests/index.js @@ -18,13 +18,15 @@ const job = { secondary1: { __testDelay: 1000, tertiary1: {}, - tertiary2: { - __testDelay: 8000 }, + tertiary2: { + __testDelay: 8000, + }, }, - secondary2: { - __testDelay: 20000, - tertiary3: { - __testDelay: 3000 }, + secondary2: { + __testDelay: 20000, + tertiary3: { + __testDelay: 3000, + }, }, }, },