V2Wizard: Clean up for a fresh start

To make code reviews easier, these files are being removed for now. They
will be added back over time in a way that results in clearer diffs.
This commit is contained in:
lucasgarfield 2024-01-05 17:19:49 +01:00 committed by Lucas Garfield
parent 247c436b90
commit e1f66dd444
11 changed files with 0 additions and 1419 deletions

View file

@ -1,70 +0,0 @@
.pf-v5-c-wizard__nav-list {
padding-right: 0px;
}
.pf-v5-c-wizard__nav {
overflow-y: unset;
}
.pf-c-popover[data-popper-reference-hidden="true"] {
font-weight: initial;
visibility: initial;
pointer-events: initial;
}
.pf-v5-c-dual-list-selector {
--pf-v5-c-dual-list-selector__menu--MinHeight: 18rem;
--pf-v5-c-dual-list-selector--GridTemplateColumns--pane--MinMax--max: 100vw;
}
.pf-c-form {
--pf-c-form--GridGap: var(--pf-global--spacer--md);
}
.pf-c-form__group-label {
--pf-c-form__group-label--PaddingBottom: var(--pf-global--spacer--xs);
}
.tiles {
display: flex;
}
.tile {
flex: 1 0 0px;
max-width: 250px;
}
.pf-c-tile:focus {
--pf-c-tile__title--Color: var(--pf-c-tile__title--Color);
--pf-c-tile__icon--Color: var(---pf-global--Color--100);
--pf-c-tile--before--BorderWidth: var(--pf-global--BorderWidth--sm);
--pf-c-tile--before--BorderColor: var(--pf-global--BorderColor--100);
}
.pf-c-tile.pf-m-selected:focus {
--pf-c-tile__title--Color: var(--pf-c-tile--focus__title--Color);
--pf-c-tile__icon--Color: var(--pf-c-tile--focus__icon--Color);
}
.provider-icon {
width: 1em;
height: 1em;
}
.pf-u-min-width {
--pf-u-min-width--MinWidth: 18ch;
}
.pf-u-max-width {
--pf-u-max-width--MaxWidth: 26rem;
}
ul.pf-m-plain {
list-style: none;
padding-left: 0;
margin-left: 0;
}
button.pf-v5-c-menu-toggle {
max-width: 100%;
}

View file

@ -1,152 +0,0 @@
import React, { useState } from 'react';
import {
Button,
Wizard,
WizardFooterWrapper,
WizardStep,
useWizardContext,
} from '@patternfly/react-core';
import { useNavigate } from 'react-router-dom';
import {
EnvironmentStateType,
filterEnvironment,
hasUserSelectedAtLeastOneEnv,
useGetAllowedTargets,
} from './steps/ImageOutput/Environment';
import ImageOutputStep from './steps/ImageOutput/ImageOutput';
import ReviewStep from './steps/Review/ReviewStep';
import { RHEL_9, X86_64 } from '../../constants';
import './CreateImageWizard.scss';
import { ArchitectureItem, Distributions } from '../../store/imageBuilderApi';
import { resolveRelPath } from '../../Utilities/path';
import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader';
/**
* @return true if the array in prevAllowedTargets is equivalent to the array
* allowedTargets, false otherwise
*/
const isIdenticalToPrev = (
prevAllowedTargets: string[],
allowedTargets: string[]
) => {
let identicalToPrev = true;
if (allowedTargets.length === prevAllowedTargets.length) {
allowedTargets.forEach((elem) => {
if (!prevAllowedTargets.includes(elem)) {
identicalToPrev = false;
}
});
} else {
identicalToPrev = false;
}
return identicalToPrev;
};
type CustomWizardFooterPropType = {
isNextDisabled: boolean;
};
/**
* The custom wizard footer is only switching the order of the buttons compared
* to the default wizard footer from the PF5 library.
*/
const CustomWizardFooter = ({ isNextDisabled }: CustomWizardFooterPropType) => {
const { goToNextStep, goToPrevStep, close } = useWizardContext();
return (
<WizardFooterWrapper>
<Button
variant="primary"
onClick={goToNextStep}
isDisabled={isNextDisabled}
>
Next
</Button>
<Button variant="secondary" onClick={goToPrevStep}>
Back
</Button>
<Button variant="link" onClick={close}>
Cancel
</Button>
</WizardFooterWrapper>
);
};
const CreateImageWizard = () => {
const navigate = useNavigate();
// Image output step states
const [release, setRelease] = useState<Distributions>(RHEL_9);
const [arch, setArch] = useState<ArchitectureItem['arch']>(X86_64);
const {
data: allowedTargets,
isFetching,
isSuccess,
isError,
} = useGetAllowedTargets({
architecture: arch,
release: release,
});
const [environment, setEnvironment] = useState<EnvironmentStateType>(
filterEnvironment(
{
aws: { selected: false, authorized: false },
azure: { selected: false, authorized: false },
gcp: { selected: false, authorized: false },
oci: { selected: false, authorized: false },
'vsphere-ova': { selected: false, authorized: false },
vsphere: { selected: false, authorized: false },
'guest-image': { selected: false, authorized: false },
'image-installer': { selected: false, authorized: false },
wsl: { selected: false, authorized: false },
},
allowedTargets
)
);
// Update of the environment when the architecture and release are changed.
// This pattern prevents the usage of a useEffect See https://react.dev/learn/you-might-not-need-an-effect#adjusting-some-state-when-a-prop-changes
const [prevAllowedTargets, setPrevAllowedTargets] = useState(allowedTargets);
if (!isIdenticalToPrev(prevAllowedTargets, allowedTargets)) {
setPrevAllowedTargets(allowedTargets);
setEnvironment(filterEnvironment(environment, allowedTargets));
}
return (
<>
<ImageBuilderHeader />
<section className="pf-l-page__main-section pf-c-page__main-section">
<Wizard onClose={() => navigate(resolveRelPath(''))} isVisitRequired>
<WizardStep
name="Image output"
id="step-image-output"
footer={
<CustomWizardFooter
isNextDisabled={!hasUserSelectedAtLeastOneEnv(environment)}
/>
}
>
<ImageOutputStep
release={release}
setRelease={setRelease}
arch={arch}
setArch={setArch}
environment={environment}
setEnvironment={setEnvironment}
isFetching={isFetching}
isError={isError}
isSuccess={isSuccess}
/>
</WizardStep>
<WizardStep
name="Review"
id="step-review"
footer={<CustomWizardFooter isNextDisabled={true} />}
>
<ReviewStep release={release} arch={arch} />
</WizardStep>
</Wizard>
</section>
</>
);
};
export default CreateImageWizard;

View file

@ -1,45 +0,0 @@
import React, { Dispatch, FormEvent, SetStateAction } from 'react';
import {
FormGroup,
FormSelect,
FormSelectOption,
} from '@patternfly/react-core';
import { ARCHS } from '../../../../constants';
import { ArchitectureItem } from '../../../../store/imageBuilderApi';
type ArchSelectType = {
setArch: Dispatch<SetStateAction<ArchitectureItem['arch']>>;
arch: ArchitectureItem['arch'];
};
/**
* Allows the user to pick the architecture to build
*/
const ArchSelect = ({ setArch, arch }: ArchSelectType) => {
const onChange = (_event: FormEvent<HTMLSelectElement>, value: string) => {
setArch(value);
};
return (
<FormGroup
isRequired={true}
label="Architecture"
data-testid="architecture-select"
>
<FormSelect
value={arch}
onChange={onChange}
aria-label="Architecture"
ouiaId="arch_select"
>
{ARCHS.map((arch, index) => (
<FormSelectOption key={index} value={arch} label={arch} />
))}
</FormSelect>
</FormGroup>
);
};
export default ArchSelect;

View file

@ -1,44 +0,0 @@
import React from 'react';
import { Alert, Button } from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
const DeveloperProgramButton = () => {
return (
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
isInline
href={'https://developers.redhat.com/about'}
>
Red Hat Developer Program
</Button>
);
};
const CentOSAcknowledgement = () => {
return (
<Alert
variant="info"
isPlain
isInline
title={
<>
CentOS Stream builds are intended for the development of future
versions of RHEL and are not supported for production workloads or
other use cases.
</>
}
>
<p>
Join the <DeveloperProgramButton /> to learn about paid and no-cost RHEL
subscription options.
</p>
</Alert>
);
};
export default CentOSAcknowledgement;

View file

@ -1,406 +0,0 @@
import React, { Dispatch, SetStateAction } from 'react';
import {
Checkbox,
FormGroup,
Popover,
Radio,
Text,
TextContent,
TextVariants,
Tile,
} from '@patternfly/react-core';
import { HelpIcon } from '@patternfly/react-icons';
import {
useGetArchitecturesQuery,
Distributions,
ArchitectureItem,
} from '../../../../store/imageBuilderApi';
import { provisioningApi } from '../../../../store/provisioningApi';
import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
type useGetAllowedTargetsPropType = {
architecture: ArchitectureItem['arch'];
release: Distributions;
};
/**
* Contacts the backend to get a list of valid targets based on the user
* requirements (release & architecture)
*
* @return an array of strings which contains the names of the authorized
* targets. Alongside the array, a couple of flags indicate the status of the
* request. isFetching stays true while the data are in transit. isError is set
* to true if anything wrong happened. isSuccess is set to true otherwise.
*
* @param architecture the selected arch (x86_64 or aarch64)
* @param release the selected release (see RELEASES in constants)
*/
export const useGetAllowedTargets = ({
architecture,
release,
}: useGetAllowedTargetsPropType) => {
const { data, isFetching, isSuccess, isError } = useGetArchitecturesQuery({
distribution: release,
});
let image_types: string[] = [];
if (isSuccess && data) {
data.forEach((elem) => {
if (elem.arch === architecture) {
image_types = elem.image_types;
}
});
}
return {
data: image_types,
isFetching: isFetching,
isSuccess: isSuccess,
isError: isError,
};
};
/**
* Type to represent the state of a target.
* A target can be selected and/or authorized. An authorized target means the
* target can be displayed to the user, selected means the user has selected
* the target.
*/
type TargetType = {
selected: boolean;
authorized: boolean;
};
/**
* Defines all the possible targets a user can build.
*/
export type EnvironmentStateType = {
aws: TargetType;
azure: TargetType;
gcp: TargetType;
oci: TargetType;
'vsphere-ova': TargetType;
vsphere: TargetType;
'guest-image': TargetType;
'image-installer': TargetType;
wsl: TargetType;
};
/**
* Takes an environment, a list of allowedTargets and updates the authorized
* status of each targets in the environment accordingly.
*
* @param environment the environment to update
* @param allowedTargets the list of targets authorized to get built
* @return an updated environment
*/
export const filterEnvironment = (
environment: EnvironmentStateType,
allowedTargets: string[]
) => {
const newEnv = { ...environment };
Object.keys(environment).forEach((target) => {
newEnv[target as keyof EnvironmentStateType].authorized =
allowedTargets.includes(target);
});
return newEnv;
};
/**
* @return true if at least one target has both its flags selected and
* authorized set to true
* @param env the environment to scan
*/
export const hasUserSelectedAtLeastOneEnv = (
env: EnvironmentStateType
): boolean => {
let atLeastOne = false;
Object.values(env).forEach(({ selected, authorized }) => {
atLeastOne = atLeastOne || (selected && authorized);
});
return atLeastOne;
};
type EnvironmentPropType = {
environment: EnvironmentStateType;
setEnvironment: Dispatch<SetStateAction<EnvironmentStateType>>;
};
/**
* Displays a component that allows the user to pick the target they want
* to build on.
*/
const Environment = ({ setEnvironment, environment }: EnvironmentPropType) => {
const prefetchSources = provisioningApi.usePrefetch('getSourceList');
const { isBeta } = useGetEnvironment();
const handleSetEnvironment = (env: string, checked: boolean) =>
setEnvironment((prevEnv) => {
const newEnv: EnvironmentStateType = {
...prevEnv,
};
newEnv[env as keyof EnvironmentStateType].selected = checked;
return newEnv;
});
// each item the user can select is depending on what's compatible with the
// architecture and the distribution they previously selected. That's why
// every sub parts are conditional to the `authorized` status of its
// corresponding key in the state.
return (
<FormGroup
isRequired={true}
label="Select target environments"
data-testid="target-select"
>
<FormGroup
label={<Text component={TextVariants.small}>Public cloud</Text>}
data-testid="target-public"
>
<div className="tiles">
{environment['aws'].authorized && (
<Tile
className="tile pf-u-mr-sm"
data-testid="upload-aws"
title="Amazon Web Services"
icon={
<img
className="provider-icon"
src={'/apps/frontend-assets/partners-icons/aws.svg'}
alt="Amazon Web Services logo"
/>
}
onClick={() =>
handleSetEnvironment('aws', !environment.aws.selected)
}
onMouseEnter={() => prefetchSources({ provider: 'aws' })}
isSelected={environment.aws.selected}
isStacked
isDisplayLarge
/>
)}
{environment['gcp'].authorized && (
<Tile
className="tile pf-u-mr-sm"
data-testid="upload-google"
title="Google Cloud Platform"
icon={
<img
className="provider-icon"
src={
'/apps/frontend-assets/partners-icons/google-cloud-short.svg'
}
alt="Google Cloud Platform logo"
/>
}
onClick={() =>
handleSetEnvironment('gcp', !environment.gcp.selected)
}
isSelected={environment.gcp.selected}
onMouseEnter={() => prefetchSources({ provider: 'gcp' })}
isStacked
isDisplayLarge
/>
)}
{environment['azure'].authorized && (
<Tile
className="tile pf-u-mr-sm"
data-testid="upload-azure"
title="Microsoft Azure"
icon={
<img
className="provider-icon"
src={
'/apps/frontend-assets/partners-icons/microsoft-azure-short.svg'
}
alt="Microsoft Azure logo"
/>
}
onClick={() =>
handleSetEnvironment('azure', !environment.azure.selected)
}
onMouseEnter={() => prefetchSources({ provider: 'azure' })}
isSelected={environment.azure.selected}
isStacked
isDisplayLarge
/>
)}
{environment.oci.authorized && isBeta() && (
<Tile
className="tile pf-u-mr-sm"
data-testid="upload-oci"
title="Oracle Cloud Infrastructure"
icon={
<img
className="provider-icon"
src={'/apps/frontend-assets/partners-icons/oracle-short.svg'}
alt="Oracle Cloud Infrastructure logo"
/>
}
onClick={() =>
handleSetEnvironment('oci', !environment.oci.selected)
}
isSelected={environment.oci.selected}
isStacked
isDisplayLarge
/>
)}
</div>
</FormGroup>
{environment['vsphere'].authorized && (
<FormGroup
label={<Text component={TextVariants.small}>Private cloud</Text>}
className="pf-u-mt-sm"
data-testid="target-private"
>
<Checkbox
label="VMware vSphere"
isChecked={
environment.vsphere.selected ||
environment['vsphere-ova'].selected
}
onChange={(_event, checked) => {
handleSetEnvironment('vsphere-ova', checked);
handleSetEnvironment('vsphere', false);
}}
aria-label="VMware checkbox"
id="checkbox-vmware"
name="VMware"
data-testid="checkbox-vmware"
/>
</FormGroup>
)}
{environment['vsphere'].authorized && (
<FormGroup
className="pf-u-mt-sm pf-u-mb-sm pf-u-ml-xl"
data-testid="target-private-vsphere-radio"
>
{environment['vsphere-ova'].authorized && (
<Radio
name="vsphere-radio"
aria-label="VMware vSphere radio button OVA"
id="vsphere-radio-ova"
label={
<>
Open virtualization format (.ova)
<Popover
maxWidth="30rem"
position="right"
bodyContent={
<TextContent>
<Text>
An OVA file is a virtual appliance used by
virtualization platforms such as VMware vSphere. It is
a package that contains files used to describe a
virtual machine, which includes a VMDK image, OVF
descriptor file and a manifest file.
</Text>
</TextContent>
}
>
<HelpIcon className="pf-u-ml-sm" />
</Popover>
</>
}
onChange={(_event, checked) => {
handleSetEnvironment('vsphere-ova', checked);
handleSetEnvironment('vsphere', !checked);
}}
isChecked={environment['vsphere-ova'].selected}
isDisabled={
!(
environment.vsphere.selected ||
environment['vsphere-ova'].selected
)
}
/>
)}
<Radio
className="pf-u-mt-sm"
name="vsphere-radio"
aria-label="VMware vSphere radio button VMDK"
id="vsphere-radio-vmdk"
label={
<>
Virtual disk (.vmdk)
<Popover
maxWidth="30rem"
position="right"
bodyContent={
<TextContent>
<Text>
A VMDK file is a virtual disk that stores the contents
of a virtual machine. This disk has to be imported into
vSphere using govc import.vmdk, use the OVA version when
using the vSphere UI.
</Text>
</TextContent>
}
>
<HelpIcon className="pf-u-ml-sm" />
</Popover>
</>
}
onChange={(_event, checked) => {
handleSetEnvironment('vsphere-ova', !checked);
handleSetEnvironment('vsphere', checked);
}}
isChecked={environment.vsphere.selected}
isDisabled={
!(
environment.vsphere.selected ||
environment['vsphere-ova'].selected
)
}
/>
</FormGroup>
)}
<FormGroup
label={<Text component={TextVariants.small}>Other</Text>}
data-testid="target-other"
>
{environment['guest-image'].authorized && (
<Checkbox
label="Virtualization - Guest image (.qcow2)"
isChecked={environment['guest-image'].selected}
onChange={(_event, checked) =>
handleSetEnvironment('guest-image', checked)
}
aria-label="Virtualization guest image checkbox"
id="checkbox-guest-image"
name="Virtualization guest image"
data-testid="checkbox-guest-image"
/>
)}
{environment['image-installer'].authorized && (
<Checkbox
label="Bare metal - Installer (.iso)"
isChecked={environment['image-installer'].selected}
onChange={(_event, checked) =>
handleSetEnvironment('image-installer', checked)
}
aria-label="Bare metal installer checkbox"
id="checkbox-image-installer"
name="Bare metal installer"
data-testid="checkbox-image-installer"
/>
)}
{environment['wsl'].authorized && isBeta() && (
<Checkbox
label="WSL - Windows Subsystem for Linux (.tar.gz)"
isChecked={environment['wsl'].selected}
onChange={(_event, checked) => handleSetEnvironment('wsl', checked)}
aria-label="windows subsystem for linux checkbox"
id="checkbox-wsl"
name="WSL"
data-testid="checkbox-wsl"
/>
)}
</FormGroup>
</FormGroup>
);
};
export default Environment;

View file

@ -1,90 +0,0 @@
import React, { Dispatch, SetStateAction } from 'react';
import {
Text,
Alert,
Bullseye,
Form,
Spinner,
Title,
} from '@patternfly/react-core';
import ArchSelect from './ArchSelect';
import CentOSAcknowledgement from './CentOSAcknowledgement';
import Environment, { EnvironmentStateType } from './Environment';
import ReleaseSelect from './ReleaseSelect';
import {
ArchitectureItem,
Distributions,
} from '../../../../store/imageBuilderApi';
import DocumentationButton from '../../../sharedComponents/DocumentationButton';
type ImageOutputPropTypes = {
release: Distributions;
setRelease: Dispatch<SetStateAction<Distributions>>;
arch: ArchitectureItem['arch'];
setArch: Dispatch<SetStateAction<ArchitectureItem['arch']>>;
environment: EnvironmentStateType;
setEnvironment: Dispatch<SetStateAction<EnvironmentStateType>>;
isFetching: boolean;
isError: boolean;
isSuccess: boolean;
};
/**
* Manages the form for the image output step by providing the user with a
* choice for:
* - a distribution
* - a release
* - a set of environments
*/
const ImageOutputStep = ({
release,
setRelease,
arch,
setArch,
setEnvironment,
environment,
isFetching,
isError,
isSuccess,
}: ImageOutputPropTypes) => {
return (
<Form>
<Title headingLevel="h2">Image output</Title>
<Text>
Image builder allows you to create a custom image and push it to target
environments.
<br />
<DocumentationButton />
</Text>
<ReleaseSelect setRelease={setRelease} release={release} />
<ArchSelect setArch={setArch} arch={arch} />
{release.match('centos-*') && <CentOSAcknowledgement />}
{isFetching && (
<Bullseye>
<Spinner size="lg" />
</Bullseye>
)}
{isError && (
<Alert
variant={'danger'}
isPlain
isInline
title={'Environments unavailable'}
>
API cannot be reached, try again later.
</Alert>
)}
{isSuccess && !isFetching && (
<Environment
setEnvironment={setEnvironment}
environment={environment}
/>
)}
</Form>
);
};
export default ImageOutputStep;

