1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-21 14:50:08 +03:00

F #3951: Improvement performance (#226)

This commit is contained in:
Sergio Betanzos 2020-09-18 11:55:21 +02:00 committed by GitHub
parent 169b072a32
commit aa1166ced0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 397 additions and 413 deletions

View File

@ -1,99 +0,0 @@
import React, { useEffect, useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useFormContext } from 'react-hook-form';
import ErrorHelper from 'client/components/FormControl/ErrorHelper';
function FormList({ step, data, setFormData }) {
const { errors } = useFormContext();
const [dialogFormData, setDialogFormData] = useState({});
const [showDialog, setShowDialog] = useState(false);
const { id, preRender, ListComponent, DialogComponent, DEFAULT_DATA } = step;
useEffect(() => {
if (preRender) preRender();
}, []);
const handleSubmit = values => {
setFormData(prevData => ({
...prevData,
[id]: Object.assign(prevData[id], {
[dialogFormData.index]: values
})
}));
setShowDialog(false);
};
const handleOpen = (index = data?.length) => {
const openData = data[index] ?? DEFAULT_DATA;
setDialogFormData({ index, data: openData });
setShowDialog(true);
};
const handleClone = index => {
const item = data[index];
const cloneItem = { ...item, name: `${item?.name}_clone` };
const cloneData = [...data];
cloneData.splice(index + 1, 0, cloneItem);
setFormData(prevData => ({ ...prevData, [id]: cloneData }));
};
const handleRemove = indexRemove => {
// TODO confirmation??
setFormData(prevData => ({
...prevData,
[id]: prevData[id]?.filter((_, index) => index !== indexRemove)
}));
};
const handleClose = () => setShowDialog(false);
return (
<>
{typeof errors[id]?.message === 'string' && (
<ErrorHelper label={errors[id]?.message} />
)}
{useMemo(
() => (
<ListComponent
list={data}
handleCreate={() => handleOpen()}
itemsProps={({ index }) => ({
handleEdit: () => handleOpen(index),
handleClone: () => handleClone(index),
handleRemove: () => handleRemove(index)
})}
/>
),
[data, handleOpen, handleClone, handleRemove]
)}
{showDialog && DialogComponent && (
<DialogComponent
open={showDialog}
values={dialogFormData?.data}
onSubmit={handleSubmit}
onCancel={handleClose}
/>
)}
</>
);
}
FormList.propTypes = {
step: PropTypes.objectOf(PropTypes.any).isRequired,
data: PropTypes.arrayOf(PropTypes.object).isRequired,
setFormData: PropTypes.func.isRequired
};
FormList.defaultProps = {
step: {},
data: [],
setFormData: data => data
};
export default FormList;

View File

@ -1,69 +0,0 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Grid } from '@material-ui/core';
import { useFormContext } from 'react-hook-form';
import ErrorHelper from 'client/components/FormControl/ErrorHelper';
import { EmptyCard } from 'client/components/Cards';
function FormListSelect({ step, data, setFormData }) {
const { errors } = useFormContext();
const { id, multiple, preRender, list, ItemComponent } = step;
useEffect(() => {
if (preRender) preRender();
}, []);
const handleSelect = index =>
setFormData(prevData => ({
...prevData,
[id]: multiple ? [...prevData[id], index] : [index]
}));
const handleUnselect = indexRemove =>
setFormData(prevData => ({
...prevData,
[id]: prevData[id]?.filter(index => index !== indexRemove)
}));
return (
<Grid container spacing={3}>
{typeof errors[id]?.message === 'string' && (
<Grid item xs={12}>
<ErrorHelper label={errors[id]?.message} />
</Grid>
)}
{list?.length === 0 ? (
<Grid item xs={6} sm={4} md={3} lg={1}>
<EmptyCard name={id} />
</Grid>
) : (
list?.map((info, index) => (
<Grid key={`${id}-${index}`} item xs={6} sm={4} md={3} lg={1}>
<ItemComponent
value={info}
isSelected={data?.some(selected => selected === info?.ID)}
handleSelect={handleSelect}
handleUnselect={handleUnselect}
/>
</Grid>
))
)}
</Grid>
);
}
FormListSelect.propTypes = {
step: PropTypes.objectOf(PropTypes.any).isRequired,
data: PropTypes.arrayOf(PropTypes.any).isRequired,
setFormData: PropTypes.func.isRequired
};
FormListSelect.defaultProps = {
step: {},
data: [],
setFormData: data => data
};
export default FormListSelect;

View File

@ -1,63 +0,0 @@
import React, { useEffect, useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useFormContext } from 'react-hook-form';
import ErrorHelper from 'client/components/FormControl/ErrorHelper';
function FormStep({ step, data, setFormData }) {
const { errors } = useFormContext();
const [showDialog, setShowDialog] = useState(false);
const { id, preRender, FormComponent, DialogComponent } = step;
useEffect(() => {
if (preRender) preRender();
}, []);
const handleOpen = () => setShowDialog(true);
const handleClose = () => setShowDialog(false);
return (
<>
{typeof errors[id]?.message === 'string' && (
<ErrorHelper label={errors[id]?.message} />
)}
{useMemo(
() => (
<FormComponent
id={id}
values={data}
setFormData={setFormData}
handleClick={handleOpen}
/>
),
[id, handleOpen, setFormData, data]
)}
{showDialog && DialogComponent && (
<DialogComponent
open={showDialog}
values={data}
onCancel={handleClose}
/>
)}
</>
);
}
FormStep.propTypes = {
step: PropTypes.objectOf(PropTypes.any).isRequired,
data: PropTypes.oneOfType([
PropTypes.array,
PropTypes.object,
PropTypes.string
]).isRequired,
setFormData: PropTypes.func.isRequired
};
FormStep.defaultProps = {
step: {},
data: {},
setFormData: data => data
};
export default FormStep;

View File

@ -6,12 +6,13 @@ import { useMediaQuery } from '@material-ui/core';
import CustomMobileStepper from 'client/components/FormStepper/MobileStepper';
import CustomStepper from 'client/components/FormStepper/Stepper';
import ErrorHelper from 'client/components/FormControl/ErrorHelper';
const FIRST_STEP = 0;
const FormStepper = ({ steps, initialValue, onSubmit }) => {
const isMobile = useMediaQuery(theme => theme.breakpoints.only('xs'));
const { watch, trigger, reset } = useFormContext();
const { watch, trigger, reset, errors } = useFormContext();
const [activeStep, setActiveStep] = useState(FIRST_STEP);
const [formData, setFormData] = useState(initialValue);
@ -73,14 +74,15 @@ const FormStepper = ({ steps, initialValue, onSubmit }) => {
return (
Content && (
<Content
step={steps[activeStep]}
data={formData[id]}
setFormData={setFormData}
/>
<>
{typeof errors[id]?.message === 'string' && (
<ErrorHelper label={errors[id]?.message} />
)}
<Content data={formData[id]} setFormData={setFormData} />
</>
)
);
}, [steps, formData, activeStep, setFormData])}
}, [steps, errors, formData, activeStep, setFormData])}
</>
);
};
@ -90,7 +92,7 @@ FormStepper.propTypes = {
PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
label: PropTypes.string.isRequired,
content: PropTypes.func.isRequired
content: PropTypes.any.isRequired
})
),
initialValue: PropTypes.objectOf(PropTypes.any),

View File

