Revamp Job flow

This commit is contained in:
Elijah Dunemask 2022-10-15 11:47:47 +00:00
parent 945afdfbbe
commit 4a0a4b29a5
86 changed files with 592 additions and 608 deletions

View file

@ -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

View file

@ -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
View 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
View 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,
};
}

View 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 };
}

View 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 };
}

View file

@ -0,0 +1,9 @@
export const jobStatus = {
OK: "o",
QUEUED: "q",
PENDING: "p",
CANCELED: "c",
ACTIVE: "a",
ERROR: "e",
};
export const socketUrl = "/";

View file

@ -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";

View file

@ -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) => {

View file

@ -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";

View file

@ -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)
);

View file

@ -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>

View file

@ -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";

View file

@ -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

View file

@ -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 (

View file

@ -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>

View file

@ -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";

View file

@ -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>

View file

@ -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>

View file

@ -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}`);
}

View file

@ -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 (

View file

@ -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";

View file

@ -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?