V2Wizrad: fix validation at file system step (HMS-3733)

this commit fix validation when usr choose duplicate mount point
andwhen there is no root partition
This commit is contained in:
Michal Gold 2024-03-17 13:02:33 +02:00 committed by Lucas Garfield
parent 92fbb8babf
commit 698d21df42
5 changed files with 117 additions and 4 deletions

View file

@ -11,6 +11,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
import DetailsStep from './steps/Details';
import FileSystemStep from './steps/FileSystem';
import { FileSystemStepFooter } from './steps/FileSystem/FileSystemConfiguration';
import ImageOutputStep from './steps/ImageOutput';
import OscapStep from './steps/Oscap';
import PackagesStep from './steps/Packages';
@ -260,7 +261,7 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
<WizardStep
name="File system configuration"
id="step-file-system"
footer={<CustomWizardFooter disableNext={false} />}
footer={<FileSystemStepFooter />}
>
<FileSystemStep />
</WizardStep>

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
Alert,
@ -8,6 +8,8 @@ import {
TextContent,
TextInput,
TextVariants,
useWizardContext,
WizardFooterWrapper,
} from '@patternfly/react-core';
import { Select, SelectOption } from '@patternfly/react-core/deprecated';
import {
@ -29,9 +31,16 @@ import {
removePartition,
selectPartitions,
changePartitionUnit,
setIsNextButtonTouched,
selectIsNextButtonTouched,
selectFileSystemPartitionMode,
} from '../../../../store/wizardSlice';
import UsrSubDirectoriesDisabled from '../../UsrSubDirectoriesDisabled';
import { ValidatedTextInput } from '../../ValidatedTextInput';
import {
getDuplicateMountPoints,
isFileSystemConfigValid,
} from '../../validators';
export type Partition = {
id: string;
@ -40,12 +49,58 @@ export type Partition = {
unit: Units;
};
export const FileSystemStepFooter = () => {
const { goToNextStep, goToPrevStep, close } = useWizardContext();
const [isValid, setIsValid] = useState(false);
const dispatch = useAppDispatch();
const [isNextDisabled, setNextDisabled] = useState(false);
const fileSystemPartitionMode = useAppSelector((state) =>
selectFileSystemPartitionMode(state)
);
const partitions = useAppSelector((state) => selectPartitions(state));
const onValidate = () => {
dispatch(setIsNextButtonTouched(false));
if (!isValid) {
setNextDisabled(true);
} else {
goToNextStep();
}
};
useEffect(() => {
if (
fileSystemPartitionMode === 'automatic' ||
isFileSystemConfigValid(partitions)
) {
setIsValid(true);
} else setIsValid(false);
setNextDisabled(false);
dispatch(setIsNextButtonTouched(true));
}, [partitions, fileSystemPartitionMode, dispatch]);
return (
<WizardFooterWrapper>
<Button onClick={onValidate} isDisabled={isNextDisabled}>
Next
</Button>
<Button variant="secondary" onClick={goToPrevStep}>
Back
</Button>
<Button ouiaId="wizard-cancel-btn" variant="link" onClick={close}>
Cancel
</Button>
</WizardFooterWrapper>
);
};
const FileSystemConfiguration = () => {
const partitions = useAppSelector((state) => selectPartitions(state));
const environments = useAppSelector((state) => selectImageTypes(state));
const dispatch = useAppDispatch();
const isNextButtonPristine = useAppSelector((state) =>
selectIsNextButtonTouched(state)
);
const handleAddPartition = () => {
const id = uuidv4();
dispatch(
@ -66,6 +121,17 @@ const FileSystemConfiguration = () => {
{partitions?.find((partition) =>
partition?.mountpoint?.includes('/usr')
) && <UsrSubDirectoriesDisabled />}
{!isNextButtonPristine &&
getDuplicateMountPoints(partitions)?.length !== 0 &&
getDuplicateMountPoints(partitions)?.length !== undefined && (
<div>
<Alert
isInline
variant="warning"
title="Duplicate mount points: All mount points must be unique. Remove the duplicate or choose a new mount point."
/>
</div>
)}
<TextContent>
<Text>
Create partitions for your image by defining mount points and minimum
@ -167,16 +233,29 @@ const getSuffix = (mountpoint: string) => {
const Row = ({ partition }: RowPropTypes) => {
const dispatch = useAppDispatch();
const partitions = useAppSelector((state) => selectPartitions(state));
const handleRemovePartition = (id: string) => {
dispatch(removePartition(id));
};
const isNextButtonPristine = useAppSelector((state) =>
selectIsNextButtonTouched(state)
);
const duplicates = getDuplicateMountPoints(partitions);
return (
<Tr>
<Td />
<Td width={20}>
<Td className="pf-m-width-20">
<MountpointPrefix partition={partition} />
{!isNextButtonPristine &&
duplicates.indexOf(partition.mountpoint) !== -1 && (
<Alert
variant="danger"
isInline
isPlain
title="Duplicate mount point."
/>
)}
</Td>
{partition.mountpoint !== '/' &&
!partition.mountpoint.startsWith('/boot') &&

View file

@ -111,6 +111,7 @@ export const mapRequestToState = (request: BlueprintResponse): wizardState => {
fileSystem: {
mode: 'automatic',
partitions: [],
isNextButtonTouched: true,
},
architecture: request.image_requests[0].architecture,

View file

@ -1,3 +1,5 @@
import { Partition } from './steps/FileSystem/FileSystemConfiguration';
export const isAwsAccountIdValid = (awsAccountId: string | undefined) => {
return (
awsAccountId !== undefined &&
@ -38,3 +40,23 @@ export const isBlueprintNameValid = (blueprintName: string) =>
export const isBlueprintDescriptionValid = (blueprintDescription: string) => {
return blueprintDescription.length <= 250;
};
export const isFileSystemConfigValid = (partitions: Partition[]) => {
const duplicates = getDuplicateMountPoints(partitions);
return duplicates.length === 0;
};
export const getDuplicateMountPoints = (partitions: Partition[]): string[] => {
const mountPointSet: Set<string> = new Set();
const duplicates: string[] = [];
if (!partitions) {
return [];
}
for (const partition of partitions) {
const mountPoint = partition.mountpoint;
if (mountPointSet.has(mountPoint)) {
duplicates.push(mountPoint);
} else {
mountPointSet.add(mountPoint);
}
}
return duplicates;
};

View file

@ -76,6 +76,7 @@ export type wizardState = {
fileSystem: {
mode: FileSystemPartitionMode;
partitions: Partition[];
isNextButtonTouched: boolean;
};
repositories: {
customRepositories: CustomRepository[];
@ -130,6 +131,7 @@ const initialState: wizardState = {
fileSystem: {
mode: 'automatic',
partitions: [],
isNextButtonTouched: true,
},
repositories: {
customRepositories: [],
@ -233,6 +235,9 @@ export const selectEnabledServices = (state: RootState) => {
export const selectFileSystemPartitionMode = (state: RootState) => {
return state.wizard.fileSystem.mode;
};
export const selectIsNextButtonTouched = (state: RootState) => {
return state.wizard.fileSystem.isNextButtonTouched;
};
export const selectPartitions = (state: RootState) => {
return state.wizard.fileSystem.partitions;
@ -379,6 +384,10 @@ export const wizardSlice = createSlice({
) => {
state.fileSystem.partitions = action.payload;
},
setIsNextButtonTouched: (state, action: PayloadAction<boolean>) => {
state.fileSystem.isNextButtonTouched = action.payload;
},
changeFileSystemPartitionMode: (
state,
action: PayloadAction<FileSystemPartitionMode>
@ -489,6 +498,7 @@ export const {
changeDisabledServices,
changeEnabledServices,
changeFileSystemConfiguration,
setIsNextButtonTouched,
changeFileSystemPartitionMode,
addPartition,
removePartition,