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,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>
);
};