View file

@ -1,117 +0,0 @@
import React, {
Dispatch,
ReactElement,
SetStateAction,
useRef,
useState,
} from 'react';
import {
Select,
SelectOption,
SelectList,
MenuToggle,
FormGroup,
} from '@patternfly/react-core';
import { RELEASES } from '../../../../constants';
import { Distributions } from '../../../../store/imageBuilderApi';
import isRhel from '../../../../Utilities/isRhel';
type ReleaseSelectType = {
setRelease: Dispatch<SetStateAction<Distributions>>;
release: Distributions;
};
/**
* Allows the user to choose the release they want to build.
* Follows the PF5 pattern: https://www.patternfly.org/components/menus/select#view-more
*/
const ReleaseSelect = ({ setRelease, release }: ReleaseSelectType) => {
// By default the component doesn't show the Centos releases and only the RHEL
// ones. The user has the option to click on a button to make them appear.
const [showDevelopmentOptions, setShowDevelopmentOptions] = useState(false);
const releaseOptions = () => {
const options: ReactElement[] = [];
const filteredRhel = new Map<string, string>();
RELEASES.forEach((value, key) => {
// Only show non-RHEL distros if expanded
if (showDevelopmentOptions || isRhel(key)) {
filteredRhel.set(key, value);
}
});
filteredRhel.forEach((value, key) => {
if (value && key) {
options.push(
<SelectOption key={value} value={key} label={key}>
{RELEASES.get(key)}
</SelectOption>
);
}
});
return options;
};
const [isOpen, setIsOpen] = useState(false);
const onToggleClick = () => {
setIsOpen(!isOpen);
};
const viewMoreRef = useRef<HTMLLIElement>(null);
const toggleRef = useRef<HTMLButtonElement>(null);
const onSelect = (
_event: React.MouseEvent<Element, MouseEvent> | undefined,
value: string | number | undefined
) => {
if (value !== 'loader') {
if (typeof value === 'string') {
setRelease(value as Distributions);
}
setIsOpen(false);
toggleRef?.current?.focus(); // Only focus the toggle when a non-loader option is selected
}
};
const toggle = (
<MenuToggle
ref={toggleRef}
onClick={onToggleClick}
isExpanded={isOpen}
isFullWidth
>
{RELEASES.get(release)}
</MenuToggle>
);
return (
<FormGroup isRequired={true} label="Release" data-testid="release-select">
<Select
ouiaId="release_select"
id="release_select"
isOpen={isOpen}
selected={release}
onSelect={onSelect}
onOpenChange={(isOpen) => setIsOpen(isOpen)}
toggle={{ toggleNode: toggle, toggleRef }}
>
<SelectList>
{releaseOptions()}
<SelectOption
{...(!showDevelopmentOptions && { isLoadButton: true })}
onClick={() => setShowDevelopmentOptions(true)}
value="loader"
ref={viewMoreRef}
>
{!showDevelopmentOptions
? 'Show options for further development of RHEL'
: undefined}
</SelectOption>
</SelectList>
</Select>
</FormGroup>
);
};
export default ReleaseSelect;

View file

