V2Wizard: Add components to the Repositories step skeleton

This adds needed components to the Repositories step skeleton.
This commit is contained in:
regexowl 2024-01-17 16:14:29 +01:00 committed by Lucas Garfield
parent 3c441425bb
commit 8a178f5b7d
6 changed files with 238 additions and 245 deletions

View file

@ -1,9 +1,5 @@
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import {
useFieldApi,
useFormApi,
} from '@data-driven-forms/react-form-renderer';
import { import {
Alert, Alert,
Button, Button,
@ -33,14 +29,35 @@ import {
import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import { RepositoryIcon } from '@patternfly/react-icons'; import { RepositoryIcon } from '@patternfly/react-icons';
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import PropTypes from 'prop-types';
import RepositoriesStatus from './RepositoriesStatus'; import RepositoriesStatus from './RepositoriesStatus';
import RepositoryUnavailable from './RepositoryUnavailable'; import RepositoryUnavailable from './RepositoryUnavailable';
import { useListRepositoriesQuery } from '../../../store/contentSourcesApi'; import {
import { releaseToVersion } from '../../../Utilities/releaseToVersion'; ApiRepositoryResponseRead,
import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; useListRepositoriesQuery,
} from '../../../../store/contentSourcesApi';
import { useAppDispatch, useAppSelector } from '../../../../store/hooks';
import { CustomRepository } from '../../../../store/imageBuilderApi';
import {
changeCustomRepositories,
selectArchitecture,
selectCustomRepositories,
selectDistribution,
} from '../../../../store/wizardSlice';
import { releaseToVersion } from '../../../../Utilities/releaseToVersion';
import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
type BulkSelectProps = {
selected: (string | undefined)[];
count: number | undefined;
filteredCount: number | undefined;
perPage: number;
handleSelectAll: Function;
handleSelectPage: Function;
handleDeselectAll: Function;
isDisabled: boolean;
};
const BulkSelect = ({ const BulkSelect = ({
selected, selected,
@ -51,7 +68,7 @@ const BulkSelect = ({
handleSelectPage, handleSelectPage,
handleDeselectAll, handleDeselectAll,
isDisabled, isDisabled,
}) => { }: BulkSelectProps) => {
const [dropdownIsOpen, setDropdownIsOpen] = useState(false); const [dropdownIsOpen, setDropdownIsOpen] = useState(false);
const numSelected = selected.length; const numSelected = selected.length;
@ -63,14 +80,17 @@ const BulkSelect = ({
const items = [ const items = [
<DropdownItem <DropdownItem
key="none" key="none"
onClick={handleDeselectAll} onClick={() => handleDeselectAll()}
>{`Select none (0 items)`}</DropdownItem>, >{`Select none (0 items)`}</DropdownItem>,
<DropdownItem key="page" onClick={handleSelectPage}>{`Select page (${ <DropdownItem
perPage > filteredCount ? filteredCount : perPage key="page"
onClick={() => handleSelectPage()}
>{`Select page (${
perPage > filteredCount! ? filteredCount : perPage
} items)`}</DropdownItem>, } items)`}</DropdownItem>,
<DropdownItem <DropdownItem
key="all" key="all"
onClick={handleSelectAll} onClick={() => handleSelectAll()}
>{`Select all (${count} items)`}</DropdownItem>, >{`Select all (${count} items)`}</DropdownItem>,
]; ];
@ -107,28 +127,12 @@ const BulkSelect = ({
); );
}; };
// Utility function to convert from Content Sources to Image Builder payload repo API schema
const convertSchemaToIBPayloadRepo = (repo) => {
const imageBuilderRepo = {
baseurl: repo.url,
rhsm: false,
check_gpg: false,
};
if (repo.gpg_key) {
imageBuilderRepo.gpgkey = repo.gpg_key;
imageBuilderRepo.check_gpg = true;
imageBuilderRepo.check_repo_gpg = repo.metadata_verification;
}
return imageBuilderRepo;
};
// Utility function to convert from Content Sources to Image Builder custom repo API schema // Utility function to convert from Content Sources to Image Builder custom repo API schema
const convertSchemaToIBCustomRepo = (repo) => { const convertSchemaToIBCustomRepo = (repo: ApiRepositoryResponseRead) => {
const imageBuilderRepo = { const imageBuilderRepo: CustomRepository = {
id: repo.uuid, id: repo.uuid!,
name: repo.name, name: repo.name,
baseurl: [repo.url], baseurl: [repo.url!],
check_gpg: false, check_gpg: false,
}; };
if (repo.gpg_key) { if (repo.gpg_key) {
@ -140,74 +144,24 @@ const convertSchemaToIBCustomRepo = (repo) => {
return imageBuilderRepo; return imageBuilderRepo;
}; };
// Utility function to convert from Image Builder to Content Sources API schema const Repositories = () => {
const convertSchemaToContentSources = (repo) => { const dispatch = useAppDispatch();
const contentSourcesRepo = {
url: repo.baseurl,
rhsm: false,
};
if (repo.gpgkey) {
contentSourcesRepo.gpg_key = repo.gpgkey;
contentSourcesRepo.metadata_verification = repo.check_repo_gpg;
}
return contentSourcesRepo; const arch = useAppSelector((state) => selectArchitecture(state));
}; const distribution = useAppSelector((state) => selectDistribution(state));
const version = releaseToVersion(distribution);
const repositoriesList = useAppSelector((state) =>
selectCustomRepositories(state)
);
const Repositories = (props) => {
const initializeRepositories = (contentSourcesReposList) => {
// Convert list of repositories into an object where key is repo URL
const contentSourcesRepos = contentSourcesReposList.reduce(
(accumulator, currentValue) => {
accumulator[currentValue.url] = currentValue;
return accumulator;
},
{}
);
// Repositories in the form state can be present when 'Recreate image' is used
// to open the wizard that are not necessarily in content sources.
const formStateReposList =
getState()?.values?.['original-payload-repositories'];
const mergeRepositories = (contentSourcesRepos, formStateReposList) => {
const formStateRepos = {};
for (const repo of formStateReposList) {
formStateRepos[repo.baseurl] = convertSchemaToContentSources(repo);
formStateRepos[repo.baseurl].name = '';
}
// In case of duplicate repo urls, the repo from Content Sources overwrites the
// repo from the form state.
const mergedRepos = { ...formStateRepos, ...contentSourcesRepos };
return mergedRepos;
};
const repositories = formStateReposList
? mergeRepositories(contentSourcesRepos, formStateReposList)
: contentSourcesRepos;
return repositories;
};
const { getState, change } = useFormApi();
const { input } = useFieldApi(props);
const [filterValue, setFilterValue] = useState(''); const [filterValue, setFilterValue] = useState('');
const [perPage, setPerPage] = useState(10); const [perPage, setPerPage] = useState(10);
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [toggleSelected, setToggleSelected] = useState('toggle-group-all'); const [toggleSelected, setToggleSelected] = useState('toggle-group-all');
const [selected, setSelected] = useState( const [selected, setSelected] = useState(
getState()?.values?.['payload-repositories'] repositoriesList ? repositoriesList.flatMap((repo) => repo.baseurl) : []
? getState().values['payload-repositories'].map((repo) => repo.baseurl)
: []
); );
const arch = getState().values?.arch;
const release = getState().values?.release;
const version = releaseToVersion(release);
const firstRequest = useListRepositoriesQuery( const firstRequest = useListRepositoriesQuery(
{ {
availableForArch: arch, availableForArch: arch,
@ -245,81 +199,89 @@ const Repositories = (props) => {
const { data, isError, isFetching, isLoading, isSuccess, refetch } = const { data, isError, isFetching, isLoading, isSuccess, refetch } =
useMemo(() => { useMemo(() => {
if (firstRequest?.data?.meta?.count > 100) { if (firstRequest?.data?.meta?.count) {
return { ...followupRequest }; if (firstRequest?.data?.meta?.count > 100) {
return { ...followupRequest };
}
} }
return { ...firstRequest }; return { ...firstRequest };
}, [firstRequest, followupRequest]); }, [firstRequest, followupRequest]);
const repositories = useMemo(() => { const handleToggleClick = (event: React.MouseEvent) => {
return data ? initializeRepositories(data.data) : {};
}, [firstRequest.data, followupRequest.data]);
const handleToggleClick = (event) => {
const id = event.currentTarget.id; const id = event.currentTarget.id;
setPage(1); setPage(1);
setToggleSelected(id); setToggleSelected(id);
}; };
const isRepoSelected = (repoURL) => selected.includes(repoURL); const isRepoSelected = (repoURL: string | undefined) =>
selected.includes(repoURL);
const handlePerPageSelect = (event, newPerPage, newPage) => { const handlePerPageSelect = (
_: React.MouseEvent,
newPerPage: number,
newPage: number
) => {
setPerPage(newPerPage); setPerPage(newPerPage);
setPage(newPage); setPage(newPage);
}; };
const handleSetPage = (event, newPage) => { const handleSetPage = (_: React.MouseEvent, newPage: number) => {
setPage(newPage); setPage(newPage);
}; };
// filter displayed selected packages // filter displayed selected packages
const handleFilterRepositories = (_, value) => { const handleFilterRepositories = (
event: React.FormEvent<HTMLInputElement>,
value: string
) => {
setPage(1); setPage(1);
setFilterValue(value); setFilterValue(value);
}; };
const filteredRepositoryURLs = useMemo(() => { const filteredRepositoryURLs = useMemo(() => {
const repoUrls = Object.values(repositories).filter((repo) => if (!data || !data.data) {
repo.name.toLowerCase().includes(filterValue.toLowerCase()) return [];
}
const repoUrls = data.data.filter((repo) =>
repo.name?.toLowerCase().includes(filterValue.toLowerCase())
); );
if (toggleSelected === 'toggle-group-all') { if (toggleSelected === 'toggle-group-all') {
return repoUrls.map((repo) => repo.url); return repoUrls.map((repo: ApiRepositoryResponseRead) => repo.url);
} else if (toggleSelected === 'toggle-group-selected') { } else if (toggleSelected === 'toggle-group-selected') {
return repoUrls return repoUrls
.filter((repo) => isRepoSelected(repo.url)) .filter((repo: ApiRepositoryResponseRead) => isRepoSelected(repo.url!))
.map((repo) => repo.url); .map((repo: ApiRepositoryResponseRead) => repo.url);
} }
}, [filterValue, repositories, toggleSelected]); }, [filterValue, data, toggleSelected]);
const handleClearFilter = () => { const handleClearFilter = () => {
setFilterValue(''); setFilterValue('');
}; };
const updateFormState = (selectedRepoURLs) => { const updateFormState = (selectedRepoURLs: (string | undefined)[]) => {
// repositories is stored as an object with repoURLs as keys // repositories is stored as an object with repoURLs as keys
const selectedRepos = []; const selectedRepos = [];
for (const repoURL of selectedRepoURLs) { for (const repoURL of selectedRepoURLs) {
selectedRepos.push(repositories[repoURL]); selectedRepos.push(data?.data?.find((repo) => repo.url === repoURL));
} }
const payloadRepositories = selectedRepos.map((repo) =>
convertSchemaToIBPayloadRepo(repo)
);
const customRepositories = selectedRepos.map((repo) => const customRepositories = selectedRepos.map((repo) =>
convertSchemaToIBCustomRepo(repo) convertSchemaToIBCustomRepo(repo!)
); );
input.onChange(payloadRepositories); dispatch(changeCustomRepositories(customRepositories));
change('custom-repositories', customRepositories);
}; };
const updateSelected = (selectedRepos) => { const updateSelected = (selectedRepos: (string | undefined)[]) => {
setSelected(selectedRepos); setSelected(selectedRepos);
updateFormState(selectedRepos); updateFormState(selectedRepos);
}; };
const handleSelect = (repoURL, rowIndex, isSelecting) => { const handleSelect = (
repoURL: string | undefined,
_: number,
isSelecting: boolean
) => {
if (isSelecting === true) { if (isSelecting === true) {
updateSelected([...selected, repoURL]); updateSelected([...selected, repoURL]);
} else if (isSelecting === false) { } else if (isSelecting === false) {
@ -330,36 +292,41 @@ const Repositories = (props) => {
}; };
const handleSelectAll = () => { const handleSelectAll = () => {
updateSelected(Object.keys(repositories)); if (data) {
updateSelected(data.data?.map((repo) => repo.url) || []);
}
}; };
const computeStart = () => perPage * (page - 1); const computeStart = () => perPage * (page - 1);
const computeEnd = () => perPage * page; const computeEnd = () => perPage * page;
const handleSelectPage = () => { const handleSelectPage = () => {
const pageRepos = filteredRepositoryURLs.slice( const pageRepos =
computeStart(), filteredRepositoryURLs &&
computeEnd() filteredRepositoryURLs.slice(computeStart(), computeEnd());
);
// Filter to avoid adding duplicates // Filter to avoid adding duplicates
const newSelected = [ const newSelected = pageRepos && [
...pageRepos.filter((repoId) => !selected.includes(repoId)), ...pageRepos.filter((repoId) => !selected.includes(repoId)),
]; ];
updateSelected([...selected, ...newSelected]); updateSelected([...selected, ...newSelected!]);
}; };
const handleDeselectAll = () => { const handleDeselectAll = () => {
updateSelected([]); updateSelected([]);
}; };
const getRepoNameByUrl = (url: string) => {
return data!.data?.find((repo) => repo.url === url)?.name;
};
return ( return (
(isError && <Error />) || (isError && <Error />) ||
(isLoading && <Loading />) || (isLoading && <Loading />) ||
(isSuccess && ( (isSuccess && (
<> <>
{Object.values(repositories).length === 0 ? ( {data.data?.length === 0 ? (
<Empty refetch={refetch} isFetching={isFetching} /> <Empty refetch={refetch} isFetching={isFetching} />
) : ( ) : (
<> <>
@ -368,8 +335,8 @@ const Repositories = (props) => {
<ToolbarItem variant="bulk-select"> <ToolbarItem variant="bulk-select">
<BulkSelect <BulkSelect
selected={selected} selected={selected}
count={Object.values(repositories).length} count={data.data?.length}
filteredCount={filteredRepositoryURLs.length} filteredCount={filteredRepositoryURLs?.length}
perPage={perPage} perPage={perPage}
handleSelectAll={handleSelectAll} handleSelectAll={handleSelectAll}
handleSelectPage={handleSelectPage} handleSelectPage={handleSelectPage}
@ -415,7 +382,9 @@ const Repositories = (props) => {
</ToolbarItem> </ToolbarItem>
<ToolbarItem variant="pagination"> <ToolbarItem variant="pagination">
<Pagination <Pagination
itemCount={filteredRepositoryURLs.length} itemCount={
filteredRepositoryURLs && filteredRepositoryURLs.length
}
perPage={perPage} perPage={perPage}
page={page} page={page}
onSetPage={handleSetPage} onSetPage={handleSetPage}
@ -441,75 +410,89 @@ const Repositories = (props) => {
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
{filteredRepositoryURLs {filteredRepositoryURLs &&
.sort((a, b) => { filteredRepositoryURLs
if (repositories[a].name < repositories[b].name) { .sort((a, b) => {
return -1; if (getRepoNameByUrl(a!)! < getRepoNameByUrl(b!)!) {
} else if ( return -1;
repositories[b].name < repositories[a].name } else if (
) { getRepoNameByUrl(b!)! < getRepoNameByUrl(a!)!
return 1; ) {
} else { return 1;
return 0; } else {
} return 0;
}) }
.slice(computeStart(), computeEnd()) })
.map((repoURL, rowIndex) => { .slice(computeStart(), computeEnd())
const repo = repositories[repoURL]; .map((repoURL, rowIndex) => {
const repoExists = repo.name ? true : false; const repo = data?.data?.find(
return ( (repo) => repo.url === repoURL
<Tr key={repo.url}> );
<Td
select={{ if (!repo) {
isSelected: isRepoSelected(repo.url), return <></>;
rowIndex: rowIndex, }
onSelect: (event, isSelecting) =>
handleSelect(repo.url, rowIndex, isSelecting), const repoExists = repo.name ? true : false;
isDisabled: return (
isFetching || repo.status !== 'Valid', <Tr key={repo.url}>
}} <Td
/> select={{
<Td dataLabel={'Name'}> isSelected: isRepoSelected(repo.url),
{repoExists rowIndex: rowIndex,
? repo.name onSelect: (event, isSelecting) =>
: 'Repository with the following url is no longer available:'} handleSelect(
<br /> repo.url,
<Button rowIndex,
component="a" isSelecting
target="_blank" ),
variant="link" isDisabled:
icon={<ExternalLinkAltIcon />} isFetching || repo.status !== 'Valid',
iconPosition="right" }}
isInline
href={repo.url}
>
{repo.url}
</Button>
</Td>
<Td dataLabel={'Architecture'}>
{repoExists ? repo.distribution_arch : '-'}
</Td>
<Td dataLabel={'Version'}>
{repoExists ? repo.distribution_versions : '-'}
</Td>
<Td dataLabel={'Packages'}>
{repoExists ? repo.package_count : '-'}
</Td>
<Td dataLabel={'Status'}>
<RepositoriesStatus
repoStatus={
repoExists ? repo.status : 'Unavailable'
}
repoUrl={repo.url}
repoIntrospections={
repo.last_introspection_time
}
repoFailCount={repo.failed_introspections_count}
/> />
</Td> <Td dataLabel={'Name'}>
</Tr> {repoExists
); ? repo.name
})} : 'Repository with the following url is no longer available:'}
<br />
<Button
component="a"
target="_blank"
variant="link"
icon={<ExternalLinkAltIcon />}
iconPosition="right"
isInline
href={repo.url}
>
{repo.url}
</Button>
</Td>
<Td dataLabel={'Architecture'}>
{repoExists ? repo.distribution_arch : '-'}
</Td>
<Td dataLabel={'Version'}>
{repoExists ? repo.distribution_versions : '-'}
</Td>
<Td dataLabel={'Packages'}>
{repoExists ? repo.package_count : '-'}
</Td>
<Td dataLabel={'Status'}>
<RepositoriesStatus
repoStatus={
repoExists ? repo.status : 'Unavailable'
}
repoUrl={repo.url}
repoIntrospections={
repo.last_introspection_time
}
repoFailCount={
repo.failed_introspections_count
}
/>
</Td>
</Tr>
);
})}
</Tbody> </Tbody>
</Table> </Table>
</PanelMain> </PanelMain>
@ -541,7 +524,12 @@ const Loading = () => {
); );
}; };
const Empty = ({ isFetching, refetch }) => { type EmptyProps = {
isFetching: boolean;
refetch: Function;
};
const Empty = ({ isFetching, refetch }: EmptyProps) => {
const { isBeta } = useGetEnvironment(); const { isBeta } = useGetEnvironment();
return ( return (
<EmptyState variant={EmptyStateVariant.lg} data-testid="empty-state"> <EmptyState variant={EmptyStateVariant.lg} data-testid="empty-state">
@ -577,20 +565,4 @@ const Empty = ({ isFetching, refetch }) => {
); );
}; };
BulkSelect.propTypes = {
selected: PropTypes.array,
count: PropTypes.number,
filteredCount: PropTypes.number,
perPage: PropTypes.number,
handleSelectAll: PropTypes.func,
handleSelectPage: PropTypes.func,
handleDeselectAll: PropTypes.func,
isDisabled: PropTypes.bool,
};
Empty.propTypes = {
isFetching: PropTypes.bool,
refetch: PropTypes.func,
};
export default Repositories; export default Repositories;

View file

@ -17,12 +17,12 @@ import {
InProgressIcon, InProgressIcon,
} from '@patternfly/react-icons'; } from '@patternfly/react-icons';
import { ApiRepositoryResponse } from '../../../store/contentSourcesApi'; import { ApiRepositoryResponse } from '../../../../store/contentSourcesApi';
import { import {
convertStringToDate, convertStringToDate,
timestampToDisplayString, timestampToDisplayString,
} from '../../../Utilities/time'; } from '../../../../Utilities/time';
import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
const getLastIntrospection = ( const getLastIntrospection = (
repoIntrospections: RepositoryStatusProps['repoIntrospections'] repoIntrospections: RepositoryStatusProps['repoIntrospections']

View file

@ -3,8 +3,8 @@ import React from 'react';
import { Alert, Button } from '@patternfly/react-core'; import { Alert, Button } from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import { useCheckRepositoriesAvailability } from '../../../Utilities/checkRepositoriesAvailability'; import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
import { useGetEnvironment } from '../../../Utilities/useGetEnvironment'; import { useCheckRepositoriesAvailability } from '../../utilities/checkRepositoriesAvailability';
const RepositoryUnavailable = () => { const RepositoryUnavailable = () => {
const { isBeta } = useGetEnvironment(); const { isBeta } = useGetEnvironment();

View file

@ -3,6 +3,8 @@ import React from 'react';
import { Button, Form, Text, Title } from '@patternfly/react-core'; import { Button, Form, Text, Title } from '@patternfly/react-core';
import { ExternalLinkAltIcon } from '@patternfly/react-icons'; import { ExternalLinkAltIcon } from '@patternfly/react-icons';
import Repositories from './Repositories';
import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment'; import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
const ManageRepositoriesButton = () => { const ManageRepositoriesButton = () => {
@ -32,6 +34,7 @@ const RepositoriesStep = () => {
<br /> <br />
<ManageRepositoriesButton /> <ManageRepositoriesButton />
</Text> </Text>
<Repositories />
</Form> </Form>
); );
}; };

View file

@ -1,24 +1,23 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useFormApi } from '@data-driven-forms/react-form-renderer'; import { useListRepositoriesQuery } from '../../../store/contentSourcesApi';
import { useAppSelector } from '../../../store/hooks';
import { releaseToVersion } from './releaseToVersion'; import {
selectArchitecture,
import { useListRepositoriesQuery } from '../store/contentSourcesApi'; selectDistribution,
selectCustomRepositories,
} from '../../../store/wizardSlice';
import { releaseToVersion } from '../../../Utilities/releaseToVersion.js';
/** /**
* This checks the list of the payload repositories against a list of repos freshly * This checks the list of the custom repositories against a list of repos freshly
* fetched from content source API and returns true whether there are some * fetched from content source API and returns true whether there are some
* repositories that are no longer available in the Repositories service. * repositories that are no longer available in the Repositories service.
*
* (The payload repositories are comming from the useFormApi hook).
*/ */
export const useCheckRepositoriesAvailability = () => { export const useCheckRepositoriesAvailability = () => {
const { getState } = useFormApi(); const arch = useAppSelector((state) => selectArchitecture(state));
const distribution = useAppSelector((state) => selectDistribution(state));
const arch = getState().values?.arch; const version = releaseToVersion(distribution);
const release = getState().values?.release;
const version = releaseToVersion(release);
// There needs to be two requests because the default limit for the // There needs to be two requests because the default limit for the
// useListRepositoriesQuery is a 100 elements, and a first request is // useListRepositoriesQuery is a 100 elements, and a first request is
@ -50,22 +49,26 @@ export const useCheckRepositoriesAvailability = () => {
); );
const { data: freshRepos, isSuccess } = useMemo(() => { const { data: freshRepos, isSuccess } = useMemo(() => {
if (firstRequest?.data?.meta?.count > 100) { if (firstRequest?.data?.meta?.count) {
return { ...followupRequest }; if (firstRequest?.data?.meta?.count > 100) {
return { ...followupRequest };
}
} }
return { ...firstRequest }; return { ...firstRequest };
}, [firstRequest, followupRequest]); }, [firstRequest, followupRequest]);
const payloadRepositories = getState()?.values?.['payload-repositories']; const customRepositories = useAppSelector((state) =>
// payloadRepositories existing === we came here from Recreate selectCustomRepositories(state)
if (isSuccess && payloadRepositories) { );
// customRepositories existing === we came here from Recreate
if (isSuccess && customRepositories) {
// Transform the fresh repos array into a Set to access its elements in O(1) // Transform the fresh repos array into a Set to access its elements in O(1)
// complexity later in the for loop. // complexity later in the for loop.
const freshReposUrls = new Set( const freshReposUrls = new Set(
freshRepos.data.map((freshRepo) => freshRepo.url) freshRepos.data?.map((freshRepo) => freshRepo.url)
); );
for (const payloadRepo of payloadRepositories) { for (const customRepo of customRepositories) {
if (!freshReposUrls.has(payloadRepo.baseurl)) { if (customRepo.baseurl && !freshReposUrls.has(customRepo.baseurl[0])) {
return true; return true;
} }
} }

View file

@ -1,6 +1,7 @@
import { PayloadAction, createSlice } from '@reduxjs/toolkit'; import { PayloadAction, createSlice } from '@reduxjs/toolkit';
import { import {
CustomRepository,
DistributionProfileItem, DistributionProfileItem,
Distributions, Distributions,
ImageRequest, ImageRequest,
@ -45,6 +46,9 @@ type wizardState = {
openScap: { openScap: {
profile: DistributionProfileItem | undefined; profile: DistributionProfileItem | undefined;
}; };
repositories: {
customRepositories: CustomRepository[];
};
details: { details: {
blueprintName: string; blueprintName: string;
blueprintDescription: string; blueprintDescription: string;
@ -76,6 +80,9 @@ const initialState: wizardState = {
openScap: { openScap: {
profile: undefined, profile: undefined,
}, },
repositories: {
customRepositories: [],
},
details: { details: {
blueprintName: '', blueprintName: '',
blueprintDescription: '', blueprintDescription: '',
@ -140,6 +147,10 @@ export const selectProfile = (state: RootState) => {
return state.wizard.openScap.profile; return state.wizard.openScap.profile;
}; };
export const selectCustomRepositories = (state: RootState) => {
return state.wizard.repositories.customRepositories;
};
export const selectBlueprintName = (state: RootState) => { export const selectBlueprintName = (state: RootState) => {
return state.wizard.details.blueprintName; return state.wizard.details.blueprintName;
}; };
@ -221,18 +232,21 @@ export const wizardSlice = createSlice({
) => { ) => {
state.registration.activationKey = action.payload; state.registration.activationKey = action.payload;
}, },
changeOscapProfile: ( changeOscapProfile: (
state, state,
action: PayloadAction<DistributionProfileItem | undefined> action: PayloadAction<DistributionProfileItem | undefined>
) => { ) => {
state.openScap.profile = action.payload; state.openScap.profile = action.payload;
}, },
changeCustomRepositories: (
state,
action: PayloadAction<CustomRepository[]>
) => {
state.repositories.customRepositories = action.payload;
},
changeBlueprintName: (state, action: PayloadAction<string>) => { changeBlueprintName: (state, action: PayloadAction<string>) => {
state.details.blueprintName = action.payload; state.details.blueprintName = action.payload;
}, },
changeBlueprintDescription: (state, action: PayloadAction<string>) => { changeBlueprintDescription: (state, action: PayloadAction<string>) => {
state.details.blueprintDescription = action.payload; state.details.blueprintDescription = action.payload;
}, },
@ -257,6 +271,7 @@ export const {
changeRegistrationType, changeRegistrationType,
changeActivationKey, changeActivationKey,
changeOscapProfile, changeOscapProfile,
changeCustomRepositories,
changeBlueprintName, changeBlueprintName,
changeBlueprintDescription, changeBlueprintDescription,
} = wizardSlice.actions; } = wizardSlice.actions;