Skip to content

Commit

Permalink
CNV-18014: Add Size drop down to Add volume modal
Browse files Browse the repository at this point in the history
Add missing Size dropdown to 'Add volume to boot from' modal
when creating a VM, also to 'Edit volume parameters' modal
in the Bootable volumes list, to separate workload type and size,
and so to set the appropriate Instancetype label to the selected
DataSource.

Fixes https://issues.redhat.com/browse/CNV-18014,
https://issues.redhat.com/browse/CNV-15501
  • Loading branch information
Hilda Stastna committed Feb 20, 2023
1 parent 07d2b4f commit 0ff0e83
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 42 deletions.
1 change: 1 addition & 0 deletions locales/en/plugin__kubevirt-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,7 @@
"Show top 5": "Show top 5",
"Show virtualization health alerts": "Show virtualization health alerts",
"Single user (RWO)": "Single user (RWO)",
"size": "size",
"Size": "Size",
"Size cannot be {{errorValue}}": "Size cannot be {{errorValue}}",
"Snapshots": "Snapshots",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTransla
import { convertResourceArrayToMap } from '@kubevirt-utils/resources/shared';
import { ANNOTATIONS } from '@kubevirt-utils/resources/template';
import { K8sResourceCommon } from '@openshift-console/dynamic-plugin-sdk';
import { Form, FormGroup, TextArea } from '@patternfly/react-core';
import { Form, FormGroup, Grid, GridItem, TextArea } from '@patternfly/react-core';

import { BootableVolumeMetadata } from '../../utils/types';
import { changeBootableVolumeMetadata } from '../../utils/utils';
import { changeBootableVolumeMetadata, getInstanceTypesWithSizes } from '../../utils/utils';

