chore(litmus-portal): Subscription fixes, added functionality to workflow table and created the schedules table. (#1927)

This commit will add the following things:
- Subscription fixes in the Workflow Table.
- Added search, filter and sort functionality in Workflow Table.
- Created the Schedules Table and a separate route to edit the scheduling of a workflow.
- Added Types for WorkflowRuns
- Changes in GraphQL schema in frontend.

Signed-off-by: Amit Kumar Das <amitkumar.das@mayadata.io>
Co-authored-by: arkajyotiMukherjee <arkajyoti.mukherjee@mayadata.io>
This commit is contained in:
Amit Kumar Das 2020-08-27 16:38:49 +05:30 committed by GitHub
parent ade9285b5a
commit 34a68a5669
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1678 additions and 109 deletions

View File

@ -18,6 +18,9 @@
}
},
"rules": {
"no-nested-ternary": 0,
"no-plusplus": 0,
"dot-notation": 0,
"camelcase": 0,
"jsx-a11y/href-no-hash": ["off"],
"react/jsx-props-no-spreading": ["off"],

View File

@ -0,0 +1,12 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.21875 7.29492H3.28125C3.03855 7.29492 2.8418 7.49168 2.8418 7.73438C2.8418 7.97707 3.03855 8.17383 3.28125 8.17383H4.21875C4.46145 8.17383 4.6582 7.97707 4.6582 7.73438C4.6582 7.49168 4.46145 7.29492 4.21875 7.29492Z" fill="black" fill-opacity="0.4"/>
<path d="M4.21875 9.16992H3.28125C3.03855 9.16992 2.8418 9.36668 2.8418 9.60938C2.8418 9.85207 3.03855 10.0488 3.28125 10.0488H4.21875C4.46145 10.0488 4.6582 9.85207 4.6582 9.60938C4.6582 9.36668 4.46145 9.16992 4.21875 9.16992Z" fill="black" fill-opacity="0.4"/>
<path d="M4.21875 11.0449H3.28125C3.03855 11.0449 2.8418 11.2417 2.8418 11.4844C2.8418 11.7271 3.03855 11.9238 3.28125 11.9238H4.21875C4.46145 11.9238 4.6582 11.7271 4.6582 11.4844C4.6582 11.2417 4.46145 11.0449 4.21875 11.0449Z" fill="black" fill-opacity="0.4"/>
<path d="M7.96875 7.29492H7.03125C6.78855 7.29492 6.5918 7.49168 6.5918 7.73438C6.5918 7.97707 6.78855 8.17383 7.03125 8.17383H7.96875C8.21145 8.17383 8.4082 7.97707 8.4082 7.73438C8.4082 7.49168 8.21145 7.29492 7.96875 7.29492Z" fill="black" fill-opacity="0.4"/>
<path d="M7.96875 9.16992H7.03125C6.78855 9.16992 6.5918 9.36668 6.5918 9.60938C6.5918 9.85207 6.78855 10.0488 7.03125 10.0488H7.96875C8.21145 10.0488 8.4082 9.85207 8.4082 9.60938C8.4082 9.36668 8.21145 9.16992 7.96875 9.16992Z" fill="black" fill-opacity="0.4"/>
<path d="M7.96875 11.0449H7.03125C6.78855 11.0449 6.5918 11.2417 6.5918 11.4844C6.5918 11.7271 6.78855 11.9238 7.03125 11.9238H7.96875C8.21145 11.9238 8.4082 11.7271 8.4082 11.4844C8.4082 11.2417 8.21145 11.0449 7.96875 11.0449Z" fill="black" fill-opacity="0.4"/>
<path d="M11.7188 7.29492H10.7812C10.5386 7.29492 10.3418 7.49168 10.3418 7.73438C10.3418 7.97707 10.5386 8.17383 10.7812 8.17383H11.7188C11.9614 8.17383 12.1582 7.97707 12.1582 7.73438C12.1582 7.49168 11.9614 7.29492 11.7188 7.29492Z" fill="black" fill-opacity="0.4"/>
<path d="M11.7188 9.16992H10.7812C10.5386 9.16992 10.3418 9.36668 10.3418 9.60938C10.3418 9.85207 10.5386 10.0488 10.7812 10.0488H11.7188C11.9614 10.0488 12.1582 9.85207 12.1582 9.60938C12.1582 9.36668 11.9614 9.16992 11.7188 9.16992Z" fill="black" fill-opacity="0.4"/>
<path d="M11.7188 11.0449H10.7812C10.5386 11.0449 10.3418 11.2417 10.3418 11.4844C10.3418 11.7271 10.5386 11.9238 10.7812 11.9238H11.7188C11.9614 11.9238 12.1582 11.7271 12.1582 11.4844C12.1582 11.2417 11.9614 11.0449 11.7188 11.0449Z" fill="black" fill-opacity="0.4"/>
<path d="M13.6816 1.9043H12.627V1.17188C12.627 0.92918 12.4302 0.732422 12.1875 0.732422C11.9448 0.732422 11.748 0.92918 11.748 1.17188V1.9043H7.93945V1.17188C7.93945 0.92918 7.7427 0.732422 7.5 0.732422C7.2573 0.732422 7.06055 0.92918 7.06055 1.17188V1.9043H3.25195V1.17188C3.25195 0.92918 3.0552 0.732422 2.8125 0.732422C2.5698 0.732422 2.37305 0.92918 2.37305 1.17188V1.9043H1.31836C0.591416 1.9043 0 2.49571 0 3.22266V12.9492C0 13.6762 0.591416 14.2676 1.31836 14.2676H13.6816C14.4086 14.2676 15 13.6762 15 12.9492C15 12.6656 15 3.46948 15 3.22266C15 2.49571 14.4086 1.9043 13.6816 1.9043ZM0.878906 3.22266C0.878906 2.98034 1.07604 2.7832 1.31836 2.7832H2.37305V3.51562C2.37305 3.75832 2.5698 3.95508 2.8125 3.95508C3.0552 3.95508 3.25195 3.75832 3.25195 3.51562V2.7832H7.06055V3.51562C7.06055 3.75832 7.2573 3.95508 7.5 3.95508C7.7427 3.95508 7.93945 3.75832 7.93945 3.51562V2.7832H11.748V3.51562C11.748 3.75832 11.9448 3.95508 12.1875 3.95508C12.4302 3.95508 12.627 3.75832 12.627 3.51562V2.7832H13.6816C13.924 2.7832 14.1211 2.98034 14.1211 3.22266V4.95117H0.878906V3.22266ZM13.6816 13.3887H1.31836C1.07604 13.3887 0.878906 13.1915 0.878906 12.9492V5.83008H14.1211V12.9492C14.1211 13.1915 13.924 13.3887 13.6816 13.3887Z" fill="black" fill-opacity="0.4"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,13 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M6.3797 3.62305C6.25035 3.62305 6.14551 3.72788 6.14551 3.85724V8.28347C6.14551 8.41273 6.25035 8.51766 6.3797 8.51766C6.50905 8.51766 6.61389 8.41273 6.61389 8.28347V3.85724C6.61389 3.72788 6.50905 3.62305 6.3797 3.62305Z" fill="black"/>
<path d="M3.61603 3.62305C3.48667 3.62305 3.38184 3.72788 3.38184 3.85724V8.28347C3.38184 8.41273 3.48667 8.51766 3.61603 8.51766C3.74538 8.51766 3.85022 8.41273 3.85022 8.28347V3.85724C3.85022 3.72788 3.74538 3.62305 3.61603 3.62305Z" fill="black"/>
<path d="M1.60166 2.9771V8.74708C1.60166 9.08812 1.72672 9.40839 1.94518 9.63819C2.16263 9.86863 2.46525 9.99945 2.78195 10H7.21293C7.52972 9.99945 7.83234 9.86863 8.0497 9.63819C8.26816 9.40839 8.39321 9.08812 8.39321 8.74708V2.9771C8.82747 2.86183 9.10887 2.4423 9.05078 1.99669C8.9926 1.55118 8.61304 1.21792 8.16369 1.21783H6.96465V0.925086C6.96602 0.678911 6.86868 0.442524 6.69441 0.268619C6.52014 0.0948051 6.28339 -0.00198181 6.03721 3.07708e-05H3.95767C3.71149 -0.00198181 3.47474 0.0948051 3.30047 0.268619C3.1262 0.442524 3.02886 0.678911 3.03023 0.925086V1.21783H1.83119C1.38184 1.21792 1.00228 1.55118 0.9441 1.99669C0.886009 2.4423 1.1674 2.86183 1.60166 2.9771ZM7.21293 9.53162H2.78195C2.38154 9.53162 2.07005 9.18765 2.07005 8.74708V2.99768H7.92483V8.74708C7.92483 9.18765 7.61334 9.53162 7.21293 9.53162ZM3.49861 0.925086C3.49706 0.803142 3.545 0.685772 3.63154 0.599689C3.71799 0.513605 3.83563 0.466309 3.95767 0.468413H6.03721C6.15925 0.466309 6.27689 0.513605 6.36334 0.599689C6.44988 0.685681 6.49782 0.803142 6.49626 0.925086V1.21783H3.49861V0.925086ZM1.83119 1.68621H8.16369C8.39651 1.68621 8.58523 1.87493 8.58523 2.10775C8.58523 2.34057 8.39651 2.5293 8.16369 2.5293H1.83119C1.59837 2.5293 1.40965 2.34057 1.40965 2.10775C1.40965 1.87493 1.59837 1.68621 1.83119 1.68621Z" fill="black"/>
<path d="M4.99786 3.62305C4.86851 3.62305 4.76367 3.72788 4.76367 3.85724V8.28347C4.76367 8.41273 4.86851 8.51766 4.99786 8.51766C5.12721 8.51766 5.23205 8.41273 5.23205 8.28347V3.85724C5.23205 3.72788 5.12721 3.62305 4.99786 3.62305Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="10" height="10" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,12 @@
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M1 6.90039L0 10.0004L3.1 9.00039L1 6.90039Z" fill="black"/>
<path d="M6.5811 1.30269L1.70215 6.18164L3.82344 8.30293L8.70239 3.42398L6.5811 1.30269Z" fill="black"/>
<path d="M9.8498 1.55L8.4498 0.15C8.2498 -0.05 7.9498 -0.05 7.7498 0.15L7.2998 0.6L9.3998 2.7L9.8498 2.25C10.0498 2.05 10.0498 1.75 9.8498 1.55Z" fill="black"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="10" height="10" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 564 B

View File

@ -11,6 +11,7 @@ import CustomStatus from '../CustomStatus/Status';
import LinearProgressBar from '../../ReturningHome/ProgressBar/LinearProgressBar';
import useStyles from './styles';
import timeDifferenceForDate from '../../../../utils/datesModifier';
import { history } from '../../../../redux/configureStore';
interface TableDataProps {
data: any;
@ -35,7 +36,7 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
<TableCell className={classes.headerStatus1}>
<CustomStatus status={JSON.parse(data.execution_data).phase} />
</TableCell>
<TableCell className={classes.workflowName}>
<TableCell className={classes.workflowNameData}>
<Typography>
<strong>{data.workflow_name}</strong>
</Typography>
@ -70,11 +71,7 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
</Typography>
</TableCell>
<TableCell>
<div className={classes.timeDiv}>
<Typography className={classes.timeData}>
{timeDifferenceForDate(data.last_updated)}
</Typography>
</div>
<Typography>{timeDifferenceForDate(data.last_updated)}</Typography>
</TableCell>
<TableCell>
<IconButton
@ -93,7 +90,15 @@ const TableData: React.FC<TableDataProps> = ({ data }) => {
open={open}
onClose={handleClose}
>
<MenuItem value="Workflow" onClick={handleMenu}>
<MenuItem
value="Workflow"
onClick={() =>
history.push({
pathname: '/workflow-underground',
state: data,
})
}
>
Show the workflow
</MenuItem>
<MenuItem value="Analysis" onClick={handleMenu}>

View File

@ -1,66 +1,175 @@
import React, { useEffect, useState } from 'react';
import { useQuery } from '@apollo/client';
import SearchIcon from '@material-ui/icons/Search';
import {
InputBase,
InputAdornment,
FormControl,
IconButton,
InputAdornment,
InputBase,
InputLabel,
Select,
MenuItem,
Typography,
TableContainer,
Select,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
TableCell,
TableContainer,
TableHead,
TablePagination,
TableRow,
Typography,
} from '@material-ui/core';
import ExpandLessIcon from '@material-ui/icons/ExpandLess';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import SearchIcon from '@material-ui/icons/Search';
import React, { useEffect, useState } from 'react';
import { WORKFLOW_DETAILS, WORKFLOW_EVENTS } from '../../../../graphql';
import {
ExecutionData,
Workflow,
WorkflowDataVars,
WorkflowRun,
WorkflowSubscription,
} from '../../../../models/workflowData';
import {
sortAlphaAsc,
sortAlphaDesc,
sortNumAsc,
sortNumDesc,
} from '../../../../utils/sort';
import Loader from '../../../Loader';
import useStyles from './styles';
import { history } from '../../../../redux/configureStore';
import { WORKFLOW_DETAILS, WORKFLOW_EVENTS } from '../../../../schemas';
import TableData from './TableData';
interface FilterOptions {
search: string;
status: string;
cluster: string;
}
interface PaginationData {
pageNo: number;
rowsPerPage: number;
}
interface SortData {
lastRun: { sort: boolean; ascending: boolean };
name: { sort: boolean; ascending: boolean };
noOfSteps: { sort: boolean; ascending: boolean };
}
const BrowseWorkflow = () => {
// Apollo query with subscribeToMore
const { subscribeToMore, ...result } = useQuery(WORKFLOW_DETAILS);
// Default table data
const [mainData, setMainData] = useState<any>();
useEffect(() => {
// Get the inital table data
setMainData(result.data);
// Once Subscription is made, this is called
subscribeToMore({
document: WORKFLOW_EVENTS,
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return setMainData(prev);
const newData = subscriptionData.data.workflowEventListener;
return setMainData({
...prev,
getWorkFlowRuns: [...prev.getWorkFlowRuns, newData],
});
},
});
}, [result.data]);
const classes = useStyles();
const [search, setSearch] = React.useState<String>('');
// Query to get workflows
const { subscribeToMore, data, loading, error } = useQuery<
Workflow,
WorkflowDataVars
>(WORKFLOW_DETAILS, { variables: { projectID: '00000' } });
const [status, setStatus] = React.useState<String>('');
// Using subscription to get realtime data
useEffect(() => {
subscribeToMore<WorkflowSubscription>({
document: WORKFLOW_EVENTS,
variables: { projectID: '00000' },
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;
const modifiedWorkflows = prev.getWorkFlowRuns.slice();
const newWorkflow = subscriptionData.data.workflowEventListener;
const handleStatusChange = (event: React.ChangeEvent<{ value: unknown }>) => {
setStatus(event.target.value as String);
};
// Updating the query data
let i = 0;
for (; i < modifiedWorkflows.length; i++) {
if (
modifiedWorkflows[i].workflow_run_id === newWorkflow.workflow_run_id
) {
modifiedWorkflows[i] = newWorkflow;
break;
}
}
if (i === modifiedWorkflows.length)
modifiedWorkflows.unshift(newWorkflow);
const [cluster, setCluster] = React.useState<String>('');
return { ...prev, getWorkFlowRuns: modifiedWorkflows };
},
});
}, [data]);
// States for filters
const [filters, setFilters] = useState<FilterOptions>({
search: '',
status: 'All',
cluster: 'All',
});
// State for sorting
const [sortData, setSortData] = useState<SortData>({
lastRun: { sort: true, ascending: true },
name: { sort: false, ascending: true },
noOfSteps: { sort: false, ascending: false },
});
// State for pagination
const [paginationData, setPaginationData] = useState<PaginationData>({
pageNo: 0,
rowsPerPage: 5,
});
const filteredData = data?.getWorkFlowRuns
.filter((dataRow) =>
dataRow.workflow_name.toLowerCase().includes(filters.search)
)
.filter((dataRow) =>
filters.status === 'All'
? true
: (JSON.parse(dataRow.execution_data) as ExecutionData).phase.includes(
filters.status
)
)
.filter((dataRow) =>
filters.cluster === 'All'
? true
: dataRow.cluster_name.toLowerCase().includes(filters.cluster)
)
.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.lastRun.sort) {
const x = parseInt(a.last_updated, 10);
const y = parseInt(b.last_updated, 10);
return sortData.lastRun.ascending
? sortNumAsc(y, x)
: sortNumDesc(y, x);
}
return 0;
})
.sort((a: WorkflowRun, b: WorkflowRun) => {
// Sorting based on non-unique fields
if (sortData.noOfSteps.sort) {
const x = Object.keys(
(JSON.parse(a.execution_data) as ExecutionData).nodes
).length;
const y = Object.keys(
(JSON.parse(b.execution_data) as ExecutionData).nodes
).length;
return sortData.noOfSteps.ascending
? sortNumAsc(x, y)
: sortNumDesc(x, y);
}
return 0;
});
const handleClusterChange = (
event: React.ChangeEvent<{ value: unknown }>
) => {
setCluster(event.target.value as String);
};
return (
<div>
<section className="Heading section">
@ -69,8 +178,11 @@ const BrowseWorkflow = () => {
id="input-with-icon-adornment"
placeholder="Search"
className={classes.search}
value={search}
onChange={(e) => setSearch(e.target.value)}
value={filters.search}
onChange={(e) => {
setFilters({ ...filters, search: e.target.value as string });
setPaginationData({ ...paginationData, pageNo: 0 });
}}
startAdornment={
<InputAdornment position="start">
<SearchIcon />
@ -84,18 +196,24 @@ const BrowseWorkflow = () => {
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={status}
onChange={handleStatusChange}
value={filters.status}
onChange={(e) => {
setFilters({ ...filters, status: e.target.value as string });
setPaginationData({ ...paginationData, pageNo: 0 });
}}
disableUnderline
>
<MenuItem value="">
<Typography className={classes.menuItem}>None</Typography>
<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="Completed">
<Typography className={classes.menuItem}>Completed</Typography>
<MenuItem value="Succeeded">
<Typography className={classes.menuItem}>Succeeded</Typography>
</MenuItem>
</Select>
</FormControl>
@ -107,12 +225,15 @@ const BrowseWorkflow = () => {
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={cluster}
onChange={handleClusterChange}
value={filters.cluster}
onChange={(e) => {
setFilters({ ...filters, cluster: e.target.value as string });
setPaginationData({ ...paginationData, pageNo: 0 });
}}
disableUnderline
>
<MenuItem value="">
<Typography className={classes.menuItem}>None</Typography>
<MenuItem value="All">
<Typography className={classes.menuItem}>All</Typography>
</MenuItem>
<MenuItem value="Predefined">
<Typography className={classes.menuItem}>
@ -126,9 +247,6 @@ const BrowseWorkflow = () => {
</MenuItem>
</Select>
</FormControl>
<Typography variant="subtitle1" className={classes.headerText}>
for the period
</Typography>
<FormControl className={classes.select}>
<InputLabel id="demo-simple-select-outlined-label">Date</InputLabel>
<Select
@ -143,44 +261,191 @@ const BrowseWorkflow = () => {
</section>
<section className="table section">
<TableContainer className={classes.tableMain}>
<Table aria-label="simple table">
<TableHead>
<TableRow className={classes.tableHead}>
<Table stickyHeader aria-label="simple table">
<TableHead className={classes.tableHead}>
<TableRow>
{/* Status */}
<TableCell className={classes.headerStatus}>Status</TableCell>
{/* Workflow Name */}
<TableCell className={classes.workflowName}>
Workflow Name
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Typography style={{ paddingTop: 10 }}>
Workflow Name
</Typography>
<div className={classes.sortDiv}>
<IconButton
aria-label="sort name ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: true, ascending: true },
lastRun: { sort: false, ascending: true },
})
}
>
<ExpandLessIcon fontSize="inherit" />
</IconButton>
<IconButton
aria-label="sort name descending"
size="small"
onClick={() =>
setSortData({
...sortData,
name: { sort: true, ascending: false },
lastRun: { sort: false, ascending: false },
})
}
>
<ExpandMoreIcon fontSize="inherit" />
</IconButton>
</div>
</div>
</TableCell>
{/* Target Cluster */}
<TableCell>
<Typography className={classes.targetCluster}>
Target Cluster
</Typography>
</TableCell>
<TableCell>Reliability Details</TableCell>
<TableCell># of experiments</TableCell>
<TableCell>Last Run</TableCell>
{/* Reliability */}
<TableCell className={classes.headData}>
Reliability Details
</TableCell>
{/* No of Experiments */}
<TableCell className={classes.headData}>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Typography style={{ paddingTop: 10 }}>
# of Steps
</Typography>
<div className={classes.sortDiv}>
<IconButton
aria-label="sort no of steps ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
noOfSteps: { sort: true, ascending: true },
})
}
>
<ExpandLessIcon fontSize="inherit" />
</IconButton>
<IconButton
aria-label="sort no of steps descending"
size="small"
onClick={() =>
setSortData({
...sortData,
noOfSteps: { sort: true, ascending: false },
})
}
>
<ExpandMoreIcon fontSize="inherit" />
</IconButton>
</div>
</div>
</TableCell>
{/* Last Run */}
<TableCell className={classes.headData}>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Typography style={{ paddingTop: 10 }}>Last Run</Typography>
<div className={classes.sortDiv}>
<IconButton
aria-label="sort last run ascending"
size="small"
onClick={() =>
setSortData({
...sortData,
lastRun: { 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,
lastRun: { sort: true, ascending: false },
name: { sort: false, ascending: true },
})
}
>
<ExpandMoreIcon fontSize="inherit" />
</IconButton>
</div>
</div>
</TableCell>
{/* Menu */}
<TableCell />
</TableRow>
</TableHead>
{/* Body */}
<TableBody>
{mainData &&
mainData.getWorkFlowRuns
.slice(0)
.reverse()
.map((data: any) => (
<TableRow
onClick={() =>
history.push({
pathname: '/workflow-underground',
state: data,
})
}
>
<TableData data={data} />
{loading ? (
<TableRow>
<TableCell colSpan={7}>
<Loader />
</TableCell>
</TableRow>
) : error ? (
<TableRow>
<TableCell colSpan={7}>
<Typography align="center">Unable to fetch data</Typography>
</TableCell>
</TableRow>
) : filteredData && filteredData.length ? (
filteredData
.slice(
paginationData.pageNo * paginationData.rowsPerPage,
paginationData.pageNo * paginationData.rowsPerPage +
paginationData.rowsPerPage
)
.map((dataRow) => (
<TableRow key={dataRow.workflow_run_id}>
<TableData data={dataRow} />
</TableRow>
))}
))
) : (
<TableRow>
<TableCell colSpan={7}>
<Typography align="center">No records available</Typography>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
{/* Pagination */}
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={filteredData?.length ?? 0}
rowsPerPage={paginationData.rowsPerPage}
page={paginationData.pageNo}
onChangePage={(_, page) =>
setPaginationData({ ...paginationData, pageNo: page })
}
onChangeRowsPerPage={(event) =>
setPaginationData({
...paginationData,
pageNo: 0,
rowsPerPage: parseInt(event.target.value, 10),
})
}
/>
</section>
</div>
);

View File

@ -35,12 +35,14 @@ const useStyles = makeStyles((theme) => ({
marginTop: theme.spacing(6.25),
border: '1px solid rgba(0,0,0,0.1)',
backgroundColor: theme.palette.common.white,
height: '29.219rem',
},
tableHead: {
opacity: 0.5,
opacity: 1,
},
headerStatus: {
paddingLeft: theme.spacing(10),
color: 'rgba(0, 0, 0, 0.4)',
},
headerStatus1: {
paddingLeft: theme.spacing(8),
@ -56,9 +58,17 @@ const useStyles = makeStyles((theme) => ({
},
workflowName: {
borderRight: '1px solid rgba(0,0,0,0.1)',
color: theme.palette.customColors.black(0.4),
},
workflowNameData: {
borderRight: '1px solid rgba(0,0,0,0.1)',
},
targetCluster: {
paddingLeft: theme.spacing(3.75),
color: theme.palette.customColors.black(0.4),
},
headData: {
color: theme.palette.customColors.black(0.4),
},
clusterName: {
marginLeft: theme.spacing(3.75),
@ -69,9 +79,6 @@ const useStyles = makeStyles((theme) => ({
stepsData: {
paddingLeft: theme.spacing(3.75),
},
timeData: {
paddingTop: theme.spacing(1.25),
},
optionBtn: {
marginLeft: 'auto',
},
@ -79,6 +86,11 @@ const useStyles = makeStyles((theme) => ({
display: 'flex',
flexDirection: 'row',
},
sortDiv: {
display: 'flex',
flexDirection: 'column',
paddingLeft: theme.spacing(1.25),
},
}));
export default useStyles;

View File

@ -0,0 +1,200 @@
import React from 'react';
import {
TableCell,
Typography,
IconButton,
MenuItem,
Menu,
Popover,
Button,
} from '@material-ui/core';
import MoreVertIcon from '@material-ui/icons/MoreVert';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import useStyles from './styles';
import LinearProgressBar from '../../ReturningHome/ProgressBar/LinearProgressBar';
import { history } from '../../../../redux/configureStore';
interface TableDataProps {
data: any;
}
const TableData: React.FC<TableDataProps> = ({ data }) => {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [popAnchorEl, setPopAnchorEl] = React.useState<null | HTMLElement>(
null
);
const open = Boolean(anchorEl);
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
setAnchorEl(event.currentTarget);
};
const isOpen = Boolean(popAnchorEl);
const id = isOpen ? 'simple-popover' : undefined;
const handlePopOverClose = () => {
setPopAnchorEl(null);
};
const handlePopOverClick = (event: React.MouseEvent<HTMLElement>) => {
setPopAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleMenu = () => {};
const formatDate = (date: any) => {
const updated = new Date(date * 1000).toString();
const day = updated.slice(8, 10);
const month = updated.slice(4, 7);
const year = updated.slice(11, 15);
const resDate = `${day} ${month} ${year}`;
return resDate;
};
const exWeight = [
{
name: 'Node add test',
value: 10,
},
{
name: 'Networking pod test',
value: 7,
},
{
name: 'Config map test',
value: 3,
},
{
name: 'Proxy-service-test',
value: 5,
},
];
return (
<>
<TableCell align="center" className={classes.workflowNameData}>
<Typography>
<strong>{data.workflow_name}</strong>
</Typography>
</TableCell>
<TableCell>
<Typography className={classes.clusterStartDate}>
{formatDate(JSON.parse(data.execution_data).startedAt)}
</Typography>
</TableCell>
<TableCell>
<div className={classes.regularityData}>
<div className={classes.expDiv}>
<img src="/icons/calender.svg" alt="Calender" />
<Typography style={{ paddingLeft: 10 }}>Once </Typography>
</div>
</div>
</TableCell>
<TableCell>
<Typography>{data.cluster_name}</Typography>
</TableCell>
<TableCell>
<Button onClick={handlePopOverClick} style={{ textTransform: 'none' }}>
{isOpen ? (
<div className={classes.expDiv}>
<Typography className={classes.expInfo}>
<strong>Show Experiment</strong>
</Typography>
<KeyboardArrowDownIcon className={classes.expInfo} />
</div>
) : (
<div className={classes.expDiv1}>
<Typography>
<strong>Show Experiment</strong>
</Typography>
<ChevronRightIcon />
</div>
)}
</Button>
<Popover
id={id}
open={isOpen}
anchorEl={popAnchorEl}
onClose={handlePopOverClose}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
style={{
marginTop: 10,
}}
>
<div className={classes.weightDiv}>
{exWeight.map((expData) => {
return (
<div style={{ marginBottom: 10 }}>
<div className={classes.weightInfo}>
<Typography>{expData.name}</Typography>
<Typography style={{ marginLeft: 'auto' }}>
{expData.value} points
</Typography>
</div>
<LinearProgressBar value={expData.value} />
</div>
);
})}
</div>
</Popover>
</TableCell>
<TableCell className={classes.menuCell}>
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={handleClick}
className={classes.optionBtn}
>
<MoreVertIcon />
</IconButton>
<Menu
id="long-menu"
anchorEl={anchorEl}
keepMounted
open={open}
onClose={handleClose}
>
<MenuItem
value="Workflow"
onClick={() =>
history.push({
pathname: '/schedule',
state: data,
})
}
>
<div className={classes.expDiv}>
<img
src="/icons/editSchedule.svg"
alt="Edit Schedule"
className={classes.btnImg}
/>
<Typography className={classes.btnText}>Edit Schedule</Typography>
</div>
</MenuItem>
<MenuItem value="Analysis" onClick={handleMenu}>
<div className={classes.expDiv}>
<img
src="/icons/deleteSchedule.svg"
alt="Delete Schedule"
className={classes.btnImg}
/>
<Typography className={classes.btnText}>
Delete Schedule
</Typography>
</div>
</MenuItem>
</Menu>
</TableCell>
</>
);
};
export default TableData;

View File

@ -0,0 +1,204 @@
import React, { useState } from 'react';
import { useQuery } from '@apollo/client';
import SearchIcon from '@material-ui/icons/Search';
import {
InputBase,
InputAdornment,
FormControl,
InputLabel,
Select,
MenuItem,
Typography,
TableContainer,
Table,
TableHead,
TableRow,
TableCell,
TableBody,
TablePagination,
} from '@material-ui/core';
import useStyles from './styles';
import { WORKFLOW_DETAILS } from '../../../../graphql';
import TableData from './TableData';
import { Workflow, WorkflowDataVars } from '../../../../models/workflowData';
import Loader from '../../../Loader';
interface FilterOption {
search: string;
cluster: string;
}
interface PaginationData {
pageNo: number;
rowsPerPage: number;
}
const ScheduleWorkflow = () => {
const classes = useStyles();
// Apollo query to get the scheduled data
const { data, loading, error } = useQuery<Workflow, WorkflowDataVars>(
WORKFLOW_DETAILS,
{ variables: { projectID: '00000' } }
);
// State for search and filtering
const [filter, setFilter] = React.useState<FilterOption>({
search: '',
cluster: 'All',
});
// State for pagination
const [paginationData, setPaginationData] = useState<PaginationData>({
pageNo: 0,
rowsPerPage: 5,
});
const filteredData = data?.getWorkFlowRuns
.filter((dataRow) =>
dataRow.workflow_name.toLowerCase().includes(filter.search)
)
.filter((dataRow) =>
filter.cluster === 'All'
? true
: dataRow.cluster_name.toLowerCase().includes(filter.cluster)
)
.reverse();
return (
<div>
<section className="Heading section">
<div className={classes.headerSection}>
<InputBase
id="input-with-icon-adornment"
placeholder="Search"
className={classes.search}
value={filter.search}
onChange={(event) =>
setFilter({ ...filter, search: event.target.value as string })
}
startAdornment={
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
}
/>
<FormControl className={classes.select}>
<InputLabel id="demo-simple-select-outlined-label">
Target Cluster
</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={filter.cluster}
onChange={(event) =>
setFilter({ ...filter, cluster: event.target.value as string })
}
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>
</div>
</section>
<section className="table section">
<TableContainer className={classes.tableMain}>
<Table stickyHeader aria-label="simple table">
<TableHead>
<TableRow className={classes.tableHead}>
<TableCell className={classes.workflowName}>
<Typography style={{ paddingLeft: 65 }}>
Workflow Name
</Typography>
</TableCell>
<TableCell className={classes.headerStatus}>
Starting Date
</TableCell>
<TableCell>
<Typography className={classes.regularity}>
Regularity
</Typography>
</TableCell>
<TableCell>
<Typography className={classes.targetCluster}>
Cluster
</Typography>
</TableCell>
<TableCell>
<Typography className={classes.showExp}>
Show Experiments
</Typography>
</TableCell>
<TableCell />
</TableRow>
</TableHead>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={7}>
<Loader />
</TableCell>
</TableRow>
) : error ? (
<TableRow>
<TableCell colSpan={7}>
<Typography align="center">Unable to fetch data</Typography>
</TableCell>
</TableRow>
) : filteredData && filteredData.length ? (
filteredData
.slice(
paginationData.pageNo * paginationData.rowsPerPage,
paginationData.pageNo * paginationData.rowsPerPage +
paginationData.rowsPerPage
)
.map((data: any) => (
<TableRow key={data.workflow_run_id}>
<TableData data={data} />
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={7}>
<Typography align="center">No records available</Typography>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={filteredData?.length ?? 0}
rowsPerPage={paginationData.rowsPerPage}
page={paginationData.pageNo}
onChangePage={(_, page) =>
setPaginationData({ ...paginationData, pageNo: page })
}
onChangeRowsPerPage={(event) => {
setPaginationData({
...paginationData,
pageNo: 0,
rowsPerPage: parseInt(event.target.value, 10),
});
}}
/>
</section>
</div>
);
};
export default ScheduleWorkflow;

View File

@ -0,0 +1,128 @@
import { makeStyles } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({
headerSection: {
width: '100%',
display: 'flex',
flexDirection: 'row',
alignContent: 'center',
alignItems: 'center',
justifyContent: 'center',
border: '1px solid ',
borderColor: theme.palette.text.hint,
backgroundColor: theme.palette.common.white,
},
search: {
marginRight: 'auto',
marginLeft: theme.spacing(6.25),
},
select: {
width: '10.375rem',
marginLeft: theme.spacing(1.25),
paddingBottom: theme.spacing(2.5),
marginRight: theme.spacing(3.75),
},
headerText: {
marginLeft: theme.spacing(3.75),
color: theme.palette.text.disabled,
paddingBottom: theme.spacing(0.625),
},
tableMain: {
marginTop: theme.spacing(6.25),
border: '1px solid rgba(0,0,0,0.1)',
backgroundColor: theme.palette.common.white,
height: '29.180rem',
},
tableHead: {
opacity: 1,
height: '4.6875rem',
},
headerStatus: {
paddingLeft: theme.spacing(10),
color: theme.palette.customColors.black(0.4),
},
headerStatus1: {
paddingLeft: theme.spacing(8),
},
progressBar: {
width: '6.5rem',
},
steps: {
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: {
borderRight: '1px solid rgba(0,0,0,0.1)',
},
regularity: {
paddingLeft: theme.spacing(3.75),
color: theme.palette.customColors.black(0.4),
},
targetCluster: {
color: theme.palette.customColors.black(0.4),
},
clusterStartDate: {
paddingLeft: theme.spacing(8),
},
regularityData: {
maxWidth: '12.5rem',
paddingLeft: theme.spacing(4),
},
stepsData: {
paddingLeft: theme.spacing(3.75),
},
expInfo: {
color: theme.palette.primary.dark,
},
showExp: {
paddingLeft: theme.spacing(1),
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: {
paddingTop: theme.spacing(1.25),
},
optionBtn: {
marginLeft: theme.spacing(-6.25),
},
menuCell: {
width: '3.125rem',
},
timeDiv: {
display: 'flex',
flexDirection: 'row',
},
btnImg: {
width: '0.8125rem',
height: '0.8125rem',
marginTop: theme.spacing(0.375),
},
btnText: {
paddingLeft: theme.spacing(1.625),
},
}));
export default useStyles;

View File

@ -5,9 +5,9 @@ import Tabs from '@material-ui/core/Tabs';
import React from 'react';
import { history } from '../../../../redux/configureStore';
import ButtonFilled from '../../../Button/ButtonFilled';
import Loader from '../../../Loader';
import BrowseWorkflow from '../BrowseWorkflow';
import useStyles from './styles';
import ScheduleWorkflow from '../ScheduleWorkflow';
import Templates from '../Templates';
interface TabPanelProps {
@ -86,7 +86,7 @@ const CenteredTabs = () => {
<BrowseWorkflow />
</TabPanel>
<TabPanel value={value} index={1}>
<Loader />
<ScheduleWorkflow />
</TabPanel>
<TabPanel value={value} index={2}>
<Templates />

View File

@ -11,7 +11,7 @@ import { useSelector } from 'react-redux';
import ButtonFilled from '../../../Button/ButtonFilled';
import ButtonOutLine from '../../../Button/ButtonOutline';
import useStyles from './styles';
import { GET_CLUSTER } from '../../../../schemas';
import { GET_CLUSTER } from '../../../../graphql';
import { RootState } from '../../../../redux/reducers';
import { UserData } from '../../../../models/user';
import useActions from '../../../../redux/actions';

View File

@ -5,8 +5,8 @@ import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
import ButtonFilled from '../Button/ButtonFilled';
import config from '../../config';
import { CREATE_USER } from '../../graphql';
import { RootState } from '../../redux/reducers';
import { CREATE_USER } from '../../schemas';
import InputField from '../InputField';
import ModalPage from './Modalpage';
import useStyles from './styles';

View File

@ -23,7 +23,7 @@ import { RootState } from '../../redux/reducers';
import useActions from '../../redux/actions';
import * as WorkflowActions from '../../redux/actions/workflow';
import parsed from '../../utils/yamlUtils';
import { CREATE_WORKFLOW } from '../../schemas';
import { CREATE_WORKFLOW } from '../../graphql';
import Unimodal from '../../containers/layouts/Unimodal';
import { history } from '../../redux/configureStore';

View File

@ -23,7 +23,7 @@ const BrowseTemplate = lazy(() =>
const HomePage = lazy(() => import('../../pages/HomePage'));
const Community = lazy(() => import('../../pages/Community'));
const Settings = lazy(() => import('../../pages/Settings'));
const SchedulePage = lazy(() => import('../../pages/SchedulePage'));
interface RoutesProps {
userData: string;
}
@ -49,6 +49,7 @@ const Routes: React.FC<RoutesProps> = ({ userData }) => {
<Route exact path="/login" component={LoginPage} />
<Route exact path="/workflows" component={Workflows} />
<Route exact path="/create-workflow" component={CreateWorkflow} />
<Route exact path="/schedule" component={SchedulePage} />
<Route
exact
path="/workflow-underground"

View File

@ -1,8 +1,8 @@
import { gql } from '@apollo/client';
export const WORKFLOW_DETAILS = gql`
query {
getWorkFlowRuns(project_id: "00002") {
query workflowDetails($projectID: String!) {
getWorkFlowRuns(project_id: $projectID) {
workflow_id
workflow_name
workflow_run_id
@ -15,8 +15,8 @@ export const WORKFLOW_DETAILS = gql`
`;
export const WORKFLOW_EVENTS = gql`
subscription {
workflowEventListener(project_id: "00002") {
subscription workflowEvents($projectID: String!) {
workflowEventListener(project_id: $projectID) {
workflow_id
workflow_name
workflow_run_id

View File

@ -0,0 +1,42 @@
export interface Node {
children: string[] | null;
finishedAt: string;
name: string;
phase: string;
startedAt: string;
type: string;
}
export interface ExecutionData {
event_type: string;
uid: string;
namespace: string;
name: string;
creationTimestamp: string;
phase: string;
startedAt: string;
finishedAt: string;
nodes: object;
}
export interface WorkflowRun {
cluster_name: string;
execution_data: string;
last_updated: string;
project_id: string;
workflow_id: string;
workflow_name: string;
workflow_run_id: string;
}
export interface Workflow {
getWorkFlowRuns: WorkflowRun[];
}
export interface WorkflowSubscription {
workflowEventListener: WorkflowRun;
}
export interface WorkflowDataVars {
projectID: string;
}

View File

@ -8,10 +8,10 @@ import InfoFilledWrap from '../../components/InfoFilled';
import QuickActionCard from '../../components/QuickActionCard';
import WelcomeModal from '../../components/WelcomeModal';
import Scaffold from '../../containers/layouts/Scaffold';
import { GET_PROJECT, GET_USER } from '../../graphql';
import useActions from '../../redux/actions';
import * as UserActions from '../../redux/actions/user';
import { RootState } from '../../redux/reducers';
import { GET_PROJECT, GET_USER } from '../../schemas';
import useStyles from './style';
const CreateWorkflowCard = () => {

View File

@ -0,0 +1,102 @@
import {
Button,
Fade,
FormControl,
Menu,
MenuItem,
TextField,
} from '@material-ui/core';
import React from 'react';
import useStyles from './styles';
interface SetTimeProps {
start: number;
end: number;
interval: number;
label: string;
type: string;
}
// dropdown menu component for setting time
const SetTime: React.FC<SetTimeProps> = ({
start,
end,
interval,
label,
type,
}) => {
const classes = useStyles();
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [age, setAge] = React.useState(0);
const open = Boolean(anchorEl);
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
setAge((event.target.value as unknown) as number);
setAnchorEl(null);
};
const handleClose = () => {
setAnchorEl(null);
};
const size = (end - start) / interval;
const names: number[] = [start];
for (let i = 1; i <= size; i += 1) {
names[i] = names[i - 1] + interval;
}
return (
<div>
<FormControl>
<TextField
className={classes.textField}
id="outlined-basic"
variant="outlined"
value={age}
onChange={handleChange}
color="secondary"
inputProps={{
style: {
fontSize: '0.75rem',
color: '#000000',
paddingLeft: 27,
height: '0.425rem',
},
'aria-label': 'change-time',
}}
/>
</FormControl>
<Button
className={classes.button}
onClick={(event) => {
setAnchorEl(event.currentTarget);
}}
endIcon={<img src="./icons/down-arrow.svg" alt="arrow" />}
disableRipple
disableFocusRipple
>
{label}
</Button>
<Menu
id="fade-menu"
anchorEl={anchorEl}
open={open}
onClose={handleClose}
TransitionComponent={Fade}
>
{names.map((name) => (
<MenuItem
key={name}
value={name}
onClick={() => {
setAge(name);
setAnchorEl(null);
}}
>
{name} {type}
</MenuItem>
))}
</Menu>
</div>
);
};
export default SetTime;

View File

@ -0,0 +1,20 @@
import { makeStyles, Theme } from '@material-ui/core/styles';
const useStyles = makeStyles((theme: Theme) => ({
button: {
fontSize: '0.75rem',
'&:hover': {
focusVisible: 'none',
background: theme.palette.secondary.contrastText,
},
marginTop: theme.spacing(0.7),
textTransform: 'none',
fontWeight: 'normal',
},
textField: {
width: '4.4375rem',
height: '2.75rem',
marginLeft: theme.spacing(1.875),
},
}));
export default useStyles;

View File

@ -0,0 +1,373 @@
import {
Divider,
FormControl,
FormControlLabel,
Radio,
RadioGroup,
Select,
Typography,
} from '@material-ui/core';
import React from 'react';
import CustomDate from '../../components/DateTime/CustomDate/index';
import CustomTime from '../../components/DateTime/CustomTime/index';
import SetTime from './SetTime/index';
import useStyles from './styles';
import Scaffold from '../../containers/layouts/Scaffold';
import ButtonFilled from '../../components/Button/ButtonFilled';
import ButtonOutline from '../../components/Button/ButtonOutline';
// To be changed to a Location Generic
interface WorkflowScheduleProps {
location: any;
}
const SchedulePage: React.FC<WorkflowScheduleProps> = () => {
const start = 0;
const end = 10;
const interval = 2;
const classes = useStyles();
// controls radio buttons
const [value, setValue] = React.useState('now');
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value);
};
// controls inner radio buttons of recurring schedule
const [valueDef, setValueDef] = React.useState('');
const handleChangeInstance = (event: React.ChangeEvent<HTMLInputElement>) => {
setValueDef(event.target.value);
};
// sets weekdays
const [days, setDays] = React.useState('Monday');
// sets dates
const [dates, setDates] = React.useState(1);
// stores dates in an array
const names: number[] = [1];
for (let i = 1; i <= 30; i += 1) {
names[i] = i + 1;
}
const weekdays: string[] = [
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
'Sunday',
];
return (
<Scaffold>
<div className={classes.rootContainer}>
<Typography className={classes.mainHeader}>
Schedule a workflow
</Typography>
<Typography className={classes.headerDesc}>
Click on test to see detailed log of your workflow
</Typography>
<div className={classes.root}>
<div className={classes.scHeader}>
{/* Upper segment */}
<div className={classes.scSegments}>
<div>
<Typography className={classes.headerText}>
<strong>Schedule details:</strong>
</Typography>
<div className={classes.schBody}>
<Typography align="left" className={classes.description}>
Choose the right time to first workflow. Below you can find
any option convenient for you.
</Typography>
</div>
</div>
<img
src="./icons/calendar.svg"
alt="calendar"
className={classes.calIcon}
/>
</div>
<Divider />
{/* Lower segment */}
<div className={classes.scFormControl}>
<FormControl component="fieldset" className={classes.formControl}>
<RadioGroup
aria-label="schedule"
name="schedule"
value={value}
onChange={handleChange}
>
{/* options to choose schedule */}
<FormControlLabel
value="now"
control={<Radio />}
label={
<Typography className={classes.radioText}>
Schedule now
</Typography>
}
/>
<FormControlLabel
value="afterSometime"
control={<Radio />}
label={
<Typography className={classes.radioText}>
Schedule after some time
</Typography>
}
/>
{value === 'afterSometime' ? (
<div className={classes.schLater}>
<Typography className={classes.captionText}>
Choose the minutes, hours, or days when you want to
start workflow
</Typography>
<div className={classes.wtDateTime}>
<Typography
variant="body2"
className={classes.captionText}
>
After
</Typography>
<SetTime
start={start}
end={end}
interval={interval}
label="Days"
type="days"
/>
<CustomTime ampm disabled={false} />
</div>
</div>
) : (
<></>
)}
<FormControlLabel
value="specificTime"
control={<Radio />}
label={
<Typography className={classes.radioText}>
Schedule at a specific time
</Typography>
}
/>
{value === 'specificTime' ? (
<div className={classes.schLater}>
<Typography className={classes.captionText}>
Select date and time to start workflow in future
</Typography>
<div className={classes.innerSpecific}>
<CustomDate disabled={false} />
<CustomTime ampm disabled={false} />
</div>
</div>
) : (
<></>
)}
<FormControlLabel
value="recurringScedule"
control={<Radio />}
label={
<Typography className={classes.radioText}>
Recurring Schedule
</Typography>
}
/>
{value === 'recurringScedule' ? (
<div className={classes.schLater}>
<Typography className={classes.captionText}>
Choose the right recurring time to start your workflow
</Typography>
{/* options to select time of recurring schedule */}
<div className={classes.innerRecurring}>
<FormControl component="fieldset">
<RadioGroup
aria-label="instanceDef"
name="instanceDef"
value={valueDef}
onChange={handleChangeInstance}
>
<FormControlLabel
value="everyHr"
control={<Radio />}
label="Every Hour"
/>
{valueDef === 'everyHr' ? (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
At
</Typography>
<SetTime
start={start}
end={end}
interval={interval}
label="th"
type=""
/>
</div>
</div>
) : (
<></>
)}
<FormControlLabel
value="everyDay"
control={<Radio />}
label="Every Day "
/>
{valueDef === 'everyDay' ? (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
At
</Typography>
<CustomTime ampm disabled={false} />
</div>
</div>
) : (
<></>
)}
<FormControlLabel
value="everyWeek"
control={<Radio />}
label="Every Week "
/>
{valueDef === 'everyWeek' ? (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
On
</Typography>
<FormControl
className={classes.formControlDT}
>
<Select
className={classes.select}
disableUnderline
value={days}
onChange={(e) => {
setDays(
(e.target.value as unknown) as string
);
}}
label="days"
inputProps={{
name: 'days',
id: 'outlined-age-native-simple',
style: {
fontSize: '0.75rem',
height: 7,
},
}}
>
{weekdays.map((day) => (
<option
className={classes.opt}
value={day}
>
{day}
</option>
))}
</Select>
</FormControl>
<Typography className={classes.scRandsub1}>
at
</Typography>
<CustomTime ampm disabled={false} />
</div>
</div>
) : (
<></>
)}
<FormControlLabel
value="everyMonth"
control={<Radio />}
label="Every Month"
/>
{valueDef === 'everyMonth' ? (
<div>
<div className={classes.scRandom}>
<Typography className={classes.scRandsub1}>
On
</Typography>
<FormControl
className={classes.formControlMonth}
>
<Select
className={classes.select}
disableUnderline
value={dates}
onChange={(e) => {
setDates(
(e.target.value as unknown) as number
);
}}
label="dates"
inputProps={{
name: 'dates',
id: 'outlined-age-native-simple',
style: {
fontSize: '0.75rem',
height: 7,
},
}}
>
{names.map((date) => (
<option
className={classes.opt}
value={date}
>
{date}
</option>
))}
</Select>
</FormControl>
<Typography className={classes.scRandsub1}>
at
</Typography>
<CustomTime ampm disabled={false} />
</div>
</div>
) : (
<></>
)}
</RadioGroup>
</FormControl>
</div>
</div>
) : (
<></>
)}
</RadioGroup>
</FormControl>
</div>
<Divider />
<div className={classes.submitDiv}>
<ButtonOutline isDisabled={false} handleClick={() => {}}>
<Typography>Cancel</Typography>
</ButtonOutline>
<div style={{ marginLeft: 'auto' }}>
<ButtonFilled isPrimary handleClick={() => {}}>
<Typography>Save Changes</Typography>
</ButtonFilled>
</div>
</div>
</div>
</div>
</div>
</Scaffold>
);
};
export default SchedulePage;

View File

@ -0,0 +1,166 @@
import { makeStyles, Theme } from '@material-ui/core/styles';
const useStyles = makeStyles((theme: Theme) => ({
rootContainer: {
paddingTop: 50,
paddingLeft: 30,
},
root: {
backgroundColor: 'rgba(255, 255, 255, 0.6)',
width: '90%',
marginTop: 30,
border: 1,
borderColor: theme.palette.text.disabled,
borderRadius: '0.1875rem',
},
scHeader: {
paddingLeft: theme.spacing(3.75),
paddingRight: theme.spacing(3.75),
paddingTop: theme.spacing(3.75),
paddingBottom: theme.spacing(3.75),
},
mainHeader: {
marginTop: theme.spacing(1.25),
fontSize: '36px',
},
headerDesc: {
fontSize: '16px',
},
/* styles for upper and lower segment */
scSegments: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-even',
},
headerText: {
marginTop: theme.spacing(1.25),
fontSize: '1.5625rem',
},
schBody: {
width: '32.18rem',
},
captionText: {
fontSize: '0.75rem',
color: theme.palette.text.disabled,
},
schLater: {
marginLeft: theme.spacing(3.75),
},
radioText: {
fontSize: '0.875rem',
},
description: {
width: '32.18rem',
marginTop: theme.spacing(3.25),
marginBottom: theme.spacing(7.5),
fontSize: '1rem',
},
calIcon: {
width: '7rem',
height: '6.31rem',
marginTop: theme.spacing(5),
marginLeft: 'auto',
},
scFormControl: {
marginTop: theme.spacing(5),
},
/* For recurring schedule options */
scRandom: {
display: 'flex',
flexDirection: 'row',
marginLeft: theme.spacing(1.625),
marginBottom: theme.spacing(4.125),
height: '2.75rem',
alignItems: 'center',
},
formControl: {
margin: theme.spacing(1),
},
/* for option- after sometime */
wtDateTime: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
width: '19.2rem',
height: '2.75rem',
marginTop: theme.spacing(2.125),
marginBottom: theme.spacing(4.125),
},
/* for option- at specific time */
innerSpecific: {
marginTop: theme.spacing(2.125),
marginBottom: theme.spacing(4.125),
display: 'flex',
flexDirection: 'row',
alignItems: 'stretch',
},
innerRecurring: {
marginTop: theme.spacing(2.125),
},
/* for each options of recurring schedule */
scRandsub1: {
margin: theme.spacing(1.875),
fontSize: '0.75rem',
color: theme.palette.text.disabled,
},
/* for selecting weekdays */
formControlDT: {
margin: theme.spacing(1),
minWidth: '6.6025rem',
minHeight: '2.75rem',
'&:select': {
focusVisible: 'none',
background: theme.palette.secondary.contrastText,
},
},
/* for selecting date of every month */
formControlMonth: {
margin: theme.spacing(1),
minWidth: '5.3125rem',
minHeight: '2.75rem',
},
/* for each select */
select: {
padding: theme.spacing(2.5),
border: '1px solid #D1D2D7',
borderRadius: '0.1875rem',
fontSize: '0.75rem',
height: '2.75rem',
},
/* for each option */
opt: {
marginBottom: theme.spacing(1),
marginLeft: theme.spacing(0.2),
marginRight: theme.spacing(0.2),
paddingLeft: theme.spacing(1),
borderRadius: '0.0625rem',
'&:hover': {
background: '#D1D2D7',
},
},
/* style for submit section */
submitDiv: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-even',
marginTop: theme.spacing(6.25),
},
}));
export default useStyles;

View File

@ -1,13 +1,11 @@
/* eslint-disable camelcase */
import React, { useState, useEffect } from 'react';
import { useSubscription } from '@apollo/client';
import { Typography } from '@material-ui/core';
import Scaffold from '../../containers/layouts/Scaffold';
import { WORKFLOW_EVENTS } from '../../schemas';
import SideBar from '../../components/Sections/WorkflowUnderground/WorkflowRepresentation';
import useStyles from './styles';
import Loader from '../../components/Loader';
import { WORKFLOW_EVENTS } from '../../graphql';
interface WorkflowUndergroundProps {
location: any;

View File

@ -71,13 +71,13 @@ function customTheme(options: ThemeOptions) {
white: (opacity: number): string => {
let op = opacity;
if (op < 0) op = 0;
if (op < 100) op = 100;
if (op > 100) op = 100;
return `rgba(255, 255, 255, ${op})`;
},
black: (opacity: number): string => {
let op = opacity;
if (op < 0) op = 0;
if (op < 100) op = 100;
if (op > 100) op = 100;
return `rgba(0, 0, 0, ${op})`;
},
},

View File

@ -0,0 +1,13 @@
const sortAlphaAsc = (a: string, b: string): number =>
a === b ? 0 : a < b ? -1 : 1;
const sortAlphaDesc = (a: string, b: string): number =>
a === b ? 0 : a > b ? -1 : 1;
const sortNumAsc = (a: number, b: number): number =>
a === b ? 0 : a < b ? -1 : 1;
const sortNumDesc = (a: number, b: number): number =>
a === b ? 0 : a > b ? -1 : 1;
export { sortAlphaAsc, sortAlphaDesc, sortNumAsc, sortNumDesc };