Polishing UI, Adding Delete Schedule Modal + Disable Back button on Editing a Schedule + Minor UI Bug Fixes (#2398)
* Adding Edit Schedule Feature * Polishing UI, Adding Delete Schedule Modal + Disable Back button on edit schedule * Minor UI bug fixes Signed-off-by: Sayan Mondal <sayan.mondal@mayadata.io>
This commit is contained in:
parent
ae9d1062d5
commit
7137a69fc7
|
@ -52,6 +52,13 @@ customWorkflowCard:
|
|||
|
||||
workflowStepper:
|
||||
continueError: To continue, please check the error in code.
|
||||
aNewChaosWorkflow: A new chaos workflow,
|
||||
successful: is successfully created!
|
||||
congratulationsSub1: Congratulations on creating your workflow! Now information about
|
||||
congratulationsSub2: it will be displayed on the workflow schedules.
|
||||
workflowBtn: Go to Workflow
|
||||
finish: Finish
|
||||
next: Next
|
||||
|
||||
######################################
|
||||
############ Pages #############
|
||||
|
@ -103,6 +110,15 @@ schedule:
|
|||
at: at
|
||||
At: At
|
||||
scheduleOn: 'On'
|
||||
missingPerm: Missing sufficient permissions :(
|
||||
requiredPerm: Looks like you do not have the required permission to create a new workflow on this project.
|
||||
contact: Contact portal administrator to upgrade your permission.
|
||||
chaosWorkflow: Chaos workflow,
|
||||
successful: was successfully updated!
|
||||
congratulationsSub1: Chaos workflow updated successfully! Now information about
|
||||
congratulationsSub2: it will be displayed on the workflow schedules.
|
||||
workflowBtn: Go to Workflow
|
||||
backBtn: Go Back
|
||||
|
||||
workflowUnderground:
|
||||
heading: 'Click on test to see detailed log of your workflow'
|
||||
|
@ -377,7 +393,7 @@ createWorkflow:
|
|||
reliabilityScore:
|
||||
header: Adjust the weights of the experiments in the workflow
|
||||
info: You have selected
|
||||
infoNext: tests in the “Kubernetes conformance test” workflow. Successful outcome of each test carries a certain weight. We have pre-selected weights for each test for you. However, you may review and modify the weigtage against.
|
||||
infoNext: tests in the “Kubernetes conformance test” workflow. Successful outcome of each test carries a certain weight. We have pre-selected weights for each test for you. However, you may review and modify the weightage against.
|
||||
infoNextStrong: The weights are relative to each other.
|
||||
testHeading: Kubernetes conformance test
|
||||
testInfo: Compare the importance of the items above and launch a demo version of Kubernetes conformance test to see how it works.
|
||||
|
@ -415,6 +431,10 @@ createWorkflow:
|
|||
future: Select date and time to start workflow in future
|
||||
recurr: Recurring Schedule
|
||||
rightRecurr: Choose the right recurring time to start your workflow
|
||||
modalHeader: Are you sure you want to delete this Schedule
|
||||
modalSubheader: This action is irreversible
|
||||
cancelBtn: Cancel
|
||||
deleteBtn: Delete
|
||||
toggleComponent:
|
||||
pass: Pass
|
||||
fail: Fail
|
||||
|
|
|
@ -8,6 +8,7 @@ interface ButtonFilledProps {
|
|||
) => void;
|
||||
isPrimary: boolean;
|
||||
isDisabled?: boolean;
|
||||
isWarning?: boolean;
|
||||
styles?: Object;
|
||||
type?: any;
|
||||
}
|
||||
|
@ -16,6 +17,7 @@ const ButtonFilled: React.FC<ButtonFilledProps> = ({
|
|||
children,
|
||||
isPrimary,
|
||||
isDisabled,
|
||||
isWarning,
|
||||
styles,
|
||||
type,
|
||||
}) => {
|
||||
|
@ -29,7 +31,9 @@ const ButtonFilled: React.FC<ButtonFilledProps> = ({
|
|||
type={type}
|
||||
onClick={handleClick}
|
||||
className={
|
||||
isPrimary
|
||||
isWarning
|
||||
? `${classes.button} ${classes.buttonWarning}`
|
||||
: isPrimary
|
||||
? `${classes.button} ${classes.buttonPrimary}`
|
||||
: `${classes.button} ${classes.buttonSecondary}`
|
||||
}
|
||||
|
|
|
@ -20,6 +20,12 @@ const useStyles = makeStyles((theme) => ({
|
|||
backgroundColor: theme.palette.primary.dark,
|
||||
},
|
||||
},
|
||||
buttonWarning: {
|
||||
backgroundColor: theme.palette.error.dark,
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.error.dark,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
|
@ -7,6 +7,7 @@ interface CustomTextProps {
|
|||
value: string;
|
||||
id: string;
|
||||
onchange: (val: string) => void;
|
||||
isEditable?: boolean;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
|
@ -23,7 +24,12 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
}));
|
||||
|
||||
// Editable text field used to edit and save the input in the text box
|
||||
const CustomText: React.FC<CustomTextProps> = ({ value, id, onchange }) => {
|
||||
const CustomText: React.FC<CustomTextProps> = ({
|
||||
value,
|
||||
id,
|
||||
onchange,
|
||||
isEditable,
|
||||
}) => {
|
||||
const [isDisabled, setIsDisabled] = React.useState(true);
|
||||
const [newValue, setNewValue] = React.useState<string>(value);
|
||||
|
||||
|
@ -58,6 +64,8 @@ const CustomText: React.FC<CustomTextProps> = ({ value, id, onchange }) => {
|
|||
}}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
{isEditable ? (
|
||||
<>
|
||||
{isDisabled ? (
|
||||
<IconButton size="medium" onClick={handleEdit}>
|
||||
<EditIcon className={classes.editBtn} data-cy="edit" />
|
||||
|
@ -67,6 +75,8 @@ const CustomText: React.FC<CustomTextProps> = ({ value, id, onchange }) => {
|
|||
<SaveIcon className={classes.saveBtn} data-cy="save" />
|
||||
</IconButton>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
import { useLazyQuery, useMutation } from '@apollo/client';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useMutation, useLazyQuery } from '@apollo/client';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { history } from '../../../redux/configureStore';
|
||||
import ButtonOutline from '../../Button/ButtonOutline';
|
||||
import TargetCopy from '../TargetCopy';
|
||||
import useStyles from './styles';
|
||||
import Scaffold from '../../../containers/layouts/Scaffold';
|
||||
import Unimodal from '../../../containers/layouts/Unimodal';
|
||||
import { GET_CLUSTER, USER_CLUSTER_REG } from '../../../graphql';
|
||||
import {
|
||||
Cluster,
|
||||
CreateClusterInput,
|
||||
CreateClusterInputResponse,
|
||||
Cluster,
|
||||
} from '../../../models/graphql/clusterData';
|
||||
import { USER_CLUSTER_REG, GET_CLUSTER } from '../../../graphql';
|
||||
import { history } from '../../../redux/configureStore';
|
||||
import { RootState } from '../../../redux/reducers';
|
||||
import Loader from '../../Loader';
|
||||
import ButtonFilled from '../../Button/ButtonFilled';
|
||||
import Unimodal from '../../../containers/layouts/Unimodal';
|
||||
import BackButton from '../../Button/BackButton';
|
||||
import ButtonFilled from '../../Button/ButtonFilled';
|
||||
import ButtonOutline from '../../Button/ButtonOutline';
|
||||
import Loader from '../../Loader';
|
||||
import TargetCopy from '../TargetCopy';
|
||||
import useStyles from './styles';
|
||||
|
||||
const ConnectTarget = () => {
|
||||
const classes = useStyles();
|
||||
|
@ -145,7 +145,7 @@ const ConnectTarget = () => {
|
|||
hasCloseBtn
|
||||
>
|
||||
<div className={classes.body}>
|
||||
<img src="icons/finish.svg" className={classes.mark} alt="mark" />
|
||||
<img src="/icons/finish.svg" className={classes.mark} alt="mark" />
|
||||
<Typography className={classes.heading}>
|
||||
{t('ConnectTargets.title')}
|
||||
<br />
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
||||
import { Tooltip, Zoom } from '@material-ui/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { preDefinedWorkflowData } from '../../models/predefinedWorkflow';
|
||||
import { RootState } from '../../redux/reducers';
|
||||
import trimString from '../../utils/trim';
|
||||
import parsed from '../../utils/yamlUtils';
|
||||
import useStyles from './styles';
|
||||
|
||||
|
@ -104,11 +106,27 @@ const CardContent: React.FC<preDefinedWorkflowData> = ({
|
|||
<div className={classes.provider}>Contributed by {provider}</div>
|
||||
</div>
|
||||
{description ? (
|
||||
<div className={classes.description}>{description}</div>
|
||||
description.length > 30 ? (
|
||||
<Tooltip
|
||||
disableFocusListener
|
||||
disableTouchListener
|
||||
title={description}
|
||||
placement="left"
|
||||
TransitionComponent={Zoom}
|
||||
className={classes.tooltip}
|
||||
>
|
||||
<div className={classes.description}>
|
||||
{trimString(description, 31)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div className={classes.description}>
|
||||
{trimString(description, 31)}
|
||||
</div>
|
||||
)
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{description ? description.length < 28 ? <br /> : <div /> : <span />}
|
||||
</div>
|
||||
{/* <Divider variant="fullWidth" className={classes.horizontalLine} />
|
||||
<div className={classes.details}>
|
||||
|
|
|
@ -5,7 +5,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
|
||||
customCard: {
|
||||
background: theme.palette.cards.background,
|
||||
height: '15.625rem',
|
||||
height: '16rem',
|
||||
width: '11.875rem',
|
||||
borderRadius: 3,
|
||||
fontSize: '0.875rem',
|
||||
|
@ -23,12 +23,20 @@ const useStyles = makeStyles((theme) => ({
|
|||
width: '70%',
|
||||
},
|
||||
|
||||
// Tooltip
|
||||
tooltip: {
|
||||
'.MuiTooltip-tooltip': {
|
||||
maxWidth: '18.75rem',
|
||||
},
|
||||
},
|
||||
|
||||
// CardContent
|
||||
|
||||
card: {
|
||||
width: theme.spacing(23),
|
||||
background: theme.palette.background.paper,
|
||||
borderRadius: 3,
|
||||
height: '16rem',
|
||||
overflow: 'hidden',
|
||||
fontSize: 14,
|
||||
margin: theme.spacing(1),
|
||||
|
@ -77,6 +85,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
// CARD CONTENT
|
||||
cardContent: {
|
||||
color: theme.palette.text.primary,
|
||||
height: '16rem',
|
||||
},
|
||||
|
||||
title: {
|
||||
|
|
|
@ -18,10 +18,11 @@ import {
|
|||
import { experimentMap, WorkflowData } from '../../models/redux/workflow';
|
||||
import useActions from '../../redux/actions';
|
||||
import * as TabActions from '../../redux/actions/tabs';
|
||||
import * as WorkflowActions from '../../redux/actions/workflow';
|
||||
import * as TemplateSelectionActions from '../../redux/actions/template';
|
||||
import * as WorkflowActions from '../../redux/actions/workflow';
|
||||
import { history } from '../../redux/configureStore';
|
||||
import { RootState } from '../../redux/reducers';
|
||||
import { cronWorkflow, workflowOnce } from '../../utils/workflowTemplate';
|
||||
import parsed from '../../utils/yamlUtils';
|
||||
import ChooseWorkflow from '../../views/CreateWorkflow/ChooseWorkflow/index';
|
||||
import ReliablityScore from '../../views/CreateWorkflow/ReliabilityScore';
|
||||
|
@ -34,7 +35,6 @@ import ButtonOutline from '../Button/ButtonOutline';
|
|||
import QontoConnector from './quontoConnector';
|
||||
import useStyles from './styles';
|
||||
import useQontoStepIconStyles from './useQontoStepIconStyles';
|
||||
import { cronWorkflow, workflowOnce } from '../../utils/workflowTemplate';
|
||||
|
||||
function getSteps(): string[] {
|
||||
return [
|
||||
|
@ -98,7 +98,7 @@ function getStepContent(
|
|||
<ChooseAWorkflowCluster gotoStep={(page: number) => gotoStep(page)} />
|
||||
);
|
||||
case 1:
|
||||
return <ChooseWorkflow />;
|
||||
return <ChooseWorkflow isEditable />;
|
||||
case 2:
|
||||
return <TuneWorkflow />;
|
||||
case 3:
|
||||
|
@ -106,7 +106,9 @@ function getStepContent(
|
|||
case 4:
|
||||
return <ScheduleWorkflow />;
|
||||
case 5:
|
||||
return <VerifyCommit gotoStep={(page: number) => gotoStep(page)} />;
|
||||
return (
|
||||
<VerifyCommit isEditable gotoStep={(page: number) => gotoStep(page)} />
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<ChooseAWorkflowCluster gotoStep={(page: number) => gotoStep(page)} />
|
||||
|
@ -390,19 +392,20 @@ const CustomStepper = () => {
|
|||
>
|
||||
<div>
|
||||
<img
|
||||
src="icons/finish.svg"
|
||||
src="/icons/finish.svg"
|
||||
className={classes.mark}
|
||||
alt="mark"
|
||||
/>
|
||||
<div className={classes.heading}>
|
||||
A new chaos workflow,
|
||||
{t('workflowStepper.aNewChaosWorkflow')}
|
||||
<br />
|
||||
<strong>was successfully created!</strong>
|
||||
<span className={classes.successful}>{name}</span>,
|
||||
<br />
|
||||
<strong>{t('workflowStepper.successful')}</strong>
|
||||
</div>
|
||||
<div className={classes.headWorkflow}>
|
||||
Congratulations on creating your first workflow! Now
|
||||
information about <br /> it will be displayed on the main
|
||||
screen of the application.
|
||||
{t('workflowStepper.congratulationsSub1')} <br />{' '}
|
||||
{t('workflowStepper.congratulationsSub2')}
|
||||
</div>
|
||||
<div className={classes.button}>
|
||||
<ButtonFilled
|
||||
|
@ -414,7 +417,7 @@ const CustomStepper = () => {
|
|||
history.push('/workflows');
|
||||
}}
|
||||
>
|
||||
<div>Back to workflow</div>
|
||||
<div>{t('workflowStepper.workflowBtn')}</div>
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -64,6 +64,10 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
textAlign: 'center',
|
||||
marginTop: theme.spacing(6),
|
||||
},
|
||||
successful: {
|
||||
fontSize: '2.2rem',
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
|
@ -112,7 +112,7 @@ const Routes: React.FC<RoutesProps> = ({ isOwner, isProjectAvailable }) => {
|
|||
/>
|
||||
<Route
|
||||
exact
|
||||
path="/workflows/schedule/:scheduleId"
|
||||
path="/workflows/schedule/:projectID/:workflowName"
|
||||
component={SchedulePage}
|
||||
/>
|
||||
<Route
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export * from './mutations';
|
||||
export * from './quries';
|
||||
export * from './queries';
|
||||
export * from './subscriptions';
|
||||
|
|
|
@ -59,6 +59,18 @@ export const DELETE_SCHEDULE = gql`
|
|||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_SCHEDULE = gql`
|
||||
mutation updateChaos($ChaosWorkFlowInput: ChaosWorkFlowInput!) {
|
||||
updateChaosWorkflow(input: $ChaosWorkFlowInput) {
|
||||
workflow_id
|
||||
workflow_name
|
||||
workflow_description
|
||||
isCustomWorkflow
|
||||
cronSyntax
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const UPDATE_DETAILS = gql`
|
||||
mutation updateUser($user: UpdateUserInput!) {
|
||||
updateUser(user: $user)
|
||||
|
|
|
@ -4,6 +4,7 @@ export interface WeightMap {
|
|||
}
|
||||
export interface CreateWorkFlowInput {
|
||||
ChaosWorkFlowInput: {
|
||||
workflow_id?: string;
|
||||
workflow_manifest: string;
|
||||
cronSyntax: string;
|
||||
workflow_name: string;
|
||||
|
@ -15,6 +16,14 @@ export interface CreateWorkFlowInput {
|
|||
};
|
||||
}
|
||||
|
||||
export interface UpdateWorkflowResponse {
|
||||
workflow_id: string;
|
||||
workflow_name: string;
|
||||
workflow_description: string;
|
||||
isCustomWorkflow: string;
|
||||
cronSyntax: string;
|
||||
}
|
||||
|
||||
export interface CreateWorkflowResponse {
|
||||
cluster_id: string;
|
||||
is_active: boolean;
|
||||
|
|
|
@ -35,7 +35,9 @@ export interface WorkflowData {
|
|||
description: string;
|
||||
weights: experimentMap[];
|
||||
isCustomWorkflow: boolean;
|
||||
isRecurring: boolean;
|
||||
namespace: string;
|
||||
workflow_id?: string;
|
||||
clustername: string;
|
||||
clusterid: string;
|
||||
cronSyntax: string;
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
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;
|
|
@ -1,20 +0,0 @@
|
|||
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;
|
|
@ -1,403 +1,554 @@
|
|||
import {
|
||||
Divider,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { useMutation, useQuery } from '@apollo/client';
|
||||
import Step from '@material-ui/core/Step';
|
||||
import { StepIconProps } from '@material-ui/core/StepIcon';
|
||||
import StepLabel from '@material-ui/core/StepLabel';
|
||||
import Stepper from '@material-ui/core/Stepper';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import YAML from 'yaml';
|
||||
import ButtonFilled from '../../components/Button/ButtonFilled';
|
||||
import ButtonOutline from '../../components/Button/ButtonOutline';
|
||||
import CustomDate from '../../components/DateTime/CustomDate/index';
|
||||
import CustomTime from '../../components/DateTime/CustomTime/index';
|
||||
import Loader from '../../components/Loader';
|
||||
import QontoConnector from '../../components/WorkflowStepper/quontoConnector';
|
||||
import useStyles from '../../components/WorkflowStepper/styles';
|
||||
import useQontoStepIconStyles from '../../components/WorkflowStepper/useQontoStepIconStyles';
|
||||
import Scaffold from '../../containers/layouts/Scaffold';
|
||||
import SetTime from './SetTime/index';
|
||||
import useStyles from './styles';
|
||||
import Unimodal from '../../containers/layouts/Unimodal';
|
||||
import { UPDATE_SCHEDULE } from '../../graphql/mutations';
|
||||
import { SCHEDULE_DETAILS } from '../../graphql/queries';
|
||||
import {
|
||||
CreateWorkFlowInput,
|
||||
UpdateWorkflowResponse,
|
||||
WeightMap,
|
||||
} from '../../models/graphql/createWorkflowData';
|
||||
import { ScheduleDataVars, Schedules } from '../../models/graphql/scheduleData';
|
||||
import { experimentMap, WorkflowData } from '../../models/redux/workflow';
|
||||
import useActions from '../../redux/actions';
|
||||
import * as TabActions from '../../redux/actions/tabs';
|
||||
import * as TemplateSelectionActions from '../../redux/actions/template';
|
||||
import * as WorkflowActions from '../../redux/actions/workflow';
|
||||
import { history } from '../../redux/configureStore';
|
||||
import { RootState } from '../../redux/reducers';
|
||||
import parsed from '../../utils/yamlUtils';
|
||||
import ChooseWorkflow from '../../views/CreateWorkflow/ChooseWorkflow/index';
|
||||
import ReliablityScore from '../../views/CreateWorkflow/ReliabilityScore';
|
||||
import ScheduleWorkflow from '../../views/CreateWorkflow/ScheduleWorkflow';
|
||||
import TuneWorkflow from '../../views/CreateWorkflow/TuneWorkflow/index';
|
||||
import VerifyCommit from '../../views/CreateWorkflow/VerifyCommit';
|
||||
import ChooseAWorkflowCluster from '../../views/CreateWorkflow/WorkflowCluster';
|
||||
import { cronWorkflow, workflowOnce } from './templates';
|
||||
|
||||
const SchedulePage: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const start = 0;
|
||||
const end = 10;
|
||||
const interval = 2;
|
||||
interface URLParams {
|
||||
workflowName: string;
|
||||
projectID: string;
|
||||
}
|
||||
|
||||
const classes = useStyles();
|
||||
// controls radio buttons
|
||||
const [value, setValue] = React.useState('now');
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(event.target.value);
|
||||
};
|
||||
interface Weights {
|
||||
experimentName: string;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
// controls inner radio buttons of recurring schedule
|
||||
const [valueDef, setValueDef] = React.useState('');
|
||||
const handleChangeInstance = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValueDef(event.target.value);
|
||||
};
|
||||
function getSteps(): string[] {
|
||||
return [
|
||||
'Target Cluster',
|
||||
'Choose a workflow',
|
||||
'Tune workflow',
|
||||
'Reliability score',
|
||||
'Schedule',
|
||||
'Verify and Commit',
|
||||
];
|
||||
}
|
||||
|
||||
// sets weekdays
|
||||
const [days, setDays] = React.useState('Monday');
|
||||
function QontoStepIcon(props: StepIconProps) {
|
||||
const classes = useQontoStepIconStyles();
|
||||
const { active, completed } = props;
|
||||
|
||||
// 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;
|
||||
if (completed) {
|
||||
return (
|
||||
<div
|
||||
className={`${classes.root} ${
|
||||
active ? classes.active : classes.completed
|
||||
}`}
|
||||
>
|
||||
<img src="/icons/NotPass.png" alt="Not Completed Icon" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
if (active) {
|
||||
return (
|
||||
<div
|
||||
className={`${classes.root} ${
|
||||
active ? classes.active : classes.completed
|
||||
}`}
|
||||
>
|
||||
<div className={classes.circle} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const weekdays: string[] = [
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday',
|
||||
'Sunday',
|
||||
];
|
||||
const [selectedDate, setSelectedDate] = React.useState<Date | null>(
|
||||
new Date(Date.now())
|
||||
return (
|
||||
<div
|
||||
className={`${classes.root} ${
|
||||
active ? classes.active : classes.completed
|
||||
}`}
|
||||
>
|
||||
{/* <img src="./icons/workflowNotActive.svg" /> */}
|
||||
<div className={classes.outerCircle}>
|
||||
<div className={classes.innerCircle} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getStepContent(
|
||||
stepIndex: number,
|
||||
gotoStep: (page: number) => void
|
||||
): React.ReactNode {
|
||||
switch (stepIndex) {
|
||||
case 0:
|
||||
return (
|
||||
<ChooseAWorkflowCluster gotoStep={(page: number) => gotoStep(page)} />
|
||||
);
|
||||
case 1:
|
||||
return <ChooseWorkflow isEditable={false} />;
|
||||
case 2:
|
||||
return <TuneWorkflow />;
|
||||
case 3:
|
||||
return <ReliablityScore />;
|
||||
case 4:
|
||||
return <ScheduleWorkflow />;
|
||||
case 5:
|
||||
return (
|
||||
<VerifyCommit
|
||||
isEditable={false}
|
||||
gotoStep={(page: number) => gotoStep(page)}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<ChooseAWorkflowCluster gotoStep={(page: number) => gotoStep(page)} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const EditScheduledWorkflow = () => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
const template = useActions(TemplateSelectionActions);
|
||||
const workflowData: WorkflowData = useSelector(
|
||||
(state: RootState) => state.workflowData
|
||||
);
|
||||
const workflow = useActions(WorkflowActions);
|
||||
// Get Parameters from URL
|
||||
const paramData: URLParams = useParams();
|
||||
|
||||
// Apollo query to get the scheduled data
|
||||
const { data, loading } = useQuery<Schedules, ScheduleDataVars>(
|
||||
SCHEDULE_DETAILS,
|
||||
{
|
||||
variables: { projectID: paramData.projectID },
|
||||
fetchPolicy: 'cache-and-network',
|
||||
}
|
||||
);
|
||||
|
||||
const handleDateChange = (date: Date | null) => {
|
||||
setSelectedDate(date);
|
||||
const wfDetails =
|
||||
data &&
|
||||
data.getScheduledWorkflows.filter(
|
||||
(wf) => wf.workflow_name === paramData.workflowName
|
||||
)[0];
|
||||
const doc = new YAML.Document();
|
||||
const a: Weights[] = [];
|
||||
useEffect(() => {
|
||||
if (wfDetails !== undefined) {
|
||||
for (let i = 0; i < wfDetails?.weightages.length; i++) {
|
||||
a.push({
|
||||
experimentName: wfDetails?.weightages[i].experiment_name,
|
||||
weight: wfDetails?.weightages[i].weightage,
|
||||
});
|
||||
}
|
||||
doc.contents = JSON.parse(wfDetails?.workflow_manifest);
|
||||
workflow.setWorkflowDetails({
|
||||
workflow_id: wfDetails?.workflow_id,
|
||||
name: wfDetails?.workflow_name,
|
||||
yaml: doc.toString(),
|
||||
id: 0,
|
||||
description: wfDetails?.workflow_description,
|
||||
weights: a,
|
||||
isCustomWorkflow: wfDetails?.isCustomWorkflow,
|
||||
clusterid: wfDetails?.cluster_id,
|
||||
cronSyntax: wfDetails?.cronSyntax,
|
||||
scheduleType: {
|
||||
scheduleOnce:
|
||||
wfDetails?.cronSyntax === '' ? 'now' : 'recurringSchedule',
|
||||
recurringSchedule: '',
|
||||
},
|
||||
scheduleInput: {
|
||||
hour_interval: 0,
|
||||
day: 1,
|
||||
weekday: 'Monday',
|
||||
time: new Date(),
|
||||
date: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
template.selectTemplate({ selectTemplateID: 0, isDisable: false });
|
||||
}, [data]);
|
||||
|
||||
const {
|
||||
yaml,
|
||||
weights,
|
||||
description,
|
||||
isCustomWorkflow,
|
||||
cronSyntax,
|
||||
name,
|
||||
clusterid,
|
||||
scheduleType,
|
||||
} = workflowData;
|
||||
|
||||
const [activeStep, setActiveStep] = React.useState(4);
|
||||
|
||||
const selectedProjectID = useSelector(
|
||||
(state: RootState) => state.userData.selectedProjectID
|
||||
);
|
||||
const isDisable = useSelector(
|
||||
(state: RootState) => state.selectTemplate.isDisable
|
||||
);
|
||||
const userRole = useSelector((state: RootState) => state.userData.userRole);
|
||||
const tabs = useActions(TabActions);
|
||||
const scheduleOnce = workflowOnce;
|
||||
const scheduleMore = cronWorkflow;
|
||||
const [invalidYaml, setinValidYaml] = React.useState(false);
|
||||
const steps = getSteps();
|
||||
|
||||
function EditYaml() {
|
||||
const oldParsedYaml = YAML.parse(yaml);
|
||||
const NewLink: string = ' ';
|
||||
let NewYaml: string = ' ';
|
||||
if (
|
||||
oldParsedYaml.kind === 'Workflow' &&
|
||||
scheduleType.scheduleOnce !== 'now'
|
||||
) {
|
||||
const oldParsedYaml = YAML.parse(yaml);
|
||||
const newParsedYaml = YAML.parse(scheduleMore);
|
||||
delete newParsedYaml.spec.workflowSpec;
|
||||
newParsedYaml.spec.schedule = cronSyntax;
|
||||
delete newParsedYaml.metadata.generateName;
|
||||
newParsedYaml.metadata.name = workflowData.name;
|
||||
newParsedYaml.metadata.labels = {
|
||||
workflow_id: workflowData.workflow_id,
|
||||
};
|
||||
newParsedYaml.spec.workflowSpec = oldParsedYaml.spec;
|
||||
const tz = {
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
|
||||
};
|
||||
Object.entries(tz).forEach(([key, value]) => {
|
||||
newParsedYaml.spec[key] = value;
|
||||
});
|
||||
NewYaml = YAML.stringify(newParsedYaml);
|
||||
workflow.setWorkflowDetails({
|
||||
link: NewLink,
|
||||
yaml: NewYaml,
|
||||
});
|
||||
}
|
||||
if (
|
||||
oldParsedYaml.kind === 'CronWorkflow' &&
|
||||
scheduleType.scheduleOnce === 'now'
|
||||
) {
|
||||
const oldParsedYaml = YAML.parse(yaml);
|
||||
const newParsedYaml = YAML.parse(scheduleOnce);
|
||||
delete newParsedYaml.spec;
|
||||
delete newParsedYaml.metadata.generateName;
|
||||
newParsedYaml.metadata.name = workflowData.name;
|
||||
newParsedYaml.spec = oldParsedYaml.spec.workflowSpec;
|
||||
newParsedYaml.metadata.labels = {
|
||||
workflow_id: workflowData.workflow_id,
|
||||
};
|
||||
NewYaml = YAML.stringify(newParsedYaml);
|
||||
workflow.setWorkflowDetails({
|
||||
link: NewLink,
|
||||
yaml: NewYaml,
|
||||
});
|
||||
}
|
||||
if (
|
||||
oldParsedYaml.kind === 'CronWorkflow' &&
|
||||
scheduleType.scheduleOnce !== 'now'
|
||||
) {
|
||||
const newParsedYaml = YAML.parse(yaml);
|
||||
newParsedYaml.spec.schedule = cronSyntax;
|
||||
delete newParsedYaml.metadata.generateName;
|
||||
newParsedYaml.metadata.name = workflowData.name;
|
||||
newParsedYaml.metadata.labels = { workflow_id: workflowData.workflow_id };
|
||||
const tz = {
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
|
||||
};
|
||||
Object.entries(tz).forEach(([key, value]) => {
|
||||
newParsedYaml.spec[key] = value;
|
||||
});
|
||||
NewYaml = YAML.stringify(newParsedYaml);
|
||||
workflow.setWorkflowDetails({
|
||||
link: NewLink,
|
||||
yaml: NewYaml,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const handleNext = () => {
|
||||
if (activeStep === 2) {
|
||||
const tests = parsed(yaml);
|
||||
const arr: experimentMap[] = [];
|
||||
const hashMap = new Map();
|
||||
weights.forEach((weight) => {
|
||||
hashMap.set(weight.experimentName, weight.weight);
|
||||
});
|
||||
tests.forEach((test) => {
|
||||
let value = 10;
|
||||
if (hashMap.has(test)) {
|
||||
value = hashMap.get(test);
|
||||
}
|
||||
arr.push({ experimentName: test, weight: value });
|
||||
});
|
||||
workflow.setWorkflowDetails({
|
||||
weights: arr,
|
||||
});
|
||||
if (arr.length === 0) {
|
||||
setinValidYaml(true);
|
||||
} else {
|
||||
setinValidYaml(false);
|
||||
setActiveStep((prevActiveStep) => prevActiveStep + 1);
|
||||
}
|
||||
} else if (activeStep === 4) {
|
||||
EditYaml();
|
||||
setActiveStep((prevActiveStep) => prevActiveStep + 1);
|
||||
} else {
|
||||
setActiveStep((prevActiveStep) => prevActiveStep + 1);
|
||||
}
|
||||
};
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
|
||||
const handleBack = () => {
|
||||
if (activeStep === 2) {
|
||||
setinValidYaml(false);
|
||||
} else if (activeStep === 4 && isDisable === true) {
|
||||
template.selectTemplate({ isDisable: false });
|
||||
}
|
||||
setActiveStep((prevActiveStep) => prevActiveStep - 1);
|
||||
};
|
||||
|
||||
const [createChaosWorkFlow] = useMutation<
|
||||
UpdateWorkflowResponse,
|
||||
CreateWorkFlowInput
|
||||
>(UPDATE_SCHEDULE, {
|
||||
onCompleted: () => {
|
||||
setOpen(true);
|
||||
},
|
||||
});
|
||||
|
||||
const handleMutation = () => {
|
||||
if (name.length !== 0 && description.length !== 0 && weights.length !== 0) {
|
||||
const weightData: WeightMap[] = [];
|
||||
|
||||
weights.forEach((data) => {
|
||||
weightData.push({
|
||||
experiment_name: data.experimentName,
|
||||
weightage: data.weight,
|
||||
});
|
||||
});
|
||||
|
||||
/* JSON.stringify takes 3 parameters [object to be converted,
|
||||
a function to alter the conversion, spaces to be shown in final result for indentation ] */
|
||||
const yml = YAML.parse(yaml);
|
||||
const yamlJson = JSON.stringify(yml, null, 2); // Converted to Stringified JSON
|
||||
|
||||
const chaosWorkFlowInputs = {
|
||||
workflow_id: wfDetails?.workflow_id,
|
||||
workflow_manifest: yamlJson,
|
||||
cronSyntax,
|
||||
workflow_name: name,
|
||||
workflow_description: description,
|
||||
isCustomWorkflow,
|
||||
weightages: weightData,
|
||||
project_id: selectedProjectID,
|
||||
cluster_id: clusterid,
|
||||
};
|
||||
|
||||
// console.log(chaosWorkFlowInputs);
|
||||
createChaosWorkFlow({
|
||||
variables: { ChaosWorkFlowInput: chaosWorkFlowInputs },
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleOpen = () => {
|
||||
handleMutation();
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
history.push('/workflows');
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
function gotoStep({ page }: { page: number }) {
|
||||
setActiveStep(page);
|
||||
}
|
||||
|
||||
// Check correct permissions for user
|
||||
if (userRole === 'Viewer')
|
||||
return (
|
||||
<>
|
||||
<Typography
|
||||
variant="h3"
|
||||
align="center"
|
||||
style={{ marginTop: '10rem', marginBottom: '3rem' }}
|
||||
>
|
||||
{t('schedule.missingPerm')}
|
||||
</Typography>
|
||||
<Typography variant="h6" align="center">
|
||||
{t('schedule.requiredPerm')}
|
||||
</Typography>
|
||||
<br />
|
||||
<Typography variant="body1" align="center">
|
||||
{t('schedule.contact')}
|
||||
</Typography>
|
||||
|
||||
{/* Back Button */}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginTop: '1rem',
|
||||
}}
|
||||
>
|
||||
<ButtonFilled isPrimary handleClick={() => history.goBack()}>
|
||||
{t('schedule.backBtn')}
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Scaffold>
|
||||
<div className={classes.rootContainer}>
|
||||
<Typography className={classes.mainHeader}>
|
||||
{t('schedule.heading')}
|
||||
</Typography>
|
||||
<Typography className={classes.headerDesc}>
|
||||
{t('schedule.headingDesc')}
|
||||
</Typography>
|
||||
{loading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<div className={classes.root}>
|
||||
<div className={classes.scHeader}>
|
||||
{/* Upper segment */}
|
||||
<div className={classes.scSegments}>
|
||||
<Stepper
|
||||
activeStep={activeStep}
|
||||
connector={<QontoConnector />}
|
||||
className={classes.stepper}
|
||||
alternativeLabel
|
||||
>
|
||||
{steps.map((label, i) => (
|
||||
<Step key={label}>
|
||||
{activeStep === i ? (
|
||||
<StepLabel StepIconComponent={QontoStepIcon}>
|
||||
<div className={classes.activeLabel} data-cy="labelText">
|
||||
{label}
|
||||
</div>
|
||||
</StepLabel>
|
||||
) : (
|
||||
<StepLabel StepIconComponent={QontoStepIcon}>
|
||||
<div className={classes.normalLabel} data-cy="labelText">
|
||||
{label}
|
||||
</div>
|
||||
</StepLabel>
|
||||
)}
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<Unimodal
|
||||
open={open}
|
||||
handleClose={handleClose}
|
||||
aria-labelledby="simple-modal-title"
|
||||
aria-describedby="simple-modal-description"
|
||||
hasCloseBtn
|
||||
>
|
||||
<div>
|
||||
<Typography className={classes.headerText}>
|
||||
<strong>{t('schedule.headingText')}</strong>
|
||||
</Typography>
|
||||
|
||||
<div className={classes.schBody}>
|
||||
<Typography align="left" className={classes.description}>
|
||||
{t('schedule.description')}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src="./icons/calendar.svg"
|
||||
alt="calendar"
|
||||
className={classes.calIcon}
|
||||
src="/icons/finish.svg"
|
||||
className={classes.mark}
|
||||
alt="mark"
|
||||
/>
|
||||
<div className={classes.heading}>
|
||||
{t('schedule.chaosWorkflow')}
|
||||
<br />
|
||||
<strong>{t('schedule.successful')}</strong>
|
||||
</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}>
|
||||
{t('schedule.scheduleNow')}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
value="afterSometime"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Typography className={classes.radioText}>
|
||||
{t('schedule.scheduleAfter')}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
{value === 'afterSometime' ? (
|
||||
<div className={classes.schLater}>
|
||||
<Typography className={classes.captionText}>
|
||||
{t('schedule.scheduleAfterSometime')}
|
||||
</Typography>
|
||||
<div className={classes.wtDateTime}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
className={classes.captionText}
|
||||
>
|
||||
{t('schedule.after')}
|
||||
</Typography>
|
||||
|
||||
<SetTime
|
||||
start={start}
|
||||
end={end}
|
||||
interval={interval}
|
||||
label="Days"
|
||||
type="days"
|
||||
/>
|
||||
<CustomTime
|
||||
handleDateChange={handleDateChange}
|
||||
value={selectedDate}
|
||||
ampm
|
||||
disabled={false}
|
||||
/>
|
||||
<div className={classes.headWorkflow}>
|
||||
{t('schedule.congratulationsSub1')}
|
||||
<br />
|
||||
{t('schedule.congratulationsSub2')}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<FormControlLabel
|
||||
value="specificTime"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Typography className={classes.radioText}>
|
||||
{t('schedule.scheduleSpecificTime')}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
|
||||
{value === 'specificTime' ? (
|
||||
<div className={classes.schLater}>
|
||||
<Typography className={classes.captionText}>
|
||||
{t('schedule.scheduleFuture')}
|
||||
</Typography>
|
||||
<div className={classes.innerSpecific}>
|
||||
<CustomDate
|
||||
selectedDate={selectedDate}
|
||||
handleDateChange={handleDateChange}
|
||||
disabled={false}
|
||||
/>
|
||||
<CustomTime
|
||||
handleDateChange={handleDateChange}
|
||||
value={selectedDate}
|
||||
ampm
|
||||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<FormControlLabel
|
||||
value="recurringScedule"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Typography className={classes.radioText}>
|
||||
{t('schedule.scheduleRecurring')}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
{value === 'recurringScedule' ? (
|
||||
<div className={classes.schLater}>
|
||||
<Typography className={classes.captionText}>
|
||||
{t('schedule.scheduleRecurringTime')}
|
||||
</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}>
|
||||
{t('schedule.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}>
|
||||
{t('schedule.At')}
|
||||
</Typography>
|
||||
<CustomTime
|
||||
handleDateChange={handleDateChange}
|
||||
value={selectedDate}
|
||||
ampm
|
||||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<FormControlLabel
|
||||
value="everyWeek"
|
||||
control={<Radio />}
|
||||
label="Every Week "
|
||||
/>
|
||||
{valueDef === 'everyWeek' ? (
|
||||
<div>
|
||||
<div className={classes.scRandom}>
|
||||
<Typography className={classes.scRandsub1}>
|
||||
{t('schedule.scheduleOn')}
|
||||
</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,
|
||||
},
|
||||
<div className={classes.button}>
|
||||
<ButtonFilled
|
||||
isPrimary
|
||||
data-cy="selectFinish"
|
||||
handleClick={() => {
|
||||
setOpen(false);
|
||||
tabs.changeWorkflowsTabs(0);
|
||||
history.push('/workflows');
|
||||
}}
|
||||
>
|
||||
{weekdays.map((day) => (
|
||||
<option
|
||||
className={classes.opt}
|
||||
value={day}
|
||||
>
|
||||
{day}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Typography className={classes.scRandsub1}>
|
||||
{t('schedule.at')}
|
||||
</Typography>
|
||||
<CustomTime
|
||||
handleDateChange={handleDateChange}
|
||||
value={selectedDate}
|
||||
ampm
|
||||
disabled={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<FormControlLabel
|
||||
value="everyMonth"
|
||||
control={<Radio />}
|
||||
label="Every Month"
|
||||
/>
|
||||
{valueDef === 'everyMonth' ? (
|
||||
<div>
|
||||
<div className={classes.scRandom}>
|
||||
<Typography className={classes.scRandsub1}>
|
||||
{t('schedule.scheduleOn')}
|
||||
</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}>
|
||||
{t('schedule.at')}
|
||||
</Typography>
|
||||
<CustomTime
|
||||
handleDateChange={handleDateChange}
|
||||
value={selectedDate}
|
||||
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>{t('schedule.save')}</Typography>
|
||||
<div>{t('workflowStepper.workflowBtn')}</div>
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
</div>
|
||||
</Unimodal>
|
||||
{getStepContent(activeStep, (page: number) =>
|
||||
gotoStep({ page })
|
||||
)}
|
||||
</div>
|
||||
{/* Control Buttons */}
|
||||
|
||||
<div className={classes.buttonGroup}>
|
||||
{activeStep === steps.length - 2 ? (
|
||||
<ButtonOutline isDisabled handleClick={handleBack}>
|
||||
<Typography>Back</Typography>
|
||||
</ButtonOutline>
|
||||
) : activeStep !== 1 ? (
|
||||
<ButtonOutline isDisabled={false} handleClick={handleBack}>
|
||||
<Typography>Back</Typography>
|
||||
</ButtonOutline>
|
||||
) : null}
|
||||
{activeStep === steps.length - 1 ? (
|
||||
<ButtonFilled handleClick={handleOpen} isPrimary>
|
||||
<div>{t('workflowStepper.finish')}</div>
|
||||
</ButtonFilled>
|
||||
) : (
|
||||
<ButtonFilled
|
||||
handleClick={() => handleNext()}
|
||||
isPrimary
|
||||
isDisabled={isDisable}
|
||||
>
|
||||
<div>
|
||||
{t('workflowStepper.next')}
|
||||
<img
|
||||
alt="next"
|
||||
src="/icons/nextArrow.svg"
|
||||
className={classes.nextArrow}
|
||||
/>
|
||||
</div>
|
||||
</ButtonFilled>
|
||||
)}
|
||||
{invalidYaml ? (
|
||||
<Typography className={classes.yamlError}>
|
||||
<strong>{t('workflowStepper.continueError')}</strong>
|
||||
</Typography>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Scaffold>
|
||||
);
|
||||
};
|
||||
|
||||
export default SchedulePage;
|
||||
export default EditScheduledWorkflow;
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
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;
|
|
@ -0,0 +1,4 @@
|
|||
export const workflowOnce =
|
||||
'{\r\n "apiVersion": "argoproj.io/v1alpha1",\r\n "kind": "Workflow",\r\n "metadata": {\r\n "generateName": "argowf-chaos-node-cpu-hog-",\r\n "namespace": "litmus"\r\n },\r\n "spec": null\r\n}';
|
||||
export const cronWorkflow =
|
||||
'{\r\n "apiVersion": "argoproj.io/v1alpha1",\r\n "kind": "CronWorkflow",\r\n "metadata": {\r\n "name": "argo-chaos-pod-memory-cron-wf",\r\n "namespace": "litmus"\r\n },\r\n "spec": {\r\n "schedule": "0 * * * *",\r\n "concurrencyPolicy": "Forbid",\r\n "startingDeadlineSeconds": 0,\r\n "workflowSpec": null\r\n }\r\n}';
|
|
@ -14,6 +14,7 @@ const initialState: WorkflowData = {
|
|||
description: '',
|
||||
weights: [],
|
||||
isCustomWorkflow: false,
|
||||
isRecurring: false,
|
||||
namespace: 'litmus',
|
||||
clusterid: '',
|
||||
cronSyntax: '',
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="87" height="87" viewBox="0 0 87 87" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M43.5 83.502C65.8675 83.502 84 65.3695 84 43.002C84 20.6344 65.8675 2.50195 43.5 2.50195C21.1325 2.50195 3 20.6344 3 43.002C3 65.3695 21.1325 83.502 43.5 83.502Z" stroke="#CA2C2C" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M55.6501 30.8516L31.3501 55.1516" stroke="#CA2C2C" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M31.3501 30.8516L55.6501 55.1516" stroke="#CA2C2C" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 610 B |
|
@ -0,0 +1,5 @@
|
|||
const trimString = (string: string, length: number): string => {
|
||||
return string.length > length ? `${string.substring(0, length)}...` : string;
|
||||
};
|
||||
|
||||
export default trimString;
|
|
@ -24,7 +24,7 @@ import React, { useEffect, useState } from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Loader from '../../../../components/Loader';
|
||||
import { WORKFLOW_LIST_DETAILS } from '../../../../graphql/quries';
|
||||
import { WORKFLOW_LIST_DETAILS } from '../../../../graphql/queries';
|
||||
import {
|
||||
ExecutionData,
|
||||
WeightageMap,
|
||||
|
|
|
@ -8,31 +8,47 @@ import {
|
|||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
|
||||
import GetAppIcon from '@material-ui/icons/GetApp';
|
||||
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import cronstrue from 'cronstrue';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import cronstrue from 'cronstrue';
|
||||
import YAML from 'yaml';
|
||||
import GetAppIcon from '@material-ui/icons/GetApp';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import YAML from 'yaml';
|
||||
import ButtonFilled from '../../../components/Button/ButtonFilled';
|
||||
import ButtonOutline from '../../../components/Button/ButtonOutline';
|
||||
import Unimodal from '../../../containers/layouts/Unimodal';
|
||||
import { ScheduleWorkflow } from '../../../models/graphql/scheduleData';
|
||||
import useStyles from './styles';
|
||||
import ExperimentPoints from './ExperimentPoints';
|
||||
import useActions from '../../../redux/actions';
|
||||
import * as WorkflowActions from '../../../redux/actions/workflow';
|
||||
import { history } from '../../../redux/configureStore';
|
||||
import { RootState } from '../../../redux/reducers';
|
||||
import { ReactComponent as CrossMarkIcon } from '../../../svg/crossmark.svg';
|
||||
import ExperimentPoints from './ExperimentPoints';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface TableDataProps {
|
||||
data: ScheduleWorkflow;
|
||||
deleteRow: (wfid: string) => void;
|
||||
}
|
||||
|
||||
interface Weights {
|
||||
experimentName: string;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
// States for PopOver to display Experiment Weights
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const [popAnchorEl, setPopAnchorEl] = React.useState<null | HTMLElement>(
|
||||
null
|
||||
);
|
||||
const [isModalOpen, setIsModalOpen] = React.useState<boolean>(false);
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const isOpen = Boolean(popAnchorEl);
|
||||
const id = isOpen ? 'simple-popover' : undefined;
|
||||
|
@ -40,6 +56,7 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
|
|||
setPopAnchorEl(null);
|
||||
};
|
||||
|
||||
const workflow = useActions(WorkflowActions);
|
||||
const userData = useSelector((state: RootState) => state.userData);
|
||||
|
||||
const handlePopOverClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
|
@ -77,6 +94,19 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
|
|||
return 'Date not available';
|
||||
};
|
||||
|
||||
const editSchedule = () => {
|
||||
history.push(
|
||||
`/workflows/schedule/${data.project_id}/${data.workflow_name}`
|
||||
);
|
||||
};
|
||||
|
||||
// If regularity is not Once then set recurring schedule state to true
|
||||
if (data.cronSyntax !== '') {
|
||||
workflow.setWorkflowDetails({
|
||||
isRecurring: true,
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableCell className={classes.workflowNameData}>
|
||||
|
@ -171,6 +201,22 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
|
|||
open={open}
|
||||
onClose={handleClose}
|
||||
>
|
||||
{data.cronSyntax !== '' ? (
|
||||
<MenuItem value="Edit_Schedule" onClick={() => editSchedule()}>
|
||||
<div className={classes.expDiv}>
|
||||
<img
|
||||
src="./icons/Edit.svg"
|
||||
alt="Edit Schedule"
|
||||
className={classes.btnImg}
|
||||
/>
|
||||
<Typography data-cy="editSchedule" className={classes.btnText}>
|
||||
Edit Schedule
|
||||
</Typography>
|
||||
</div>
|
||||
</MenuItem>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<MenuItem
|
||||
value="Download"
|
||||
onClick={() =>
|
||||
|
@ -188,10 +234,7 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
|
|||
</div>
|
||||
</MenuItem>
|
||||
{userData.userRole !== 'Viewer' ? (
|
||||
<MenuItem
|
||||
value="Analysis"
|
||||
onClick={() => deleteRow(data.workflow_id)}
|
||||
>
|
||||
<MenuItem value="Analysis" onClick={() => setIsModalOpen(true)}>
|
||||
<div className={classes.expDiv}>
|
||||
<img
|
||||
src="/icons/deleteSchedule.svg"
|
||||
|
@ -209,6 +252,39 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
|
|||
) : null}
|
||||
</Menu>
|
||||
</TableCell>
|
||||
{isModalOpen ? (
|
||||
<Unimodal open={isModalOpen} handleClose={handleClose} hasCloseBtn>
|
||||
<div className={classes.modalDiv}>
|
||||
<CrossMarkIcon />
|
||||
<Typography className={classes.modalHeader}>
|
||||
{t('createWorkflow.scheduleWorkflow.modalHeader')}
|
||||
</Typography>
|
||||
<Typography className={classes.modalConfirm}>
|
||||
{t('createWorkflow.scheduleWorkflow.modalSubheader')}
|
||||
</Typography>
|
||||
<div className={classes.modalBtns}>
|
||||
<ButtonOutline
|
||||
isDisabled={false}
|
||||
handleClick={() => setIsModalOpen(false)}
|
||||
>
|
||||
{t('createWorkflow.scheduleWorkflow.cancelBtn')}
|
||||
</ButtonOutline>
|
||||
<ButtonFilled
|
||||
isPrimary={false}
|
||||
isWarning
|
||||
handleClick={() => {
|
||||
deleteRow(data.workflow_id);
|
||||
setIsModalOpen(false);
|
||||
}}
|
||||
>
|
||||
{t('createWorkflow.scheduleWorkflow.deleteBtn')}
|
||||
</ButtonFilled>
|
||||
</div>
|
||||
</div>
|
||||
</Unimodal>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -181,6 +181,34 @@ const useStyles = makeStyles((theme) => ({
|
|||
: theme.palette.error.dark,
|
||||
fontWeight: 500,
|
||||
},
|
||||
|
||||
// Modal
|
||||
modalDiv: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '25rem',
|
||||
marginTop: '10%',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
modalHeader: {
|
||||
fontSize: '2.125rem',
|
||||
fontWeight: 400,
|
||||
marginBottom: theme.spacing(2.5),
|
||||
marginTop: theme.spacing(2.5),
|
||||
width: '31.25rem',
|
||||
},
|
||||
modalConfirm: {
|
||||
fontSize: '1.25rem',
|
||||
marginBottom: theme.spacing(5),
|
||||
width: '31.25rem',
|
||||
},
|
||||
modalBtns: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: '16rem',
|
||||
},
|
||||
}));
|
||||
|
||||
export default useStyles;
|
||||
|
|
|
@ -59,8 +59,10 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
// Experiment Details
|
||||
|
||||
experimentWrapperDiv: {
|
||||
display: 'inline-block',
|
||||
margin: '1.6rem 0',
|
||||
display: 'grid',
|
||||
margin: '2rem',
|
||||
gridTemplateColumns: '1fr 1fr 1fr 1fr',
|
||||
gridGap: '1.5rem',
|
||||
},
|
||||
tests: {
|
||||
width: '17rem',
|
||||
|
|
|
@ -15,7 +15,7 @@ const Templates = () => {
|
|||
const testWeights: number[] = [];
|
||||
|
||||
// Setting initial selected template ID to 0
|
||||
template.selectTemplate({ selectedTemplateID: 0, isDisable: true });
|
||||
template.selectTemplate({ selectedTemplateID: -1, isDisable: true });
|
||||
|
||||
const selectWorkflow = (index: number) => {
|
||||
// Updating template ID to the selected one
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import { Typography } from '@material-ui/core';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import ButtonFilled from '../../../components/Button/ButtonFilled';
|
||||
import ButtonOutline from '../../../components/Button/ButtonOutline';
|
||||
import InputField from '../../../components/InputField';
|
||||
import PredifinedWorkflows from '../../../components/PredifinedWorkflows';
|
||||
import workflowsList from '../../../components/PredifinedWorkflows/data';
|
||||
import Unimodal from '../../../containers/layouts/Unimodal';
|
||||
import { WorkflowData } from '../../../models/redux/workflow';
|
||||
import useActions from '../../../redux/actions';
|
||||
import * as TemplateSelectionActions from '../../../redux/actions/template';
|
||||
import * as WorkflowActions from '../../../redux/actions/workflow';
|
||||
|
@ -18,7 +19,11 @@ import useStyles, { CssTextField } from './styles';
|
|||
|
||||
// import { getWkfRunCount } from "../../utils";
|
||||
|
||||
const ChooseWorkflow: React.FC = () => {
|
||||
interface ChooseWorkflowProps {
|
||||
isEditable?: boolean;
|
||||
}
|
||||
|
||||
const ChooseWorkflow: React.FC<ChooseWorkflowProps> = ({ isEditable }) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
@ -30,6 +35,9 @@ const ChooseWorkflow: React.FC = () => {
|
|||
const selectedTemplateID = useSelector(
|
||||
(state: RootState) => state.selectTemplate.selectedTemplateID
|
||||
);
|
||||
const workflowData: WorkflowData = useSelector(
|
||||
(state: RootState) => state.workflowData
|
||||
);
|
||||
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const isSuccess = React.useRef<boolean>(false);
|
||||
|
@ -92,17 +100,24 @@ const ChooseWorkflow: React.FC = () => {
|
|||
const selectWorkflow = (index: number) => {
|
||||
template.selectTemplate({ selectedTemplateID: index, isDisable: false });
|
||||
|
||||
const timeStampBasedWorkflowName: string = `argowf-chaos-${
|
||||
workflowsList[index].title
|
||||
}-${Math.round(new Date().getTime() / 1000)}`;
|
||||
const timeStampBasedWorkflowName: string = isEditable
|
||||
? `argowf-chaos-${workflowsList[index].title}-${Math.round(
|
||||
new Date().getTime() / 1000
|
||||
)}`
|
||||
: workflowData.name;
|
||||
|
||||
workflow.setWorkflowDetails({
|
||||
name: timeStampBasedWorkflowName,
|
||||
link: workflowsList[index].chaosWkfCRDLink,
|
||||
id: workflowsList[index].workflowID,
|
||||
yaml: 'none',
|
||||
description: workflowsList[index].description,
|
||||
isCustomWorkflow: workflowsList[index].isCustom,
|
||||
description: isEditable
|
||||
? workflowsList[index].description
|
||||
: workflowData.description,
|
||||
isCustomWorkflow: isEditable
|
||||
? workflowsList[index].isCustom
|
||||
: workflowData.isCustomWorkflow,
|
||||
isRecurring: false,
|
||||
});
|
||||
|
||||
setWorkflowData({
|
||||
|
@ -119,16 +134,22 @@ const ChooseWorkflow: React.FC = () => {
|
|||
useEffect(() => {
|
||||
const index = selectedTemplateID;
|
||||
|
||||
const timeStampBasedWorkflowName: string = `argowf-chaos-${
|
||||
workflowsList[index].title
|
||||
}-${Math.round(new Date().getTime() / 1000)}`;
|
||||
const timeStampBasedWorkflowName: string = isEditable
|
||||
? `argowf-chaos-${workflowsList[index].title}-${Math.round(
|
||||
new Date().getTime() / 1000
|
||||
)}`
|
||||
: workflowData.name;
|
||||
|
||||
workflow.setWorkflowDetails({
|
||||
name: timeStampBasedWorkflowName,
|
||||
link: workflowsList[index].chaosWkfCRDLink,
|
||||
id: workflowsList[index].workflowID,
|
||||
yaml: 'none',
|
||||
description: workflowsList[index].description,
|
||||
isCustomWorkflow: workflowsList[index].isCustom,
|
||||
isCustomWorkflow: isEditable
|
||||
? workflowsList[index].isCustom
|
||||
: workflowData.isCustomWorkflow,
|
||||
isRecurring: false,
|
||||
});
|
||||
|
||||
setWorkflowData({
|
||||
|
@ -199,6 +220,7 @@ const ChooseWorkflow: React.FC = () => {
|
|||
<InputField
|
||||
// id="filled-workflowname-input"
|
||||
label={t('createWorkflow.chooseWorkflow.label.workflowName')}
|
||||
disabled={!isEditable}
|
||||
styles={{
|
||||
width: '100%',
|
||||
}}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { useLazyQuery, useQuery } from '@apollo/client';
|
||||
import {
|
||||
Button,
|
||||
ClickAwayListener,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
|
@ -14,28 +15,27 @@ import {
|
|||
RadioGroup,
|
||||
Select,
|
||||
Typography,
|
||||
ClickAwayListener,
|
||||
} from '@material-ui/core';
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import YAML from 'yaml';
|
||||
import ButtonFilled from '../../../../components/Button/ButtonFilled';
|
||||
import InputField from '../../../../components/InputField';
|
||||
import Loader from '../../../../components/Loader';
|
||||
import { GET_CHARTS_DATA, GET_HUB_STATUS } from '../../../../graphql';
|
||||
import { GET_EXPERIMENT_YAML } from '../../../../graphql/queries';
|
||||
import { MyHubDetail } from '../../../../models/graphql/user';
|
||||
import { Charts, HubStatus } from '../../../../models/redux/myhub';
|
||||
import * as WorkflowActions from '../../../../redux/actions/workflow';
|
||||
import useActions from '../../../../redux/actions';
|
||||
import { RootState } from '../../../../redux/reducers';
|
||||
import useStyles, { CustomTextField, MenuProps } from './styles';
|
||||
import WorkflowDetails from '../../../../pages/WorkflowDetails';
|
||||
import { GET_EXPERIMENT_YAML } from '../../../../graphql/quries';
|
||||
import BackButton from '../BackButton';
|
||||
import useActions from '../../../../redux/actions';
|
||||
import * as TemplateSelectionActions from '../../../../redux/actions/template';
|
||||
import * as WorkflowActions from '../../../../redux/actions/workflow';
|
||||
import { history } from '../../../../redux/configureStore';
|
||||
import { RootState } from '../../../../redux/reducers';
|
||||
import BackButton from '../BackButton';
|
||||
import useStyles, { CustomTextField, MenuProps } from './styles';
|
||||
|
||||
interface WorkflowDetails {
|
||||
workflow_name: string;
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import { useLazyQuery } from '@apollo/client';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import YAML from 'yaml';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useLazyQuery } from '@apollo/client';
|
||||
import BackButton from '../BackButton';
|
||||
import { useSelector } from 'react-redux';
|
||||
import YAML from 'yaml';
|
||||
import ButtonFilled from '../../../../components/Button/ButtonFilled';
|
||||
import InputField from '../../../../components/InputField';
|
||||
import useStyles from './styles';
|
||||
import { RootState } from '../../../../redux/reducers';
|
||||
import Loader from '../../../../components/Loader';
|
||||
import { GET_ENGINE_YAML } from '../../../../graphql/queries';
|
||||
import useActions from '../../../../redux/actions';
|
||||
import * as WorkflowActions from '../../../../redux/actions/workflow';
|
||||
import Loader from '../../../../components/Loader';
|
||||
import { GET_ENGINE_YAML } from '../../../../graphql/quries';
|
||||
import { RootState } from '../../../../redux/reducers';
|
||||
import BackButton from '../BackButton';
|
||||
import useStyles from './styles';
|
||||
|
||||
interface EnvValues {
|
||||
name: string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Typography } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import ButtonFilled from '../../../components/Button/ButtonFilled';
|
||||
import ButtonOutline from '../../../components/Button/ButtonOutline';
|
||||
import Center from '../../../containers/layouts/Center';
|
||||
|
@ -95,7 +95,7 @@ const ReliablityScore = () => {
|
|||
data-cy="testRunButton"
|
||||
>
|
||||
<div className={classes.buttonOutlineDiv}>
|
||||
<img src="icons/video.png" alt="Play icon" />
|
||||
<img src="/icons/video.png" alt="Play icon" />
|
||||
<Typography className={classes.buttonOutlineText}>
|
||||
{t('createWorkflow.reliabilityScore.button.demo')}
|
||||
</Typography>
|
||||
|
|
|
@ -8,17 +8,18 @@ import {
|
|||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import YAML from 'yaml';
|
||||
import CustomDate from '../../../components/DateTime/CustomDate/index';
|
||||
import CustomTime from '../../../components/DateTime/CustomTime/index';
|
||||
import { WorkflowData } from '../../../models/redux/workflow';
|
||||
import useActions from '../../../redux/actions';
|
||||
import * as TemplateSelectionActions from '../../../redux/actions/template';
|
||||
import * as WorkflowActions from '../../../redux/actions/workflow';
|
||||
import { RootState } from '../../../redux/reducers';
|
||||
import SetTime from './SetTime/index';
|
||||
import useStyles from './styles';
|
||||
import * as WorkflowActions from '../../../redux/actions/workflow';
|
||||
import * as TemplateSelectionActions from '../../../redux/actions/template';
|
||||
|
||||
interface ScheduleSyntax {
|
||||
minute: string | undefined;
|
||||
|
@ -71,6 +72,10 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
workflow.setWorkflowDetails({
|
||||
cronSyntax,
|
||||
});
|
||||
if (value === 'disable')
|
||||
workflow.setWorkflowDetails({
|
||||
isDisabled: true,
|
||||
});
|
||||
}, [cronValue]);
|
||||
|
||||
const classes = useStyles();
|
||||
|
@ -188,6 +193,9 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
// UseEffect to update the values of CronSyntax on radio button change
|
||||
useEffect(() => {
|
||||
if (value === 'now') {
|
||||
workflow.setWorkflowDetails({
|
||||
isDisabled: false,
|
||||
});
|
||||
setValueDef('');
|
||||
setCronValue({
|
||||
minute: '',
|
||||
|
@ -197,6 +205,11 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
day_week: '',
|
||||
});
|
||||
}
|
||||
if (value === 'disable') {
|
||||
workflow.setWorkflowDetails({
|
||||
isDisabled: true,
|
||||
});
|
||||
}
|
||||
if (value === 'specificTime') {
|
||||
setValueDef('');
|
||||
setCronValue({
|
||||
|
@ -226,6 +239,9 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
}
|
||||
}
|
||||
if (valueDef === 'everyHr') {
|
||||
workflow.setWorkflowDetails({
|
||||
isDisabled: false,
|
||||
});
|
||||
setCronValue({
|
||||
minute: minute.toString(),
|
||||
hour: '0-23',
|
||||
|
@ -235,6 +251,9 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
});
|
||||
}
|
||||
if (valueDef === 'everyDay') {
|
||||
workflow.setWorkflowDetails({
|
||||
isDisabled: false,
|
||||
});
|
||||
setCronValue({
|
||||
minute: selectedTime?.getMinutes().toString(),
|
||||
hour: selectedTime?.getHours().toString(),
|
||||
|
@ -244,6 +263,9 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
});
|
||||
}
|
||||
if (valueDef === 'everyWeek') {
|
||||
workflow.setWorkflowDetails({
|
||||
isDisabled: false,
|
||||
});
|
||||
setCronValue({
|
||||
minute: selectedTime?.getMinutes().toString(),
|
||||
hour: selectedTime?.getHours().toString(),
|
||||
|
@ -253,6 +275,9 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
});
|
||||
}
|
||||
if (valueDef === 'everyMonth') {
|
||||
workflow.setWorkflowDetails({
|
||||
isDisabled: false,
|
||||
});
|
||||
setCronValue({
|
||||
minute: selectedTime?.getMinutes().toString(),
|
||||
hour: selectedTime?.getHours().toString(),
|
||||
|
@ -262,6 +287,9 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
});
|
||||
}
|
||||
if (value === 'recurringSchedule' && valueDef === '') {
|
||||
workflow.setWorkflowDetails({
|
||||
isDisabled: false,
|
||||
});
|
||||
template.selectTemplate({ isDisable: true });
|
||||
} else {
|
||||
template.selectTemplate({ isDisable: false });
|
||||
|
@ -279,7 +307,7 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
<div className={classes.scHeader}>
|
||||
{/* Upper segment */}
|
||||
<div className={classes.scSegments}>
|
||||
<div>
|
||||
<div aria-details="content wrapper" style={{ width: '90%' }}>
|
||||
<Typography className={classes.headerText}>
|
||||
<strong>{t('createWorkflow.scheduleWorkflow.header')}</strong>
|
||||
</Typography>
|
||||
|
@ -308,6 +336,7 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
onChange={handleChange}
|
||||
>
|
||||
{/* options to choose schedule */}
|
||||
{!workflowData.isRecurring ? (
|
||||
<FormControlLabel
|
||||
value="now"
|
||||
control={<Radio />}
|
||||
|
@ -317,7 +346,20 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
</Typography>
|
||||
}
|
||||
/>
|
||||
) : YAML.parse(workflowData.yaml).spec.suspend === false ? (
|
||||
<FormControlLabel
|
||||
value="disable"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Typography className={classes.radioText}>
|
||||
Disable Schedule
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{/* <FormControlLabel
|
||||
value="specificTime"
|
||||
disabled
|
||||
control={<Radio />}
|
||||
|
@ -326,7 +368,7 @@ const ScheduleWorkflow: React.FC = () => {
|
|||
{t('createWorkflow.scheduleWorkflow.radio.specific')}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
/> */}
|
||||
{value === 'specificTime' ? (
|
||||
<div className={classes.schLater}>
|
||||
<Typography className={classes.captionText}>
|
||||
|
|
|
@ -40,6 +40,7 @@ const useStyles = makeStyles((theme: Theme) => ({
|
|||
|
||||
radioText: {
|
||||
fontSize: '0.875rem',
|
||||
color: theme.palette.text.primary,
|
||||
},
|
||||
description: {
|
||||
width: '32.18rem',
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Typography } from '@material-ui/core';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import YAML from 'yaml';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Loader from '../../../components/Loader';
|
||||
import YamlEditor from '../../../components/YamlEditor/Editor';
|
||||
import { WorkflowData } from '../../../models/redux/workflow';
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Divider, IconButton, Typography } from '@material-ui/core';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import EditIcon from '@material-ui/icons/Edit';
|
||||
import cronstrue from 'cronstrue';
|
||||
import YAML from 'yaml';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import YAML from 'yaml';
|
||||
import AdjustedWeights from '../../../components/AdjustedWeights';
|
||||
import ButtonFilled from '../../../components/Button/ButtonFilled';
|
||||
import ButtonOutline from '../../../components/Button/ButtonOutline/index';
|
||||
|
@ -23,9 +23,13 @@ import useStyles from './styles';
|
|||
|
||||
interface VerifyCommitProps {
|
||||
gotoStep: (page: number) => void;
|
||||
isEditable?: boolean;
|
||||
}
|
||||
|
||||
const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
|
||||
const VerifyCommit: React.FC<VerifyCommitProps> = ({
|
||||
gotoStep,
|
||||
isEditable,
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
@ -114,7 +118,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
|
|||
</Typography>
|
||||
</div>
|
||||
<img
|
||||
src="./icons/b-finance.png"
|
||||
src="/icons/b-finance.png"
|
||||
alt="bfinance"
|
||||
className={classes.bfinIcon}
|
||||
/>
|
||||
|
@ -139,6 +143,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
|
|||
onchange={(changedName: string) =>
|
||||
handleNameChange({ changedName })
|
||||
}
|
||||
isEditable={workflowData.isRecurring ? false : isEditable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -172,6 +177,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
|
|||
onchange={(changedDesc: string) =>
|
||||
handleDescChange({ changedDesc })
|
||||
}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -233,7 +239,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
|
|||
</div>
|
||||
{/* <div className={classes.editButton2}> */}
|
||||
<ButtonOutline
|
||||
isDisabled={false}
|
||||
isDisabled={workflowData.isRecurring}
|
||||
handleClick={() => gotoStep(3)}
|
||||
data-cy="testRunButton"
|
||||
>
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
REMOVE_INVITATION,
|
||||
SEND_INVITE,
|
||||
} from '../../../../../graphql/mutations';
|
||||
import { GET_USER } from '../../../../../graphql/quries';
|
||||
import { GET_USER } from '../../../../../graphql/queries';
|
||||
import {
|
||||
MemberInvitation,
|
||||
MemberInviteNew,
|
||||
|
|
|
@ -10,11 +10,12 @@ import {
|
|||
Typography,
|
||||
} from '@material-ui/core';
|
||||
import React, { useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import ButtonFilled from '../../../../../components/Button/ButtonFilled';
|
||||
import Loader from '../../../../../components/Loader';
|
||||
import { ALL_USERS, GET_USER, SEND_INVITE } from '../../../../../graphql';
|
||||
import { SEND_INVITE } from '../../../../../graphql/mutations';
|
||||
import { ALL_USERS, GET_USER } from '../../../../../graphql/queries';
|
||||
import {
|
||||
MemberInviteNew,
|
||||
UserInvite,
|
||||
|
|
|
@ -9,7 +9,7 @@ import ButtonOutline from '../../../components/Button/ButtonOutline';
|
|||
import Loader from '../../../components/Loader';
|
||||
import Unimodal from '../../../containers/layouts/Unimodal';
|
||||
import { REMOVE_INVITATION } from '../../../graphql/mutations';
|
||||
import { GET_USER } from '../../../graphql/quries';
|
||||
import { GET_USER } from '../../../graphql/queries';
|
||||
import { MemberInvitation } from '../../../models/graphql/invite';
|
||||
import { Member } from '../../../models/graphql/user';
|
||||
import { RootState } from '../../../redux/reducers';
|
||||
|
|
Loading…
Reference in New Issue