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:
Sayan Mondal 2021-01-13 11:40:00 +05:30 committed by GitHub
parent ae9d1062d5
commit 7137a69fc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 923 additions and 770 deletions

View File

@ -52,6 +52,13 @@ customWorkflowCard:
workflowStepper: workflowStepper:
continueError: To continue, please check the error in code. 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 ############# ############ Pages #############
@ -103,6 +110,15 @@ schedule:
at: at at: at
At: At At: At
scheduleOn: 'On' 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: workflowUnderground:
heading: 'Click on test to see detailed log of your workflow' heading: 'Click on test to see detailed log of your workflow'
@ -377,7 +393,7 @@ createWorkflow:
reliabilityScore: reliabilityScore:
header: Adjust the weights of the experiments in the workflow header: Adjust the weights of the experiments in the workflow
info: You have selected 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. infoNextStrong: The weights are relative to each other.
testHeading: Kubernetes conformance test 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. 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 future: Select date and time to start workflow in future
recurr: Recurring Schedule recurr: Recurring Schedule
rightRecurr: Choose the right recurring time to start your workflow 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: toggleComponent:
pass: Pass pass: Pass
fail: Fail fail: Fail

View File

@ -8,6 +8,7 @@ interface ButtonFilledProps {
) => void; ) => void;
isPrimary: boolean; isPrimary: boolean;
isDisabled?: boolean; isDisabled?: boolean;
isWarning?: boolean;
styles?: Object; styles?: Object;
type?: any; type?: any;
} }
@ -16,6 +17,7 @@ const ButtonFilled: React.FC<ButtonFilledProps> = ({
children, children,
isPrimary, isPrimary,
isDisabled, isDisabled,
isWarning,
styles, styles,
type, type,
}) => { }) => {
@ -29,7 +31,9 @@ const ButtonFilled: React.FC<ButtonFilledProps> = ({
type={type} type={type}
onClick={handleClick} onClick={handleClick}
className={ className={
isPrimary isWarning
? `${classes.button} ${classes.buttonWarning}`
: isPrimary
? `${classes.button} ${classes.buttonPrimary}` ? `${classes.button} ${classes.buttonPrimary}`
: `${classes.button} ${classes.buttonSecondary}` : `${classes.button} ${classes.buttonSecondary}`
} }

View File

@ -20,6 +20,12 @@ const useStyles = makeStyles((theme) => ({
backgroundColor: theme.palette.primary.dark, backgroundColor: theme.palette.primary.dark,
}, },
}, },
buttonWarning: {
backgroundColor: theme.palette.error.dark,
'&:hover': {
backgroundColor: theme.palette.error.dark,
},
},
})); }));
export default useStyles; export default useStyles;

View File

@ -7,6 +7,7 @@ interface CustomTextProps {
value: string; value: string;
id: string; id: string;
onchange: (val: string) => void; onchange: (val: string) => void;
isEditable?: boolean;
} }
const useStyles = makeStyles((theme: Theme) => ({ 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 // 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 [isDisabled, setIsDisabled] = React.useState(true);
const [newValue, setNewValue] = React.useState<string>(value); const [newValue, setNewValue] = React.useState<string>(value);
@ -58,15 +64,19 @@ const CustomText: React.FC<CustomTextProps> = ({ value, id, onchange }) => {
}} }}
onChange={handleChange} onChange={handleChange}
/> />
{isDisabled ? ( {isEditable ? (
<IconButton size="medium" onClick={handleEdit}> <>
<EditIcon className={classes.editBtn} data-cy="edit" /> {isDisabled ? (
</IconButton> <IconButton size="medium" onClick={handleEdit}>
) : ( <EditIcon className={classes.editBtn} data-cy="edit" />
<IconButton size="medium" onClick={handleSave}> </IconButton>
<SaveIcon className={classes.saveBtn} data-cy="save" /> ) : (
</IconButton> <IconButton size="medium" onClick={handleSave}>
)} <SaveIcon className={classes.saveBtn} data-cy="save" />
</IconButton>
)}
</>
) : null}
</div> </div>
); );
}; };

View File

