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,6 +751,10 @@ createWorkflow:
edit: Edit workflow name edit: Edit workflow name
cancel: Cancel cancel: Cancel
save: Save 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: reliabilityScore:
header: Adjust the weights of the experiments in the workflow header: Adjust the weights of the experiments in the workflow
info: You have selected info: You have selected

View File

@ -1,15 +1,24 @@
import { useLazyQuery, useQuery } from '@apollo/client';
import { import {
AccordionDetails, AccordionDetails,
FormControl,
InputLabel,
MenuItem,
RadioGroup, RadioGroup,
Select,
Typography, Typography,
useTheme, useTheme,
} from '@material-ui/core'; } from '@material-ui/core';
import { LitmusCard, RadioButton, Search } from 'litmus-ui'; import { LitmusCard, RadioButton, Search } from 'litmus-ui';
import localforage from 'localforage'; import localforage from 'localforage';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import data from '../../../components/PredifinedWorkflows/data'; import { useTranslation } from 'react-i18next';
import { preDefinedWorkflowData } from '../../../models/predefinedWorkflow'; import config from '../../../config';
import useStyles from './styles'; 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 { interface ChooseWorkflowRadio {
selected: string; selected: string;
@ -17,6 +26,7 @@ interface ChooseWorkflowRadio {
} }
const ChoosePreDefinedExperiments = () => { const ChoosePreDefinedExperiments = () => {
const { t } = useTranslation();
const classes = useStyles(); const classes = useStyles();
const { palette } = useTheme(); const { palette } = useTheme();
@ -24,7 +34,35 @@ const ChoosePreDefinedExperiments = () => {
const [search, setSearch] = useState<string | null>(null); const [search, setSearch] = useState<string | null>(null);
const [selected, setSelected] = useState<string>(''); 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>) => { const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setSelected(event.target.value); setSelected(event.target.value);
const selection: ChooseWorkflowRadio = { const selection: ChooseWorkflowRadio = {
@ -35,13 +73,30 @@ const ChoosePreDefinedExperiments = () => {
localforage.setItem('hasSetWorkflowData', false); localforage.setItem('hasSetWorkflowData', false);
}; };
const filteredPreDefinedWorkflows = data.filter( const filteredPreDefinedWorkflows = workflowList.filter((w: string) => {
(w: preDefinedWorkflowData) => {
if (search === null) return w; if (search === null) return w;
if (w.title.toLowerCase().includes(search.toLowerCase())) return w; if (w.toLowerCase().includes(search.toLowerCase())) return w;
return null; 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 // Selects Option A -> Sub Experiment Options which was already selected by the user
useEffect(() => { useEffect(() => {
@ -54,10 +109,61 @@ 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 ( return (
<AccordionDetails> <AccordionDetails>
{/* Wrapping content inside the Accordion to take full width */} {/* Wrapping content inside the Accordion to take full width */}
<div style={{ width: '100%' }}> <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="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 <Search
data-cy="agentSearch" data-cy="agentSearch"
id="input-with-icon-textfield" id="input-with-icon-textfield"
@ -65,49 +171,68 @@ const ChoosePreDefinedExperiments = () => {
value={search} value={search}
onChange={(event) => setSearch(event.target.value)} onChange={(event) => setSearch(event.target.value)}
/> />
</div>
</div>
{/* Leaving some space between the search and pre-defined workflow cards */} {/* Leaving some space between the search and pre-defined workflow cards */}
<br /> <br />
<br />
{/* List of Pre-defined workflows */} {/* List of Pre-defined workflows */}
<div className={classes.predefinedWorkflowDiv}> <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 <RadioGroup
value={selected} value={selected}
data-cy="PredefinedWorkflowsRadioGroup" data-cy="PredefinedWorkflowsRadioGroup"
onChange={handleChange} onChange={handleChange}
> >
{filteredPreDefinedWorkflows.map((workflowData) => ( {filteredPreDefinedWorkflows?.map((wfData) => (
<LitmusCard <LitmusCard
width="100%" width="100%"
height="5rem" height="5rem"
key={workflowData.workflowID} key={wfData}
borderColor={palette.border.main} borderColor={palette.border.main}
className={classes.predefinedWorkflowCard} className={classes.predefinedWorkflowCard}
> >
<RadioButton value={workflowData.workflowID.toString()}> <RadioButton value={wfData}>
{/* Wrap the entire body with 100% width to divide into 40-60 */} {/* Wrap the entire body with 100% width to divide into 40-60 */}
<div id="body"> <div id="body">
{/* Left Div => Icon + Name of Workflow */} {/* Left Div => Icon + Name of Workflow */}
<div id="left-div"> <div id="left-div">
<img <img
className={classes.experimentIcon} className={classes.experimentIcon}
src={workflowData.urlToIcon} src={`${config.grahqlEndpoint}/icon/${selectedProjectID}/${selectedHub}/predefined/${wfData}.png`}
alt="Experiment Icon" alt="Experiment Icon"
/> />
<Typography className={classes.predefinedWorkflowName}> <Typography className={classes.predefinedWorkflowName}>
{workflowData.title} {wfData}
</Typography> </Typography>
</div> </div>
{/* Right Div => Description of Workflow */}
<div id="right-div">
<Typography>{workflowData.description}</Typography>
</div>
</div> </div>
</RadioButton> </RadioButton>
</LitmusCard> </LitmusCard>
))} ))}
</RadioGroup> </RadioGroup>
)}
</div> </div>
{/* Bottom Blur */} {/* Bottom Blur */}
<div className={classes.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
divider: { divider: {
border: 'none', border: 'none',
@ -72,8 +75,9 @@ const useStyles = makeStyles((theme: Theme) => ({
// Accordion Expanded Body [Content] // Accordion Expanded Body [Content]
predefinedWorkflowDiv: { predefinedWorkflowDiv: {
height: '15rem', maxHeight: '15rem',
overflowY: 'scroll', overflowY: 'scroll',
padding: theme.spacing(3, 0, 3, 0),
}, },
MuiAccordionroot: { MuiAccordionroot: {
'&.MuiAccordion-root:before': { '&.MuiAccordion-root:before': {
@ -97,11 +101,6 @@ const useStyles = makeStyles((theme: Theme) => ({
display: 'flex', display: 'flex',
marginLeft: theme.spacing(2), marginLeft: theme.spacing(2),
}, },
'& #right-div': {
width: '20rem',
display: 'flex',
},
}, },
existingWorkflowCard: { existingWorkflowCard: {
alignItems: 'center', alignItems: 'center',
@ -149,7 +148,7 @@ const useStyles = makeStyles((theme: Theme) => ({
height: '3rem', height: '3rem',
}, },
predefinedWorkflowName: { predefinedWorkflowName: {
marginLeft: theme.spacing(2), marginLeft: theme.spacing(4),
marginTop: theme.spacing(1.5), marginTop: theme.spacing(1.5),
}, },
blur: { blur: {
@ -158,7 +157,7 @@ const useStyles = makeStyles((theme: Theme) => ({
position: 'absolute', position: 'absolute',
bottom: 0, bottom: 0,
background: theme.palette.background.paper, background: theme.palette.background.paper,
opacity: '0.8', opacity: '0.5',
filter: 'blur(1rem)', filter: 'blur(1rem)',
}, },
@ -233,6 +232,11 @@ const useStyles = makeStyles((theme: Theme) => ({
margin: theme.spacing(2.5), margin: theme.spacing(2.5),
alignItems: 'center', alignItems: 'center',
}, },
inputMyHubDiv: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
},
formControl: { formControl: {
minWidth: '9rem', minWidth: '9rem',
marginLeft: theme.spacing(1), marginLeft: theme.spacing(1),

View File

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