chore(litmus-portal): Added date filter in workflows table and some important refactorings (#1962)

This commit has the following changes:
- Date filter in the workflows table
- Important refactoring in the Browse Workflow Section
- Minor CSS fixes

Signed-off-by: Amit Kumar Das <amitkumar.das@mayadata.io>
This commit is contained in:
Amit Kumar Das 2020-09-04 17:55:52 +05:30 committed by GitHub
parent d74c1d3898
commit 9fb8cef94f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 571 additions and 198 deletions

View File

@ -2753,6 +2753,16 @@
"csstype": "^2.2.0" "csstype": "^2.2.0"
} }
}, },
"@types/react-date-range": {
"version": "0.95.1",
"resolved": "https://registry.npmjs.org/@types/react-date-range/-/react-date-range-0.95.1.tgz",
"integrity": "sha512-jzuh7fL9F1Y3eSoeyWoS/H2zSCn9WXvlbLfOOox9bopmZjyNwp/fx08TYrgi8jAJ60F2Aap8uA2BQbe8+7mNGg==",
"dev": true,
"requires": {
"@types/react": "*",
"moment": ">=2.14.0"
}
},
"@types/react-dom": { "@types/react-dom": {
"version": "16.9.8", "version": "16.9.8",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.8.tgz",
@ -13718,6 +13728,24 @@
"gl-mat4": "^1.0.1" "gl-mat4": "^1.0.1"
} }
}, },
"materialui-daterange-picker": {
"version": "1.1.91",
"resolved": "https://registry.npmjs.org/materialui-daterange-picker/-/materialui-daterange-picker-1.1.91.tgz",
"integrity": "sha512-vMEQlI3WrTuWEVgAhO6P5X6WfC46A1I4BwnlaEKz9Tbsw/1vj1fms5BX37yJkZZs4+Rbj1XIp34l7uy3UyuVyw==",
"requires": {
"@material-ui/core": "^4.9.4",
"@material-ui/icons": "^4.9.1",
"classnames": "^2.2.6",
"date-fns": "^1.30.1"
},
"dependencies": {
"date-fns": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
"integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="
}
}
},
"math-log2": { "math-log2": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/math-log2/-/math-log2-1.0.1.tgz", "resolved": "https://registry.npmjs.org/math-log2/-/math-log2-1.0.1.tgz",
@ -14174,8 +14202,7 @@
"moment": { "moment": {
"version": "2.27.0", "version": "2.27.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==", "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
"dev": true
}, },
"monotone-convex-hull-2d": { "monotone-convex-hull-2d": {
"version": "1.0.1", "version": "1.0.1",

View File

@ -27,6 +27,8 @@
"js-yaml": "^3.14.0", "js-yaml": "^3.14.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"localforage": "^1.7.3", "localforage": "^1.7.3",
"materialui-daterange-picker": "^1.1.91",
"moment": "^2.27.0",
"plotly.js": "^1.54.6", "plotly.js": "^1.54.6",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"rc-progress": "^3.0.0", "rc-progress": "^3.0.0",

View File

@ -12,9 +12,10 @@ import LinearProgressBar from '../../ReturningHome/ProgressBar/LinearProgressBar
import useStyles from './styles'; import useStyles from './styles';
import timeDifferenceForDate from '../../../../utils/datesModifier'; import timeDifferenceForDate from '../../../../utils/datesModifier';
import { history } from '../../../../redux/configureStore'; import { history } from '../../../../redux/configureStore';
import { WorkflowRun } from '../../../../models/workflowData';
interface TableDataProps { interface TableDataProps {
data: any; data: WorkflowRun;
} }
const TableData: React.FC<TableDataProps> = ({ data }) => { const TableData: React.FC<TableDataProps> = ({ data }) => {
@ -33,7 +34,7 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
const handleMenu = () => {}; const handleMenu = () => {};
return ( return (
<> <>
<TableCell className={classes.headerStatus1}> <TableCell className={classes.tableDataStatus}>
<CustomStatus status={JSON.parse(data.execution_data).phase} /> <CustomStatus status={JSON.parse(data.execution_data).phase} />
</TableCell> </TableCell>
<TableCell className={classes.workflowNameData}> <TableCell className={classes.workflowNameData}>

View File

@ -0,0 +1,169 @@
import React from 'react';
import {
InputBase,
InputAdornment,
FormControl,
InputLabel,
Select,
MenuItem,
Typography,
IconButton,
Popover,
} from '@material-ui/core';
import SearchIcon from '@material-ui/icons/Search';
import { DateRangePicker } from 'materialui-daterange-picker';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import useStyles from './styles';
interface HeaderSectionProps {
searchValue: string;
statusValue: string;
clusterValue: string;
isOpen: boolean;
isDateOpen: boolean;
popAnchorEl: HTMLElement | null;
displayDate: string;
changeSearch: (
event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
) => void;
changeStatus: (
event: React.ChangeEvent<{
name?: string | undefined;
value: unknown;
}>,
child: React.ReactNode
) => void;
changeCluster: (
event: React.ChangeEvent<{
name?: string | undefined;
value: unknown;
}>,
child: React.ReactNode
) => void;
popOverClick: (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => void;
popOverClose: (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
) => void;
toggle: () => void;
selectDate: (range: any) => void;
}
const HeaderSection: React.FC<HeaderSectionProps> = ({
searchValue,
statusValue,
clusterValue,
isOpen,
popAnchorEl,
isDateOpen,
displayDate,
changeSearch,
changeStatus,
changeCluster,
popOverClick,
popOverClose,
toggle,
selectDate,
}) => {
const classes = useStyles();
return (
<div>
<div className={classes.headerSection}>
{/* Search Field */}
<InputBase
id="input-with-icon-adornment"
placeholder="Search"
className={classes.search}
value={searchValue}
onChange={changeSearch}
startAdornment={
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
}
/>
{/* Select Workflow */}
<FormControl
variant="outlined"
className={classes.formControl}
color="secondary"
focused
>
<InputLabel className={classes.selectText}>
Workflow Status
</InputLabel>
<Select
value={statusValue}
onChange={changeStatus}
label="Workflow Status"
className={classes.selectText}
>
<MenuItem value="All">All</MenuItem>
<MenuItem value="Failed">Failed</MenuItem>
<MenuItem value="Running">Running</MenuItem>
<MenuItem value="Succeeded">Succeeded</MenuItem>
</Select>
</FormControl>
{/* Select Cluster */}
<FormControl
variant="outlined"
className={classes.formControl}
color="secondary"
focused
>
<InputLabel className={classes.selectText}>Target Cluster</InputLabel>
<Select
value={clusterValue}
onChange={changeCluster}
label="Target Cluster"
className={classes.selectText}
>
<MenuItem value="All">All</MenuItem>
<MenuItem value="Predefined">Cluset pre-defined</MenuItem>
<MenuItem value="Kubernetes">Kubernetes Cluster</MenuItem>
</Select>
</FormControl>
<div className={classes.selectDate}>
<Typography className={classes.displayDate}>
{displayDate}
<IconButton
style={{ width: 10, height: 10 }}
onClick={popOverClick}
>
{isOpen ? <KeyboardArrowDownIcon /> : <ChevronRightIcon />}
</IconButton>
</Typography>
</div>
<Popover
open={isOpen}
anchorEl={popAnchorEl}
onClose={popOverClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
style={{
marginTop: 10,
}}
>
<DateRangePicker
open={isDateOpen}
toggle={toggle}
onChange={selectDate}
/>
</Popover>
</div>
</div>
);
};
export default HeaderSection;

View File

@ -1,12 +1,6 @@
import { useQuery } from '@apollo/client'; import { useQuery } from '@apollo/client';
import { import {
FormControl,
IconButton, IconButton,
InputAdornment,
InputBase,
InputLabel,
MenuItem,
Select,
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
@ -16,9 +10,9 @@ import {
TableRow, TableRow,
Typography, Typography,
} from '@material-ui/core'; } from '@material-ui/core';
import moment from 'moment';
import ExpandLessIcon from '@material-ui/icons/ExpandLess'; import ExpandLessIcon from '@material-ui/icons/ExpandLess';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import SearchIcon from '@material-ui/icons/Search';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { WORKFLOW_DETAILS, WORKFLOW_EVENTS } from '../../../../graphql'; import { WORKFLOW_DETAILS, WORKFLOW_EVENTS } from '../../../../graphql';
@ -41,6 +35,7 @@ import {
import Loader from '../../../Loader'; import Loader from '../../../Loader';
import useStyles from './styles'; import useStyles from './styles';
import TableData from './TableData'; import TableData from './TableData';
import HeaderSection from './headerSection';
interface FilterOptions { interface FilterOptions {
search: string; search: string;
@ -59,6 +54,12 @@ interface SortData {
noOfSteps: { sort: boolean; ascending: boolean }; noOfSteps: { sort: boolean; ascending: boolean };
} }
interface DateData {
dateValue: string;
fromDate: Date | undefined;
toDate: Date | undefined;
}
const BrowseWorkflow = () => { const BrowseWorkflow = () => {
const classes = useStyles(); const classes = useStyles();
const userData: UserData = useSelector((state: RootState) => state.userData); const userData: UserData = useSelector((state: RootState) => state.userData);
@ -72,7 +73,6 @@ const BrowseWorkflow = () => {
variables: { projectID: selectedProjectID }, variables: { projectID: selectedProjectID },
fetchPolicy: 'cache-and-network', fetchPolicy: 'cache-and-network',
}); });
// Using subscription to get realtime data // Using subscription to get realtime data
useEffect(() => { useEffect(() => {
subscribeToMore<WorkflowSubscription>({ subscribeToMore<WorkflowSubscription>({
@ -121,6 +121,29 @@ const BrowseWorkflow = () => {
rowsPerPage: 5, rowsPerPage: 5,
}); });
const [popAnchorEl, setPopAnchorEl] = React.useState<null | HTMLElement>(
null
);
const isOpen = Boolean(popAnchorEl);
const [open, setOpen] = React.useState<boolean>(false);
const handlePopOverClose = () => {
setPopAnchorEl(null);
setOpen(false);
};
const handlePopOverClick = (event: React.MouseEvent<HTMLElement>) => {
setPopAnchorEl(event.currentTarget);
setOpen(true);
};
// State for start date and end date
const [dateRange, setDateRange] = React.useState<DateData>({
dateValue: 'Select a period',
fromDate: new Date(0),
toDate: new Date(new Date().valueOf() + 1000 * 3600 * 24),
});
const filteredData = data?.getWorkFlowRuns const filteredData = data?.getWorkFlowRuns
.filter((dataRow) => .filter((dataRow) =>
dataRow.workflow_name.toLowerCase().includes(filters.search) dataRow.workflow_name.toLowerCase().includes(filters.search)
@ -137,6 +160,14 @@ const BrowseWorkflow = () => {
? true ? true
: dataRow.cluster_name.toLowerCase().includes(filters.cluster) : dataRow.cluster_name.toLowerCase().includes(filters.cluster)
) )
.filter((dataRow) => {
return dateRange.fromDate && dateRange.toDate === undefined
? true
: parseInt(dataRow.last_updated, 10) * 1000 >=
new Date(moment(dateRange.fromDate).format()).getTime() &&
parseInt(dataRow.last_updated, 10) * 1000 <=
new Date(moment(dateRange.toDate).format()).getTime();
})
.sort((a: WorkflowRun, b: WorkflowRun) => { .sort((a: WorkflowRun, b: WorkflowRun) => {
// Sorting based on unique fields // Sorting based on unique fields
if (sortData.name.sort) { if (sortData.name.sort) {
@ -178,94 +209,73 @@ const BrowseWorkflow = () => {
return 0; return 0;
}); });
// Functions passed as props in the headerSeaction
const changeSearch = (
event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
) => {
setFilters({ ...filters, search: event.target.value as string });
setPaginationData({ ...paginationData, pageNo: 0 });
};
const changeStatus = (
event: React.ChangeEvent<{
name?: string | undefined;
value: unknown;
}>
) => {
setFilters({ ...filters, status: event.target.value as string });
setPaginationData({ ...paginationData, pageNo: 0 });
};
const changeCluster = (
event: React.ChangeEvent<{
name?: string | undefined;
value: unknown;
}>
) => {
setFilters({ ...filters, cluster: event.target.value as string });
setPaginationData({ ...paginationData, pageNo: 0 });
};
// Function to set the date range for filtering
const dateChange = (range: any) => {
setDateRange({
dateValue: `${moment(range?.startDate)
.format('DD.MM.YYYY')
.toString()}-${moment(range?.endDate).format('DD.MM.YYYY').toString()}`,
fromDate:
moment(range?.startDate).format('DD-MM-YYYY') ===
moment(new Date()).format('DD-MM-YYYY')
? new Date(new Date().setHours(0, 0, 0))
: new Date(range?.startDate.setHours(0, 0, 0)),
toDate:
moment(range?.endDate).format('DD-MM-YYYY') ===
moment(new Date()).format('DD-MM-YYYY')
? new Date(new Date().setHours(23, 59, 59))
: new Date(range?.endDate.setHours(23, 59, 59)),
});
};
return ( return (
<div> <div>
<section className="Heading section"> <section className="Heading section">
<div className={classes.headerSection}> {/* Header Section */}
<InputBase <HeaderSection
id="input-with-icon-adornment" searchValue={filters.search}
placeholder="Search" changeSearch={changeSearch}
className={classes.search} statusValue={filters.status}
value={filters.search} changeStatus={changeStatus}
onChange={(e) => { clusterValue={filters.cluster}
setFilters({ ...filters, search: e.target.value as string }); changeCluster={changeCluster}
setPaginationData({ ...paginationData, pageNo: 0 }); popOverClick={handlePopOverClick}
}} popOverClose={handlePopOverClose}
startAdornment={ isOpen={isOpen}
<InputAdornment position="start"> popAnchorEl={popAnchorEl}
<SearchIcon /> isDateOpen={open}
</InputAdornment> toggle={handlePopOverClose}
} displayDate={dateRange.dateValue}
/> selectDate={dateChange}
<FormControl className={classes.select}> />
<InputLabel id="demo-simple-select-outlined-label">
Workflow Status
</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={filters.status}
onChange={(e) => {
setFilters({ ...filters, status: e.target.value as string });
setPaginationData({ ...paginationData, pageNo: 0 });
}}
disableUnderline
>
<MenuItem value="All">
<Typography className={classes.menuItem}>All</Typography>
</MenuItem>
<MenuItem value="Failed">
<Typography className={classes.menuItem}>Failed</Typography>
</MenuItem>
<MenuItem value="Running">
<Typography className={classes.menuItem}>Running</Typography>
</MenuItem>
<MenuItem value="Succeeded">
<Typography className={classes.menuItem}>Succeeded</Typography>
</MenuItem>
</Select>
</FormControl>
<FormControl className={classes.select1}>
<InputLabel id="demo-simple-select-outlined-label">
Target Cluster
</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={filters.cluster}
onChange={(e) => {
setFilters({ ...filters, cluster: e.target.value as string });
setPaginationData({ ...paginationData, pageNo: 0 });
}}
disableUnderline
>
<MenuItem value="All">
<Typography className={classes.menuItem}>All</Typography>
</MenuItem>
<MenuItem value="Predefined">
<Typography className={classes.menuItem}>
Cluset pre-defined
</Typography>
</MenuItem>
<MenuItem value="Kubernetes">
<Typography className={classes.menuItem}>
Kubernetes Cluster
</Typography>
</MenuItem>
</Select>
</FormControl>
<FormControl className={classes.select}>
<InputLabel id="demo-simple-select-outlined-label">Date</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value=""
disableUnderline
onChange={() => {}}
/>
</FormControl>
</div>
</section> </section>
<section className="table section"> <section className="table section">
<TableContainer className={classes.tableMain}> <TableContainer className={classes.tableMain}>
@ -394,7 +404,7 @@ const BrowseWorkflow = () => {
</div> </div>
</TableCell> </TableCell>
{/* Menu */} {/* Menu Cell */}
<TableCell /> <TableCell />
</TableRow> </TableRow>
</TableHead> </TableHead>

View File

@ -1,39 +1,60 @@
import { makeStyles } from '@material-ui/core'; import { makeStyles } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
// Header Section Properties
headerSection: { headerSection: {
width: '100%', width: '100%',
height: '5.625rem',
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
alignContent: 'center', alignContent: 'center',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
border: '1px solid ', border: '1px solid ',
borderColor: theme.palette.text.hint, borderColor: theme.palette.customColors.black(0.07),
backgroundColor: theme.palette.common.white, backgroundColor: theme.palette.common.white,
}, },
search: { search: {
fontSize: 14,
marginRight: 'auto', marginRight: 'auto',
borderBottom: `1px solid ${theme.palette.customColors.black(0.1)}`,
marginLeft: theme.spacing(6.25), marginLeft: theme.spacing(6.25),
}, },
select: {
width: '9.375rem', // Form Select Properties
marginLeft: theme.spacing(1.25), formControl: {
paddingBottom: theme.spacing(2.5), margin: theme.spacing(0.5),
marginRight: theme.spacing(2.5),
height: '2.5rem',
minWidth: '9rem',
}, },
select1: {
width: '10.375rem', selectText: {
marginLeft: theme.spacing(1.25), height: '2.5rem',
paddingBottom: theme.spacing(2.5), padding: theme.spacing(0.5),
}, },
headerText: {
marginLeft: theme.spacing(3.75), selectDate: {
color: theme.palette.text.disabled, display: 'flex',
paddingBottom: theme.spacing(0.625), flexDirection: 'row',
height: '2.5rem',
minWidth: '9rem',
border: '1.7px solid',
borderRadius: 4,
borderColor: theme.palette.secondary.main,
marginRight: theme.spacing(3.75),
}, },
displayDate: {
marginLeft: theme.spacing(1.875),
paddingTop: theme.spacing(0.75),
width: '100%',
},
// Table and Table Data Properties
tableMain: { tableMain: {
marginTop: theme.spacing(6.25), marginTop: theme.spacing(6.25),
border: '1px solid rgba(0,0,0,0.1)', border: `1px solid ${theme.palette.customColors.black(0.07)}`,
backgroundColor: theme.palette.common.white, backgroundColor: theme.palette.common.white,
height: '29.219rem', height: '29.219rem',
}, },
@ -42,10 +63,22 @@ const useStyles = makeStyles((theme) => ({
}, },
headerStatus: { headerStatus: {
paddingLeft: theme.spacing(10), paddingLeft: theme.spacing(10),
color: 'rgba(0, 0, 0, 0.4)', color: theme.palette.customColors.black(0.4),
}, },
headerStatus1: { workflowName: {
paddingLeft: theme.spacing(8), borderRight: `1px solid ${theme.palette.customColors.black(0.1)}`,
color: theme.palette.customColors.black(0.4),
},
sortDiv: {
display: 'flex',
flexDirection: 'column',
paddingLeft: theme.spacing(1.25),
},
headData: {
color: theme.palette.customColors.black(0.4),
},
tableDataStatus: {
paddingLeft: theme.spacing(8.5),
}, },
progressBar: { progressBar: {
width: '6.5rem', width: '6.5rem',
@ -53,23 +86,13 @@ const useStyles = makeStyles((theme) => ({
steps: { steps: {
marginLeft: theme.spacing(5.625), marginLeft: theme.spacing(5.625),
}, },
menuItem: {
paddingLeft: theme.spacing(1.75),
},
workflowName: {
borderRight: '1px solid rgba(0,0,0,0.1)',
color: theme.palette.customColors.black(0.4),
},
workflowNameData: { workflowNameData: {
borderRight: '1px solid rgba(0,0,0,0.1)', borderRight: `1px solid ${theme.palette.customColors.black(0.1)}`,
}, },
targetCluster: { targetCluster: {
paddingLeft: theme.spacing(3.75), paddingLeft: theme.spacing(3.75),
color: theme.palette.customColors.black(0.4), color: theme.palette.customColors.black(0.4),
}, },
headData: {
color: theme.palette.customColors.black(0.4),
},
clusterName: { clusterName: {
marginLeft: theme.spacing(3.75), marginLeft: theme.spacing(3.75),
}, },
@ -86,11 +109,6 @@ const useStyles = makeStyles((theme) => ({
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
}, },
sortDiv: {
display: 'flex',
flexDirection: 'column',
paddingLeft: theme.spacing(1.25),
},
})); }));
export default useStyles; export default useStyles;

View File

@ -11,47 +11,52 @@ import {
import MoreVertIcon from '@material-ui/icons/MoreVert'; import MoreVertIcon from '@material-ui/icons/MoreVert';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import moment from 'moment';
import useStyles from './styles'; import useStyles from './styles';
import LinearProgressBar from '../../ReturningHome/ProgressBar/LinearProgressBar'; import LinearProgressBar from '../../ReturningHome/ProgressBar/LinearProgressBar';
import { history } from '../../../../redux/configureStore'; import { history } from '../../../../redux/configureStore';
import { WorkflowRun } from '../../../../models/workflowData';
interface TableDataProps { interface TableDataProps {
data: any; data: WorkflowRun;
deleteRow: (workflowId: string) => void;
} }
const TableData: React.FC<TableDataProps> = ({ data }) => { const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
const classes = useStyles(); const classes = useStyles();
// States for PopOver to display Experiment Weights
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [popAnchorEl, setPopAnchorEl] = React.useState<null | HTMLElement>( const [popAnchorEl, setPopAnchorEl] = React.useState<null | HTMLElement>(
null null
); );
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const isOpen = Boolean(popAnchorEl); const isOpen = Boolean(popAnchorEl);
const id = isOpen ? 'simple-popover' : undefined; const id = isOpen ? 'simple-popover' : undefined;
const handlePopOverClose = () => { const handlePopOverClose = () => {
setPopAnchorEl(null); setPopAnchorEl(null);
}; };
const handlePopOverClick = (event: React.MouseEvent<HTMLElement>) => { const handlePopOverClick = (event: React.MouseEvent<HTMLElement>) => {
setPopAnchorEl(event.currentTarget); setPopAnchorEl(event.currentTarget);
}; };
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
}; };
const handleMenu = () => {};
// Function to convert UNIX time in format of DD MMM YYY
const formatDate = (date: any) => { const formatDate = (date: any) => {
const updated = new Date(date * 1000).toString(); const updated = new Date(date * 1000).toString();
const day = updated.slice(8, 10); const resDate = moment(updated).format('DD MMM YYYY');
const month = updated.slice(4, 7);
const year = updated.slice(11, 15);
const resDate = `${day} ${month} ${year}`;
return resDate; return resDate;
}; };
// Dummy Experiment Weights
const exWeight = [ const exWeight = [
{ {
name: 'Node add test', name: 'Node add test',
@ -103,7 +108,7 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
<KeyboardArrowDownIcon className={classes.expInfo} /> <KeyboardArrowDownIcon className={classes.expInfo} />
</div> </div>
) : ( ) : (
<div className={classes.expDiv1}> <div className={classes.expDiv}>
<Typography> <Typography>
<strong>Show Experiment</strong> <strong>Show Experiment</strong>
</Typography> </Typography>
@ -180,7 +185,10 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
<Typography className={classes.btnText}>Edit Schedule</Typography> <Typography className={classes.btnText}>Edit Schedule</Typography>
</div> </div>
</MenuItem> </MenuItem>
<MenuItem value="Analysis" onClick={handleMenu}> <MenuItem
value="Analysis"
onClick={() => deleteRow(data.workflow_id)}
>
<div className={classes.expDiv}> <div className={classes.expDiv}>
<img <img
src="/icons/deleteSchedule.svg" src="/icons/deleteSchedule.svg"

View File

@ -16,13 +16,27 @@ import {
TableCell, TableCell,
TableBody, TableBody,
TablePagination, TablePagination,
IconButton,
} from '@material-ui/core'; } from '@material-ui/core';
import ExpandLessIcon from '@material-ui/icons/ExpandLess';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import useStyles from './styles'; import useStyles from './styles';
import { WORKFLOW_DETAILS } from '../../../../graphql'; import { WORKFLOW_DETAILS } from '../../../../graphql';
import TableData from './TableData'; import TableData from './TableData';
import { Workflow, WorkflowDataVars } from '../../../../models/workflowData'; import {
Workflow,
WorkflowDataVars,
WorkflowRun,
ExecutionData,
} from '../../../../models/workflowData';
import Loader from '../../../Loader'; import Loader from '../../../Loader';
import {
sortAlphaAsc,
sortAlphaDesc,
sortNumAsc,
sortNumDesc,
} from '../../../../utils/sort';
import { UserData } from '../../../../models/user'; import { UserData } from '../../../../models/user';
import { RootState } from '../../../../redux/reducers'; import { RootState } from '../../../../redux/reducers';
@ -36,6 +50,11 @@ interface PaginationData {
rowsPerPage: number; rowsPerPage: number;
} }
interface SortData {
startDate: { sort: boolean; ascending: boolean };
name: { sort: boolean; ascending: boolean };
}
const ScheduleWorkflow = () => { const ScheduleWorkflow = () => {
const classes = useStyles(); const classes = useStyles();
const userData: UserData = useSelector((state: RootState) => state.userData); const userData: UserData = useSelector((state: RootState) => state.userData);
@ -59,6 +78,12 @@ const ScheduleWorkflow = () => {
rowsPerPage: 5, rowsPerPage: 5,
}); });
// State for sorting
const [sortData, setSortData] = useState<SortData>({
name: { sort: false, ascending: true },
startDate: { sort: true, ascending: true },
});
const filteredData = data?.getWorkFlowRuns const filteredData = data?.getWorkFlowRuns
.filter((dataRow) => .filter((dataRow) =>
dataRow.workflow_name.toLowerCase().includes(filter.search) dataRow.workflow_name.toLowerCase().includes(filter.search)
@ -68,12 +93,41 @@ const ScheduleWorkflow = () => {
? true ? true
: dataRow.cluster_name.toLowerCase().includes(filter.cluster) : dataRow.cluster_name.toLowerCase().includes(filter.cluster)
) )
.reverse(); .sort((a: WorkflowRun, b: WorkflowRun) => {
// Sorting based on unique fields
if (sortData.name.sort) {
const x = a.workflow_name;
const y = b.workflow_name;
return sortData.name.ascending
? sortAlphaAsc(x, y)
: sortAlphaDesc(x, y);
}
if (sortData.startDate.sort) {
const x = parseInt(
(JSON.parse(a.execution_data) as ExecutionData).startedAt,
10
);
const y = parseInt(
(JSON.parse(b.execution_data) as ExecutionData).startedAt,
10
);
return sortData.startDate.ascending
? sortNumAsc(y, x)
: sortNumDesc(y, x);
}
return 0;
});
const deleteRow = () => {
// Delete Mutation Here
};
return ( return (
<div> <div>
<section className="Heading section"> <section className="Heading section">
<div className={classes.headerSection}> <div className={classes.headerSection}>
{/* Search Field */}
<InputBase <InputBase
id="input-with-icon-adornment" id="input-with-icon-adornment"
placeholder="Search" placeholder="Search"
@ -88,59 +142,126 @@ const ScheduleWorkflow = () => {
</InputAdornment> </InputAdornment>
} }
/> />
<FormControl className={classes.select}> {/* Select Cluster */}
<InputLabel id="demo-simple-select-outlined-label"> <FormControl
variant="outlined"
className={classes.formControl}
color="secondary"
focused
>
<InputLabel className={classes.selectText}>
Target Cluster Target Cluster
</InputLabel> </InputLabel>
<Select <Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={filter.cluster} value={filter.cluster}
onChange={(event) => onChange={(event) =>
setFilter({ ...filter, cluster: event.target.value as string }) setFilter({ ...filter, cluster: event.target.value as string })
} }
disableUnderline label="Target Cluster"
className={classes.selectText}
> >
<MenuItem value="All"> <MenuItem value="All">All</MenuItem>
<Typography className={classes.menuItem}>All</Typography> <MenuItem value="Predefined">Cluset pre-defined</MenuItem>
</MenuItem> <MenuItem value="Kubernetes">Kubernetes Cluster</MenuItem>
<MenuItem value="Predefined">
<Typography className={classes.menuItem}>
Cluset pre-defined
</Typography>
</MenuItem>
<MenuItem value="Kubernetes">
<Typography className={classes.menuItem}>
Kubernetes Cluster
</Typography>
</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
</div> </div>
</section> </section>
<section className="table section"> <section className="table section">
{/* Table Header */}
<TableContainer className={classes.tableMain}> <TableContainer className={classes.tableMain}>
<Table stickyHeader aria-label="simple table"> <Table stickyHeader aria-label="simple table">
<TableHead> <TableHead>
<TableRow className={classes.tableHead}> <TableRow className={classes.tableHead}>
{/* WorkFlow Name */}
<TableCell className={classes.workflowName}> <TableCell className={classes.workflowName}>
<Typography style={{ paddingLeft: 65 }}> <div style={{ display: 'flex', flexDirection: 'row' }}>
Workflow Name <Typography style={{ paddingLeft: 65, paddingTop: 10 }}>
</Typography> Workflow Name
</Typography>
<div className={classes.sortDiv}>
<IconButton
aria-label="sort name ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: true, ascending: true },
startDate: { sort: false, ascending: true },
})
}
>
<ExpandLessIcon fontSize="inherit" />
</IconButton>
<IconButton
aria-label="sort name descending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: true, ascending: false },
startDate: { sort: false, ascending: false },
})
}
>
<ExpandMoreIcon fontSize="inherit" />
</IconButton>
</div>
</div>
</TableCell> </TableCell>
{/* Starting Date */}
<TableCell className={classes.headerStatus}> <TableCell className={classes.headerStatus}>
Starting Date <div style={{ display: 'flex', flexDirection: 'row' }}>
<Typography style={{ paddingTop: 10 }}>
Starting Date
</Typography>
<div className={classes.sortDiv}>
<IconButton
aria-label="sort last run ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
startDate: { sort: true, ascending: true },
name: { sort: false, ascending: true },
})
}
>
<ExpandLessIcon fontSize="inherit" />
</IconButton>
<IconButton
aria-label="sort last run descending"
size="small"
onClick={() =>
setSortData({
...sortData,
startDate: { sort: true, ascending: false },
name: { sort: false, ascending: true },
})
}
>
<ExpandMoreIcon fontSize="inherit" />
</IconButton>
</div>
</div>
</TableCell> </TableCell>
{/* Regularity */}
<TableCell> <TableCell>
<Typography className={classes.regularity}> <Typography className={classes.regularity}>
Regularity Regularity
</Typography> </Typography>
</TableCell> </TableCell>
{/* Cluster */}
<TableCell> <TableCell>
<Typography className={classes.targetCluster}> <Typography className={classes.targetCluster}>
Cluster Cluster
</Typography> </Typography>
</TableCell> </TableCell>
{/* Show Experiments */}
<TableCell> <TableCell>
<Typography className={classes.showExp}> <Typography className={classes.showExp}>
Show Experiments Show Experiments
@ -169,9 +290,9 @@ const ScheduleWorkflow = () => {
paginationData.pageNo * paginationData.rowsPerPage + paginationData.pageNo * paginationData.rowsPerPage +
paginationData.rowsPerPage paginationData.rowsPerPage
) )
.map((data: any) => ( .map((data) => (
<TableRow key={data.workflow_run_id}> <TableRow key={data.workflow_run_id}>
<TableData data={data} /> <TableData data={data} deleteRow={deleteRow} />
</TableRow> </TableRow>
)) ))
) : ( ) : (
@ -184,6 +305,8 @@ const ScheduleWorkflow = () => {
</TableBody> </TableBody>
</Table> </Table>
</TableContainer> </TableContainer>
{/* Pagination Section */}
<TablePagination <TablePagination
rowsPerPageOptions={[5, 10, 25]} rowsPerPageOptions={[5, 10, 25]}
component="div" component="div"

View File

@ -1,27 +1,39 @@
import { makeStyles } from '@material-ui/core'; import { makeStyles } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
// Header Section Properties
headerSection: { headerSection: {
width: '100%', width: '100%',
height: '5.625rem',
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
alignContent: 'center', alignContent: 'center',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
border: '1px solid ', border: '1px solid ',
borderColor: theme.palette.text.hint, borderColor: theme.palette.customColors.black(0.07),
backgroundColor: theme.palette.common.white, backgroundColor: theme.palette.common.white,
}, },
search: { search: {
fontSize: 14,
marginRight: 'auto', marginRight: 'auto',
borderBottom: `1px solid ${theme.palette.customColors.black(0.1)}`,
marginLeft: theme.spacing(6.25), marginLeft: theme.spacing(6.25),
}, },
select: {
width: '10.375rem', // Form Select Properties
marginLeft: theme.spacing(1.25), formControl: {
paddingBottom: theme.spacing(2.5), margin: theme.spacing(0.5),
marginRight: theme.spacing(3.75), marginRight: theme.spacing(6.25),
height: '2.5rem',
minWidth: '9rem',
}, },
selectText: {
height: '2.5rem',
padding: theme.spacing(0.5),
},
// Table and Table Data Properties
headerText: { headerText: {
marginLeft: theme.spacing(3.75), marginLeft: theme.spacing(3.75),
color: theme.palette.text.disabled, color: theme.palette.text.disabled,
@ -29,9 +41,9 @@ const useStyles = makeStyles((theme) => ({
}, },
tableMain: { tableMain: {
marginTop: theme.spacing(6.25), marginTop: theme.spacing(6.25),
border: '1px solid rgba(0,0,0,0.1)', border: `1px solid ${theme.palette.customColors.black(0.07)}`,
backgroundColor: theme.palette.common.white, backgroundColor: theme.palette.common.white,
height: '29.180rem', height: '29.220rem',
}, },
tableHead: { tableHead: {
opacity: 1, opacity: 1,
@ -54,11 +66,16 @@ const useStyles = makeStyles((theme) => ({
paddingLeft: theme.spacing(1.75), paddingLeft: theme.spacing(1.75),
}, },
workflowName: { workflowName: {
borderRight: '1px solid rgba(0,0,0,0.1)', borderRight: `1px solid ${theme.palette.customColors.black(0.1)}`,
color: theme.palette.customColors.black(0.4), color: theme.palette.customColors.black(0.4),
}, },
sortDiv: {
display: 'flex',
flexDirection: 'column',
marginLeft: theme.spacing(1.25),
},
workflowNameData: { workflowNameData: {
borderRight: '1px solid rgba(0,0,0,0.1)', borderRight: `1px solid ${theme.palette.customColors.black(0.1)}`,
}, },
regularity: { regularity: {
paddingLeft: theme.spacing(3.75), paddingLeft: theme.spacing(3.75),
@ -84,24 +101,6 @@ const useStyles = makeStyles((theme) => ({
paddingLeft: theme.spacing(1), paddingLeft: theme.spacing(1),
color: theme.palette.customColors.black(0.4), color: theme.palette.customColors.black(0.4),
}, },
expDiv: {
display: 'flex',
flexDirection: 'row',
},
expDiv1: {
display: 'flex',
flexDirection: 'row',
cursor: 'pointer',
},
weightDiv: {
width: 243,
padding: '25px 20px',
},
weightInfo: {
display: 'flex',
flexDirection: 'row',
paddingBottom: theme.spacing(0.625),
},
clusterData: { clusterData: {
paddingTop: theme.spacing(1.25), paddingTop: theme.spacing(1.25),
}, },
@ -123,6 +122,22 @@ const useStyles = makeStyles((theme) => ({
btnText: { btnText: {
paddingLeft: theme.spacing(1.625), paddingLeft: theme.spacing(1.625),
}, },
// Experiment Weights PopOver Property
weightDiv: {
width: '15.1875rem',
padding: theme.spacing(3.125, 2.6),
},
weightInfo: {
display: 'flex',
flexDirection: 'row',
paddingBottom: theme.spacing(0.625),
},
expDiv: {
display: 'flex',
flexDirection: 'row',
cursor: 'pointer',
},
})); }));
export default useStyles; export default useStyles;