Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add volume mode and access mode selection for VM related volumes in the Persistent Volume grid #1529

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/app/common/components/SimpleSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React, { useState } from 'react';
import {
Select,
SelectOption,
SelectOptionObject,
SelectProps,
SelectOptionProps,
SelectProps,
} from '@patternfly/react-core';
import React, { useState } from 'react';

import './SimpleSelect.css';

Expand Down Expand Up @@ -51,6 +51,7 @@ const SimpleSelect: React.FunctionComponent<ISimpleSelectProps> = ({
<SelectOption
key={option.toString()}
value={option}
description={(option as OptionWithValue)?.props?.description}
{...(typeof option === 'object' && (option as OptionWithValue).props)}
/>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import React from 'react';
import { useState } from 'react';
import {
Dropdown,
DropdownGroup,
DropdownItem,
DropdownPosition,
KebabToggle,
Flex,
FlexItem,
DropdownGroup,
KebabToggle,
} from '@patternfly/react-core';
import { useOpenModal } from '../../../../duck';
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import WizardContainer from '../Wizard/WizardContainer';
import ConfirmModal from '../../../../../common/components/ConfirmModal';
import { IPlan } from '../../../../../plan/duck/types';
import { useDispatch } from 'react-redux';
import { PlanActions } from '../../../../../plan/duck';
import { IPlan } from '../../../../../plan/duck/types';
import { useOpenModal } from '../../../../duck';
import WizardContainer from '../Wizard/WizardContainer';
import { MigrationActionsDropdownGroup } from './MigrationActionsDropdownGroup';
import { MigrationConfirmModals, useMigrationConfirmModalState } from './MigrationConfirmModals';
interface IPlanActionsProps {
Expand Down
18 changes: 9 additions & 9 deletions src/app/home/pages/PlansPage/components/Wizard/GeneralForm.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React, { useEffect, useRef } from 'react';
import { useFormikContext } from 'formik';
import { IFormValues } from './WizardContainer';
import { Form, FormGroup, TextContent, Text, TextInput, Tooltip } from '@patternfly/react-core';
import { Form, FormGroup, Text, TextContent, TextInput, Tooltip } from '@patternfly/react-core';
import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon';
import spacing from '@patternfly/react-styles/css/utilities/Spacing/spacing';
import { useFormikContext } from 'formik';
import React, { useEffect, useRef } from 'react';
import { useSelector } from 'react-redux';
import { DefaultRootState } from '../../../../../../configureStore';
import { ICluster } from '../../../../../cluster/duck/types';
import SimpleSelect, { OptionWithValue } from '../../../../../common/components/SimpleSelect';
import { usePausedPollingEffect } from '../../../../../common/context';
import { useForcedValidationOnChange } from '../../../../../common/duck/hooks';
import { validatedState } from '../../../../../common/helpers';
import { ICluster } from '../../../../../cluster/duck/types';
import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/js/icons/exclamation-triangle-icon';
import { usePausedPollingEffect } from '../../../../../common/context';
import { IStorage } from '../../../../../storage/duck/types';
import { MigrationType } from '../../types';
import { useSelector } from 'react-redux';
import { DefaultRootState } from '../../../../../../configureStore';
import { IFormValues } from './WizardContainer';

export type IGeneralFormProps = {
isEdit: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useFormikContext } from 'formik';
import React from 'react';
import SimpleSelect, { OptionWithValue } from '../../../../../common/components/SimpleSelect';
import {
IMigPlanStorageClass,
IPlanPersistentVolume,
IVolumeAccessModes,
} from '../../../../../plan/duck/types';
import { IFormValues } from './WizardContainer';

const styles = require('./PVStorageClassSelect.module').default;

interface IPVAccessModeSelectProps {
pv: IPlanPersistentVolume;
currentPV: IPlanPersistentVolume;
storageClasses: IMigPlanStorageClass[];
}

export const PVAccessModeSelect: React.FunctionComponent<IPVAccessModeSelectProps> = ({
pv,
currentPV,
storageClasses,
}: IPVAccessModeSelectProps) => {
const { values, setFieldValue } = useFormikContext<IFormValues>();

const currentStorageClass = values.pvStorageClassAssignment[currentPV.name];
const volumeAccessModes = currentStorageClass.volumeAccessModes;
const currentVolumeMode = currentStorageClass.volumeMode;
const possibleAccessModes = volumeAccessModes.find(
(volumeAccessMode: IVolumeAccessModes) => volumeAccessMode.volumeMode === currentVolumeMode
) || { accessModes: [] as string[] };

const onAccessModeChange = (currentPV: IPlanPersistentVolume, value: string) => {
currentStorageClass.accessMode = value;
const updatedAssignment = {
...values.pvStorageClassAssignment,
[currentPV.name]: currentStorageClass,
};
setFieldValue('pvStorageClassAssignment', updatedAssignment);
};

const accessModeOptions: OptionWithValue[] = [
...possibleAccessModes.accessModes.map((value: string) => ({
value: value,
toString: () => value,
})),
];
accessModeOptions.splice(1, 1); // remove ReadOnly option
accessModeOptions.splice(0, 0, { value: 'auto', toString: () => 'Auto' });

return (
<SimpleSelect
id="select-storage-class"
aria-label="Select storage class"
className={styles.copySelectStyle}
onChange={(option: any) => onAccessModeChange(currentPV, option.value)}
options={accessModeOptions}
placeholderText="Select volume mode..."
value={
accessModeOptions.find(
(option) => currentStorageClass && option.value === currentStorageClass.accessMode
) || accessModeOptions[0]
}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,26 @@ export const PVStorageClassSelect: React.FunctionComponent<IPVStorageClassSelect
}: IPVStorageClassSelectProps) => {
const { values, setFieldValue } = useFormikContext<IFormValues>();

const currentStorageClass = values.pvStorageClassAssignment[pv.name];
const currentStorageClass = values.pvStorageClassAssignment[currentPV.name];

const onStorageClassChange = (currentPV: IPlanPersistentVolume, value: string) => {
const newSc = storageClasses.find((sc) => sc !== '' && sc.name === value) || '';
const newSc = storageClasses.find((sc) => sc.name === value) || '';
const copy = JSON.parse(JSON.stringify(newSc));
copy.volumeMode = 'auto';
copy.accessMode = 'auto';
const updatedAssignment = {
...values.pvStorageClassAssignment,
[currentPV.name]: newSc,
[currentPV.name]: copy,
};
setFieldValue('pvStorageClassAssignment', updatedAssignment);
};

const noneOption = { value: '', toString: () => 'None' };
const storageClassOptions: OptionWithValue[] = [
...storageClasses.map((storageClass) => ({
value: storageClass !== '' && storageClass.name,
value: storageClass.name,
toString: () => targetStorageClassToString(storageClass),
props: { description: storageClass.provisioner },
})),
noneOption,
];

return (
Expand All @@ -48,11 +50,9 @@ export const PVStorageClassSelect: React.FunctionComponent<IPVStorageClassSelect
onChange={(option: any) => onStorageClassChange(currentPV, option.value)}
options={storageClassOptions}
value={
currentStorageClass === ''
? noneOption
: storageClassOptions.find(
(option) => currentStorageClass && option.value === currentStorageClass.name
) || undefined
storageClassOptions.find(
(option) => currentStorageClass && option.value === currentStorageClass.name
) || undefined
}
placeholderText="Select a storage class..."
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { useFormikContext } from 'formik';
import React from 'react';
import SimpleSelect, { OptionWithValue } from '../../../../../common/components/SimpleSelect';
import {
IMigPlanStorageClass,
IPlanPersistentVolume,
IVolumeAccessModes,
} from '../../../../../plan/duck/types';
import { IFormValues } from './WizardContainer';

const styles = require('./PVStorageClassSelect.module').default;

interface IPVVolumeModeSelectProps {
pv: IPlanPersistentVolume;
currentPV: IPlanPersistentVolume;
storageClasses: IMigPlanStorageClass[];
}

export const PVVolumeModeSelect: React.FunctionComponent<IPVVolumeModeSelectProps> = ({
pv,
currentPV,
storageClasses,
}: IPVVolumeModeSelectProps) => {
const { values, setFieldValue } = useFormikContext<IFormValues>();

const currentStorageClass = values.pvStorageClassAssignment[currentPV.name];
const volumeAccessModes = currentStorageClass.volumeAccessModes;

const onVolumeModeChange = (currentPV: IPlanPersistentVolume, value: string) => {
currentStorageClass.volumeMode = value;
const updatedAssignment = {
...values.pvStorageClassAssignment,
[currentPV.name]: currentStorageClass,
};
setFieldValue('pvStorageClassAssignment', updatedAssignment);
};

const volumeModeOptions: OptionWithValue[] = [
...volumeAccessModes.map((volumeAccessMode: IVolumeAccessModes) => ({
value: volumeAccessMode.volumeMode,
toString: () => volumeAccessMode.volumeMode,
})),
];
volumeModeOptions.splice(0, 0, { value: 'auto', toString: () => 'Auto' });

return (
<SimpleSelect
id="select-storage-class"
aria-label="Select storage class"
className={styles.copySelectStyle}
onChange={(option: any) => onVolumeModeChange(currentPV, option.value)}
options={volumeModeOptions}
placeholderText="Select volume mode..."
value={
volumeModeOptions.find(
(option) => currentStorageClass && option.value === currentStorageClass.volumeMode
) || volumeModeOptions[0]
}
/>
);
};
74 changes: 53 additions & 21 deletions src/app/home/pages/PlansPage/components/Wizard/VolumesTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,13 @@ import {
import {
getSuggestedPvStorageClasses,
pvcNameToString,
targetAccessModeToString,
targetStorageClassToString,
targetVolumeModeToString,
} from '../../helpers';
import { PVAccessModeSelect } from './PVAccessModeSelect';
import { PVStorageClassSelect } from './PVStorageClassSelect';
import { PVVolumeModeSelect } from './PVVolumeModeSelect';
import { VerifyCopyCheckbox } from './VerifyCopyCheckbox';
import { VerifyCopyWarningModal, VerifyWarningState } from './VerifyCopyWarningModal';
import { IFormValues, IOtherProps } from './WizardContainer';
Expand Down Expand Up @@ -128,6 +132,8 @@ const VolumesTable: React.FunctionComponent<IVolumesTableProps> = ({
{ title: 'Source storage class', transforms: [sortable] },
{ title: 'Size', transforms: [sortable] },
{ title: 'Target storage class', transforms: [sortable] },
{ title: 'Target volume mode', transforms: [sortable] },
{ title: 'Target access mode', transforms: [sortable] },
{
title: (
<React.Fragment>
Expand Down Expand Up @@ -172,6 +178,8 @@ const VolumesTable: React.FunctionComponent<IVolumesTableProps> = ({
pv.storageClass,
pv.capacity,
pv.selection.storageClass,
pv.pvc.volumeMode,
pv.pvc.accessModes[0],
pv.selection.verify,
]
: [
Expand Down Expand Up @@ -219,6 +227,20 @@ const VolumesTable: React.FunctionComponent<IVolumesTableProps> = ({
getItemValue: (pv) =>
targetStorageClassToString(values.pvStorageClassAssignment[pv.name]),
},
{
key: 'volumeMode',
title: 'Target volume mode',
type: FilterType.search,
placeholderText: 'Filter by volume mode...',
getItemValue: (pv) => targetVolumeModeToString(values.pvStorageClassAssignment[pv.name]),
},
{
key: 'accessMode',
title: 'Target access mode',
type: FilterType.search,
placeholderText: 'Filter by access mode...',
getItemValue: (pv) => targetAccessModeToString(values.pvStorageClassAssignment[pv.name]),
},
]
: [
...commonFilterCategories,
Expand Down Expand Up @@ -400,6 +422,12 @@ const VolumesTable: React.FunctionComponent<IVolumesTableProps> = ({
<PVStorageClassSelect {...{ pv, currentPV, storageClasses, currentStorageClass }} />
),
},
{
title: <PVVolumeModeSelect {...{ pv, currentPV, storageClasses }} />,
},
{
title: <PVAccessModeSelect {...{ pv, currentPV, storageClasses }} />,
},
{
title: (
<VerifyCopyCheckbox
Expand Down Expand Up @@ -556,11 +584,13 @@ const VolumesTable: React.FunctionComponent<IVolumesTableProps> = ({
isSelected: allRowsSelected,
}}
/>
{columns.map((column, columnIndex) => (
<Th key={columnIndex} width={columnIndex === 0 ? 20 : 10}>
{column.title}
</Th>
))}
{columns
.filter((column, columnIndex) => columnIndex !== 0)
.map((column, columnIndex) => (
<Th key={columnIndex} width={10}>
{column.title}
</Th>
))}
<Th width={20}></Th>
</Tr>
</Thead>
Expand All @@ -577,22 +607,24 @@ const VolumesTable: React.FunctionComponent<IVolumesTableProps> = ({
props: row,
}}
/>
{row.cells.map((cell, cellIndex) => {
const shiftedIndex = cellIndex + 1;
console.log('cell', cell);
return (
<Td
key={`${rowIndex}_${shiftedIndex}`}
dataLabel={
typeof columns[cellIndex].title === 'string'
? (columns[cellIndex].title as string)
: ''
}
>
{typeof cell !== 'string' ? cell.title : cell}
</Td>
);
})}
{row.cells
.filter((column, columnIndex) => columnIndex !== 0)
.map((cell, cellIndex) => {
const shiftedIndex = cellIndex + 1;
console.log('cell', cell);
return (
<Td
key={`${rowIndex}_${shiftedIndex}`}
dataLabel={
typeof columns[cellIndex].title === 'string'
? (columns[cellIndex].title as string)
: ''
}
>
{typeof cell !== 'string' ? cell.title : cell}
</Td>
);
})}
</Tr>
);
})}
Expand Down
Loading