Microsave
This commit is contained in:
parent
02c483950c
commit
d94796173e
17 changed files with 735 additions and 228 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
<<<<<<< HEAD
|
||||||
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_seq') PRIMARY KEY,
|
||||||
|
@ -16,15 +17,29 @@ weblog_url 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;
|
||||||
|
|
||||||
|
=======
|
||||||
|
|
||||||
|
> > > > > > > b023d8910c89d80573499890a958c0df649849e1
|
||||||
|
|
||||||
# Tables
|
# Tables
|
||||||
|
|
||||||
PG Database Tables Mapped Out
|
PG Database Tables Mapped Out
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
## `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 |
|
||||||
|
=======
|
||||||
|
Table `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 |
|
||||||
|
|-----|-----------|------------|-------------|---------------------|---------------|----------------|------------|--------------|---------|------------------------|---------------------|--------------------|
|
||||||
|
| int | string | string | string | string | string | timestamp | boolean | string | boolean | string | string | string |
|
||||||
|
| 1 | My Test | My Class | My Method | /path/to/test_class | API/UI/MOBILE | Date.now() | false | Test Suite A | true | I am a test that fails | https://example.com | http://example.com |
|
||||||
|
|
||||||
|
> > > > > > > b023d8910c89d80573499890a958c0df649849e1
|
||||||
|
|
||||||
- id Automatically Generated
|
- id Automatically Generated
|
||||||
- test_name\* Name of test
|
- test_name\* Name of test
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"start:dev": "nodemon dist/app.js",
|
"start:dev": "nodemon dist/app.js",
|
||||||
"start:dev:replit": "npm run start:dev & npm run start:react:replit",
|
"start:dev:replit": "npm run start:dev & npm run start:react:replit",
|
||||||
"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 node --max-old-space-size=512 node_modules/.bin/react-scripts start",
|
||||||
"test": "node tests/index.js",
|
"test": "node tests/index.js",
|
||||||
"test:api": "node tests/api.js",
|
"test:api": "node tests/api.js",
|
||||||
"test:dev": "nodemon tests/index.js"
|
"test:dev": "nodemon tests/index.js"
|
||||||
|
|
|
@ -127,7 +127,11 @@ export default function Views() {
|
||||||
>
|
>
|
||||||
{navHeader()}
|
{navHeader()}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Avatar alt="Remy Sharp" src="/assets/QA.png" onClick={reloadPage}/>
|
<Avatar
|
||||||
|
alt="Remy Sharp"
|
||||||
|
src="/assets/QA.png"
|
||||||
|
onClick={reloadPage}
|
||||||
|
/>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</Box>
|
</Box>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
import React, { useReducer, createContext, useMemo } from "react";
|
import React, { useReducer, createContext, useMemo } from "react";
|
||||||
const JobContext = createContext();
|
const JobContext = createContext();
|
||||||
|
|
||||||
|
export const jobStatus = {
|
||||||
|
OK: "o",
|
||||||
|
QUEUED: "q",
|
||||||
|
PENDING: "p",
|
||||||
|
CANCELED: "c",
|
||||||
|
ACTIVE: "a",
|
||||||
|
ERROR: "e",
|
||||||
|
};
|
||||||
|
|
||||||
const ACTIONS = {
|
const ACTIONS = {
|
||||||
CREATE: "c",
|
CREATE: "c",
|
||||||
UPDATE: "u",
|
UPDATE: "u",
|
||||||
|
@ -10,6 +19,22 @@ const ACTIONS = {
|
||||||
const initialState = {
|
const initialState = {
|
||||||
jobs: [],
|
jobs: [],
|
||||||
};
|
};
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
name: "Job1",
|
||||||
|
test: "someTestName",
|
||||||
|
status: JOB_STATUS.SUCCESS,
|
||||||
|
exitcode: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
OR
|
||||||
|
{
|
||||||
|
compound: true,
|
||||||
|
name: "Compound Job",
|
||||||
|
pipeline: [{}]
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
const reducer = (state, action) => {
|
const reducer = (state, action) => {
|
||||||
// Current Jobs
|
// Current Jobs
|
||||||
|
@ -36,32 +61,35 @@ 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 jobUpdate = (job, jobId) =>
|
||||||
|
dispatch({ type: ACTIONS.UPDATE, jobId, job });
|
||||||
const jobCreate = (job) =>
|
const jobCreate = (job) =>
|
||||||
dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } });
|
dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } });
|
||||||
const jobDelete = (jobId) => dispatch({ type: ACTIONS.DELETE, jobId });
|
const jobDelete = (jobId) => dispatch({ type: ACTIONS.DELETE, jobId });
|
||||||
|
|
||||||
function retryAll(failing){
|
function retryAll(failing) {
|
||||||
// Query Full Locator
|
// Query Full Locator
|
||||||
console.log("Would retry all failing tests!");
|
console.log("Would retry all failing tests!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function activeJobStates() {
|
||||||
|
const jobs = { state };
|
||||||
|
console.log("Would return all active job states");
|
||||||
|
}
|
||||||
|
|
||||||
|
function jobBuilder() {}
|
||||||
|
|
||||||
function jobBuilder(){
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
state,
|
state,
|
||||||
dispatch,
|
dispatch,
|
||||||
jobUpdate,
|
jobUpdate,
|
||||||
jobCreate,
|
jobCreate,
|
||||||
jobDelete,
|
jobDelete,
|
||||||
retryAll
|
retryAll,
|
||||||
|
activeJobStates,
|
||||||
};
|
};
|
||||||
const contextValue = useMemo(() => context, [state, dispatch]);
|
const contextValue = useMemo(() => context, [state, dispatch]);
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,50 @@
|
||||||
import React, { useReducer, createContext, useMemo } from "react";
|
import React, { useReducer, createContext, useMemo } from "react";
|
||||||
|
import { jobStatus } from "./JobContext.jsx";
|
||||||
|
|
||||||
const StoreContext = createContext();
|
const StoreContext = createContext();
|
||||||
|
|
||||||
const ACTIONS = {
|
const ACTIONS = {
|
||||||
UPDATE: "u",
|
UPDATE: "u",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const failingMock = new Array(10).fill(0).map((v, i) => ({
|
||||||
|
class: `SomeTestClass${i % 2 ? i - 1 : i / 2}`,
|
||||||
|
name: `TestThatDoesOneThing${i + 1}`,
|
||||||
|
timestamp: `2022-05-10T16:${2 + i}:33.810Z`,
|
||||||
|
silencedUntil: i % 4 ? null : `2022-05-10T16:${2 + i}:33.810Z`,
|
||||||
|
frequency: "1hour",
|
||||||
|
type: i % 3 ? "api" : "ui",
|
||||||
|
dailyFails: i + 1,
|
||||||
|
screenshot: "https://example.com",
|
||||||
|
recentResults: [1, 0, 0, 1, 0],
|
||||||
|
isCompound: i % 5 ? false : true,
|
||||||
|
failedMessage: `Some Test FailureMessage ${i}`,
|
||||||
|
jobStatus: (() => {
|
||||||
|
switch (i) {
|
||||||
|
case 1:
|
||||||
|
return jobStatus.OK;
|
||||||
|
case 3:
|
||||||
|
return jobStatus.ERROR;
|
||||||
|
case 4:
|
||||||
|
return jobStatus.PENDING;
|
||||||
|
case 5:
|
||||||
|
return jobStatus.ACTIVE;
|
||||||
|
case 6:
|
||||||
|
return jobStatus.CANCELED;
|
||||||
|
case 8:
|
||||||
|
return jobStatus.QUEUED;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
}));
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
intervals: [],
|
intervals: [],
|
||||||
failing: [],
|
catalog: [],
|
||||||
|
failing: failngMock,
|
||||||
regions: [],
|
regions: [],
|
||||||
|
catalogSearch: "",
|
||||||
focusJob: false,
|
focusJob: false,
|
||||||
simplifiedControls: false,
|
simplifiedControls: false,
|
||||||
defaultRegion: "us", // Local Store
|
defaultRegion: "us", // Local Store
|
||||||
|
|
|
@ -11,22 +11,36 @@ export default function About() {
|
||||||
<div className="about">
|
<div className="about">
|
||||||
<Container maxWidth="sm">
|
<Container maxWidth="sm">
|
||||||
<Typography variant="h6" gutterBottom component="div">
|
<Typography variant="h6" gutterBottom component="div">
|
||||||
<Box fontWeight='bold' display='inline'>Why?</Box>
|
<Box fontWeight="bold" display="inline">
|
||||||
</Typography>
|
Why?
|
||||||
<Typography variant="body1">
|
</Box>
|
||||||
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! 🤯
|
</Typography>
|
||||||
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 variant="body1">
|
||||||
</Typography>
|
Qualiteer was designed to solve the issue of "on call". A state of
|
||||||
<br/>
|
being in which QA tests will fail, stiring everyone into a frenzy of
|
||||||
<Typography variant="subtitle1" style={{ wordWrap: "break-word", whiteSpace:"normal" }}>
|
what is broken in production! 🤯 Qualiteer gives users power to
|
||||||
<Box fontWeight='bold' display='inline'>{"Repository: "} </Box>
|
resolve and reattempt failing tests, run a particular suite of tests,
|
||||||
<Link href={repoUrl} >{repoUrl}</Link>
|
and mute pesky alerts reminding you the navbar's color changed... 🤦♂️
|
||||||
</Typography>
|
</Typography>
|
||||||
<br/>
|
<br />
|
||||||
<div style={{justifyContent:"center", width:"100%", display:"flex"}}>
|
<Typography
|
||||||
<Link href={memeUrl} variant="h6" underline="none">Qualiteer</Link>
|
variant="subtitle1"
|
||||||
</div>
|
style={{ wordWrap: "break-word", whiteSpace: "normal" }}
|
||||||
</Container>
|
>
|
||||||
</div>
|
<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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import { useState, useContext } from "react";
|
import { useState, useContext } from "react";
|
||||||
import StoreContext from "../ctx/StoreContext.jsx";
|
import StoreContext from "../ctx/StoreContext.jsx";
|
||||||
|
|
||||||
import SpeedDial from '@mui/material/SpeedDial';
|
import SpeedDial from "@mui/material/SpeedDial";
|
||||||
import SpeedDialAction from '@mui/material/SpeedDialAction';
|
import SpeedDialAction from "@mui/material/SpeedDialAction";
|
||||||
import SpeedDialIcon from '@mui/material/SpeedDialIcon';
|
import SpeedDialIcon from "@mui/material/SpeedDialIcon";
|
||||||
|
|
||||||
import Button from '@mui/material/Button';
|
import Button from "@mui/material/Button";
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from "@mui/material/Dialog";
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
import DialogContentText from '@mui/material/DialogContentText';
|
import DialogContentText from "@mui/material/DialogContentText";
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
|
|
||||||
export default function Alerting() {
|
export default function Alerting() {
|
||||||
const { state: store, updateStore } = useContext(StoreContext);
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
@ -18,29 +18,23 @@ export default function Alerting() {
|
||||||
const [alertDialogOpen, setAlertDialogOpen] = useState(false);
|
const [alertDialogOpen, setAlertDialogOpen] = useState(false);
|
||||||
const quickAlertClick = () => setAlertDialogOpen(!alertDialogOpen);
|
const quickAlertClick = () => setAlertDialogOpen(!alertDialogOpen);
|
||||||
|
|
||||||
function silenceAlert(){
|
function silenceAlert() {}
|
||||||
|
|
||||||
}
|
|
||||||
const handleClose = (confirmed) => () => {
|
const handleClose = (confirmed) => () => {
|
||||||
quickAlertClick();
|
quickAlertClick();
|
||||||
if(!confirmed) return;
|
if (!confirmed) return;
|
||||||
silenceAlert();
|
silenceAlert();
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="alerting">
|
<div className="alerting">
|
||||||
<Dialog
|
<Dialog
|
||||||
open={alertDialogOpen}
|
open={alertDialogOpen}
|
||||||
onClose={handleClose()}
|
onClose={handleClose()}
|
||||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
|
||||||
maxWidth="xs"
|
maxWidth="xs"
|
||||||
>
|
>
|
||||||
<DialogTitle>
|
<DialogTitle>Silence Alert</DialogTitle>
|
||||||
Silence Alert
|
<DialogContent></DialogContent>
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
|
||||||
|
|
||||||
</DialogContent>
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose()}>Cancel</Button>
|
<Button onClick={handleClose()}>Cancel</Button>
|
||||||
<Button onClick={handleClose(true)} autoFocus>
|
<Button onClick={handleClose(true)} autoFocus>
|
||||||
|
@ -50,8 +44,8 @@ export default function Alerting() {
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<SpeedDial
|
<SpeedDial
|
||||||
ariaLabel="Silence Alert"
|
ariaLabel="Silence Alert"
|
||||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
sx={{ position: "fixed", bottom: 16, right: 16 }}
|
||||||
icon={<SpeedDialIcon />}
|
icon={<SpeedDialIcon />}
|
||||||
onClick={quickAlertClick}
|
onClick={quickAlertClick}
|
||||||
open={false}
|
open={false}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useContext } from "react";
|
import { useEffect, useContext } from "react";
|
||||||
import StoreContext from "../ctx/StoreContext.jsx";
|
import StoreContext from "../ctx/StoreContext.jsx";
|
||||||
import JobContext from "../ctx/JobContext.jsx";
|
import JobContext from "../ctx/JobContext.jsx";
|
||||||
|
|
||||||
|
@ -16,14 +16,25 @@ export default function Catalog() {
|
||||||
|
|
||||||
const { state: store, updateStore } = useContext(StoreContext);
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const handleSearchChange = (e) =>
|
||||||
|
updateStore({ catalogSearch: e.target.value });
|
||||||
|
|
||||||
|
const handleSearchClear = () => updateStore({ catalogSearch: "" });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return function unmount() {
|
||||||
|
handleSearchClear();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="catalog">
|
<div className="catalog">
|
||||||
<CatalogSearch />
|
<CatalogSearch
|
||||||
<TextField
|
onChange={handleSearchChange}
|
||||||
label="Search Catalog"
|
onClear={handleSearchClear}
|
||||||
type="search"
|
clearOnUnmount
|
||||||
variant="filled"
|
/>
|
||||||
/>
|
<h6>{store.catalogSearch}</h6>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,48 +2,55 @@ import { useState, useContext } from "react";
|
||||||
import StoreContext from "../ctx/StoreContext.jsx";
|
import StoreContext from "../ctx/StoreContext.jsx";
|
||||||
import JobContext from "../ctx/JobContext.jsx";
|
import JobContext from "../ctx/JobContext.jsx";
|
||||||
|
|
||||||
import SpeedDial from '@mui/material/SpeedDial';
|
import SpeedDial from "@mui/material/SpeedDial";
|
||||||
import SpeedDialAction from '@mui/material/SpeedDialAction';
|
import SpeedDialAction from "@mui/material/SpeedDialAction";
|
||||||
import SpeedDialIcon from '@mui/material/SpeedDialIcon';
|
import SpeedDialIcon from "@mui/material/SpeedDialIcon";
|
||||||
|
|
||||||
import Button from '@mui/material/Button';
|
import Button from "@mui/material/Button";
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from "@mui/material/Dialog";
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
import DialogContentText from '@mui/material/DialogContentText';
|
import DialogContentText from "@mui/material/DialogContentText";
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
|
|
||||||
import ReplayIcon from '@mui/icons-material/Replay';
|
import ReplayIcon from "@mui/icons-material/Replay";
|
||||||
|
|
||||||
|
import FailingBox from "./components/FailingBox.jsx";
|
||||||
|
|
||||||
export default function Failing() {
|
export default function Failing() {
|
||||||
const {
|
const { state: jobState, retryAll, activeJobStates } = useContext(JobContext);
|
||||||
state: jobState,
|
|
||||||
retryAll
|
|
||||||
} = useContext(JobContext);
|
|
||||||
|
|
||||||
const { state: store, updateStore } = useContext(StoreContext);
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
const { failing } = store;
|
||||||
|
|
||||||
|
/* TODO
|
||||||
|
for(var j of activeJobStates()){
|
||||||
|
const failingTest = failing.find((f)=>f.name===j.testName);
|
||||||
|
if(!failingTest) continue;
|
||||||
|
failingTest.jobStatus= j.status;
|
||||||
|
}*/
|
||||||
|
|
||||||
const [retryAllOpen, setRetryAllOpen] = useState(false);
|
const [retryAllOpen, setRetryAllOpen] = useState(false);
|
||||||
const retryAllClick = () => setRetryAllOpen(!retryAllOpen);
|
const retryAllClick = () => setRetryAllOpen(!retryAllOpen);
|
||||||
const handleClose = (confirmed) => ()=> {
|
const handleClose = (confirmed) => () => {
|
||||||
retryAllClick();
|
retryAllClick();
|
||||||
if(!confirmed) return;
|
if (!confirmed) return;
|
||||||
retryAll(store.failing);
|
retryAll(store.failing);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="failing">
|
<div className="failing">
|
||||||
|
{failing.map((v, i) => (
|
||||||
|
<FailingBox key={i} failingTest={v} />
|
||||||
|
))}
|
||||||
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={retryAllOpen}
|
open={retryAllOpen}
|
||||||
onClose={handleClose()}
|
onClose={handleClose()}
|
||||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
|
||||||
maxWidth="xs"
|
maxWidth="xs"
|
||||||
>
|
>
|
||||||
<DialogTitle>
|
<DialogTitle>Retry all failing tests?</DialogTitle>
|
||||||
Retry all failing tests?
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
This will create x jobs and run y tests
|
This will create x jobs and run y tests
|
||||||
|
@ -56,15 +63,14 @@ export default function Failing() {
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<SpeedDial
|
<SpeedDial
|
||||||
ariaLabel="Retry All"
|
ariaLabel="Retry All"
|
||||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
sx={{ position: "fixed", bottom: 16, right: 16 }}
|
||||||
icon={<ReplayIcon />}
|
icon={<ReplayIcon />}
|
||||||
onClick={retryAllClick}
|
onClick={retryAllClick}
|
||||||
open={false}
|
open={false}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,14 @@ import { useState, useContext } from "react";
|
||||||
import StoreContext from "../ctx/StoreContext.jsx";
|
import StoreContext from "../ctx/StoreContext.jsx";
|
||||||
import JobContext from "../ctx/JobContext.jsx";
|
import JobContext from "../ctx/JobContext.jsx";
|
||||||
|
|
||||||
|
import ClickAwayListener from "@mui/material/ClickAwayListener";
|
||||||
|
import SpeedDial from "@mui/material/SpeedDial";
|
||||||
|
import SpeedDialAction from "@mui/material/SpeedDialAction";
|
||||||
|
import SpeedDialIcon from "@mui/material/SpeedDialIcon";
|
||||||
|
|
||||||
import ClickAwayListener from '@mui/material/ClickAwayListener';
|
import PageviewIcon from "@mui/icons-material/Pageview";
|
||||||
import SpeedDial from '@mui/material/SpeedDial';
|
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
||||||
import SpeedDialAction from '@mui/material/SpeedDialAction';
|
import ViewCarouselIcon from "@mui/icons-material/ViewCarousel";
|
||||||
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() {
|
export default function Jobs() {
|
||||||
const {
|
const {
|
||||||
|
@ -26,30 +25,32 @@ export default function Jobs() {
|
||||||
|
|
||||||
const quickOpenClick = () => setQuickOpen(!quickOpen);
|
const quickOpenClick = () => setQuickOpen(!quickOpen);
|
||||||
const quickOpenClose = () => setQuickOpen(false);
|
const quickOpenClose = () => setQuickOpen(false);
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{name: "Suite", icon: <ViewCarouselIcon/>}, {name: "Compound", icon: <ViewColumnIcon/>}, {name: "Manual", icon: <PageviewIcon/>}
|
{ name: "Suite", icon: <ViewCarouselIcon /> },
|
||||||
]
|
{ name: "Compound", icon: <ViewColumnIcon /> },
|
||||||
|
{ name: "Manual", icon: <PageviewIcon /> },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="jobs">
|
<div className="jobs">
|
||||||
<ClickAwayListener onClickAway={quickOpenClose}>
|
<ClickAwayListener onClickAway={quickOpenClose}>
|
||||||
<SpeedDial
|
<SpeedDial
|
||||||
ariaLabel="New Job"
|
ariaLabel="New Job"
|
||||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
sx={{ position: "fixed", bottom: 16, right: 16 }}
|
||||||
icon={<SpeedDialIcon />}
|
icon={<SpeedDialIcon />}
|
||||||
onClick={quickOpenClick}
|
onClick={quickOpenClick}
|
||||||
open={quickOpen}
|
open={quickOpen}
|
||||||
>
|
>
|
||||||
{actions.map((action) => (
|
{actions.map((action) => (
|
||||||
<SpeedDialAction
|
<SpeedDialAction
|
||||||
key={action.name}
|
key={action.name}
|
||||||
icon={action.icon}
|
icon={action.icon}
|
||||||
tooltipTitle={action.name}
|
tooltipTitle={action.name}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</SpeedDial>
|
</SpeedDial>
|
||||||
</ClickAwayListener>
|
</ClickAwayListener>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +1,80 @@
|
||||||
import { useContext, useState, useEffect } from "react";
|
import React, { useContext, useState, useEffect } from "react";
|
||||||
import StoreContext from "../ctx/StoreContext.jsx";
|
import StoreContext from "../ctx/StoreContext.jsx";
|
||||||
|
|
||||||
import MultiOptionDialog from "./components/MultiOptionDialog.jsx";
|
import MultiOptionDialog from "./components/MultiOptionDialog.jsx";
|
||||||
|
|
||||||
import * as React from 'react';
|
import Box from "@mui/material/Box";
|
||||||
import PropTypes from 'prop-types';
|
import Button from "@mui/material/Button";
|
||||||
import Box from '@mui/material/Box';
|
import List from "@mui/material/List";
|
||||||
import Button from '@mui/material/Button';
|
import ListItem from "@mui/material/ListItem";
|
||||||
import List from '@mui/material/List';
|
import ListItemText from "@mui/material/ListItemText";
|
||||||
import ListItem from '@mui/material/ListItem';
|
|
||||||
import ListItemText from '@mui/material/ListItemText';
|
|
||||||
import Switch from "@mui/material/Switch";
|
import Switch from "@mui/material/Switch";
|
||||||
import SummarizeIcon from '@mui/icons-material/Summarize';
|
import SummarizeIcon from "@mui/icons-material/Summarize";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
|
|
||||||
export default function Settings(props) {
|
export default function Settings(props) {
|
||||||
|
|
||||||
const { state: store, updateStore } = useContext(StoreContext);
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
const { regions } = store;
|
const { regions } = store;
|
||||||
const { pages } = props;
|
const { pages } = props;
|
||||||
|
|
||||||
const defaultDialog = {title: "", options: [], current: null, onSelect: null, open: false};
|
const defaultDialog = {
|
||||||
const [dialog, setDialog] = React.useState(defaultDialog);
|
title: "",
|
||||||
|
options: [],
|
||||||
const optionSettings = {region: {
|
current: null,
|
||||||
title: "Region",
|
onSelect: null,
|
||||||
options: ["us", "au"],
|
open: false,
|
||||||
current: store.defaultRegion,
|
};
|
||||||
onSelect: (r) => updateStore({defaultRegion: r})
|
const [dialog, setDialog] = useState(defaultDialog);
|
||||||
},
|
|
||||||
defaultPage: {
|
const optionSettings = {
|
||||||
title: "Default Page",
|
region: {
|
||||||
options: ["failing", "alerting"],
|
title: "Region",
|
||||||
current: store.defaultPage,
|
options: [],
|
||||||
onSelect: (p) => updateStore({defaultPage: p})
|
current: store.defaultRegion,
|
||||||
}}
|
onSelect: (r) => updateStore({ defaultRegion: r }),
|
||||||
|
},
|
||||||
|
defaultPage: {
|
||||||
|
title: "Default Page",
|
||||||
|
options: pages,
|
||||||
|
current: store.defaultPage,
|
||||||
|
onSelect: (p) => updateStore({ defaultPage: p }),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const handleOptionsMenu = (s) => {
|
const handleOptionsMenu = (s) => {
|
||||||
setDialog({...s, open:true});
|
setDialog({ ...s, open: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = (newValue, onSelect) => {
|
const handleClose = (newValue, onSelect) => {
|
||||||
setDialog({...dialog, open:false})
|
setDialog({ ...dialog, open: false });
|
||||||
if (!newValue) return;
|
if (!newValue) return;
|
||||||
onSelect(newValue);
|
onSelect(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleToggle = (booleanSetting) => ()=> {
|
const handleToggle = (booleanSetting) => () => {
|
||||||
const storeUpdate = {};
|
const storeUpdate = {};
|
||||||
storeUpdate[booleanSetting] = !store[booleanSetting];
|
storeUpdate[booleanSetting] = !store[booleanSetting];
|
||||||
updateStore(storeUpdate)
|
updateStore(storeUpdate);
|
||||||
}
|
};
|
||||||
|
|
||||||
function MultiOptionSubtext(props){
|
function MultiOptionSubtext(props) {
|
||||||
return( <React.Fragment>
|
return (
|
||||||
<Typography
|
<React.Fragment>
|
||||||
sx={{ display: 'inline' }}
|
<Typography
|
||||||
component="span"
|
sx={{ display: "inline" }}
|
||||||
variant="body2"
|
component="span"
|
||||||
color="primary"
|
variant="body2"
|
||||||
>
|
color="primary"
|
||||||
{props.value}
|
>
|
||||||
</Typography>
|
{props.value}
|
||||||
</React.Fragment>)
|
</Typography>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
|
<Box sx={{ width: "100%", bgcolor: "background.paper" }}>
|
||||||
<List component="div" role="group">
|
<List component="div" role="group">
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
button
|
button
|
||||||
divider
|
divider
|
||||||
|
@ -78,39 +82,48 @@ const defaultDialog = {title: "", options: [], current: null, onSelect: null, op
|
||||||
aria-label="default page"
|
aria-label="default page"
|
||||||
onClick={() => handleOptionsMenu(optionSettings.defaultPage)}
|
onClick={() => handleOptionsMenu(optionSettings.defaultPage)}
|
||||||
>
|
>
|
||||||
<ListItemText primary="Default Page" secondary={
|
<ListItemText
|
||||||
<MultiOptionSubtext value={optionSettings.defaultPage.current} />
|
primary="Default Page"
|
||||||
}/>
|
secondary={
|
||||||
|
<MultiOptionSubtext value={optionSettings.defaultPage.current} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem
|
<ListItem
|
||||||
button
|
button
|
||||||
divider
|
divider
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-label="region"
|
aria-label="region"
|
||||||
onClick={() => handleOptionsMenu(optionSettings.region)}
|
onClick={() => handleOptionsMenu(optionSettings.region)}
|
||||||
|
disabled={optionSettings.region.options.length === 0}
|
||||||
>
|
>
|
||||||
<ListItemText primary="Region" secondary={<MultiOptionSubtext value={optionSettings.region.current} />} />
|
<ListItemText
|
||||||
|
primary="Region"
|
||||||
|
secondary={
|
||||||
|
<MultiOptionSubtext value={optionSettings.region.current} />
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem button divider>
|
<ListItem button divider>
|
||||||
<ListItemText primary="Simplified Controls" />
|
<ListItemText primary="Simplified Controls" />
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
onChange={handleToggle("simplifiedControls")}
|
onChange={handleToggle("simplifiedControls")}
|
||||||
checked={store.simplifiedControls}
|
checked={store.simplifiedControls}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<ListItem button divider>
|
<ListItem button divider>
|
||||||
<ListItemText primary="Focus New Jobs" />
|
<ListItemText primary="Focus New Jobs" />
|
||||||
<Switch
|
<Switch
|
||||||
edge="end"
|
edge="end"
|
||||||
onChange={handleToggle("focusJob")}
|
onChange={handleToggle("focusJob")}
|
||||||
checked={store.focusJob}
|
checked={store.focusJob}
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|
||||||
<MultiOptionDialog
|
<MultiOptionDialog
|
||||||
id="multi-options-menu"
|
id="multi-options-menu"
|
||||||
keepMounted
|
keepMounted
|
||||||
|
|
86
src/views/components/CatalogBox.jsx
Normal file
86
src/views/components/CatalogBox.jsx
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
import React, { useState, useContext } from "react";
|
||||||
|
import StoreContext from "../../ctx/StoreContext.jsx";
|
||||||
|
import JobContext from "../../ctx/JobContext.jsx";
|
||||||
|
|
||||||
|
import Accordion from "@mui/material/Accordion";
|
||||||
|
import AccordionDetails from "@mui/material/AccordionDetails";
|
||||||
|
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||||
|
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
|
||||||
|
export default function CatalogBox(props) {
|
||||||
|
const { catalogTest } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
name: testName,
|
||||||
|
class: testClass,
|
||||||
|
repo: testRepo,
|
||||||
|
isCompound,
|
||||||
|
type: testType
|
||||||
|
} = catalogTest;
|
||||||
|
|
||||||
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const { state: jobState, jobBuilder} = useContext(JobContext);
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const toggleOpen = () => setOpen(!open);
|
||||||
|
|
||||||
|
function Actions() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<IconButton aria-label="play" component="span" color="primary">
|
||||||
|
<NotificationsIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton color="error" aria-label="delete" component="span">
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion expanded={open} disableGutters={true} onChange={toggleOpen}
|
||||||
|
square>
|
||||||
|
<AccordionSummary
|
||||||
|
style={{
|
||||||
|
backgroundColor: "rgba(0, 0, 0, .03)",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography component={"span"} style={{ wordBreak: "break-word" }}>
|
||||||
|
{`${testClass}#`}
|
||||||
|
<Box fontWeight="bold" display="inline">
|
||||||
|
{testName}
|
||||||
|
</Box>
|
||||||
|
<br />
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
sx={{ ml: "auto", display: { md: "none", lg: "none", xl: "none" } }}
|
||||||
|
>
|
||||||
|
<Actions />
|
||||||
|
</Stack>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
sx={{
|
||||||
|
ml: "auto",
|
||||||
|
display: { xs: "none", sm: "none", md: "flex", lg: "flex" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Actions />
|
||||||
|
</Stack>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography>{JSON.stringify(catalogTest)}</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</AccordionSummary>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,31 +1,44 @@
|
||||||
import * as React from 'react';
|
import { useEffect, useRef } from "react";
|
||||||
import Paper from '@mui/material/Paper';
|
import Paper from "@mui/material/Paper";
|
||||||
import InputBase from '@mui/material/InputBase';
|
import InputBase from "@mui/material/InputBase";
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from "@mui/material/Divider";
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from "@mui/material/IconButton";
|
||||||
import MenuIcon from '@mui/icons-material/Menu';
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
import SearchIcon from '@mui/icons-material/Search';
|
import SearchIcon from "@mui/icons-material/Search";
|
||||||
import DirectionsIcon from '@mui/icons-material/Directions';
|
import DirectionsIcon from "@mui/icons-material/Directions";
|
||||||
import ClearOutlinedIcon from "@mui/icons-material/ClearOutlined";
|
import ClearOutlinedIcon from "@mui/icons-material/ClearOutlined";
|
||||||
|
|
||||||
|
export default function CatalogSearch(props) {
|
||||||
|
const { onChange, onClear } = props;
|
||||||
|
|
||||||
|
const searchRef = useRef(null);
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
searchRef.current.children[0].value = null;
|
||||||
|
searchRef.current.children[0].focus();
|
||||||
|
onClear();
|
||||||
|
};
|
||||||
|
|
||||||
export default function SearchBar(props) {
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper component="form" sx={{ display: "flex", alignItems: "center" }}>
|
||||||
component="form"
|
|
||||||
sx={{ display: 'flex', alignItems: 'center'}}
|
|
||||||
>
|
|
||||||
<InputBase
|
<InputBase
|
||||||
sx={{flex: 1 }}
|
ref={searchRef}
|
||||||
|
sx={{ ml: 1, flex: 1 }}
|
||||||
placeholder="Search Catalog"
|
placeholder="Search Catalog"
|
||||||
inputProps={{ 'aria-label': `search catalog` }}
|
inputProps={{ "aria-label": `search catalog` }}
|
||||||
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
<IconButton type="submit" sx={{ p: '18px' }} aria-label="search">
|
<IconButton type="submit" sx={{ p: "10px" }} aria-label="search">
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" />
|
<Divider sx={{ height: 26, m: 0.5 }} orientation="vertical" />
|
||||||
<IconButton sx={{ p: '8px' }} aria-label="clear">
|
<IconButton
|
||||||
|
sx={{ p: "10px", mr: 0.5 }}
|
||||||
|
aria-label="clear"
|
||||||
|
onClick={handleClear}
|
||||||
|
>
|
||||||
<ClearOutlinedIcon />
|
<ClearOutlinedIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
166
src/views/components/FailingBox.jsx
Normal file
166
src/views/components/FailingBox.jsx
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
import React, { useState, useContext } from "react";
|
||||||
|
import StoreContext from "../../ctx/StoreContext.jsx";
|
||||||
|
import JobContext, { jobStatus } from "../../ctx/JobContext.jsx";
|
||||||
|
|
||||||
|
import Accordion from "@mui/material/Accordion";
|
||||||
|
import AccordionDetails from "@mui/material/AccordionDetails";
|
||||||
|
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||||
|
import ReplayIcon from "@mui/icons-material/Replay";
|
||||||
|
import PhotoCameraIcon from "@mui/icons-material/PhotoCamera";
|
||||||
|
|
||||||
|
import CheckIcon from "@mui/icons-material/Check";
|
||||||
|
import ClearIcon from "@mui/icons-material/Clear";
|
||||||
|
import ViewColumnIcon from "@mui/icons-material/ViewColumn";
|
||||||
|
import PendingIcon from "@mui/icons-material/Pending";
|
||||||
|
import VisibilityIcon from "@mui/icons-material/Visibility";
|
||||||
|
import DoNotDisturbIcon from "@mui/icons-material/DoNotDisturb";
|
||||||
|
|
||||||
|
import Badge from "@mui/material/Badge";
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
import Box from "@mui/material/Box";
|
||||||
|
|
||||||
|
const stopPropagation = (e) => e.stopPropagation() && e.preventDefault();
|
||||||
|
|
||||||
|
export default function FailingBox(props) {
|
||||||
|
const { failingTest } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
class: testClass,
|
||||||
|
name: testName,
|
||||||
|
timestamp,
|
||||||
|
silencedUntil,
|
||||||
|
type,
|
||||||
|
dailyFails,
|
||||||
|
screenshot: screenshotUrl,
|
||||||
|
recentResults,
|
||||||
|
failedMessage,
|
||||||
|
isCompound,
|
||||||
|
jobStatus: testJobStatus,
|
||||||
|
} = failingTest;
|
||||||
|
|
||||||
|
const { state: jobState, retryTest, retryJobStatus } = useContext(JobContext);
|
||||||
|
|
||||||
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const toggleOpen = () => setOpen(!open);
|
||||||
|
|
||||||
|
function badgeColor() {
|
||||||
|
if (dailyFails === 1) return "primary";
|
||||||
|
else if (dailyFails === 2) return "secondary";
|
||||||
|
else if (dailyFails < 6) return "warning";
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
function jobIcon() {
|
||||||
|
switch (testJobStatus) {
|
||||||
|
case jobStatus.OK:
|
||||||
|
return <CheckIcon color="success" />;
|
||||||
|
case jobStatus.ERROR:
|
||||||
|
return <ClearIcon color="error" />;
|
||||||
|
case jobStatus.PENDING:
|
||||||
|
return <PendingIcon color="info" />;
|
||||||
|
case jobStatus.ACTIVE:
|
||||||
|
return <VisibilityIcon color="primary" />;
|
||||||
|
case jobStatus.CANCELED:
|
||||||
|
return <DoNotDisturbIcon color="warning" />;
|
||||||
|
case jobStatus.QUEUED:
|
||||||
|
return <ViewColumnIcon color="secondary" />;
|
||||||
|
default:
|
||||||
|
return <ReplayIcon />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Actions() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<IconButton aria-label="photo" component="span">
|
||||||
|
<PhotoCameraIcon />
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton aria-label="retry" component="span">
|
||||||
|
{jobIcon()}
|
||||||
|
</IconButton>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
aria-label="silence"
|
||||||
|
component="span"
|
||||||
|
color={silencedUntil ? "primary" : "default"}
|
||||||
|
>
|
||||||
|
<NotificationsIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton color="error" aria-label="delete" component="span">
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion
|
||||||
|
expanded={open}
|
||||||
|
onChange={toggleOpen}
|
||||||
|
disableGutters={true}
|
||||||
|
square
|
||||||
|
>
|
||||||
|
<AccordionSummary
|
||||||
|
style={{
|
||||||
|
backgroundColor: "rgba(0, 0, 0, .03)",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
sx={{ mr: 2 }}
|
||||||
|
color={badgeColor()}
|
||||||
|
badgeContent={dailyFails}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "top",
|
||||||
|
horizontal: "left",
|
||||||
|
}}
|
||||||
|
></Badge>
|
||||||
|
<Typography component={"span"} style={{ wordBreak: "break-word" }}>
|
||||||
|
{`${testClass}#`}
|
||||||
|
<Box fontWeight="bold" display="inline">
|
||||||
|
{testName}{" "}
|
||||||
|
</Box>
|
||||||
|
<br />
|
||||||
|
<span className="recent-results">
|
||||||
|
{recentResults.map(
|
||||||
|
(v, i) =>
|
||||||
|
(v && <CheckIcon key={i} color="success" />) || (
|
||||||
|
<ClearIcon key={i} color="error" />
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
{isCompound && <ViewColumnIcon />}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
onClick={stopPropagation}
|
||||||
|
sx={{ ml: "auto", display: { md: "none", lg: "none", xl: "none" } }}
|
||||||
|
>
|
||||||
|
<Actions />
|
||||||
|
</Stack>
|
||||||
|
<Stack
|
||||||
|
onClick={stopPropagation}
|
||||||
|
direction="row"
|
||||||
|
sx={{
|
||||||
|
ml: "auto",
|
||||||
|
display: { xs: "none", sm: "none", md: "flex", lg: "flex" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Actions />
|
||||||
|
</Stack>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails>
|
||||||
|
<Typography>{failedMessage}</Typography>
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,24 +1,22 @@
|
||||||
import {useState, useRef, useEffect} from "react";
|
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';
|
|
||||||
|
|
||||||
|
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) {
|
export default function MultiOptionDialog(props) {
|
||||||
|
|
||||||
const { dialog: dialogProp, onClose, open, ...other } = props;
|
const { dialog: dialogProp, onClose, open, ...other } = props;
|
||||||
const [value, setValue] = useState(dialogProp.current);
|
const [value, setValue] = useState(dialogProp.current);
|
||||||
const [dialog, setDialog] = useState(dialogProp);
|
const [dialog, setDialog] = useState(dialogProp);
|
||||||
|
|
||||||
const radioGroupRef = useRef(null);
|
const radioGroupRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDialog(dialogProp);
|
setDialog(dialogProp);
|
||||||
setValue(dialogProp.current);
|
setValue(dialogProp.current);
|
||||||
}, [dialogProp, open]);
|
}, [dialogProp, open]);
|
||||||
|
@ -30,14 +28,14 @@ export default function MultiOptionDialog(props) {
|
||||||
const handleCancel = () => onClose();
|
const handleCancel = () => onClose();
|
||||||
|
|
||||||
const handleOk = () => onClose(value, dialog.onSelect);
|
const handleOk = () => onClose(value, dialog.onSelect);
|
||||||
|
|
||||||
|
|
||||||
const handleChange = (e) =>{ setValue(e.target.value);
|
const handleChange = (e) => {
|
||||||
}
|
setValue(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
|
||||||
maxWidth="xs"
|
maxWidth="xs"
|
||||||
TransitionProps={{ onEntering: handleEntering }}
|
TransitionProps={{ onEntering: handleEntering }}
|
||||||
open={open}
|
open={open}
|
||||||
|
|
47
src/views/components/SilenceDialog.jsx
Normal file
47
src/views/components/SilenceDialog.jsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import { useState, useContext } from "react";
|
||||||
|
import StoreContext from "../../ctx/StoreContext.jsx";
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
export default function SilenceDialog(props) {
|
||||||
|
const { silence, open, onClose } = props;
|
||||||
|
|
||||||
|
const [silenceEntry, setSilenceEntry] = useState(silence);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSilenceEntry(silence);
|
||||||
|
}, [silence, open]);
|
||||||
|
|
||||||
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
|
||||||
|
const upsertSilence = () => {
|
||||||
|
console.log("Would upsert silence", silenceEntry);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancel = () => onClose();
|
||||||
|
|
||||||
|
const handleOk = () => onClose(silenceEntry);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
|
||||||
|
maxWidth="xs"
|
||||||
|
open={open}
|
||||||
|
>
|
||||||
|
<DialogTitle>Silence Alert</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<span>{JSON.stringify(silenceEntry)}</span>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button autoFocus onClick={handleCancel}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleOk}>Ok</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
75
src/views/components/SilencingBox.jsx
Normal file
75
src/views/components/SilencingBox.jsx
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
import React, { useState, useContext } from "react";
|
||||||
|
import StoreContext from "../../ctx/StoreContext.jsx";
|
||||||
|
|
||||||
|
import Accordion from "@mui/material/Accordion";
|
||||||
|
import AccordionSummary from "@mui/material/AccordionSummary";
|
||||||
|
import Typography from "@mui/material/Typography";
|
||||||
|
|
||||||
|
import IconButton from "@mui/material/IconButton";
|
||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||||
|
|
||||||
|
import Stack from "@mui/material/Stack";
|
||||||
|
|
||||||
|
export default function SilencingBox(props) {
|
||||||
|
const { silenceEntry } = props;
|
||||||
|
|
||||||
|
const {
|
||||||
|
name: testName,
|
||||||
|
method: testMethod,
|
||||||
|
class: testClass,
|
||||||
|
id: silenceId,
|
||||||
|
silencedUntil,
|
||||||
|
} = silenceEntry;
|
||||||
|
|
||||||
|
const { state: store, updateStore } = useContext(StoreContext);
|
||||||
|
|
||||||
|
function Actions() {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<IconButton aria-label="modify" component="span" color="primary">
|
||||||
|
<NotificationsIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton color="error" aria-label="delete" component="span">
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion expanded={false} disableGutters={true} square>
|
||||||
|
<AccordionSummary
|
||||||
|
style={{
|
||||||
|
backgroundColor: "rgba(0, 0, 0, .03)",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography component={"span"} style={{ wordBreak: "break-word" }}>
|
||||||
|
{`Test Name: ${testName}`}
|
||||||
|
<br />
|
||||||
|
{`Method: ${testMethod}`}
|
||||||
|
<br />
|
||||||
|
{`Test Class: ${testClass}`}
|
||||||
|
<br />
|
||||||
|
{`Silenced Until: ${silencedUntil} Remaining Time: 2:50`}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Stack
|
||||||
|
sx={{ ml: "auto", display: { md: "none", lg: "none", xl: "none" } }}
|
||||||
|
>
|
||||||
|
<Actions />
|
||||||
|
</Stack>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
sx={{
|
||||||
|
ml: "auto",
|
||||||
|
display: { xs: "none", sm: "none", md: "flex", lg: "flex" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Actions />
|
||||||
|
</Stack>
|
||||||
|
</AccordionSummary>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue