V2Wizard: Create a folder for Review step and copy review component and enable tests

This created a new folder for the Review step and copies components that will be needed:
- ReviewStep.tsx
- ReviewStepTables.tsx
- ReviewStepTextLists.tsx
This commit is contained in:
mgold1234 2024-01-23 12:56:47 +02:00 committed by Lucas Garfield
parent 34feb3fe60
commit c80e933c2c
7 changed files with 1380 additions and 515 deletions

View file

@ -0,0 +1,196 @@
import React, { useState } from 'react';
import {
ExpandableSection,
Text,
TextContent,
TextVariants,
} from '@patternfly/react-core';
import {
ContentList,
FSCList,
ImageDetailsList,
ImageOutputList,
OscapList,
RegisterLaterList,
RegisterNowList,
TargetEnvAWSList,
TargetEnvGCPList,
TargetEnvOciList,
TargetEnvOtherList,
} from './ReviewStepTextLists';
import isRhel from '../../../../../src/Utilities/isRhel';
import { useAppSelector } from '../../../../store/hooks';
import {
selectBlueprintDescription,
selectBlueprintName,
selectDistribution,
selectImageTypes,
selectProfile,
selectRegistrationType,
} from '../../../../store/wizardSlice';
const Review = () => {
const blueprintName = useAppSelector((state) => selectBlueprintName(state));
const blueprintDescription = useAppSelector((state) =>
selectBlueprintDescription(state)
);
const distribution = useAppSelector((state) => selectDistribution(state));
const environments = useAppSelector((state) => selectImageTypes(state));
const oscapProfile = useAppSelector((state) => selectProfile(state));
const registrationType = useAppSelector((state) =>
selectRegistrationType(state)
);
const [isExpandedImageOutput, setIsExpandedImageOutput] = useState(false);
const [isExpandedTargetEnvs, setIsExpandedTargetEnvs] = useState(false);
const [isExpandedFSC, setIsExpandedFSC] = useState(false);
const [isExpandedContent, setIsExpandedContent] = useState(false);
const [isExpandedRegistration, setIsExpandedRegistration] = useState(false);
const [isExpandedImageDetail, setIsExpandedImageDetail] = useState(false);
const [isExpandedOscapDetail, setIsExpandedOscapDetail] = useState(false);
const onToggleImageOutput = (isExpandedImageOutput: boolean) =>
setIsExpandedImageOutput(isExpandedImageOutput);
const onToggleTargetEnvs = (isExpandedTargetEnvs: boolean) =>
setIsExpandedTargetEnvs(isExpandedTargetEnvs);
const onToggleFSC = (isExpandedFSC: boolean) =>
setIsExpandedFSC(isExpandedFSC);
const onToggleContent = (isExpandedContent: boolean) =>
setIsExpandedContent(isExpandedContent);
const onToggleRegistration = (isExpandedRegistration: boolean) =>
setIsExpandedRegistration(isExpandedRegistration);
const onToggleImageDetail = (isExpandedImageDetail: boolean) =>
setIsExpandedImageDetail(isExpandedImageDetail);
const onToggleOscapDetails = (isExpandedOscapDetail: boolean) =>
setIsExpandedOscapDetail(isExpandedOscapDetail);
return (
<>
<ExpandableSection
toggleContent={'Image output'}
onToggle={(_event, isExpandedImageOutput) =>
onToggleImageOutput(isExpandedImageOutput)
}
isExpanded={isExpandedImageOutput}
isIndented
data-testid="image-output-expandable"
>
<ImageOutputList />
</ExpandableSection>
<ExpandableSection
toggleContent={'Target environments'}
onToggle={(_event, isExpandedTargetEnvs) =>
onToggleTargetEnvs(isExpandedTargetEnvs)
}
isExpanded={isExpandedTargetEnvs}
isIndented
data-testid="target-environments-expandable"
>
{environments.includes('aws') && <TargetEnvAWSList />}
{environments.includes('gcp') && <TargetEnvGCPList />}
{environments.includes('oci') && <TargetEnvOciList />}
{environments.includes('vsphere') && (
<TextContent>
<Text component={TextVariants.h3}>VMware vSphere (.vmdk)</Text>
<TargetEnvOtherList />
</TextContent>
)}
{environments.includes('vsphere-ova') && (
<TextContent>
<Text component={TextVariants.h3}>VMware vSphere (.ova)</Text>
<TargetEnvOtherList />
</TextContent>
)}
{environments.includes('guest-image') && (
<TextContent>
<Text component={TextVariants.h3}>
Virtualization - Guest image (.qcow2)
</Text>
<TargetEnvOtherList />
</TextContent>
)}
{environments.includes('image-installer') && (
<TextContent>
<Text component={TextVariants.h3}>
Bare metal - Installer (.iso)
</Text>
<TargetEnvOtherList />
</TextContent>
)}
{environments.includes('wsl') && (
<TextContent>
<Text component={TextVariants.h3}>
WSL - Windows Subsystem for Linux (.tar.gz)
</Text>
<TargetEnvOtherList />
</TextContent>
)}
</ExpandableSection>
{isRhel(distribution) && (
<ExpandableSection
toggleContent={'Registration'}
onToggle={(_event, isExpandedRegistration) =>
onToggleRegistration(isExpandedRegistration)
}
isExpanded={isExpandedRegistration}
isIndented
data-testid="registration-expandable"
>
{registrationType === 'register-later' && <RegisterLaterList />}
{registrationType.startsWith('register-now') && <RegisterNowList />}
</ExpandableSection>
)}
{oscapProfile && (
<ExpandableSection
toggleContent={'OpenSCAP'}
onToggle={(_event, isExpandedOscapDetail) =>
onToggleOscapDetails(isExpandedOscapDetail)
}
isExpanded={isExpandedOscapDetail}
isIndented
data-testid="oscap-detail-expandable"
>
<OscapList />
</ExpandableSection>
)}
<ExpandableSection
toggleContent={'File system configuration'}
onToggle={(_event, isExpandedFSC) => onToggleFSC(isExpandedFSC)}
isExpanded={isExpandedFSC}
isIndented
data-testid="file-system-configuration-expandable"
>
<FSCList />
</ExpandableSection>
<ExpandableSection
toggleContent={'Content'}
onToggle={(_event, isExpandedContent) =>
onToggleContent(isExpandedContent)
}
isExpanded={isExpandedContent}
isIndented
data-testid="content-expandable"
>
<ContentList />
</ExpandableSection>
{(blueprintName || blueprintDescription) && (
<ExpandableSection
toggleContent={'Image details'}
onToggle={(_event, isExpandedImageDetail) =>
onToggleImageDetail(isExpandedImageDetail)
}
isExpanded={isExpandedImageDetail}
isIndented
data-testid="image-details-expandable"
>
<ImageDetailsList />
</ExpandableSection>
)}
</>
);
};
export default Review;

View file

@ -0,0 +1,104 @@
import React from 'react';
import { Alert, Panel, PanelMain, Spinner } from '@patternfly/react-core';
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import { useListRepositoriesQuery } from '../../../../store/contentSourcesApi';
import { useAppSelector } from '../../../../store/hooks';
import { selectCustomRepositories } from '../../../../store/wizardSlice';
type repoPropType = {
repoUrl: string[] | undefined;
};
const RepoName = ({ repoUrl }: repoPropType) => {
const { data, isSuccess, isFetching, isError } = useListRepositoriesQuery({
// @ts-ignore
url: repoUrl,
contentType: 'rpm',
origin: 'external',
});
const errorLoading = () => {
return (
<Alert
variant="danger"
isInline
isPlain
title="Error loading repository name"
/>
);
};
return (
<>
{/*
this might be a tad bit hacky
"isSuccess" indicates only that the query fetched successfuly, but it
doesn't differentiate between a scenario when the repository was found
in the response and when it was not
for this reason I've split the "isSuccess" into two paths:
- query finished and the repo was found -> render the name of the repo
- query finished, but the repo was not found -> render an error
*/}
{isSuccess && data.data?.[0]?.name && <p>{data.data?.[0].name}</p>}
{isSuccess && !data.data?.[0]?.name && errorLoading()}
{isFetching && <Spinner size="md" />}
{isError && errorLoading()}
</>
);
};
export const FSReviewTable = () => {
return (
<Panel isScrollable>
<PanelMain maxHeight="30ch">
<Table aria-label="File system configuration table" variant="compact">
<Thead>
<Tr>
<Th>Mount point</Th>
<Th>File system type</Th>
<Th>Minimum size</Th>
</Tr>
</Thead>
</Table>
</PanelMain>
</Panel>
);
};
export const PackagesTable = () => {
return (
<Panel isScrollable>
<PanelMain maxHeight="30ch"></PanelMain>
</Panel>
);
};
export const RepositoriesTable = () => {
const repositoriesList = useAppSelector((state) =>
selectCustomRepositories(state)
);
return (
<Panel isScrollable>
<PanelMain maxHeight="30ch">
<Table aria-label="Custom repositories table" variant="compact">
<Thead>
<Tr>
<Th>Name</Th>
</Tr>
</Thead>
<Tbody data-testid="repositories-tbody-review">
{repositoriesList?.map((repo, repoIndex) => (
<Tr key={repoIndex}>
<Td className="pf-m-width-60">
<RepoName repoUrl={repo.baseurl} />
</Td>
</Tr>
))}
</Tbody>
</Table>
</PanelMain>
</Panel>
);
};

