Savepoint
This commit is contained in:
parent
7db1a3456b
commit
02c483950c
45 changed files with 5136 additions and 256 deletions
3
.breakpoints
Normal file
3
.breakpoints
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"files": {}
|
||||||
|
}
|
|
@ -1,32 +1,38 @@
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
### The end goal of this application is to provide a powerful application able to process and handle all of the your realtime QA data needs
|
### The end goal of this application is to provide a powerful application able to process and handle all of the your realtime QA data needs
|
||||||
|
|
||||||
## v0.0.1
|
## v0.0.1
|
||||||
|
|
||||||
- [x] Initial Skeleton
|
- [x] Initial Skeleton
|
||||||
- [x] Frontend Drafts
|
- [x] Frontend Drafts
|
||||||
- [ ] Frontend Core
|
- [ ] Frontend Core
|
||||||
- [ ] Frontend Pages
|
- [ ] Frontend Pages
|
||||||
|
|
||||||
## v0.0.2
|
## v0.0.2
|
||||||
|
|
||||||
- [ ] Database Queries (Req PG)
|
- [ ] Database Queries (Req PG)
|
||||||
- [ ] Database Tables (Req PG)
|
- [ ] Database Tables (Req PG)
|
||||||
- [ ] Backend Routes (Req Database)
|
- [ ] Backend Routes (Req Database)
|
||||||
- [ ] Crons
|
- [ ] Crons
|
||||||
|
|
||||||
## v0.0.3
|
## v0.0.3
|
||||||
|
|
||||||
- [ ] Rabbitmq Consumers (Req Database)
|
- [ ] Rabbitmq Consumers (Req Database)
|
||||||
- [ ] Alerting
|
- [ ] Alerting
|
||||||
- [ ] Silencing
|
- [ ] Silencing
|
||||||
|
|
||||||
## v0.0.4
|
## v0.0.4
|
||||||
|
|
||||||
- [ ] Auth
|
- [ ] Auth
|
||||||
|
|
||||||
## v0.0.5
|
## v0.0.5
|
||||||
|
|
||||||
- [ ] Docker config
|
- [ ] Docker config
|
||||||
- [ ] Gitlab Integration
|
- [ ] Gitlab Integration
|
||||||
- [ ] Garden config
|
- [ ] Garden config
|
||||||
|
|
||||||
## v0.0.6
|
## v0.0.6
|
||||||
|
|
||||||
- [ ] Internal Tests
|
- [ ] Internal Tests
|
||||||
- [ ] Self Test Suite
|
- [ ] Self Test Suite
|
||||||
|
|
||||||
|
|
18
dev/query.js
Normal file
18
dev/query.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import {
|
||||||
|
insertQuery,
|
||||||
|
selectWhereAnyQuery,
|
||||||
|
updateWhereAnyQuery,
|
||||||
|
} from "../lib/database/pg-query.js";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
var data = JSON.parse(readFileSync("lib/routes/mocks/results.json"));
|
||||||
|
|
||||||
|
var table = "test_results";
|
||||||
|
var queries = data.results.map((r) => insertQuery(table, r));
|
||||||
|
queries.forEach((q) => console.log(q + ";"));
|
||||||
|
|
||||||
|
console.log();
|
||||||
|
|
||||||
|
table = "test_catalog";
|
||||||
|
data = JSON.parse(readFileSync("lib/routes/mocks/catalog.json"));
|
||||||
|
queries = data["tests:full"].map((r) => insertQuery(table, r));
|
||||||
|
queries.forEach((q) => console.log(q + ";"));
|
3982
dist/bundles/qualiteer-executor.js
vendored
3982
dist/bundles/qualiteer-executor.js
vendored
File diff suppressed because one or more lines are too long
|
@ -13,4 +13,3 @@ if(error) ERR("EXEC", error);
|
||||||
OK("EXEC", "Internal Executor Finished!");
|
OK("EXEC", "Internal Executor Finished!");
|
||||||
process.exit(error ? 1 : 0);
|
process.exit(error ? 1 : 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,11 @@ import path from "path";
|
||||||
const internalDeploy = process.env.INTERNAL_DEPLOY === "true";
|
const internalDeploy = process.env.INTERNAL_DEPLOY === "true";
|
||||||
const executorUrl = process.env.EXECUTOR_URL;
|
const executorUrl = process.env.EXECUTOR_URL;
|
||||||
const executorScriptOnly = process.env.EXECUTOR_SCRIPT_ONLY === "true";
|
const executorScriptOnly = process.env.EXECUTOR_SCRIPT_ONLY === "true";
|
||||||
const executorBin = process.env.EXECUTOR_BIN ?? `qltr-executor${executorScriptOnly ? ".js": ""}`;
|
const executorBin =
|
||||||
|
process.env.EXECUTOR_BIN ?? `qltr-executor${executorScriptOnly ? ".js" : ""}`;
|
||||||
|
|
||||||
const qualiteerUrl = process.env.QUALITEER_URL ?? "file:///home/runner/Qualiteer/bin/executor";
|
const qualiteerUrl =
|
||||||
|
process.env.QUALITEER_URL ?? "file:///home/runner/Qualiteer/bin/executor";
|
||||||
|
|
||||||
const kubCmd = "kubectl apply -f";
|
const kubCmd = "kubectl apply -f";
|
||||||
const jobsDir = "jobs/";
|
const jobsDir = "jobs/";
|
||||||
|
@ -16,11 +18,15 @@ const defaults = JSON.parse(
|
||||||
);
|
);
|
||||||
|
|
||||||
const wrapCommand = (jobId, command) => {
|
const wrapCommand = (jobId, command) => {
|
||||||
const bin = executorScriptOnly ? `node ${executorBin}`:`chmod +x ${executorBin} && ./${executorBin}`;
|
const bin = executorScriptOnly
|
||||||
const cmd = command.map((arg)=>JSON.stringify(arg))
|
? `node ${executorBin}`
|
||||||
const curlCmd = `curl -o qltr-executor ${executorUrl} && ${bin} ${qualiteerUrl} ${jobId} ${cmd.join(" ")}`;
|
: `chmod +x ${executorBin} && ./${executorBin}`;
|
||||||
|
const cmd = command.map((arg) => JSON.stringify(arg));
|
||||||
|
const curlCmd = `curl -o qltr-executor ${executorUrl} && ${bin} ${qualiteerUrl} ${jobId} ${cmd.join(
|
||||||
|
" "
|
||||||
|
)}`;
|
||||||
return curlCmd;
|
return curlCmd;
|
||||||
}
|
};
|
||||||
|
|
||||||
const createFile = (job) => {
|
const createFile = (job) => {
|
||||||
const { name } = job.metadata;
|
const { name } = job.metadata;
|
||||||
|
|
|
@ -5,11 +5,15 @@ import express from "express";
|
||||||
import results from "../routes/results-route.js";
|
import results from "../routes/results-route.js";
|
||||||
import alerting from "../routes/alerting-route.js";
|
import alerting from "../routes/alerting-route.js";
|
||||||
import react from "../routes/react-route.js";
|
import react from "../routes/react-route.js";
|
||||||
import tests from "../routes/tests-route.js";
|
import catalog from "../routes/catalog-route.js";
|
||||||
import jobs from "../routes/jobs-route.js";
|
import jobs from "../routes/jobs-route.js";
|
||||||
|
|
||||||
|
import mock from "../routes/mock-route.js";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
// Special Routes
|
||||||
app.all("/", (req, res) => res.redirect("/qualiteer"));
|
app.all("/", (req, res) => res.redirect("/qualiteer"));
|
||||||
|
if (process.env.MOCK_ROUTES === "true") app.use(mock);
|
||||||
|
|
||||||
// Middlewares
|
// Middlewares
|
||||||
|
|
||||||
|
@ -17,7 +21,7 @@ app.all("/", (req, res) => res.redirect("/qualiteer"));
|
||||||
app.use(react); // Static Build Route
|
app.use(react); // Static Build Route
|
||||||
app.use("/api/results", results);
|
app.use("/api/results", results);
|
||||||
app.use("/api/alerting", alerting);
|
app.use("/api/alerting", alerting);
|
||||||
app.use("/api/tests", tests);
|
app.use("/api/catalog", catalog);
|
||||||
app.use("/api/jobs", jobs);
|
app.use("/api/jobs", jobs);
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
|
@ -17,9 +17,11 @@ CREATE TABLE test_results (
|
||||||
ALTER SEQUENCE test_results_id_seq OWNED BY test_results.id;
|
ALTER SEQUENCE test_results_id_seq OWNED BY test_results.id;
|
||||||
|
|
||||||
# Tables
|
# Tables
|
||||||
|
|
||||||
PG Database Tables Mapped Out
|
PG Database Tables Mapped Out
|
||||||
|
|
||||||
## ```test_results```
|
## `test_results`
|
||||||
|
|
||||||
| id | test_name | test_class | test_method | test_path | test_type | test_timestamp | test_retry | origin | failed | failed_message | screenshot_url | weblog_url |
|
| id | test_name | test_class | test_method | test_path | test_type | test_timestamp | test_retry | origin | failed | failed_message | screenshot_url | weblog_url |
|
||||||
| int | string | string | string | string | string | timestamp | boolean | string | boolean | string | string | string |
|
| int | string | string | string | string | string | timestamp | boolean | string | boolean | string | string | string |
|
||||||
| 1 | My Test | My Test Class | My Failing Test Method | My Test Class Path | API | Date.now() | false | Test Suite A | true | Some Failure Messsage | screenshotUrl | weblogUrl |
|
| 1 | My Test | My Test Class | My Failing Test Method | My Test Class Path | API | Date.now() | false | Test Suite A | true | Some Failure Messsage | screenshotUrl | weblogUrl |
|
||||||
|
@ -37,6 +39,3 @@ PG Database Tables Mapped Out
|
||||||
- failed_message Failure Message of test or null
|
- failed_message Failure Message of test or null
|
||||||
- screenshot_url Screenshot of failure
|
- screenshot_url Screenshot of failure
|
||||||
- weblog_url Log from the web console
|
- weblog_url Log from the web console
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,14 @@
|
||||||
CREATE SEQUENCE test_results_id_seq;
|
CREATE SEQUENCE test_results_id_seq;
|
||||||
CREATE TABLE test_results (
|
CREATE TABLE test_results (
|
||||||
id bigint NOT NULL DEFAULT nextval('test_results_seq') PRIMARY KEY,
|
id bigint NOT NULL DEFAULT nextval('test_results_id_seq') PRIMARY KEY,
|
||||||
test_name varchar(255) DEFAULT NULL,
|
name varchar(255) DEFAULT NULL,
|
||||||
test_class varchar(255) DEFAULT NULL,
|
"method" varchar(255) DEFAULT NULL,
|
||||||
test_method varchar(255) DEFAULT NULL,
|
env varchar(31) DEFAULT NULL,
|
||||||
test_path varchar(255) DEFAULT NULL,
|
"timestamp" TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
test_type varchar(32) DEFAULT NULL,
|
retry BOOLEAN DEFAULT FALSE,
|
||||||
test_timestamp timestamptz NOT NULL DEFAULT now(),
|
|
||||||
test_retry BOOLEAN DEFAULT FALSE,
|
|
||||||
origin varchar(255) DEFAULT NULL,
|
|
||||||
failed BOOLEAN DEFAULT FALSE,
|
failed BOOLEAN DEFAULT FALSE,
|
||||||
failed_message varchar(2047) DEFAULT NULL,
|
failed_message varchar(2047) DEFAULT NULL,
|
||||||
screenshot_url varchar(255) DEFAULT NULL,
|
screenshot varchar(255) DEFAULT NULL,
|
||||||
weblog_url varchar(255) DEFAULT NULL,
|
weblog varchar(255) DEFAULT NULL
|
||||||
);
|
);
|
||||||
ALTER SEQUENCE test_results_id_seq OWNED BY test_results.id;
|
ALTER SEQUENCE test_results_id_seq OWNED BY test_results.id;
|
||||||
|
|
||||||
|
|
||||||
|
|
18
lib/database/migrations/2_create_catalog_table.sql
Normal file
18
lib/database/migrations/2_create_catalog_table.sql
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
CREATE SEQUENCE test_catalog_id_seq;
|
||||||
|
CREATE TABLE test_catalog (
|
||||||
|
id bigint NOT NULL DEFAULT nextval('test_catalog_id_seq') PRIMARY KEY,
|
||||||
|
name varchar(255) DEFAULT NULL,
|
||||||
|
class varchar(255) DEFAULT NULL,
|
||||||
|
compound BOOLEAN DEFAULT FALSE,
|
||||||
|
type varchar(31) DEFAULT NULL,
|
||||||
|
markers varchar(255)[] DEFAULT NULL,
|
||||||
|
ignored BOOLEAN DEFAULT FALSE,
|
||||||
|
comment varchar(1023) DEFAULT NULL,
|
||||||
|
coverage varchar(255)[] DEFAULT NULL,
|
||||||
|
env varchar(31)[] DEFAULT NULL,
|
||||||
|
"path" varchar(255) DEFAULT NULL,
|
||||||
|
regions varchar(15)[] DEFAULT NULL,
|
||||||
|
origin varchar(255) DEFAULT NULL,
|
||||||
|
cron varchar(127) DEFAULT NULL
|
||||||
|
);
|
||||||
|
ALTER SEQUENCE test_catalog_id_seq OWNED BY test_catalog.id;
|
|
@ -2,7 +2,7 @@
|
||||||
import { migrate } from "postgres-migrations";
|
import { migrate } from "postgres-migrations";
|
||||||
import createPgp from "pg-promise";
|
import createPgp from "pg-promise";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { WARN } from "../util/logging.js";
|
import { INFO, WARN } from "../util/logging.js";
|
||||||
// Environment Variables
|
// Environment Variables
|
||||||
const {
|
const {
|
||||||
POSTGRES_DATABASE: database,
|
POSTGRES_DATABASE: database,
|
||||||
|
|
|
@ -12,4 +12,3 @@ export const getSilencedTests = () => {
|
||||||
const query = `SELECT * from ${table}`;
|
const query = `SELECT * from ${table}`;
|
||||||
return pg.query(query);
|
return pg.query(query);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,4 +12,3 @@ export const getTests = () => {
|
||||||
const query = `SELECT * from ${table}`;
|
const query = `SELECT * from ${table}`;
|
||||||
return pg.query(query);
|
return pg.query(query);
|
||||||
};
|
};
|
||||||
|
|
64
lib/database/queries/results.js
Normal file
64
lib/database/queries/results.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import pg from "../postgres.js";
|
||||||
|
// Imports
|
||||||
|
import {
|
||||||
|
insertQuery,
|
||||||
|
selectWhereAnyQuery,
|
||||||
|
selectWhereAllQuery,
|
||||||
|
updateWhereAnyQuery,
|
||||||
|
} from "../pg-query.js";
|
||||||
|
// Constants
|
||||||
|
const table = "test_results";
|
||||||
|
// Queries
|
||||||
|
export const insertTestResult = (testResult) => {
|
||||||
|
const {
|
||||||
|
test_name,
|
||||||
|
test_class,
|
||||||
|
test_method,
|
||||||
|
test_path,
|
||||||
|
test_type,
|
||||||
|
test_timestamp,
|
||||||
|
test_retry,
|
||||||
|
origin,
|
||||||
|
failed,
|
||||||
|
failed_message,
|
||||||
|
screenshot_url,
|
||||||
|
expected_screenshot_url,
|
||||||
|
weblog_url,
|
||||||
|
} = testResult;
|
||||||
|
|
||||||
|
var query = insertQuery(table, {
|
||||||
|
test_name,
|
||||||
|
test_class,
|
||||||
|
test_method,
|
||||||
|
test_path,
|
||||||
|
test_type,
|
||||||
|
test_timestamp,
|
||||||
|
test_retry,
|
||||||
|
origin,
|
||||||
|
failed,
|
||||||
|
failed_message,
|
||||||
|
screenshot_url,
|
||||||
|
expected_screenshot_url,
|
||||||
|
weblog_url,
|
||||||
|
});
|
||||||
|
|
||||||
|
query += "\n RETURNING *";
|
||||||
|
return pg.query(query);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCurrentlyFailing = () => {
|
||||||
|
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);
|
||||||
|
|
||||||
|
/*SELECT * FROM test_results WHERE "timestamp" BETWEEN NOW() - INTERVAL '24 HOURS' AND NOW(); <-- Last 24 hours all runs*/
|
||||||
|
|
||||||
|
/* SELECT * FROM test_results tr1 WHERE timestamp BETWEEN NOW() - INTERVAL '24 HOURS' AND NOW() AND timestamp = (SELECT MAX(timestamp) FROM test_results tr2 WHERE tr1.name = tr2.name); <-- Last 24 hours only most recent
|
||||||
|
*/
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCurrentlyFailingFull = (env) => {
|
||||||
|
const query = `WITH recent AS (SELECT * FROM test_results WHERE (timestamp BETWEEN NOW() - INTERVAL '24 HOURS' AND NOW()) AND NOT(failed AND retry) AND env='prod') SELECT * FROM recent INNER JOIN test_catalog USING(name) WHERE timestamp = (SELECT MAX(timestamp) FROM recent r2 WHERE recent.name = r2.name) AND failed;
|
||||||
|
;`;
|
||||||
|
return pg.query(query);
|
||||||
|
};
|
|
@ -1,51 +0,0 @@
|
||||||
import pg from "../postgres.js";
|
|
||||||
// Imports
|
|
||||||
import {
|
|
||||||
insertQuery,
|
|
||||||
selectWhereAnyQuery,
|
|
||||||
updateWhereAnyQuery,
|
|
||||||
} from "../pg-query.js";
|
|
||||||
// Constants
|
|
||||||
const table = "test_results";
|
|
||||||
// Queries
|
|
||||||
export const insertTestResult = (testResult) => {
|
|
||||||
const {
|
|
||||||
test_name,
|
|
||||||
test_class,
|
|
||||||
test_method,
|
|
||||||
test_path,
|
|
||||||
test_type,
|
|
||||||
test_timestamp,
|
|
||||||
test_retry,
|
|
||||||
origin,
|
|
||||||
failed,
|
|
||||||
failed_message,
|
|
||||||
screenshot_url,
|
|
||||||
expected_screenshot_url,
|
|
||||||
weblog_url,
|
|
||||||
} = testResult;
|
|
||||||
|
|
||||||
var query = insertQuery(table, {
|
|
||||||
test_name,
|
|
||||||
test_class,
|
|
||||||
test_method,
|
|
||||||
test_path,
|
|
||||||
test_type,
|
|
||||||
test_timestamp,
|
|
||||||
test_retry,
|
|
||||||
origin,
|
|
||||||
failed,
|
|
||||||
failed_message,
|
|
||||||
screenshot_url,
|
|
||||||
expected_screenshot_url,
|
|
||||||
weblog_url,
|
|
||||||
});
|
|
||||||
|
|
||||||
query += "\n RETURNING *";
|
|
||||||
return pg.query(query);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getCurrentlyFailing = () => {
|
|
||||||
const query = "SELECT *";
|
|
||||||
return pg.query(query);
|
|
||||||
};
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Router, json as jsonMiddleware } from "express";
|
import { Router, json as jsonMiddleware } from "express";
|
||||||
import { getSilencedTests } from "../database/queries/silenced_tests.js";
|
import { getSilencedTests } from "../database/queries/alerting.js";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// Apply Middlewares
|
// Apply Middlewares
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Router, json as jsonMiddleware } from "express";
|
import { Router, json as jsonMiddleware } from "express";
|
||||||
import { getTests } from "../database/queries/tests.js";
|
import { getTests } from "../database/queries/catalog.js";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
const maxSize = 1024 * 1024 * 100; // 100MB
|
const maxSize = 1024 * 1024 * 100; // 100MB
|
37
lib/routes/mock-route.js
Normal file
37
lib/routes/mock-route.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { Router } from "express";
|
||||||
|
import { readFileSync } from "fs";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
const catalog = "lib/routes/mocks/catalog.json";
|
||||||
|
const alerting = "lib/routes/mocks/alerting.json";
|
||||||
|
const results = "lib/routes/mocks/results.json";
|
||||||
|
|
||||||
|
const query = async (mock) => JSON.parse(readFileSync(mock));
|
||||||
|
|
||||||
|
// Queries
|
||||||
|
router.get("/api/catalog/tests", (req, res) => {
|
||||||
|
query(catalog).then((catalog) => {
|
||||||
|
res.json(req.get("full") ? catalog["tests:full"] : catalog.tests);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get("/api/results/failing", async (req, res) => {
|
||||||
|
query(results).then(async (results) => {
|
||||||
|
if (req.get("count")) res.json({ failing: results.results.length });
|
||||||
|
else if (!req.get("full")) res.json(results.results);
|
||||||
|
else
|
||||||
|
query(catalog).then((catalog) => {
|
||||||
|
res.json(
|
||||||
|
results.results.map((r) => ({
|
||||||
|
...catalog["tests:full"].find((t) => t.name === r.name),
|
||||||
|
...r,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mutations
|
||||||
|
|
||||||
|
export default router;
|
32
lib/routes/mocks/alerting.json
Normal file
32
lib/routes/mocks/alerting.json
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"silenced": [
|
||||||
|
{
|
||||||
|
"name": "Test1",
|
||||||
|
"class": "TestClass1",
|
||||||
|
"method": "TestMethod1",
|
||||||
|
"regions": ["us"],
|
||||||
|
"alerting": "MyUtcDate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test2",
|
||||||
|
"class": null,
|
||||||
|
"method": "TestMethod1",
|
||||||
|
"regions": [],
|
||||||
|
"alerting": "MyUtcDate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "*",
|
||||||
|
"class": "*",
|
||||||
|
"method": "TestMethod1",
|
||||||
|
"regions": [],
|
||||||
|
"alerting": "MyUtcDate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test3",
|
||||||
|
"class": "TestClass3",
|
||||||
|
"method": "*",
|
||||||
|
"regions": ["us", "au"],
|
||||||
|
"alerting": "MyUtcDate"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
92
lib/routes/mocks/catalog.json
Normal file
92
lib/routes/mocks/catalog.json
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
{
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"name": "Test 1",
|
||||||
|
"class": "Test Class 1",
|
||||||
|
"compound": false,
|
||||||
|
"type": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test 2",
|
||||||
|
"class": "Test Class 2",
|
||||||
|
"compound": false,
|
||||||
|
"type": "ui"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test Primary",
|
||||||
|
"class": "Test Class Primary",
|
||||||
|
"compound": true,
|
||||||
|
"type": "api"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test Secondary",
|
||||||
|
"class": "Test Class Secondary",
|
||||||
|
"compound": true,
|
||||||
|
"type": "ui"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tests:full": [
|
||||||
|
{
|
||||||
|
"name": "Test1",
|
||||||
|
"class": "TestClass1",
|
||||||
|
"compound": false,
|
||||||
|
"type": "api",
|
||||||
|
"markers": ["Service1"],
|
||||||
|
"ignored": false,
|
||||||
|
"comment": "This Comment Is Part Of Test 1",
|
||||||
|
"coverage": ["/api/test1", "/api/test2"],
|
||||||
|
"env": ["prod", "ci"],
|
||||||
|
"path": "tests/api/test1.js",
|
||||||
|
"regions": ["us", "ca"],
|
||||||
|
"origin": "Repo1",
|
||||||
|
"cron": "1hour"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test2",
|
||||||
|
"class": "TestClass2",
|
||||||
|
"compound": false,
|
||||||
|
"type": "ui",
|
||||||
|
"markers": ["Service2"],
|
||||||
|
"ignored": false,
|
||||||
|
"comment": "Comment belonging to Test2",
|
||||||
|
"coverage": ["Page1/FeatureA"],
|
||||||
|
"env": ["prod"],
|
||||||
|
"path": "tests/ui/test2.js",
|
||||||
|
"regions": [],
|
||||||
|
"origin": "Repo2",
|
||||||
|
"cron": "30min"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TestPrimary",
|
||||||
|
"class": "TestClassPrimary",
|
||||||
|
"compound": true,
|
||||||
|
"type": "api",
|
||||||
|
"markers": [
|
||||||
|
"ServiceComplex",
|
||||||
|
"compound_Repo2!TestClassSecondary#TestSecondary"
|
||||||
|
],
|
||||||
|
"ignored": false,
|
||||||
|
"comment": "Comment belonging to Test Primary",
|
||||||
|
"coverage": ["/api/compound"],
|
||||||
|
"env": ["ci"],
|
||||||
|
"path": "tests/api/primary.js",
|
||||||
|
"regions": [],
|
||||||
|
"origin": "Repo1",
|
||||||
|
"cron": "2hour"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TestSecondary",
|
||||||
|
"class": "TestClassSecondary",
|
||||||
|
"compound": true,
|
||||||
|
"type": "ui",
|
||||||
|
"markers": ["ServiceComplex"],
|
||||||
|
"ignored": false,
|
||||||
|
"coverage": ["PageComplex/FeatureA"],
|
||||||
|
"env": ["ci"],
|
||||||
|
"path": "tests/ui/secondary.js",
|
||||||
|
"regions": [],
|
||||||
|
"origin": "Repo2",
|
||||||
|
"cron": "2hour"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
48
lib/routes/mocks/results.json
Normal file
48
lib/routes/mocks/results.json
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"results": [
|
||||||
|
{
|
||||||
|
"name": "Test1",
|
||||||
|
"method": "Test1Method",
|
||||||
|
"env": "prod",
|
||||||
|
"timestamp": "2022-05-10T16:35:27.220Z",
|
||||||
|
"retry": false,
|
||||||
|
"failed": true,
|
||||||
|
"failed_message": "Some failure message",
|
||||||
|
"screenshot": "https://example.com",
|
||||||
|
"weblog": "https://example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test2",
|
||||||
|
"method": "Test2Method",
|
||||||
|
"env": "prod",
|
||||||
|
"timestamp": "2022-05-10T16:35:31.682Z",
|
||||||
|
"retry": false,
|
||||||
|
"failed": true,
|
||||||
|
"failed_message": "Some failure message 2",
|
||||||
|
"screenshot": "https://example.com",
|
||||||
|
"weblog": "https://example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test1",
|
||||||
|
"method": null,
|
||||||
|
"env": "prod",
|
||||||
|
"timestamp": "2022-05-10T16:35:33.810Z",
|
||||||
|
"retry": false,
|
||||||
|
"failed": false,
|
||||||
|
"failed_message": null,
|
||||||
|
"screenshot": "https://example.com",
|
||||||
|
"weblog": "https://example.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Test1",
|
||||||
|
"method": null,
|
||||||
|
"env": "ci",
|
||||||
|
"timestamp": "2022-05-10T16:35:33.810Z",
|
||||||
|
"retry": false,
|
||||||
|
"failed": false,
|
||||||
|
"failed_message": null,
|
||||||
|
"screenshot": "https://example.com",
|
||||||
|
"weblog": "https://example.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { Router, json as jsonMiddleware } from "express";
|
import { Router, json as jsonMiddleware } from "express";
|
||||||
import { getCurrentlyFailing } from "../database/queries/test_results.js";
|
import { getCurrentlyFailing } from "../database/queries/results.js";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
// Apply Middlewares
|
// Apply Middlewares
|
||||||
|
|
|
@ -67,7 +67,8 @@ export default class Executor {
|
||||||
report(d, dType) {
|
report(d, dType) {
|
||||||
this.buf[dType] += d;
|
this.buf[dType] += d;
|
||||||
if (!this.buf[dType].includes("\n")) return;
|
if (!this.buf[dType].includes("\n")) return;
|
||||||
if(this.buf[dType].endsWith("\n")) this.buf[dType] = this.buf[dType].slice(0, -1);
|
if (this.buf[dType].endsWith("\n"))
|
||||||
|
this.buf[dType] = this.buf[dType].slice(0, -1);
|
||||||
this.socket.emit(events.JOB_REP, this.buf[dType]);
|
this.socket.emit(events.JOB_REP, this.buf[dType]);
|
||||||
if (dType === ERR) console.error(`err: ${this.buf[dType]}`);
|
if (dType === ERR) console.error(`err: ${this.buf[dType]}`);
|
||||||
else console.log(`out: ${this.buf[dType]}`);
|
else console.log(`out: ${this.buf[dType]}`);
|
||||||
|
|
134
package-lock.json
generated
134
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "qualiteer",
|
"name": "qualiteer",
|
||||||
"version": "1.0.0",
|
"version": "0.0.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "qualiteer",
|
"name": "qualiteer",
|
||||||
"version": "1.0.0",
|
"version": "0.0.1",
|
||||||
"license": "LGPL-2.1-only",
|
"license": "LGPL-2.1-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"amqplib": "^0.8.0",
|
"amqplib": "^0.8.0",
|
||||||
|
@ -18,12 +18,13 @@
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pg-promise": "^10.11.1",
|
"pg-promise": "^10.11.1",
|
||||||
"postgres-migrations": "^5.3.0",
|
"postgres-migrations": "^5.3.0",
|
||||||
|
"react-router-dom": "^6.3.0",
|
||||||
"socket.io": "^4.4.1",
|
"socket.io": "^4.4.1",
|
||||||
"socket.io-client": "^4.4.1",
|
"socket.io-client": "^4.4.1",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"qualiteer": "bin/app.js"
|
"qualiteer": "dist/app.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@emotion/react": "^11.9.0",
|
"@emotion/react": "^11.9.0",
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
"@rollup/plugin-commonjs": "^22.0.0",
|
"@rollup/plugin-commonjs": "^22.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||||
"@rollup/plugin-replace": "^4.0.0",
|
"@rollup/plugin-replace": "^4.0.0",
|
||||||
|
"axios": "^0.27.2",
|
||||||
"caxa": "^2.1.0",
|
"caxa": "^2.1.0",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
|
@ -1904,7 +1906,6 @@
|
||||||
"version": "7.17.9",
|
"version": "7.17.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
|
||||||
"integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
|
"integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
},
|
},
|
||||||
|
@ -5160,6 +5161,30 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.14.9",
|
||||||
|
"form-data": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/axios/node_modules/form-data": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/axobject-query": {
|
"node_modules/axobject-query": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||||
|
@ -9674,6 +9699,14 @@
|
||||||
"he": "bin/he"
|
"he": "bin/he"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/history": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.7.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hoist-non-react-statics": {
|
"node_modules/hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
@ -12764,8 +12797,7 @@
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "3.14.1",
|
"version": "3.14.1",
|
||||||
|
@ -13173,7 +13205,6 @@
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
},
|
},
|
||||||
|
@ -15814,7 +15845,6 @@
|
||||||
"version": "18.1.0",
|
"version": "18.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
|
||||||
"integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
|
"integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
},
|
},
|
||||||
|
@ -16012,7 +16042,6 @@
|
||||||
"version": "18.1.0",
|
"version": "18.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
|
||||||
"integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
|
"integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.22.0"
|
"scheduler": "^0.22.0"
|
||||||
|
@ -16042,6 +16071,30 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"history": "^5.2.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
|
||||||
|
"dependencies": {
|
||||||
|
"history": "^5.2.0",
|
||||||
|
"react-router": "6.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-scripts": {
|
"node_modules/react-scripts": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||||
|
@ -16407,8 +16460,7 @@
|
||||||
"node_modules/regenerator-runtime": {
|
"node_modules/regenerator-runtime": {
|
||||||
"version": "0.13.9",
|
"version": "0.13.9",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||||
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
|
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/regenerator-transform": {
|
"node_modules/regenerator-transform": {
|
||||||
"version": "0.15.0",
|
"version": "0.15.0",
|
||||||
|
@ -16913,7 +16965,6 @@
|
||||||
"version": "0.22.0",
|
"version": "0.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
|
||||||
"integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
|
"integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
|
@ -21090,7 +21141,6 @@
|
||||||
"version": "7.17.9",
|
"version": "7.17.9",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.9.tgz",
|
||||||
"integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
|
"integrity": "sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
|
@ -23495,6 +23545,29 @@
|
||||||
"integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==",
|
"integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "0.27.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
|
||||||
|
"integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.14.9",
|
||||||
|
"form-data": "^4.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"form-data": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"axobject-query": {
|
"axobject-query": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
|
||||||
|
@ -26887,6 +26960,14 @@
|
||||||
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"history": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.7.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"hoist-non-react-statics": {
|
"hoist-non-react-statics": {
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
@ -29151,8 +29232,7 @@
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"js-yaml": {
|
"js-yaml": {
|
||||||
"version": "3.14.1",
|
"version": "3.14.1",
|
||||||
|
@ -29495,7 +29575,6 @@
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||||
}
|
}
|
||||||
|
@ -31312,7 +31391,6 @@
|
||||||
"version": "18.1.0",
|
"version": "18.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-18.1.0.tgz",
|
||||||
"integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
|
"integrity": "sha512-4oL8ivCz5ZEPyclFQXaNksK3adutVS8l2xzZU0cqEFrE9Sb7fC0EFK5uEk74wIreL1DERyjvsU915j1pcT2uEQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
|
@ -31455,7 +31533,6 @@
|
||||||
"version": "18.1.0",
|
"version": "18.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.1.0.tgz",
|
||||||
"integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
|
"integrity": "sha512-fU1Txz7Budmvamp7bshe4Zi32d0ll7ect+ccxNu9FlObT605GOEB8BfO4tmRJ39R5Zj831VCpvQ05QPBW5yb+w==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0",
|
"loose-envify": "^1.1.0",
|
||||||
"scheduler": "^0.22.0"
|
"scheduler": "^0.22.0"
|
||||||
|
@ -31479,6 +31556,23 @@
|
||||||
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==",
|
"integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"react-router": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
|
||||||
|
"requires": {
|
||||||
|
"history": "^5.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"react-router-dom": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==",
|
||||||
|
"requires": {
|
||||||
|
"history": "^5.2.0",
|
||||||
|
"react-router": "6.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-scripts": {
|
"react-scripts": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
|
||||||
|
@ -31727,8 +31821,7 @@
|
||||||
"regenerator-runtime": {
|
"regenerator-runtime": {
|
||||||
"version": "0.13.9",
|
"version": "0.13.9",
|
||||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz",
|
||||||
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==",
|
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"regenerator-transform": {
|
"regenerator-transform": {
|
||||||
"version": "0.15.0",
|
"version": "0.15.0",
|
||||||
|
@ -32072,7 +32165,6 @@
|
||||||
"version": "0.22.0",
|
"version": "0.22.0",
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
|
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.22.0.tgz",
|
||||||
"integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
|
"integrity": "sha512-6QAm1BgQI88NPYymgGQLCZgvep4FyePDWFpXVK+zNSUgHwlqpJy8VEh8Et0KxTACS4VWwMousBElAZOH9nkkoQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"loose-envify": "^1.1.0"
|
"loose-envify": "^1.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
"start:react": "react-scripts start",
|
"start:react": "react-scripts start",
|
||||||
"start:react:replit": "DANGEROUSLY_DISABLE_HOST_CHECK=true npm run start:react",
|
"start:react:replit": "DANGEROUSLY_DISABLE_HOST_CHECK=true npm run start:react",
|
||||||
"test": "node tests/index.js",
|
"test": "node tests/index.js",
|
||||||
|
"test:api": "node tests/api.js",
|
||||||
"test:dev": "nodemon tests/index.js"
|
"test:dev": "nodemon tests/index.js"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
|
@ -49,6 +50,7 @@
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"pg-promise": "^10.11.1",
|
"pg-promise": "^10.11.1",
|
||||||
"postgres-migrations": "^5.3.0",
|
"postgres-migrations": "^5.3.0",
|
||||||
|
"react-router-dom": "^6.3.0",
|
||||||
"socket.io": "^4.4.1",
|
"socket.io": "^4.4.1",
|
||||||
"socket.io-client": "^4.4.1",
|
"socket.io-client": "^4.4.1",
|
||||||
"uuid": "^8.3.2"
|
"uuid": "^8.3.2"
|
||||||
|
@ -61,6 +63,7 @@
|
||||||
"@rollup/plugin-commonjs": "^22.0.0",
|
"@rollup/plugin-commonjs": "^22.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^13.3.0",
|
"@rollup/plugin-node-resolve": "^13.3.0",
|
||||||
"@rollup/plugin-replace": "^4.0.0",
|
"@rollup/plugin-replace": "^4.0.0",
|
||||||
|
"axios": "^0.27.2",
|
||||||
"caxa": "^2.1.0",
|
"caxa": "^2.1.0",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 18 KiB |
BIN
public/assets/QA.png
Normal file
BIN
public/assets/QA.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -6,7 +6,7 @@ export default {
|
||||||
input: "lib/core/executor.js",
|
input: "lib/core/executor.js",
|
||||||
output: {
|
output: {
|
||||||
file: "dist/bundles/qualiteer-executor.js",
|
file: "dist/bundles/qualiteer-executor.js",
|
||||||
format: "cjs"
|
format: "cjs",
|
||||||
},
|
},
|
||||||
plugins: [nodeResolve(), commonjs(), terser()],
|
plugins: [nodeResolve(), commonjs(), terser()],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { useContext } from "react";
|
|
||||||
|
|
||||||
// Import Contexts
|
// Import Contexts
|
||||||
import { JobProvider } from "./ctx/JobContext.jsx";
|
import { JobProvider } from "./ctx/JobContext.jsx";
|
||||||
import { ViewProvider } from "./ctx/ViewContext.jsx";
|
|
||||||
import { StoreProvider } from "./ctx/StoreContext.jsx";
|
import { StoreProvider } from "./ctx/StoreContext.jsx";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
// Import Views
|
// Import Views
|
||||||
import Views from "./Views.jsx";
|
import Views from "./Views.jsx";
|
||||||
|
|
||||||
|
@ -13,9 +10,9 @@ export default function Dashboard() {
|
||||||
<div className="qualiteer">
|
<div className="qualiteer">
|
||||||
<JobProvider>
|
<JobProvider>
|
||||||
<StoreProvider>
|
<StoreProvider>
|
||||||
<ViewProvider>
|
<BrowserRouter>
|
||||||
<Views />
|
<Views />
|
||||||
</ViewProvider>
|
</BrowserRouter>
|
||||||
</StoreProvider>
|
</StoreProvider>
|
||||||
</JobProvider>
|
</JobProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|
159
src/Views.jsx
159
src/Views.jsx
|
@ -1,21 +1,23 @@
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
import ViewContext from "./ctx/ViewContext.jsx";
|
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
Routes,
|
||||||
|
Route,
|
||||||
|
Link,
|
||||||
|
BrowserRouter,
|
||||||
|
Navigate,
|
||||||
|
useLocation,
|
||||||
|
} from "react-router-dom";
|
||||||
import AppBar from "@mui/material/AppBar";
|
import AppBar from "@mui/material/AppBar";
|
||||||
|
import Badge, { BadgeProps } from "@mui/material/Badge";
|
||||||
|
import { styled } from "@mui/material/styles";
|
||||||
import Box from "@mui/material/Box";
|
import Box from "@mui/material/Box";
|
||||||
import Toolbar from "@mui/material/Toolbar";
|
import Toolbar from "@mui/material/Toolbar";
|
||||||
import IconButton from "@mui/material/IconButton";
|
import IconButton from "@mui/material/IconButton";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import Menu from "@mui/material/Menu";
|
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import Container from "@mui/material/Container";
|
|
||||||
import Avatar from "@mui/material/Avatar";
|
import Avatar from "@mui/material/Avatar";
|
||||||
import Button from "@mui/material/Button";
|
|
||||||
import Tooltip from "@mui/material/Tooltip";
|
|
||||||
import MenuItem from "@mui/material/MenuItem";
|
|
||||||
import Drawer from "@mui/material/Drawer";
|
import Drawer from "@mui/material/Drawer";
|
||||||
import ListItem from "@mui/material/ListItem";
|
|
||||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||||
import ListItemText from "@mui/material/ListItemText";
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
import List from "@mui/material/List";
|
import List from "@mui/material/List";
|
||||||
|
@ -24,48 +26,68 @@ import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||||
import WorkIcon from "@mui/icons-material/Work";
|
import WorkIcon from "@mui/icons-material/Work";
|
||||||
import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
|
import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted";
|
||||||
import SettingsIcon from "@mui/icons-material/Settings";
|
import SettingsIcon from "@mui/icons-material/Settings";
|
||||||
import ErrorIcon from "@mui/icons-material/Error";
|
import WarningIcon from "@mui/icons-material/Warning";
|
||||||
|
import InfoIcon from "@mui/icons-material/Info";
|
||||||
|
// Import Pages
|
||||||
|
import Failing from "./views/Failing.jsx";
|
||||||
|
import Alerting from "./views/Alerting.jsx";
|
||||||
|
import Jobs from "./views/Jobs.jsx";
|
||||||
|
import Catalog from "./views/Catalog.jsx";
|
||||||
|
import Settings from "./views/Settings.jsx";
|
||||||
|
import About from "./views/About.jsx";
|
||||||
|
|
||||||
const pages = ["failing", "alerting", "jobs", "tests", "settings"];
|
const pages = ["failing", "alerting", "jobs", "catalog", "settings", "about"];
|
||||||
const icons = [
|
const icons = [
|
||||||
ErrorIcon,
|
<Badge badgeContent={4} color="error">
|
||||||
NotificationsIcon,
|
<WarningIcon />
|
||||||
WorkIcon,
|
</Badge>,
|
||||||
FormatListBulletedIcon,
|
<NotificationsIcon />,
|
||||||
SettingsIcon,
|
<Badge badgeContent={4} color="primary">
|
||||||
|
<WorkIcon />
|
||||||
|
</Badge>,
|
||||||
|
<FormatListBulletedIcon />,
|
||||||
|
<SettingsIcon />,
|
||||||
|
<InfoIcon />,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const drawerWidth = 240;
|
||||||
|
|
||||||
export default function Views() {
|
export default function Views() {
|
||||||
const [view, setView] = useState(pages[0]);
|
const location = useLocation();
|
||||||
const [drawerOpen, setDrawer] = React.useState(false);
|
const [drawerOpen, setDrawer] = React.useState(false);
|
||||||
|
|
||||||
const toggleDrawer = () => setDrawer(!drawerOpen);
|
const toggleDrawer = () => setDrawer(!drawerOpen);
|
||||||
const closeDrawer = () => setDrawer(false);
|
const closeDrawer = () => setDrawer(false);
|
||||||
const openPage = (e) => setView(e.target.outerText.toLowerCase());
|
|
||||||
|
const reloadPage = () => window.location.reload(false);
|
||||||
|
|
||||||
|
const SideBadge = styled(Badge)(({ theme }) => ({
|
||||||
|
"& .MuiBadge-badge": {
|
||||||
|
right: -6,
|
||||||
|
top: 10,
|
||||||
|
padding: "0 4px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const navHeader = () => {
|
||||||
|
const pathStr =
|
||||||
|
location.pathname.charAt(1).toUpperCase() + location.pathname.slice(2);
|
||||||
|
if (location.pathname !== "/failing") return pathStr;
|
||||||
|
return (
|
||||||
|
<SideBadge badgeContent={4} color="error" overlap="circular">
|
||||||
|
{pathStr}
|
||||||
|
</SideBadge>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppBar position="static" color="secondary">
|
<div className="view">
|
||||||
<Container maxWidth="xl">
|
<AppBar
|
||||||
<Toolbar disableGutters>
|
position="fixed"
|
||||||
<Drawer open={drawerOpen} onClose={closeDrawer}>
|
sx={{ bgcolor: "black", zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
||||||
{" "}
|
|
||||||
<Box sx={{ width: 250 }} role="presentation">
|
|
||||||
<List>
|
|
||||||
{pages.map((text, index) => (
|
|
||||||
<ListItemButton
|
|
||||||
key={text}
|
|
||||||
onClick={openPage}
|
|
||||||
selected={view === text}
|
|
||||||
>
|
>
|
||||||
<ListItemIcon>{/*icons[index]*/}</ListItemIcon>
|
<Box sx={{ flexGrow: 1, margin: "0 20px" }}>
|
||||||
<ListItemText
|
<Toolbar disableGutters>
|
||||||
primary={text.charAt(0).toUpperCase() + text.slice(1)}
|
|
||||||
/>
|
|
||||||
</ListItemButton>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
</Box>
|
|
||||||
</Drawer>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
size="large"
|
size="large"
|
||||||
edge="start"
|
edge="start"
|
||||||
|
@ -76,27 +98,52 @@ export default function Views() {
|
||||||
>
|
>
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography
|
<Drawer open={drawerOpen} onClose={closeDrawer}>
|
||||||
variant="h6"
|
<Toolbar />
|
||||||
noWrap
|
<Box sx={{ width: 250, overflow: "auto" }} role="presentation">
|
||||||
component="div"
|
<List>
|
||||||
sx={{ mr: 2, display: { xs: "none", md: "flex" } }}
|
{pages.map((text, index) => (
|
||||||
|
<ListItemButton
|
||||||
|
key={text}
|
||||||
|
component={Link}
|
||||||
|
to={"/" + text}
|
||||||
|
selected={location.pathname === "/" + text}
|
||||||
|
onClick={closeDrawer}
|
||||||
>
|
>
|
||||||
{view.charAt(0).toUpperCase() + view.slice(1)}
|
<ListItemIcon>{icons[index]}</ListItemIcon>
|
||||||
</Typography>
|
<ListItemText
|
||||||
<Typography
|
primary={text.charAt(0).toUpperCase() + text.slice(1)}
|
||||||
variant="h6"
|
/>
|
||||||
noWrap
|
</ListItemButton>
|
||||||
component="div"
|
))}
|
||||||
sx={{ flexGrow: 1, display: { xs: "flex", md: "none" } }}
|
</List>
|
||||||
>
|
|
||||||
{view.charAt(0).toUpperCase() + view.slice(1)}
|
|
||||||
</Typography>
|
|
||||||
<Box sx={{ flexGrow: 0 }}>
|
|
||||||
<Avatar alt="Remy Sharp" src="/assets/QA.jpg" />
|
|
||||||
</Box>
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
noWrap
|
||||||
|
component="div"
|
||||||
|
sx={{ flexGrow: 1 }}
|
||||||
|
>
|
||||||
|
{navHeader()}
|
||||||
|
</Typography>
|
||||||
|
<Avatar alt="Remy Sharp" src="/assets/QA.png" onClick={reloadPage}/>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</Container>
|
</Box>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|
||||||
|
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
|
||||||
|
<Toolbar />
|
||||||
|
<Routes>
|
||||||
|
<Route exact path="/" element={<Navigate to="/failing" replace />} />
|
||||||
|
<Route path="/failing" element={<Failing />} />
|
||||||
|
<Route path="/alerting" element={<Alerting />} />
|
||||||
|
<Route path="/jobs" element={<Jobs />} />
|
||||||
|
<Route path="/catalog" element={<Catalog />} />
|
||||||
|
<Route path="/settings" element={<Settings pages={pages} />} />
|
||||||
|
<Route path="/about" element={<About />} />
|
||||||
|
</Routes>
|
||||||
|
</Box>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,16 +36,32 @@ const reducer = (state, action) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const JobProvider = ({ children }) => {
|
export const JobProvider = ({ children }) => {
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
const [state, dispatch] = useReducer(reducer, initialState);
|
||||||
|
|
||||||
|
const jobUpdate = (job, jobId) => dispatch({ type: ACTIONS.UPDATE, jobId, job });
|
||||||
|
const jobCreate = (job) =>
|
||||||
|
dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } });
|
||||||
|
const jobDelete = (jobId) => dispatch({ type: ACTIONS.DELETE, jobId });
|
||||||
|
|
||||||
|
function retryAll(failing){
|
||||||
|
// Query Full Locator
|
||||||
|
console.log("Would retry all failing tests!");
|
||||||
|
}
|
||||||
|
|
||||||
|
function jobBuilder(){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
state,
|
state,
|
||||||
dispatch,
|
dispatch,
|
||||||
jobUpdate: (job, jobId) => dispatch({ type: ACTIONS.UPDATE, jobId, job }),
|
jobUpdate,
|
||||||
jobCreate: (job) =>
|
jobCreate,
|
||||||
dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } }),
|
jobDelete,
|
||||||
jobDelete: (jobId) => dispatch({ type: ACTIONS.DELETE, jobId }),
|
retryAll
|
||||||
};
|
};
|
||||||
const contextValue = useMemo(() => context, [state, dispatch]);
|
const contextValue = useMemo(() => context, [state, dispatch]);
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,18 @@ const ACTIONS = {
|
||||||
UPDATE: "u",
|
UPDATE: "u",
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState = {};
|
const initialState = {
|
||||||
|
intervals: [],
|
||||||
|
failing: [],
|
||||||
|
regions: [],
|
||||||
|
focusJob: false,
|
||||||
|
simplifiedControls: false,
|
||||||
|
defaultRegion: "us", // Local Store
|
||||||
|
defaultPage: "failing", // Local Store
|
||||||
|
};
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
const reducer = (state, action) => {
|
||||||
|
const { store } = action;
|
||||||
// Actions
|
// Actions
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ACTIONS.UPDATE:
|
case ACTIONS.UPDATE:
|
||||||
|
@ -23,7 +32,7 @@ export const StoreProvider = ({ children }) => {
|
||||||
const context = {
|
const context = {
|
||||||
state,
|
state,
|
||||||
dispatch,
|
dispatch,
|
||||||
updateStore: (store) => dispatch(state, { type: ACTIONS.UPDATE, store }),
|
updateStore: (store) => dispatch({ type: ACTIONS.UPDATE, store }),
|
||||||
};
|
};
|
||||||
const contextValue = useMemo(() => context, [state, dispatch]);
|
const contextValue = useMemo(() => context, [state, dispatch]);
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import React, { useReducer, createContext, useMemo } from "react";
|
|
||||||
const ViewContext = createContext();
|
|
||||||
|
|
||||||
const ACTIONS = {
|
|
||||||
UPDATE: "u",
|
|
||||||
};
|
|
||||||
|
|
||||||
const initialState = {
|
|
||||||
activePage: "Home",
|
|
||||||
};
|
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
|
||||||
// Actions
|
|
||||||
switch (action.type) {
|
|
||||||
case ACTIONS.UPDATE:
|
|
||||||
return { ...state };
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ViewProvider = ({ children }) => {
|
|
||||||
const [state, dispatch] = useReducer(reducer, initialState);
|
|
||||||
|
|
||||||
const contextValue = useMemo(() => ({ state, dispatch }), [state, dispatch]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ViewContext.Provider value={contextValue}>{children}</ViewContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ViewContext;
|
|
32
src/views/About.jsx
Normal file
32
src/views/About.jsx
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
import Link from "@mui/material/Link";
|
||||||
|
import Container from "@mui/material/Container";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
|
||||||
|
const memeUrl = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
|
||||||
|
const repoUrl = "https://gitlab.com/dunemask/Qualiteer";
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
return (
|
||||||
|
<div className="about">
|
||||||
|
<Container maxWidth="sm">
|
||||||
|
<Typography variant="h6" gutterBottom component="div">
|
||||||
|
<Box fontWeight='bold' display='inline'>Why?</Box>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1">
|
||||||
|
Qualiteer was designed to solve the issue of "on call". A state of being in which QA tests will fail, stiring everyone into a frenzy of what is broken in production! 🤯
|
||||||
|
Qualiteer gives users power to resolve and reattempt failing tests, run a particular suite of tests, and mute pesky alerts reminding you the navbar's color changed... 🤦♂️
|
||||||
|
</Typography>
|
||||||
|
<br/>
|
||||||
|
<Typography variant="subtitle1" style={{ wordWrap: "break-word", whiteSpace:"normal" }}>
|
||||||
|
<Box fontWeight='bold' display='inline'>{"Repository: "} </Box>
|
||||||
|
<Link href={repoUrl} >{repoUrl}</Link>
|
||||||
|
</Typography>
|
||||||
|
<br/>
|
||||||
|
<div style={{justifyContent:"center", width:"100%", display:"flex"}}>
|
||||||
|
<Link href={memeUrl} variant="h6" underline="none">Qualiteer</Link>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
61
src/views/Alerting.jsx
Normal file
61
src/views/Alerting.jsx
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { useState, useContext } from "react";
|
||||||
|
import StoreContext from "../ctx/StoreContext.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';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogContentText from '@mui/material/DialogContentText';
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
|
||||||
|
export default function Alerting() {
|
||||||
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const [alertDialogOpen, setAlertDialogOpen] = useState(false);
|
||||||
|
const quickAlertClick = () => setAlertDialogOpen(!alertDialogOpen);
|
||||||
|
|
||||||
|
function silenceAlert(){
|
||||||
|
|
||||||
|
}
|
||||||
|
const handleClose = (confirmed) => () => {
|
||||||
|
quickAlertClick();
|
||||||
|
if(!confirmed) return;
|
||||||
|
silenceAlert();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="alerting">
|
||||||
|
<Dialog
|
||||||
|
open={alertDialogOpen}
|
||||||
|
onClose={handleClose()}
|
||||||
|
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
||||||
|
maxWidth="xs"
|
||||||
|
>
|
||||||
|
<DialogTitle>
|
||||||
|
Silence Alert
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleClose()}>Cancel</Button>
|
||||||
|
<Button onClick={handleClose(true)} autoFocus>
|
||||||
|
Silence
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<SpeedDial
|
||||||
|
ariaLabel="Silence Alert"
|
||||||
|
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||||
|
icon={<SpeedDialIcon />}
|
||||||
|
onClick={quickAlertClick}
|
||||||
|
open={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
29
src/views/Catalog.jsx
Normal file
29
src/views/Catalog.jsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { useContext } from "react";
|
||||||
|
import StoreContext from "../ctx/StoreContext.jsx";
|
||||||
|
import JobContext from "../ctx/JobContext.jsx";
|
||||||
|
|
||||||
|
import TextField from "@mui/material/TextField";
|
||||||
|
|
||||||
|
import CatalogSearch from "./components/CatalogSearch.jsx";
|
||||||
|
|
||||||
|
export default function Catalog() {
|
||||||
|
const {
|
||||||
|
state: jobState,
|
||||||
|
dispatch: jobDispatch,
|
||||||
|
jobUpdate,
|
||||||
|
jobCreate,
|
||||||
|
} = useContext(JobContext);
|
||||||
|
|
||||||
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="catalog">
|
||||||
|
<CatalogSearch />
|
||||||
|
<TextField
|
||||||
|
label="Search Catalog"
|
||||||
|
type="search"
|
||||||
|
variant="filled"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
70
src/views/Failing.jsx
Normal file
70
src/views/Failing.jsx
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
import { useState, useContext } from "react";
|
||||||
|
import StoreContext from "../ctx/StoreContext.jsx";
|
||||||
|
import JobContext from "../ctx/JobContext.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';
|
||||||
|
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';
|
||||||
|
|
||||||
|
export default function Failing() {
|
||||||
|
const {
|
||||||
|
state: jobState,
|
||||||
|
retryAll
|
||||||
|
} = useContext(JobContext);
|
||||||
|
|
||||||
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const [retryAllOpen, setRetryAllOpen] = useState(false);
|
||||||
|
const retryAllClick = () => setRetryAllOpen(!retryAllOpen);
|
||||||
|
const handleClose = (confirmed) => ()=> {
|
||||||
|
retryAllClick();
|
||||||
|
if(!confirmed) return;
|
||||||
|
retryAll(store.failing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="failing">
|
||||||
|
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={retryAllOpen}
|
||||||
|
onClose={handleClose()}
|
||||||
|
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
||||||
|
maxWidth="xs"
|
||||||
|
>
|
||||||
|
<DialogTitle>
|
||||||
|
Retry all failing tests?
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
This will create x jobs and run y tests
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={handleClose()}>Cancel</Button>
|
||||||
|
<Button onClick={handleClose(true)} autoFocus>
|
||||||
|
Yes
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<SpeedDial
|
||||||
|
ariaLabel="Retry All"
|
||||||
|
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||||
|
icon={<ReplayIcon />}
|
||||||
|
onClick={retryAllClick}
|
||||||
|
open={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
55
src/views/Jobs.jsx
Normal file
55
src/views/Jobs.jsx
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { useState, useContext } from "react";
|
||||||
|
import StoreContext from "../ctx/StoreContext.jsx";
|
||||||
|
import JobContext from "../ctx/JobContext.jsx";
|
||||||
|
|
||||||
|
|
||||||
|
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
||||||
|
import SpeedDial from '@mui/material/SpeedDial';
|
||||||
|
import SpeedDialAction from '@mui/material/SpeedDialAction';
|
||||||
|
import SpeedDialIcon from '@mui/material/SpeedDialIcon';
|
||||||
|
|
||||||
|
import PageviewIcon from '@mui/icons-material/Pageview';
|
||||||
|
import ViewColumnIcon from '@mui/icons-material/ViewColumn';
|
||||||
|
import ViewCarouselIcon from '@mui/icons-material/ViewCarousel';
|
||||||
|
|
||||||
|
export default function Jobs() {
|
||||||
|
const {
|
||||||
|
state: jobState,
|
||||||
|
dispatch: jobDispatch,
|
||||||
|
jobUpdate,
|
||||||
|
jobCreate,
|
||||||
|
} = useContext(JobContext);
|
||||||
|
|
||||||
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const [quickOpen, setQuickOpen] = useState(false);
|
||||||
|
|
||||||
|
const quickOpenClick = () => setQuickOpen(!quickOpen);
|
||||||
|
const quickOpenClose = () => setQuickOpen(false);
|
||||||
|
|
||||||
|
const actions = [
|
||||||
|
{name: "Suite", icon: <ViewCarouselIcon/>}, {name: "Compound", icon: <ViewColumnIcon/>}, {name: "Manual", icon: <PageviewIcon/>}
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="jobs">
|
||||||
|
<ClickAwayListener onClickAway={quickOpenClose}>
|
||||||
|
<SpeedDial
|
||||||
|
ariaLabel="New Job"
|
||||||
|
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||||
|
icon={<SpeedDialIcon />}
|
||||||
|
onClick={quickOpenClick}
|
||||||
|
open={quickOpen}
|
||||||
|
>
|
||||||
|
{actions.map((action) => (
|
||||||
|
<SpeedDialAction
|
||||||
|
key={action.name}
|
||||||
|
icon={action.icon}
|
||||||
|
tooltipTitle={action.name}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SpeedDial>
|
||||||
|
</ClickAwayListener>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
124
src/views/Settings.jsx
Normal file
124
src/views/Settings.jsx
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
import { useContext, useState, useEffect } from "react";
|
||||||
|
import StoreContext from "../ctx/StoreContext.jsx";
|
||||||
|
|
||||||
|
import MultiOptionDialog from "./components/MultiOptionDialog.jsx";
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import List from '@mui/material/List';
|
||||||
|
import ListItem from '@mui/material/ListItem';
|
||||||
|
import ListItemText from '@mui/material/ListItemText';
|
||||||
|
import Switch from "@mui/material/Switch";
|
||||||
|
import SummarizeIcon from '@mui/icons-material/Summarize';
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
|
|
||||||
|
export default function Settings(props) {
|
||||||
|
|
||||||
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
const { regions } = store;
|
||||||
|
const { pages } = props;
|
||||||
|
|
||||||
|
const defaultDialog = {title: "", options: [], current: null, onSelect: null, open: false};
|
||||||
|
const [dialog, setDialog] = React.useState(defaultDialog);
|
||||||
|
|
||||||
|
const optionSettings = {region: {
|
||||||
|
title: "Region",
|
||||||
|
options: ["us", "au"],
|
||||||
|
current: store.defaultRegion,
|
||||||
|
onSelect: (r) => updateStore({defaultRegion: r})
|
||||||
|
},
|
||||||
|
defaultPage: {
|
||||||
|
title: "Default Page",
|
||||||
|
options: ["failing", "alerting"],
|
||||||
|
current: store.defaultPage,
|
||||||
|
onSelect: (p) => updateStore({defaultPage: p})
|
||||||
|
}}
|
||||||
|
|
||||||
|
const handleOptionsMenu = (s) => {
|
||||||
|
setDialog({...s, open:true});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = (newValue, onSelect) => {
|
||||||
|
setDialog({...dialog, open:false})
|
||||||
|
if (!newValue) return;
|
||||||
|
onSelect(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggle = (booleanSetting) => ()=> {
|
||||||
|
const storeUpdate = {};
|
||||||
|
storeUpdate[booleanSetting] = !store[booleanSetting];
|
||||||
|
updateStore(storeUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MultiOptionSubtext(props){
|
||||||
|
return( <React.Fragment>
|
||||||
|
<Typography
|
||||||
|
sx={{ display: 'inline' }}
|
||||||
|
component="span"
|
||||||
|
variant="body2"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
{props.value}
|
||||||
|
</Typography>
|
||||||
|
</React.Fragment>)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
|
||||||
|
<List component="div" role="group">
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
button
|
||||||
|
divider
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-label="default page"
|
||||||
|
onClick={() => handleOptionsMenu(optionSettings.defaultPage)}
|
||||||
|
>
|
||||||
|
<ListItemText primary="Default Page" secondary={
|
||||||
|
<MultiOptionSubtext value={optionSettings.defaultPage.current} />
|
||||||
|
}/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem
|
||||||
|
button
|
||||||
|
divider
|
||||||
|
aria-haspopup="true"
|
||||||
|
aria-label="region"
|
||||||
|
onClick={() => handleOptionsMenu(optionSettings.region)}
|
||||||
|
>
|
||||||
|
<ListItemText primary="Region" secondary={<MultiOptionSubtext value={optionSettings.region.current} />} />
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem button divider>
|
||||||
|
<ListItemText primary="Simplified Controls" />
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
onChange={handleToggle("simplifiedControls")}
|
||||||
|
checked={store.simplifiedControls}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem button divider>
|
||||||
|
<ListItemText primary="Focus New Jobs" />
|
||||||
|
<Switch
|
||||||
|
edge="end"
|
||||||
|
onChange={handleToggle("focusJob")}
|
||||||
|
checked={store.focusJob}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<MultiOptionDialog
|
||||||
|
id="multi-options-menu"
|
||||||
|
keepMounted
|
||||||
|
open={dialog.open}
|
||||||
|
onClose={handleClose}
|
||||||
|
dialog={dialog}
|
||||||
|
/>
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
31
src/views/components/CatalogSearch.jsx
Normal file
31
src/views/components/CatalogSearch.jsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import InputBase from '@mui/material/InputBase';
|
||||||
|
import Divider from '@mui/material/Divider';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import MenuIcon from '@mui/icons-material/Menu';
|
||||||
|
import SearchIcon from '@mui/icons-material/Search';
|
||||||
|
import DirectionsIcon from '@mui/icons-material/Directions';
|
||||||
|
import ClearOutlinedIcon from "@mui/icons-material/ClearOutlined";
|
||||||
|
|
||||||
|
export default function SearchBar(props) {
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
component="form"
|
||||||
|
sx={{ display: 'flex', alignItems: 'center'}}
|
||||||
|
>
|
||||||
|
<InputBase
|
||||||
|
sx={{flex: 1 }}
|
||||||
|
placeholder="Search Catalog"
|
||||||
|
inputProps={{ 'aria-label': `search catalog` }}
|
||||||
|
/>
|
||||||
|
<IconButton type="submit" sx={{ p: '18px' }} aria-label="search">
|
||||||
|
<SearchIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" />
|
||||||
|
<IconButton sx={{ p: '8px' }} aria-label="clear">
|
||||||
|
<ClearOutlinedIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
}
|
73
src/views/components/MultiOptionDialog.jsx
Normal file
73
src/views/components/MultiOptionDialog.jsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import {useState, useRef, useEffect} from "react";
|
||||||
|
|
||||||
|
import Button from "@mui/material/Button"
|
||||||
|
import DialogTitle from '@mui/material/DialogTitle';
|
||||||
|
import DialogContent from '@mui/material/DialogContent';
|
||||||
|
import DialogActions from '@mui/material/DialogActions';
|
||||||
|
import Dialog from '@mui/material/Dialog';
|
||||||
|
import RadioGroup from '@mui/material/RadioGroup';
|
||||||
|
import Radio from '@mui/material/Radio';
|
||||||
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||||
|
|
||||||
|
|
||||||
|
export default function MultiOptionDialog(props) {
|
||||||
|
|
||||||
|
const { dialog: dialogProp, onClose, open, ...other } = props;
|
||||||
|
const [value, setValue] = useState(dialogProp.current);
|
||||||
|
const [dialog, setDialog] = useState(dialogProp);
|
||||||
|
|
||||||
|
const radioGroupRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDialog(dialogProp);
|
||||||
|
setValue(dialogProp.current);
|
||||||
|
}, [dialogProp, open]);
|
||||||
|
|
||||||
|
const handleEntering = () => {
|
||||||
|
if (radioGroupRef.current != null) radioGroupRef.current.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => onClose();
|
||||||
|
|
||||||
|
const handleOk = () => onClose(value, dialog.onSelect);
|
||||||
|
|
||||||
|
|
||||||
|
const handleChange = (e) =>{ setValue(e.target.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
||||||
|
maxWidth="xs"
|
||||||
|
TransitionProps={{ onEntering: handleEntering }}
|
||||||
|
open={open}
|
||||||
|
{...other}
|
||||||
|
>
|
||||||
|
<DialogTitle>{dialog.title}</DialogTitle>
|
||||||
|
<DialogContent dividers>
|
||||||
|
<RadioGroup
|
||||||
|
ref={radioGroupRef}
|
||||||
|
aria-label={dialogProp.title}
|
||||||
|
name={dialog.title}
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
{dialog.options.map((option) => (
|
||||||
|
<FormControlLabel
|
||||||
|
value={option}
|
||||||
|
key={option}
|
||||||
|
control={<Radio />}
|
||||||
|
label={option}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</RadioGroup>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button autoFocus onClick={handleCancel}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleOk}>Ok</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
22
tests/api.js
Normal file
22
tests/api.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import "dotenv/config"; // Load Envars
|
||||||
|
import Qualiteer from "qualiteer";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
const qltr = new Qualiteer();
|
||||||
|
await qltr.start();
|
||||||
|
|
||||||
|
const url = "https://Qualiteer.elijahparker3.repl.co";
|
||||||
|
|
||||||
|
const testsUrl = "/api/catalog/tests";
|
||||||
|
const resultsUrl = "/api/results/failing";
|
||||||
|
|
||||||
|
const get = (...args) => axios.get(`${url}/${args[0]}`, args[1]);
|
||||||
|
|
||||||
|
var res = await get(resultsUrl);
|
||||||
|
console.log(res.data);
|
||||||
|
|
||||||
|
res = await get(resultsUrl, { headers: { count: true } });
|
||||||
|
|
||||||
|
console.log(res.data);
|
|
@ -11,7 +11,11 @@ const url = "https://Qualiteer.elijahparker3.repl.co";
|
||||||
|
|
||||||
// Create an initiator and make a job request
|
// Create an initiator and make a job request
|
||||||
const primary = new Initiator(url);
|
const primary = new Initiator(url);
|
||||||
const job = { command: ["node", "dev/other.js"], name: "testing", image: "node" };
|
const job = {
|
||||||
|
command: ["node", "dev/other.js"],
|
||||||
|
name: "testing",
|
||||||
|
image: "node",
|
||||||
|
};
|
||||||
await primary.newJob(job, null, () => console.log("Primary Job Concluded"));
|
await primary.newJob(job, null, () => console.log("Primary Job Concluded"));
|
||||||
|
|
||||||
/*const { clients } = qltr.jobs;
|
/*const { clients } = qltr.jobs;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue