V2 Wizard: Add File System Configuration Step (HMS-2781)

The FSC step is added to the wizard and takes full advantage of Redux
for state management.

This is still a work in progress.

Supported features:
1. Select partition mountpoint prefix (e.g. /var, /home)
2. Edit partition mountpoint suffix (e.g. /home/videogames)
3. Change displayed units (KiB, MiB, GiB)

Supported but buggy features:
1. Edit partition size

Unsupported features:
1. Add partitions
2. Remove partitions
3. Validation
This commit is contained in:
mgold1234 2024-02-18 12:45:45 +02:00 committed by Lucas Garfield
parent d063279b79
commit 430ea83df0
20 changed files with 751 additions and 111 deletions

7
package-lock.json generated
View file

@ -48,6 +48,7 @@
"@types/react": "18.2.64",
"@types/react-dom": "18.2.21",
"@types/react-redux": "7.1.33",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "7.1.0",
"@typescript-eslint/parser": "7.1.1",
"babel-jest": "29.7.0",
@ -4861,6 +4862,12 @@
"version": "0.0.3",
"license": "MIT"
},
"node_modules/@types/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==",
"dev": true
},
"node_modules/@types/webpack": {
"version": "4.41.34",
"dev": true,

View file

@ -73,6 +73,7 @@
"@types/react": "18.2.64",
"@types/react-dom": "18.2.21",
"@types/react-redux": "7.1.33",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "7.1.0",
"@typescript-eslint/parser": "7.1.1",
"babel-jest": "29.7.0",

View file

@ -10,6 +10,7 @@ import {
import { useNavigate, useSearchParams } from 'react-router-dom';
import DetailsStep from './steps/Details';
import FileSystemStep from './steps/FileSystem';
import ImageOutputStep from './steps/ImageOutput';
import OscapStep from './steps/Oscap';
import PackagesStep from './steps/Packages';
@ -131,7 +132,6 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
selectAzureResourceGroup(state)
);
const azureSource = useAppSelector((state) => selectAzureSource(state));
const registrationType = useAppSelector((state) =>
selectRegistrationType(state)
);
@ -249,6 +249,13 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
>
<OscapStep />
</WizardStep>
<WizardStep
name="File system configuration"
id="step-file-system"
footer={<CustomWizardFooter disableNext={false} />}
>
<FileSystemStep />
</WizardStep>
<WizardStep
name="Content"
id="step-content"

View file

@ -36,7 +36,7 @@ const EditImageWizard = ({ blueprintId }: EditImageWizardProps) => {
navigate(resolveRelPath(''));
}
}, [error, navigate]);
return <CreateImageWizard startStepIndex={12} />;
return <CreateImageWizard startStepIndex={13} />;
};
export default EditImageWizard;

View file

@ -0,0 +1,19 @@
import React from 'react';
import { Alert } from '@patternfly/react-core';
const UsrSubDirectoriesDisabled = () => {
return (
<Alert
variant="warning"
title="Sub-directories for the /usr mount point are no longer supported"
isInline
>
Please note that including sub-directories in the /usr path is no longer
supported. Previously included mount points with /usr sub-directory are
replaced by /usr when recreating an image.
</Alert>
);
};
export default UsrSubDirectoriesDisabled;

View file

@ -0,0 +1,44 @@
import React from 'react';
import {
Button,
Text,
TextContent,
TextVariants,
} from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
const FileSystemAutomaticPartition = () => {
return (
<TextContent>
<Text component={TextVariants.h3}>Automatic partitioning</Text>
<Text>
Red Hat will automatically partition your image to what is best,
depending on the target environment(s).
</Text>
<Text>
The target environment sometimes dictates the partitioning scheme or
parts of it, and sometimes the target environment is unknown (e.g., for
the .qcow2 generic cloud image).
</Text>
<Text>
Using automatic partitioning will apply the most current supported
configuration.
<br></br>
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/creating_customized_images_by_using_insights_image_builder/customizing-file-systems-during-the-image-creation"
className="pf-u-pl-0"
>
Customizing file systems during the image creation
</Button>
</Text>
</TextContent>
);
};
export default FileSystemAutomaticPartition;

View file

@ -0,0 +1,329 @@
import React, { useState } from 'react';
import {
Button,
Popover,
Text,
TextContent,
TextInput,
TextVariants,
} from '@patternfly/react-core';
import { Select, SelectOption } from '@patternfly/react-core/deprecated';
import {
HelpIcon,
MinusCircleIcon,
PlusCircleIcon,
} from '@patternfly/react-icons';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import { UNIT_GIB, UNIT_KIB, UNIT_MIB } from '../../../../constants';
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
import {
changePartitionMinSize,
changePartitionMountpoint,
selectPartitions,
} from '../../../../store/wizardSlice';
import UsrSubDirectoriesDisabled from '../../UsrSubDirectoriesDisabled';
import { ValidatedTextInput } from '../../ValidatedTextInput';
export type Partition = {
id: string;
mountpoint: string;
min_size: string;
};
const FileSystemConfiguration = () => {
const partitions = useAppSelector((state) => selectPartitions(state));
return (
<>
<TextContent>
<Text component={TextVariants.h3}>Configure partitions</Text>
</TextContent>
{partitions?.find((partition) =>
partition?.mountpoint?.includes('/usr')
) && <UsrSubDirectoriesDisabled />}
<TextContent>
<Text>
Create partitions for your image by defining mount points and minimum
sizes. Image builder creates partitions with a logical volume (LVM)
device type.
</Text>
<Text>
The order of partitions may change when the image is installed in
order to conform to best practices and ensure functionality.
<br></br>
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/creating_customized_images_by_using_insights_image_builder/customizing-file-systems-during-the-image-creation"
className="pf-u-pl-0"
>
Read more about manual configuration here
</Button>
</Text>
</TextContent>
<Table aria-label="File system table" variant="compact">
<Thead>
<Tr>
<Th />
<Th>Mount point</Th>
<Th></Th>
<Th>Type</Th>
<Th>
Minimum size
<Popover
hasAutoWidth
bodyContent={
<TextContent>
<Text>
Image Builder may extend this size based on requirements,
selected packages, and configurations.
</Text>
</TextContent>
}
>
<Button
variant="plain"
aria-label="File system configuration info"
aria-describedby="file-system-configuration-info"
className="pf-c-form__group-label-help"
>
<HelpIcon />
</Button>
</Popover>
</Th>
<Th />
<Th />
</Tr>
</Thead>
<Tbody data-testid="file-system-configuration-tbody">
{partitions &&
partitions.map((partition) => (
<Row key={partition.id} partition={partition} />
))}
</Tbody>
</Table>
<TextContent>
<Button
ouiaId="add-partition"
data-testid="file-system-add-partition"
className="pf-u-text-align-left"
variant="link"
icon={<PlusCircleIcon />}
onClick={() => {}}
>
Add partition
</Button>
</TextContent>
</>
);
};
type RowPropTypes = {
partition: Partition;
};
const getPrefix = (mountpoint: string) => {
return mountpoint.split('/')[1] ? '/' + mountpoint.split('/')[1] : '/';
};
const getSuffix = (mountpoint: string) => {
const prefix = getPrefix(mountpoint);
return mountpoint.substring(prefix.length);
};
const Row = ({ partition }: RowPropTypes) => {
const [units, setUnits] = useState<Units>('MiB');
return (
<Tr>
<Td />
<Td className="pf-m-width-15">
<MountpointPrefix partition={partition} />
</Td>
<Td className="pf-m-width-15">
<MountpointSuffix partition={partition} />
</Td>
<Td className="pf-m-width-20">xfs</Td>
<Td className="pf-m-width-30">
<MinimumSize partition={partition} units={units} />
</Td>
<Td className="pf-m-width-30">
<SizeUnit units={units} setUnits={setUnits} />
</Td>
<Td className="pf-m-width-10">
<Button
variant="link"
icon={<MinusCircleIcon />}
onClick={() => {}}
data-testid="remove-mount-point"
isDisabled={true}
/>
</Td>
</Tr>
);
};
export const mountpointPrefixes = [
'/app',
'/boot',
'/data',
'/home',
'/opt',
'/srv',
'/tmp',
'/usr',
'/var',
];
type MountpointPrefixPropTypes = {
partition: Partition;
};
const MountpointPrefix = ({ partition }: MountpointPrefixPropTypes) => {
const dispatch = useAppDispatch();
const [isOpen, setIsOpen] = useState(false);
const prefix = getPrefix(partition.mountpoint);
const suffix = getSuffix(partition.mountpoint);
const onToggle = (isOpen: boolean) => {
setIsOpen(isOpen);
};
const onSelect = (event: React.MouseEvent, selection: string) => {
setIsOpen(false);
const mountpoint = selection + suffix;
dispatch(
changePartitionMountpoint({ id: partition.id, mountpoint: mountpoint })
);
};
return (
<Select
ouiaId="mount-point"
isOpen={isOpen}
onToggle={(_event, isOpen) => onToggle(isOpen)}
onSelect={onSelect}
selections={prefix}
>
{mountpointPrefixes.map((prefix, index) => {
return <SelectOption key={index} value={prefix} />;
})}
</Select>
);
};
type MountpointSuffixPropTypes = {
partition: Partition;
};
const MountpointSuffix = ({ partition }: MountpointSuffixPropTypes) => {
const dispatch = useAppDispatch();
const prefix = getPrefix(partition.mountpoint);
const suffix = getSuffix(partition.mountpoint);
return (
<TextInput
value={suffix}
type="text"
onChange={(event: React.FormEvent, suffix) => {
const mountpoint = prefix + suffix;
dispatch(
changePartitionMountpoint({
id: partition.id,
mountpoint: mountpoint,
})
);
}}
aria-label="text input example"
/>
);
};
type MinimumSizePropTypes = {
partition: Partition;
units: Units;
};
type Units = 'KiB' | 'MiB' | 'GiB';
const getConversionFactor = (units: Units) => {
switch (units) {
case 'KiB':
return UNIT_KIB;
case 'MiB':
return UNIT_MIB;
case 'GiB':
return UNIT_GIB;
}
};
const MinimumSize = ({ partition, units }: MinimumSizePropTypes) => {
const conversionFactor = getConversionFactor(units);
const convertToDisplayUnits = (minSize: string) => {
return (parseInt(minSize) * conversionFactor).toString();
};
const convertToBytes = (minSize: string) => {
return (parseInt(minSize) / conversionFactor).toString();
};
const dispatch = useAppDispatch();
return (
<ValidatedTextInput
ariaLabel="minimum partition size"
helperText=""
validator={() => true}
value={convertToDisplayUnits(partition.min_size)}
type="text"
onChange={(event, minSize) => {
dispatch(
changePartitionMinSize({
id: partition.id,
min_size: convertToBytes(minSize),
})
);
}}
/>
);
};
type SizeUnitPropTypes = {
units: Units;
setUnits: React.Dispatch<React.SetStateAction<Units>>;
};
const SizeUnit = ({ units, setUnits }: SizeUnitPropTypes) => {
const [isOpen, setIsOpen] = useState(false);
const onToggle = (isOpen: boolean) => {
setIsOpen(isOpen);
};
const onSelect = (event: React.MouseEvent, selection: Units) => {
setUnits(selection);
setIsOpen(false);
};
return (
<Select
ouiaId="mount-point"
isOpen={isOpen}
onToggle={(_event, isOpen) => onToggle(isOpen)}
onSelect={onSelect}
selections={units}
>
<SelectOption value={'KiB'} />
<SelectOption value={'MiB'} />
<SelectOption value={'GiB'} />
</Select>
);
};
export default FileSystemConfiguration;

