qualiteer/src/ctx/JobContext.jsx

278 lines
7 KiB
JavaScript

import React, { useReducer, createContext, useMemo } from "react";
import Initiator from "@qltr/initiator";
const JobContext = createContext();
export const jobStatus = {
OK: "o",
QUEUED: "q",
PENDING: "p",
CANCELED: "c",
ACTIVE: "a",
ERROR: "e",
};
const ACTIONS = {
CREATE: "c",
UPDATE: "u",
DELETE: "d",
PIPELINE: "p",
};
const url = "https://qualiteer.elijahparker3.repl.co/";
const initialState = {
jobs: [],
pipelines: [],
};
const reducer = (state, action) => {
// Current Jobs
const { jobs, pipelines } = state;
var jobIndex;
// Actions
switch (action.type) {
case ACTIONS.CREATE:
jobs.push(action.job);
return { ...state, jobs };
case ACTIONS.UPDATE:
jobIndex = jobs.findIndex(
(j) => j.jobId === (action.job.jobId ?? action.jobId)
);
jobs[jobIndex] = { ...jobs[jobIndex], ...action.job };
return { ...state, jobs };
case ACTIONS.DELETE:
jobIndex = jobs.findIndex((j) => j.jobId === action.jobId);
jobs.splice(jobIndex, 1);
return { ...state, jobs };
case ACTIONS.PIPELINE:
return { ...state, pipelines };
default:
return state;
}
};
export const JobProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const jobUpdate = (job, jobId) =>
dispatch({ type: ACTIONS.UPDATE, jobId, job });
const jobCreate = (job) =>
dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } });
const jobDelete = (jobId) => dispatch({ type: ACTIONS.DELETE, jobId });
const updatePipelines = (pipelines) =>
dispatch({ type: ACTIONS.pipeline, pipelines });
function retryAll(failing) {
// Query Full Locator
console.log("Would retry all failing tests!");
return jobFactory({ testNames: ["single"] });
}
function pipelineComponentJob(jobPipeline, pipelineReq) {
const i = new Initiator(url);
const jobId = `j${Date.now()}`;
const job = {
name: jobId,
status: jobStatus.PENDING,
jobId,
isPipeline: true,
initiator: i,
pipelineId: jobPipeline.id,
branchId: pipelineReq.pipeline.__test,
};
const request = {
image: "node",
name: jobId,
...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);
};
const onClose = (c) => {
const job = state.jobs.find((j) => j.jobId === jobId);
job.exitcode = c;
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: {
...p,
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) {
const { tree, branches, selectedBranches } = 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: [], selectedBranches };
const { pipelines } = state;
pipelines.push(pipeline);
updatePipelines([...pipelines]);
pipelineComponentJob(pipeline, pipelineReq);
return pipeline;
}
function pipelineCancel(pipelineId) {
const pipeline = state.pipelines.find((p) => p.id === pipelineId);
pipeline.isCanceled = true;
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) {
const job = state.jobs.find((j) => j.jobId === jobId);
if (job.initiator.sk) job.initiator.sk.close();
job.status = jobStatus.CANCELED;
jobUpdate({ ...job }, jobId);
}
function jobDestroy(jobId) {
const job = state.jobs.find((j) => j.jobId === jobId);
if (
job.initiator.sk &&
job.status !== jobStatus.OK &&
job.status !== jobStatus.ERROR &&
job.status !== jobStatus.CANCELED
) {
job.initiator.sk.close();
}
jobDelete(jobId);
}
function jobFactory(builderCache) {
if (builderCache.tree) return pipelineFactory(builderCache);
// Find test
const i = new Initiator(url);
const jobId = `j${Date.now()}`;
const job = {
name: jobId,
status: jobStatus.PENDING,
jobId,
isPipeline: false,
builderCache,
initiator: i,
};
const request = {
testNames: builderCache.testNames,
image: "node",
type: "single",
name: jobId,
};
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);
};
const onClose = (c) => {
const job = state.jobs.find((j) => j.jobId === jobId);
job.exitcode = c;
job.status = c === 0 ? jobStatus.OK : jobStatus.ERROR;
jobUpdate({ ...job }, jobId);
};
const started = i.newJob(request, onLog, onClose);
started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId));
return jobId;
}
const context = {
state,
dispatch,
jobUpdate,
jobCreate,
jobDelete,
retryAll,
jobFactory,
jobCancel,
jobDestroy,
pipelineCancel,
pipelineDestroy,
};
const contextValue = useMemo(() => context, [state, dispatch]);
return (
<JobContext.Provider value={contextValue}>{children}</JobContext.Provider>
);
};
export default JobContext;