@ -1,24 +1,24 @@
import { useLazyQuery, useMutation } from '@apollo/client';
import { Typography } from '@material-ui/core'; import { Typography } from '@material-ui/core';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMutation, useLazyQuery } from '@apollo/client';
import { useSelector } from 'react-redux'; 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 Scaffold from '../../../containers/layouts/Scaffold';
import Unimodal from '../../../containers/layouts/Unimodal';
import { GET_CLUSTER, USER_CLUSTER_REG } from '../../../graphql';
import { import {
Cluster,
CreateClusterInput, CreateClusterInput,
CreateClusterInputResponse, CreateClusterInputResponse,
Cluster,
} from '../../../models/graphql/clusterData'; } from '../../../models/graphql/clusterData';
import { USER_CLUSTER_REG, GET_CLUSTER } from '../../../graphql'; import { history } from '../../../redux/configureStore';
import { RootState } from '../../../redux/reducers'; 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 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 ConnectTarget = () => {
const classes = useStyles(); const classes = useStyles();
@ -145,7 +145,7 @@ const ConnectTarget = () => {
hasCloseBtn hasCloseBtn
> >
<div className={classes.body}> <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}> <Typography className={classes.heading}>
{t('ConnectTargets.title')} {t('ConnectTargets.title')}
<br /> <br />

View File

@ -1,9 +1,11 @@
/* eslint-disable jsx-a11y/click-events-have-key-events */ /* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/no-static-element-interactions */
import { Tooltip, Zoom } from '@material-ui/core';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { preDefinedWorkflowData } from '../../models/predefinedWorkflow'; import { preDefinedWorkflowData } from '../../models/predefinedWorkflow';
import { RootState } from '../../redux/reducers'; import { RootState } from '../../redux/reducers';
import trimString from '../../utils/trim';
import parsed from '../../utils/yamlUtils'; import parsed from '../../utils/yamlUtils';
import useStyles from './styles'; import useStyles from './styles';
@ -104,11 +106,27 @@ const CardContent: React.FC<preDefinedWorkflowData> = ({
<div className={classes.provider}>Contributed by {provider}</div> <div className={classes.provider}>Contributed by {provider}</div>
</div> </div>
{description ? ( {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 /> <span />
)} )}
{description ? description.length < 28 ? <br /> : <div /> : <span />}
</div> </div>
{/* <Divider variant="fullWidth" className={classes.horizontalLine} /> {/* <Divider variant="fullWidth" className={classes.horizontalLine} />
<div className={classes.details}> <div className={classes.details}>

View File

@ -5,7 +5,7 @@ const useStyles = makeStyles((theme) => ({
customCard: { customCard: {
background: theme.palette.cards.background, background: theme.palette.cards.background,
height: '15.625rem', height: '16rem',
width: '11.875rem', width: '11.875rem',
borderRadius: 3, borderRadius: 3,
fontSize: '0.875rem', fontSize: '0.875rem',
@ -23,12 +23,20 @@ const useStyles = makeStyles((theme) => ({
width: '70%', width: '70%',
}, },
// Tooltip
tooltip: {
'.MuiTooltip-tooltip': {
maxWidth: '18.75rem',
},
},
// CardContent // CardContent
card: { card: {
width: theme.spacing(23), width: theme.spacing(23),
background: theme.palette.background.paper, background: theme.palette.background.paper,
borderRadius: 3, borderRadius: 3,
height: '16rem',
overflow: 'hidden', overflow: 'hidden',
fontSize: 14, fontSize: 14,
margin: theme.spacing(1), margin: theme.spacing(1),
@ -77,6 +85,7 @@ const useStyles = makeStyles((theme) => ({
// CARD CONTENT // CARD CONTENT
cardContent: { cardContent: {
color: theme.palette.text.primary, color: theme.palette.text.primary,
height: '16rem',
}, },
title: { title: {

View File

@ -18,10 +18,11 @@ import {
import { experimentMap, WorkflowData } from '../../models/redux/workflow'; import { experimentMap, WorkflowData } from '../../models/redux/workflow';
import useActions from '../../redux/actions'; import useActions from '../../redux/actions';
import * as TabActions from '../../redux/actions/tabs'; import * as TabActions from '../../redux/actions/tabs';
import * as WorkflowActions from '../../redux/actions/workflow';
import * as TemplateSelectionActions from '../../redux/actions/template'; import * as TemplateSelectionActions from '../../redux/actions/template';
import * as WorkflowActions from '../../redux/actions/workflow';
import { history } from '../../redux/configureStore'; import { history } from '../../redux/configureStore';
import { RootState } from '../../redux/reducers'; import { RootState } from '../../redux/reducers';
import { cronWorkflow, workflowOnce } from '../../utils/workflowTemplate';
import parsed from '../../utils/yamlUtils'; import parsed from '../../utils/yamlUtils';
import ChooseWorkflow from '../../views/CreateWorkflow/ChooseWorkflow/index'; import ChooseWorkflow from '../../views/CreateWorkflow/ChooseWorkflow/index';
import ReliablityScore from '../../views/CreateWorkflow/ReliabilityScore'; import ReliablityScore from '../../views/CreateWorkflow/ReliabilityScore';
@ -34,7 +35,6 @@ import ButtonOutline from '../Button/ButtonOutline';
import QontoConnector from './quontoConnector'; import QontoConnector from './quontoConnector';
import useStyles from './styles'; import useStyles from './styles';
import useQontoStepIconStyles from './useQontoStepIconStyles'; import useQontoStepIconStyles from './useQontoStepIconStyles';
import { cronWorkflow, workflowOnce } from '../../utils/workflowTemplate';
function getSteps(): string[] { function getSteps(): string[] {
return [ return [
@ -98,7 +98,7 @@ function getStepContent(
<ChooseAWorkflowCluster gotoStep={(page: number) => gotoStep(page)} /> <ChooseAWorkflowCluster gotoStep={(page: number) => gotoStep(page)} />
); );
case 1: case 1:
return <ChooseWorkflow />; return <ChooseWorkflow isEditable />;
case 2: case 2:
return <TuneWorkflow />; return <TuneWorkflow />;
case 3: case 3:
@ -106,7 +106,9 @@ function getStepContent(
case 4: case 4:
return <ScheduleWorkflow />; return <ScheduleWorkflow />;
case 5: case 5:
return <VerifyCommit gotoStep={(page: number) => gotoStep(page)} />; return (
<VerifyCommit isEditable gotoStep={(page: number) => gotoStep(page)} />
);
default: default:
return ( return (
<ChooseAWorkflowCluster gotoStep={(page: number) => gotoStep(page)} /> <ChooseAWorkflowCluster gotoStep={(page: number) => gotoStep(page)} />
@ -390,19 +392,20 @@ const CustomStepper = () => {
> >
<div> <div>
<img <img
src="icons/finish.svg" src="/icons/finish.svg"
className={classes.mark} className={classes.mark}
alt="mark" alt="mark"
/> />
<div className={classes.heading}> <div className={classes.heading}>
A new chaos workflow, {t('workflowStepper.aNewChaosWorkflow')}
<br /> <br />
<strong>was successfully created!</strong> <span className={classes.successful}>{name}</span>,
<br />
<strong>{t('workflowStepper.successful')}</strong>
</div> </div>
<div className={classes.headWorkflow}> <div className={classes.headWorkflow}>
Congratulations on creating your first workflow! Now {t('workflowStepper.congratulationsSub1')} <br />{' '}
information about <br /> it will be displayed on the main {t('workflowStepper.congratulationsSub2')}
screen of the application.
</div> </div>
<div className={classes.button}> <div className={classes.button}>
<ButtonFilled <ButtonFilled
@ -414,7 +417,7 @@ const CustomStepper = () => {
history.push('/workflows'); history.push('/workflows');
}} }}
> >
<div>Back to workflow</div> <div>{t('workflowStepper.workflowBtn')}</div>
</ButtonFilled> </ButtonFilled>
</div> </div>
</div> </div>

View File

@ -64,6 +64,10 @@ const useStyles = makeStyles((theme: Theme) => ({
textAlign: 'center', textAlign: 'center',
marginTop: theme.spacing(6), marginTop: theme.spacing(6),
}, },
successful: {
fontSize: '2.2rem',
fontWeight: 'bold',
},
})); }));
export default useStyles; export default useStyles;

View File

@ -112,7 +112,7 @@ const Routes: React.FC<RoutesProps> = ({ isOwner, isProjectAvailable }) => {
/> />
<Route <Route
exact exact
path="/workflows/schedule/:scheduleId" path="/workflows/schedule/:projectID/:workflowName"
component={SchedulePage} component={SchedulePage}
/> />
<Route <Route

View File

@ -1,3 +1,3 @@
export * from './mutations'; export * from './mutations';
export * from './quries'; export * from './queries';
export * from './subscriptions'; export * from './subscriptions';

View File

@ -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` export const UPDATE_DETAILS = gql`
mutation updateUser($user: UpdateUserInput!) { mutation updateUser($user: UpdateUserInput!) {
updateUser(user: $user) updateUser(user: $user)

View File

@ -4,6 +4,7 @@ export interface WeightMap {
} }
export interface CreateWorkFlowInput { export interface CreateWorkFlowInput {
ChaosWorkFlowInput: { ChaosWorkFlowInput: {
workflow_id?: string;
workflow_manifest: string; workflow_manifest: string;
cronSyntax: string; cronSyntax: string;
workflow_name: 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 { export interface CreateWorkflowResponse {
cluster_id: string; cluster_id: string;
is_active: boolean; is_active: boolean;

View File

@ -35,7 +35,9 @@ export interface WorkflowData {
description: string; description: string;
weights: experimentMap[]; weights: experimentMap[];
isCustomWorkflow: boolean; isCustomWorkflow: boolean;
isRecurring: boolean;
namespace: string; namespace: string;
workflow_id?: string;
clustername: string; clustername: string;
clusterid: string; clusterid: string;
cronSyntax: string; cronSyntax: string;

View File

@ -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;

View File

@ -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;

View File

@ -1,403 +1,554 @@
import { import { useMutation, useQuery } from '@apollo/client';
Divider, import Step from '@material-ui/core/Step';
FormControl, import { StepIconProps } from '@material-ui/core/StepIcon';
FormControlLabel, import StepLabel from '@material-ui/core/StepLabel';
Radio, import Stepper from '@material-ui/core/Stepper';
RadioGroup, import Typography from '@material-ui/core/Typography';
Select, import React, { useEffect } from 'react';
Typography,
} from '@material-ui/core';
import React from 'react';
import { useTranslation } from 'react-i18next'; 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 ButtonFilled from '../../components/Button/ButtonFilled';
import ButtonOutline from '../../components/Button/ButtonOutline'; import ButtonOutline from '../../components/Button/ButtonOutline';
import CustomDate from '../../components/DateTime/CustomDate/index'; import Loader from '../../components/Loader';
import CustomTime from '../../components/DateTime/CustomTime/index'; 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 Scaffold from '../../containers/layouts/Scaffold';
import SetTime from './SetTime/index'; import Unimodal from '../../containers/layouts/Unimodal';
import useStyles from './styles'; 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 = () => { interface URLParams {
const { t } = useTranslation(); workflowName: string;
const start = 0; projectID: string;
const end = 10; }
const interval = 2;
const classes = useStyles(); interface Weights {
// controls radio buttons experimentName: string;
const [value, setValue] = React.useState('now'); weight: number;
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => { }
setValue(event.target.value);
};
// controls inner radio buttons of recurring schedule function getSteps(): string[] {
const [valueDef, setValueDef] = React.useState(''); return [
const handleChangeInstance = (event: React.ChangeEvent<HTMLInputElement>) => { 'Target Cluster',
setValueDef(event.target.value); 'Choose a workflow',
}; 'Tune workflow',
'Reliability score',
'Schedule',
'Verify and Commit',
];
}
// sets weekdays function QontoStepIcon(props: StepIconProps) {
const [days, setDays] = React.useState('Monday'); const classes = useQontoStepIconStyles();
const { active, completed } = props;
// sets dates if (completed) {
const [dates, setDates] = React.useState(1); return (
<div
// stores dates in an array className={`${classes.root} ${
const names: number[] = [1]; active ? classes.active : classes.completed
for (let i = 1; i <= 30; i += 1) { }`}
names[i] = i + 1; >
<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[] = [ return (
'Monday', <div
'Tuesday', className={`${classes.root} ${
'Wednesday', active ? classes.active : classes.completed
'Thursday', }`}
'Friday', >
'Saturday', {/* <img src="./icons/workflowNotActive.svg" /> */}
'Sunday', <div className={classes.outerCircle}>
]; <div className={classes.innerCircle} />
const [selectedDate, setSelectedDate] = React.useState<Date | null>( </div>
new Date(Date.now()) </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) => { const wfDetails =
setSelectedDate(date); 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 ( return (
<Scaffold> <Scaffold>
<div className={classes.rootContainer}> {loading ? (
<Typography className={classes.mainHeader}> <Loader />
{t('schedule.heading')} ) : (
</Typography>
<Typography className={classes.headerDesc}>
{t('schedule.headingDesc')}
</Typography>
<div className={classes.root}> <div className={classes.root}>
<div className={classes.scHeader}> <Stepper
{/* Upper segment */} activeStep={activeStep}
<div className={classes.scSegments}> 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> <div>
<Typography className={classes.headerText}> <Unimodal
<strong>{t('schedule.headingText')}</strong> open={open}
</Typography> handleClose={handleClose}
aria-labelledby="simple-modal-title"
<div className={classes.schBody}> aria-describedby="simple-modal-description"
<Typography align="left" className={classes.description}> hasCloseBtn
{t('schedule.description')}
</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 */} <div>
<FormControlLabel <img
value="now" src="/icons/finish.svg"
control={<Radio />} className={classes.mark}
label={ alt="mark"
<Typography className={classes.radioText}> />
{t('schedule.scheduleNow')} <div className={classes.heading}>
</Typography> {t('schedule.chaosWorkflow')}
} <br />
/> <strong>{t('schedule.successful')}</strong>
<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>
</div> </div>
) : ( <div className={classes.headWorkflow}>
<></> {t('schedule.congratulationsSub1')}
)} <br />
<FormControlLabel {t('schedule.congratulationsSub2')}
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> </div>
) : ( <div className={classes.button}>
<></> <ButtonFilled
)} isPrimary
<FormControlLabel data-cy="selectFinish"
value="recurringScedule" handleClick={() => {
control={<Radio />} setOpen(false);
label={ tabs.changeWorkflowsTabs(0);
<Typography className={classes.radioText}> history.push('/workflows');
{t('schedule.scheduleRecurring')} }}
</Typography> >
} <div>{t('workflowStepper.workflowBtn')}</div>
/> </ButtonFilled>
{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,
},
}}
>
{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> </div>
) : ( </div>
<></> </Unimodal>
)} {getStepContent(activeStep, (page: number) =>
</RadioGroup> gotoStep({ page })
</FormControl> )}
</div> </div>
<Divider /> {/* Control Buttons */}
<div className={classes.submitDiv}>
<ButtonOutline isDisabled={false} handleClick={() => {}}> <div className={classes.buttonGroup}>
<Typography>Cancel</Typography> {activeStep === steps.length - 2 ? (
</ButtonOutline> <ButtonOutline isDisabled handleClick={handleBack}>
<div style={{ marginLeft: 'auto' }}> <Typography>Back</Typography>
<ButtonFilled isPrimary handleClick={() => {}}> </ButtonOutline>
<Typography>{t('schedule.save')}</Typography> ) : activeStep !== 1 ? (
</ButtonFilled> <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>
</div> </div>
</div> </div>
</div> )}
</Scaffold> </Scaffold>
); );
}; };
export default SchedulePage; export default EditScheduledWorkflow;

