HMS-3796: Add snapshot date selection to wizard
This commit is contained in:
parent
a97b4d082d
commit
3231b324f0
42 changed files with 1958 additions and 257 deletions
|
|
@ -5,9 +5,11 @@ extends: [
|
|||
rules:
|
||||
"@typescript-eslint/ban-ts-comment":
|
||||
- error
|
||||
- ts-expect-error: 'allow-with-description'
|
||||
ts-ignore: 'allow-with-description'
|
||||
- ts-expect-error: "allow-with-description"
|
||||
ts-ignore: "allow-with-description"
|
||||
ts-nocheck: true
|
||||
ts-check: true
|
||||
minimumDescriptionLength: 5
|
||||
"@typescript-eslint/ban-types": off
|
||||
"@typescript-eslint/no-unused-vars":
|
||||
- warn
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ rules:
|
|||
prefer-const:
|
||||
- error
|
||||
- destructuring: any
|
||||
no-console: 2
|
||||
no-console: 1
|
||||
eqeqeq: error
|
||||
array-callback-return: warn
|
||||
# Temporarily disabled
|
||||
|
|
|
|||
9
.prettierrc
Normal file
9
.prettierrc
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"semi": true,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": false,
|
||||
"jsxSingleQuote": false,
|
||||
"bracketSpacing": true,
|
||||
"tsxSingleQuote": true,
|
||||
"tsSingleQuote": true
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ const config: ConfigFile = {
|
|||
'listRepositories',
|
||||
'listRepositoriesRpms',
|
||||
'searchRpm',
|
||||
'listFeatures',
|
||||
'listSnapshotsByDate',
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -72,3 +72,13 @@ ul.pf-m-plain {
|
|||
.panel-border {
|
||||
--pf-v5-c-panel--before--BorderColor: #BEE1F4;
|
||||
}
|
||||
|
||||
// Targets the alert within the Reviewsteps > content dropdown
|
||||
// Removes excess top margin padding
|
||||
div.pf-v5-c-alert.pf-m-inline.pf-m-plain.pf-m-warning {
|
||||
margin-top: 18px;
|
||||
|
||||
h4 {
|
||||
margin-block-start: 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
Button,
|
||||
|
|
@ -20,6 +20,7 @@ import RegistrationStep from './steps/Registration';
|
|||
import RepositoriesStep from './steps/Repositories';
|
||||
import ReviewStep from './steps/Review';
|
||||
import ReviewWizardFooter from './steps/Review/Footer/Footer';
|
||||
import SnapshotStep from './steps/Snapshot';
|
||||
import Aws from './steps/TargetEnvironment/Aws';
|
||||
import Azure from './steps/TargetEnvironment/Azure';
|
||||
import Gcp from './steps/TargetEnvironment/Gcp';
|
||||
|
|
@ -32,6 +33,7 @@ import {
|
|||
} from './validators';
|
||||
|
||||
import { RHEL_8, AARCH64 } from '../../constants';
|
||||
import { useListFeaturesQuery } from '../../store/contentSourcesApi';
|
||||
import { useAppDispatch, useAppSelector } from '../../store/hooks';
|
||||
import './CreateImageWizard.scss';
|
||||
import {
|
||||
|
|
@ -53,6 +55,8 @@ import {
|
|||
selectRegistrationType,
|
||||
selectStepValidation,
|
||||
addImageType,
|
||||
selectSnapshotDate,
|
||||
selectUseLatest,
|
||||
} from '../../store/wizardSlice';
|
||||
import { resolveRelPath } from '../../Utilities/path';
|
||||
import { ImageBuilderHeader } from '../sharedComponents/ImageBuilderHeader';
|
||||
|
|
@ -93,14 +97,33 @@ export const CustomWizardFooter = ({
|
|||
};
|
||||
|
||||
type CreateImageWizardProps = {
|
||||
startStepIndex?: number;
|
||||
isEdit?: boolean;
|
||||
};
|
||||
|
||||
const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
|
||||
const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const [searchParams] = useSearchParams();
|
||||
|
||||
// Remove this and all fallthrough logic when snapshotting is enabled in Prod-stable
|
||||
// =========================TO REMOVE=======================
|
||||
const { data, isSuccess, isFetching, isError } =
|
||||
useListFeaturesQuery(undefined);
|
||||
|
||||
const snapshottingEnabled = useMemo(
|
||||
() =>
|
||||
!(
|
||||
!isError &&
|
||||
!isFetching &&
|
||||
isSuccess &&
|
||||
data?.snapshots?.accessible === false &&
|
||||
data?.snapshots?.enabled === false
|
||||
),
|
||||
[data, isSuccess, isFetching, isError]
|
||||
);
|
||||
|
||||
// =========================TO REMOVE=======================
|
||||
|
||||
// IMPORTANT: Ensure the wizard starts with a fresh initial state
|
||||
useEffect(() => {
|
||||
dispatch(initializeWizard());
|
||||
|
|
@ -139,6 +162,11 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
|
|||
const registrationType = useAppSelector(selectRegistrationType);
|
||||
const activationKey = useAppSelector(selectActivationKey);
|
||||
|
||||
const snapshotDate = useAppSelector(selectSnapshotDate);
|
||||
const useLatest = useAppSelector(selectUseLatest);
|
||||
|
||||
const snapshotStepRequiresChoice = !useLatest && !snapshotDate;
|
||||
|
||||
const [currentStep, setCurrentStep] = React.useState<WizardStepType>();
|
||||
const onStepChange = (
|
||||
_event: React.MouseEvent<HTMLButtonElement>,
|
||||
|
|
@ -152,7 +180,7 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
|
|||
<ImageBuilderHeader />
|
||||
<section className="pf-l-page__main-section pf-c-page__main-section">
|
||||
<Wizard
|
||||
startIndex={startStepIndex}
|
||||
startIndex={isEdit ? (snapshottingEnabled ? 14 : 13) : 1}
|
||||
onClose={() => navigate(resolveRelPath(''))}
|
||||
onStepChange={onStepChange}
|
||||
isVisitRequired
|
||||
|
|
@ -268,10 +296,27 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
|
|||
name="Content"
|
||||
id="step-content"
|
||||
steps={[
|
||||
...(snapshottingEnabled
|
||||
? [
|
||||
<WizardStep
|
||||
name="Repository snapshot"
|
||||
id="wizard-repository-snapshot"
|
||||
key="wizard-repository-snapshot"
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={snapshotStepRequiresChoice}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<SnapshotStep />
|
||||
</WizardStep>,
|
||||
]
|
||||
: []),
|
||||
<WizardStep
|
||||
name="Custom repositories"
|
||||
id="wizard-custom-repositories"
|
||||
key="wizard-custom-repositories"
|
||||
isDisabled={snapshotStepRequiresChoice}
|
||||
footer={<CustomWizardFooter disableNext={false} />}
|
||||
>
|
||||
<RepositoriesStep />
|
||||
|
|
@ -280,12 +325,13 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
|
|||
name="Additional packages"
|
||||
id="wizard-additional-packages"
|
||||
key="wizard-additional-packages"
|
||||
isDisabled={snapshotStepRequiresChoice}
|
||||
footer={<CustomWizardFooter disableNext={false} />}
|
||||
>
|
||||
<PackagesStep />
|
||||
</WizardStep>,
|
||||
]}
|
||||
></WizardStep>
|
||||
/>
|
||||
<WizardStep
|
||||
name="Details"
|
||||
id="step-details"
|
||||
|
|
@ -295,6 +341,7 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
|
|||
? 'error'
|
||||
: 'default'
|
||||
}
|
||||
isDisabled={snapshotStepRequiresChoice}
|
||||
footer={
|
||||
<CustomWizardFooter
|
||||
disableNext={detailsValidation !== 'success'}
|
||||
|
|
@ -306,9 +353,11 @@ const CreateImageWizard = ({ startStepIndex = 1 }: CreateImageWizardProps) => {
|
|||
<WizardStep
|
||||
name="Review"
|
||||
id="step-review"
|
||||
isDisabled={snapshotStepRequiresChoice}
|
||||
footer={<ReviewWizardFooter />}
|
||||
>
|
||||
<ReviewStep />
|
||||
{/* Intentional prop drilling for simplicity - To be removed */}
|
||||
<ReviewStep snapshottingEnabled={snapshottingEnabled} />
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const EditImageWizard = ({ blueprintId }: EditImageWizardProps) => {
|
|||
navigate(resolveRelPath(''));
|
||||
}
|
||||
}, [error, navigate]);
|
||||
return <CreateImageWizard startStepIndex={13} />;
|
||||
return <CreateImageWizard isEdit />;
|
||||
};
|
||||
|
||||
export default EditImageWizard;
|
||||
|
|
|
|||
|
|
@ -351,7 +351,11 @@ const Repositories = () => {
|
|||
|
||||
const handleSelectAll = () => {
|
||||
if (data) {
|
||||
updateSelected(data.data?.map((repo) => repo.url) || []);
|
||||
updateSelected(
|
||||
data.data
|
||||
?.filter(({ status }) => status === 'Valid')
|
||||
.map((repo) => repo.url) || []
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import {
|
|||
selectRegistrationType,
|
||||
} from '../../../../store/wizardSlice';
|
||||
|
||||
const Review = () => {
|
||||
const Review = ({ snapshottingEnabled }: { snapshottingEnabled: boolean }) => {
|
||||
const blueprintName = useAppSelector(selectBlueprintName);
|
||||
const blueprintDescription = useAppSelector(selectBlueprintDescription);
|
||||
const distribution = useAppSelector(selectDistribution);
|
||||
|
|
@ -172,7 +172,8 @@ const Review = () => {
|
|||
isIndented
|
||||
data-testid="content-expandable"
|
||||
>
|
||||
<ContentList />
|
||||
{/* Intentional prop drilling for simplicity - To be removed */}
|
||||
<ContentList snapshottingEnabled={snapshottingEnabled} />
|
||||
</ExpandableSection>
|
||||
{(blueprintName || blueprintDescription) && (
|
||||
<ExpandableSection
|
||||
|
|
|
|||
|
|
@ -1,9 +1,20 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Alert, Panel, PanelMain, Spinner } from '@patternfly/react-core';
|
||||
import {
|
||||
Alert,
|
||||
EmptyState,
|
||||
EmptyStateHeader,
|
||||
EmptyStateIcon,
|
||||
Panel,
|
||||
PanelMain,
|
||||
Spinner,
|
||||
} from '@patternfly/react-core';
|
||||
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
|
||||
|
||||
import { useListRepositoriesQuery } from '../../../../store/contentSourcesApi';
|
||||
import {
|
||||
ApiSnapshotForDate,
|
||||
useListRepositoriesQuery,
|
||||
} from '../../../../store/contentSourcesApi';
|
||||
import { useAppSelector } from '../../../../store/hooks';
|
||||
import {
|
||||
selectCustomRepositories,
|
||||
|
|
@ -93,6 +104,95 @@ export const FSReviewTable = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const Error = () => {
|
||||
return (
|
||||
<Alert title="Repositories unavailable" variant="danger" isPlain isInline>
|
||||
Repositories cannot be reached, try again later.
|
||||
</Alert>
|
||||
);
|
||||
};
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
<EmptyState>
|
||||
<EmptyStateHeader
|
||||
titleText="Loading"
|
||||
icon={<EmptyStateIcon icon={Spinner} />}
|
||||
headingLevel="h4"
|
||||
/>
|
||||
</EmptyState>
|
||||
);
|
||||
};
|
||||
|
||||
export const SnapshotTable = ({
|
||||
snapshotForDate,
|
||||
}: {
|
||||
snapshotForDate: ApiSnapshotForDate[];
|
||||
}) => {
|
||||
const { data, isSuccess, isLoading, isError } = useListRepositoriesQuery({
|
||||
uuid: snapshotForDate.map(({ repository_uuid }) => repository_uuid).join(),
|
||||
origin: 'red_hat,external', // Make sure to show both redhat and custom
|
||||
});
|
||||
|
||||
const isAfterSet = new Set(
|
||||
snapshotForDate
|
||||
.filter(({ is_after }) => is_after)
|
||||
.map(({ repository_uuid }) => repository_uuid)
|
||||
);
|
||||
|
||||
const stringToDateToMMDDYYYY = (strDate: string) => {
|
||||
const date = new Date(strDate);
|
||||
return `${(date.getMonth() + 1).toString().padStart(2, '0')}/${date
|
||||
.getDate()
|
||||
.toString()
|
||||
.padStart(2, '0')}/${date.getFullYear()}`;
|
||||
};
|
||||
|
||||
return (
|
||||
(isError && <Error />) ||
|
||||
(isLoading && <Loading />) ||
|
||||
(isSuccess && (
|
||||
<Panel isScrollable>
|
||||
<PanelMain maxHeight="30ch">
|
||||
<Table aria-label="Packages table" variant="compact">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Last snapshot date</Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody data-testid="packages-tbody-review">
|
||||
{data?.data?.map(({ uuid, name, last_snapshot }, pkgIndex) => (
|
||||
<Tr key={pkgIndex}>
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
{uuid && isAfterSet.has(uuid) ? (
|
||||
<Alert
|
||||
title={
|
||||
last_snapshot?.created_at
|
||||
? stringToDateToMMDDYYYY(last_snapshot.created_at)
|
||||
: 'N/A'
|
||||
}
|
||||
variant="warning"
|
||||
isPlain
|
||||
isInline
|
||||
/>
|
||||
) : last_snapshot?.created_at ? (
|
||||
stringToDateToMMDDYYYY(last_snapshot.created_at)
|
||||
) : (
|
||||
'N/A'
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</PanelMain>
|
||||
</Panel>
|
||||
))
|
||||
);
|
||||
};
|
||||
|
||||
export const PackagesTable = () => {
|
||||
const packages = useAppSelector(selectPackages);
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
|
|
@ -16,7 +16,11 @@ import {
|
|||
import { ExclamationTriangleIcon } from '@patternfly/react-icons';
|
||||
|
||||
import ActivationKeyInformation from './../Registration/ActivationKeyInformation';
|
||||
import { PackagesTable, RepositoriesTable } from './ReviewStepTables';
|
||||
import {
|
||||
PackagesTable,
|
||||
RepositoriesTable,
|
||||
SnapshotTable,
|
||||
} from './ReviewStepTables';
|
||||
import { FSReviewTable } from './ReviewStepTables';
|
||||
|
||||
import {
|
||||
|
|
@ -26,6 +30,7 @@ import {
|
|||
RHEL_8_MAINTENANCE_SUPPORT,
|
||||
RHEL_9,
|
||||
} from '../../../../constants';
|
||||
import { useListSnapshotsByDateMutation } from '../../../../store/contentSourcesApi';
|
||||
import { useAppSelector } from '../../../../store/hooks';
|
||||
import { useGetSourceListQuery } from '../../../../store/provisioningApi';
|
||||
import { useShowActivationKeyQuery } from '../../../../store/rhsmApi';
|
||||
|
|
@ -51,8 +56,14 @@ import {
|
|||
selectRegistrationType,
|
||||
selectFileSystemPartitionMode,
|
||||
selectRecommendedRepositories,
|
||||
selectSnapshotDate,
|
||||
selectUseLatest,
|
||||
} from '../../../../store/wizardSlice';
|
||||
import { toMonthAndYear } from '../../../../Utilities/time';
|
||||
import {
|
||||
convertMMDDYYYYToYYYYMMDD,
|
||||
toMonthAndYear,
|
||||
yyyyMMddFormat,
|
||||
} from '../../../../Utilities/time';
|
||||
import { MinimumSizePopover } from '../FileSystem/FileSystemTable';
|
||||
import { MajorReleasesLifecyclesChart } from '../ImageOutput/ReleaseLifecycle';
|
||||
import OscapProfileInformation from '../Oscap/OscapProfileInformation';
|
||||
|
|
@ -385,17 +396,126 @@ export const TargetEnvOtherList = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export const ContentList = () => {
|
||||
export const ContentList = ({
|
||||
snapshottingEnabled,
|
||||
}: {
|
||||
snapshottingEnabled: boolean;
|
||||
}) => {
|
||||
const customRepositories = useAppSelector(selectCustomRepositories);
|
||||
const packages = useAppSelector(selectPackages);
|
||||
const recommendedRepositories = useAppSelector(selectRecommendedRepositories);
|
||||
const snapshotDate = useAppSelector(selectSnapshotDate);
|
||||
const useLatest = useAppSelector(selectUseLatest);
|
||||
|
||||
const customAndRecommendedRepositoryUUIDS = useMemo(
|
||||
() =>
|
||||
[
|
||||
...customRepositories.map(({ id }) => id),
|
||||
...recommendedRepositories.map(({ uuid }) => uuid),
|
||||
] as string[],
|
||||
[customRepositories, recommendedRepositories]
|
||||
);
|
||||
|
||||
const [listSnapshotsByDate, { data, isSuccess, isLoading, isError }] =
|
||||
useListSnapshotsByDateMutation();
|
||||
|
||||
useEffect(() => {
|
||||
listSnapshotsByDate({
|
||||
apiListSnapshotByDateRequest: {
|
||||
repository_uuids: customAndRecommendedRepositoryUUIDS,
|
||||
date: useLatest
|
||||
? yyyyMMddFormat(new Date())
|
||||
: convertMMDDYYYYToYYYYMMDD(snapshotDate),
|
||||
},
|
||||
});
|
||||
}, [
|
||||
customAndRecommendedRepositoryUUIDS,
|
||||
listSnapshotsByDate,
|
||||
snapshotDate,
|
||||
useLatest,
|
||||
]);
|
||||
|
||||
const duplicatePackages = packages.filter(
|
||||
(item, index) => packages.indexOf(item) !== index
|
||||
);
|
||||
|
||||
const noRepositoriesSelected =
|
||||
customAndRecommendedRepositoryUUIDS.length === 0;
|
||||
|
||||
const hasSnapshotDateAfter = data?.data?.some(({ is_after }) => is_after);
|
||||
|
||||
const snapshottingText = useMemo(() => {
|
||||
switch (true) {
|
||||
case noRepositoriesSelected:
|
||||
return 'No repositories selected';
|
||||
case isLoading:
|
||||
return '';
|
||||
case useLatest:
|
||||
return 'Use latest';
|
||||
case !!snapshotDate:
|
||||
return `State as of ${snapshotDate}`;
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}, [noRepositoriesSelected, isLoading, useLatest, snapshotDate]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TextContent>
|
||||
<TextList component={TextListVariants.dl}>
|
||||
{snapshottingEnabled ? (
|
||||
<>
|
||||
<TextListItem
|
||||
component={TextListItemVariants.dt}
|
||||
className="pf-u-min-width"
|
||||
>
|
||||
Repository Snapshot
|
||||
</TextListItem>
|
||||
<TextListItem
|
||||
component={TextListItemVariants.dd}
|
||||
data-testid="snapshot-method"
|
||||
>
|
||||
<Popover
|
||||
position="bottom"
|
||||
headerContent={
|
||||
useLatest
|
||||
? 'Repositories as of today'
|
||||
: `Repositories as of ${snapshotDate}`
|
||||
}
|
||||
hasAutoWidth
|
||||
minWidth="60rem"
|
||||
bodyContent={
|
||||
<SnapshotTable snapshotForDate={data?.data || []} />
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="link"
|
||||
aria-label="Snapshot method"
|
||||
className="pf-u-p-0"
|
||||
isDisabled={noRepositoriesSelected || isLoading || isError}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{snapshottingText}
|
||||
</Button>
|
||||
</Popover>
|
||||
{!useLatest &&
|
||||
!isLoading &&
|
||||
isSuccess &&
|
||||
hasSnapshotDateAfter ? (
|
||||
<Alert
|
||||
variant="warning"
|
||||
isInline
|
||||
isPlain
|
||||
title="A snapshot for this date is not available for some repositories."
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</TextListItem>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<TextListItem component={TextListItemVariants.dt}>
|
||||
Custom repositories
|
||||
</TextListItem>
|
||||
|
|
|
|||
|
|
@ -4,13 +4,18 @@ import { Form, Title } from '@patternfly/react-core';
|
|||
|
||||
import Review from './ReviewStep';
|
||||
|
||||
const ReviewStep = () => {
|
||||
const ReviewStep = ({
|
||||
snapshottingEnabled,
|
||||
}: {
|
||||
snapshottingEnabled: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<Form>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
Review
|
||||
</Title>
|
||||
<Review />
|
||||
{/* Intentional prop drilling for simplicity - To be removed */}
|
||||
<Review snapshottingEnabled={snapshottingEnabled} />
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
117
src/Components/CreateImageWizardV2/steps/Snapshot/Snapshot.tsx
Normal file
117
src/Components/CreateImageWizardV2/steps/Snapshot/Snapshot.tsx
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
import React from 'react';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
DatePicker,
|
||||
Flex,
|
||||
FormGroup,
|
||||
Grid,
|
||||
Radio,
|
||||
Text,
|
||||
Title,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import ConditionalTooltip from './components/ConditionalTooltip';
|
||||
|
||||
import { useListFeaturesQuery } from '../../../../store/contentSourcesApi';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
|
||||
import {
|
||||
selectSnapshotDate,
|
||||
selectUseLatest,
|
||||
changeUseLatest,
|
||||
changeSnapshotDate,
|
||||
} from '../../../../store/wizardSlice';
|
||||
import {
|
||||
dateToMMDDYYYY,
|
||||
parseMMDDYYYYtoDate,
|
||||
} from '../../../../Utilities/time';
|
||||
|
||||
const dateValidators = [
|
||||
(date: Date) => {
|
||||
if (date.getTime() > Date.now()) {
|
||||
return 'Cannot set a date in the future';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
];
|
||||
|
||||
export default function Snapshot() {
|
||||
const dispatch = useAppDispatch();
|
||||
const snapshotDate = useAppSelector(selectSnapshotDate);
|
||||
const useLatest = useAppSelector(selectUseLatest);
|
||||
return (
|
||||
<>
|
||||
<FormGroup>
|
||||
<Radio
|
||||
id="use latest snapshot radio"
|
||||
ouiaId="use-latest-snapshot-radio"
|
||||
name="use-latest-snapshot"
|
||||
label="Use latest content"
|
||||
description="Use the newest repository state available when building this image."
|
||||
isChecked={useLatest}
|
||||
onChange={() => !useLatest && dispatch(changeUseLatest(true))}
|
||||
/>
|
||||
<Radio
|
||||
id="use snapshot date radio"
|
||||
ouiaId="use-snapshot-date-radio"
|
||||
name="use-snapshot-date"
|
||||
label="Use a snapshot"
|
||||
description="Target a date and build images with repository information from this date."
|
||||
isChecked={!useLatest}
|
||||
onChange={() => useLatest && dispatch(changeUseLatest(false))}
|
||||
/>
|
||||
</FormGroup>
|
||||
{useLatest ? (
|
||||
<>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
Use latest content
|
||||
</Title>
|
||||
<Grid>
|
||||
<Text>
|
||||
Image Builder will automatically use the newest state of
|
||||
repositories when building this image.
|
||||
</Text>
|
||||
</Grid>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
Use a snapshot
|
||||
</Title>
|
||||
<FormGroup label="Select snapshot date" isRequired>
|
||||
<Flex
|
||||
direction={{ default: 'row' }}
|
||||
alignContent={{ default: 'alignContentCenter' }}
|
||||
>
|
||||
<DatePicker
|
||||
id="pick snapshot date radio"
|
||||
name="pick-snapshot-date"
|
||||
value={snapshotDate}
|
||||
required
|
||||
requiredDateOptions={{ isRequired: true }}
|
||||
placeholder="MM/DD/YYYY"
|
||||
dateParse={parseMMDDYYYYtoDate}
|
||||
dateFormat={dateToMMDDYYYY}
|
||||
validators={dateValidators}
|
||||
onChange={(_, val) => dispatch(changeSnapshotDate(val))}
|
||||
/>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() => dispatch(changeSnapshotDate(''))}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</Flex>
|
||||
</FormGroup>
|
||||
<Grid>
|
||||
<Text>
|
||||
Image Builder will reflect the state of repositories based on the
|
||||
selected date when building this image.
|
||||
</Text>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import React, { cloneElement } from 'react';
|
||||
|
||||
import { Tooltip, TooltipProps } from '@patternfly/react-core';
|
||||
|
||||
interface Props extends TooltipProps {
|
||||
show: boolean;
|
||||
setDisabled?: boolean;
|
||||
}
|
||||
|
||||
const ConditionalTooltip = ({ show, children, setDisabled, ...rest }: Props) =>
|
||||
show ? (
|
||||
<Tooltip {...rest}>
|
||||
<div>
|
||||
{children &&
|
||||
cloneElement(
|
||||
children,
|
||||
setDisabled ? { isDisabled: setDisabled } : undefined
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<div>{children}</div>
|
||||
);
|
||||
|
||||
export default ConditionalTooltip;
|
||||
37
src/Components/CreateImageWizardV2/steps/Snapshot/index.tsx
Normal file
37
src/Components/CreateImageWizardV2/steps/Snapshot/index.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Button, Form, Grid, Text, Title } from '@patternfly/react-core';
|
||||
import { ExternalLinkAltIcon } from '@patternfly/react-icons';
|
||||
import { useHref } from 'react-router-dom';
|
||||
|
||||
import Snapshot from './Snapshot';
|
||||
|
||||
export default function SnapshotStep() {
|
||||
const path = useHref('image-builder');
|
||||
const pathname = path.split('image-builder')[0] + 'content';
|
||||
return (
|
||||
<Form>
|
||||
<Title headingLevel="h1" size="xl">
|
||||
Repository snapshot
|
||||
</Title>
|
||||
<Grid>
|
||||
<Text>
|
||||
Control the consistency of the packages in the repository used to
|
||||
build the image.
|
||||
</Text>
|
||||
<Button
|
||||
component="a"
|
||||
target="_blank"
|
||||
variant="link"
|
||||
iconPosition="right"
|
||||
isInline
|
||||
icon={<ExternalLinkAltIcon />}
|
||||
href={pathname + '/repositories'}
|
||||
>
|
||||
Create and manage repositories here
|
||||
</Button>
|
||||
</Grid>
|
||||
<Snapshot />
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
|
@ -46,7 +46,13 @@ import {
|
|||
wizardState,
|
||||
selectFileSystemPartitionMode,
|
||||
selectPartitions,
|
||||
selectSnapshotDate,
|
||||
selectUseLatest,
|
||||
} from '../../../store/wizardSlice';
|
||||
import {
|
||||
convertMMDDYYYYToYYYYMMDD,
|
||||
convertYYYYMMDDTOMMDDYYYY,
|
||||
} from '../../../Utilities/time';
|
||||
import {
|
||||
convertSchemaToIBCustomRepo,
|
||||
convertSchemaToIBPayloadRepo,
|
||||
|
|
@ -102,6 +108,11 @@ export const mapRequestToState = (request: BlueprintResponse): wizardState => {
|
|||
(image) => image.image_type === 'azure'
|
||||
);
|
||||
|
||||
const snapshot_date = convertYYYYMMDDTOMMDDYYYY(
|
||||
request.image_requests.find((image) => !!image.snapshot_date)
|
||||
?.snapshot_date || ''
|
||||
);
|
||||
|
||||
const awsUploadOptions = aws?.upload_request
|
||||
.options as AwsUploadRequestOptions;
|
||||
const gcpUploadOptions = gcp?.upload_request
|
||||
|
|
@ -154,6 +165,10 @@ export const mapRequestToState = (request: BlueprintResponse): wizardState => {
|
|||
source: { id: awsUploadOptions?.share_with_sources?.[0] },
|
||||
sourceId: awsUploadOptions?.share_with_sources?.[0],
|
||||
},
|
||||
snapshotting: {
|
||||
useLatest: !snapshot_date,
|
||||
snapshotDate: snapshot_date,
|
||||
},
|
||||
repositories: {
|
||||
customRepositories: request.customizations.custom_repositories || [],
|
||||
payloadRepositories: request.customizations.payload_repositories || [],
|
||||
|
|
@ -179,6 +194,8 @@ export const mapRequestToState = (request: BlueprintResponse): wizardState => {
|
|||
|
||||
const getImageRequests = (state: RootState): ImageRequest[] => {
|
||||
const imageTypes = selectImageTypes(state);
|
||||
const snapshotDate = convertMMDDYYYYToYYYYMMDD(selectSnapshotDate(state));
|
||||
const useLatest = selectUseLatest(state);
|
||||
return imageTypes.map((type) => ({
|
||||
architecture: selectArchitecture(state),
|
||||
image_type: type,
|
||||
|
|
@ -186,6 +203,7 @@ const getImageRequests = (state: RootState): ImageRequest[] => {
|
|||
type: uploadTypeByTargetEnv(type),
|
||||
options: getImageOptions(type, state),
|
||||
},
|
||||
snapshot_date: useLatest ? '' : snapshotDate,
|
||||
}));
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,49 +0,0 @@
|
|||
export const timestampToDisplayString = (ts) => {
|
||||
// timestamp has format 2021-04-27T12:31:12Z
|
||||
// must be converted to ms timestamp and then reformatted to Apr 27, 2021
|
||||
if (!ts) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// get YYYY-MM-DD format
|
||||
const ms = Date.parse(ts);
|
||||
const options = { month: 'short', day: 'numeric', year: 'numeric' };
|
||||
const tsDisplay = new Intl.DateTimeFormat('en-US', options).format(ms);
|
||||
return tsDisplay;
|
||||
};
|
||||
|
||||
export const convertStringToDate = (createdAtAsString) => {
|
||||
if (isNaN(Date.parse(createdAtAsString))) {
|
||||
// converts property created_at of the image object from string to UTC
|
||||
const [dateValues, timeValues] = createdAtAsString.split(' ');
|
||||
const datetimeString = `${dateValues}T${timeValues}Z`;
|
||||
return Date.parse(datetimeString);
|
||||
} else {
|
||||
return Date.parse(createdAtAsString);
|
||||
}
|
||||
};
|
||||
|
||||
export const computeHoursToExpiration = (imageCreatedAt) => {
|
||||
if (imageCreatedAt) {
|
||||
const currentTime = Date.now();
|
||||
// miliseconds in hour - needed for calculating the difference
|
||||
// between current date and the date of the image creation
|
||||
const msInHour = 1000 * 60 * 60;
|
||||
const timeUntilExpiration = Math.floor(
|
||||
(currentTime - convertStringToDate(imageCreatedAt)) / msInHour
|
||||
);
|
||||
return timeUntilExpiration;
|
||||
} else {
|
||||
// when creating a new image, the compose.created_at can be undefined when first queued
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const toMonthAndYear = (dateString) => {
|
||||
const options = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
};
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', options);
|
||||
};
|
||||
85
src/Utilities/time.ts
Normal file
85
src/Utilities/time.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
export const parseMMDDYYYYtoDate = (val: string) => {
|
||||
const [mm, dd, yyyy] = val.split('/');
|
||||
const newVal = `${yyyy}-${mm}-${dd}`;
|
||||
return mm && dd && yyyy ? new Date(`${newVal}T00:00:00`) : new Date('');
|
||||
};
|
||||
|
||||
export const parseYYYYMMDDToDate = (val: string) =>
|
||||
val ? new Date(`${val}T00:00:00`) : new Date('');
|
||||
|
||||
export const yyyyMMddFormat = (date: Date) =>
|
||||
`${date.getFullYear()}-${(date.getMonth() + 1)
|
||||
.toString()
|
||||
.padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
|
||||
|
||||
export const convertMMDDYYYYToYYYYMMDD = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
const date = parseMMDDYYYYtoDate(dateStr);
|
||||
return yyyyMMddFormat(date);
|
||||
};
|
||||
|
||||
export const dateToMMDDYYYY = (date: Date) =>
|
||||
`${(date.getMonth() + 1).toString().padStart(2, '0')}/${date
|
||||
.getDate()
|
||||
.toString()
|
||||
.padStart(2, '0')}/${date.getFullYear()}`;
|
||||
|
||||
export const convertYYYYMMDDTOMMDDYYYY = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
const date = parseYYYYMMDDToDate(dateStr);
|
||||
return dateToMMDDYYYY(date);
|
||||
};
|
||||
|
||||
export const toMonthAndYear = (dateString: string) => {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
};
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', options);
|
||||
};
|
||||
|
||||
export const timestampToDisplayString = (ts?: string) => {
|
||||
// timestamp has format 2021-04-27T12:31:12Z
|
||||
// must be converted to ms timestamp and then reformatted to Apr 27, 2021
|
||||
if (!ts) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// get YYYY-MM-DD format
|
||||
const ms = Date.parse(ts);
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
};
|
||||
const tsDisplay = new Intl.DateTimeFormat('en-US', options).format(ms);
|
||||
return tsDisplay;
|
||||
};
|
||||
|
||||
export const convertStringToDate = (createdAtAsString: string = '') => {
|
||||
if (isNaN(Date.parse(createdAtAsString))) {
|
||||
// converts property created_at of the image object from string to UTC
|
||||
const [dateValues, timeValues] = createdAtAsString.split(' ');
|
||||
const datetimeString = `${dateValues}T${timeValues}Z`;
|
||||
return Date.parse(datetimeString);
|
||||
} else {
|
||||
return Date.parse(createdAtAsString);
|
||||
}
|
||||
};
|
||||
|
||||
export const computeHoursToExpiration = (imageCreatedAt: string) => {
|
||||
if (imageCreatedAt) {
|
||||
const currentTime = Date.now();
|
||||
// miliseconds in hour - needed for calculating the difference
|
||||
// between current date and the date of the image creation
|
||||
const msInHour = 1000 * 60 * 60;
|
||||
const timeUntilExpiration = Math.floor(
|
||||
(currentTime - convertStringToDate(imageCreatedAt)) / msInHour
|
||||
);
|
||||
return timeUntilExpiration;
|
||||
} else {
|
||||
// when creating a new image, the compose.created_at can be undefined when first queued
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import { emptyContentSourcesApi as api } from "./emptyContentSourcesApi";
|
||||
const injectedRtkApi = api.injectEndpoints({
|
||||
endpoints: (build) => ({
|
||||
listFeatures: build.query<ListFeaturesApiResponse, ListFeaturesApiArg>({
|
||||
query: () => ({ url: `/features/` }),
|
||||
}),
|
||||
listRepositories: build.query<
|
||||
ListRepositoriesApiResponse,
|
||||
ListRepositoriesApiArg
|
||||
|
|
@ -56,10 +59,22 @@ const injectedRtkApi = api.injectEndpoints({
|
|||
body: queryArg.apiContentUnitSearchRequest,
|
||||
}),
|
||||
}),
|
||||
listSnapshotsByDate: build.mutation<
|
||||
ListSnapshotsByDateApiResponse,
|
||||
ListSnapshotsByDateApiArg
|
||||
>({
|
||||
query: (queryArg) => ({
|
||||
url: `/snapshots/for_date/`,
|
||||
method: "POST",
|
||||
body: queryArg.apiListSnapshotByDateRequest,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
overrideExisting: false,
|
||||
});
|
||||
export { injectedRtkApi as contentSourcesApi };
|
||||
export type ListFeaturesApiResponse = /** status 200 OK */ ApiFeatureSet;
|
||||
export type ListFeaturesApiArg = void;
|
||||
export type ListRepositoriesApiResponse =
|
||||
/** status 200 OK */ ApiRepositoryCollectionResponseRead;
|
||||
export type ListRepositoriesApiArg = {
|
||||
|
|
@ -81,11 +96,11 @@ export type ListRepositoriesApiArg = {
|
|||
name?: string;
|
||||
/** A comma separated list of URLs to control api response. */
|
||||
url?: string;
|
||||
/** A comma separated list of uuids to control api response. */
|
||||
/** A comma separated list of UUIDs to control api response. */
|
||||
uuid?: string;
|
||||
/** Sort the response data based on specific repository parameters. Sort criteria can include `name`, `url`, `status`, and `package_count`. */
|
||||
sortBy?: string;
|
||||
/** A comma separated list of statuses to control api response. Statuses can include `pending`, `valid`, `invalid`. */
|
||||
/** A comma separated list of statuses to control api response. Statuses can include `pending`, `valid`, `invalid`, `unavailable`. */
|
||||
status?: string;
|
||||
/** A comma separated list of origins to filter api response. Origins can include `red_hat` and `external`. */
|
||||
origin?: string;
|
||||
|
|
@ -117,6 +132,21 @@ export type SearchRpmApiArg = {
|
|||
/** request body */
|
||||
apiContentUnitSearchRequest: ApiContentUnitSearchRequest;
|
||||
};
|
||||
export type ListSnapshotsByDateApiResponse =
|
||||
/** status 200 OK */ ApiListSnapshotByDateResponse;
|
||||
export type ListSnapshotsByDateApiArg = {
|
||||
/** request body */
|
||||
apiListSnapshotByDateRequest: ApiListSnapshotByDateRequest;
|
||||
};
|
||||
export type ApiFeature = {
|
||||
/** Whether the current user can access the feature */
|
||||
accessible?: boolean;
|
||||
/** Whether the feature is enabled on the running server */
|
||||
enabled?: boolean;
|
||||
};
|
||||
export type ApiFeatureSet = {
|
||||
[key: string]: ApiFeature;
|
||||
};
|
||||
export type ApiSnapshotResponse = {
|
||||
/** Count of each content type */
|
||||
added_counts?: {
|
||||
|
|
@ -138,6 +168,26 @@ export type ApiSnapshotResponse = {
|
|||
url?: string;
|
||||
uuid?: string;
|
||||
};
|
||||
export type ApiTaskInfoResponse = {
|
||||
/** Timestamp of task creation */
|
||||
created_at?: string;
|
||||
/** Timestamp task ended running at */
|
||||
ended_at?: string;
|
||||
/** Error thrown while running task */
|
||||
error?: string;
|
||||
/** Organization ID of the owner */
|
||||
org_id?: string;
|
||||
/** Name of the associated repository */
|
||||
repository_name?: string;
|
||||
/** UUID of the associated repository */
|
||||
repository_uuid?: string;
|
||||
/** Status of task (running, failed, completed, canceled, pending) */
|
||||
status?: string;
|
||||
/** Type of task */
|
||||
type?: string;
|
||||
/** UUID of the object */
|
||||
uuid?: string;
|
||||
};
|
||||
export type ApiRepositoryResponse = {
|
||||
/** Content Type (rpm) of the repository */
|
||||
content_type?: string;
|
||||
|
|
@ -149,11 +199,16 @@ export type ApiRepositoryResponse = {
|
|||
failed_introspections_count?: number;
|
||||
/** GPG key for repository */
|
||||
gpg_key?: string;
|
||||
/** Label used to configure the yum repository on clients */
|
||||
label?: string;
|
||||
/** Error of last attempted introspection */
|
||||
last_introspection_error?: string;
|
||||
/** Status of last introspection */
|
||||
last_introspection_status?: string;
|
||||
/** Timestamp of last attempted introspection */
|
||||
last_introspection_time?: string;
|
||||
last_snapshot?: ApiSnapshotResponse;
|
||||
last_snapshot_task?: ApiTaskInfoResponse;
|
||||
/** UUID of the last snapshot task */
|
||||
last_snapshot_task_uuid?: string;
|
||||
/** UUID of the last dao.Snapshot */
|
||||
|
|
@ -174,7 +229,7 @@ export type ApiRepositoryResponse = {
|
|||
package_count?: number;
|
||||
/** Enable snapshotting and hosting of this repository */
|
||||
snapshot?: boolean;
|
||||
/** Status of repository introspection (Valid, Invalid, Unavailable, Pending) */
|
||||
/** Combined status of last introspection and snapshot of repository (Valid, Invalid, Unavailable, Pending) */
|
||||
status?: string;
|
||||
/** URL of the remote yum repository */
|
||||
url?: string;
|
||||
|
|
@ -192,11 +247,16 @@ export type ApiRepositoryResponseRead = {
|
|||
failed_introspections_count?: number;
|
||||
/** GPG key for repository */
|
||||
gpg_key?: string;
|
||||
/** Label used to configure the yum repository on clients */
|
||||
label?: string;
|
||||
/** Error of last attempted introspection */
|
||||
last_introspection_error?: string;
|
||||
/** Status of last introspection */
|
||||
last_introspection_status?: string;
|
||||
/** Timestamp of last attempted introspection */
|
||||
last_introspection_time?: string;
|
||||
last_snapshot?: ApiSnapshotResponse;
|
||||
last_snapshot_task?: ApiTaskInfoResponse;
|
||||
/** UUID of the last snapshot task */
|
||||
last_snapshot_task_uuid?: string;
|
||||
/** UUID of the last dao.Snapshot */
|
||||
|
|
@ -219,7 +279,7 @@ export type ApiRepositoryResponseRead = {
|
|||
package_count?: number;
|
||||
/** Enable snapshotting and hosting of this repository */
|
||||
snapshot?: boolean;
|
||||
/** Status of repository introspection (Valid, Invalid, Unavailable, Pending) */
|
||||
/** Combined status of last introspection and snapshot of repository (Valid, Invalid, Unavailable, Pending) */
|
||||
status?: string;
|
||||
/** URL of the remote yum repository */
|
||||
url?: string;
|
||||
|
|
@ -286,7 +346,7 @@ export type ApiRepositoryRequest = {
|
|||
url?: string;
|
||||
};
|
||||
export type ApiRepositoryRpm = {
|
||||
/** The Architecture of the rpm */
|
||||
/** The architecture of the rpm */
|
||||
arch?: string;
|
||||
/** The checksum of the rpm */
|
||||
checksum?: string;
|
||||
|
|
@ -322,12 +382,31 @@ export type ApiContentUnitSearchRequest = {
|
|||
search?: string;
|
||||
/** URLs of repositories to search */
|
||||
urls?: string[];
|
||||
/** List of RepositoryConfig UUIDs to search */
|
||||
/** List of repository UUIDs to search */
|
||||
uuids?: string[];
|
||||
};
|
||||
export type ApiSnapshotForDate = {
|
||||
/** Is the snapshot after the specified date */
|
||||
is_after?: boolean;
|
||||
match?: ApiSnapshotResponse;
|
||||
/** Repository uuid for associated snapshot */
|
||||
repository_uuid?: string;
|
||||
};
|
||||
export type ApiListSnapshotByDateResponse = {
|
||||
/** Requested Data */
|
||||
data?: ApiSnapshotForDate[];
|
||||
};
|
||||
export type ApiListSnapshotByDateRequest = {
|
||||
/** Exact date to search by. */
|
||||
date?: string;
|
||||
/** Repository UUIDs to find snapshots for */
|
||||
repository_uuids?: string[];
|
||||
};
|
||||
export const {
|
||||
useListFeaturesQuery,
|
||||
useListRepositoriesQuery,
|
||||
useCreateRepositoryMutation,
|
||||
useListRepositoriesRpmsQuery,
|
||||
useSearchRpmMutation,
|
||||
useListSnapshotsByDateMutation,
|
||||
} = injectedRtkApi;
|
||||
|
|
|
|||
|
|
@ -77,6 +77,10 @@ export type wizardState = {
|
|||
partitions: Partition[];
|
||||
isNextButtonTouched: boolean;
|
||||
};
|
||||
snapshotting: {
|
||||
useLatest: boolean;
|
||||
snapshotDate: string;
|
||||
};
|
||||
repositories: {
|
||||
customRepositories: CustomRepository[];
|
||||
payloadRepositories: Repository[];
|
||||
|
|
@ -136,6 +140,10 @@ const initialState: wizardState = {
|
|||
partitions: [],
|
||||
isNextButtonTouched: true,
|
||||
},
|
||||
snapshotting: {
|
||||
useLatest: true,
|
||||
snapshotDate: '',
|
||||
},
|
||||
repositories: {
|
||||
customRepositories: [],
|
||||
payloadRepositories: [],
|
||||
|
|
@ -241,6 +249,13 @@ export const selectPartitions = (state: RootState) => {
|
|||
return state.wizard.fileSystem.partitions;
|
||||
};
|
||||
|
||||
export const selectUseLatest = (state: RootState) => {
|
||||
return state.wizard.snapshotting.useLatest;
|
||||
};
|
||||
export const selectSnapshotDate = (state: RootState) => {
|
||||
return state.wizard.snapshotting.snapshotDate;
|
||||
};
|
||||
|
||||
export const selectCustomRepositories = (state: RootState) => {
|
||||
return state.wizard.repositories.customRepositories;
|
||||
};
|
||||
|
|
@ -505,6 +520,12 @@ export const wizardSlice = createSlice({
|
|||
state.fileSystem.partitions[partitionIndex].min_size = min_size;
|
||||
}
|
||||
},
|
||||
changeUseLatest: (state, action: PayloadAction<boolean>) => {
|
||||
state.snapshotting.useLatest = action.payload;
|
||||
},
|
||||
changeSnapshotDate: (state, action: PayloadAction<string>) => {
|
||||
state.snapshotting.snapshotDate = action.payload;
|
||||
},
|
||||
changeCustomRepositories: (
|
||||
state,
|
||||
action: PayloadAction<CustomRepository[]>
|
||||
|
|
@ -624,6 +645,8 @@ export const {
|
|||
changePartitionUnit,
|
||||
changePartitionMinSize,
|
||||
changePartitionOrder,
|
||||
changeUseLatest,
|
||||
changeSnapshotDate,
|
||||
changeCustomRepositories,
|
||||
changePayloadRepositories,
|
||||
addRecommendedRepository,
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ describe('Step Compliance', () => {
|
|||
})
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
await clickNext(); // skip RepositorySnapshot
|
||||
await clickNext(); // skip Repositories
|
||||
|
||||
// check that there are no Packages contained when selecting the "None" profile option
|
||||
|
|
@ -172,6 +173,7 @@ describe('Step Compliance', () => {
|
|||
await screen.findByRole('heading', { name: /File system configuration/i });
|
||||
await screen.findByText(/tmp/i);
|
||||
|
||||
await clickNext(); // skip RepositorySnapshots
|
||||
await clickNext(); // skip Repositories
|
||||
|
||||
// check that the Packages contains correct packages
|
||||
|
|
|
|||
|
|
@ -155,6 +155,8 @@ describe('Step Packages', () => {
|
|||
await clickNext();
|
||||
// skip OpenSCAP
|
||||
await clickNext();
|
||||
// skip snapshots
|
||||
await clickNext();
|
||||
// skip Repositories
|
||||
await clickNext();
|
||||
// skip fsc
|
||||
|
|
@ -431,6 +433,8 @@ describe('Step Custom repositories', () => {
|
|||
await clickNext();
|
||||
// skip fsc
|
||||
await clickNext();
|
||||
// skip snapshots
|
||||
await clickNext();
|
||||
};
|
||||
|
||||
test('selected repositories stored in and retrieved from form state', async () => {
|
||||
|
|
|
|||
|
|
@ -410,6 +410,7 @@ describe('Step Upload to AWS', () => {
|
|||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await enterBlueprintName();
|
||||
await clickNext();
|
||||
|
||||
|
|
@ -602,6 +603,7 @@ describe('Step Registration', () => {
|
|||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await enterBlueprintName();
|
||||
await clickNext();
|
||||
const review = await screen.findByTestId('review-registration');
|
||||
|
|
@ -648,6 +650,7 @@ describe('Step Registration', () => {
|
|||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await enterBlueprintName();
|
||||
await clickNext();
|
||||
const review = await screen.findByTestId('review-registration');
|
||||
|
|
@ -695,6 +698,7 @@ describe('Step Registration', () => {
|
|||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await enterBlueprintName();
|
||||
await clickNext();
|
||||
const review = await screen.findByTestId('review-registration');
|
||||
|
|
@ -725,6 +729,7 @@ describe('Step Registration', () => {
|
|||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await enterBlueprintName();
|
||||
await clickNext();
|
||||
await screen.findByText('Register the system later');
|
||||
|
|
@ -858,6 +863,8 @@ describe('Step Details', () => {
|
|||
await clickNext();
|
||||
// skip fsc
|
||||
await clickNext();
|
||||
// skip snapshots
|
||||
await clickNext();
|
||||
};
|
||||
|
||||
test('image name invalid for more than 63 chars', async () => {
|
||||
|
|
@ -932,6 +939,8 @@ describe('Step Review', () => {
|
|||
await clickNext();
|
||||
// skip OpenScap
|
||||
await clickNext();
|
||||
// skip snpashotstep
|
||||
await clickNext();
|
||||
// skip repositories
|
||||
await clickNext();
|
||||
// skip packages
|
||||
|
|
@ -993,7 +1002,8 @@ describe('Step Review', () => {
|
|||
|
||||
// skip Oscap
|
||||
await clickNext();
|
||||
|
||||
// skip snpashotstep
|
||||
await clickNext();
|
||||
// skip packages
|
||||
await clickNext();
|
||||
// skip repositories
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ const goToDetailsStep = async () => {
|
|||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
};
|
||||
|
||||
const enterBlueprintDescription = async () => {
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ const goToReviewStep = async () => {
|
|||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await enterBlueprintName();
|
||||
await clickNext();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ const clickToReview = async () => {
|
|||
await clickNext(); // skip Registration
|
||||
await clickNext(); // skip OSCAP
|
||||
await clickNext(); // skip FSC
|
||||
await clickNext(); // skip SnapshotRepositories
|
||||
await clickNext(); // skip Repositories
|
||||
await clickNext(); // skip Packages
|
||||
const nameInput = await screen.findByRole('textbox', {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ const selectNone = async () => {
|
|||
|
||||
const goToReviewStep = async () => {
|
||||
await clickNext(); // File system configuration
|
||||
await clickNext(); // Snapshot repositories
|
||||
await clickNext(); // Custom repositories
|
||||
await clickNext(); // Additional packages
|
||||
await clickNext(); // Details
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ const goToPackagesStep = async () => {
|
|||
await clickRegisterLater();
|
||||
await clickNext(); // OpenSCAP
|
||||
await clickNext(); // File System
|
||||
await clickNext(); // Snapshots
|
||||
await clickNext(); // Custom repositories
|
||||
await clickNext(); // Additional packages
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ const goToReviewStep = async () => {
|
|||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await clickNext();
|
||||
await enterBlueprintName();
|
||||
await clickNext();
|
||||
};
|
||||
|
|
@ -78,6 +79,7 @@ describe('registration request generated correctly', () => {
|
|||
const imageRequest: ImageRequest = {
|
||||
architecture: 'x86_64',
|
||||
image_type: 'image-installer',
|
||||
snapshot_date: '',
|
||||
upload_request: {
|
||||
options: {},
|
||||
type: 'aws.s3',
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ const goToRepositoriesStep = async () => {
|
|||
await clickRegisterLater();
|
||||
await clickNext(); // OpenSCAP
|
||||
await clickNext(); // File System
|
||||
await clickNext(); // Snapshot
|
||||
await clickNext(); // Custom repositories
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
import { screen } from '@testing-library/react';
|
||||
import { userEvent } from '@testing-library/user-event';
|
||||
|
||||
import { CREATE_BLUEPRINT } from '../../../../../constants';
|
||||
import {
|
||||
CreateBlueprintRequest,
|
||||
CustomRepository,
|
||||
Repository,
|
||||
} from '../../../../../store/imageBuilderApi';
|
||||
import { clickNext } from '../../../../testUtils';
|
||||
import {
|
||||
blueprintRequest,
|
||||
clickRegisterLater,
|
||||
enterBlueprintName,
|
||||
interceptBlueprintRequest,
|
||||
render,
|
||||
} from '../../wizardTestUtils';
|
||||
|
||||
jest.mock('@redhat-cloud-services/frontend-components/useChrome', () => ({
|
||||
useChrome: () => ({
|
||||
auth: {
|
||||
getUser: () => {
|
||||
return {
|
||||
identity: {
|
||||
internal: {
|
||||
org_id: 5,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
isBeta: () => true,
|
||||
isProd: () => true,
|
||||
getEnvironment: () => 'prod',
|
||||
}),
|
||||
}));
|
||||
|
||||
const goToSnapshotStep = async () => {
|
||||
const bareMetalCheckBox = await screen.findByRole('checkbox', {
|
||||
name: /bare metal installer checkbox/i,
|
||||
});
|
||||
await userEvent.click(bareMetalCheckBox);
|
||||
await clickNext(); // Registration
|
||||
await clickRegisterLater();
|
||||
await clickNext(); // OpenSCAP
|
||||
await clickNext(); // File System
|
||||
await clickNext();
|
||||
};
|
||||
|
||||
const goToReviewStep = async () => {
|
||||
await clickNext(); // Repositories step
|
||||
await clickNext(); // Additional packages
|
||||
await clickNext();
|
||||
await clickNext(); // Details
|
||||
await enterBlueprintName();
|
||||
await clickNext();
|
||||
};
|
||||
|
||||
const selectFirstRepository = async () => {
|
||||
await userEvent.click(
|
||||
await screen.findByRole('checkbox', { name: /select row 0/i })
|
||||
);
|
||||
};
|
||||
|
||||
const selectUseSnapshot = async () => {
|
||||
await userEvent.click(
|
||||
await screen.findByRole('radio', { name: /Use a snapshot/i })
|
||||
);
|
||||
};
|
||||
|
||||
const updateDatePickerWithValue = async (date: string) => {
|
||||
await userEvent.type(
|
||||
await screen.findByRole('textbox', { name: /Date picker/i }),
|
||||
date
|
||||
);
|
||||
};
|
||||
|
||||
const clickContentDropdown = async () => {
|
||||
await userEvent.click(
|
||||
(
|
||||
await screen.findAllByRole('button', { name: /Content/i })
|
||||
)[1]
|
||||
);
|
||||
};
|
||||
|
||||
const getSnapshotMethodElement = async () =>
|
||||
await screen.findByRole('button', { name: /Snapshot method/i });
|
||||
|
||||
describe('repository snapshot tab - ', () => {
|
||||
const expectedPayloadRepositories: Repository[] = [
|
||||
{
|
||||
baseurl: 'http://valid.link.to.repo.org/x86_64/',
|
||||
check_gpg: true,
|
||||
check_repo_gpg: false,
|
||||
gpgkey:
|
||||
'-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGN9300BEAC1FLODu0cL6saMMHa7yJY1JZUc+jQUI/HdECQrrsTaPXlcc7nM\nykYMMv6amPqbnhH/R5BW2Ano+OMse+PXtUr0NXU4OcvxbnnXkrVBVUf8mXI9DzLZ\njw8KoD+4/s0BuzO78zAJF5uhuyHMAK0ll9v0r92kK45Fas9iZTfRFcqFAzvgjScf\n5jeBnbRs5U3UTz9mtDy802mk357o1A8BD0qlu3kANDpjLbORGWdAj21A6sMJDYXy\nHS9FBNV54daNcr+weky2L9gaF2yFjeu2rSEHCSfkbWfpSiVUx/bDTj7XS6XDOuJT\nJqvGS8jHqjHAIFBirhCA4cY/jLKxWyMr5N6IbXpPAYgt8/YYz2aOYVvdyB8tZ1u1\nkVsMYSGcvTBexZCn1cDkbO6I+waIlsc0uxGqUGBKF83AVYCQqOkBjF1uNnu9qefE\nkEc9obr4JZsAgnisboU25ss5ZJddKlmFMKSi66g4S5ChLEPFq7MB06PhLFioaD3L\nEXza7XitoW5VBwr0BSVKAHMC0T2xbm70zY06a6gQRlvr9a10lPmv4Tptc7xgQReg\nu1TlFPbrkGJ0d8O6vHQRAd3zdsNaVr4gX0Tg7UYiqT9ZUkP7hOc8PYXQ28hHrHTB\nA63MTq0aiPlJ/ivTuX8M6+Bi25dIV6N6IOUi/NQKIYxgovJCDSdCAAM0fQARAQAB\ntCFMdWNhcyBHYXJmaWVsZCA8bHVjYXNAcmVkaGF0LmNvbT6JAlcEEwEIAEEWIQTO\nQZeiHnXqdjmfUURc6PeuecS2PAUCY33fTQIbAwUJA8JnAAULCQgHAgIiAgYVCgkI\nCwIEFgIDAQIeBwIXgAAKCRBc6PeuecS2PCk3D/9jW7xrBB/2MQFKd5l+mNMFyKwc\nL9M/M5RFI9GaQRo55CwnPb0nnxOJR1V5GzZ/YGii53H2ose65CfBOE2L/F/RvKF0\nH9S9MInixlahzzKtV3TpDoZGk5oZIHEMuPmPS4XaHggolrzExY0ib0mQuBBE/uEV\n/HlyHEunBKPhTkAe+6Q+2dl22SUuVfWr4Uzlp65+DkdN3M37WI1a3Suhnef3rOSM\nV6puUzWRR7qcYs5C2In87AcYPn92P5ur1y/C32r8Ftg3fRWnEzI9QfRG52ojNOLK\nyGQ8ZC9PGe0q7VFcF7ridT/uzRU+NVKldbJg+rvBnszb1MjNuR7rUQHyvGmbsUVQ\nRCsgdovkee3lP4gfZHzk2SSLVSo0+NJRNaM90EmPk14Pgi/yfRSDGBVvLBbEanYI\nv1ZtdIPRyKi+/IaMOu/l7nayM/8RzghdU+0f1FAif5qf9nXuI13P8fqcqfu67gNd\nkh0UUF1XyR5UHHEZQQDqCuKEkZJ/+27jYlsG1ZiLb1odlIWoR44RP6k5OJl0raZb\nyLXbAfpITsXiJJBpCam9P9+XR5VSfgkqp5hIa7J8piN3DoMpoExg4PPQr6PbLAJy\nOUCOnuB7yYVbj0wYuMXTuyrcBHh/UymQnS8AMpQoEkCLWS/A/Hze/pD23LgiBoLY\nXIn5A2EOAf7t2IMSlA==\n=OanT\n-----END PGP PUBLIC KEY BLOCK-----',
|
||||
rhsm: false,
|
||||
},
|
||||
];
|
||||
|
||||
const expectedCustomRepositories: CustomRepository[] = [
|
||||
{
|
||||
baseurl: ['http://valid.link.to.repo.org/x86_64/'],
|
||||
check_gpg: true,
|
||||
check_repo_gpg: false,
|
||||
gpgkey: [
|
||||
'-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGN9300BEAC1FLODu0cL6saMMHa7yJY1JZUc+jQUI/HdECQrrsTaPXlcc7nM\nykYMMv6amPqbnhH/R5BW2Ano+OMse+PXtUr0NXU4OcvxbnnXkrVBVUf8mXI9DzLZ\njw8KoD+4/s0BuzO78zAJF5uhuyHMAK0ll9v0r92kK45Fas9iZTfRFcqFAzvgjScf\n5jeBnbRs5U3UTz9mtDy802mk357o1A8BD0qlu3kANDpjLbORGWdAj21A6sMJDYXy\nHS9FBNV54daNcr+weky2L9gaF2yFjeu2rSEHCSfkbWfpSiVUx/bDTj7XS6XDOuJT\nJqvGS8jHqjHAIFBirhCA4cY/jLKxWyMr5N6IbXpPAYgt8/YYz2aOYVvdyB8tZ1u1\nkVsMYSGcvTBexZCn1cDkbO6I+waIlsc0uxGqUGBKF83AVYCQqOkBjF1uNnu9qefE\nkEc9obr4JZsAgnisboU25ss5ZJddKlmFMKSi66g4S5ChLEPFq7MB06PhLFioaD3L\nEXza7XitoW5VBwr0BSVKAHMC0T2xbm70zY06a6gQRlvr9a10lPmv4Tptc7xgQReg\nu1TlFPbrkGJ0d8O6vHQRAd3zdsNaVr4gX0Tg7UYiqT9ZUkP7hOc8PYXQ28hHrHTB\nA63MTq0aiPlJ/ivTuX8M6+Bi25dIV6N6IOUi/NQKIYxgovJCDSdCAAM0fQARAQAB\ntCFMdWNhcyBHYXJmaWVsZCA8bHVjYXNAcmVkaGF0LmNvbT6JAlcEEwEIAEEWIQTO\nQZeiHnXqdjmfUURc6PeuecS2PAUCY33fTQIbAwUJA8JnAAULCQgHAgIiAgYVCgkI\nCwIEFgIDAQIeBwIXgAAKCRBc6PeuecS2PCk3D/9jW7xrBB/2MQFKd5l+mNMFyKwc\nL9M/M5RFI9GaQRo55CwnPb0nnxOJR1V5GzZ/YGii53H2ose65CfBOE2L/F/RvKF0\nH9S9MInixlahzzKtV3TpDoZGk5oZIHEMuPmPS4XaHggolrzExY0ib0mQuBBE/uEV\n/HlyHEunBKPhTkAe+6Q+2dl22SUuVfWr4Uzlp65+DkdN3M37WI1a3Suhnef3rOSM\nV6puUzWRR7qcYs5C2In87AcYPn92P5ur1y/C32r8Ftg3fRWnEzI9QfRG52ojNOLK\nyGQ8ZC9PGe0q7VFcF7ridT/uzRU+NVKldbJg+rvBnszb1MjNuR7rUQHyvGmbsUVQ\nRCsgdovkee3lP4gfZHzk2SSLVSo0+NJRNaM90EmPk14Pgi/yfRSDGBVvLBbEanYI\nv1ZtdIPRyKi+/IaMOu/l7nayM/8RzghdU+0f1FAif5qf9nXuI13P8fqcqfu67gNd\nkh0UUF1XyR5UHHEZQQDqCuKEkZJ/+27jYlsG1ZiLb1odlIWoR44RP6k5OJl0raZb\nyLXbAfpITsXiJJBpCam9P9+XR5VSfgkqp5hIa7J8piN3DoMpoExg4PPQr6PbLAJy\nOUCOnuB7yYVbj0wYuMXTuyrcBHh/UymQnS8AMpQoEkCLWS/A/Hze/pD23LgiBoLY\nXIn5A2EOAf7t2IMSlA==\n=OanT\n-----END PGP PUBLIC KEY BLOCK-----',
|
||||
],
|
||||
id: 'ae39f556-6986-478a-95d1-f9c7e33d066c',
|
||||
name: '01-test-valid-repo',
|
||||
},
|
||||
];
|
||||
|
||||
test('select use a snapshot with 1 repo selected', async () => {
|
||||
await render();
|
||||
await goToSnapshotStep();
|
||||
await selectUseSnapshot();
|
||||
await updateDatePickerWithValue('04/22/2024');
|
||||
await clickNext(); // To repositories step
|
||||
await selectFirstRepository();
|
||||
await goToReviewStep();
|
||||
await clickContentDropdown();
|
||||
|
||||
const snapshotMethodElement = await getSnapshotMethodElement();
|
||||
// Check date was recorded correctly
|
||||
expect(snapshotMethodElement).toHaveTextContent('State as of 04/22/2024');
|
||||
// Check that the button is clickable (has 1 repo selected)
|
||||
expect(snapshotMethodElement).toHaveAttribute('aria-disabled', 'false');
|
||||
|
||||
// Check the date was passed correctly to the blueprint
|
||||
const receivedRequest = await interceptBlueprintRequest(CREATE_BLUEPRINT);
|
||||
blueprintRequest.image_requests[0].snapshot_date = '2024-04-22';
|
||||
|
||||
const expectedRequest: CreateBlueprintRequest = {
|
||||
...blueprintRequest,
|
||||
customizations: {
|
||||
custom_repositories: expectedCustomRepositories,
|
||||
payload_repositories: expectedPayloadRepositories,
|
||||
},
|
||||
};
|
||||
|
||||
expect(receivedRequest).toEqual(expectedRequest);
|
||||
});
|
||||
|
||||
test('select use a snapshot with no repos selected', async () => {
|
||||
await render();
|
||||
await goToSnapshotStep();
|
||||
await selectUseSnapshot();
|
||||
await updateDatePickerWithValue('04/22/2024');
|
||||
await clickNext(); // To repositories step
|
||||
await goToReviewStep();
|
||||
await clickContentDropdown();
|
||||
|
||||
const snapshotMethodElement = await getSnapshotMethodElement();
|
||||
// Check date was recorded correctly
|
||||
expect(snapshotMethodElement).toHaveTextContent('No repositories selected');
|
||||
// Check that the button is clickable (has 1 repo selected)
|
||||
expect(snapshotMethodElement).toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
});
|
||||
|
|
@ -43,6 +43,7 @@ const goToReview = async () => {
|
|||
await clickRegisterLater();
|
||||
await clickNext(); // OpenSCAP
|
||||
await clickNext(); // File system customization
|
||||
await clickNext(); // Snapshot repositories
|
||||
await clickNext(); // Custom repositories
|
||||
await clickNext(); // Additional packages
|
||||
await clickNext(); // Details
|
||||
|
|
@ -93,6 +94,7 @@ describe('aws image type request generated correctly', () => {
|
|||
const expectedImageRequest: ImageRequest = {
|
||||
architecture: 'x86_64',
|
||||
image_type: 'aws',
|
||||
snapshot_date: '',
|
||||
upload_request: {
|
||||
options: {
|
||||
share_with_sources: ['123'],
|
||||
|
|
@ -119,6 +121,7 @@ describe('aws image type request generated correctly', () => {
|
|||
const expectedImageRequest: ImageRequest = {
|
||||
architecture: 'x86_64',
|
||||
image_type: 'aws',
|
||||
snapshot_date: '',
|
||||
upload_request: {
|
||||
options: {
|
||||
share_with_accounts: ['123123123123'],
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ const goToReview = async () => {
|
|||
await clickRegisterLater();
|
||||
await clickNext(); // OpenSCAP
|
||||
await clickNext(); // File system customization
|
||||
await clickNext(); // Snapshot repositories
|
||||
await clickNext(); // Custom repositories
|
||||
await clickNext(); // Additional packages
|
||||
await clickNext(); // Details
|
||||
|
|
@ -120,6 +121,7 @@ describe('azure image type request generated correctly', () => {
|
|||
const expectedImageRequest: ImageRequest = {
|
||||
architecture: 'x86_64',
|
||||
image_type: 'azure',
|
||||
snapshot_date: '',
|
||||
upload_request: {
|
||||
options: {
|
||||
source_id: '666',
|
||||
|
|
@ -150,6 +152,7 @@ describe('azure image type request generated correctly', () => {
|
|||
const expectedImageRequest: ImageRequest = {
|
||||
architecture: 'x86_64',
|
||||
image_type: 'azure',
|
||||
snapshot_date: '',
|
||||
upload_request: {
|
||||
type: 'azure',
|
||||
options: {
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ const goToReview = async () => {
|
|||
await clickRegisterLater();
|
||||
await clickNext(); // OpenSCAP
|
||||
await clickNext(); // File system customization
|
||||
await clickNext(); // Snapshot repositories
|
||||
await clickNext(); // Custom repositories
|
||||
await clickNext(); // Additional packages
|
||||
await clickNext(); // Details
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ const goToReviewStep = async () => {
|
|||
await clickRegisterLater();
|
||||
await clickNext(); // OpenSCAP
|
||||
await clickNext(); // File system customization
|
||||
await clickNext(); // Snapshots
|
||||
await clickNext(); // Custom repositories
|
||||
await clickNext(); // Additional packages
|
||||
await clickNext(); // Details
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ const routes = [
|
|||
export const imageRequest: ImageRequest = {
|
||||
architecture: 'x86_64',
|
||||
image_type: 'image-installer',
|
||||
snapshot_date: '',
|
||||
upload_request: {
|
||||
options: {},
|
||||
type: 'aws.s3',
|
||||
|
|
|
|||
3
src/test/fixtures/blueprints.ts
vendored
3
src/test/fixtures/blueprints.ts
vendored
|
|
@ -144,6 +144,7 @@ export const mockBlueprintComposes: GetBlueprintComposesApiResponse = {
|
|||
{
|
||||
architecture: 'x86_64',
|
||||
image_type: 'aws',
|
||||
snapshot_date: '',
|
||||
upload_request: {
|
||||
type: 'aws',
|
||||
options: {
|
||||
|
|
@ -165,6 +166,7 @@ export const mockBlueprintComposes: GetBlueprintComposesApiResponse = {
|
|||
{
|
||||
architecture: 'x86_64',
|
||||
image_type: 'aws',
|
||||
snapshot_date: '',
|
||||
upload_request: {
|
||||
type: 'aws',
|
||||
options: {
|
||||
|
|
@ -186,6 +188,7 @@ export const mockBlueprintComposes: GetBlueprintComposesApiResponse = {
|
|||
{
|
||||
architecture: 'x86_64',
|
||||
image_type: 'gcp',
|
||||
snapshot_date: '',
|
||||
upload_request: {
|
||||
type: 'gcp',
|
||||
options: {
|
||||
|
|
|
|||
16
src/test/fixtures/features.ts
vendored
Normal file
16
src/test/fixtures/features.ts
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { ListFeaturesApiResponse } from '../../store/contentSourcesApi';
|
||||
|
||||
export const mockedFeatureResponse: ListFeaturesApiResponse = {
|
||||
admintasks: {
|
||||
enabled: true,
|
||||
accessible: false,
|
||||
},
|
||||
newrepositoryfiltering: {
|
||||
enabled: false,
|
||||
accessible: false,
|
||||
},
|
||||
snapshots: {
|
||||
enabled: true,
|
||||
accessible: true,
|
||||
},
|
||||
};
|
||||
74
src/test/fixtures/snapshots.ts
vendored
Normal file
74
src/test/fixtures/snapshots.ts
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
export const mockSourcesPackagesResults = {
|
||||
data: [
|
||||
{
|
||||
repository_uuid: '893dbe59-c473-4933-b23c-be6ae806e48d',
|
||||
is_after: false,
|
||||
match: {
|
||||
uuid: '8e1165f2-95f6-4046-a593-6858b34306a3',
|
||||
created_at: '2024-04-10T15:43:29.95512Z',
|
||||
repository_path:
|
||||
'00db8641/893dbe59-c473-4933-b23c-be6ae806e48d/d22d7840-ef14-4420-bccf-408de7949ff1',
|
||||
content_counts: {
|
||||
'rpm.advisory': 2,
|
||||
'rpm.package': 8,
|
||||
'rpm.packagecategory': 1,
|
||||
'rpm.packagegroup': 2,
|
||||
},
|
||||
added_counts: {
|
||||
'rpm.advisory': 2,
|
||||
'rpm.package': 8,
|
||||
'rpm.packagecategory': 1,
|
||||
'rpm.packagegroup': 2,
|
||||
},
|
||||
removed_counts: {},
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
repository_uuid: '50467940-7d8e-4eee-a3bd-f8a2556c406c',
|
||||
is_after: false,
|
||||
match: {
|
||||
uuid: 'cc0e882b-e988-4b9a-844a-54d47cb5a9a9',
|
||||
created_at: '2024-04-10T15:50:59.368233Z',
|
||||
repository_path:
|
||||
'00db8641/50467940-7d8e-4eee-a3bd-f8a2556c406c/a1f85ae7-22ac-4e28-8ae3-f8cb249fa5b7',
|
||||
content_counts: {
|
||||
'rpm.advisory': 2,
|
||||
'rpm.package': 8,
|
||||
},
|
||||
added_counts: {
|
||||
'rpm.advisory': 2,
|
||||
'rpm.package': 8,
|
||||
},
|
||||
removed_counts: {},
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
{
|
||||
repository_uuid: 'b805848d-1918-4861-9f63-c72f83cbd018',
|
||||
is_after: false,
|
||||
match: {
|
||||
uuid: 'e65b258d-d571-4411-8955-0214fd396aa0',
|
||||
created_at: '2024-04-17T00:42:07.045744Z',
|
||||
repository_path:
|
||||
'00db8641/b805848d-1918-4861-9f63-c72f83cbd018/78335df8-d5e6-4d65-a32f-80a09e6fc17d',
|
||||
content_counts: {
|
||||
'rpm.advisory': 4565,
|
||||
'rpm.package': 20851,
|
||||
'rpm.packagecategory': 1,
|
||||
'rpm.packageenvironment': 1,
|
||||
'rpm.packagegroup': 21,
|
||||
},
|
||||
added_counts: {
|
||||
'rpm.advisory': 4565,
|
||||
'rpm.package': 20851,
|
||||
'rpm.packagecategory': 1,
|
||||
'rpm.packageenvironment': 1,
|
||||
'rpm.packagegroup': 21,
|
||||
},
|
||||
removed_counts: {},
|
||||
url: '',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
import 'whatwg-fetch';
|
||||
//Needed for correct jest extends types
|
||||
import '@testing-library/jest-dom';
|
||||
import failOnConsole from 'jest-fail-on-console';
|
||||
|
||||
import { server } from './mocks/server';
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
mockCloneStatus,
|
||||
mockStatus,
|
||||
} from '../fixtures/composes';
|
||||
import { mockedFeatureResponse } from '../fixtures/features';
|
||||
import {
|
||||
distributionOscapProfiles,
|
||||
oscapCustomizations,
|
||||
|
|
@ -60,6 +61,15 @@ export const handlers = [
|
|||
const { search } = await req.json();
|
||||
return res(ctx.status(200), ctx.json(mockSourcesPackagesResults(search)));
|
||||
}),
|
||||
rest.get(`${CONTENT_SOURCES_API}/features/`, async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(mockedFeatureResponse));
|
||||
}),
|
||||
rest.post(
|
||||
`${CONTENT_SOURCES_API}/snapshots/for_date/`,
|
||||
async (req, res, ctx) => {
|
||||
return res(ctx.status(200), ctx.json(mockSourcesPackagesResults));
|
||||
}
|
||||
),
|
||||
rest.get(`${IMAGE_BUILDER_API}/packages`, (req, res, ctx) => {
|
||||
const search = req.url.searchParams.get('search');
|
||||
return res(ctx.status(200), ctx.json(mockPackagesResults(search)));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue