Refactor and more linking

This commit is contained in:
Dunemask 2022-08-12 13:08:00 +00:00
parent f17c7e01f5
commit 2db11ac3dd
24 changed files with 197 additions and 177 deletions

View file

@ -1,7 +1,7 @@
// Import Contexts
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { JobProvider } from "./ctx/JobContext.jsx";
import { StoreProvider } from "./ctx/StoreContext.jsx";
import { JobProvider } from "@qltr/jobs";
import { StoreProvider } from "@qltr/store";
import { BrowserRouter } from "react-router-dom";
// Import Views
import Views from "./views/Views.jsx";

View file

@ -1,5 +1,5 @@
import React, { useReducer, createContext, useMemo } from "react";
import Initiator from "../../lib/sockets/clients/Initiator.js";
import Initiator from "@qltr/initiator";
const JobContext = createContext();
export const jobStatus = {

56
src/util/JobTools.jsx Normal file
View file

@ -0,0 +1,56 @@
import { useContext } from "react";
import JobContext, { jobStatus } from "@qltr/jobs";
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 const pipelineJobs = (pipeline, jobs) =>
jobs.filter((j) => j.isPipeline && j.pipelineId === pipeline.id);
export const useJobIconState = (job) => statusIcon(job.status);
export const usePipelineIconState = (pipeline) => {
const { state: jobState } = useContext(JobContext);
const jobStatuses = pipelineJobs(pipeline, jobState.jobs).map(
(j) => j.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 (pipeline.isCanceled) return statusIcon(jobStatus.CANCELED);
return statusIcon(jobStatus.QUEUED);
};
export const selectedPipelineBranches = (pipeline) =>
pipeline.branches.map((b) =>
b.filter((t) => pipeline.selectedBranches.includes(t))
);
export const findPipelineJobByTestName = (pipeline, jobs, testName) =>
pipelineJobs(pipeline, jobs).find((j) => j.branchId === testName);

View file

@ -1,7 +1,7 @@
import { useContext, useState } from "react";
import { useCurrentlyFailing } from "../Queries.jsx";
import JobContext from "../ctx/JobContext.jsx";
import StoreContext from "../ctx/StoreContext.jsx";
import { useCurrentlyFailing } from "@qltr/queries";
import JobContext from "@qltr/jobs";
import StoreContext from "@qltr/store";
import { Link, useLocation } from "react-router-dom";
import AppBar from "@mui/material/AppBar";
@ -28,7 +28,7 @@ import SentimentSatisfiedAltIcon from "@mui/icons-material/SentimentSatisfiedAlt
const drawerWidth = 250;
export default function Navbar(props) {
export default function Navbar() {
const { state: jobState } = useContext(JobContext);
const { state: store } = useContext(StoreContext);
const { isLoading, data: failing } = useCurrentlyFailing();

View file

@ -2,7 +2,7 @@ import { useContext } from "react";
import { Routes, Route, Navigate } from "react-router-dom";
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import StoreContext from "../ctx/StoreContext.jsx";
import StoreContext from "@qltr/store";
// Import Navbar
import Navbar from "./Navbar.jsx";
// Import Pages

View file

@ -1,6 +1,6 @@
import React, { useState, useContext } from "react";
import { useSilencedAlerts } from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx";
import { useSilencedAlerts } from "@qltr/queries";
import StoreContext from "@qltr/store";
import SilencedBox from "./SilencedBox.jsx";
import SilenceDialog from "./SilenceDialog.jsx";

View file

@ -1,5 +1,5 @@
import { useState, useContext, useEffect } from "react";
import StoreContext from "../../ctx/StoreContext.jsx";
import StoreContext from "@qltr/store";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useTheme } from "@mui/material/styles";

View file

@ -1,5 +1,5 @@
import React, { useState, useContext } from "react";
import StoreContext from "../../ctx/StoreContext.jsx";
import StoreContext from "@qltr/store";
import Accordion from "@mui/material/Accordion";
import AccordionSummary from "@mui/material/AccordionSummary";
@ -8,7 +8,6 @@ import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import Stack from "@mui/material/Stack";
export default function SilencingBox(props) {

View file

@ -1,13 +1,14 @@
import { useEffect, useContext } from "react";
import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext from "../../ctx/JobContext.jsx";
import StoreContext from "@qltr/store";
import JobContext from "@qltr/jobs";
import CatalogBox from "./CatalogBox.jsx";
import CatalogSearch from "./CatalogSearch.jsx";
import { useCatalogTests } from "../../Queries.jsx";
import { useCatalogTests } from "@qltr/queries";
import TextField from "@mui/material/TextField";
export default function Catalog() {
const { state: store, updateStore } = useContext(StoreContext);
const { state: jobState } = useContext(JobContext);
const { isLoading, data: tests } = useCatalogTests();
const handleSearchChange = (e) =>
updateStore({ catalogSearch: e.target.value });
@ -20,6 +21,31 @@ export default function Catalog() {
};
}, []);
const catalogWithJobs = () => {
for (var test of tests) {
if (test.isPipeline) {
const pipeline = jobState.pipelines.find((p) =>
p.selectedBranches.includes(test.name)
);
if (!pipeline) continue;
const pipelineJob = jobState.jobs.find(
(j) =>
j.isPipeline &&
j.pipelineId === pipeline.id &&
j.branchId === test.name
);
if (!pipelineJob) test.pipeline = pipeline;
test.job = pipelineJob;
continue;
}
const job = jobState.jobs.find(
(j) => !j.isPipeline && j.builderCache.testNames.includes(test.name)
);
if (job) test.job = job;
}
return tests;
};
return (
<div className="catalog">
<CatalogSearch
@ -30,7 +56,9 @@ export default function Catalog() {
<h6>{store.catalogSearch}</h6>
{isLoading
? null
: tests.map((v, i) => <CatalogBox key={i} catalogTest={v} />)}
: catalogWithJobs().map((v, i) => (
<CatalogBox key={i} catalogTest={v} />
))}
</div>
);
}

View file

@ -1,8 +1,9 @@
import React, { useState, useContext } from "react";
import { useNavigate } from "react-router-dom";
import { usePipelineMappings } from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext from "../../ctx/JobContext.jsx";
import { usePipelineMappings } from "@qltr/queries";
import StoreContext from "@qltr/store";
import JobContext, { jobStatus } from "@qltr/jobs";
import { useJobIconState, usePipelineIconState } from "@qltr/util/JobTools";
import Accordion from "@mui/material/Accordion";
import AccordionDetails from "@mui/material/AccordionDetails";
@ -11,15 +12,11 @@ import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
import Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
import {
selectBranch,
asTree,
asBranches,
as1d,
} from "../../../lib/jobs/pipelines.js";
import { asTree, asBranches, as1d } from "@qltr/util/pipelines.js";
export default function CatalogBox(props) {
const { catalogTest } = props;
@ -30,24 +27,28 @@ export default function CatalogBox(props) {
repo: testRepo,
isPipeline,
type: testType,
job,
pipeline,
} = catalogTest;
const navigate = useNavigate();
const { data: pipelineMappings, isLoading } = usePipelineMappings();
const { state: store, updateStore } = useContext(StoreContext);
const { state: store } = useContext(StoreContext);
const { state: jobState, jobFactory } = useContext(JobContext);
const { jobFactory } = useContext(JobContext);
const [open, setOpen] = useState(false);
const toggleOpen = () => setOpen(!open);
const runTest = (e) => {
e.preventDefault();
e.stopPropagation();
console.log(catalogTest);
const navigateToJob = () => {
if (pipeline) return navigate(`/qualiteer/jobs#p${pipeline.id}`);
navigate(`/qualiteer/jobs#${job.jobId}`);
};
const runTest = () => {
if (isPipeline) return runPipelineTest();
const jobId = jobFactory({ testNames: [testName] });
const jobId = jobFactory({ testNames: [testName], isTriage: true });
if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
};
@ -63,6 +64,20 @@ export default function CatalogBox(props) {
if (store.focusJob) navigate(`/qualiteer/jobs#p${pipeline.id}`);
};
const jobOnClick = (e) => {
e.preventDefault();
e.stopPropagation();
if (pipeline) return navigateToJob();
if (!job) return runTest();
navigateToJob();
};
function jobIcon() {
if (pipeline) return usePipelineIconState(pipeline);
if (!job) return <PlayArrowIcon />;
return useJobIconState(job);
}
function Actions() {
return (
<React.Fragment>
@ -70,9 +85,9 @@ export default function CatalogBox(props) {
color="success"
aria-label="play"
component="span"
onClick={runTest}
onClick={jobOnClick}
>
<PlayArrowIcon />
{jobIcon()}
</IconButton>
</React.Fragment>
);
@ -98,6 +113,11 @@ export default function CatalogBox(props) {
{`${testClass}#`}
<Box fontWeight="bold" display="inline">
{testName}
{isPipeline && (
<IconButton component="span">
<ViewColumnIcon />
</IconButton>
)}
</Box>
<br />
</Typography>
@ -119,7 +139,7 @@ export default function CatalogBox(props) {
</AccordionSummary>
<AccordionDetails>
<Typography component={"span"} style={{ wordBreak: "break-word" }}>
{JSON.stringify(catalogTest)}
{"Test info"}
</Typography>
</AccordionDetails>
</Accordion>

View file

@ -1,8 +1,8 @@
import { useState, useContext } from "react";
import { useNavigate } from "react-router-dom";
import { useCurrentlyFailing, useSilencedAlerts } from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext from "../../ctx/JobContext.jsx";
import { useCurrentlyFailing, useSilencedAlerts } from "@qltr/queries";
import StoreContext from "@qltr/store";
import JobContext from "@qltr/jobs";
import SilenceDialog from "../alerting/SilenceDialog.jsx";
import FailingBox from "./FailingBox.jsx";
@ -14,20 +14,14 @@ import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import ReplayIcon from "@mui/icons-material/Replay";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
export default function Failing() {
const { state: jobState, retryAll } = useContext(JobContext);
const navigate = useNavigate();
const {
state: store,
updateStore,
silenceRequest,
} = useContext(StoreContext);
const { state: store, silenceRequest } = useContext(StoreContext);
const { isLoading, data: failing } = useCurrentlyFailing();
const { isSilencedLoading, data: silencedAlerts } = useSilencedAlerts();
const [silenceEntry, setSilenceEntry] = useState({ open: false });

View file

@ -1,8 +1,9 @@
import React, { useState, useContext } from "react";
import { useNavigate } from "react-router-dom";
import { usePipelineMappings } from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext, { jobStatus } from "../../ctx/JobContext.jsx";
import { usePipelineMappings } from "@qltr/queries";
import StoreContext from "@qltr/store";
import JobContext, { jobStatus } from "@qltr/jobs";
import { useJobIconState, usePipelineIconState } from "@qltr/util/JobTools";
import Accordion from "@mui/material/Accordion";
import AccordionDetails from "@mui/material/AccordionDetails";
@ -33,12 +34,7 @@ import Badge from "@mui/material/Badge";
import Stack from "@mui/material/Stack";
import Box from "@mui/material/Box";
import {
selectBranch,
asTree,
asBranches,
as1d,
} from "../../../lib/jobs/pipelines.js";
import { asTree, asBranches, as1d } from "@qltr/util/pipelines.js";
const stopPropagation = (e) => e.stopPropagation() && e.preventDefault();
@ -112,45 +108,13 @@ export default function FailingBox(props) {
const jobOnClick = () => {
if (pipeline) return navigateToJob;
if (!job) return retryTest;
switch (job.status) {
case jobStatus.OK:
return navigateToJob;
case jobStatus.ERROR:
return retryTest;
case jobStatus.PENDING:
return navigateToJob;
case jobStatus.ACTIVE:
return navigateToJob;
case jobStatus.CANCELED:
return navigateToJob;
case jobStatus.QUEUED:
return navigateToJob;
default:
return retryTest;
}
navigateToJob();
};
function jobIcon() {
if (pipeline && pipeline.isCanceled)
return <DoNotDisturbIcon color="warning" />;
if (pipeline) return <ViewColumnIcon color="secondary" />;
if (pipeline) return usePipelineIconState(pipeline);
if (!job) return <ReplayIcon />;
switch (job.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 />;
}
return useJobIconState(job);
}
function Actions() {

View file

@ -1,21 +1,12 @@
import React, { useState, useContext } from "react";
import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext, { jobStatus } from "../../ctx/JobContext.jsx";
import React from "react";
import { useJobIconState } from "@qltr/util/JobTools";
import Accordion from "@mui/material/Accordion";
import AccordionDetails from "@mui/material/AccordionDetails";
import AccordionSummary from "@mui/material/AccordionSummary";
import Typography from "@mui/material/Typography";
import IconButton from "@mui/material/IconButton";
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 Box from "@mui/material/Box";
import Stack from "@mui/material/Stack";
export default function JobBox(props) {
@ -23,25 +14,6 @@ export default function JobBox(props) {
const { name, status } = job;
function jobIcon() {
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 />;
}
}
return (
<Accordion expanded={false} disableGutters={true} square>
<AccordionSummary
@ -58,7 +30,7 @@ export default function JobBox(props) {
</Typography>
<Stack sx={{ ml: "auto" }}>
<IconButton aria-label="retry" component="span">
{jobIcon()}
{useJobIconState(job)}
</IconButton>
</Stack>
</AccordionSummary>

View file

@ -1,5 +1,5 @@
import React from "react";
import { jobStatus } from "../../ctx/JobContext.jsx";
import { jobStatus } from "@qltr/jobs";
import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";

View file

@ -1,6 +1,7 @@
import React, { useState, useContext } from "react";
import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext, { jobStatus } from "../../ctx/JobContext.jsx";
import StoreContext from "@qltr/store";
import JobContext, { jobStatus } from "@qltr/jobs";
import { usePipelineIconState } from "@qltr/util/JobTools";
import Accordion from "@mui/material/Accordion";
import AccordionDetails from "@mui/material/AccordionDetails";
@ -20,10 +21,7 @@ import Stack from "@mui/material/Stack";
export default function JobPipelineBox(props) {
const { pipeline } = props;
function jobIcon() {
return <ViewColumnIcon />;
}
const pipelineIcon = usePipelineIconState(pipeline);
return (
<Accordion expanded={false} disableGutters={true} square>
@ -41,7 +39,7 @@ export default function JobPipelineBox(props) {
</Typography>
<Stack sx={{ ml: "auto" }}>
<IconButton aria-label="" component="span">
{jobIcon()}
{pipelineIcon}
</IconButton>
</Stack>
</AccordionSummary>

View file

@ -1,6 +1,12 @@
import React, { useContext } from "react";
import { useNavigate } from "react-router-dom";
import JobContext, { jobStatus } from "../../ctx/JobContext.jsx";
import JobContext, { jobStatus } from "@qltr/jobs";
import {
selectedPipelineBranches,
pipelineJobs,
findPipelineJobByTestName,
useJobIconState,
} from "@qltr/util/JobTools";
import Box from "@mui/material/Box";
import AppBar from "@mui/material/AppBar";
@ -44,20 +50,8 @@ function JobPipelineDisplay(props) {
setAnchorEl(null);
};
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 findJob = (testName) =>
pipelineJobs.find((j) => j.branchId === testName);
const selectJob = (testName) => () => {
const job = findJob(testName);
const job = findPipelineJobByTestName(pipeline, jobState.jobs, testName);
if (!job) return;
navigate(`/qualiteer/jobs#${job.jobId}`);
};
@ -80,31 +74,16 @@ function JobPipelineDisplay(props) {
};
function pipelineActive() {
return pipelineJobs.find(
return pipelineJobs(pipeline, jobState.jobs).find(
(j) => j.status === jobStatus.ACTIVE || j.status === jobStatus.PENDING
);
}
function jobIcon(name) {
const job = findJob(name);
const status = job ? job.status : null;
if (pipeline.isCanceled) return <DoNotDisturbIcon color="warning" />;
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 <ViewColumnIcon color="secondary" />;
}
const job = findPipelineJobByTestName(pipeline, jobState.jobs, name);
if (!job) return <ViewColumnIcon color="secondary" />;
return useJobIconState(job);
}
return (
@ -133,7 +112,7 @@ function JobPipelineDisplay(props) {
</Box>
</AppBar>
<Toolbar disableGutters />
{selectedBranches().map((track, i) => (
{selectedPipelineBranches(pipeline).map((track, i) => (
<React.Fragment key={i}>
<Typography variant="h6">{i + 1}</Typography>
<Box>

View file

@ -1,7 +1,7 @@
import React, { useContext, useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import JobContext, { jobStatus } from "../../ctx/JobContext.jsx";
import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext, { jobStatus } from "@qltr/jobs";
import StoreContext from "@qltr/store";
import Box from "@mui/material/Box";
import AppBar from "@mui/material/AppBar";
import Toolbar from "@mui/material/Toolbar";

View file

@ -1,7 +1,7 @@
import React, { useState, useContext, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import JobContext from "../../ctx/JobContext.jsx";
import JobContext from "@qltr/jobs";
import JobBox from "./JobBox.jsx";
import JobPipelineBox from "./JobPipelineBox.jsx";
import JobView from "./JobView.jsx";

View file

@ -1,7 +1,7 @@
import React, { useContext, useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import StoreContext from "../../../ctx/StoreContext.jsx";
import JobContext from "../../../ctx/JobContext.jsx";
import StoreContext from "@qltr/store";
import JobContext from "@qltr/jobs";
import Dialog from "@mui/material/Dialog";
import Toolbar from "@mui/material/Toolbar";

View file

@ -1,5 +1,5 @@
import React, { useContext } from "react";
import { usePipelineMappings } from "../../../Queries.jsx";
import React from "react";
import { usePipelineMappings } from "@qltr/queries";
import Button from "@mui/material/Button";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";

View file

@ -13,7 +13,7 @@ import {
asTree,
asBranches,
as1d,
} from "../../../../lib/jobs/pipelines.js";
} from "@qltr/util/pipelines.js";
function PipelineTrackSelector(props) {
const { cache, setCache, back, next } = props;

View file

@ -1,5 +1,6 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "node:path";
export default () => {
return defineConfig({
@ -10,16 +11,25 @@ export default () => {
port: 443,
},
proxy: {
'/api': 'http://localhost:52000',
'/socket.io': {
target: 'ws://localhost:52000',
ws: true
}
}
"/api": "http://localhost:52000",
"/socket.io": {
target: "ws://localhost:52000",
ws: true,
},
},
},
build: {
outDir: "./build",
},
base: "/qualiteer/",
resolve: {
alias: {
"@qltr/util": path.resolve("./src/util/"),
"@qltr/queries": path.resolve("./src/util/queries"),
"@qltr/jobs": path.resolve("./src/ctx/JobContext.jsx"),
"@qltr/store": path.resolve("./src/ctx/StoreContext.jsx"),
"@qltr/initiator": path.resolve("./lib/sockets/clients/Initiator.js"),
},
},
});
};