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

View File

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

View File

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

View File

@ -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,15 +64,19 @@ const CustomText: React.FC<CustomTextProps> = ({ value, id, onchange }) => {
}}
onChange={handleChange}
/>
{isDisabled ? (
<IconButton size="medium" onClick={handleEdit}>
<EditIcon className={classes.editBtn} data-cy="edit" />
</IconButton>
) : (
<IconButton size="medium" onClick={handleSave}>
<SaveIcon className={classes.saveBtn} data-cy="save" />
</IconButton>
)}
{isEditable ? (
<>
{isDisabled ? (
<IconButton size="medium" onClick={handleEdit}>
<EditIcon className={classes.editBtn} data-cy="edit" />
</IconButton>
) : (
<IconButton size="medium" onClick={handleSave}>
<SaveIcon className={classes.saveBtn} data-cy="save" />
</IconButton>
)}
</>
) : null}
</div>
);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,3 @@
export * from './mutations';
export * from './quries';
export * from './queries';
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`
mutation updateUser($user: UpdateUserInput!) {
updateUser(user: $user)

View File

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

View File

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

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 {
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>
<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}
/>
</div>
<Divider />
{/* Lower segment */}
<div className={classes.scFormControl}>
<FormControl component="fieldset" className={classes.formControl}>
<RadioGroup
aria-label="schedule"
name="schedule"
value={value}
onChange={handleChange}
<Unimodal
open={open}
handleClose={handleClose}
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
hasCloseBtn
>
{/* 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>
<div>
<img
src="/icons/finish.svg"
className={classes.mark}
alt="mark"
/>
<div className={classes.heading}>
{t('schedule.chaosWorkflow')}
<br />
<strong>{t('schedule.successful')}</strong>
</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 className={classes.headWorkflow}>
{t('schedule.congratulationsSub1')}
<br />
{t('schedule.congratulationsSub2')}
</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,
},
}}
>
{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 className={classes.button}>
<ButtonFilled
isPrimary
data-cy="selectFinish"
handleClick={() => {
setOpen(false);
tabs.changeWorkflowsTabs(0);
history.push('/workflows');
}}
>
<div>{t('workflowStepper.workflowBtn')}</div>
</ButtonFilled>
</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>
</ButtonFilled>
</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>
</div>
)}
</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: '',
weights: [],
isCustomWorkflow: false,
isRecurring: false,
namespace: 'litmus',
clusterid: '',
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 { 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,

View File

@ -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>
) : (
<></>
)}
</>
);
};

View File

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

View File

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

View File

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

View File

@ -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%',
}}

View File

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

View File

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

View File

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

View File

@ -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,16 +336,30 @@ const ScheduleWorkflow: React.FC = () => {
onChange={handleChange}
>
{/* options to choose schedule */}
<FormControlLabel
value="now"
control={<Radio />}
label={
<Typography className={classes.radioText}>
{t('createWorkflow.scheduleWorkflow.radio.now')}
</Typography>
}
/>
<FormControlLabel
{!workflowData.isRecurring ? (
<FormControlLabel
value="now"
control={<Radio />}
label={
<Typography className={classes.radioText}>
{t('createWorkflow.scheduleWorkflow.radio.now')}
</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}>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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