@ -42,7 +42,7 @@ function ListCards({ handleCreate, list, CardComponent, cardsProps }) {
{Array.isArray(list) &&
list?.map((value, index) => (
<Grid key={`card-${index}`} item xs={12} sm={4} md={3} lg={2}>
<CardComponent value={value} {...cardsProps({ index })} />
<CardComponent value={value} {...cardsProps({ value, index })} />
</Grid>
))}
</Grid>

View File

@ -1,22 +1,19 @@
import React from 'react';
import React, { useCallback } from 'react';
import FormStep from 'client/components/FormStepper/FormStep';
import FormWithSchema from 'client/components/Forms/FormWithSchema';
import { FORM_FIELDS, STEP_FORM_SCHEMA } from './schema';
const BasicConfiguration = () => {
const STEP_ID = 'application';
export const STEP_ID = 'application';
return {
id: STEP_ID,
label: 'Application Overview',
content: FormStep,
resolver: STEP_FORM_SCHEMA,
FormComponent: () => (
<FormWithSchema cy="form-flow" fields={FORM_FIELDS} id={STEP_ID} />
)
};
};
const BasicConfiguration = () => ({
id: STEP_ID,
label: 'Application Overview',
resolver: STEP_FORM_SCHEMA,
content: useCallback(
() => <FormWithSchema cy="form-flow" fields={FORM_FIELDS} id={STEP_ID} />,
[]
)
});
export default BasicConfiguration;

View File

@ -1,28 +1,46 @@
import { useMemo } from 'react';
import React, { useEffect, useCallback } from 'react';
import useOpennebula from 'client/hooks/useOpennebula';
import useListForm from 'client/hooks/useListForm';
import ListCards from 'client/components/List/ListCards';
import { ClusterCard } from 'client/components/Cards';
import FormListSelect from 'client/components/FormStepper/FormListSelect';
import { STEP_FORM_SCHEMA } from './schema';
const Clusters = () => {
const STEP_ID = 'clusters';
const { clusters, getClusters } = useOpennebula();
export const STEP_ID = 'clusters';
return useMemo(
() => ({
id: STEP_ID,
label: 'Where will it run?',
content: FormListSelect,
resolver: STEP_FORM_SCHEMA,
preRender: getClusters,
list: clusters?.sort((a, b) => a.ID - b.ID),
ItemComponent: ClusterCard
}),
[getClusters, clusters]
);
};
const Clusters = () => ({
id: STEP_ID,
label: 'Where will it run?',
resolver: STEP_FORM_SCHEMA,
content: useCallback(({ data, setFormData }) => {
const { clusters, getClusters } = useOpennebula();
const { handleSelect, handleUnselect } = useListForm({
key: STEP_ID,
setList: setFormData
});
useEffect(() => {
getClusters();
}, []);
return (
<ListCards
list={clusters}
CardComponent={ClusterCard}
cardsProps={({ value }) => {
const { ID } = value;
return {
isSelected: data?.some(selected => selected === ID),
handleSelect: () => handleSelect(ID),
handleUnselect: () => handleUnselect(ID)
};
}}
/>
);
}, [])
});
export default Clusters;

View File

@ -1,52 +1,79 @@
import React, { useMemo } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import useOpennebula from 'client/hooks/useOpennebula';
import { DialogForm } from 'client/components/Dialogs';
import { NetworkCard } from 'client/components/Cards';
import FormList from 'client/components/FormStepper/FormList';
import useListForm from 'client/hooks/useListForm';
import FormWithSchema from 'client/components/Forms/FormWithSchema';
import ListCards from 'client/components/List/ListCards';
import { FORM_FIELDS, NETWORK_FORM_SCHEMA, STEP_FORM_SCHEMA } from './schema';
const Networks = () => {
const STEP_ID = 'networking';
const { getVNetworks, getVNetworksTemplates } = useOpennebula();
export const STEP_ID = 'networking';
return useMemo(
() => ({
id: STEP_ID,
label: 'Configure Networking',
content: FormList,
preRender: () => {
getVNetworks();
getVNetworksTemplates();
},
resolver: STEP_FORM_SCHEMA,
DEFAULT_DATA: NETWORK_FORM_SCHEMA.default(),
ListComponent: ({ list, handleCreate, itemsProps }) => (
const Networks = () => ({
id: STEP_ID,
label: 'Configure Networking',
resolver: STEP_FORM_SCHEMA,
content: useCallback(({ data, setFormData }) => {
const [showDialog, setShowDialog] = useState(false);
const { getVNetworks, getVNetworksTemplates } = useOpennebula();
const {
editingData,
handleSave,
handleEdit,
handleClone,
handleRemove
} = useListForm({
key: STEP_ID,
list: data,
setList: setFormData,
defaultValue: NETWORK_FORM_SCHEMA.default()
});
useEffect(() => {
getVNetworks();
getVNetworksTemplates();
}, []);
return (
<>
<ListCards
list={list}
handleCreate={handleCreate}
list={data}
CardComponent={NetworkCard}
cardsProps={itemsProps}
handleCreate={() => {
handleEdit();
setShowDialog(true);
}}
cardsProps={({ index }) => ({
handleEdit: () => {
handleEdit(index);
setShowDialog(true);
},
handleClone: () => handleClone(index),
handleRemove: () => handleRemove(index)
})}
/>
),
ItemComponent: NetworkCard,
DialogComponent: props => (
<DialogForm
title={'Network form'}
resolver={NETWORK_FORM_SCHEMA}
{...props}
>
<FormWithSchema cy="form-dg-network" fields={FORM_FIELDS} />
</DialogForm>
)
}),
[getVNetworks, getVNetworksTemplates]
);
};
{showDialog && (
<DialogForm
title={'Network form'}
resolver={NETWORK_FORM_SCHEMA}
open={showDialog}
values={editingData?.data}
onSubmit={values => {
handleSave(values);
setShowDialog(false);
}}
onCancel={() => setShowDialog(false)}
>
<FormWithSchema cy="form-dg-network" fields={FORM_FIELDS} />
</DialogForm>
)}
</>
);
}, [])
});
export default Networks;

View File

@ -81,22 +81,15 @@ export const FORM_FIELDS = [
},
validation: yup
.string()
.when('type', {
is: type =>
TYPES_NETWORKS.some(
({ value, select }) => type === value && select === SELECT.network
),
then: yup
.string()
.trim()
.required('Network is required field'),
otherwise: yup
.string()
.trim()
.required('Network template is required field')
})
.required()
.default(null)
.trim()
.when('type', (type, schema) =>
TYPES_NETWORKS.some(
({ value, select }) => type === value && select === SELECT.network
)
? schema.required('Network is required field')
: schema.required('Network template is required field')
)
.default(undefined)
},
{
name: 'extra',

View File

@ -1,22 +1,19 @@
import React from 'react';
import React, { useCallback } from 'react';
import FormStep from 'client/components/FormStepper/FormStep';
import FormWithSchema from 'client/components/Forms/FormWithSchema';
import { FORM_FIELDS, STEP_FORM_SCHEMA } from './schema';
const BasicConfiguration = () => {
const STEP_ID = 'role';
export const STEP_ID = 'role';
return {
id: STEP_ID,
label: 'Configuration',
content: FormStep,
resolver: STEP_FORM_SCHEMA,
FormComponent: () => (
<FormWithSchema cy="form-flow" fields={FORM_FIELDS} id={STEP_ID} />
)
};
};
const BasicConfiguration = () => ({
id: STEP_ID,
label: 'Configuration',
resolver: STEP_FORM_SCHEMA,
content: useCallback(
() => <FormWithSchema cy="form-tier" fields={FORM_FIELDS} id={STEP_ID} />,
[]
)
});
export default BasicConfiguration;

View File

@ -0,0 +1,44 @@
import React, { useCallback, useContext } from 'react';
import useListForm from 'client/hooks/useListForm';
import ListCards from 'client/components/List/ListCards';
import { STEP_ID as NETWORKING } from 'client/containers/Application/Create/Steps/Networking';
import { Context } from 'client/containers/Application/Create/Steps/Roles';
import { STEP_FORM_SCHEMA } from './schema';
export const STEP_ID = 'networks';
const Networks = () => ({
id: STEP_ID,
label: 'Networks',
resolver: STEP_FORM_SCHEMA,
content: useCallback(({ data, setFormData }) => {
const { nestedForm: list } = useContext(Context);
const { handleSelect, handleUnselect } = useListForm({
key: STEP_ID,
multiple: true,
setList: setFormData
});
console.log('list', list);
return (
<ListCards
list={list[NETWORKING]}
CardComponent={() => <h1>hi</h1>}
cardsProps={({ value }) => {
const { ID } = value;
return {
isSelected: data?.some(selected => selected === ID),
handleSelect: () => handleSelect(ID),
handleUnselect: () => handleUnselect(ID)
};
}}
/>
);
}, [])
});
export default Networks;

View File

@ -0,0 +1,6 @@
import * as yup from 'yup';
export const STEP_FORM_SCHEMA = yup
.array()
.of(yup.string().trim())
.default(undefined);

View File

@ -1,44 +1,50 @@
import React from 'react';
import React, { useCallback } from 'react';
import TemplateIcon from '@material-ui/icons/InsertDriveFileOutlined';
import MarketplaceIcon from '@material-ui/icons/ShoppingCartOutlined';
import DockerLogo from 'client/icons/docker';
import ProcessScreen from 'client/components/ProcessScreen';
import FormStep from 'client/components/FormStepper/FormStep';
import ListTemplates from './List/Templates';
import ListMarketApps from './List/MarketApps';
import DockerFile from './List/Docker';
import { STEP_FORM_SCHEMA } from './schema';
const Template = () => {
const STEP_ID = 'template';
const SCREENS = [
{
id: 'template',
button: <TemplateIcon style={{ fontSize: 100 }} />,
screen: ListTemplates
},
{
id: 'app',
button: <MarketplaceIcon style={{ fontSize: 100 }} />,
screen: ListMarketApps
},
{
id: 'docker',
button: <DockerLogo width="100" height="100%" color="#066da5" />,
screen: DockerFile
}
];
export const STEP_ID = 'template';
return {
id: STEP_ID,
label: 'Template',
content: FormStep,
resolver: STEP_FORM_SCHEMA,
FormComponent: props => ProcessScreen({ screens: SCREENS, ...props })
};
};
const SCREENS = [
{
id: 'template',
button: <TemplateIcon style={{ fontSize: 100 }} />,
screen: ListTemplates
},
{
id: 'app',
button: <MarketplaceIcon style={{ fontSize: 100 }} />,
screen: ListMarketApps
},
{
id: 'docker',
button: <DockerLogo width="100" height="100%" color="#066da5" />,
screen: DockerFile
}
];
const Template = () => ({
id: STEP_ID,
label: 'Template',
resolver: STEP_FORM_SCHEMA,
content: useCallback(
({ data, setFormData }) =>
ProcessScreen({
screens: SCREENS,
id: STEP_ID,
values: data ?? {},
setFormData
}),
[]
)
});
export default Template;

View File

@ -1,61 +1,125 @@
import React, { useMemo } from 'react';
import * as yup from 'yup';
import React, { useEffect, useState, useCallback } from 'react';
import * as yup from 'yup';
import { useWatch } from 'react-hook-form';
import useListForm from 'client/hooks/useListForm';
import FormStepper from 'client/components/FormStepper';
import { DialogForm } from 'client/components/Dialogs';
import FormList from 'client/components/FormStepper/FormList';
import FlowWithFAB from 'client/components/Flows/FlowWithFAB';
import Steps from './Steps';
export const Context = React.createContext({});
export const STEP_ID = 'tiers';
const Roles = () => {
const STEP_ID = 'tiers';
const { steps, defaultValues, resolvers } = Steps();
return useMemo(
() => ({
id: STEP_ID,
label: 'Tier Definition',
content: FormList,
DEFAULT_DATA: defaultValues,
resolver: yup
.array()
.of(resolvers)
.min(1)
.required()
.default([]),
ListComponent: ({ list, handleCreate }) => (
<div>
<button onClick={handleCreate}>Add role</button>
<div>{JSON.stringify(list)}</div>
</div>
),
DialogComponent: ({ values, onSubmit, onCancel, ...props }) => (
<DialogForm
title={'Role form'}
resolver={resolvers}
values={values}
onCancel={onCancel}
{...props}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%'
}}
>
<FormStepper
steps={steps}
initialValue={values}
onSubmit={onSubmit}
/>
return {
id: STEP_ID,
label: 'Tier Definition',
DEFAULT_DATA: defaultValues,
resolver: yup
.array()
.of(resolvers)
.min(1)
.required()
.default([]),
content: useCallback(({ data, setFormData }) => {
const [showDialog, setShowDialog] = useState(false);
const [nestedForm, setNestedForm] = useState({});
const form = useWatch({});
const { editingData, handleEdit, handleSave } = useListForm({
key: STEP_ID,
list: data,
setList: setFormData,
defaultValue: defaultValues
});
useEffect(() => {
setNestedForm(form);
}, []);
return (
<>
<div>
<button
onClick={() => {
handleEdit();
setShowDialog(true);
}}
>
Add role
</button>
<div>{JSON.stringify(data)}</div>
</div>
</DialogForm>
)
}),
[]
);
{showDialog && (
<Context.Provider value={{ nestedForm }}>
<DialogForm
open={showDialog}
title={'Tier form'}
resolver={resolvers}
values={editingData.data}
onCancel={() => setShowDialog(false)}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%'
}}
>
<FormStepper
steps={steps}
initialValue={editingData.data}
onSubmit={values => {
handleSave(values);
setShowDialog(false);
}}
/>
</div>
</DialogForm>
</Context.Provider>
)}
</>
);
}, [])
/* DialogComponent: ({ values, onSubmit, onCancel, ...props }) => {
const form = useWatch({});
const [nestedForm, setNestedForm] = useState({});
useEffect(() => {
setNestedForm(form);
}, []);
return (
<Context.Provider value={{ nestedForm }}>
<DialogForm
title={'Tier form'}
resolver={resolvers}
values={values}
onCancel={onCancel}
{...props}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%'
}}
>
<FormStepper
steps={steps}
initialValue={values}
onSubmit={onSubmit}
/>
</div>
</DialogForm>
</Context.Provider>
); */
};
};
export default Roles;

View File

@ -0,0 +1,61 @@
import { useState } from 'react';
const useListSelect = ({ multiple, key, list, setList, defaultValue }) => {
const [editingData, setEditingData] = useState({});
const handleSelect = index =>
setList(prevData => ({
...prevData,
[key]: multiple ? [...prevData[key], index] : [index]
}));
const handleUnselect = indexRemove =>
setList(prevData => ({
...prevData,
[key]: prevData[key]?.filter(index => index !== indexRemove)
}));
const handleSave = values => {
setList(prevData => ({
...prevData,
[key]: Object.assign(prevData[key], {
[editingData.index]: values
})
}));
};
const handleEdit = (index = list?.length) => {
const openData = list[index] ?? defaultValue;
setEditingData({ index, data: openData });
};
const handleClone = index => {
const item = list[index];
const cloneItem = { ...item, name: `${item?.name}_clone` };
const cloneData = [...list];
cloneData.splice(index + 1, 0, cloneItem);
setList(prevData => ({ ...prevData, [key]: cloneData }));
};
const handleRemove = indexRemove => {
// TODO confirmation??
setList(prevData => ({
...prevData,
[key]: prevData[key]?.filter((_, index) => index !== indexRemove)
}));
};
return {
editingData,
handleSelect,
handleUnselect,
handleSave,
handleEdit,
handleClone,
handleRemove
};
};
export default useListSelect;