Savepoint
This commit is contained in:
parent
7db1a3456b
commit
02c483950c
45 changed files with 5136 additions and 256 deletions
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>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue