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 TABLE test_results (
|
||||
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;
|
||||
|
||||
=======
|
||||
|
||||
> > > > > > > b023d8910c89d80573499890a958c0df649849e1
|
||||
|
||||
# Tables
|
||||
|
||||
PG Database Tables Mapped Out
|
||||
|
||||
<<<<<<< HEAD
|
||||
|
||||
## `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 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
|
||||
- test_name\* Name of test
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"start:dev": "nodemon dist/app.js",
|
||||
"start:dev:replit": "npm run start:dev & npm run start:react:replit",
|
||||
"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:api": "node tests/api.js",
|
||||
"test:dev": "nodemon tests/index.js"
|
||||
|
|
|
@ -127,7 +127,11 @@ export default function Views() {
|
|||
>
|
||||
{navHeader()}
|
||||
</Typography>
|
||||
<Avatar alt="Remy Sharp" src="/assets/QA.png" onClick={reloadPage}/>
|
||||
<Avatar
|
||||
alt="Remy Sharp"
|
||||
src="/assets/QA.png"
|
||||
onClick={reloadPage}
|
||||
/>
|
||||
</Toolbar>
|
||||
</Box>
|
||||
</AppBar>
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
import React, { useReducer, createContext, useMemo } from "react";
|
||||
const JobContext = createContext();
|
||||
|
||||
export const jobStatus = {
|
||||
OK: "o",
|
||||
QUEUED: "q",
|
||||
PENDING: "p",
|
||||
CANCELED: "c",
|
||||
ACTIVE: "a",
|
||||
ERROR: "e",
|
||||
};
|
||||
|
||||
const ACTIONS = {
|
||||
CREATE: "c",
|
||||
UPDATE: "u",
|
||||
|
@ -10,6 +19,22 @@ const ACTIONS = {
|
|||
const initialState = {
|
||||
jobs: [],
|
||||
};
|
||||
/*
|
||||
{
|
||||
name: "Job1",
|
||||
test: "someTestName",
|
||||
status: JOB_STATUS.SUCCESS,
|
||||
exitcode: 0
|
||||
}
|
||||
|
||||
OR
|
||||
{
|
||||
compound: true,
|
||||
name: "Compound Job",
|
||||
pipeline: [{}]
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
const reducer = (state, action) => {
|
||||
// Current Jobs
|
||||
|
@ -36,24 +61,26 @@ const reducer = (state, action) => {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const JobProvider = ({ children }) => {
|
||||
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) =>
|
||||
dispatch({ type: ACTIONS.CREATE, job: { ...job, log: [] } });
|
||||
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 retryAll(failing) {
|
||||
// Query Full Locator
|
||||
console.log("Would retry all failing tests!");
|
||||
}
|
||||
|
||||
function jobBuilder(){
|
||||
function activeJobStates() {
|
||||
const jobs = { state };
|
||||
console.log("Would return all active job states");
|
||||
}
|
||||
|
||||
}
|
||||
function jobBuilder() {}
|
||||
|
||||
const context = {
|
||||
state,
|
||||
|
@ -61,7 +88,8 @@ function jobBuilder(){
|
|||
jobUpdate,
|
||||
jobCreate,
|
||||
jobDelete,
|
||||
retryAll
|
||||
retryAll,
|
||||
activeJobStates,
|
||||
};
|
||||
const contextValue = useMemo(() => context, [state, dispatch]);
|
||||
|
||||
|
|
|
@ -1,14 +1,50 @@
|
|||
import React, { useReducer, createContext, useMemo } from "react";
|
||||
import { jobStatus } from "./JobContext.jsx";
|
||||
|
||||
const StoreContext = createContext();
|
||||
|
||||
const ACTIONS = {
|
||||
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 = {
|
||||
intervals: [],
|
||||
failing: [],
|
||||
catalog: [],
|
||||
failing: failngMock,
|
||||
regions: [],
|
||||
catalogSearch: "",
|
||||
focusJob: false,
|
||||
simplifiedControls: false,
|
||||
defaultRegion: "us", // Local Store
|
||||
|
|
|
@ -11,22 +11,36 @@ export default function About() {
|
|||
<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>
|
||||
);
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
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 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 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);
|
||||
|
@ -18,29 +18,23 @@ export default function Alerting() {
|
|||
const [alertDialogOpen, setAlertDialogOpen] = useState(false);
|
||||
const quickAlertClick = () => setAlertDialogOpen(!alertDialogOpen);
|
||||
|
||||
function silenceAlert(){
|
||||
|
||||
}
|
||||
function silenceAlert() {}
|
||||
const handleClose = (confirmed) => () => {
|
||||
quickAlertClick();
|
||||
if(!confirmed) return;
|
||||
if (!confirmed) return;
|
||||
silenceAlert();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="alerting">
|
||||
<Dialog
|
||||
open={alertDialogOpen}
|
||||
onClose={handleClose()}
|
||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
>
|
||||
<DialogTitle>
|
||||
Silence Alert
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
|
||||
</DialogContent>
|
||||
<DialogTitle>Silence Alert</DialogTitle>
|
||||
<DialogContent></DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose()}>Cancel</Button>
|
||||
<Button onClick={handleClose(true)} autoFocus>
|
||||
|
@ -50,8 +44,8 @@ export default function Alerting() {
|
|||
</Dialog>
|
||||
|
||||
<SpeedDial
|
||||
ariaLabel="Silence Alert"
|
||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||
ariaLabel="Silence Alert"
|
||||
sx={{ position: "fixed", bottom: 16, right: 16 }}
|
||||
icon={<SpeedDialIcon />}
|
||||
onClick={quickAlertClick}
|
||||
open={false}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useContext } from "react";
|
||||
import { useEffect, useContext } from "react";
|
||||
import StoreContext from "../ctx/StoreContext.jsx";
|
||||
import JobContext from "../ctx/JobContext.jsx";
|
||||
|
||||
|
@ -16,14 +16,25 @@ export default function Catalog() {
|
|||
|
||||
const { state: store, updateStore } = useContext(StoreContext);
|
||||
|
||||
const handleSearchChange = (e) =>
|
||||
updateStore({ catalogSearch: e.target.value });
|
||||
|
||||
const handleSearchClear = () => updateStore({ catalogSearch: "" });
|
||||
|
||||
useEffect(() => {
|
||||
return function unmount() {
|
||||
handleSearchClear();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="catalog">
|
||||
<CatalogSearch />
|
||||
<TextField
|
||||
label="Search Catalog"
|
||||
type="search"
|
||||
variant="filled"
|
||||
/>
|
||||
<CatalogSearch
|
||||
onChange={handleSearchChange}
|
||||
onClear={handleSearchClear}
|
||||
clearOnUnmount
|
||||
/>
|
||||
<h6>{store.catalogSearch}</h6>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,48 +2,55 @@ 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 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 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';
|
||||
import ReplayIcon from "@mui/icons-material/Replay";
|
||||
|
||||
import FailingBox from "./components/FailingBox.jsx";
|
||||
|
||||
export default function Failing() {
|
||||
const {
|
||||
state: jobState,
|
||||
retryAll
|
||||
} = useContext(JobContext);
|
||||
const { state: jobState, retryAll, activeJobStates } = useContext(JobContext);
|
||||
|
||||
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 retryAllClick = () => setRetryAllOpen(!retryAllOpen);
|
||||
const handleClose = (confirmed) => ()=> {
|
||||
const handleClose = (confirmed) => () => {
|
||||
retryAllClick();
|
||||
if(!confirmed) return;
|
||||
if (!confirmed) return;
|
||||
retryAll(store.failing);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="failing">
|
||||
|
||||
{failing.map((v, i) => (
|
||||
<FailingBox key={i} failingTest={v} />
|
||||
))}
|
||||
|
||||
<Dialog
|
||||
open={retryAllOpen}
|
||||
onClose={handleClose()}
|
||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
>
|
||||
<DialogTitle>
|
||||
Retry all failing tests?
|
||||
</DialogTitle>
|
||||
<DialogTitle>Retry all failing tests?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
This will create x jobs and run y tests
|
||||
|
@ -57,14 +64,13 @@ export default function Failing() {
|
|||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<SpeedDial
|
||||
<SpeedDial
|
||||
ariaLabel="Retry All"
|
||||
sx={{ position: 'absolute', bottom: 16, right: 16 }}
|
||||
sx={{ position: "fixed", bottom: 16, right: 16 }}
|
||||
icon={<ReplayIcon />}
|
||||
onClick={retryAllClick}
|
||||
open={false}
|
||||
open={false}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,15 +2,14 @@ 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 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';
|
||||
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 {
|
||||
|
@ -28,28 +27,30 @@ export default function Jobs() {
|
|||
const quickOpenClose = () => setQuickOpen(false);
|
||||
|
||||
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 (
|
||||
<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>
|
||||
<ClickAwayListener onClickAway={quickOpenClose}>
|
||||
<SpeedDial
|
||||
ariaLabel="New Job"
|
||||
sx={{ position: "fixed", 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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,76 +1,80 @@
|
|||
import { useContext, useState, useEffect } from "react";
|
||||
import React, { 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 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 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 defaultDialog = {
|
||||
title: "",
|
||||
options: [],
|
||||
current: null,
|
||||
onSelect: null,
|
||||
open: false,
|
||||
};
|
||||
const [dialog, setDialog] = 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 optionSettings = {
|
||||
region: {
|
||||
title: "Region",
|
||||
options: [],
|
||||
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) => {
|
||||
setDialog({...s, open:true});
|
||||
setDialog({ ...s, open: true });
|
||||
};
|
||||
|
||||
const handleClose = (newValue, onSelect) => {
|
||||
setDialog({...dialog, open:false})
|
||||
setDialog({ ...dialog, open: false });
|
||||
if (!newValue) return;
|
||||
onSelect(newValue);
|
||||
};
|
||||
|
||||
const handleToggle = (booleanSetting) => ()=> {
|
||||
const handleToggle = (booleanSetting) => () => {
|
||||
const storeUpdate = {};
|
||||
storeUpdate[booleanSetting] = !store[booleanSetting];
|
||||
updateStore(storeUpdate)
|
||||
}
|
||||
updateStore(storeUpdate);
|
||||
};
|
||||
|
||||
function MultiOptionSubtext(props){
|
||||
return( <React.Fragment>
|
||||
<Typography
|
||||
sx={{ display: 'inline' }}
|
||||
component="span"
|
||||
variant="body2"
|
||||
color="primary"
|
||||
>
|
||||
{props.value}
|
||||
</Typography>
|
||||
</React.Fragment>)
|
||||
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' }}>
|
||||
<Box sx={{ width: "100%", bgcolor: "background.paper" }}>
|
||||
<List component="div" role="group">
|
||||
|
||||
<ListItem
|
||||
button
|
||||
divider
|
||||
|
@ -78,37 +82,46 @@ const defaultDialog = {title: "", options: [], current: null, onSelect: null, op
|
|||
aria-label="default page"
|
||||
onClick={() => handleOptionsMenu(optionSettings.defaultPage)}
|
||||
>
|
||||
<ListItemText primary="Default Page" secondary={
|
||||
<MultiOptionSubtext value={optionSettings.defaultPage.current} />
|
||||
}/>
|
||||
<ListItemText
|
||||
primary="Default Page"
|
||||
secondary={
|
||||
<MultiOptionSubtext value={optionSettings.defaultPage.current} />
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
<ListItem
|
||||
button
|
||||
divider
|
||||
aria-haspopup="true"
|
||||
aria-label="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 button divider>
|
||||
<ListItem button divider>
|
||||
<ListItemText primary="Simplified Controls" />
|
||||
<Switch
|
||||
edge="end"
|
||||
onChange={handleToggle("simplifiedControls")}
|
||||
checked={store.simplifiedControls}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
onChange={handleToggle("simplifiedControls")}
|
||||
checked={store.simplifiedControls}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<ListItem button divider>
|
||||
<ListItem button divider>
|
||||
<ListItemText primary="Focus New Jobs" />
|
||||
<Switch
|
||||
edge="end"
|
||||
onChange={handleToggle("focusJob")}
|
||||
checked={store.focusJob}
|
||||
/>
|
||||
<Switch
|
||||
edge="end"
|
||||
onChange={handleToggle("focusJob")}
|
||||
checked={store.focusJob}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
<MultiOptionDialog
|
||||
|
|
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,29 +1,42 @@
|
|||
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";
|
||||
import { useEffect, useRef } 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 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 (
|
||||
<Paper
|
||||
component="form"
|
||||
sx={{ display: 'flex', alignItems: 'center'}}
|
||||
>
|
||||
<Paper component="form" sx={{ display: "flex", alignItems: "center" }}>
|
||||
<InputBase
|
||||
sx={{flex: 1 }}
|
||||
ref={searchRef}
|
||||
sx={{ ml: 1, flex: 1 }}
|
||||
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 />
|
||||
</IconButton>
|
||||
<Divider sx={{ height: 28, m: 0.5 }} orientation="vertical" />
|
||||
<IconButton sx={{ p: '8px' }} aria-label="clear">
|
||||
<Divider sx={{ height: 26, m: 0.5 }} orientation="vertical" />
|
||||
<IconButton
|
||||
sx={{ p: "10px", mr: 0.5 }}
|
||||
aria-label="clear"
|
||||
onClick={handleClear}
|
||||
>
|
||||
<ClearOutlinedIcon />
|
||||
</IconButton>
|
||||
</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,17 +1,15 @@
|
|||
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 { 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);
|
||||
|
@ -31,13 +29,13 @@ export default function MultiOptionDialog(props) {
|
|||
|
||||
const handleOk = () => onClose(value, dialog.onSelect);
|
||||
|
||||
|
||||
const handleChange = (e) =>{ setValue(e.target.value);
|
||||
}
|
||||
const handleChange = (e) => {
|
||||
setValue(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
sx={{ '& .MuiDialog-paper': { width: '80%', maxHeight: 435 } }}
|
||||
sx={{ "& .MuiDialog-paper": { width: "80%", maxHeight: 435 } }}
|
||||
maxWidth="xs"
|
||||
TransitionProps={{ onEntering: handleEntering }}
|
||||
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