Upgrades people!

This commit is contained in:
Dunemask 2022-08-09 04:29:10 +00:00
parent f84234150f
commit 8ad56e8d38
40 changed files with 483 additions and 379 deletions

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -1,7 +1,7 @@
export default function executorConfig(args){ export default function executorConfig(payload){
return { return {
command: ()=> args.slice(2), command: ({command})=> command,
url: process.env.QUALITEER_URL, url: ({url})=>url ,
jobId: () => args[1], jobId: ({jobId}) => jobId,
} }
} }

View file

@ -10,13 +10,15 @@ const table = "silenced_tests";
const PG_DISABLED = process.env.POSTGRES_DISABLED; const PG_DISABLED = process.env.POSTGRES_DISABLED;
const silencedMock = () => { const silencedMock = () => {
return [{ return [
name: `failing`, {
class: `failing.js`, name: `failing`,
method: "FAKEMETHOD", class: `failing.js`,
id: 0, method: "FAKEMETHOD",
silencedUntil: new Date().toJSON(), id: 0,
}] silencedUntil: new Date().toJSON(),
},
];
}; };
// Queries // Queries

View file

@ -20,12 +20,12 @@ const testsMock = () => {
isCompound: false, isCompound: false,
type: "api", type: "api",
description: "This is a single test", description: "This is a single test",
tags: ["cron_1hour","reg_us", "env_ci", "proj_core", "skip_alt"], tags: ["cron_1hour", "reg_us", "env_ci", "proj_core", "skip_alt"],
path: "tests/assets/suite/single.js", path: "tests/assets/suite/single.js",
created: Date.now(), created: Date.now(),
mergeRequest: "https://example.com" mergeRequest: "https://example.com",
}, },
{ {
id: 1, id: 1,
name: "failing", name: "failing",
class: "failing.js", class: "failing.js",
@ -33,12 +33,12 @@ const testsMock = () => {
isCompound: false, isCompound: false,
type: "ui", type: "ui",
description: "This is a failing test", description: "This is a failing test",
tags: ["cron_1hour","reg_us", "env_ci", "proj_core"], tags: ["cron_1hour", "reg_us", "env_ci", "proj_core"],
path: "tests/assets/suite/failing.js", path: "tests/assets/suite/failing.js",
created: Date.now(), created: Date.now(),
mergeRequest: "https://example.com" mergeRequest: "https://example.com",
}, },
{ {
id: 2, id: 2,
name: "primary", name: "primary",
class: "primary.js", class: "primary.js",
@ -46,11 +46,18 @@ const testsMock = () => {
isCompound: true, isCompound: true,
type: "api", type: "api",
description: "This is a primary test", description: "This is a primary test",
tags: ["cron_1hour","reg_us", "proj_core", "skip_alt", "compound_secondary"], tags: [
"cron_1hour",
"reg_us",
"proj_core",
"skip_alt",
"compound_secondary",
],
path: "tests/assets/suite/primary.js", path: "tests/assets/suite/primary.js",
created: Date.now(), created: Date.now(),
mergeRequest: "https://example.com" mergeRequest: "https://example.com",
}, { },
{
id: 3, id: 3,
name: "secondary", name: "secondary",
class: "secondary.js", class: "secondary.js",
@ -58,12 +65,12 @@ const testsMock = () => {
isCompound: true, isCompound: true,
type: "api", type: "api",
description: "This is a secondary test", description: "This is a secondary test",
tags: ["cron_1hour","reg_us", "proj_core", "compound_tertiary"], tags: ["cron_1hour", "reg_us", "proj_core", "compound_tertiary"],
path: "tests/assets/suite/secondary.js", path: "tests/assets/suite/secondary.js",
created: Date.now(), created: Date.now(),
mergeRequest: "https://example.com" mergeRequest: "https://example.com",
}, },
{ {
id: 4, id: 4,
name: "tertiary", name: "tertiary",
class: "tertiary.js", class: "tertiary.js",
@ -71,21 +78,21 @@ const testsMock = () => {
isCompound: true, isCompound: true,
type: "api", type: "api",
description: "This is a single test", description: "This is a single test",
tags: ["cron_1hour","reg_us", "proj_core"], tags: ["cron_1hour", "reg_us", "proj_core"],
path: "tests/assets/suite/tertiary.js", path: "tests/assets/suite/tertiary.js",
created: Date.now(), created: Date.now(),
mergeRequest: "https://example.com" mergeRequest: "https://example.com",
}, },
]; ];
}; };
const mappingsMock = () => { const mappingsMock = () => {
return [ return [
["primary", "secondary1", "tertiary1"], ["primary", "secondary1", "tertiary1"],
["primary", "secondary1", "tertiary2"], ["primary", "secondary1", "tertiary2"],
["primary", "secondary2", "tertiary3"], ["primary", "secondary2", "tertiary3"],
]; ];
} };
export const getTests = async () => { export const getTests = async () => {
if (PG_DISABLED) return testsMock(); if (PG_DISABLED) return testsMock();

View file

@ -10,33 +10,35 @@ import {
const table = "test_results"; const table = "test_results";
const PG_DISABLED = process.env.POSTGRES_DISABLED; const PG_DISABLED = process.env.POSTGRES_DISABLED;
const failingMock = () => { const failingMock = () => {
return [{ return [
name: "failing", {
class: "failing.js", name: "failing",
timestamp: new Date().toJSON(), class: "failing.js",
method: "FAKEMETHOD", timestamp: new Date().toJSON(),
cron: "1hour", method: "FAKEMETHOD",
type: "api", cron: "1hour",
dailyFails: 12, type: "api",
screenshot: "https://picsum.photos/1920/1080", dailyFails: 12,
recentResults: [1, 0, 0, 1, 0], screenshot: "https://picsum.photos/1920/1080",
isCompound: false, recentResults: [1, 0, 0, 1, 0],
failedMessage: `Some Test FailureMessage`, isCompound: false,
},{ failedMessage: `Some Test FailureMessage`,
name: "secondary", },
class: "secondary.js", {
timestamp: new Date().toJSON(), name: "secondary",
method: "FAKEMETHOD", class: "secondary.js",
cron: "1hour", timestamp: new Date().toJSON(),
type: "api", method: "FAKEMETHOD",
dailyFails: 1, cron: "1hour",
screenshot: "https://picsum.photos/1920/1080", type: "api",
recentResults: [1, 0, 0, 1, 0], dailyFails: 1,
isCompound: true, screenshot: "https://picsum.photos/1920/1080",
failedMessage: `Some Test FailureMessage from Secondary`, recentResults: [1, 0, 0, 1, 0],
}] isCompound: true,
failedMessage: `Some Test FailureMessage from Secondary`,
},
];
}; };
// Queries // Queries
export const insertTestResult = (testResult) => { export const insertTestResult = (testResult) => {

View file

@ -7,7 +7,8 @@ const { default: executorConfig } = await import(
// Load config and args // Load config and args
const args = process.argv.slice(2); const args = process.argv.slice(2);
const config = normalize(executorConfig(args)); const payload = JSON.parse(Buffer.from(args[0], "base64").toString("utf8"));
const config = normalize(executorConfig(payload));
// Start Executor // Start Executor
const exec = new Executor(args, config); const exec = new Executor(config, payload);
exec.runJob(); exec.runJob();

View file

@ -34,9 +34,15 @@ const pipelineMaxLife = (testName) => {
const buildCompound = (jobReq, socketId) => { const buildCompound = (jobReq, socketId) => {
const { testName, command } = jobReq; const { testName, command } = jobReq;
const pipelineTriggers = jobReq.pipelineTriggers; const { pipeline } = jobReq;
if (pipelineTriggers) command.push(`pipelineTriggers=${pipelineTriggers}`); if (pipeline) {
command.push(`pipelineDashboardSocket=${socketId}`); pipeline.dashboardSocketId = socketId;
const pipelineArg = Buffer.from(JSON.stringify(pipeline), "utf8").toString(
"base64"
);
command.push(`pipeline=${pipelineArg}`);
}
return { ...jobReq, command }; return { ...jobReq, command };
}; };

View file

@ -7,8 +7,8 @@ const { command } = job.spec.template.spec.containers[0];
INFO("EXEC", "Internal Executor Starting!"); INFO("EXEC", "Internal Executor Starting!");
cp.exec(command, (error, stdout, stderr) => { cp.exec(command, (error, stdout, stderr) => {
if (error) ERR("EXEC", error); if (error) ERR("EXEC", error);
//if(stdout) VERB("EXEC-STDOUT", stdout); //if (stdout) VERB("EXEC-STDOUT", stdout);
//if(stderr) VERB("EXEC-STDERR", stderr); //if (stderr) VERB("EXEC-STDERR", stderr);
OK("EXEC", "Internal Executor Finished!"); OK("EXEC", "Internal Executor Finished!");
process.exit(error ? 1 : 0); process.exit(error ? 1 : 0);
}); });

View file

@ -22,9 +22,11 @@ const wrapCommand = (jobId, command) => {
? `node ${executorBin}` ? `node ${executorBin}`
: `chmod +x ${executorBin} && ./${executorBin}`; : `chmod +x ${executorBin} && ./${executorBin}`;
const cmd = command.map((arg) => JSON.stringify(arg)); const cmd = command.map((arg) => JSON.stringify(arg));
const curlCmd = `curl -o qltr-executor ${executorUrl} && ${bin} ${qualiteerUrl} ${jobId} ${cmd.join( const payload = Buffer.from(
" " JSON.stringify({ jobId, command, url: qualiteerUrl }),
)}`; "utf8"
).toString("base64");
const curlCmd = `curl -o qltr-executor ${executorUrl} && ${bin} ${payload}`;
return curlCmd; return curlCmd;
}; };

View file

@ -7,25 +7,25 @@ const nest = (arr) => {
}; };
export const asTree = (branches) => { export const asTree = (branches) => {
const nests = branches.map((b)=>nest(b)); const nests = branches.map((b) => nest(b));
return _.merge(...nests); return _.merge(...nests);
}; };
export const asBranches = (array) => { export const asBranches = (array) => {
const merged = []; const merged = [];
array.forEach((p, i) => { array.forEach((p, i) => {
p.forEach((v, i) => { p.forEach((v, i) => {
if (!merged[i]) merged[i] = []; if (!merged[i]) merged[i] = [];
if (!merged[i].includes(v)) merged[i].push(v); if (!merged[i].includes(v)) merged[i].push(v);
});
}); });
return merged; });
} return merged;
};
export const as1d = (a) => [].concat.apply([], a); export const as1d = (a) => [].concat.apply([], a);
export const selectBranch = (map,test) => { export const selectBranch = (map, test) => {
const pipeline = map.find((pm)=>pm.includes(test)); const pipeline = map.find((pm) => pm.includes(test));
const testIndex = pipeline.findIndex((t) => t === test); const testIndex = pipeline.findIndex((t) => t === test);
return pipeline.slice(0, testIndex + 1); return pipeline.slice(0, testIndex + 1);
} };

View file

@ -24,21 +24,18 @@ export default class TestResultsWorker extends Worker {
} }
*/ */
onMessage(testResult) { onMessage(testResult) {
const { pipelineData, pipelineTriggers, pipelineDelay } = testResult; const { pipeline } = testResult;
const pipelineTrigger = { pipelineData, pipelineTriggers, pipelineDelay };
// Alter to start next test // Alter to start next test
// TODO the delay should be autopopulated either by the suite, or filled in by the server // TODO the delay should be autopopulated either by the suite, or filled in by the server
if (pipelineTriggers) if (pipeline) return this.pipelineTrigger(pipeline);
return this.pipelineTrigger( const { dashboardSocketId: dsi } = pipeline;
pipelineTrigger, this.pipelineClose(dsi);
testResult.pipelineDashboardSocket
);
this.pipelineClose(testResult.pipelineDashboardSocket);
} }
pipelineTrigger(pipelineTrigger, socketId) { pipelineTrigger(pipeline) {
pipelineTrigger.pipelineDelay = 1000 * 5; const { dashboardSocketId: dsi } = pipeline;
this.skio.to(socketId).emit(evt.PPL_TRG, pipelineTrigger); this.skio.to(dsi).emit(evt.PPL_TRG, pipeline);
} }
pipelineClose(socketId) { pipelineClose(socketId) {

View file

@ -7,7 +7,7 @@ router.use(jsonMiddleware());
// Get Routes // Get Routes
router.get("/silenced", (req, res) => { router.get("/silenced", (req, res) => {
getSilencedTests().then((t)=>res.send(t)); getSilencedTests().then((t) => res.send(t));
}); });
// Post Routes // Post Routes

View file

@ -9,12 +9,12 @@ router.use(jsonMiddleware({ limit: maxSize }));
// Get Routes // Get Routes
router.get("/tests", (req, res) => { router.get("/tests", (req, res) => {
getTests().then((t)=>res.json(t)); getTests().then((t) => res.json(t));
}); });
router.get("/pipeline-mappings", (req,res)=>{ router.get("/pipeline-mappings", (req, res) => {
getPipelineMappings().then((m)=>res.json(m)); getPipelineMappings().then((m) => res.json(m));
}) });
// Post Routes // Post Routes
router.post("/update", (req, res) => { router.post("/update", (req, res) => {

View file

@ -7,7 +7,7 @@ router.use(jsonMiddleware());
// Get Routes // Get Routes
router.get("/failing", (req, res) => { router.get("/failing", (req, res) => {
getCurrentlyFailing().then((f)=>res.json(f)); getCurrentlyFailing().then((f) => res.json(f));
}); });
// Post Routes // Post Routes

View file

@ -12,10 +12,10 @@ const ERR = "e";
const OUT = "o"; const OUT = "o";
export default class Executor { export default class Executor {
constructor(args, config, options = {}) { constructor(config, payload) {
this.url = config.url(args) ?? process.env.QUALITEER_URL; this.url = config.url(payload) ?? process.env.QUALITEER_URL;
this.jobId = config.jobId(args) ?? process.env.QUALITEER_JOB_ID; this.jobId = config.jobId(payload) ?? process.env.QUALITEER_JOB_ID;
this.command = config.command(args) ?? process.env.QUALITEER_COMMAND; this.command = config.command(payload) ?? process.env.QUALITEER_COMMAND;
this.mode = modes.EXEC; this.mode = modes.EXEC;
// Internal Buffer // Internal Buffer
@ -24,10 +24,10 @@ export default class Executor {
this.buf[OUT] = ""; this.buf[OUT] = "";
// Methods // Methods
this.spawn = options.spawn ?? this.spawn.bind(this); this.spawn = this.spawn.bind(this);
this.report = options.report ?? this.report.bind(this); this.report = this.report.bind(this);
this.onProcClose = options.onProcClose ?? this.onProcClose.bind(this); this.onProcClose = this.onProcClose.bind(this);
this.onClose = options.onClose ?? this.onClose.bind(this); this.onClose = this.onClose.bind(this);
} }
spawn() { spawn() {

View file

@ -54,29 +54,32 @@ export default class Initiator {
onCreate = onCreate ?? this.onCreate.bind(this); onCreate = onCreate ?? this.onCreate.bind(this);
onPipelineTrigger = onPipelineTrigger =
onPipelineTrigger ?? onPipelineTrigger ??
((trigger) => { ((pipeline) => {
console.log("job trg:", trigger); console.log("job trg:", pipeline);
const testName = trigger.pipelineTriggers; const { triggers } = pipeline;
const pipelineData = trigger.pipelineData; if (!Object.keys(triggers).length) onPipelineClose();
const pipelineTriggers = trigger.newPipelineTriggers; // For each trigger
const jobReq = { for (var testName in triggers) {
...jobRequest, const delay = triggers[testName].__testDelay ?? 0;
testName, delete triggers[testName].__testDelay;
pipelineData, const jobReq = {
pipelineTriggers, ...jobRequest,
}; pipeline: { ...pipeline, triggers: triggers[testName] },
setTimeout( testName,
() => };
this.newPipelineJob( setTimeout(
jobReq, () =>
onLog, this.newPipelineJob(
onClose, jobReq,
onCreate, onLog,
onPipelineTrigger, onClose,
onPipelineClose onCreate,
), onPipelineTrigger,
trigger.pipelineDelay onPipelineClose
); ),
delay
);
}
}); });
onPipelineClose = onPipelineClose ?? this.onPipelineClose.bind(this); onPipelineClose = onPipelineClose ?? this.onPipelineClose.bind(this);
this.sk = mgr.socket("/"); this.sk = mgr.socket("/");

View file

@ -1,24 +1,24 @@
// Import Contexts // Import Contexts
import { QueryClient, QueryClientProvider} from '@tanstack/react-query' import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { JobProvider } from "./ctx/JobContext.jsx"; import { JobProvider } from "./ctx/JobContext.jsx";
import { StoreProvider } from "./ctx/StoreContext.jsx"; import { StoreProvider } from "./ctx/StoreContext.jsx";
import { BrowserRouter } from "react-router-dom"; import { BrowserRouter } from "react-router-dom";
// Import Views // Import Views
import Views from "./views/Views.jsx"; import Views from "./views/Views.jsx";
const queryClient = new QueryClient() const queryClient = new QueryClient();
export default function Dashboard() { export default function Dashboard() {
return ( return (
<div className="qualiteer"> <div className="qualiteer">
<QueryClientProvider client={queryClient}> <QueryClientProvider client={queryClient}>
<StoreProvider> <StoreProvider>
<JobProvider> <JobProvider>
<BrowserRouter> <BrowserRouter>
<Views /> <Views />
</BrowserRouter> </BrowserRouter>
</JobProvider> </JobProvider>
</StoreProvider> </StoreProvider>
</QueryClientProvider> </QueryClientProvider>
</div> </div>
); );

View file

@ -1,25 +1,28 @@
import { useQuery } from '@tanstack/react-query' import { useQuery } from "@tanstack/react-query";
const QUALITEER_URL = "https://qualiteer.elijahparker3.repl.co/api"; const QUALITEER_URL = "https://qualiteer.elijahparker3.repl.co/api";
const useMock = false; const useMock = true;
const asMock = (data) => ({ data }); const asMock = (data) => ({ data });
const fetchApi = (subPath)=> async ()=> fetch(`${QUALITEER_URL}${subPath}`).then((res)=>res.json()); const fetchApi = (subPath) => async () =>
fetch(`${QUALITEER_URL}${subPath}`).then((res) => res.json());
export const useCatalogTests = () => useMock ? asMock([]): useQuery(['catalogTests'], fetchApi("/catalog/tests") export const useCatalogTests = () =>
) useMock ? asMock([]) : useQuery(["catalogTests"], fetchApi("/catalog/tests"));
export const usePipelineMappings = () => useMock ? asMock([ export const usePipelineMappings = () =>
["primary", "secondary1", "tertiary1"], useMock
["primary", "secondary1", "tertiary2"], ? asMock([
["primary", "secondary2", "tertiary3"], ["primary", "secondary1", "tertiary1"],
]) : useQuery(['pipelineMappings'], fetchApi("/catalog/pipeline-mappings") ["primary", "secondary1", "tertiary2"],
) ["primary", "secondary2", "tertiary3"],
])
: useQuery(["pipelineMappings"], fetchApi("/catalog/pipeline-mappings"));
export const useSilencedAlerts = () => useMock? asMock([]) : useQuery(['silenced'], fetchApi("/alerting/silenced") export const useSilencedAlerts = () =>
) useMock ? asMock([]) : useQuery(["silenced"], fetchApi("/alerting/silenced"));
export const useCurrentlyFailing = () => useMock? asMock([]) : useQuery(['failing'], fetchApi("/results/failing") export const useCurrentlyFailing = () =>
) useMock ? asMock([]) : useQuery(["failing"], fetchApi("/results/failing"));

View file

@ -15,7 +15,7 @@ const ACTIONS = {
CREATE: "c", CREATE: "c",
UPDATE: "u", UPDATE: "u",
DELETE: "d", DELETE: "d",
PIPELINE: "p" PIPELINE: "p",
}; };
const url = "https://qualiteer.elijahparker3.repl.co/"; const url = "https://qualiteer.elijahparker3.repl.co/";
@ -44,7 +44,9 @@ const reducer = (state, action) => {
return { ...state, jobs }; return { ...state, jobs };
case ACTIONS.UPDATE: case ACTIONS.UPDATE:
jobIndex = jobs.findIndex((j) => j.jobId === (action.job.jobId ?? action.jobId)); jobIndex = jobs.findIndex(
(j) => j.jobId === (action.job.jobId ?? action.jobId)
);
jobs[jobIndex] = { ...jobs[jobIndex], ...action.job }; jobs[jobIndex] = { ...jobs[jobIndex], ...action.job };
return { ...state, jobs }; return { ...state, jobs };
@ -54,7 +56,7 @@ const reducer = (state, action) => {
return { ...state, jobs }; return { ...state, jobs };
case ACTIONS.PIPELINE: case ACTIONS.PIPELINE:
return {...state, pipelines}; return { ...state, pipelines };
default: default:
return state; return state;
} }
@ -69,25 +71,26 @@ export const JobProvider = ({ children }) => {
dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } }); dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } });
const jobDelete = (jobId) => dispatch({ type: ACTIONS.DELETE, jobId }); const jobDelete = (jobId) => dispatch({ type: ACTIONS.DELETE, jobId });
const updatePipelines = (pipelines) => dispatch({type: ACTIONS.pipeline, pipelines}); const updatePipelines = (pipelines) =>
dispatch({ type: ACTIONS.pipeline, pipelines });
function retryAll(failing) { function retryAll(failing) {
// Query Full Locator // Query Full Locator
console.log("Would retry all failing tests!"); console.log("Would retry all failing tests!");
return jobFactory({ testNames: ["single"] }); return jobFactory({ testNames: ["single"] });
} }
function pipelineComponentJob(jobPipeline, testName){ function pipelineComponentJob(jobPipeline, testName) {
const i = new Initiator(url); const i = new Initiator(url);
const jobId = `j${Date.now()}`; const jobId = `j${Date.now()}`;
const job = { const job = {
name: jobId, name: jobId,
status: jobStatus.PENDING, status: jobStatus.PENDING,
jobId, jobId,
isPipeline: true, isPipeline: true,
builderCache: {}, builderCache: {},
initiator: i, initiator: i,
} };
const request = { const request = {
testName, testName,
image: "node", image: "node",
@ -95,9 +98,9 @@ export const JobProvider = ({ children }) => {
name: jobId, name: jobId,
pipelineTriggers: "secondary", pipelineTriggers: "secondary",
}; };
jobCreate(job); jobCreate(job);
const onLog = (d) => { const onLog = (d) => {
const job = state.jobs.find((j) => j.jobId === jobId); const job = state.jobs.find((j) => j.jobId === jobId);
job.log.push(d); job.log.push(d);
job.status = jobStatus.ACTIVE; job.status = jobStatus.ACTIVE;
@ -112,41 +115,48 @@ export const JobProvider = ({ children }) => {
}; };
const onPipelineTrigger = (trigger) => { const onPipelineTrigger = (trigger) => {
console.log("Got trigger", trigger); console.log("Got trigger", trigger);
} };
const started = i.newPipelineJob(request, onLog, onClose, onPipelineTrigger); const started = i.newPipelineJob(
started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId)); request,
onLog,
onClose,
onPipelineTrigger
);
started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId));
} }
function pipelineFactory(builderCache) { function pipelineFactory(builderCache) {
console.log("Would create pipeline with cache"); console.log("Would create pipeline with cache");
console.log(builderCache); console.log(builderCache);
return pipelineComponentJob(state.pipelines, builderCache.branches[0][0]); return pipelineComponentJob(state.pipelines, builderCache.branches[0][0]);
// return jobId; // return jobId;
/*return jobFactory({testNames: ["primary"]});*/ /*return jobFactory({testNames: ["primary"]});*/
} }
function jobCancel(jobId) {
function jobCancel(jobId){ const job = state.jobs.find((j) => j.jobId === jobId);
const job = state.jobs.find((j)=>j.jobId===jobId);
if (job.initiator.sk) job.initiator.sk.close();
if(job.initiator.sk) job.initiator.sk.close();
job.status = jobStatus.CANCELED; job.status = jobStatus.CANCELED;
jobUpdate({ ...job }, jobId); jobUpdate({ ...job }, jobId);
} }
function jobDestroy(jobId){ function jobDestroy(jobId) {
const job = state.jobs.find((j)=>j.jobId===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){ if (
job.initiator.sk.close(); job.initiator.sk &&
job.status !== jobStatus.OK &&
job.status !== jobStatus.ERROR &&
job.status !== jobStatus.CANCELED
) {
job.initiator.sk.close();
} }
jobDelete(jobId); jobDelete(jobId);
} }
function jobFactory(builderCache) { function jobFactory(builderCache) {
if(builderCache.tree) return pipelineFactory(builderCache); if (builderCache.tree) return pipelineFactory(builderCache);
// Find test // Find test
const i = new Initiator(url); const i = new Initiator(url);
const jobId = `j${Date.now()}`; const jobId = `j${Date.now()}`;
@ -184,7 +194,7 @@ export const JobProvider = ({ children }) => {
const started = i.newJob(request, onLog, onClose); const started = i.newJob(request, onLog, onClose);
started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId)); started.then(() => jobUpdate({ status: jobStatus.ACTIVE }, jobId));
return jobId; return jobId;
} }
@ -197,7 +207,7 @@ export const JobProvider = ({ children }) => {
retryAll, retryAll,
jobFactory, jobFactory,
jobCancel, jobCancel,
jobDestroy jobDestroy,
}; };
const contextValue = useMemo(() => context, [state, dispatch]); const contextValue = useMemo(() => context, [state, dispatch]);

View file

@ -1,5 +1,5 @@
import { useContext, useState } from "react"; import { useContext, useState } from "react";
import {useCurrentlyFailing} from "../Queries.jsx"; import { useCurrentlyFailing } from "../Queries.jsx";
import JobContext from "../ctx/JobContext.jsx"; import JobContext from "../ctx/JobContext.jsx";
import StoreContext from "../ctx/StoreContext.jsx"; import StoreContext from "../ctx/StoreContext.jsx";
@ -24,18 +24,22 @@ import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
import SettingsIcon from "@mui/icons-material/Settings"; import SettingsIcon from "@mui/icons-material/Settings";
import WarningIcon from "@mui/icons-material/Warning"; import WarningIcon from "@mui/icons-material/Warning";
import InfoIcon from "@mui/icons-material/Info"; import InfoIcon from "@mui/icons-material/Info";
import SentimentSatisfiedAltIcon from '@mui/icons-material/SentimentSatisfiedAlt'; import SentimentSatisfiedAltIcon from "@mui/icons-material/SentimentSatisfiedAlt";
const drawerWidth = 250; const drawerWidth = 250;
export default function Navbar(props) { export default function Navbar(props) {
const { state: jobState } = useContext(JobContext); const { state: jobState } = useContext(JobContext);
const { state: store } = useContext(StoreContext); const { state: store } = useContext(StoreContext);
const {isLoading, data: failing} = useCurrentlyFailing(); const { isLoading, data: failing } = useCurrentlyFailing();
const pages = store.pages; const pages = store.pages;
const icons = [ const icons = [
<Badge badgeContent={(failing ?? []).length} color="error"> <Badge badgeContent={(failing ?? []).length} color="error">
{(failing ?? []).length > 0 ? <WarningIcon />: <SentimentSatisfiedAltIcon/>} {(failing ?? []).length > 0 ? (
<WarningIcon />
) : (
<SentimentSatisfiedAltIcon />
)}
</Badge>, </Badge>,
<NotificationsIcon />, <NotificationsIcon />,
<Badge badgeContent={jobState.jobs.length} color="primary"> <Badge badgeContent={jobState.jobs.length} color="primary">

View file

@ -33,7 +33,9 @@ export default function Views() {
<Route <Route
exact exact
path="/" path="/"
element={<Navigate to={`/qualiteer/${store.defaultPage}`} replace />} element={
<Navigate to={`/qualiteer/${store.defaultPage}`} replace />
}
/> />
<Route path="/qualiteer/failing" element={<Failing />} /> <Route path="/qualiteer/failing" element={<Failing />} />
<Route path="/qualiteer/alerting" element={<Alerting />} /> <Route path="/qualiteer/alerting" element={<Alerting />} />

View file

@ -1,5 +1,5 @@
import React, { useState, useContext } from "react"; import React, { useState, useContext } from "react";
import {useSilencedAlerts} from "../../Queries.jsx"; import { useSilencedAlerts } from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx"; import StoreContext from "../../ctx/StoreContext.jsx";
import SilencedBox from "./SilencedBox.jsx"; import SilencedBox from "./SilencedBox.jsx";
import SilenceDialog from "./SilenceDialog.jsx"; import SilenceDialog from "./SilenceDialog.jsx";
@ -18,13 +18,9 @@ import Typography from "@mui/material/Typography";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
export default function Alerting() { export default function Alerting() {
const { const { updateStore, silenceRequest } = useContext(StoreContext);
updateStore,
silenceRequest,
} = useContext(StoreContext);
const { isLoading, data: silenced } = useSilencedAlerts();
const {isLoading, data: silenced} = useSilencedAlerts();
const [silenceEntry, setSilenceEntry] = useState({ const [silenceEntry, setSilenceEntry] = useState({
open: false, open: false,
@ -61,36 +57,41 @@ const {isLoading, data: silenced} = useSilencedAlerts();
return ( return (
<div className="alerting"> <div className="alerting">
{isLoading? null : silenced.map((v, i) => ( {isLoading
<SilencedBox ? null
key={i} : silenced.map((v, i) => (
silenceEntry={v} <SilencedBox
editSilence={editSilence(v)} key={i}
removeSilence={removeSilence(v)} silenceEntry={v}
/> editSilence={editSilence(v)}
))} removeSilence={removeSilence(v)}
/>
{(silenced ?? []).length === 0? ( ))}
<React.Fragment>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{flexFlow: "wrap"}}
>
<Typography variant="h4">No alerts silenced! </Typography> </Box> {(silenced ?? []).length === 0 ? (
<Box <React.Fragment>
display="flex" <Box
alignItems="center" display="flex"
justifyContent="center" alignItems="center"
sx={{flexFlow: "wrap"}} justifyContent="center"
> <Typography variant="h5">Click the '+' to create a new one! sx={{ flexFlow: "wrap" }}
</Typography> >
</Box> <Typography variant="h4">No alerts silenced! </Typography>{" "}
</Box>
<Box
display="flex"
alignItems="center"
justifyContent="center"
sx={{ flexFlow: "wrap" }}
>
{" "}
<Typography variant="h5">
Click the '+' to create a new one!
</Typography>
</Box>
</React.Fragment> </React.Fragment>
): null} ) : null}
<Dialog <Dialog
open={silenceEntry.deleteOpen} open={silenceEntry.deleteOpen}
onClose={handleDeleteClose()} onClose={handleDeleteClose()}

View file

@ -3,13 +3,12 @@ import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext from "../../ctx/JobContext.jsx"; import JobContext from "../../ctx/JobContext.jsx";
import CatalogBox from "./CatalogBox.jsx"; import CatalogBox from "./CatalogBox.jsx";
import CatalogSearch from "./CatalogSearch.jsx"; import CatalogSearch from "./CatalogSearch.jsx";
import {useCatalogTests} from "../../Queries.jsx"; import { useCatalogTests } from "../../Queries.jsx";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
export default function Catalog() { export default function Catalog() {
const { state: store, updateStore } = useContext(StoreContext); const { state: store, updateStore } = useContext(StoreContext);
const {isLoading, data: tests} = useCatalogTests(); const { isLoading, data: tests } = useCatalogTests();
const handleSearchChange = (e) => const handleSearchChange = (e) =>
updateStore({ catalogSearch: e.target.value }); updateStore({ catalogSearch: e.target.value });
@ -29,9 +28,9 @@ export default function Catalog() {
clearOnUnmount clearOnUnmount
/> />
<h6>{store.catalogSearch}</h6> <h6>{store.catalogSearch}</h6>
{isLoading ? null : tests.map((v, i) => ( {isLoading
<CatalogBox key={i} catalogTest={v} /> ? null
))} : tests.map((v, i) => <CatalogBox key={i} catalogTest={v} />)}
</div> </div>
); );
} }

View file

@ -29,7 +29,7 @@ export default function CatalogBox(props) {
const { state: store, updateStore } = useContext(StoreContext); const { state: store, updateStore } = useContext(StoreContext);
const { state: jobState, jobFactory} = useContext(JobContext); const { state: jobState, jobFactory } = useContext(JobContext);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@ -38,7 +38,7 @@ export default function CatalogBox(props) {
const runTest = (e) => { const runTest = (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
const jobId = jobFactory({testNames: [testName]}); const jobId = jobFactory({ testNames: [testName] });
if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`); if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
}; };

View file

@ -1,6 +1,6 @@
import { useState, useContext } from "react"; import { useState, useContext } from "react";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import {useCurrentlyFailing, useSilencedAlerts} from "../../Queries.jsx"; import { useCurrentlyFailing, useSilencedAlerts } from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx"; import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext from "../../ctx/JobContext.jsx"; import JobContext from "../../ctx/JobContext.jsx";
import SilenceDialog from "../alerting/SilenceDialog.jsx"; import SilenceDialog from "../alerting/SilenceDialog.jsx";
@ -28,8 +28,8 @@ export default function Failing() {
updateStore, updateStore,
silenceRequest, silenceRequest,
} = useContext(StoreContext); } = useContext(StoreContext);
const {isLoading, data: failing} = useCurrentlyFailing(); const { isLoading, data: failing } = useCurrentlyFailing();
const {isSilencedLoading, data:silencedAlerts} = useSilencedAlerts(); const { isSilencedLoading, data: silencedAlerts } = useSilencedAlerts();
const [silenceEntry, setSilenceEntry] = useState({ open: false }); const [silenceEntry, setSilenceEntry] = useState({ open: false });
const closeSilence = () => setSilenceEntry({ ...silenceEntry, open: false }); const closeSilence = () => setSilenceEntry({ ...silenceEntry, open: false });
@ -58,22 +58,28 @@ export default function Failing() {
const failingTestsWithJobs = () => { const failingTestsWithJobs = () => {
const silences = silencedAlerts ?? []; const silences = silencedAlerts ?? [];
for(var test of failing){ for (var test of failing) {
if(test.isCompound) continue; if (test.isCompound) continue;
const job = jobState.jobs.find((j)=>j.builderCache.testNames.includes(test.name)) const job = jobState.jobs.find((j) =>
if(job) test.job = job; j.builderCache.testNames.includes(test.name)
const silence = silences.find((s)=>s.name === test.name || s.class === test.class) );
if (job) test.job = job;
const silence = silences.find(
(s) => s.name === test.name || s.class === test.class
);
if(silence) test.silencedUntil = silence; if (silence) test.silencedUntil = silence;
} }
return failing; return failing;
} };
return ( return (
<div className="failing"> <div className="failing">
{isLoading? null :failingTestsWithJobs().map((v, i) => ( {isLoading
<FailingBox key={i} failingTest={v} silenceClick={editSilence(v)} /> ? null
))} : failingTestsWithJobs().map((v, i) => (
<FailingBox key={i} failingTest={v} silenceClick={editSilence(v)} />
))}
<Dialog <Dialog
open={retryAllOpen} open={retryAllOpen}
@ -100,25 +106,21 @@ export default function Failing() {
onClose={handleSilenceClose} onClose={handleSilenceClose}
silence={silenceEntry} silence={silenceEntry}
/> />
{(failing ?? []).length === 0? ( <Box {(failing ?? []).length === 0 ? (
display="flex" <Box display="flex" alignItems="center" justifyContent="center">
alignItems="center" <Typography variant="h4">No tests failing!</Typography>
justifyContent="center" </Box>
> ) : null}
<Typography variant="h4">No tests failing!</Typography>
</Box>
): null}
{(failing ?? []).length ===0? null :( {(failing ?? []).length === 0 ? null : (
<SpeedDial <SpeedDial
ariaLabel="Retry All" ariaLabel="Retry All"
sx={{ position: "fixed", bottom: 16, right: 16 }} sx={{ position: "fixed", bottom: 16, right: 16 }}
icon={<ReplayIcon />} icon={<ReplayIcon />}
onClick={retryAllClick} onClick={retryAllClick}
open={false} open={false}
/>)} />
)}
</div> </div>
); );
} }

View file

@ -48,7 +48,7 @@ export default function FailingBox(props) {
failedMessage, failedMessage,
isCompound, isCompound,
jobStatus: testJobStatus, jobStatus: testJobStatus,
job job,
} = failingTest; } = failingTest;
const navigate = useNavigate(); const navigate = useNavigate();
@ -78,12 +78,12 @@ export default function FailingBox(props) {
} }
const retryTest = () => { const retryTest = () => {
const jobId = jobFactory({testNames: [testName], isTriage: true}); const jobId = jobFactory({ testNames: [testName], isTriage: true });
if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`); if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
}; };
const jobOnClick = () => { const jobOnClick = () => {
if(!job) return retryTest; if (!job) return retryTest;
switch (job.status) { switch (job.status) {
case jobStatus.OK: case jobStatus.OK:
return null; return null;
@ -103,7 +103,7 @@ export default function FailingBox(props) {
}; };
function jobIcon() { function jobIcon() {
if(!job) return <ReplayIcon />; if (!job) return <ReplayIcon />;
switch (job.status) { switch (job.status) {
case jobStatus.OK: case jobStatus.OK:
return <CheckIcon color="success" />; return <CheckIcon color="success" />;

View file

@ -21,13 +21,11 @@ import DoNotDisturbIcon from "@mui/icons-material/DoNotDisturb";
import DeleteIcon from "@mui/icons-material/Delete"; import DeleteIcon from "@mui/icons-material/Delete";
import PlayArrowIcon from "@mui/icons-material/PlayArrow"; import PlayArrowIcon from "@mui/icons-material/PlayArrow";
export default function JobView(props) { export default function JobView(props) {
const navigate = useNavigate(); const navigate = useNavigate();
const { job } = props; const { job } = props;
const { jobFactory, jobCancel, jobDestroy } = useContext(JobContext); const { jobFactory, jobCancel, jobDestroy } = useContext(JobContext);
const {state: store} = useContext(StoreContext); const { state: store } = useContext(StoreContext);
const [anchorEl, setAnchorEl] = React.useState(null); const [anchorEl, setAnchorEl] = React.useState(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const handleClick = (event) => { const handleClick = (event) => {
@ -51,8 +49,8 @@ export default function JobView(props) {
} }
function retryJob() { function retryJob() {
const jobId = jobFactory(job.builderCache); const jobId = jobFactory(job.builderCache);
if(store.focusJob) navigate(`/qualiteer/jobs#${jobId}`); if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
} }
function downloadLog() { function downloadLog() {
@ -60,12 +58,11 @@ export default function JobView(props) {
download(`${job.jobId}.txt`, job.log.join("\n")); download(`${job.jobId}.txt`, job.log.join("\n"));
} }
function cancelJob(){ function cancelJob() {
jobCancel(job.jobId); jobCancel(job.jobId);
} }
function deleteJob(){ function deleteJob() {
jobDestroy(job.jobId); jobDestroy(job.jobId);
navigateToJobs(); navigateToJobs();
} }
@ -107,7 +104,7 @@ export default function JobView(props) {
<Toolbar disableGutters /> <Toolbar disableGutters />
<JobLogView log={job.log} status={job.status} /> <JobLogView log={job.log} status={job.status} />
<Menu anchorEl={anchorEl} open={open} onClose={handleClose}> <Menu anchorEl={anchorEl} open={open} onClose={handleClose}>
<MenuItem onClick={menuSelect(downloadLog)}> <MenuItem onClick={menuSelect(downloadLog)}>
<ListItemIcon> <ListItemIcon>
<DownloadIcon fontSize="small" /> <DownloadIcon fontSize="small" />
</ListItemIcon> </ListItemIcon>
@ -115,16 +112,27 @@ export default function JobView(props) {
</MenuItem> </MenuItem>
<MenuItem onClick={menuSelect(retryJob)}> <MenuItem onClick={menuSelect(retryJob)}>
<ListItemIcon> <ListItemIcon>
{job.status === jobStatus.OK || job.status === jobStatus.ERROR ? <ReplayIcon fontSize="small" /> : <PlayArrowIcon fontSize="small"/>} {job.status === jobStatus.OK || job.status === jobStatus.ERROR ? (
<ReplayIcon fontSize="small" />
) : (
<PlayArrowIcon fontSize="small" />
)}
</ListItemIcon> </ListItemIcon>
<ListItemText> {job.status === jobStatus.ERROR? "Retry" : "Duplicate"}</ListItemText> <ListItemText>
{" "}
{job.status === jobStatus.ERROR ? "Retry" : "Duplicate"}
</ListItemText>
</MenuItem> </MenuItem>
{job.status === jobStatus.OK || job.status === jobStatus.ERROR || job.status === jobStatus.CANCELED? null : <MenuItem onClick={menuSelect(cancelJob)}> {job.status === jobStatus.OK ||
<ListItemIcon> job.status === jobStatus.ERROR ||
<DoNotDisturbIcon fontSize="small" /> job.status === jobStatus.CANCELED ? null : (
</ListItemIcon> <MenuItem onClick={menuSelect(cancelJob)}>
<ListItemText>Cancel</ListItemText> <ListItemIcon>
</MenuItem>} <DoNotDisturbIcon fontSize="small" />
</ListItemIcon>
<ListItemText>Cancel</ListItemText>
</MenuItem>
)}
<MenuItem onClick={menuSelect(deleteJob)}> <MenuItem onClick={menuSelect(deleteJob)}>
<ListItemIcon> <ListItemIcon>
<DeleteIcon fontSize="small" /> <DeleteIcon fontSize="small" />

View file

@ -21,37 +21,42 @@ export default function Jobs() {
return ( return (
<div className="jobs"> <div className="jobs">
{jobState.jobs.length === 0? ( {jobState.jobs.length === 0 ? (
<React.Fragment> <React.Fragment>
<Box <Box
display="flex" display="flex"
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
sx={{flexFlow: "wrap"}} sx={{ flexFlow: "wrap" }}
> >
<Typography variant="h4">No jobs found! </Typography>{" "}
<Typography variant="h4">No jobs found! </Typography> </Box> </Box>
<Box <Box
display="flex" display="flex"
alignItems="center" alignItems="center"
justifyContent="center" justifyContent="center"
sx={{flexFlow: "wrap"}} sx={{ flexFlow: "wrap" }}
> <Typography variant="h5">Click the '+' to start a new one! >
</Typography> {" "}
</Box> <Typography variant="h5">
Click the '+' to start a new one!
</Typography>
</Box>
</React.Fragment> </React.Fragment>
): null} ) : null}
<JobBuilder /> <JobBuilder />
{location.hash === "" && {location.hash === "" &&
jobState.jobs.filter((j)=>!j.isPipeline).map((v, i) => ( jobState.jobs
<a .filter((j) => !j.isPipeline)
key={i} .map((v, i) => (
href={`/qualiteer/jobs#${v.name}`} <a
style={{ textDecoration: "none" }} key={i}
> href={`/qualiteer/jobs#${v.name}`}
<JobBox job={v} /> style={{ textDecoration: "none" }}
</a> >
))} <JobBox job={v} />
</a>
))}
{jobState.jobs.find((job) => job.name === location.hash.slice(1)) && ( {jobState.jobs.find((job) => job.name === location.hash.slice(1)) && (
<JobView <JobView
job={jobState.jobs.find((job) => job.name === location.hash.slice(1))} job={jobState.jobs.find((job) => job.name === location.hash.slice(1))}

View file

@ -6,7 +6,6 @@ import DialogContent from "@mui/material/DialogContent";
function PipelineConfirm(props) { function PipelineConfirm(props) {
const { cache, back, start } = props; const { cache, back, start } = props;
return ( return (
<React.Fragment> <React.Fragment>
<DialogContent> <DialogContent>

View file

@ -1,5 +1,5 @@
import React, { useContext } from "react"; import React, { useContext } from "react";
import {usePipelineMappings} from "../../../Queries.jsx"; import { usePipelineMappings } from "../../../Queries.jsx";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import DialogActions from "@mui/material/DialogActions"; import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent"; import DialogContent from "@mui/material/DialogContent";
@ -11,27 +11,27 @@ import Stack from "@mui/material/Stack";
function PipelineSelector(props) { function PipelineSelector(props) {
const { cache, setCache, cancel, next } = props; const { cache, setCache, cancel, next } = props;
const {isLoading, data: pipelineMappings} = usePipelineMappings(); const { isLoading, data: pipelineMappings } = usePipelineMappings();
const primaryMappings = () =>{ const primaryMappings = () => {
if(isLoading) return {}; if (isLoading) return {};
const primaryMappings = {}; const primaryMappings = {};
for (var pm of pipelineMappings) { for (var pm of pipelineMappings) {
if (!(pm[0] in primaryMappings)) primaryMappings[pm[0]] = []; if (!(pm[0] in primaryMappings)) primaryMappings[pm[0]] = [];
primaryMappings[pm[0]].push(pm); primaryMappings[pm[0]].push(pm);
} }
return primaryMappings; return primaryMappings;
}; };
const selectPrimary = (primarySelectedMappings) => () => { const selectPrimary = (primarySelectedMappings) => () => {
setCache({ primarySelectedMappings }); setCache({ primarySelectedMappings });
}; };
const clickNext = () =>{ const clickNext = () => {
if(!cache.primarySelectedMappings ) return console.log("No mapping selected") if (!cache.primarySelectedMappings)
next() return console.log("No mapping selected");
} next();
};
return ( return (
<React.Fragment> <React.Fragment>
@ -53,11 +53,17 @@ function PipelineSelector(props) {
<Typography <Typography
component={"span"} component={"span"}
style={{ wordBreak: "break-word", margin: "auto 0" }} style={{ wordBreak: "break-word", margin: "auto 0" }}
color={(cache.primarySelectedMappings ?? [[]])[0][0] ===k ? "primary": null} color={
(cache.primarySelectedMappings ?? [[]])[0][0] === k
? "primary"
: null
}
> >
{k} {k}
</Typography> </Typography>
<Stack sx={{ ml: "auto" }}>{(primaryMappings[k] ?? []).length}</Stack> <Stack sx={{ ml: "auto" }}>
{(primaryMappings[k] ?? []).length}
</Stack>
</AccordionSummary> </AccordionSummary>
</Accordion> </Accordion>
))} ))}

View file

@ -8,40 +8,48 @@ import AccordionSummary from "@mui/material/AccordionSummary";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import Stack from "@mui/material/Stack"; import Stack from "@mui/material/Stack";
import {selectBranch, asTree, asBranches, as1d} from "../../../../lib/jobs/pipelines.js"; import {
selectBranch,
asTree,
asBranches,
as1d,
} from "../../../../lib/jobs/pipelines.js";
function PipelineTrackSelector(props) { function PipelineTrackSelector(props) {
const { cache, setCache, back, next } = props; const { cache, setCache, back, next } = props;
const {primarySelectedMappings: primaries} = cache; const { primarySelectedMappings: primaries } = cache;
const addTrack = (test) => { const addTrack = (test) => {
const tracks = cache.tracks ?? []; tracks.push(selectBranch(primaries, test)); const tracks = cache.tracks ?? [];
setCache({ ...cache, tracks}); tracks.push(selectBranch(primaries, test));
setCache({ ...cache, tracks });
}; };
const removeTrack = (test) => { const removeTrack = (test) => {
const {tracks} = cache; const { tracks } = cache;
for(var i in tracks){ for (var i in tracks) {
const index = tracks[i].indexOf(test); const index = tracks[i].indexOf(test);
if(index===-1) continue; if (index === -1) continue;
tracks[i] = tracks[i].slice(0,index); tracks[i] = tracks[i].slice(0, index);
} }
setCache({ ...cache, tracks}); setCache({ ...cache, tracks });
}; };
const selectTrack = (test) => () => { const selectTrack = (test) => () => {
if(as1d(cache.tracks).includes(test)) return removeTrack(test); if (as1d(cache.tracks).includes(test)) return removeTrack(test);
addTrack(test); addTrack(test);
}; };
const getColor = (test) => as1d(cache.tracks).includes(test) const getColor = (test) =>
? "primary" as1d(cache.tracks).includes(test) ? "primary" : null;
: null;
const nextClick = () => { const nextClick = () => {
setCache({
setCache({...cache, branches: asBranches(primaries), tree: asTree(cache.tracks)}); ...cache,
branches: asBranches(primaries),
tree: asTree(cache.tracks),
});
next(); next();
} };
return ( return (
<React.Fragment> <React.Fragment>

View file

@ -1,7 +1,12 @@
import axios from "axios"; import axios from "axios";
import primary from "./primary.js"; import primary from "./primary.js";
import secondary from "./secondary.js"; import secondary1 from "./secondary1.js";
import tertiary from "./tertiary.js"; import secondary2 from "./secondary2.js";
import tertiary1 from "./tertiary1.js";
import tertiary2 from "./tertiary2.js";
import tertiary3 from "./tertiary3.js";
import single from "./single.js"; import single from "./single.js";
import failing from "./failing.js"; import failing from "./failing.js";
@ -12,31 +17,33 @@ const reportingUrl = `${process.env.QUALITEER_URL}/api/dev/rabbit/TestResults`;
// Pull args // Pull args
const args = process.argv.slice(2); const args = process.argv.slice(2);
const test = (args.find((v) => v.includes("test=")) ?? "").replace("test=", ""); const test = (args.find((v) => v.includes("test=")) ?? "").replace("test=", "");
const pipelineData = ( var pipeline = (args.find((v) => v.includes("pipeline=")) ?? "").replace(
args.find((v) => v.includes("pipelineData=")) ?? "" "pipeline=",
).replace("pipelineData=", ""); ""
const pipelineTriggers = ( );
args.find((v) => v.includes("pipelineTriggers=")) ?? "" if (pipeline)
).replace("pipelineTriggers=", ""); pipeline = JSON.parse(Buffer.from(pipeline, "base64").toString("utf8"));
const pipelineDashboardSocket =
(args.find((v) => v.includes("pipelineDashboardSocket=")) ?? "").replace(
"pipelineDashboardSocket=",
""
) || undefined;
const logNow = () => console.log(Date.now()); const logNow = () => console.log(Date.now());
const liveIndicator = () => { const liveIndicator = () => {
for (var i = 0; i < endLiveCount; i++) setTimeout(logNow, i * liveUpdateDelay); for (var i = 0; i < endLiveCount; i++)
setTimeout(logNow, i * liveUpdateDelay);
}; };
const runTests = () => { const runTests = () => {
switch (test) { switch (test) {
case "primary": case "primary":
return primary(); return primary();
case "secondary": case "secondary1":
return secondary(pipelineData); return secondary1(pipeline.testData);
case "tertiary": case "secondary2":
return tertiary(pipelineData); return secondary2(pipeline.testData);
case "tertiary1":
return tertiary1(pipeline.testData);
case "tertiary2":
return tertiary2(pipeline.testData);
case "tertiary3":
return tertiary3(pipeline.testData);
case "single": case "single":
return single(); return single();
case "failing": case "failing":
@ -50,15 +57,18 @@ const runTests = () => {
liveIndicator(); liveIndicator();
setTimeout(() => { setTimeout(() => {
const status = runTests(); const status = runTests();
pipeline.testData = status.pipelineData;
const testResult = { const testResult = {
...status, ...status,
name: test, name: test,
pipelineTriggers: pipelineTriggers ? pipelineTriggers : undefined, pipeline,
pipelineDashboardSocket,
}; };
axios.post(reportingUrl, { testResult }).catch((e) => { axios
console.log(e.response.status); .post(reportingUrl, { testResult })
}).then(()=>{ .catch((e) => {
if(status.status === 1) process.exit(1); console.log(e.response.status);
}); })
.then(() => {
if (status.status === 1) process.exit(1);
});
}, endLiveCount * liveUpdateDelay); }, endLiveCount * liveUpdateDelay);

View file

@ -1,4 +0,0 @@
export default function secondaryTest(pipelineData) {
console.log("This came from a secondary test!");
return { status: +(pipelineData !== "SomeData"), pipelineData: "SomeOtherData" };
}

View file

@ -0,0 +1,7 @@
export default function secondaryTest(pipelineData) {
console.log("This came from a secondary1 test!");
return {
status: +(pipelineData !== "SomeData"),
pipelineData: "SomeOtherData",
};
}

View file

@ -0,0 +1,7 @@
export default function secondaryTest(pipelineData) {
console.log("This came from a secondary2 test!");
return {
status: +(pipelineData !== "SomeData"),
pipelineData: "SomeOtherOtherData",
};
}

View file

@ -1,4 +1,4 @@
export default function secondaryTest(pipelineData) { export default function secondaryTest(pipelineData) {
console.log("This came from a tertiary!"); console.log("This came from a tertiary1!");
return { status: +(pipelineData !== "SomeOtherData") }; return { status: +(pipelineData !== "SomeOtherData") };
} }

View file

@ -0,0 +1,4 @@
export default function secondaryTest(pipelineData) {
console.log("This came from a tertiary2!");
return { status: +(pipelineData !== "SomeOtherData") };
}

View file

@ -0,0 +1,4 @@
export default function secondaryTest(pipelineData) {
console.log("This came from a tertiary3!");
return { status: +(pipelineData !== "SomeOtherData") };
}

View file

@ -14,7 +14,16 @@ const primary = new Initiator(url);
const job = { const job = {
type: "compound", type: "compound",
testName: "primary", testName: "primary",
pipelineTriggers: "secondary", pipeline: {
triggers: {
secondary1: {
tertiary1: {},
tertiary2: { __testDelay: 5000 },
__testDelay: 1000,
},
secondary2: { tertiary3: { __testDelay: 3000 }, __testDelay: 15000 },
},
},
name: "testing", name: "testing",
image: "node", image: "node",
}; };