diff --git a/lib/database/queries/alerting.js b/lib/database/queries/alerting.js
index bcbf806..4bb0ce5 100644
--- a/lib/database/queries/alerting.js
+++ b/lib/database/queries/alerting.js
@@ -7,8 +7,21 @@ import {
} from "../pg-query.js";
// Constants
const table = "silenced_tests";
+const PG_DISABLED = process.env.POSTGRES_DISABLED;
+
+const silencedMock = () => {
+ return [{
+ name: `single`,
+ class: `single.js`,
+ method: "FAKEMETHOD",
+ id: 0,
+ silencedUntil: new Date().toJSON(),
+ }]
+};
+
// Queries
-export const getSilencedTests = () => {
+export const getSilencedTests = async () => {
+ if (PG_DISABLED) return silencedMock();
const query = `SELECT * from ${table}`;
return pg.query(query);
};
diff --git a/lib/database/queries/catalog.js b/lib/database/queries/catalog.js
index e0c27e1..b521a39 100644
--- a/lib/database/queries/catalog.js
+++ b/lib/database/queries/catalog.js
@@ -7,8 +7,94 @@ import {
} from "../pg-query.js";
// Constants
const table = "tests";
+const PG_DISABLED = process.env.POSTGRES_DISABLED;
// Queries
-export const getTests = () => {
+
+const testsMock = () => {
+ return [
+ {
+ id: 0,
+ name: "single",
+ class: "single.js",
+ image: "node:latest",
+ isCompound: false,
+ type: "api",
+ description: "This is a single test",
+ tags: ["cron_1hour","reg_us", "env_ci", "proj_core", "skip_alt"],
+ path: "tests/assets/suite/single.js",
+ created: Date.now(),
+ mergeRequest: "https://example.com"
+ },
+ {
+ id: 1,
+ name: "failing",
+ class: "failing.js",
+ image: "node:latest",
+ isCompound: false,
+ type: "ui",
+ description: "This is a failing test",
+ tags: ["cron_1hour","reg_us", "env_ci", "proj_core"],
+ path: "tests/assets/suite/failing.js",
+ created: Date.now(),
+ mergeRequest: "https://example.com"
+ },
+ {
+ id: 2,
+ name: "primary",
+ class: "primary.js",
+ image: "node:latest",
+ isCompound: true,
+ type: "api",
+ description: "This is a primary test",
+ tags: ["cron_1hour","reg_us", "proj_core", "skip_alt", "compound_secondary"],
+ path: "tests/assets/suite/primary.js",
+ created: Date.now(),
+ mergeRequest: "https://example.com"
+ }, {
+ id: 3,
+ name: "secondary",
+ class: "secondary.js",
+ image: "node:latest",
+ isCompound: true,
+ type: "api",
+ description: "This is a secondary test",
+ tags: ["cron_1hour","reg_us", "proj_core", "compound_tertiary"],
+ path: "tests/assets/suite/secondary.js",
+ created: Date.now(),
+ mergeRequest: "https://example.com"
+ },
+ {
+ id: 4,
+ name: "tertiary",
+ class: "tertiary.js",
+ image: "node:latest",
+ isCompound: true,
+ type: "api",
+ description: "This is a single test",
+ tags: ["cron_1hour","reg_us", "proj_core"],
+ path: "tests/assets/suite/tertiary.js",
+ created: Date.now(),
+ mergeRequest: "https://example.com"
+ },
+ ];
+};
+
+const mappingsMock = () => {
+ return [
+ ["primary", "secondary1", "tertiary1"],
+ ["primary", "secondary1", "tertiary2"],
+ ["primary", "secondary2", "tertiary3"],
+];
+}
+
+export const getTests = async () => {
+ if (PG_DISABLED) return testsMock();
+ const query = `SELECT * from ${table}`;
+ return pg.query(query);
+};
+
+export const getPipelineMappings = async () => {
+ if (PG_DISABLED) return mappingsMock();
const query = `SELECT * from ${table}`;
return pg.query(query);
};
diff --git a/lib/database/queries/results.js b/lib/database/queries/results.js
index 1cb0b99..9302c2e 100644
--- a/lib/database/queries/results.js
+++ b/lib/database/queries/results.js
@@ -8,6 +8,36 @@ import {
} from "../pg-query.js";
// Constants
const table = "test_results";
+const PG_DISABLED = process.env.POSTGRES_DISABLED;
+
+
+const failingMock = () => {
+ return [{
+ name: "failing",
+ class: "failing.js",
+ timestamp: new Date().toJSON(),
+ method: "FAKEMETHOD",
+ cron: "1hour",
+ type: "api",
+ dailyFails: 12,
+ screenshot: "https://picsum.photos/1920/1080",
+ recentResults: [1, 0, 0, 1, 0],
+ isCompound: false,
+ failedMessage: `Some Test FailureMessage`,
+},{
+ name: "secondary",
+ class: "secondary.js",
+ timestamp: new Date().toJSON(),
+ method: "FAKEMETHOD",
+ cron: "1hour",
+ type: "api",
+ dailyFails: 1,
+ screenshot: "https://picsum.photos/1920/1080",
+ recentResults: [1, 0, 0, 1, 0],
+ isCompound: true,
+ failedMessage: `Some Test FailureMessage from Secondary`,
+}]
+};
// Queries
export const insertTestResult = (testResult) => {
const {
@@ -46,7 +76,9 @@ export const insertTestResult = (testResult) => {
return pg.query(query);
};
-export const getCurrentlyFailing = () => {
+export const getCurrentlyFailing = async () => {
+ if (PG_DISABLED) return failingMock();
+ /**/
const query = `WITH recent as (SELECT * FROM test_results WHERE (timestamp BETWEEN NOW() - INTERVAL '24 HOURS' AND NOW()) AND NOT(failed AND retry)) SELECT * FROM recent WHERE timestamp = (SELECT MAX(timestamp) FROM recent r2 WHERE recent.name = r2.name) AND failed;
`;
return pg.query(query);
diff --git a/lib/routes/alerting-route.js b/lib/routes/alerting-route.js
index 31d9b90..d111e08 100644
--- a/lib/routes/alerting-route.js
+++ b/lib/routes/alerting-route.js
@@ -7,7 +7,7 @@ router.use(jsonMiddleware());
// Get Routes
router.get("/silenced", (req, res) => {
- getSilencedTests().then(res.send);
+ getSilencedTests().then((t)=>res.send(t));
});
// Post Routes
diff --git a/lib/routes/catalog-route.js b/lib/routes/catalog-route.js
index a2f5b41..a9c58c3 100644
--- a/lib/routes/catalog-route.js
+++ b/lib/routes/catalog-route.js
@@ -1,5 +1,5 @@
import { Router, json as jsonMiddleware } from "express";
-import { getTests } from "../database/queries/catalog.js";
+import { getTests, getPipelineMappings } from "../database/queries/catalog.js";
const router = Router();
const maxSize = 1024 * 1024 * 100; // 100MB
@@ -8,11 +8,14 @@ const maxSize = 1024 * 1024 * 100; // 100MB
router.use(jsonMiddleware({ limit: maxSize }));
// Get Routes
-router.get("/tests", async (req, res) => {
- const tests = await getTests();
- res.json(tests);
+router.get("/tests", (req, res) => {
+ getTests().then((t)=>res.json(t));
});
+router.get("/pipeline-mappings", (req,res)=>{
+ getPipelineMappings().then((m)=>res.json(m));
+})
+
// Post Routes
router.post("/update", (req, res) => {
// Update All Tests
diff --git a/lib/routes/results-route.js b/lib/routes/results-route.js
index 025b8ff..b6efa70 100644
--- a/lib/routes/results-route.js
+++ b/lib/routes/results-route.js
@@ -7,7 +7,7 @@ router.use(jsonMiddleware());
// Get Routes
router.get("/failing", (req, res) => {
- res.send([]);
+ getCurrentlyFailing().then((f)=>res.json(f));
});
// Post Routes
diff --git a/package-lock.json b/package-lock.json
index ec33c0e..da418ff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.0.1",
"license": "LGPL-2.1-only",
"dependencies": {
+ "@tanstack/react-query": "^4.0.0",
"amqplib": "^0.8.0",
"chalk": "^5.0.1",
"dotenv": "^16.0.0",
@@ -1071,6 +1072,42 @@
"node": ">=6"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.0.10.tgz",
+ "integrity": "sha512-9LsABpZXkWZHi4P1ozRETEDXQocLAxVzQaIhganxbNuz/uA3PsCAJxJTiQrknG5htLMzOF5MqM9G10e6DCxV1A==",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.0.10.tgz",
+ "integrity": "sha512-Wn5QhZUE5wvr6rGClV7KeQIUsdTmYR9mgmMZen7DSRWauHW2UTynFg3Kkf6pw+XlxxOLsyLWwz/Q6q1lSpM3TQ==",
+ "dependencies": {
+ "@tanstack/query-core": "^4.0.0-beta.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ },
+ "react-native": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@types/component-emitter": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz",
@@ -1153,6 +1190,11 @@
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
"dev": true
},
+ "node_modules/@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
"node_modules/@vitejs/plugin-react": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-1.1.1.tgz",
@@ -5646,6 +5688,14 @@
"node": ">=4"
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
@@ -6585,6 +6635,21 @@
"defer-to-connect": "^1.0.1"
}
},
+ "@tanstack/query-core": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.0.10.tgz",
+ "integrity": "sha512-9LsABpZXkWZHi4P1ozRETEDXQocLAxVzQaIhganxbNuz/uA3PsCAJxJTiQrknG5htLMzOF5MqM9G10e6DCxV1A=="
+ },
+ "@tanstack/react-query": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-4.0.10.tgz",
+ "integrity": "sha512-Wn5QhZUE5wvr6rGClV7KeQIUsdTmYR9mgmMZen7DSRWauHW2UTynFg3Kkf6pw+XlxxOLsyLWwz/Q6q1lSpM3TQ==",
+ "requires": {
+ "@tanstack/query-core": "^4.0.0-beta.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "use-sync-external-store": "^1.2.0"
+ }
+ },
"@types/component-emitter": {
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz",
@@ -6667,6 +6732,11 @@
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
"dev": true
},
+ "@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
"@vitejs/plugin-react": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-1.1.1.tgz",
@@ -10052,6 +10122,12 @@
"prepend-http": "^2.0.0"
}
},
+ "use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+ "requires": {}
+ },
"util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
diff --git a/package.json b/package.json
index 51750e6..5c5073a 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
"path": "^0.12.7",
"pg-promise": "^10.11.1",
"postgres-migrations": "^5.3.0",
+ "@tanstack/react-query": "^4.0.0",
"react-router-dom": "^6.3.0",
"socket.io": "^4.4.1",
"socket.io-client": "^4.4.1",
diff --git a/src/Dashboard.jsx b/src/Dashboard.jsx
index 43d679b..b7a1d09 100644
--- a/src/Dashboard.jsx
+++ b/src/Dashboard.jsx
@@ -1,13 +1,17 @@
// Import Contexts
+import { QueryClient, QueryClientProvider} from '@tanstack/react-query'
import { JobProvider } from "./ctx/JobContext.jsx";
import { StoreProvider } from "./ctx/StoreContext.jsx";
import { BrowserRouter } from "react-router-dom";
// Import Views
import Views from "./views/Views.jsx";
+const queryClient = new QueryClient()
+
export default function Dashboard() {
return (
+
@@ -15,6 +19,7 @@ export default function Dashboard() {
+
);
}
diff --git a/src/Queries.jsx b/src/Queries.jsx
new file mode 100644
index 0000000..1856066
--- /dev/null
+++ b/src/Queries.jsx
@@ -0,0 +1,27 @@
+import { useQuery } from '@tanstack/react-query'
+
+const QUALITEER_URL = "https://qualiteer.elijahparker3.repl.co/api";
+
+export const useCatalogTests = () => useQuery(['catalogTests'], () =>
+ fetch(`${QUALITEER_URL}/catalog/tests`).then(res =>
+ res.json()
+ )
+ )
+
+export const usePipelineMappings = () => useQuery(['pipelineMappings'], () =>
+ fetch(`${QUALITEER_URL}/catalog/pipeline-mappings`).then(res =>
+ res.json()
+ )
+ )
+
+export const useSilencedAlerts = () => useQuery(['silenced'], () =>
+ fetch(`${QUALITEER_URL}/alerting/silenced`).then(res =>
+ res.json()
+ )
+ )
+
+export const useCurrentlyFailing = () => useQuery(['failing'], () =>
+ fetch(`${QUALITEER_URL}/results/failing`).then(res =>
+ res.json()
+ )
+ )
\ No newline at end of file
diff --git a/src/ctx/JobContext.jsx b/src/ctx/JobContext.jsx
index 471d9db..b84cc76 100644
--- a/src/ctx/JobContext.jsx
+++ b/src/ctx/JobContext.jsx
@@ -72,15 +72,14 @@ export const JobProvider = ({ children }) => {
return jobFactory({ testNames: ["single"] });
}
- function jobBuilder(tests) {
- if (!Array.isArray(tests)) throw Error("Error from within JobContext.jsx");
- console.log("Would run tests", tests);
- return jobFactory({ testNames: ["single"] });
+ function pipelineFactory(builderCache) {
+ console.log("Would create pipeline with cache");
+ console.log(builderCache);
+ return jobFactory({testNames: ["single"]});
}
- function pipelineFactory(builderCache) {}
-
function jobFactory(builderCache) {
+ if(builderCache.pipelineMasterTrack) return pipelineFactory(builderCache);
// Find test
const i = new Initiator(url);
const jobId = `j${Date.now()}`;
@@ -141,7 +140,6 @@ export const JobProvider = ({ children }) => {
jobDelete,
retryAll,
retrySingle,
- jobBuilder,
jobFactory,
};
const contextValue = useMemo(() => context, [state, dispatch]);
diff --git a/src/ctx/StoreContext.jsx b/src/ctx/StoreContext.jsx
index 60e2cad..974f295 100644
--- a/src/ctx/StoreContext.jsx
+++ b/src/ctx/StoreContext.jsx
@@ -1,66 +1,10 @@
import React, { useReducer, createContext, useMemo } from "react";
-
const StoreContext = createContext();
const ACTIONS = {
UPDATE: "u",
};
-const pipelineMappingsMock = [
- ["primary", "secondary1", "tertiary1"],
- ["primary", "secondary1", "tertiary2"],
- ["primary", "secondary2", "tertiary3"],
-];
-
-const silencedMock = new Array(10).fill(0).map((v, i) => ({
- name: `Test${i + 1}`,
- class: `SomeTestClass${i % 2 ? i - 1 : i / 2}`,
- method: "someMethod",
- id: Date.now(),
- silencedUntil: `2022-05-10T16:${2 + i}:33.810Z`,
-}));
-
-const catalogMock = new Array(10).fill(0).map((v, i) => ({
- name: `Test${i + 1}`,
- class: `SomeTestClass${i % 2 ? i - 1 : i / 2}`,
- repo: "Repo",
- isCompound: i % 5 ? false : true,
- type: i % 3 ? "api" : "ui",
-}));
-
-const failingMock = new Array(12).fill(0).map((v, i) => ({
- class: `SomeTestClass${i % 2 ? i - 1 : i / 2}`,
- name: `TestThatDoesOneThing${i + 1}`,
- timestamp: `2022-05-10T16:0${i < 10 ? i : i - 10}:33.810Z`,
- method: `SomeMethod`,
- silencedUntil: i % 4 ? null : `2022-05-10T16:0${i}:33.810Z`,
- frequency: "1hour",
- type: i % 3 ? "api" : "ui",
- dailyFails: i + 1,
- screenshot: "https://picsum.photos/1920/1080",
- recentResults: [1, 0, 0, 1, 0],
- isCompound: i % 5 ? false : true,
- failedMessage: `Some Test FailureMessage ${i}`,
- jobStatus: (() => {
- switch (i) {
- case 1:
- return "o";
- case 3:
- return "e";
- case 4:
- return "p";
- case 5:
- return "a";
- case 6:
- return "c";
- case 8:
- return "q";
- default:
- return null;
- }
- })(),
-}));
-
const localStorage = { setItem: () => {}, getItem: () => {} };
const localSettings = localStorage.getItem("settings");
@@ -78,10 +22,6 @@ const settingsKeys = Object.keys(defaultSettings);
const initialState = {
pages: ["failing", "alerting", "jobs", "catalog", "settings", "about"],
intervals: [],
- catalog: catalogMock,
- failing: failingMock,
- silenced: silencedMock,
- pipelineMappings: pipelineMappingsMock,
regions: [],
catalogSearch: "",
...settings,
diff --git a/src/views/Navbar.jsx b/src/views/Navbar.jsx
index 5d1f568..842171d 100644
--- a/src/views/Navbar.jsx
+++ b/src/views/Navbar.jsx
@@ -1,6 +1,7 @@
import { useContext, useState } from "react";
-import StoreContext from "../ctx/StoreContext.jsx";
+import {useCurrentlyFailing} from "../Queries.jsx";
import JobContext from "../ctx/JobContext.jsx";
+import StoreContext from "../ctx/StoreContext.jsx";
import { Link, useLocation } from "react-router-dom";
import AppBar from "@mui/material/AppBar";
@@ -23,16 +24,18 @@ import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
import SettingsIcon from "@mui/icons-material/Settings";
import WarningIcon from "@mui/icons-material/Warning";
import InfoIcon from "@mui/icons-material/Info";
+import SentimentSatisfiedAltIcon from '@mui/icons-material/SentimentSatisfiedAlt';
const drawerWidth = 250;
export default function Navbar(props) {
const { state: jobState } = useContext(JobContext);
const { state: store } = useContext(StoreContext);
+ const {isLoading, data: failing} = useCurrentlyFailing();
const pages = store.pages;
const icons = [
-
-
+
+ {(failing ?? []).length > 0 ? : }
,
,
@@ -66,7 +69,7 @@ export default function Navbar(props) {
if (location.pathname !== "/qualiteer/failing") return pathStr;
return (
diff --git a/src/views/alerting/Alerting.jsx b/src/views/alerting/Alerting.jsx
index d2de54a..54c58fd 100644
--- a/src/views/alerting/Alerting.jsx
+++ b/src/views/alerting/Alerting.jsx
@@ -1,4 +1,5 @@
-import { useState, useContext } from "react";
+import React, { useState, useContext } from "react";
+import {useSilencedAlerts} from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx";
import SilencedBox from "./SilencedBox.jsx";
import SilenceDialog from "./SilenceDialog.jsx";
@@ -13,14 +14,18 @@ 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 Typography from "@mui/material/Typography";
+import Box from "@mui/material/Box";
export default function Alerting() {
const {
- state: store,
updateStore,
silenceRequest,
} = useContext(StoreContext);
+
+const {isLoading, data: silenced} = useSilencedAlerts();
+
const [silenceEntry, setSilenceEntry] = useState({
open: false,
deleteOpen: false,
@@ -56,7 +61,7 @@ export default function Alerting() {
return (
- {store.silenced.map((v, i) => (
+ {isLoading? null : silenced.map((v, i) => (
))}
+
+ {(silenced ?? []).length === 0? (
+
+
+
+ No alerts silenced!
+ Click the '+' to create a new one!
+
+
+
+ ): null}
+
diff --git a/src/views/catalog/CatalogBox.jsx b/src/views/catalog/CatalogBox.jsx
index 74de3f6..b8bca28 100644
--- a/src/views/catalog/CatalogBox.jsx
+++ b/src/views/catalog/CatalogBox.jsx
@@ -29,7 +29,7 @@ export default function CatalogBox(props) {
const { state: store, updateStore } = useContext(StoreContext);
- const { state: jobState, jobBuilder } = useContext(JobContext);
+ const { state: jobState, jobFactory} = useContext(JobContext);
const [open, setOpen] = useState(false);
@@ -38,7 +38,7 @@ export default function CatalogBox(props) {
const runTest = (e) => {
e.preventDefault();
e.stopPropagation();
- const jobId = jobBuilder([catalogTest]);
+ const jobId = jobFactory({testNames: [testName]});
if (store.focusJob) navigate(`/qualiteer/jobs#${jobId}`);
};
diff --git a/src/views/failing/Failing.jsx b/src/views/failing/Failing.jsx
index 58d51e7..e737e75 100644
--- a/src/views/failing/Failing.jsx
+++ b/src/views/failing/Failing.jsx
@@ -1,13 +1,12 @@
import { useState, useContext } from "react";
import { useNavigate } from "react-router-dom";
+import {useCurrentlyFailing} from "../../Queries.jsx";
import StoreContext from "../../ctx/StoreContext.jsx";
import JobContext from "../../ctx/JobContext.jsx";
import SilenceDialog from "../alerting/SilenceDialog.jsx";
import FailingBox from "./FailingBox.jsx";
import SpeedDial from "@mui/material/SpeedDial";
-import SpeedDialAction from "@mui/material/SpeedDialAction";
-import SpeedDialIcon from "@mui/material/SpeedDialIcon";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
@@ -17,6 +16,9 @@ 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);
@@ -26,7 +28,7 @@ export default function Failing() {
updateStore,
silenceRequest,
} = useContext(StoreContext);
- const { failing } = store;
+ const {isLoading, data: failing} = useCurrentlyFailing();
const [silenceEntry, setSilenceEntry] = useState({ open: false });
@@ -56,7 +58,7 @@ export default function Failing() {
return (
- {failing.map((v, i) => (
+ {isLoading? null :failing.map((v, i) => (
))}
@@ -85,13 +87,24 @@ export default function Failing() {
onClose={handleSilenceClose}
silence={silenceEntry}
/>
+ {(failing ?? []).length === 0? (
+ No tests failing!
+
+
+
+ ): null}
+ {(failing ?? []).length ===0? null :(
}
onClick={retryAllClick}
open={false}
- />
+ />)}
);
}
diff --git a/src/views/jobs/Jobs.jsx b/src/views/jobs/Jobs.jsx
index c6833c3..5e38600 100644
--- a/src/views/jobs/Jobs.jsx
+++ b/src/views/jobs/Jobs.jsx
@@ -1,10 +1,12 @@
-import { useState, useContext, useEffect } from "react";
+import React, { useState, useContext, useEffect } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import JobContext from "../../ctx/JobContext.jsx";
import JobBox from "./JobBox.jsx";
import JobView from "./JobView.jsx";
import JobBuilder from "./builder/JobBuilder.jsx";
+import Typography from "@mui/material/Typography";
+import Box from "@mui/material/Box";
export default function Jobs() {
const { state: jobState } = useContext(JobContext);
@@ -19,6 +21,26 @@ export default function Jobs() {
return (
+ {jobState.jobs.length === 0? (
+
+
+
+ No jobs found!
+ Click the '+' to start a new one!
+
+
+
+ ): null}
{location.hash === "" &&
jobState.jobs.map((v, i) => (
diff --git a/src/views/jobs/builder/PipelineConfirm.jsx b/src/views/jobs/builder/PipelineConfirm.jsx
index 48a6adb..c120861 100644
--- a/src/views/jobs/builder/PipelineConfirm.jsx
+++ b/src/views/jobs/builder/PipelineConfirm.jsx
@@ -6,6 +6,7 @@ import DialogContent from "@mui/material/DialogContent";
function PipelineConfirm(props) {
const { cache, back, start } = props;
+
return (
diff --git a/src/views/jobs/builder/PipelineSelector.jsx b/src/views/jobs/builder/PipelineSelector.jsx
index b893cca..b2a9d0e 100644
--- a/src/views/jobs/builder/PipelineSelector.jsx
+++ b/src/views/jobs/builder/PipelineSelector.jsx
@@ -1,6 +1,5 @@
import React, { useContext } from "react";
-import StoreContext from "../../../ctx/StoreContext.jsx";
-
+import {usePipelineMappings} from "../../../Queries.jsx";
import Button from "@mui/material/Button";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
@@ -12,14 +11,19 @@ import Stack from "@mui/material/Stack";
function PipelineSelector(props) {
const { cache, setCache, cancel, next } = props;
- const { state: store } = useContext(StoreContext);
- const { pipelineMappings } = store;
- const primaryMappings = {};
+ const {isLoading, data: pipelineMappings} = usePipelineMappings();
+
+ const primaryMappings = () =>{
+ if(isLoading) return {};
+ const primaryMappings = {};
for (var pm of pipelineMappings) {
if (!(pm[0] in primaryMappings)) primaryMappings[pm[0]] = [];
primaryMappings[pm[0]].push(pm);
}
-
+ return primaryMappings;
+ };
+
+
const selectPrimary = (primarySelectedMappings) => () => {
setCache({ primarySelectedMappings });
};
@@ -27,13 +31,13 @@ function PipelineSelector(props) {
return (
- {Object.keys(primaryMappings).map((k, i) => (
+ {Object.keys(primaryMappings()).map((k, i) => (
{k}
- {primaryMappings[k].length}
+ {(primaryMappings[k] ?? []).length}
))}