Revamp Job flow
This commit is contained in:
parent
945afdfbbe
commit
4a0a4b29a5
86 changed files with 592 additions and 608 deletions
|
@ -1,6 +1,6 @@
|
|||
// Import Contexts
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { JobProvider } from "@qltr/jobs";
|
||||
import { JobProvider } from "@qltr/jobctx";
|
||||
import { StoreProvider } from "@qltr/store";
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
// Import Views
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
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",
|
||||
|
@ -18,12 +8,7 @@ const ACTIONS = {
|
|||
PIPELINE: "p",
|
||||
};
|
||||
|
||||
const url = "/";
|
||||
|
||||
const initialState = {
|
||||
jobs: [],
|
||||
pipelines: [],
|
||||
};
|
||||
const initialState = { jobs: [], pipelines: [] };
|
||||
|
||||
const reducer = (state, action) => {
|
||||
// Current Jobs
|
||||
|
@ -56,220 +41,21 @@ const reducer = (state, action) => {
|
|||
|
||||
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) {
|
||||
if (t === "__testDelay") continue;
|
||||
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,
|
||||
() => {},
|
||||
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] } },
|
||||
isTriage: builderCache.triageFailing,
|
||||
};
|
||||
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,
|
||||
isTriage: builderCache.isTriage,
|
||||
};
|
||||
|
||||
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,
|
||||
updatePipelines,
|
||||
};
|
||||
const contextValue = useMemo(() => context, [state, dispatch]);
|
||||
|
||||
|
@ -277,5 +63,4 @@ export const JobProvider = ({ children }) => {
|
|||
<JobContext.Provider value={contextValue}>{children}</JobContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobContext;
|
||||
|
|
44
src/job-core/JobCore.jsx
Normal file
44
src/job-core/JobCore.jsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { useContext } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import JobContext from "@qltr/jobctx";
|
||||
import Initiator from "@qltr/initiator";
|
||||
import { useOneshotCore } from "./OneshotCore.jsx";
|
||||
import { usePipelineCore } from "./PipelineCore.jsx";
|
||||
import { useJobExtra } from "./JobExtra.jsx";
|
||||
import { jobStatus, socketUrl } from "./job-config.js";
|
||||
|
||||
export function useJobCore() {
|
||||
const { state, jobUpdate, jobCreate, jobDelete } = useContext(JobContext);
|
||||
const { pipelineStart, pipelineCancel, pipelineDestroy } = usePipelineCore();
|
||||
const { oneshotStart, oneshotCancel, oneshotDestroy } = useOneshotCore();
|
||||
const jobExtra = useJobExtra();
|
||||
|
||||
function retryAll(failing) {
|
||||
console.log("Would retry all failing tests!");
|
||||
}
|
||||
|
||||
function jobCompose(builderCache) {
|
||||
if (builderCache.tree) return pipelineStart(builderCache);
|
||||
return oneshotStart(builderCache);
|
||||
}
|
||||
|
||||
return {
|
||||
// Job Context
|
||||
state,
|
||||
// Job Core
|
||||
jobCompose,
|
||||
retryAll,
|
||||
// Oneshot
|
||||
oneshotStart,
|
||||
oneshotCancel,
|
||||
oneshotDestroy,
|
||||
// Pipeline
|
||||
pipelineCancel,
|
||||
pipelineDestroy,
|
||||
pipelineStart,
|
||||
// Job Extra
|
||||
...jobExtra,
|
||||
};
|
||||
}
|
||||
|
||||
export { jobStatus } from "./job-config.js";
|
81
src/job-core/JobExtra.jsx
Normal file
81
src/job-core/JobExtra.jsx
Normal file
|
@ -0,0 +1,81 @@
|
|||
import { useContext } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import JobContext from "@qltr/jobctx";
|
||||
import { jobStatus } from "./job-config.js";
|
||||
// Icons
|
||||
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 ReplayIcon from "@mui/icons-material/Replay";
|
||||
|
||||
function statusIcon(status) {
|
||||
switch (status) {
|
||||
case jobStatus.OK:
|
||||
return <CheckIcon color="success" />;
|
||||
case jobStatus.ERROR:
|
||||
return <ClearIcon color="error" />;
|
||||
case jobStatus.PENDING:
|
||||
return <PendingIcon color="info" />;
|
||||
case jobStatus.ACTIVE:
|
||||
return <VisibilityIcon color="primary" />;
|
||||
case jobStatus.CANCELED:
|
||||
return <DoNotDisturbIcon color="warning" />;
|
||||
case jobStatus.QUEUED:
|
||||
return <ViewColumnIcon color="secondary" />;
|
||||
default:
|
||||
return <ReplayIcon />;
|
||||
}
|
||||
}
|
||||
|
||||
export function useJobExtra() {
|
||||
const { state, jobUpdate, jobCreate, jobDelete } = useContext(JobContext);
|
||||
const navigate = useNavigate();
|
||||
|
||||
function pipelineJobs(pl) {
|
||||
return state.jobs.filter((j) => j.isPipeline && j.pipelineId === pl.id);
|
||||
}
|
||||
|
||||
const jobIcon = ({ status }) => statusIcon(status);
|
||||
|
||||
function pipelineIcon(pl) {
|
||||
const jobStatuses = pipelineJobs(pl).map(({ status }) => status);
|
||||
if (jobStatuses.includes(jobStatus.ERROR))
|
||||
return statusIcon(jobStatus.ERROR);
|
||||
if (jobStatuses.includes(jobStatus.ACTIVE))
|
||||
return statusIcon(jobStatus.ACTIVE);
|
||||
if (jobStatuses.includes(jobStatus.PENDING))
|
||||
return statusIcon(jobStatus.PENDING);
|
||||
if (pl.isCanceled) return statusIcon(jobStatus.CANCELED);
|
||||
if (jobStatuses.includes(jobStatus.OK)) return statusIcon(jobStatus.OK);
|
||||
return statusIcon(jobStatus.QUEUED);
|
||||
}
|
||||
|
||||
function selectedPipelineBranches(pl) {
|
||||
return pl.branches.map((b) =>
|
||||
b.filter((t) => pl.selectedBranches.find((b) => b.name == t.name))
|
||||
);
|
||||
}
|
||||
|
||||
function findPipelineJobByTestName(pl, testName) {
|
||||
return pipelineJobs(pl).find((j) => j.branchId === testName);
|
||||
}
|
||||
|
||||
// Nav
|
||||
const toJob = (jobId) => navigate(`/qualiteer/jobs#job-${jobId}`);
|
||||
const toPipeline = (plId) => navigate(`/qualiteer/jobs#pipeline-${plId}`);
|
||||
const toJobs = () => navigate(`/qualiteer/jobs`);
|
||||
|
||||
return {
|
||||
pipelineJobs,
|
||||
jobIcon,
|
||||
pipelineIcon,
|
||||
selectedPipelineBranches,
|
||||
findPipelineJobByTestName,
|
||||
toJob,
|
||||
toPipeline,
|
||||
toJobs,
|
||||
};
|
||||
}
|
63
src/job-core/OneshotCore.jsx
Normal file
63
src/job-core/OneshotCore.jsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { useContext } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import JobContext from "@qltr/jobctx";
|
||||
import Initiator from "@qltr/initiator";
|
||||
import { jobStatus, socketUrl } from "./job-config.js";
|
||||
|
||||
export function useOneshotCore() {
|
||||
const { state, jobUpdate, jobCreate, jobDelete } = useContext(JobContext);
|
||||
|
||||
function oneshotCancel(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 oneshotDestroy(jobId) {
|
||||
const job = state.jobs.find((j) => j.jobId === jobId);
|
||||
const { status } = job;
|
||||
const isTriggered = !!job.initiator.sk;
|
||||
const isCompleted = status === jobStatus.OK || status === jobStatus.ERROR;
|
||||
const isCanceled = status === jobStatus.CANCELED;
|
||||
if (isTriggered && !isCompleted && !isCanceled) job.initiator.sk.close();
|
||||
jobDelete(jobId);
|
||||
}
|
||||
|
||||
function oneshotStart(builderCache) {
|
||||
const { testNames, isTriage } = builderCache;
|
||||
const initiator = new Initiator(socketUrl);
|
||||
const jobId = uuidv4();
|
||||
const job = {
|
||||
name: jobId,
|
||||
status: jobStatus.PENDING,
|
||||
jobId,
|
||||
isPipeline: false,
|
||||
builderCache,
|
||||
initiator,
|
||||
};
|
||||
const request = { testNames, type: "single", isTriage };
|
||||
|
||||
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 = initiator.newJob(request, onLog, onClose, () => {});
|
||||
started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId));
|
||||
return jobId;
|
||||
}
|
||||
return { oneshotStart, oneshotCancel, oneshotDestroy };
|
||||
}
|
110
src/job-core/PipelineCore.jsx
Normal file
110
src/job-core/PipelineCore.jsx
Normal file
|
@ -0,0 +1,110 @@
|
|||
import { useContext } from "react";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import JobContext from "@qltr/jobctx";
|
||||
import Initiator from "@qltr/initiator";
|
||||
import { jobStatus, socketUrl } from "./job-config.js";
|
||||
|
||||
export function usePipelineCore() {
|
||||
const { state, jobUpdate, jobCreate, jobDelete, updatePipelines } =
|
||||
useContext(JobContext);
|
||||
|
||||
function pipelineJob(pl, plReq) {
|
||||
const initiator = new Initiator(socketUrl);
|
||||
const jobId = uuidv4();
|
||||
const job = {
|
||||
status: jobStatus.PENDING,
|
||||
jobId,
|
||||
isPipeline: true,
|
||||
initiator,
|
||||
pipelineId: pl.id,
|
||||
branchId: plReq.pipeline.__test,
|
||||
};
|
||||
|
||||
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) {
|
||||
if (t === "__testDelay") continue;
|
||||
const delay = triggers[t].__testDelay ?? 0;
|
||||
delete triggers[t].__testDelay;
|
||||
const plTrigger = { ...p, triggers: triggers[t], __test: t };
|
||||
const jobReq = { ...plReq, pipeline: plTrigger };
|
||||
const timer = setTimeout(() => pipelineJob(pl, jobReq), delay);
|
||||
const triggerAt = Date.now() + delay;
|
||||
pl.pendingTriggers.push({ testName: t, timer, triggerAt });
|
||||
}
|
||||
};
|
||||
const started = initiator.newPipelineJob(
|
||||
plReq,
|
||||
onLog,
|
||||
onClose,
|
||||
() => {},
|
||||
onPipelineTrigger
|
||||
);
|
||||
started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId));
|
||||
}
|
||||
|
||||
function pipelineStart(builderCache) {
|
||||
const { tree, branches, selectedBranches, triageFailing } = builderCache;
|
||||
const __test = Object.keys(tree)[0];
|
||||
const plReq = {
|
||||
pipeline: { __test, triggers: { ...tree[__test] } },
|
||||
isTriage: triageFailing,
|
||||
};
|
||||
const id = uuidv4();
|
||||
const pipeline = { id, branches, pendingTriggers: [], selectedBranches };
|
||||
const { pipelines } = state;
|
||||
pipelines.push(pipeline);
|
||||
updatePipelines([...pipelines]);
|
||||
pipelineJob(pipeline, plReq);
|
||||
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
|
||||
);
|
||||
var isTriggered, isCompleted, isCanceled;
|
||||
for (var j of jobs) {
|
||||
isTriggered = !!j.initiator.sk;
|
||||
isCompleted = status === jobStatus.OK || status === jobStatus.ERROR;
|
||||
isCanceled = status === jobStatus.CANCELED;
|
||||
if (isTriggered && !isCompleted && !isCanceled) j.initiator.sk.close();
|
||||
jobDelete(j.jobId);
|
||||
}
|
||||
state.pipelines.splice(pipelineIndex, 1);
|
||||
}
|
||||
|
||||
return { pipelineStart, pipelineCancel, pipelineDestroy };
|
||||
}
|
9
src/job-core/job-config.js
Normal file
9
src/job-core/job-config.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
export const jobStatus = {
|
||||
OK: "o",
|
||||
QUEUED: "q",
|
||||
PENDING: "p",
|
||||
CANCELED: "c",
|
||||
ACTIVE: "a",
|
||||
ERROR: "e",
|
||||
};
|
||||
export const socketUrl = "/";
|
|
@ -1,5 +1,6 @@
|
|||
import { useContext } from "react";
|
||||
import JobContext, { jobStatus } from "@qltr/jobs";
|
||||
import JobContext from "@qltr/jobctx";
|
||||
import { jobStatus } from "../job-core/job-config.js";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import _ from "lodash";
|
||||
import merge from "lodash.merge";
|
||||
|
||||
const nest = (arr) => {
|
||||
const obj = {};
|
||||
|
@ -8,7 +8,7 @@ const nest = (arr) => {
|
|||
|
||||
export const asTree = (branches) => {
|
||||
const nests = branches.map((b) => nest(b));
|
||||
return _.merge(...nests);
|
||||
return merge(...nests);
|
||||
};
|
||||
|
||||
export const asBranches = (array) => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useContext, useState } from "react";
|
||||
import { useCurrentlyFailing } from "@qltr/queries";
|
||||
import JobContext from "@qltr/jobs";
|
||||
import JobContext from "@qltr/jobctx";
|
||||
import StoreContext from "@qltr/store";
|
||||
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useEffect, useContext } from "react";
|
||||
import StoreContext from "@qltr/store";
|
||||
import JobContext from "@qltr/jobs";
|
||||
import JobContext from "@qltr/jobctx";
|
||||
import CatalogBox from "./CatalogBox.jsx";
|
||||
import CatalogSearch from "./CatalogSearch.jsx";
|
||||
import { useCatalogTests } from "@qltr/queries";
|
||||
|
@ -40,6 +40,7 @@ export default function Catalog() {
|
|||
test.job = pipelineJob;
|
||||
continue;
|
||||
}
|
||||
|
||||
const job = jobState.jobs.find(
|
||||
(j) => !j.isPipeline && j.builderCache.testNames.includes(test.name)
|
||||
);
|
||||
|
|
|
@ -2,12 +2,7 @@ import React, { useState, useContext } from "react";
|
|||
|
||||
import { usePipelineMappings } from "@qltr/queries";
|
||||
import StoreContext from "@qltr/store";
|
||||
import JobContext, { jobStatus } from "@qltr/jobs";
|
||||
import {
|
||||
useJobIconState,
|
||||
usePipelineIconState,
|
||||
useJobNav,
|
||||
} from "@qltr/util/JobTools";
|
||||
import { useJobCore, jobStatus } from "@qltr/jobcore";
|
||||
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
|
@ -39,25 +34,24 @@ export default function CatalogBox(props) {
|
|||
|
||||
const { data: pipelineMappings, isLoading } = usePipelineMappings();
|
||||
const { state: store } = useContext(StoreContext);
|
||||
const { jobFactory } = useContext(JobContext);
|
||||
const jobNav = useJobNav();
|
||||
const { jobCompose, toPipeline, toJob, jobIcon, pipelineIcon } = useJobCore();
|
||||
const [open, setOpen] = useState(false);
|
||||
const toggleOpen = () => setOpen(!open);
|
||||
const theme = useTheme();
|
||||
const minifyActions = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
const navigateToJob = () => {
|
||||
if (pipeline) return jobNav.toPipeline(pipeline.id);
|
||||
jobNav.toJob(job.jobId);
|
||||
if (pipeline) return toPipeline(pipeline.id);
|
||||
toJob(job.jobId);
|
||||
};
|
||||
|
||||
const runTest = () => {
|
||||
if (isPipeline) return runPipelineTest();
|
||||
const jobId = jobFactory({
|
||||
const jobId = jobCompose({
|
||||
testNames: [testName],
|
||||
isTriage: store.triageFailing,
|
||||
});
|
||||
if (store.focusJob) jobNav.toJob(jobId);
|
||||
if (store.focusJob) toJob(jobId);
|
||||
};
|
||||
|
||||
const runPipelineTest = () => {
|
||||
|
@ -74,8 +68,8 @@ export default function CatalogBox(props) {
|
|||
selectedBranches: as1d(primaries),
|
||||
isTriage: true,
|
||||
};
|
||||
const pipeline = jobFactory(builderCache);
|
||||
if (store.focusJob) jobNav.toPipeline(pipeline.id);
|
||||
const pipeline = jobCompose(builderCache);
|
||||
if (store.focusJob) toPipeline(pipeline.id);
|
||||
};
|
||||
|
||||
const jobOnClick = (e) => {
|
||||
|
@ -86,10 +80,10 @@ export default function CatalogBox(props) {
|
|||
navigateToJob();
|
||||
};
|
||||
|
||||
function jobIcon() {
|
||||
if (pipeline) return usePipelineIconState(pipeline);
|
||||
function boxIcon() {
|
||||
if (pipeline) return pipelineIcon(pipeline);
|
||||
if (!job) return <PlayArrowIcon />;
|
||||
return useJobIconState(job);
|
||||
return jobIcon(job);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -130,7 +124,7 @@ export default function CatalogBox(props) {
|
|||
component="span"
|
||||
onClick={jobOnClick}
|
||||
>
|
||||
{jobIcon()}
|
||||
{boxIcon()}
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</AccordionSummary>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { useState, useContext } from "react";
|
||||
import { useCurrentlyFailing, useSilencedAlerts } from "@qltr/queries";
|
||||
import JobContext from "@qltr/jobs";
|
||||
import { useJobNav } from "@qltr/util/JobTools";
|
||||
import JobContext from "@qltr/jobctx";
|
||||
import SilenceDialog, { useSilenceDialog } from "../alerting/SilenceDialog.jsx";
|
||||
import FailingBox from "./FailingBox.jsx";
|
||||
import QuickSilence, { useQuickSilence } from "./QuickSilence.jsx";
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import React, { useState, useContext } from "react";
|
||||
import { usePipelineMappings, useIgnoreResult } from "@qltr/queries";
|
||||
import StoreContext from "@qltr/store";
|
||||
import JobContext, { jobStatus } from "@qltr/jobs";
|
||||
import {
|
||||
useJobIconState,
|
||||
usePipelineIconState,
|
||||
useJobNav,
|
||||
} from "@qltr/util/JobTools";
|
||||
|
||||
import { useJobCore, jobStatus } from "@qltr/jobcore";
|
||||
import useMediaQuery from "@mui/material/useMediaQuery";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import Accordion from "@mui/material/Accordion";
|
||||
|
@ -63,9 +59,8 @@ export default function FailingBox(props) {
|
|||
const runHistory = recentResults ? [...recentResults].reverse() : null;
|
||||
|
||||
const { data: pipelineMappings, isLoading } = usePipelineMappings();
|
||||
const { jobFactory } = useContext(JobContext);
|
||||
const { jobCompose, jobIcon, pipelineIcon } = useJobCore();
|
||||
const { state: store, updateStore, removeFailure } = useContext(StoreContext);
|
||||
const jobNav = useJobNav();
|
||||
const theme = useTheme();
|
||||
const minifyActions = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const [open, setOpen] = useState(false);
|
||||
|
@ -97,22 +92,22 @@ export default function FailingBox(props) {
|
|||
selectedBranches: as1d(primaries),
|
||||
isTriage: store.triageFailing,
|
||||
};
|
||||
const pipeline = jobFactory(builderCache);
|
||||
if (store.focusJob) jobNav.toPipeline(pipeline.id);
|
||||
const pipeline = jobCompose(builderCache);
|
||||
if (store.focusJob) toPipeline(pipeline.id);
|
||||
};
|
||||
|
||||
const retryTest = () => {
|
||||
if (isPipeline) return retryPipelineTest();
|
||||
const jobId = jobFactory({
|
||||
const jobId = jobCompose({
|
||||
testNames: [testName],
|
||||
isTriage: store.triageFailing,
|
||||
});
|
||||
if (store.focusJob) jobNav.toJob(jobId);
|
||||
if (store.focusJob) toJob(jobId);
|
||||
};
|
||||
|
||||
const navigateToJob = () => {
|
||||
if (pipeline) return jobNav.toPipeline(pipeline.id);
|
||||
jobNav.toJob(job.jobId);
|
||||
if (pipeline) return toPipeline(pipeline.id);
|
||||
toJob(job.jobId);
|
||||
};
|
||||
|
||||
const jobOnClick = () => {
|
||||
|
@ -121,10 +116,10 @@ export default function FailingBox(props) {
|
|||
navigateToJob();
|
||||
};
|
||||
|
||||
function jobIcon() {
|
||||
if (pipeline) return usePipelineIconState(pipeline);
|
||||
function boxIcon() {
|
||||
if (pipeline) return pipelineIcon(pipeline);
|
||||
if (!job) return <ReplayIcon />;
|
||||
return useJobIconState(job);
|
||||
return jobIcon(job);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -220,7 +215,7 @@ export default function FailingBox(props) {
|
|||
</a>
|
||||
|
||||
<IconButton aria-label="retry" component="span" onClick={jobOnClick}>
|
||||
{jobIcon()}
|
||||
{boxIcon()}
|
||||
</IconButton>
|
||||
|
||||
<IconButton
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
// React
|
||||
import React, { useState, useContext } from "react";
|
||||
import JobContext from "@qltr/jobs";
|
||||
import StoreContext from "@qltr/store";
|
||||
import { useJobNav } from "@qltr/util/JobTools";
|
||||
import { useJobCore } from "@qltr/jobcore";
|
||||
|
||||
// Components
|
||||
import Button from "@mui/material/Button";
|
||||
|
@ -18,17 +17,16 @@ import ReplayIcon from "@mui/icons-material/Replay";
|
|||
|
||||
export default function FailingRetry(props) {
|
||||
const { failing } = props;
|
||||
const { state: jobState, retryAll } = useContext(JobContext);
|
||||
const { state: jobState, retryAll, toJob } = useJobCore();
|
||||
const { state: store } = useContext(StoreContext);
|
||||
const [open, setOpen] = useState(false);
|
||||
const jobNav = useJobNav();
|
||||
const toggleOpen = () => setOpen(!open);
|
||||
const dialogClose = (confirmed) => () => {
|
||||
toggleOpen();
|
||||
if (!confirmed) return;
|
||||
const jobId = retryAll(failing);
|
||||
if (!store.focusJob) return;
|
||||
jobNav.toJob(jobId);
|
||||
toJob(jobId);
|
||||
};
|
||||
if (!failing || failing.length === 0) return;
|
||||
return (
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from "react";
|
||||
|
||||
import { useJobIconState } from "@qltr/util/JobTools";
|
||||
import { useJobCore } from "@qltr/jobcore";
|
||||
import Accordion from "@mui/material/Accordion";
|
||||
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
@ -10,8 +10,8 @@ import IconButton from "@mui/material/IconButton";
|
|||
import Stack from "@mui/material/Stack";
|
||||
|
||||
export default function JobBox(props) {
|
||||
const { jobIcon } = useJobCore();
|
||||
const { job } = props;
|
||||
|
||||
const { name, status } = job;
|
||||
|
||||
return (
|
||||
|
@ -30,7 +30,7 @@ export default function JobBox(props) {
|
|||
</Typography>
|
||||
<Stack sx={{ ml: "auto" }}>
|
||||
<IconButton aria-label="retry" component="span">
|
||||
{useJobIconState(job)}
|
||||
{jobIcon(job)}
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</AccordionSummary>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from "react";
|
||||
import { jobStatus } from "@qltr/jobs";
|
||||
import { jobStatus } from "@qltr/jobcore";
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useState, useContext } from "react";
|
||||
import StoreContext from "@qltr/store";
|
||||
import JobContext, { jobStatus } from "@qltr/jobs";
|
||||
import { usePipelineIconState } from "@qltr/util/JobTools";
|
||||
import { useJobCore, jobStatus } from "@qltr/jobcore";
|
||||
|
||||
import Accordion from "@mui/material/Accordion";
|
||||
import AccordionDetails from "@mui/material/AccordionDetails";
|
||||
|
@ -21,7 +20,8 @@ import Stack from "@mui/material/Stack";
|
|||
|
||||
export default function JobPipelineBox(props) {
|
||||
const { pipeline } = props;
|
||||
const pipelineIcon = usePipelineIconState(pipeline);
|
||||
const { pipelineIcon } = useJobCore();
|
||||
const boxIcon = pipelineIcon(pipeline);
|
||||
|
||||
return (
|
||||
<Accordion expanded={false} disableGutters={true} square>
|
||||
|
@ -39,7 +39,7 @@ export default function JobPipelineBox(props) {
|
|||
</Typography>
|
||||
<Stack sx={{ ml: "auto" }}>
|
||||
<IconButton aria-label="" component="span">
|
||||
{pipelineIcon}
|
||||
{boxIcon}
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</AccordionSummary>
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
import React, { useContext } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import JobContext, { jobStatus } from "@qltr/jobs";
|
||||
import {
|
||||
selectedPipelineBranches,
|
||||
pipelineJobs,
|
||||
findPipelineJobByTestName,
|
||||
useJobIconState,
|
||||
useJobNav,
|
||||
} from "@qltr/util/JobTools";
|
||||
import { useJobCore, jobStatus } from "@qltr/jobcore";
|
||||
|
||||
import Box from "@mui/material/Box";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
|
@ -31,28 +24,29 @@ import DeleteIcon from "@mui/icons-material/Delete";
|
|||
|
||||
function JobPipelineDisplay(props) {
|
||||
const { pipeline } = props;
|
||||
|
||||
const {
|
||||
state: jobState,
|
||||
pipelineCancel,
|
||||
pipelineDestroy,
|
||||
} = useContext(JobContext);
|
||||
selectedPipelineBranches,
|
||||
pipelineJobs,
|
||||
findPipelineJobByTestName,
|
||||
toJob,
|
||||
jobIcon,
|
||||
} = useJobCore();
|
||||
|
||||
const jobNav = useJobNav();
|
||||
const nav = useNavigate();
|
||||
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const handleClick = (event) => setAnchorEl(event.currentTarget);
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
|
||||
const selectJob = (testName) => () => {
|
||||
const job = findPipelineJobByTestName(pipeline, jobState.jobs, testName);
|
||||
const job = findPipelineJobByTestName(pipeline, testName);
|
||||
if (!job) return;
|
||||
jobNav.toJob(job.jobId);
|
||||
toJob(job.jobId);
|
||||
};
|
||||
|
||||
function cancelPipeline() {
|
||||
|
@ -69,16 +63,16 @@ function JobPipelineDisplay(props) {
|
|||
};
|
||||
|
||||
function pipelineActive() {
|
||||
return pipelineJobs(pipeline, jobState.jobs).find(
|
||||
return pipelineJobs(pipeline).find(
|
||||
(j) => j.status === jobStatus.ACTIVE || j.status === jobStatus.PENDING
|
||||
);
|
||||
}
|
||||
|
||||
function jobIcon(name) {
|
||||
function boxIcon(name) {
|
||||
if (pipeline.isCanceled) return <DoNotDisturbIcon color="warning" />;
|
||||
const job = findPipelineJobByTestName(pipeline, jobState.jobs, name);
|
||||
const job = findPipelineJobByTestName(pipeline, name);
|
||||
if (!job) return <ViewColumnIcon color="secondary" />;
|
||||
return useJobIconState(job);
|
||||
return jobIcon(job);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -133,7 +127,7 @@ function JobPipelineDisplay(props) {
|
|||
</Typography>
|
||||
<Stack sx={{ ml: "auto" }}>
|
||||
<IconButton aria-label="retry" component="span">
|
||||
{jobIcon(test.name)}
|
||||
{boxIcon(test.name)}
|
||||
</IconButton>
|
||||
</Stack>
|
||||
</AccordionSummary>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useContext, useState, useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import JobContext, { jobStatus } from "@qltr/jobs";
|
||||
import { useJobCore, jobStatus } from "@qltr/jobcore";
|
||||
import StoreContext from "@qltr/store";
|
||||
import Box from "@mui/material/Box";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
|
@ -25,16 +25,12 @@ import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
|||
export default function JobPipelinePendingView(props) {
|
||||
const navigate = useNavigate();
|
||||
const { job } = props;
|
||||
const { jobFactory, jobCancel, jobDestroy } = useContext(JobContext);
|
||||
const { jobCompose, jobCancel, jobDestroy } = useJobCore();
|
||||
const { state: store } = useContext(StoreContext);
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const handleClick = (event) => setAnchorEl(event.currentTarget);
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
|
||||
function download(filename, text) {
|
||||
var element = document.createElement("a");
|
||||
|
@ -50,7 +46,7 @@ export default function JobPipelinePendingView(props) {
|
|||
}
|
||||
|
||||
function retryJob() {
|
||||
const jobId = jobFactory(job.builderCache);
|
||||
const jobId = jobCompose(job.builderCache);
|
||||
if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useContext, useState, useEffect } from "react";
|
||||
import { useJobNav } from "@qltr/util/JobTools";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import JobContext, { jobStatus } from "@qltr/jobs";
|
||||
import { useJobCore, jobStatus } from "@qltr/jobcore";
|
||||
import StoreContext from "@qltr/store";
|
||||
import Box from "@mui/material/Box";
|
||||
import AppBar from "@mui/material/AppBar";
|
||||
|
@ -25,18 +24,14 @@ import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
|||
|
||||
export default function JobView(props) {
|
||||
const { job } = props;
|
||||
const { jobFactory, jobCancel, jobDestroy } = useContext(JobContext);
|
||||
const { jobCompose, jobCancel, jobDestroy, toPipeline, toJob } = useJobCore();
|
||||
const { state: store } = useContext(StoreContext);
|
||||
const jobNav = useJobNav();
|
||||
|
||||
const nav = useNavigate();
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const handleClick = (event) => setAnchorEl(event.currentTarget);
|
||||
const handleClose = () => setAnchorEl(null);
|
||||
|
||||
function download(filename, text) {
|
||||
var element = document.createElement("a");
|
||||
|
@ -52,8 +47,8 @@ export default function JobView(props) {
|
|||
}
|
||||
|
||||
function retryJob() {
|
||||
const jobId = jobFactory(job.builderCache);
|
||||
if (store.focusJob) jobNav.toJob(jobId);
|
||||
const jobId = jobCompose(job.builderCache);
|
||||
if (store.focusJob) toJob(jobId);
|
||||
}
|
||||
|
||||
function downloadLog() {
|
||||
|
@ -76,8 +71,8 @@ export default function JobView(props) {
|
|||
};
|
||||
|
||||
function navigateToJobs() {
|
||||
if (job.isPipeline) return jobNav.toPipeline(job.pipelineId);
|
||||
jobNav.toJobs();
|
||||
if (job.isPipeline) return toPipeline(job.pipelineId);
|
||||
toJobs();
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useContext, useEffect } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
|
||||
import JobContext from "@qltr/jobs";
|
||||
import JobContext from "@qltr/jobctx";
|
||||
import JobBox from "./JobBox.jsx";
|
||||
import JobPipelineBox from "./JobPipelineBox.jsx";
|
||||
import JobView from "./JobView.jsx";
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useContext, useState } from "react";
|
||||
import StoreContext from "@qltr/store";
|
||||
import JobContext from "@qltr/jobs";
|
||||
import { useJobNav } from "@qltr/util/JobTools";
|
||||
import { useJobCore } from "@qltr/jobcore";
|
||||
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import Toolbar from "@mui/material/Toolbar";
|
||||
|
@ -28,8 +27,7 @@ import PipelineConfirm from "./PipelineConfirm.jsx";
|
|||
|
||||
export default function JobBuilder() {
|
||||
const { state: store } = useContext(StoreContext);
|
||||
const { jobFactory } = useContext(JobContext);
|
||||
const jobNav = useJobNav();
|
||||
const { jobCompose } = useJobCore();
|
||||
const [quickOpen, setQuickOpen] = useState(false);
|
||||
const [jobDialogOpen, setJobDialogOpen] = useState(false);
|
||||
|
||||
|
@ -52,8 +50,8 @@ export default function JobBuilder() {
|
|||
const handleClose = (confirmed) => () => {
|
||||
setJobDialogOpen(false);
|
||||
if (!confirmed) return;
|
||||
const jobId = jobFactory({ ...cache, isTriage: store.triageFailing });
|
||||
if (store.focusJob) jobNav.toJob(jobId);
|
||||
const jobId = jobCompose({ ...cache, isTriage: store.triageFailing });
|
||||
if (store.focusJob) toJob(jobId);
|
||||
};
|
||||
|
||||
// Pull info from url if possible?
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue