type(ux): Added myhub selection for pre-defined workflows (#2868)

* Added myhub selection for pre-defined workflows
* Removed commented code
* Minor change
* Fixed deepscan issue

Signed-off-by: Amit Kumar Das <amit@chaosnative.com>
This commit is contained in:
Amit Kumar Das 2021-06-09 17:19:20 +05:30 committed by GitHub
parent ecc865f690
commit e414ea38cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 234 additions and 74 deletions

View File

@ -751,11 +751,15 @@ createWorkflow:
edit: Edit workflow name
cancel: Cancel
save: Save
noMyHub: No MyHub selected
selectHub: Select a hub and continue with a pre-defined workflow
noPredefined: No Pre-defined experiments present
addPredefined: Add a pre-defined workflow to continue scheduling.
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 weightage against.
infoNextStrong: These weights are used to calculate the resiliency score at the end of test.
infoNextStrong: These weights are used to calculate the resiliency score at the end of test.
testInfo: Compare the importance of the items above and launch a demo version of Kubernetes conformance test to see how it works.
button:
demo: Demo Launch

View File

@ -1,15 +1,24 @@
import { useLazyQuery, useQuery } from '@apollo/client';
import {
AccordionDetails,
FormControl,
InputLabel,
MenuItem,
RadioGroup,
Select,
Typography,
useTheme,
} from '@material-ui/core';
import { LitmusCard, RadioButton, Search } from 'litmus-ui';
import localforage from 'localforage';
import React, { useEffect, useState } from 'react';
import data from '../../../components/PredifinedWorkflows/data';
import { preDefinedWorkflowData } from '../../../models/predefinedWorkflow';
import useStyles from './styles';
import { useTranslation } from 'react-i18next';
import config from '../../../config';
import { GET_HUB_STATUS, GET_PREDEFINED_WORKFLOW_LIST } from '../../../graphql';
import { MyHubDetail } from '../../../models/graphql/user';
import { HubStatus } from '../../../models/redux/myhub';
import { getProjectID } from '../../../utils/getSearchParams';
import useStyles, { MenuProps } from './styles';
interface ChooseWorkflowRadio {
selected: string;
@ -17,6 +26,7 @@ interface ChooseWorkflowRadio {
}
const ChoosePreDefinedExperiments = () => {
const { t } = useTranslation();
const classes = useStyles();
const { palette } = useTheme();
@ -24,7 +34,35 @@ const ChoosePreDefinedExperiments = () => {
const [search, setSearch] = useState<string | null>(null);
const [selected, setSelected] = useState<string>('');
// Methods
const selectedProjectID = getProjectID();
const [selectedHub, setSelectedHub] = useState('');
const [availableHubs, setAvailableHubs] = useState<MyHubDetail[]>([]);
const [workflowList, setWorkflowlist] = useState([]);
// Get all MyHubs with status
const { data, loading } = useQuery<HubStatus>(GET_HUB_STATUS, {
variables: { data: selectedProjectID },
fetchPolicy: 'cache-and-network',
});
/**
* 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([]);
},
});
/**
* Function to handle changes in Radio Buttons
*/
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelected(event.target.value);
const selection: ChooseWorkflowRadio = {
@ -35,13 +73,30 @@ const ChoosePreDefinedExperiments = () => {
localforage.setItem('hasSetWorkflowData', false);
};
const filteredPreDefinedWorkflows = data.filter(
(w: preDefinedWorkflowData) => {
if (search === null) return w;
if (w.title.toLowerCase().includes(search.toLowerCase())) return w;
return null;
}
);
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);
};
// Selects Option A -> Sub Experiment Options which was already selected by the user
useEffect(() => {
@ -54,60 +109,130 @@ const ChoosePreDefinedExperiments = () => {
);
}, []);
/**
* 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');
getPredefinedWorkflow({
variables: {
hubname: 'Chaos Hub',
projectid: selectedProjectID,
},
});
}
});
}
}, [loading]);
return (
<AccordionDetails>
{/* Wrapping content inside the Accordion to take full width */}
<div style={{ width: '100%' }}>
<Search
data-cy="agentSearch"
id="input-with-icon-textfield"
placeholder="Search"
value={search}
onChange={(event) => setSearch(event.target.value)}
/>
<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="myHubDropDown"
value={selectedHub}
onChange={(e) => {
handleMyHubChange(e);
}}
label="Select MyHub"
MenuProps={MenuProps}
>
{availableHubs.map((hubs) => (
<MenuItem
key={hubs.HubName}
data-cy="hubOption"
value={hubs.HubName}
>
{hubs.HubName}
</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 />
<br />
{/* List of Pre-defined workflows */}
<div className={classes.predefinedWorkflowDiv}>
<RadioGroup
value={selected}
data-cy="PredefinedWorkflowsRadioGroup"
onChange={handleChange}
>
{filteredPreDefinedWorkflows.map((workflowData) => (
<LitmusCard
width="100%"
height="5rem"
key={workflowData.workflowID}
borderColor={palette.border.main}
className={classes.predefinedWorkflowCard}
>
<RadioButton value={workflowData.workflowID.toString()}>
{/* 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={workflowData.urlToIcon}
alt="Experiment Icon"
/>
<Typography className={classes.predefinedWorkflowName}>
{workflowData.title}
</Typography>
{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?.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>
{/* Right Div => Description of Workflow */}
<div id="right-div">
<Typography>{workflowData.description}</Typography>
</div>
</div>
</RadioButton>
</LitmusCard>
))}
</RadioGroup>
</RadioButton>
</LitmusCard>
))}
</RadioGroup>
)}
</div>
{/* Bottom Blur */}
<div className={classes.blur} />

View File

@ -40,6 +40,9 @@ const useStyles = makeStyles((theme: Theme) => ({
},
},
searchDiv: {
margin: theme.spacing(2, 0, 0, 2.25),
},
// Divider
divider: {
border: 'none',
@ -72,8 +75,9 @@ const useStyles = makeStyles((theme: Theme) => ({
// Accordion Expanded Body [Content]
predefinedWorkflowDiv: {
height: '15rem',
maxHeight: '15rem',
overflowY: 'scroll',
padding: theme.spacing(3, 0, 3, 0),
},
MuiAccordionroot: {
'&.MuiAccordion-root:before': {
@ -97,11 +101,6 @@ const useStyles = makeStyles((theme: Theme) => ({
display: 'flex',
marginLeft: theme.spacing(2),
},
'& #right-div': {
width: '20rem',
display: 'flex',
},
},
existingWorkflowCard: {
alignItems: 'center',
@ -149,7 +148,7 @@ const useStyles = makeStyles((theme: Theme) => ({
height: '3rem',
},
predefinedWorkflowName: {
marginLeft: theme.spacing(2),
marginLeft: theme.spacing(4),
marginTop: theme.spacing(1.5),
},
blur: {
@ -158,7 +157,7 @@ const useStyles = makeStyles((theme: Theme) => ({
position: 'absolute',
bottom: 0,
background: theme.palette.background.paper,
opacity: '0.8',
opacity: '0.5',
filter: 'blur(1rem)',
},
@ -233,6 +232,11 @@ const useStyles = makeStyles((theme: Theme) => ({
margin: theme.spacing(2.5),
alignItems: 'center',
},
inputMyHubDiv: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
formControl: {
minWidth: '9rem',
marginLeft: theme.spacing(1),

View File

@ -1,3 +1,4 @@
import { useLazyQuery } from '@apollo/client';
import { Avatar, Typography } from '@material-ui/core';
import { ButtonOutlined, InputField, Modal } from 'litmus-ui';
import localforage from 'localforage';
@ -9,21 +10,24 @@ import React, {
} from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import data from '../../../components/PredifinedWorkflows/data';
import config from '../../../config';
import { GET_EXPERIMENT_DATA } from '../../../graphql';
import { ChooseWorkflowRadio } from '../../../models/localforage/radioButton';
import { WorkflowDetailsProps } from '../../../models/localforage/workflow';
import { ExperimentDetail } from '../../../models/redux/myhub';
import useActions from '../../../redux/actions';
import * as AlertActions from '../../../redux/actions/alert';
import * as WorkflowActions from '../../../redux/actions/workflow';
import { RootState } from '../../../redux/reducers';
import capitalize from '../../../utils/capitalize';
import { getProjectID } from '../../../utils/getSearchParams';
import { validateWorkflowName } from '../../../utils/validate';
import useStyles from './styles';
const WorkflowSettings = forwardRef((_, ref) => {
const classes = useStyles();
const [avatarModal, setAvatarModal] = useState<boolean>(false);
const projectID = getProjectID();
// Workflow States
const [name, setName] = useState<string>('');
const [descriptionHeader, setDescriptionHeader] = useState<JSX.Element>(
@ -38,6 +42,24 @@ const WorkflowSettings = forwardRef((_, ref) => {
const { manifest } = useSelector(
(state: RootState) => state.workflowManifest
);
const [hubName, setHubName] = useState('');
// Query to get charts of selected MyHub
const [getWorkflowDetails] = useLazyQuery<ExperimentDetail>(
GET_EXPERIMENT_DATA,
{
fetchPolicy: 'cache-and-network',
onCompleted: (data) => {
if (data.getHubExperiment !== undefined) {
setName(data.getHubExperiment.Metadata.Name.toLowerCase());
setDescription(data.getHubExperiment.Spec.CategoryDescription);
setIcon(
`${config.grahqlEndpoint}/icon/${projectID}/${hubName}/predefined/${data.getHubExperiment.Metadata.Name}.png`
);
setCRDLink(data.getHubExperiment.Metadata.Name);
}
},
}
);
const { t } = useTranslation();
const alert = useActions(AlertActions);
@ -81,16 +103,20 @@ const WorkflowSettings = forwardRef((_, ref) => {
const initializeWithDefault = () => {
localforage.getItem('selectedScheduleOption').then((value) => {
// Map over the list of predefined workflows and extract the name and detail
if ((value as ChooseWorkflowRadio).selected === 'A') {
// Map over the list of predefined workflows and extract the name and detail
data.map((w) => {
if (w.workflowID.toString() === (value as ChooseWorkflowRadio).id) {
setName(w.title);
setDescription(w.details);
setIcon(w.urlToIcon);
setCRDLink(w.experimentPath);
}
return null;
localforage.getItem('selectedHub').then((hub) => {
setHubName(hub as string);
getWorkflowDetails({
variables: {
data: {
HubName: hub as string,
ProjectID: projectID,
ChartName: 'predefined',
ExperimentName: (value as ChooseWorkflowRadio).id,
},
},
});
});
}
if ((value as ChooseWorkflowRadio).selected === 'B') {
@ -123,6 +149,7 @@ const WorkflowSettings = forwardRef((_, ref) => {
* and call checkForStoredData()
* else it will initializeWithDefault()
*/
localforage.getItem('hasSetWorkflowData').then((isDataPresent) => {
return isDataPresent ? checkForStoredData() : initializeWithDefault();
});