type EditBootableVolumesModalProps = {
dataSource: V1beta1DataSource;
Expand All @@ -41,51 +41,106 @@ const EditBootableVolumesModal: FC<EditBootableVolumesModalProps> = ({
() => Object.keys(convertResourceArrayToMap(preferences)).sort((a, b) => a.localeCompare(b)),
[preferences],
);
const initialMetadata: BootableVolumeMetadata = useMemo(() => {

const instanceTypesAndSizesObject = useMemo(
() => getInstanceTypesWithSizes(instanceTypesNames),
[instanceTypesNames],
);

const initialParams = useMemo(() => {
const instanceTypeLabel =
dataSource?.metadata?.labels?.[DEFAULT_INSTANCETYPE_LABEL]?.split('.');

return {
labels: dataSource?.metadata?.labels,
annotations: dataSource?.metadata?.annotations,
preference: dataSource?.metadata?.labels?.[DEFAULT_PREFERENCE_LABEL],
instanceType: instanceTypeLabel?.[0],
size: instanceTypeLabel?.[1],
description: dataSource?.metadata?.annotations?.[ANNOTATIONS.description],
};
}, [dataSource]);
const [metadata, setMetadata] = useState<BootableVolumeMetadata>(initialMetadata);

const setBootableVolumeMetadata = (key: string, fieldKey: string) => (value: string) =>
setMetadata((prevState) => ({
...prevState,
[key]: { ...prevState[key], [fieldKey]: value },
}));
const [preference, setPreference] = useState<string>(initialParams.preference);
const [instanceType, setInstanceType] = useState<string>(initialParams.instanceType);
const [size, setSize] = useState<string>(initialParams.size);
const [sizeOptions, setSizeOptions] = useState<string[]>(
instanceTypesAndSizesObject[instanceType] || [],
);
const [description, setDescription] = useState<string>(initialParams.description);

const onInstanceTypeChange = (value: string) => {
setInstanceType(value);
setSizeOptions(instanceTypesAndSizesObject[value]);
setSize(instanceTypesAndSizesObject[value][0]);
};

const onChangeVolumeParams = () => {
const preferenceLabel = preference && { [DEFAULT_PREFERENCE_LABEL]: preference };
const instanceLabel = instanceType && {
[DEFAULT_INSTANCETYPE_LABEL]: `${instanceType}.${size}`,
};
const descriptionAnnotation = description && { [ANNOTATIONS.description]: description };

const metadata: BootableVolumeMetadata = {
labels: {
...dataSource?.metadata?.labels,
...preferenceLabel,
...instanceLabel,
},
annotations: {
...dataSource?.metadata?.annotations,
...descriptionAnnotation,
},
};

return changeBootableVolumeMetadata(dataSource, metadata);
};

return (
<TabModal<K8sResourceCommon>
obj={dataSource}
isOpen={isOpen}
onClose={onClose}
headerText={t('Edit volume parameters')}
onSubmit={changeBootableVolumeMetadata(dataSource, initialMetadata, metadata)}
onSubmit={onChangeVolumeParams()}
>
<Form>
<FormGroup label={t('Preference')} isRequired>
<FilterSelect
selected={metadata?.labels?.[DEFAULT_PREFERENCE_LABEL]}
setSelected={setBootableVolumeMetadata('labels', DEFAULT_PREFERENCE_LABEL)}
selected={preference}
setSelected={setPreference}
options={preferencesNames}
groupVersionKind={VirtualMachineClusterPreferenceModelGroupVersionKind}
optionLabelText={t('preference')}
/>
</FormGroup>
<FormGroup label={t('Default InstanceType')}>
<FilterSelect
selected={metadata?.labels?.[DEFAULT_INSTANCETYPE_LABEL]}
setSelected={setBootableVolumeMetadata('labels', DEFAULT_INSTANCETYPE_LABEL)}
options={instanceTypesNames}
groupVersionKind={VirtualMachineClusterInstancetypeModelGroupVersionKind}
optionLabelText={t('InstanceType')}
/>
</FormGroup>
<Grid hasGutter>
<GridItem span={6}>
<FormGroup label={t('Default InstanceType')}>
<FilterSelect
selected={instanceType}
setSelected={onInstanceTypeChange}
options={Object.keys(instanceTypesAndSizesObject) || []}
groupVersionKind={VirtualMachineClusterInstancetypeModelGroupVersionKind}
optionLabelText={t('InstanceType')}
/>
</FormGroup>
</GridItem>
<GridItem span={6}>
<FormGroup label={t('Size')}>
<FilterSelect
selected={size}
setSelected={setSize}
options={sizeOptions}
groupVersionKind={VirtualMachineClusterInstancetypeModelGroupVersionKind}
optionLabelText={t('size')}
/>
</FormGroup>
</GridItem>
</Grid>
<FormGroup label={t('Description')}>
<TextArea
value={metadata?.annotations?.description}
onChange={setBootableVolumeMetadata('annotations', ANNOTATIONS?.description)}
value={description}
onChange={setDescription}
resizeOrientation="vertical"
aria-label={t('description text area')}
/>
Expand Down
33 changes: 27 additions & 6 deletions src/views/bootablevolumes/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ export const deleteBootableVolumeMetadata = (obj: V1beta1DataSource) => {
};

export const changeBootableVolumeMetadata =
(
obj: V1beta1DataSource,
initialMetadata: BootableVolumeMetadata,
metadata: BootableVolumeMetadata,
) =>
async () => {
(obj: V1beta1DataSource, metadata: BootableVolumeMetadata) => async () => {
const initialMetadata = {
labels: obj?.metadata?.labels,
annotations: obj?.metadata?.annotations,
};

initialMetadata !== metadata &&
(await k8sPatch({
model: DataSourceModel,
Expand All @@ -98,3 +98,24 @@ export const changeBootableVolumeMetadata =
],
}));
};

interface InstanceTypesAndSizesObject {
[key: string]: string[];
}

export const getInstanceTypesWithSizes = (
instanceTypesNames: string[] = [],
): InstanceTypesAndSizesObject => {
// eslint-disable-next-line prefer-const
let instanceTypesAndSizes: InstanceTypesAndSizesObject = {};

instanceTypesNames.forEach((instanceType) => {
const [instanceTypePart, sizePart] = instanceType.split('.');

instanceTypesAndSizes[instanceTypePart] !== undefined
? instanceTypesAndSizes[instanceTypePart].push(sizePart)
: (instanceTypesAndSizes[instanceTypePart] = [sizePart]);
});

return instanceTypesAndSizes;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { FC, useState } from 'react';
import React, { FC, useMemo, useState } from 'react';
import produce from 'immer';
import { getInstanceTypesWithSizes } from 'src/views/bootablevolumes/utils/utils';

import {
DEFAULT_INSTANCETYPE_LABEL,
Expand All @@ -21,7 +22,7 @@ import { UPLOAD_STATUS } from '@kubevirt-utils/hooks/useCDIUpload/utils';
import { useKubevirtTranslation } from '@kubevirt-utils/hooks/useKubevirtTranslation';
import { ANNOTATIONS } from '@kubevirt-utils/resources/template';
import { k8sCreate } from '@openshift-console/dynamic-plugin-sdk';
import { Form, FormGroup, TextInput, Title } from '@patternfly/react-core';
import { Form, FormGroup, Grid, GridItem, TextInput, Title } from '@patternfly/react-core';

import { AddBootableVolumeButtonProps } from '../AddBootableVolumeButton/AddBootableVolumeButton';

Expand Down Expand Up @@ -71,6 +72,46 @@ const AddBootableVolumeModal: FC<AddBootableVolumeModalProps> = ({

const isUploadForm = formSelection === RADIO_FORM_SELECTION.UPLOAD_IMAGE;

const instanceTypesAndSizesObject = useMemo(
() => getInstanceTypesWithSizes(instanceTypesNames),
[instanceTypesNames],
);
const instanceTypeAndSize = useMemo(
() => labels?.[DEFAULT_INSTANCETYPE_LABEL]?.split('.') || ['', ''],
[labels],
);

const [instanceType, setInstanceType] = useState<string>(instanceTypeAndSize[0]);
const [instanceSize, setInstanceSize] = useState<string>(instanceTypeAndSize[1]);
const [sizeOptions, setSizeOptions] = useState<string[]>(
instanceTypesAndSizesObject[instanceType] || [],
);

const onInstanceTypeChange = (value: string) => {
const newSizesArray = instanceTypesAndSizesObject[value];
setInstanceType(value);
setSizeOptions(newSizesArray);
setInstanceSize(newSizesArray[0]); // when changing Default InstanceType, set the Size to 1st option
setBootableVolume((prevState) => ({
...prevState,
labels: {
...prevState?.labels,
[DEFAULT_INSTANCETYPE_LABEL]: `${value}.${newSizesArray[0]}`,
},
}));
};

const onInstanceSizeChange = (value: string) => {
setInstanceSize(value);
setBootableVolume((prevState) => ({
...prevState,
labels: {
...prevState?.labels,
[DEFAULT_INSTANCETYPE_LABEL]: `${instanceType}.${value}`,
},
}));
};

const setBootableVolumeField = (key: string, fieldKey?: string) => (value: string) =>
fieldKey
? setBootableVolume((prevState) => ({
Expand Down Expand Up @@ -197,15 +238,32 @@ const AddBootableVolumeModal: FC<AddBootableVolumeModalProps> = ({
optionLabelText={t('preference')}
/>
</FormGroup>
<FormGroup label={t('Default InstanceType')}>
<FilterSelect
selected={labels?.[DEFAULT_INSTANCETYPE_LABEL]}
setSelected={setBootableVolumeField('labels', DEFAULT_INSTANCETYPE_LABEL)}
options={instanceTypesNames}
groupVersionKind={VirtualMachineClusterInstancetypeModelGroupVersionKind}
optionLabelText={t('InstanceType')}
/>
</FormGroup>

<Grid hasGutter>
<GridItem span={6}>
<FormGroup label={t('Default InstanceType')}>
<FilterSelect
selected={instanceType}
setSelected={onInstanceTypeChange}
options={Object.keys(instanceTypesAndSizesObject) || []}
groupVersionKind={VirtualMachineClusterInstancetypeModelGroupVersionKind}
optionLabelText={t('InstanceType')}
/>
</FormGroup>
</GridItem>
<GridItem span={6}>
<FormGroup label={t('Size')}>
<FilterSelect
selected={instanceSize}
setSelected={onInstanceSizeChange}
options={sizeOptions}
groupVersionKind={VirtualMachineClusterInstancetypeModelGroupVersionKind}
optionLabelText={t('size')}
/>
</FormGroup>
</GridItem>
</Grid>

<FormGroup label={t('Description')}>
<TextInput
id="description"
Expand Down

0 comments on commit 0ff0e83

Please sign in to comment.