@ -1,42 +0,0 @@
import React, { useState } from 'react';
import { ExpandableSection, Form, Title } from '@patternfly/react-core';
import { ImageOutputList } from './imageOutput';
import {
ArchitectureItem,
Distributions,
} from '../../../../store/imageBuilderApi';
type ReviewStepPropTypes = {
release: Distributions;
arch: ArchitectureItem['arch'];
};
const ReviewStep = ({ release, arch }: ReviewStepPropTypes) => {
const [isExpandedImageOutput, setIsExpandedImageOutput] = useState(false);
const onToggleImageOutput = (isExpandedImageOutput: boolean) =>
setIsExpandedImageOutput(isExpandedImageOutput);
return (
<>
<Form>
<Title headingLevel="h2">Review</Title>
<ExpandableSection
toggleContent={'Image output'}
onToggle={(_event, isExpandedImageOutput) =>
onToggleImageOutput(isExpandedImageOutput)
}
isExpanded={isExpandedImageOutput}
isIndented
data-testid="image-output-expandable"
>
<ImageOutputList release={release} arch={arch} />
</ExpandableSection>
</Form>
</>
);
};
export default ReviewStep;

View file

@ -1,46 +0,0 @@
import React from 'react';
import {
TextContent,
TextList,
TextListItem,
TextListVariants,
TextListItemVariants,
} from '@patternfly/react-core';
import { RELEASES } from '../../../../constants';
import {
ArchitectureItem,
Distributions,
} from '../../../../store/imageBuilderApi';
type ImageOutputListPropTypes = {
release: Distributions;
arch: ArchitectureItem['arch'];
};
export const ImageOutputList = ({
release,
arch,
}: ImageOutputListPropTypes) => {
return (
<TextContent>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Release
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{RELEASES.get(release)}
</TextListItem>
<TextListItem component={TextListItemVariants.dt}>
Architecture
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>{arch}</TextListItem>
</TextList>
<br />
</TextContent>
);
};

View file

@ -1,145 +0,0 @@
import React from 'react';
import '@testing-library/jest-dom';
import { screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import CreateImageWizard from '../../../Components/CreateImageWizardV2/CreateImageWizard';
import ShareImageModal from '../../../Components/ShareImageModal/ShareImageModal';
import { server } from '../../mocks/server.js';
import {
clickNext,
renderCustomRoutesWithReduxRouter,
verifyCancelButton,
} from '../../testUtils';
const routes = [
{
path: 'insights/image-builder/*',
element: <div />,
},
{
path: 'insights/image-builder/imagewizard/:composeId?',
element: <CreateImageWizard />,
},
{
path: 'insights/image-builder/share /:composeId',
element: <ShareImageModal />,
},
];
jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
useChrome: () => ({
auth: {
getUser: () => {
return {
identity: {
internal: {
org_id: 5,
},
},
};
},
},
isBeta: () => true,
isProd: () => true,
getEnvironment: () => 'prod',
}),
}));
jest.mock('@unleash/proxy-client-react', () => ({
useUnleashContext: () => jest.fn(),
useFlag: jest.fn((flag) =>
flag === 'image-builder.enable-content-sources' ? true : false
),
}));
beforeAll(() => {
// scrollTo is not defined in jsdom
window.HTMLElement.prototype.scrollTo = function () {};
});
afterEach(() => {
jest.clearAllMocks();
server.resetHandlers();
});
describe('Create Image Wizard', () => {
test('renders component', () => {
renderCustomRoutesWithReduxRouter('imagewizard', {}, routes);
// check heading
screen.getByRole('heading', { name: /Image Builder/ });
screen.getByRole('button', { name: 'Image output' });
});
});
describe('Step Image output', () => {
test('clicking Next loads the review step with correct information about the image output', async () => {
const user = userEvent.setup();
await renderCustomRoutesWithReduxRouter('imagewizard', {}, routes);
// select aws as upload destination
await user.click(await screen.findByTestId('upload-aws'));
await screen.findByRole('heading', { name: 'Image output' });
await clickNext();
await screen.findByRole('heading', { name: 'Review' });
const view = screen.getByTestId('image-output-expandable');
await user.click(await within(view).findByText(/image output/i));
expect(await screen.findByText(/x86_64/i)).not.toBeNaN();
expect(
await screen.findByText(/red hat enterprise linux \(rhel\) 9/i)
).not.toBeNaN();
});
test('selecting rhel8 and aarch64 shows accordingly in the review step', async () => {
const user = userEvent.setup();
await renderCustomRoutesWithReduxRouter('imagewizard', {}, routes);
// select rhel8
const releaseMenu = screen.getAllByRole('button', {
name: /Red Hat Enterprise Linux \(RHEL\) 9/,
})[0];
await user.click(releaseMenu);
await user.click(
await screen.findByRole('option', {
name: /Red Hat Enterprise Linux \(RHEL\) 8/,
})
);
// Change to aarch
await user.selectOptions(
await screen.findByRole('combobox', {
name: /architecture/i,
}),
'aarch64'
);
// select aws as upload destination
await user.click(await screen.findByTestId('upload-aws'));
await clickNext();
await screen.findByRole('heading', { name: 'Review' });
const view = screen.getByTestId('image-output-expandable');
await user.click(await within(view).findByText(/image output/i));
expect(await screen.findByText(/aarch64/i)).not.toBeNaN();
expect(
await screen.findByText(/red hat enterprise linux \(rhel\) 8/i)
).not.toBeNaN();
});
test('clicking Cancel loads landing page', async () => {
const { router } = await renderCustomRoutesWithReduxRouter(
'imagewizard',
{},
routes
);
await clickNext();
await verifyCancelButton(router);
});
});