View file

@ -0,0 +1,49 @@
import React from 'react';
import { FormGroup, Label, Radio } from '@patternfly/react-core';
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
import {
changeFileSystemPartitionMode,
selectFileSystemPartitionMode,
} from '../../../../store/wizardSlice';
const FileSystemPartition = () => {
const dispatch = useAppDispatch();
const fileSystemPartition = useAppSelector((state) =>
selectFileSystemPartitionMode(state)
);
return (
<FormGroup>
<Radio
id="automatic file system config radio"
label={
<>
<Label isCompact color="blue">
Recommended
</Label>{' '}
Use automatic partitioning
</>
}
name="sc-radio-automatic"
description="Automatically partition your image to what is best, depending on the target environment(s)"
isChecked={fileSystemPartition === 'automatic'}
onChange={() => {
dispatch(changeFileSystemPartitionMode('automatic'));
}}
/>
<Radio
id="manual file system config radio"
label="Manually configure partitions"
name="fsc-radio-manual"
description="Manually configure the file system of your image by adding, removing, and editing partitions"
isChecked={fileSystemPartition === 'manual'}
onChange={() => {
dispatch(changeFileSystemPartitionMode('manual'));
}}
/>
</FormGroup>
);
};
export default FileSystemPartition;

View file

@ -0,0 +1,39 @@
import React from 'react';
import { Text, Form, Title } from '@patternfly/react-core';
import FileSystemAutomaticPartition from './FileSystemAutomaticPartitionInformation';
import FileSystemConfiguration from './FileSystemConfiguration';
import FileSystemPartition from './FileSystemPartition';
import { useAppSelector } from '../../../../store/hooks';
import { selectFileSystemPartitionMode } from '../../../../store/wizardSlice';
export type FileSystemPartitionMode = 'automatic' | 'manual';
const FileSystemStep = () => {
const fileSystemPartitionMode = useAppSelector((state) =>
selectFileSystemPartitionMode(state)
);
return (
<Form>
<Title headingLevel="h2">File system configuration</Title>
<Text>Define the partitioning of the image</Text>
{fileSystemPartitionMode === 'automatic' ? (
<>
<FileSystemPartition />
<FileSystemAutomaticPartition />
</>
) : fileSystemPartitionMode === 'manual' ? (
<>
<FileSystemPartition />
<FileSystemConfiguration />
</>
) : (
fileSystemPartitionMode === 'oscap' && <FileSystemConfiguration />
)}
</Form>
);
};
export default FileSystemStep;

