type(bugfix): Fixed Radio Button selection issue at Choose Workflow Screen 🐛 (#2933)

* Fixed Radio Button selection issue at Choose Workflow Screen

Signed-off-by: Sayan Mondal <sayan@chaosnative.com>

* Fixed Issues with workflow localforage state and persistence

Signed-off-by: Sayan Mondal <sayan@chaosnative.com>
This commit is contained in:
Sayan Mondal 2021-06-24 11:45:08 +05:30 committed by GitHub
parent b7db9f42cb
commit c9ac99b947
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 293 additions and 266 deletions

View File

@ -1,4 +1,11 @@
import { Paper, Step, StepLabel, Stepper, Typography } from '@material-ui/core';
import {
Paper,
Step,
StepLabel,
Stepper,
Tooltip,
Typography,
} from '@material-ui/core';
import clsx from 'clsx';
import { ButtonFilled, ButtonOutlined } from 'litmus-ui';
import React from 'react';
@ -77,11 +84,23 @@ const LitmusStepper: React.FC<LitmusStepperProps> = ({
{/* Stepper Actions */}
<div className={classes.stepperActions}>
{activeStep > 0 && (
{activeStep === 2 ? (
<Tooltip
title="All selected Workflow Data will be lost"
placement="top"
leaveDelay={300}
>
<div>
<ButtonOutlined onClick={handleBack}>
<Typography>{t('workflowStepper.back')}</Typography>
</ButtonOutlined>
</div>
</Tooltip>
) : activeStep > 0 ? (
<ButtonOutlined onClick={handleBack}>
<Typography>{t('workflowStepper.back')}</Typography>
</ButtonOutlined>
)}
) : null}
{moreStepperActions}
<div className={classes.endAction}>
{activeStep !== steps.length - 1 ? (

View File

@ -1,3 +1,4 @@
import { Tooltip } from '@material-ui/core';
import Snackbar from '@material-ui/core/Snackbar';
import Typography from '@material-ui/core/Typography';
import MuiAlert, { AlertProps } from '@material-ui/lab/Alert';
@ -145,6 +146,26 @@ const WorkflowStepper = () => {
Finish
</ButtonFilled>
)
) : activeStep === 2 ? (
<div className={classes.headerButtonWrapper} aria-label="buttons">
<Tooltip
title="All selected Workflow Data will be lost"
placement="top"
leaveDelay={300}
>
<div>
<ButtonOutlined
className={classes.btn}
onClick={() => handleBack()}
>
Back
</ButtonOutlined>
</div>
</Tooltip>
<ButtonFilled className={classes.btn} onClick={() => handleNext()}>
Next
</ButtonFilled>
</div>
) : (
// Apply headerButtonWrapper style for top button's div
<div className={classes.headerButtonWrapper} aria-label="buttons">

View File

@ -8,7 +8,7 @@ import {
} from '@material-ui/core';
import { LitmusCard, RadioButton, Search } from 'litmus-ui';
import localforage from 'localforage';
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
DELETE_WORKFLOW_TEMPLATE,
@ -18,17 +18,23 @@ import {
ListManifestTemplate,
ListManifestTemplateArray,
} from '../../../models/graphql/workflowListData';
import { getProjectID } from '../../../utils/getSearchParams';
import * as WorkflowActions from '../../../redux/actions/workflow';
import useStyles from './styles';
import useActions from '../../../redux/actions';
import * as WorkflowActions from '../../../redux/actions/workflow';
import { getProjectID } from '../../../utils/getSearchParams';
import useStyles from './styles';
interface ChooseWorkflowRadio {
selected: string;
id: string;
}
const ChooseWorkflowFromExisting = () => {
interface ChooseWorkflowFromExistingProps {
selectedExp: (expID: string) => void;
}
const ChooseWorkflowFromExisting: React.FC<ChooseWorkflowFromExistingProps> = ({
selectedExp,
}) => {
const { t } = useTranslation();
const classes = useStyles();
const { palette } = useTheme();
@ -73,6 +79,7 @@ const ChooseWorkflowFromExisting = () => {
selected: 'B',
id: event.target.value,
};
selectedExp(selection.id);
const templateData = filteredExistingWorkflows.filter((workflow) => {
return workflow.template_id === event.target.value;
})[0];
@ -83,17 +90,6 @@ const ChooseWorkflowFromExisting = () => {
localforage.setItem('hasSetWorkflowData', false);
};
// Selects Option B -> Sub Experiment Options which was already selected by the user
useEffect(() => {
localforage
.getItem('selectedScheduleOption')
.then((value) =>
value !== null
? setSelected((value as ChooseWorkflowRadio).id)
: setSelected('')
);
}, []);
return (
<AccordionDetails>
{/* Wrapping content inside the Accordion to take full width */}

View File

@ -25,234 +25,229 @@ interface ChooseWorkflowRadio {
id: string;
}
const ChoosePreDefinedExperiments = () => {
const { t } = useTranslation();
const classes = useStyles();
const { palette } = useTheme();
interface ChoosePreDefinedExperimentsProps {
selectedExp: (expID: string) => void;
}
// Local States
const [search, setSearch] = useState<string | null>(null);
const [selected, setSelected] = useState<string>('');
const ChoosePreDefinedExperiments: React.FC<ChoosePreDefinedExperimentsProps> =
({ selectedExp }) => {
const { t } = useTranslation();
const classes = useStyles();
const { palette } = useTheme();
const selectedProjectID = getProjectID();
const [selectedHub, setSelectedHub] = useState('');
const [availableHubs, setAvailableHubs] = useState<MyHubDetail[]>([]);
const [workflowList, setWorkflowlist] = useState([]);
// Local States
const [search, setSearch] = useState<string | null>(null);
const [selected, setSelected] = useState<string>('');
// Get all MyHubs with status
const { data, loading } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: selectedProjectID },
fetchPolicy: 'cache-and-network',
});
const selectedProjectID = getProjectID();
const [selectedHub, setSelectedHub] = useState('');
const [availableHubs, setAvailableHubs] = useState<MyHubDetail[]>([]);
const [workflowList, setWorkflowlist] = useState([]);
/**
* Query to get the list of Pre-defined workflows
*/
const [getPredefinedWorkflow] = useLazyQuery(GET_PREDEFINED_WORKFLOW_LIST, {
fetchPolicy: 'network-only',
onCompleted: (data) => {
if (data.GetPredefinedWorkflowList !== undefined) {
setWorkflowlist(data.GetPredefinedWorkflowList);
}
},
onError: () => {
setWorkflowlist([]);
},
});
// Get all MyHubs with status
const { data, loading } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: selectedProjectID },
fetchPolicy: 'cache-and-network',
});
/**
* Function to handle changes in Radio Buttons
*/
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelected(event.target.value);
const selection: ChooseWorkflowRadio = {
selected: 'A',
id: event.target.value,
};
localforage.setItem('selectedScheduleOption', selection);
localforage.setItem('hasSetWorkflowData', false);
};
const filteredPreDefinedWorkflows = workflowList.filter((w: string) => {
if (search === null) return w;
if (w.toLowerCase().includes(search.toLowerCase())) return w;
return null;
});
/**
* Function to handle change in MyHub dropdown
*/
const handleMyHubChange = (
event: React.ChangeEvent<{
name?: string | undefined;
value: unknown;
}>
) => {
setSelectedHub(event.target.value as string);
getPredefinedWorkflow({
variables: {
hubname: event.target.value as string,
projectid: selectedProjectID,
/**
* Query to get the list of Pre-defined workflows
*/
const [getPredefinedWorkflow] = useLazyQuery(GET_PREDEFINED_WORKFLOW_LIST, {
fetchPolicy: 'network-only',
onCompleted: (data) => {
if (data.GetPredefinedWorkflowList !== undefined) {
setWorkflowlist(data.GetPredefinedWorkflowList);
}
},
onError: () => {
setWorkflowlist([]);
},
});
localforage.setItem('selectedHub', event.target.value as string);
/**
* Function to handle changes in Radio Buttons
*/
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelected(event.target.value);
const selection: ChooseWorkflowRadio = {
selected: 'A',
id: event.target.value,
};
selectedExp(selection.id);
localforage.setItem('selectedScheduleOption', selection);
localforage.setItem('hasSetWorkflowData', false);
};
const filteredPreDefinedWorkflows = workflowList.filter((w: string) => {
if (search === null) return w;
if (w.toLowerCase().includes(search.toLowerCase())) return w;
return null;
});
/**
* Function to handle change in MyHub dropdown
*/
const handleMyHubChange = (
event: React.ChangeEvent<{
name?: string | undefined;
value: unknown;
}>
) => {
setSelectedHub(event.target.value as string);
getPredefinedWorkflow({
variables: {
hubname: event.target.value as string,
projectid: selectedProjectID,
},
});
localforage.setItem('selectedHub', event.target.value as string);
};
/**
* UseEffect to check if Chaos Hub exists and if exists
* fetch the pre-defined workflows
*/
useEffect(() => {
if (data?.getHubStatus !== undefined) {
if (data.getHubStatus.length) {
setAvailableHubs([...data.getHubStatus]);
}
data.getHubStatus.forEach((hubData) => {
if (hubData.HubName.toLowerCase() === 'chaos hub') {
setSelectedHub('Chaos Hub');
localforage.setItem('selectedHub', 'Chaos Hub');
getPredefinedWorkflow({
variables: {
hubname: 'Chaos Hub',
projectid: selectedProjectID,
},
});
}
});
}
}, [loading]);
return (
<AccordionDetails>
{/* Wrapping content inside the Accordion to take full width */}
<div style={{ width: '100%' }}>
<div style={{ display: 'flex' }}>
<div className={classes.inputMyHubDiv}>
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel className={classes.label}>
{t('createWorkflow.chooseWorkflow.selectMyHub')}
</InputLabel>
<Select
data-cy="selectPreDefinedMyHub"
value={selectedHub}
onChange={(e) => {
handleMyHubChange(e);
}}
label="Select MyHub"
MenuProps={MenuProps}
>
{availableHubs.map((hubs) => (
<MenuItem key={hubs.HubName} value={hubs.HubName}>
<Typography data-cy="PreDefinedHubOption">
{hubs.HubName}
</Typography>
</MenuItem>
))}
</Select>
</FormControl>
</div>
<div className={classes.searchDiv}>
<Search
data-cy="agentSearch"
id="input-with-icon-textfield"
placeholder="Search"
value={search}
onChange={(event) => setSearch(event.target.value)}
/>
</div>
</div>
{/* Leaving some space between the search and pre-defined workflow cards */}
<br />
{/* List of Pre-defined workflows */}
<div className={classes.predefinedWorkflowDiv}>
{selectedHub === '' ? (
<div className={classes.noTemplatesDiv}>
<Typography className={classes.noTemplatesText}>
<strong>{t('createWorkflow.chooseWorkflow.noMyHub')}</strong>
</Typography>
<Typography className={classes.noTemplatesDesc}>
{t('createWorkflow.chooseWorkflow.selectHub')}
</Typography>
</div>
) : workflowList.length === 0 ? (
<div className={classes.noTemplatesDiv}>
<Typography className={classes.noTemplatesText}>
<strong>
{t('createWorkflow.chooseWorkflow.noPredefined')}
</strong>
</Typography>
<Typography className={classes.noTemplatesDesc}>
{t('createWorkflow.chooseWorkflow.addPredefined')}
</Typography>
</div>
) : (
<RadioGroup
value={selected}
data-cy="PredefinedWorkflowsRadioGroup"
onChange={handleChange}
>
{filteredPreDefinedWorkflows?.length > 0 ? (
filteredPreDefinedWorkflows?.map((wfData) => (
<LitmusCard
width="100%"
height="5rem"
key={wfData}
borderColor={palette.border.main}
className={classes.predefinedWorkflowCard}
>
<RadioButton value={wfData}>
{/* Wrap the entire body with 100% width to divide into 40-60 */}
<div id="body">
{/* Left Div => Icon + Name of Workflow */}
<div id="left-div">
<img
className={classes.experimentIcon}
src={`${config.grahqlEndpoint}/icon/${selectedProjectID}/${selectedHub}/predefined/${wfData}.png`}
alt="Experiment Icon"
/>
<Typography
className={classes.predefinedWorkflowName}
>
{wfData}
</Typography>
</div>
</div>
</RadioButton>
</LitmusCard>
))
) : (
<div className={classes.noTemplatesDiv}>
<Typography className={classes.noTemplatesText}>
<strong>
{t('createWorkflow.chooseWorkflow.noPredefined')}
</strong>
</Typography>
<Typography className={classes.noTemplatesDesc}>
{t('createWorkflow.chooseWorkflow.searchTerm')}
</Typography>
</div>
)}
</RadioGroup>
)}
</div>
{/* Bottom Blur */}
<div className={classes.blur} />
</div>
</AccordionDetails>
);
};
// Selects Option A -> Sub Experiment Options which was already selected by the user
useEffect(() => {
localforage
.getItem('selectedScheduleOption')
.then((value) =>
value !== null
? setSelected((value as ChooseWorkflowRadio).id)
: setSelected('')
);
}, []);
/**
* UseEffect to check if Chaos Hub exists and if exists
* fetch the pre-defined workflows
*/
useEffect(() => {
if (data?.getHubStatus !== undefined) {
if (data.getHubStatus.length) {
setAvailableHubs([...data.getHubStatus]);
}
data.getHubStatus.forEach((hubData) => {
if (hubData.HubName.toLowerCase() === 'chaos hub') {
setSelectedHub('Chaos Hub');
localforage.setItem('selectedHub', 'Chaos Hub');
getPredefinedWorkflow({
variables: {
hubname: 'Chaos Hub',
projectid: selectedProjectID,
},
});
}
});
}
}, [loading]);
return (
<AccordionDetails>
{/* Wrapping content inside the Accordion to take full width */}
<div style={{ width: '100%' }}>
<div style={{ display: 'flex' }}>
<div className={classes.inputMyHubDiv}>
<FormControl variant="outlined" className={classes.formControl}>
<InputLabel className={classes.label}>
{t('createWorkflow.chooseWorkflow.selectMyHub')}
</InputLabel>
<Select
data-cy="selectPreDefinedMyHub"
value={selectedHub}
onChange={(e) => {
handleMyHubChange(e);
}}
label="Select MyHub"
MenuProps={MenuProps}
>
{availableHubs.map((hubs) => (
<MenuItem key={hubs.HubName} value={hubs.HubName}>
<Typography data-cy="PreDefinedHubOption">
{hubs.HubName}
</Typography>
</MenuItem>
))}
</Select>
</FormControl>
</div>
<div className={classes.searchDiv}>
<Search
data-cy="agentSearch"
id="input-with-icon-textfield"
placeholder="Search"
value={search}
onChange={(event) => setSearch(event.target.value)}
/>
</div>
</div>
{/* Leaving some space between the search and pre-defined workflow cards */}
<br />
{/* List of Pre-defined workflows */}
<div className={classes.predefinedWorkflowDiv}>
{selectedHub === '' ? (
<div className={classes.noTemplatesDiv}>
<Typography className={classes.noTemplatesText}>
<strong>{t('createWorkflow.chooseWorkflow.noMyHub')}</strong>
</Typography>
<Typography className={classes.noTemplatesDesc}>
{t('createWorkflow.chooseWorkflow.selectHub')}
</Typography>
</div>
) : workflowList.length === 0 ? (
<div className={classes.noTemplatesDiv}>
<Typography className={classes.noTemplatesText}>
<strong>
{t('createWorkflow.chooseWorkflow.noPredefined')}
</strong>
</Typography>
<Typography className={classes.noTemplatesDesc}>
{t('createWorkflow.chooseWorkflow.addPredefined')}
</Typography>
</div>
) : (
<RadioGroup
value={selected}
data-cy="PredefinedWorkflowsRadioGroup"
onChange={handleChange}
>
{filteredPreDefinedWorkflows?.length > 0 ? (
filteredPreDefinedWorkflows?.map((wfData) => (
<LitmusCard
width="100%"
height="5rem"
key={wfData}
borderColor={palette.border.main}
className={classes.predefinedWorkflowCard}
>
<RadioButton value={wfData}>
{/* Wrap the entire body with 100% width to divide into 40-60 */}
<div id="body">
{/* Left Div => Icon + Name of Workflow */}
<div id="left-div">
<img
className={classes.experimentIcon}
src={`${config.grahqlEndpoint}/icon/${selectedProjectID}/${selectedHub}/predefined/${wfData}.png`}
alt="Experiment Icon"
/>
<Typography
className={classes.predefinedWorkflowName}
>
{wfData}
</Typography>
</div>
</div>
</RadioButton>
</LitmusCard>
))
) : (
<div className={classes.noTemplatesDiv}>
<Typography className={classes.noTemplatesText}>
<strong>
{t('createWorkflow.chooseWorkflow.noPredefined')}
</strong>
</Typography>
<Typography className={classes.noTemplatesDesc}>
{t('createWorkflow.chooseWorkflow.searchTerm')}
</Typography>
</div>
)}
</RadioGroup>
)}
</div>
{/* Bottom Blur */}
<div className={classes.blur} />
</div>
</AccordionDetails>
);
};
export default ChoosePreDefinedExperiments;

View File

@ -14,7 +14,6 @@ import React, {
} from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { ChooseWorkflowRadio } from '../../../models/localforage/radioButton';
import useActions from '../../../redux/actions';
import * as AlertActions from '../../../redux/actions/alert';
import * as WorkflowActions from '../../../redux/actions/workflow';
@ -30,6 +29,7 @@ const ChooseWorkflow = forwardRef((_, ref) => {
const { t } = useTranslation();
const alert = useActions(AlertActions);
const [selected, setSelected] = useState<string>('');
const [id, setSelectedID] = useState<string | undefined>(undefined);
const workflowDetails = useSelector(
(state: RootState) => state.workflowManifest.manifest
);
@ -50,11 +50,21 @@ const ChooseWorkflow = forwardRef((_, ref) => {
}
};
useEffect(() => {
workflowAction.setWorkflowManifest({ manifest: '' });
}, []);
function onNext() {
if (selected === '') {
alert.changeAlertState(true); // No Workflow Type has been selected and user clicked on Next
return false;
}
if (selected === 'A' || selected === 'B') {
if (id === undefined) {
alert.changeAlertState(true);
return false;
}
}
if (selected === 'D' && workflowDetails === '') {
alert.changeAlertState(true);
return false;
@ -63,20 +73,9 @@ const ChooseWorkflow = forwardRef((_, ref) => {
return true;
}
useEffect(() => {
localforage.getItem('selectedScheduleOption').then((value) => {
if (value) {
setSelected((value as ChooseWorkflowRadio).selected);
} else setSelected('');
});
workflowAction.setWorkflowManifest({
manifest: '',
});
/**
* Removes the workflow details if already present
*/
localforage.removeItem('workflow');
}, []);
const pickedExperiment = (subExpNumber: string) => {
setSelectedID(subExpNumber);
};
useImperativeHandle(ref, () => ({
onNext,
@ -120,7 +119,7 @@ const ChooseWorkflow = forwardRef((_, ref) => {
</span>
</RadioButton>
</AccordionSummary>
<ChoosePreDefinedExperiments />
<ChoosePreDefinedExperiments selectedExp={pickedExperiment} />
</Accordion>
<Accordion
@ -141,7 +140,7 @@ const ChooseWorkflow = forwardRef((_, ref) => {
</span>
</RadioButton>
</AccordionSummary>
<ChooseWorkflowFromExisting />
<ChooseWorkflowFromExisting selectedExp={pickedExperiment} />
</Accordion>
<Accordion

View File

@ -272,13 +272,13 @@ const TuneWorkflow = forwardRef((_, ref) => {
* Index DB Fetching for extracting selected Button and Workflow Details
*/
const getSelectedWorkflowDetails = () => {
localforage.getItem('workflow').then((workflow) =>
localforage.getItem('workflow').then((workflow) => {
setWorkflow({
name: (workflow as WorkflowDetailsProps).name,
crd: (workflow as WorkflowDetailsProps).CRDLink,
description: (workflow as WorkflowDetailsProps).description,
})
);
});
});
localforage.getItem('selectedScheduleOption').then((value) => {
/**
* Setting default data when MyHub is selected
@ -331,7 +331,7 @@ const TuneWorkflow = forwardRef((_, ref) => {
useEffect(() => {
getSelectedWorkflowDetails();
}, []);
}, [manifest]);
/**
* Graphql Query for fetching Engine YAML

View File

@ -241,11 +241,7 @@ const VerifyCommit = forwardRef(
isLoading(loading);
const handleMutation = () => {
if (
workflow.name.length !== 0 &&
workflow.description.length !== 0 &&
weights.length !== 0
) {
if (workflow.name.length !== 0 && weights.length !== 0) {
const weightData: WeightMap[] = [];
weights.forEach((data) => {

View File

@ -130,6 +130,7 @@ const WorkflowSettings = forwardRef((_, ref) => {
},
});
});
workflowAction.setWorkflowManifest({ manifest: '' });
}
if ((value as ChooseWorkflowRadio).selected === 'B') {
getSavedTemplateDetails({