View file

@ -1,262 +0,0 @@
import React from 'react';
import '@testing-library/jest-dom';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import CreateImageWizard from '../../../../../Components/CreateImageWizardV2/CreateImageWizard';
import { AARCH64, RHEL_8, RHEL_9, X86_64 } from '../../../../../constants';
import { mockArchitecturesByDistro } from '../../../../fixtures/architectures';
import { server } from '../../../../mocks/server';
import { renderCustomRoutesWithReduxRouter } from '../../../../testUtils';
const routes = [
{
path: 'insights/image-builder/imagewizard/:composeId?',
element: <CreateImageWizard />,
},
];
jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
useChrome: () => ({
auth: {
getUser: () => {
return {
identity: {
internal: {
org_id: 5,
},
},
};
},
},
isBeta: () => true,
isProd: () => true,
getEnvironment: () => 'prod',
}),
}));
beforeAll(() => {
// scrollTo is not defined in jsdom
window.HTMLElement.prototype.scrollTo = function () {};
});
afterEach(() => {
jest.clearAllMocks();
server.resetHandlers();
});
describe('Check that the target filtering is in accordance to mock content', () => {
test('rhel9 x86_64', async () => {
await renderCustomRoutesWithReduxRouter('imagewizard', {}, routes);
// make sure this test is in SYNC with the mocks
let images_types: string[] = [];
mockArchitecturesByDistro(RHEL_9).forEach((elem) => {
if (elem.arch === X86_64) {
images_types = elem.image_types;
}
});
expect(images_types).toContain('aws');
expect(images_types).toContain('gcp');
expect(images_types).toContain('azure');
expect(images_types).toContain('guest-image');
expect(images_types).toContain('image-installer');
expect(images_types).toContain('vsphere');
expect(images_types).toContain('vsphere-ova');
expect(images_types).not.toContain('wsl');
// make sure the UX conforms to the mocks
await waitFor(async () => await screen.findByTestId('upload-aws'));
await screen.findByTestId('upload-google');
await screen.findByTestId('upload-azure');
await screen.findByTestId('checkbox-guest-image');
await screen.findByTestId('checkbox-image-installer');
await screen.findByText(/vmware vsphere/i);
await screen.findByText(/open virtualization format \(\.ova\)/i);
expect(
screen.queryByText(/wsl - windows subsystem for linux \(\.tar\.gz\)/i)
).not.toBeInTheDocument();
});
test('rhel8 x86_64', async () => {
const user = userEvent.setup();
await renderCustomRoutesWithReduxRouter('imagewizard', {}, routes);
// select rhel8
const releaseMenu = screen.getAllByRole('button', {
name: /Red Hat Enterprise Linux \(RHEL\) 9/,
})[0];
await user.click(releaseMenu);
await user.click(
await screen.findByRole('option', {
name: /Red Hat Enterprise Linux \(RHEL\) 8/,
})
);
// make sure this test is in SYNC with the mocks
let images_types: string[] = [];
mockArchitecturesByDistro(RHEL_8).forEach((elem) => {
if (elem.arch === X86_64) {
images_types = elem.image_types;
}
});
expect(images_types).toContain('aws');
expect(images_types).toContain('gcp');
expect(images_types).toContain('azure');
expect(images_types).toContain('guest-image');
expect(images_types).toContain('image-installer');
expect(images_types).toContain('vsphere');
expect(images_types).toContain('vsphere-ova');
expect(images_types).toContain('wsl');
// make sure the UX conforms to the mocks
await waitFor(async () => await screen.findByTestId('upload-aws'));
await screen.findByTestId('upload-google');
await screen.findByTestId('upload-azure');
await screen.findByTestId('checkbox-guest-image');
await screen.findByTestId('checkbox-image-installer');
await screen.findByText(/vmware vsphere/i);
await screen.findByText(/open virtualization format \(\.ova\)/i);
await screen.findByText(/wsl - windows subsystem for linux \(\.tar\.gz\)/i);
});
test('rhel9 aarch64', async () => {
const user = userEvent.setup();
await renderCustomRoutesWithReduxRouter('imagewizard', {}, routes);
// select aarch64
await user.selectOptions(
await screen.findByRole('combobox', {
name: /architecture/i,
}),
'aarch64'
);
// make sure this test is in SYNC with the mocks
let images_types: string[] = [];
mockArchitecturesByDistro(RHEL_9).forEach((elem) => {
if (elem.arch === AARCH64) {
images_types = elem.image_types;
}
});
expect(images_types).toContain('aws');
expect(images_types).not.toContain('gcp');
expect(images_types).not.toContain('azure');
expect(images_types).toContain('guest-image');
expect(images_types).toContain('image-installer');
expect(images_types).not.toContain('vsphere');
expect(images_types).not.toContain('vsphere-ova');
expect(images_types).not.toContain('wsl');
// make sure the UX conforms to the mocks
await waitFor(async () => await screen.findByTestId('upload-aws'));
expect(screen.queryByTestId('upload-google')).not.toBeInTheDocument();
expect(screen.queryByTestId('upload-azure')).not.toBeInTheDocument();
await screen.findByTestId('checkbox-guest-image');
await screen.findByTestId('checkbox-image-installer');
expect(screen.queryByText(/vmware vsphere/i)).not.toBeInTheDocument();
expect(
screen.queryByText(/open virtualization format \(\.ova\)/i)
).not.toBeInTheDocument();
expect(
screen.queryByText(/wsl - windows subsystem for linux \(\.tar\.gz\)/i)
).not.toBeInTheDocument();
});
test('rhel8 aarch64', async () => {
const user = userEvent.setup();
await renderCustomRoutesWithReduxRouter('imagewizard', {}, routes);
// select rhel8
const releaseMenu = screen.getAllByRole('button', {
name: /Red Hat Enterprise Linux \(RHEL\) 9/,
})[0];
await user.click(releaseMenu);
await user.click(
await screen.findByRole('option', {
name: /Red Hat Enterprise Linux \(RHEL\) 8/,
})
);
// select aarch64
await user.selectOptions(
await screen.findByRole('combobox', {
name: /architecture/i,
}),
'aarch64'
);
// make sure this test is in SYNC with the mocks
let images_types: string[] = [];
mockArchitecturesByDistro(RHEL_8).forEach((elem) => {
if (elem.arch === AARCH64) {
images_types = elem.image_types;
}
});
expect(images_types).toContain('aws');
expect(images_types).not.toContain('gcp');
expect(images_types).not.toContain('azure');
expect(images_types).toContain('guest-image');
expect(images_types).toContain('image-installer');
expect(images_types).not.toContain('vsphere');
expect(images_types).not.toContain('vsphere-ova');
expect(images_types).not.toContain('wsl');
// make sure the UX conforms to the mocks
await waitFor(async () => await screen.findByTestId('upload-aws'));
expect(screen.queryByTestId('upload-google')).not.toBeInTheDocument();
expect(screen.queryByTestId('upload-azure')).not.toBeInTheDocument();
await screen.findByTestId('checkbox-guest-image');
await screen.findByTestId('checkbox-image-installer');
expect(screen.queryByText(/vmware vsphere/i)).not.toBeInTheDocument();
expect(
screen.queryByText(/open virtualization format \(\.ova\)/i)
).not.toBeInTheDocument();
expect(
screen.queryByText(/wsl - windows subsystem for linux \(\.tar\.gz\)/i)
).not.toBeInTheDocument();
});
});
describe('Check step consistency', () => {
test('going back and forth with selected options only keeps the one compatible', async () => {
const user = userEvent.setup();
await renderCustomRoutesWithReduxRouter('imagewizard', {}, routes);
// select x86_64
await user.selectOptions(
await screen.findByRole('combobox', {
name: /architecture/i,
}),
'x86_64'
);
await waitFor(async () => await screen.findByTestId('upload-aws'));
// select GCP, it's available for x86_64
await user.click(await screen.findByTestId('upload-google'));
await waitFor(async () =>
expect(await screen.findByRole('button', { name: /Next/ })).toBeEnabled()
);
// Change to aarch
await user.selectOptions(
await screen.findByRole('combobox', {
name: /architecture/i,
}),
'aarch64'
);
// GCP not being compatible with arch, the next button is disabled
await waitFor(async () =>
expect(await screen.findByRole('button', { name: /Next/ })).toBeDisabled()
);
// clicking on AWS the user can go next
await user.click(await screen.findByTestId('upload-aws'));
await waitFor(async () =>
expect(await screen.findByRole('button', { name: /Next/ })).toBeEnabled()
);
// and going back to x86_64 the user should keep the next button visible
await user.selectOptions(
await screen.findByRole('combobox', {
name: /architecture/i,
}),
'x86_64'
);
await waitFor(async () =>
expect(await screen.findByRole('button', { name: /Next/ })).toBeEnabled()
);
});
});