View file

@ -64,6 +64,7 @@ export const FSReviewTable = () => {
<Th>Minimum size</Th>
</Tr>
</Thead>
<Tbody data-testid="file-system-configuration-tbody-review"></Tbody>
</Table>
</PanelMain>
</Panel>

View file

@ -19,6 +19,7 @@ import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome'
import ActivationKeyInformation from './../Registration/ActivationKeyInformation';
import { PackagesTable, RepositoriesTable } from './ReviewStepTables';
import { FSReviewTable } from './ReviewStepTables';
import {
RELEASES,
@ -50,6 +51,7 @@ import {
selectGcpShareMethod,
selectPackages,
selectRegistrationType,
selectFileSystemPartitionMode,
} from '../../../../store/wizardSlice';
import { toMonthAndYear } from '../../../../Utilities/time';
import { MajorReleasesLifecyclesChart } from '../ImageOutput/ReleaseLifecycle';
@ -103,13 +105,85 @@ export const ImageOutputList = () => {
);
};
export const FSCList = () => {
const fileSystemPartitionMode = useAppSelector((state) =>
selectFileSystemPartitionMode(state)
);
return (
<TextContent>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Configuration type
</TextListItem>
<TextListItem
component={TextListItemVariants.dd}
data-testid="partitioning-auto-manual"
>
{fileSystemPartitionMode === 'manual' ? 'Manual' : 'Automatic'}
{fileSystemPartitionMode === 'manual' && (
<>
{' '}
<Popover
position="bottom"
headerContent="Partitions"
hasAutoWidth
minWidth="30rem"
bodyContent={<FSReviewTable />}
>
<Button
data-testid="file-system-configuration-popover"
variant="link"
aria-label="File system configuration info"
aria-describedby="file-system-configuration-info"
className="pf-u-pt-0 pf-u-pb-0"
>
View partitions
</Button>
</Popover>
</>
)}
</TextListItem>
{fileSystemPartitionMode === 'manual' && (
<>
<TextListItem component={TextListItemVariants.dt}>
Image size (minimum)
<Popover
hasAutoWidth
bodyContent={
<TextContent>
<Text>
Image Builder may extend this size based on requirements,
selected packages, and configurations.
</Text>
</TextContent>
}
>
<Button
variant="plain"
aria-label="File system configuration info"
aria-describedby="file-system-configuration-info"
className="pf-c-form__group-label-help"
>
<HelpIcon />
</Button>
</Popover>
</TextListItem>
<MinSize />
</>
)}
</TextList>
<br />
</TextContent>
);
};
export const MinSize = () => {
return <TextListItem component={TextListItemVariants.dd} />;
};
export const TargetEnvAWSList = () => {
const { isSuccess } = useGetSourceListQuery({
provider: 'aws',

View file

@ -108,6 +108,11 @@ export const mapRequestToState = (request: BlueprintResponse): wizardState => {
enabled: [],
},
},
fileSystem: {
mode: 'automatic',
partitions: [],
},
architecture: request.image_requests[0].architecture,
distribution: request.distribution,
imageTypes: request.image_requests.map((image) => image.image_type),

View file

@ -10,6 +10,8 @@ import {
} from './imageBuilderApi';
import { ActivationKeys } from './rhsmApi';
import { FileSystemPartitionMode } from '../Components/CreateImageWizardV2/steps/FileSystem';
import { Partition } from '../Components/CreateImageWizardV2/steps/FileSystem/FileSystemConfiguration';
import { IBPackageWithRepositoryInfo } from '../Components/CreateImageWizardV2/steps/Packages/Packages';
import { AwsShareMethod } from '../Components/CreateImageWizardV2/steps/TargetEnvironment/Aws';
import { AzureShareMethod } from '../Components/CreateImageWizardV2/steps/TargetEnvironment/Azure';
@ -68,7 +70,10 @@ export type wizardState = {
enabled: string[] | undefined;
};
};
fileSystem: {
mode: FileSystemPartitionMode;
partitions: Partition[];
};
repositories: {
customRepositories: CustomRepository[];
payloadRepositories: Repository[];
@ -119,6 +124,14 @@ const initialState: wizardState = {
enabled: [],
},
},
fileSystem: {
mode: 'automatic',
partitions: [
{ id: '1', mountpoint: '/', min_size: '500' },
{ id: '2', mountpoint: '/home', min_size: '500' },
{ id: '3', mountpoint: '/home/var', min_size: '500' },
],
},
repositories: {
customRepositories: [],
payloadRepositories: [],
@ -218,6 +231,14 @@ export const selectEnabledServices = (state: RootState) => {
return state.wizard.openScap.services.enabled;
};
export const selectFileSystemPartitionMode = (state: RootState) => {
return state.wizard.fileSystem.mode;
};
export const selectPartitions = (state: RootState) => {
return state.wizard.fileSystem.partitions;
};
export const selectCustomRepositories = (state: RootState) => {
return state.wizard.repositories.customRepositories;
};
@ -353,6 +374,36 @@ export const wizardSlice = createSlice({
) => {
state.openScap.services.enabled = action.payload;
},
changeFileSystemPartitionMode: (
state,
action: PayloadAction<FileSystemPartitionMode>
) => {
state.fileSystem.mode = action.payload;
},
changePartitionMountpoint: (
state,
action: PayloadAction<{ id: string; mountpoint: string }>
) => {
const { id, mountpoint } = action.payload;
const partitionIndex = state.fileSystem.partitions.findIndex(
(partition) => partition.id === id
);
if (partitionIndex !== -1) {
state.fileSystem.partitions[partitionIndex].mountpoint = mountpoint;
}
},
changePartitionMinSize: (
state,
action: PayloadAction<{ id: string; min_size: string }>
) => {
const { id, min_size } = action.payload;
const partitionIndex = state.fileSystem.partitions.findIndex(
(partition) => partition.id === id
);
if (partitionIndex !== -1) {
state.fileSystem.partitions[partitionIndex].min_size = min_size;
}
},
changeCustomRepositories: (
state,
action: PayloadAction<CustomRepository[]>
@ -409,6 +460,9 @@ export const {
changeKernel,
changeDisabledServices,
changeEnabledServices,
changeFileSystemPartitionMode,
changePartitionMountpoint,
changePartitionMinSize,
changeCustomRepositories,
changePayloadRepositories,
addPackage,

View file

@ -144,7 +144,7 @@ describe('Step Packages', () => {
// skip Repositories
await clickNext();
// skip fsc
//await clickNext();
await clickNext();
};
test('clicking Next loads Image name', async () => {
@ -365,7 +365,10 @@ describe('Step Custom repositories', () => {
// skip OpenSCAP
await clickNext();
// skip fsc
//await clickNext();
await clickNext();
// // skip packages
// await clickNext();
};
test('selected repositories stored in and retrieved from form state', async () => {

View file

@ -121,7 +121,7 @@ describe('Create Image Wizard', () => {
await screen.findByRole('button', { name: 'Image output' });
await screen.findByRole('button', { name: 'Register' });
// await screen.findByRole('button', { name: 'File system configuration' });
await screen.findByRole('button', { name: 'File system configuration' });
await screen.findByRole('button', { name: 'Content' });
await screen.findByRole('button', { name: 'Custom repositories' });
await screen.findByRole('button', { name: 'Additional packages' });
@ -430,6 +430,8 @@ describe('Step Upload to AWS', () => {
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await enterBlueprintName();
await clickNext();
@ -556,18 +558,21 @@ describe('Step Registration', () => {
});
};
// test('clicking Next loads file system configuration', async () => {
// await setUp();
test('clicking Next loads file system configuration', async () => {
await setUp();
// const registerLaterRadio = await screen.findByTestId('registration-radio-later');
// await user.click(registerLaterRadio);
const registerLaterRadio = await screen.findByTestId(
'registration-radio-later'
);
await user.click(registerLaterRadio);
// await clickNext();
await clickNext();
await clickNext();
// await screen.findByRole('heading', {
// name: 'File system configuration',
// });
// });
await screen.findByRole('heading', {
name: 'File system configuration',
});
});
test('clicking Back loads Upload to AWS', async () => {
await setUp();
@ -619,6 +624,7 @@ describe('Step Registration', () => {
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await enterBlueprintName();
await clickNext();
const review = await screen.findByTestId('review-registration');
@ -663,6 +669,8 @@ describe('Step Registration', () => {
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await enterBlueprintName();
await clickNext();
const review = await screen.findByTestId('review-registration');
@ -709,6 +717,7 @@ describe('Step Registration', () => {
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await enterBlueprintName();
await clickNext();
const review = await screen.findByTestId('review-registration');
@ -737,6 +746,8 @@ describe('Step Registration', () => {
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await enterBlueprintName();
await clickNext();
await screen.findByText('Register the system later');
@ -762,86 +773,77 @@ describe('Step Registration', () => {
});
});
// describe('Step File system configuration', () => {
// const user = userEvent.setup();
// const setUp = async () => {
// ({ router } = await renderCustomRoutesWithReduxRouter(
// 'imagewizard',
// {},
// routes
// ));
// // select aws as upload destination
// await waitFor(
// async () => await user.click(await screen.findByTestId('upload-aws'))
// );
// await clickNext();
// // aws step
// await switchToAWSManual();
// await user.type(
// await screen.findByTestId('aws-account-id'),
// '012345678901'
// );
// await clickNext();
// // skip registration
// await screen.findByRole('textbox', {
// name: 'Select activation key',
// });
// const registerLaterRadio = await screen.findByTestId('registration-radio-later');
// await user.click(registerLaterRadio);
// await clickNext();
// };
// test('Error validation occurs upon clicking next button', async () => {
// await setUp();
// const manuallyConfigurePartitions = await screen.findByText(
// /manually configure partitions/i
// );
// await user.click(manuallyConfigurePartitions);
// const addPartition = await screen.findByTestId('file-system-add-partition');
// // Create duplicate partitions
// await user.click(addPartition);
// await user.click(addPartition);
// expect(await getNextButton()).toBeEnabled();
// // Clicking next causes errors to appear
// await clickNext();
// const mountPointWarning = await screen.findByRole('heading', {
// name: /danger alert: duplicate mount points: all mount points must be unique\. remove the duplicate or choose a new mount point\./i,
// hidden: true,
// });
// const mountPointAlerts = screen.getAllByRole('heading', {
// name: /danger alert: duplicate mount point\./i,
// });
// const tbody = await screen.findByTestId('file-system-configuration-tbody');
// const rows = within(tbody).getAllByRole('row');
// expect(rows).toHaveLength(3);
// // Change mountpoint of final row to /var, resolving errors
// const mountPointOptions = within(rows[2]).getAllByRole('button', {
// name: 'Options menu',
// })[0];
// await user.click(mountPointOptions);
// const varButton = await within(rows[2]).findByRole('option', {
// name: '/var',
// });
// await user.click(varButton);
// await waitFor(() => expect(mountPointWarning).not.toBeInTheDocument());
// await waitFor(() => expect(mountPointAlerts[0]).not.toBeInTheDocument());
// await waitFor(() => expect(mountPointAlerts[1]).not.toBeInTheDocument());
// expect(await getNextButton()).toBeEnabled();
// });
// });
describe('Step File system configuration', () => {
// const user = userEvent.setup();
// const setUp = async () => {
// ({ router } = await renderCustomRoutesWithReduxRouter(
// 'imagewizard',
// {},
// routes
// ));
// // select aws as upload destination
// await waitFor(
// async () => await user.click(await screen.findByTestId('upload-aws'))
// );
// await clickNext();
// // aws step
// await switchToAWSManual();
// await user.type(
// screen.getByRole('textbox', {
// name: /aws account id/i,
// }),
// '012345678901'
// );
// await clickNext();
// // skip registration
// await screen.findByRole('textbox', {
// name: 'Select activation key',
// });
// const registerLaterRadio = await screen.findByTestId(
// 'registration-radio-later'
// );
// await user.click(registerLaterRadio);
// await clickNext();
// await clickNext();
// };
//test('Error validation occurs upon clicking next button', async () => {
// await setUp();
// const manuallyConfigurePartitions = await screen.findByText(
// /manually configure partitions/i
// );
// await user.click(manuallyConfigurePartitions);
// const addPartition = await screen.findByTestId('file-system-add-partition');
// // Create duplicate partitions
// await user.click(addPartition);
// await user.click(addPartition);
// expect(await getNextButton()).toBeDisabled();
// // Clicking next causes errors to appear
// await clickNext();
// const mountPointWarning = await screen.findByRole('heading', {
// name: /danger alert: duplicate mount points: all mount points must be unique\. remove the duplicate or choose a new mount point\./i,
// hidden: true,
// });
// const mountPointAlerts = screen.getAllByRole('heading', {
// name: /danger alert: duplicate mount point\./i,
// });
// const tbody = await screen.findByTestId('file-system-configuration-tbody');
// const rows = within(tbody).getAllByRole('row');
// expect(rows).toHaveLength(3);
// //Change mountpoint of final row to /var, resolving errors
// const mountPointOptions = within(rows[2]).getAllByRole('button', {
// name: 'Options menu',
// })[0];
// await user.click(mountPointOptions);
// const varButton = await within(rows[2]).findByRole('option', {
// name: '/var',
// });
// await user.click(varButton);
// // await waitFor(() => expect(mountPointWarning).not.toBeInTheDocument());
// // await waitFor(() => expect(mountPointAlerts[0]).not.toBeInTheDocument());
// // await waitFor(() => expect(mountPointAlerts[1]).not.toBeInTheDocument());
// expect(await getNextButton()).toBeEnabled();
//});
});
describe('Step Details', () => {
const user = userEvent.setup();
@ -883,7 +885,7 @@ describe('Step Details', () => {
// skip packages
await clickNext();
// skip fsc
//await clickNext();
await clickNext();
};
test('image name invalid for more than 63 chars', async () => {
@ -962,6 +964,7 @@ describe('Step Review', () => {
await clickNext();
// skip packages
await clickNext();
await clickNext();
// skip Details
const blueprintName = await screen.findByRole('textbox', {
name: /blueprint name/i,
@ -1023,7 +1026,7 @@ describe('Step Review', () => {
await clickNext();
// skip repositories
await clickNext();
// skip Details
await clickNext();
const blueprintName = await screen.findByRole('textbox', {
name: /blueprint name/i,
});
@ -1061,7 +1064,9 @@ describe('Step Review', () => {
});
const contentExpandable = await screen.findByTestId('content-expandable');
// const fscExpandable = screen.getByText(/file system configuration/i);
const fscExpandable = screen.getByTestId(
'file-system-configuration-expandable'
);
await user.click(targetExpandable);
await screen.findByText('AWS');
@ -1071,19 +1076,17 @@ describe('Step Review', () => {
await within(contentExpandable).findByText('Custom repositories');
await within(contentExpandable).findByText('Additional packages');
// await user.click(fscExpandable);
// await screen.findByText('Configuration type');
await user.click(fscExpandable);
await screen.findByText('Configuration type');
});
test('has no Registration expandable for centos', async () => {
await setUpCentOS();
const targetExpandable = screen.getByText(/target environments/i);
const contentExpandable = await screen.findByTestId('content-expandable');
//const fscExpandable = await screen.findByTestId(
// 'file-system-configuration-expandable'
// );
// });
const fscExpandable = await screen.findByTestId(
'file-system-configuration-expandable'
);
expect(
screen.queryByTestId('registration-expandable')
).not.toBeInTheDocument();
@ -1093,12 +1096,11 @@ describe('Step Review', () => {
await user.click(contentExpandable);
await within(contentExpandable).findByText('Custom repositories');
await within(contentExpandable).findByText('Additional packages');
await user.click(fscExpandable);
await screen.findByText('Configuration type');
});
});
// await user.click(fscExpandable);
// await screen.findByText('Configuration type');
// });
// });
describe('Keyboard accessibility', () => {
const user = userEvent.setup();
@ -1199,8 +1201,8 @@ describe('Keyboard accessibility', () => {
// TODO: Focus on textbox on OpenSCAP step
await clickNext();
// File system configuration
// await clickNext();
//File system configuration
await clickNext();
// TODO: Focus on textbox on Custom Repos step
await clickNext();

View file

@ -36,6 +36,7 @@ const goToDetailsStep = async () => {
await clickNext();
await clickNext();
await clickNext();
await clickNext();
};
const enterBlueprintDescription = async () => {

View file

@ -39,6 +39,7 @@ const goToPackagesStep = async () => {
await clickNext(); // Registration
await clickRegisterLater();
await clickNext(); // OpenSCAP
await clickNext(); // File System
await clickNext(); // Custom repositories
await clickNext(); // Additional packages
};

View file

@ -69,6 +69,7 @@ const goToReviewStep = async () => {
await clickNext();
await clickNext();
await clickNext();
await clickNext();
await enterBlueprintName();
await clickNext();
};

View file

@ -43,11 +43,13 @@ const goToRepositoriesStep = async () => {
await clickNext(); // Registration
await clickRegisterLater();
await clickNext(); // OpenSCAP
await clickNext(); // File System
await clickNext(); // Custom repositories
};
const goToReviewStep = async () => {
await clickNext(); // Additional packages
await clickNext();
await clickNext(); // Details
await enterBlueprintName();
await clickNext(); // Review

View file

@ -119,6 +119,7 @@ const goToReviewStep = async () => {
await goToRegistrationStep(); // Register
await clickRegisterLater();
await clickNext(); // OpenSCAP
await clickNext(); // File system customization
await clickNext(); // Custom repositories
await clickNext(); // Additional packages
await clickNext(); // Details