View File

@ -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;

View File

@ -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}';

View File

@ -14,6 +14,7 @@ const initialState: WorkflowData = {
description: '', description: '',
weights: [], weights: [],
isCustomWorkflow: false, isCustomWorkflow: false,
isRecurring: false,
namespace: 'litmus', namespace: 'litmus',
clusterid: '', clusterid: '',
cronSyntax: '', cronSyntax: '',

View File

@ -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

View File

@ -0,0 +1,5 @@
const trimString = (string: string, length: number): string => {
return string.length > length ? `${string.substring(0, length)}...` : string;
};
export default trimString;

View File

@ -24,7 +24,7 @@ import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import Loader from '../../../../components/Loader'; import Loader from '../../../../components/Loader';
import { WORKFLOW_LIST_DETAILS } from '../../../../graphql/quries'; import { WORKFLOW_LIST_DETAILS } from '../../../../graphql/queries';
import { import {
ExecutionData, ExecutionData,
WeightageMap, WeightageMap,

View File

@ -8,31 +8,47 @@ import {
Typography, Typography,
} from '@material-ui/core'; } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import ChevronRightIcon from '@material-ui/icons/ChevronRight';
import GetAppIcon from '@material-ui/icons/GetApp';
import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
import MoreVertIcon from '@material-ui/icons/MoreVert'; import MoreVertIcon from '@material-ui/icons/MoreVert';
import cronstrue from 'cronstrue';
import moment from 'moment'; import moment from 'moment';
import React from 'react'; import React from 'react';
import cronstrue from 'cronstrue'; import { useTranslation } from 'react-i18next';
import YAML from 'yaml';
import GetAppIcon from '@material-ui/icons/GetApp';
import { useSelector } from 'react-redux'; 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 { ScheduleWorkflow } from '../../../models/graphql/scheduleData';
import useStyles from './styles'; import useActions from '../../../redux/actions';
import ExperimentPoints from './ExperimentPoints'; import * as WorkflowActions from '../../../redux/actions/workflow';
import { history } from '../../../redux/configureStore';
import { RootState } from '../../../redux/reducers'; import { RootState } from '../../../redux/reducers';
import { ReactComponent as CrossMarkIcon } from '../../../svg/crossmark.svg';
import ExperimentPoints from './ExperimentPoints';
import useStyles from './styles';
interface TableDataProps { interface TableDataProps {
data: ScheduleWorkflow; data: ScheduleWorkflow;
deleteRow: (wfid: string) => void; deleteRow: (wfid: string) => void;
} }
interface Weights {
experimentName: string;
weight: number;
}
const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => { const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
const classes = useStyles(); const classes = useStyles();
const { t } = useTranslation();
// States for PopOver to display Experiment Weights // States for PopOver to display Experiment Weights
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [popAnchorEl, setPopAnchorEl] = React.useState<null | HTMLElement>( const [popAnchorEl, setPopAnchorEl] = React.useState<null | HTMLElement>(
null null
); );
const [isModalOpen, setIsModalOpen] = React.useState<boolean>(false);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const isOpen = Boolean(popAnchorEl); const isOpen = Boolean(popAnchorEl);
const id = isOpen ? 'simple-popover' : undefined; const id = isOpen ? 'simple-popover' : undefined;
@ -40,6 +56,7 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
setPopAnchorEl(null); setPopAnchorEl(null);
}; };
const workflow = useActions(WorkflowActions);
const userData = useSelector((state: RootState) => state.userData); const userData = useSelector((state: RootState) => state.userData);
const handlePopOverClick = (event: React.MouseEvent<HTMLElement>) => { const handlePopOverClick = (event: React.MouseEvent<HTMLElement>) => {
@ -77,6 +94,19 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
return 'Date not available'; 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 ( return (
<> <>
<TableCell className={classes.workflowNameData}> <TableCell className={classes.workflowNameData}>
@ -171,6 +201,22 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
open={open} open={open}
onClose={handleClose} 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 <MenuItem
value="Download" value="Download"
onClick={() => onClick={() =>
@ -188,10 +234,7 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
</div> </div>
</MenuItem> </MenuItem>
{userData.userRole !== 'Viewer' ? ( {userData.userRole !== 'Viewer' ? (
<MenuItem <MenuItem value="Analysis" onClick={() => setIsModalOpen(true)}>
value="Analysis"
onClick={() => deleteRow(data.workflow_id)}
>
<div className={classes.expDiv}> <div className={classes.expDiv}>
<img <img
src="/icons/deleteSchedule.svg" src="/icons/deleteSchedule.svg"
@ -209,6 +252,39 @@ const TableData: React.FC<TableDataProps> = ({ data, deleteRow }) => {
) : null} ) : null}
</Menu> </Menu>
</TableCell> </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>
) : (
<></>
)}
</> </>
); );
}; };

View File

@ -181,6 +181,34 @@ const useStyles = makeStyles((theme) => ({
: theme.palette.error.dark, : theme.palette.error.dark,
fontWeight: 500, 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; export default useStyles;

View File

@ -59,8 +59,10 @@ const useStyles = makeStyles((theme: Theme) => ({
// Experiment Details // Experiment Details
experimentWrapperDiv: { experimentWrapperDiv: {
display: 'inline-block', display: 'grid',
margin: '1.6rem 0', margin: '2rem',
gridTemplateColumns: '1fr 1fr 1fr 1fr',
gridGap: '1.5rem',
}, },
tests: { tests: {
width: '17rem', width: '17rem',

View File

@ -15,7 +15,7 @@ const Templates = () => {
const testWeights: number[] = []; const testWeights: number[] = [];
// Setting initial selected template ID to 0 // Setting initial selected template ID to 0
template.selectTemplate({ selectedTemplateID: 0, isDisable: true }); template.selectTemplate({ selectedTemplateID: -1, isDisable: true });
const selectWorkflow = (index: number) => { const selectWorkflow = (index: number) => {
// Updating template ID to the selected one // Updating template ID to the selected one

View File

@ -1,14 +1,15 @@
import { Typography } from '@material-ui/core'; import { Typography } from '@material-ui/core';
import Divider from '@material-ui/core/Divider'; import Divider from '@material-ui/core/Divider';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import ButtonFilled from '../../../components/Button/ButtonFilled'; import ButtonFilled from '../../../components/Button/ButtonFilled';
import ButtonOutline from '../../../components/Button/ButtonOutline'; import ButtonOutline from '../../../components/Button/ButtonOutline';
import InputField from '../../../components/InputField'; import InputField from '../../../components/InputField';
import PredifinedWorkflows from '../../../components/PredifinedWorkflows'; import PredifinedWorkflows from '../../../components/PredifinedWorkflows';
import workflowsList from '../../../components/PredifinedWorkflows/data'; import workflowsList from '../../../components/PredifinedWorkflows/data';
import Unimodal from '../../../containers/layouts/Unimodal'; import Unimodal from '../../../containers/layouts/Unimodal';
import { WorkflowData } from '../../../models/redux/workflow';
import useActions from '../../../redux/actions'; import useActions from '../../../redux/actions';
import * as TemplateSelectionActions from '../../../redux/actions/template'; import * as TemplateSelectionActions from '../../../redux/actions/template';
import * as WorkflowActions from '../../../redux/actions/workflow'; import * as WorkflowActions from '../../../redux/actions/workflow';
@ -18,7 +19,11 @@ import useStyles, { CssTextField } from './styles';
// import { getWkfRunCount } from "../../utils"; // import { getWkfRunCount } from "../../utils";
const ChooseWorkflow: React.FC = () => { interface ChooseWorkflowProps {
isEditable?: boolean;
}
const ChooseWorkflow: React.FC<ChooseWorkflowProps> = ({ isEditable }) => {
const classes = useStyles(); const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
@ -30,6 +35,9 @@ const ChooseWorkflow: React.FC = () => {
const selectedTemplateID = useSelector( const selectedTemplateID = useSelector(
(state: RootState) => state.selectTemplate.selectedTemplateID (state: RootState) => state.selectTemplate.selectedTemplateID
); );
const workflowData: WorkflowData = useSelector(
(state: RootState) => state.workflowData
);
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const isSuccess = React.useRef<boolean>(false); const isSuccess = React.useRef<boolean>(false);
@ -92,17 +100,24 @@ const ChooseWorkflow: React.FC = () => {
const selectWorkflow = (index: number) => { const selectWorkflow = (index: number) => {
template.selectTemplate({ selectedTemplateID: index, isDisable: false }); template.selectTemplate({ selectedTemplateID: index, isDisable: false });
const timeStampBasedWorkflowName: string = `argowf-chaos-${ const timeStampBasedWorkflowName: string = isEditable
workflowsList[index].title ? `argowf-chaos-${workflowsList[index].title}-${Math.round(
}-${Math.round(new Date().getTime() / 1000)}`; new Date().getTime() / 1000
)}`
: workflowData.name;
workflow.setWorkflowDetails({ workflow.setWorkflowDetails({
name: timeStampBasedWorkflowName, name: timeStampBasedWorkflowName,
link: workflowsList[index].chaosWkfCRDLink, link: workflowsList[index].chaosWkfCRDLink,
id: workflowsList[index].workflowID, id: workflowsList[index].workflowID,
yaml: 'none', yaml: 'none',
description: workflowsList[index].description, description: isEditable
isCustomWorkflow: workflowsList[index].isCustom, ? workflowsList[index].description
: workflowData.description,
isCustomWorkflow: isEditable
? workflowsList[index].isCustom
: workflowData.isCustomWorkflow,
isRecurring: false,
}); });
setWorkflowData({ setWorkflowData({
@ -119,16 +134,22 @@ const ChooseWorkflow: React.FC = () => {
useEffect(() => { useEffect(() => {
const index = selectedTemplateID; const index = selectedTemplateID;
const timeStampBasedWorkflowName: string = `argowf-chaos-${ const timeStampBasedWorkflowName: string = isEditable
workflowsList[index].title ? `argowf-chaos-${workflowsList[index].title}-${Math.round(
}-${Math.round(new Date().getTime() / 1000)}`; new Date().getTime() / 1000
)}`
: workflowData.name;
workflow.setWorkflowDetails({ workflow.setWorkflowDetails({
name: timeStampBasedWorkflowName, name: timeStampBasedWorkflowName,
link: workflowsList[index].chaosWkfCRDLink, link: workflowsList[index].chaosWkfCRDLink,
id: workflowsList[index].workflowID, id: workflowsList[index].workflowID,
yaml: 'none', yaml: 'none',
description: workflowsList[index].description, description: workflowsList[index].description,
isCustomWorkflow: workflowsList[index].isCustom, isCustomWorkflow: isEditable
? workflowsList[index].isCustom
: workflowData.isCustomWorkflow,
isRecurring: false,
}); });
setWorkflowData({ setWorkflowData({
@ -199,6 +220,7 @@ const ChooseWorkflow: React.FC = () => {
<InputField <InputField
// id="filled-workflowname-input" // id="filled-workflowname-input"
label={t('createWorkflow.chooseWorkflow.label.workflowName')} label={t('createWorkflow.chooseWorkflow.label.workflowName')}
disabled={!isEditable}
styles={{ styles={{
width: '100%', width: '100%',
}} }}

View File

@ -1,6 +1,7 @@
import { useLazyQuery, useQuery } from '@apollo/client'; import { useLazyQuery, useQuery } from '@apollo/client';
import { import {
Button, Button,
ClickAwayListener,
FormControl, FormControl,
FormControlLabel, FormControlLabel,
IconButton, IconButton,
@ -14,28 +15,27 @@ import {
RadioGroup, RadioGroup,
Select, Select,
Typography, Typography,
ClickAwayListener,
} from '@material-ui/core'; } 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 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 YAML from 'yaml';
import ButtonFilled from '../../../../components/Button/ButtonFilled'; import ButtonFilled from '../../../../components/Button/ButtonFilled';
import InputField from '../../../../components/InputField'; import InputField from '../../../../components/InputField';
import Loader from '../../../../components/Loader'; import Loader from '../../../../components/Loader';
import { GET_CHARTS_DATA, GET_HUB_STATUS } from '../../../../graphql'; import { GET_CHARTS_DATA, GET_HUB_STATUS } from '../../../../graphql';
import { GET_EXPERIMENT_YAML } from '../../../../graphql/queries';
import { MyHubDetail } from '../../../../models/graphql/user'; import { MyHubDetail } from '../../../../models/graphql/user';
import { Charts, HubStatus } from '../../../../models/redux/myhub'; 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 WorkflowDetails from '../../../../pages/WorkflowDetails';
import { GET_EXPERIMENT_YAML } from '../../../../graphql/quries'; import useActions from '../../../../redux/actions';
import BackButton from '../BackButton';
import * as TemplateSelectionActions from '../../../../redux/actions/template'; import * as TemplateSelectionActions from '../../../../redux/actions/template';
import * as WorkflowActions from '../../../../redux/actions/workflow';
import { history } from '../../../../redux/configureStore'; import { history } from '../../../../redux/configureStore';
import { RootState } from '../../../../redux/reducers';
import BackButton from '../BackButton';
import useStyles, { CustomTextField, MenuProps } from './styles';
interface WorkflowDetails { interface WorkflowDetails {
workflow_name: string; workflow_name: string;

View File

@ -1,18 +1,18 @@
import { useLazyQuery } from '@apollo/client';
import { Typography } from '@material-ui/core'; import { Typography } from '@material-ui/core';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import YAML from 'yaml';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useLazyQuery } from '@apollo/client'; import { useSelector } from 'react-redux';
import BackButton from '../BackButton'; import YAML from 'yaml';
import ButtonFilled from '../../../../components/Button/ButtonFilled'; import ButtonFilled from '../../../../components/Button/ButtonFilled';
import InputField from '../../../../components/InputField'; import InputField from '../../../../components/InputField';
import useStyles from './styles'; import Loader from '../../../../components/Loader';
import { RootState } from '../../../../redux/reducers'; import { GET_ENGINE_YAML } from '../../../../graphql/queries';
import useActions from '../../../../redux/actions'; import useActions from '../../../../redux/actions';
import * as WorkflowActions from '../../../../redux/actions/workflow'; import * as WorkflowActions from '../../../../redux/actions/workflow';
import Loader from '../../../../components/Loader'; import { RootState } from '../../../../redux/reducers';
import { GET_ENGINE_YAML } from '../../../../graphql/quries'; import BackButton from '../BackButton';
import useStyles from './styles';
interface EnvValues { interface EnvValues {
name: string; name: string;

View File

@ -1,7 +1,7 @@
import { Typography } from '@material-ui/core'; import { Typography } from '@material-ui/core';
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import ButtonFilled from '../../../components/Button/ButtonFilled'; import ButtonFilled from '../../../components/Button/ButtonFilled';
import ButtonOutline from '../../../components/Button/ButtonOutline'; import ButtonOutline from '../../../components/Button/ButtonOutline';
import Center from '../../../containers/layouts/Center'; import Center from '../../../containers/layouts/Center';
@ -95,7 +95,7 @@ const ReliablityScore = () => {
data-cy="testRunButton" data-cy="testRunButton"
> >
<div className={classes.buttonOutlineDiv}> <div className={classes.buttonOutlineDiv}>
<img src="icons/video.png" alt="Play icon" /> <img src="/icons/video.png" alt="Play icon" />
<Typography className={classes.buttonOutlineText}> <Typography className={classes.buttonOutlineText}>
{t('createWorkflow.reliabilityScore.button.demo')} {t('createWorkflow.reliabilityScore.button.demo')}
</Typography> </Typography>

View File

@ -8,17 +8,18 @@ import {
Typography, Typography,
} from '@material-ui/core'; } from '@material-ui/core';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import YAML from 'yaml';
import CustomDate from '../../../components/DateTime/CustomDate/index'; import CustomDate from '../../../components/DateTime/CustomDate/index';
import CustomTime from '../../../components/DateTime/CustomTime/index'; import CustomTime from '../../../components/DateTime/CustomTime/index';
import { WorkflowData } from '../../../models/redux/workflow'; import { WorkflowData } from '../../../models/redux/workflow';
import useActions from '../../../redux/actions'; 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 { RootState } from '../../../redux/reducers';
import SetTime from './SetTime/index'; import SetTime from './SetTime/index';
import useStyles from './styles'; import useStyles from './styles';
import * as WorkflowActions from '../../../redux/actions/workflow';
import * as TemplateSelectionActions from '../../../redux/actions/template';
interface ScheduleSyntax { interface ScheduleSyntax {
minute: string | undefined; minute: string | undefined;
@ -71,6 +72,10 @@ const ScheduleWorkflow: React.FC = () => {
workflow.setWorkflowDetails({ workflow.setWorkflowDetails({
cronSyntax, cronSyntax,
}); });
if (value === 'disable')
workflow.setWorkflowDetails({
isDisabled: true,
});
}, [cronValue]); }, [cronValue]);
const classes = useStyles(); const classes = useStyles();
@ -188,6 +193,9 @@ const ScheduleWorkflow: React.FC = () => {
// UseEffect to update the values of CronSyntax on radio button change // UseEffect to update the values of CronSyntax on radio button change
useEffect(() => { useEffect(() => {
if (value === 'now') { if (value === 'now') {
workflow.setWorkflowDetails({
isDisabled: false,
});
setValueDef(''); setValueDef('');
setCronValue({ setCronValue({
minute: '', minute: '',
@ -197,6 +205,11 @@ const ScheduleWorkflow: React.FC = () => {
day_week: '', day_week: '',
}); });
} }
if (value === 'disable') {
workflow.setWorkflowDetails({
isDisabled: true,
});
}
if (value === 'specificTime') { if (value === 'specificTime') {
setValueDef(''); setValueDef('');
setCronValue({ setCronValue({
@ -226,6 +239,9 @@ const ScheduleWorkflow: React.FC = () => {
} }
} }
if (valueDef === 'everyHr') { if (valueDef === 'everyHr') {
workflow.setWorkflowDetails({
isDisabled: false,
});
setCronValue({ setCronValue({
minute: minute.toString(), minute: minute.toString(),
hour: '0-23', hour: '0-23',
@ -235,6 +251,9 @@ const ScheduleWorkflow: React.FC = () => {
}); });
} }
if (valueDef === 'everyDay') { if (valueDef === 'everyDay') {
workflow.setWorkflowDetails({
isDisabled: false,
});
setCronValue({ setCronValue({
minute: selectedTime?.getMinutes().toString(), minute: selectedTime?.getMinutes().toString(),
hour: selectedTime?.getHours().toString(), hour: selectedTime?.getHours().toString(),
@ -244,6 +263,9 @@ const ScheduleWorkflow: React.FC = () => {
}); });
} }
if (valueDef === 'everyWeek') { if (valueDef === 'everyWeek') {
workflow.setWorkflowDetails({
isDisabled: false,
});
setCronValue({ setCronValue({
minute: selectedTime?.getMinutes().toString(), minute: selectedTime?.getMinutes().toString(),
hour: selectedTime?.getHours().toString(), hour: selectedTime?.getHours().toString(),
@ -253,6 +275,9 @@ const ScheduleWorkflow: React.FC = () => {
}); });
} }
if (valueDef === 'everyMonth') { if (valueDef === 'everyMonth') {
workflow.setWorkflowDetails({
isDisabled: false,
});
setCronValue({ setCronValue({
minute: selectedTime?.getMinutes().toString(), minute: selectedTime?.getMinutes().toString(),
hour: selectedTime?.getHours().toString(), hour: selectedTime?.getHours().toString(),
@ -262,6 +287,9 @@ const ScheduleWorkflow: React.FC = () => {
}); });
} }
if (value === 'recurringSchedule' && valueDef === '') { if (value === 'recurringSchedule' && valueDef === '') {
workflow.setWorkflowDetails({
isDisabled: false,
});
template.selectTemplate({ isDisable: true }); template.selectTemplate({ isDisable: true });
} else { } else {
template.selectTemplate({ isDisable: false }); template.selectTemplate({ isDisable: false });
@ -279,7 +307,7 @@ const ScheduleWorkflow: React.FC = () => {
<div className={classes.scHeader}> <div className={classes.scHeader}>
{/* Upper segment */} {/* Upper segment */}
<div className={classes.scSegments}> <div className={classes.scSegments}>
<div> <div aria-details="content wrapper" style={{ width: '90%' }}>
<Typography className={classes.headerText}> <Typography className={classes.headerText}>
<strong>{t('createWorkflow.scheduleWorkflow.header')}</strong> <strong>{t('createWorkflow.scheduleWorkflow.header')}</strong>
</Typography> </Typography>
@ -308,16 +336,30 @@ const ScheduleWorkflow: React.FC = () => {
onChange={handleChange} onChange={handleChange}
> >
{/* options to choose schedule */} {/* options to choose schedule */}
<FormControlLabel {!workflowData.isRecurring ? (
value="now" <FormControlLabel
control={<Radio />} value="now"
label={ control={<Radio />}
<Typography className={classes.radioText}> label={
{t('createWorkflow.scheduleWorkflow.radio.now')} <Typography className={classes.radioText}>
</Typography> {t('createWorkflow.scheduleWorkflow.radio.now')}
} </Typography>
/> }
<FormControlLabel />
) : YAML.parse(workflowData.yaml).spec.suspend === false ? (
<FormControlLabel
value="disable"
control={<Radio />}
label={
<Typography className={classes.radioText}>
Disable Schedule
</Typography>
}
/>
) : (
<></>
)}
{/* <FormControlLabel
value="specificTime" value="specificTime"
disabled disabled
control={<Radio />} control={<Radio />}
@ -326,7 +368,7 @@ const ScheduleWorkflow: React.FC = () => {
{t('createWorkflow.scheduleWorkflow.radio.specific')} {t('createWorkflow.scheduleWorkflow.radio.specific')}
</Typography> </Typography>
} }
/> /> */}
{value === 'specificTime' ? ( {value === 'specificTime' ? (
<div className={classes.schLater}> <div className={classes.schLater}>
<Typography className={classes.captionText}> <Typography className={classes.captionText}>

View File

@ -40,6 +40,7 @@ const useStyles = makeStyles((theme: Theme) => ({
radioText: { radioText: {
fontSize: '0.875rem', fontSize: '0.875rem',
color: theme.palette.text.primary,
}, },
description: { description: {
width: '32.18rem', width: '32.18rem',

View File

@ -1,9 +1,9 @@
import { Typography } from '@material-ui/core'; import { Typography } from '@material-ui/core';
import Divider from '@material-ui/core/Divider'; import Divider from '@material-ui/core/Divider';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import YAML from 'yaml'; import YAML from 'yaml';
import { useTranslation } from 'react-i18next';
import Loader from '../../../components/Loader'; import Loader from '../../../components/Loader';
import YamlEditor from '../../../components/YamlEditor/Editor'; import YamlEditor from '../../../components/YamlEditor/Editor';
import { WorkflowData } from '../../../models/redux/workflow'; import { WorkflowData } from '../../../models/redux/workflow';

View File

@ -1,10 +1,10 @@
import { Divider, IconButton, Typography } from '@material-ui/core'; 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 EditIcon from '@material-ui/icons/Edit';
import cronstrue from 'cronstrue'; import cronstrue from 'cronstrue';
import YAML from 'yaml'; import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import YAML from 'yaml';
import AdjustedWeights from '../../../components/AdjustedWeights'; import AdjustedWeights from '../../../components/AdjustedWeights';
import ButtonFilled from '../../../components/Button/ButtonFilled'; import ButtonFilled from '../../../components/Button/ButtonFilled';
import ButtonOutline from '../../../components/Button/ButtonOutline/index'; import ButtonOutline from '../../../components/Button/ButtonOutline/index';
@ -23,9 +23,13 @@ import useStyles from './styles';
interface VerifyCommitProps { interface VerifyCommitProps {
gotoStep: (page: number) => void; gotoStep: (page: number) => void;
isEditable?: boolean;
} }
const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => { const VerifyCommit: React.FC<VerifyCommitProps> = ({
gotoStep,
isEditable,
}) => {
const classes = useStyles(); const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
@ -114,7 +118,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
</Typography> </Typography>
</div> </div>
<img <img
src="./icons/b-finance.png" src="/icons/b-finance.png"
alt="bfinance" alt="bfinance"
className={classes.bfinIcon} className={classes.bfinIcon}
/> />
@ -139,6 +143,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
onchange={(changedName: string) => onchange={(changedName: string) =>
handleNameChange({ changedName }) handleNameChange({ changedName })
} }
isEditable={workflowData.isRecurring ? false : isEditable}
/> />
</div> </div>
</div> </div>
@ -172,6 +177,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
onchange={(changedDesc: string) => onchange={(changedDesc: string) =>
handleDescChange({ changedDesc }) handleDescChange({ changedDesc })
} }
isEditable={isEditable}
/> />
</div> </div>
</div> </div>
@ -233,7 +239,7 @@ const VerifyCommit: React.FC<VerifyCommitProps> = ({ gotoStep }) => {
</div> </div>
{/* <div className={classes.editButton2}> */} {/* <div className={classes.editButton2}> */}
<ButtonOutline <ButtonOutline
isDisabled={false} isDisabled={workflowData.isRecurring}
handleClick={() => gotoStep(3)} handleClick={() => gotoStep(3)}
data-cy="testRunButton" data-cy="testRunButton"
> >

View File

@ -17,7 +17,7 @@ import {
REMOVE_INVITATION, REMOVE_INVITATION,
SEND_INVITE, SEND_INVITE,
} from '../../../../../graphql/mutations'; } from '../../../../../graphql/mutations';
import { GET_USER } from '../../../../../graphql/quries'; import { GET_USER } from '../../../../../graphql/queries';
import { import {
MemberInvitation, MemberInvitation,
MemberInviteNew, MemberInviteNew,

View File

@ -10,11 +10,12 @@ import {
Typography, Typography,
} from '@material-ui/core'; } from '@material-ui/core';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import ButtonFilled from '../../../../../components/Button/ButtonFilled'; import ButtonFilled from '../../../../../components/Button/ButtonFilled';
import Loader from '../../../../../components/Loader'; 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 { import {
MemberInviteNew, MemberInviteNew,
UserInvite, UserInvite,

View File

@ -9,7 +9,7 @@ import ButtonOutline from '../../../components/Button/ButtonOutline';
import Loader from '../../../components/Loader'; import Loader from '../../../components/Loader';
import Unimodal from '../../../containers/layouts/Unimodal'; import Unimodal from '../../../containers/layouts/Unimodal';
import { REMOVE_INVITATION } from '../../../graphql/mutations'; 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 { MemberInvitation } from '../../../models/graphql/invite';
import { Member } from '../../../models/graphql/user'; import { Member } from '../../../models/graphql/user';
import { RootState } from '../../../redux/reducers'; import { RootState } from '../../../redux/reducers';