View file

@ -0,0 +1,532 @@
import React, { useEffect, useState } from 'react';
import {
Alert,
Button,
Popover,
Text,
TextContent,
TextList,
TextListItem,
TextListVariants,
TextListItemVariants,
TextVariants,
Spinner,
} from '@patternfly/react-core';
import { ExclamationTriangleIcon, HelpIcon } from '@patternfly/react-icons';
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome';
import ReleaseLifecycle from './../ImageOutput/ReleaseLifecycle';
import ActivationKeyInformation from './../Registration/ActivationKeyInformation';
import { AwsAccountId } from './../TargetEnvironment/Aws/AwsAccountId';
import { RepositoriesTable } from './ReviewStepTables';
import { RELEASES } from '../../../../constants';
import { extractProvisioningList } from '../../../../store/helpers';
import { useAppSelector } from '../../../../store/hooks';
import { useGetOscapCustomizationsQuery } from '../../../../store/imageBuilderApi';
import { useGetSourceListQuery } from '../../../../store/provisioningApi';
import { useShowActivationKeyQuery } from '../../../../store/rhsmApi';
import {
selectActivationKey,
selectArchitecture,
selectAwsAccountId,
selectAwsShareMethod,
selectBlueprintDescription,
selectBlueprintName,
selectCustomRepositories,
selectDistribution,
selectGcpAccountType,
selectGcpEmail,
selectGcpShareMethod,
selectProfile,
selectRegistrationType,
} from '../../../../store/wizardSlice';
import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
const ExpirationWarning = () => {
return (
<div className="pf-u-mr-sm pf-u-font-size-sm pf-u-warning-color-100">
<ExclamationTriangleIcon /> Expires 14 days after creation
</div>
);
};
export const ImageOutputList = () => {
const distribution = useAppSelector((state) => selectDistribution(state));
const arch = useAppSelector((state) => selectArchitecture(state));
return (
<TextContent>
<ReleaseLifecycle />
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Release
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{RELEASES.get(distribution)}
</TextListItem>
<TextListItem component={TextListItemVariants.dt}>
Architecture
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>{arch}</TextListItem>
</TextList>
<br />
</TextContent>
);
};
export const FSCList = () => {
return (
<TextContent>
<br />
</TextContent>
);
};
export const TargetEnvAWSList = () => {
const { data: rawAWSSources, isSuccess } = useGetSourceListQuery({
provider: 'aws',
});
const awsAccountId = useAppSelector((state) => selectAwsAccountId(state));
const awsShareMethod = useAppSelector((state) => selectAwsShareMethod(state));
const awsSources = extractProvisioningList(rawAWSSources);
const { isBeta } = useGetEnvironment();
return (
<TextContent>
<Text component={TextVariants.h3}>AWS</Text>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Image type
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
Red Hat hosted image
<br />
<ExpirationWarning />
</TextListItem>
<TextListItem component={TextListItemVariants.dt}>
Shared to account
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{!isBeta() && awsAccountId}
{isBeta() && awsShareMethod === 'sources' && isSuccess && (
<AwsAccountId />
)}
{isBeta() && awsShareMethod === 'manual' && awsAccountId}
</TextListItem>
<TextListItem component={TextListItemVariants.dt}>
{awsShareMethod === 'sources' ? 'Source' : null}
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{isSuccess && awsShareMethod === 'sources'
? awsSources?.find((source) => source.id === source)?.name
: null}
</TextListItem>
<TextListItem component={TextListItemVariants.dt}>
Default region
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
us-east-1
</TextListItem>
</TextList>
<br />
</TextContent>
);
};
export const TargetEnvGCPList = () => {
const accountType = useAppSelector((state) => selectGcpAccountType(state));
const sharedMethod = useAppSelector((state) => selectGcpShareMethod(state));
const email = useAppSelector((state) => selectGcpEmail(state));
return (
<TextContent>
<Text component={TextVariants.h3}>GCP</Text>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Image type
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
Red Hat hosted image
<br />
<ExpirationWarning />
</TextListItem>
<>
{sharedMethod === 'withInsights' ? (
<>
<TextListItem component={TextListItemVariants.dt}>
Shared with
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
Red Hat Insights only
<br />
</TextListItem>
</>
) : (
<>
<TextListItem component={TextListItemVariants.dt}>
Account type
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{accountType === 'group'
? 'Google group'
: accountType === 'service'
? 'Service account'
: accountType === 'google'
? 'Google account'
: 'Domain'}
</TextListItem>
<TextListItem component={TextListItemVariants.dt}>
{accountType === 'domain' ? 'Domain' : 'Principal'}
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{email || accountType}
</TextListItem>
</>
)}
</>
</TextList>
<br />
</TextContent>
);
};
export const TargetEnvAzureList = () => {
return (
<TextContent>
<Text component={TextVariants.h3}>Microsoft Azure</Text>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Image type
</TextListItem>
</TextList>
<br />
</TextContent>
);
};
export const TargetEnvOciList = () => {
return (
<TextContent>
<Text component={TextVariants.h3}>Oracle Cloud Infrastructure</Text>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Object Storage URL
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
The URL for the built image will be ready to copy
<br />
</TextListItem>
</TextList>
<br />
</TextContent>
);
};
export const TargetEnvOtherList = () => {
return (
<>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Image type
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
Built image will be available for download
</TextListItem>
</TextList>
<br />
</>
);
};
export const ContentList = () => {
const customRepositories = useAppSelector((state) =>
selectCustomRepositories(state)
);
return (
<TextContent>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Additional Red Hat
<br />
and 3rd party packages
</TextListItem>
<TextListItem
component={TextListItemVariants.dd}
data-testid="chosen-packages-count"
>
{0}
</TextListItem>
<TextListItem component={TextListItemVariants.dt}>
Custom repositories
</TextListItem>
<TextListItem
component={TextListItemVariants.dd}
data-testid="custom-repositories-count"
>
{customRepositories?.length > 0 ? (
<Popover
position="bottom"
headerContent="Custom repositories"
hasAutoWidth
minWidth="30rem"
bodyContent={<RepositoriesTable />}
>
<Button
variant="link"
aria-label="About custom repositories"
className="pf-u-p-0"
>
{customRepositories?.length || 0}
</Button>
</Popover>
) : (
0
)}
</TextListItem>
</TextList>
<br />
</TextContent>
);
};
export const RegisterLaterList = () => {
return (
<TextContent>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Registration type
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
Register the system later
</TextListItem>
</TextList>
<br />
</TextContent>
);
};
export const RegisterNowList = () => {
const activationKey = useAppSelector((state) => selectActivationKey(state));
const registrationType = useAppSelector((state) =>
selectRegistrationType(state)
);
const [orgId, setOrgId] = useState<string | undefined>(undefined);
const { auth } = useChrome();
useEffect(() => {
(async () => {
const userData = await auth?.getUser();
const id = userData?.identity?.internal?.org_id;
setOrgId(id);
})();
});
const { isError } = useShowActivationKeyQuery(
// @ts-ignore - type of 'activationKey' might not be strictly compatible with the expected type for 'name'.
{ name: activationKey },
{
skip: !activationKey,
}
);
return (
<>
<TextContent>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Registration type
</TextListItem>
<TextListItem
component={TextListItemVariants.dd}
data-testid="review-registration"
>
<TextList isPlain>
{registrationType?.startsWith('register-now') && (
<TextListItem>
Register with Red Hat Subscription Manager (RHSM)
<br />
</TextListItem>
)}
{registrationType === 'register-now-insights' ||
(registrationType === 'register-now-rhc' && (
<TextListItem>
Connect to Red Hat Insights
<br />
</TextListItem>
))}
{registrationType === 'register-now-rhc' && (
<TextListItem>
Use remote host configuration (rhc) utility
<br />
</TextListItem>
)}
</TextList>
</TextListItem>
<TextListItem component={TextListItemVariants.dt}>
Activation key
<Popover
bodyContent={
<TextContent>
<Text>
Activation keys enable you to register a system with
appropriate subscriptions, system purpose, and repositories
attached.
<br />
<br />
If using an activation key with command line registration,
you must provide your organization&apos;s ID. Your
organization&apos;s ID is{' '}
{orgId !== undefined ? orgId : <Spinner size="md" />}
</Text>
</TextContent>
}
>
<Button
variant="plain"
aria-label="About activation key"
className="pf-u-pl-sm pf-u-pt-0 pf-u-pb-0"
size="sm"
>
<HelpIcon />
</Button>
</Popover>
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
<ActivationKeyInformation />
</TextListItem>
</TextList>
<br />
</TextContent>
{isError && (
<Alert
title="Information about the activation key unavailable"
variant="danger"
isPlain
isInline
>
Information about the activation key cannot be loaded. Please check
the key was not removed and try again later.
</Alert>
)}
</>
);
};
export const ImageDetailsList = () => {
const blueprintName = useAppSelector((state) => selectBlueprintName(state));
const blueprintDescription = useAppSelector((state) =>
selectBlueprintDescription(state)
);
return (
<TextContent>
<TextList component={TextListVariants.dl}>
{blueprintName && (
<>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Image name
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{blueprintName}
</TextListItem>
</>
)}
{blueprintDescription && (
<>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Description
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{blueprintDescription}
</TextListItem>
</>
)}
</TextList>
<br />
</TextContent>
);
};
export const OscapList = () => {
const oscapProfile = useAppSelector((state) => selectProfile(state));
const release = useAppSelector((state) => selectDistribution(state));
const { data } = useGetOscapCustomizationsQuery(
{
distribution: release,
// @ts-ignore if oscapProfile is undefined the query is going to get skipped, so it's safe here to ignore the linter here
profile: oscapProfile,
},
{
skip: !oscapProfile,
}
);
return (
<TextContent>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Profile name:
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{data?.openscap?.profile_name}
</TextListItem>
</TextList>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Profile description:
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{data?.openscap?.profile_description}
</TextListItem>
</TextList>
<TextList component={TextListVariants.dl}>
<TextListItem
component={TextListItemVariants.dt}
className="pf-u-min-width"
>
Reference ID:
</TextListItem>
<TextListItem component={TextListItemVariants.dd}>
{oscapProfile}
</TextListItem>
</TextList>
<br />
</TextContent>
);
};

View file

@ -0,0 +1,16 @@
import React from 'react';
import { Form, Title } from '@patternfly/react-core';
import Review from './ReviewStep';
const ReviewStep = () => {
return (
<Form>
<Title headingLevel="h2">Review</Title>
<Review />
</Form>
);
};
export default ReviewStep;