V2Wizard: Add repository recommendations
This PR is a part of Proactive Assistance - Repository recommendations. New toggle group was added which allows user to see results from non-added repositories in a case that searched term didn't return any results from added custom and distribution repos.
This commit is contained in:
parent
39cb4336b4
commit
bfd7420925
2 changed files with 311 additions and 95 deletions
|
|
@ -1,27 +1,37 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import {
|
||||
Alert,
|
||||
Bullseye,
|
||||
Button,
|
||||
EmptyState,
|
||||
EmptyStateBody,
|
||||
EmptyStateFooter,
|
||||
EmptyStateHeader,
|
||||
EmptyStateIcon,
|
||||
EmptyStateVariant,
|
||||
Icon,
|
||||
Pagination,
|
||||
PaginationVariant,
|
||||
Popover,
|
||||
SearchInput,
|
||||
Text,
|
||||
TextContent,
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
Toolbar,
|
||||
ToolbarContent,
|
||||
ToolbarItem,
|
||||
} from '@patternfly/react-core';
|
||||
import { SearchIcon } from '@patternfly/react-icons';
|
||||
import { HelpIcon, OptimizeIcon, SearchIcon } from '@patternfly/react-icons';
|
||||
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { RH_ICON_SIZE } from '../../../../constants';
|
||||
import { useSearchRpmMutation } from '../../../../store/contentSourcesApi';
|
||||
import {
|
||||
useListRepositoriesQuery,
|
||||
useSearchRpmMutation,
|
||||
} from '../../../../store/contentSourcesApi';
|
||||
import { useAppSelector } from '../../../../store/hooks';
|
||||
import {
|
||||
Package,
|
||||
|
|
@ -34,9 +44,12 @@ import {
|
|||
selectCustomRepositories,
|
||||
selectDistribution,
|
||||
addPackage,
|
||||
addRecommendedRepository,
|
||||
removeRecommendedRepository,
|
||||
} from '../../../../store/wizardSlice';
|
||||
import { useGetEnvironment } from '../../../../Utilities/useGetEnvironment';
|
||||
|
||||
type PackageRepository = 'distro' | 'custom' | '';
|
||||
type PackageRepository = 'distro' | 'custom' | 'recommended' | '';
|
||||
|
||||
export type IBPackageWithRepositoryInfo = {
|
||||
name: Package['name'];
|
||||
|
|
@ -45,82 +58,6 @@ export type IBPackageWithRepositoryInfo = {
|
|||
isRequiredByOpenScap: boolean;
|
||||
};
|
||||
|
||||
const EmptySearch = () => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Bullseye>
|
||||
<EmptyState variant={EmptyStateVariant.sm}>
|
||||
<EmptyStateHeader icon={<EmptyStateIcon icon={SearchIcon} />} />
|
||||
<EmptyStateBody>
|
||||
Search above to add additional
|
||||
<br />
|
||||
packages to your image.
|
||||
</EmptyStateBody>
|
||||
</EmptyState>
|
||||
</Bullseye>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
const NoResultsFound = () => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Bullseye>
|
||||
<EmptyState variant={EmptyStateVariant.sm}>
|
||||
<EmptyStateHeader titleText="No results found" headingLevel="h4" />
|
||||
<EmptyStateBody>Adjust your search and try again.</EmptyStateBody>
|
||||
</EmptyState>
|
||||
</Bullseye>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
const TooManyResults = () => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Bullseye>
|
||||
<EmptyState variant={EmptyStateVariant.sm}>
|
||||
<EmptyStateHeader
|
||||
icon={<EmptyStateIcon icon={SearchIcon} />}
|
||||
titleText="Too many results to display"
|
||||
headingLevel="h4"
|
||||
/>
|
||||
<EmptyStateBody>
|
||||
Please make the search more specific and try again.
|
||||
</EmptyStateBody>
|
||||
</EmptyState>
|
||||
</Bullseye>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
const TooManyResultsWithExactMatch = () => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Bullseye>
|
||||
<EmptyState variant={EmptyStateVariant.sm}>
|
||||
<EmptyStateHeader
|
||||
titleText="Too many results to display"
|
||||
headingLevel="h4"
|
||||
/>
|
||||
<EmptyStateBody>
|
||||
To see more results, please make the search more specific and try
|
||||
again.
|
||||
</EmptyStateBody>
|
||||
</EmptyState>
|
||||
</Bullseye>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
const Packages = () => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
|
|
@ -129,21 +66,33 @@ const Packages = () => {
|
|||
const customRepositories = useAppSelector(selectCustomRepositories);
|
||||
const packages = useAppSelector(selectPackages);
|
||||
|
||||
// select the correct version of EPEL repository
|
||||
// the urls are copied over from the content service
|
||||
const epelRepoUrlByDistribution = distribution.startsWith('rhel-8')
|
||||
? 'https://dl.fedoraproject.org/pub/epel/8/Everything/x86_64/'
|
||||
: 'https://dl.fedoraproject.org/pub/epel/9/Everything/x86_64/';
|
||||
|
||||
const { data: epelRepo, isSuccess: isSuccessEpelRepo } =
|
||||
useListRepositoriesQuery({
|
||||
url: epelRepoUrlByDistribution,
|
||||
});
|
||||
|
||||
const [perPage, setPerPage] = useState(10);
|
||||
const [page, setPage] = useState(1);
|
||||
const [toggleSelected, setToggleSelected] = useState('toggle-available');
|
||||
|
||||
/*FOLLOW UP
|
||||
const [toggleSourceRepos, setToggleSourceRepos] = useState(
|
||||
'toggle-included-repos'
|
||||
);
|
||||
*/
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState<string>('');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [
|
||||
searchRpms,
|
||||
{ data: dataCustomPackages, isSuccess: isSuccessCustomPackages },
|
||||
] = useSearchRpmMutation();
|
||||
const [
|
||||
searchRecommendedRpms,
|
||||
{ data: dataRecommendedPackages, isSuccess: isSuccessRecommendedPackages },
|
||||
] = useSearchRpmMutation();
|
||||
|
||||
const { data: dataDistroPackages, isSuccess: isSuccessDistroPackages } =
|
||||
useGetPackagesQuery(
|
||||
|
|
@ -172,12 +121,146 @@ const Packages = () => {
|
|||
});
|
||||
};
|
||||
|
||||
fetchCustomPackages();
|
||||
if (searchTerm.length > 1) {
|
||||
fetchCustomPackages();
|
||||
}
|
||||
}, [customRepositories, searchRpms, searchTerm]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchRecommendedPackages = async () => {
|
||||
await searchRecommendedRpms({
|
||||
apiContentUnitSearchRequest: {
|
||||
search: searchTerm,
|
||||
urls: [epelRepoUrlByDistribution],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (searchTerm.length > 1) {
|
||||
fetchRecommendedPackages();
|
||||
}
|
||||
}, [distribution, searchRecommendedRpms, searchTerm]);
|
||||
|
||||
const EmptySearch = () => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Bullseye>
|
||||
<EmptyState variant={EmptyStateVariant.sm}>
|
||||
<EmptyStateHeader icon={<EmptyStateIcon icon={SearchIcon} />} />
|
||||
<EmptyStateBody>
|
||||
Search above to add additional
|
||||
<br />
|
||||
packages to your image.
|
||||
</EmptyStateBody>
|
||||
</EmptyState>
|
||||
</Bullseye>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
const TooManyResults = () => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Bullseye>
|
||||
<EmptyState variant={EmptyStateVariant.sm}>
|
||||
<EmptyStateHeader
|
||||
icon={<EmptyStateIcon icon={SearchIcon} />}
|
||||
titleText="Too many results to display"
|
||||
headingLevel="h4"
|
||||
/>
|
||||
<EmptyStateBody>
|
||||
Please make the search more specific and try again.
|
||||
</EmptyStateBody>
|
||||
</EmptyState>
|
||||
</Bullseye>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
const TooManyResultsWithExactMatch = () => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Bullseye>
|
||||
<EmptyState variant={EmptyStateVariant.sm}>
|
||||
<EmptyStateHeader
|
||||
titleText="Too many results to display"
|
||||
headingLevel="h4"
|
||||
/>
|
||||
<EmptyStateBody>
|
||||
To see more results, please make the search more specific and
|
||||
try again.
|
||||
</EmptyStateBody>
|
||||
</EmptyState>
|
||||
</Bullseye>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
const NoResultsFound = () => {
|
||||
const { isBeta } = useGetEnvironment();
|
||||
return (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<Bullseye>
|
||||
<EmptyState variant={EmptyStateVariant.sm}>
|
||||
<EmptyStateHeader icon={<EmptyStateIcon icon={SearchIcon} />} />
|
||||
<EmptyStateHeader
|
||||
titleText="No results found"
|
||||
headingLevel="h4"
|
||||
/>
|
||||
<EmptyStateBody>
|
||||
Adjust your search and try again, or search from{' '}
|
||||
<Button
|
||||
variant="link"
|
||||
isInline
|
||||
component="a"
|
||||
target="_blank"
|
||||
href={
|
||||
isBeta() ? '/preview/settings/content' : '/settings/content'
|
||||
}
|
||||
>
|
||||
your repositories
|
||||
</Button>{' '}
|
||||
and{' '}
|
||||
<Button
|
||||
variant="link"
|
||||
isInline
|
||||
component="a"
|
||||
target="_blank"
|
||||
href={
|
||||
isBeta()
|
||||
? '/preview/settings/content/popular-repositories'
|
||||
: '/settings/content/popular-repositories'
|
||||
}
|
||||
>
|
||||
popular repositories
|
||||
</Button>
|
||||
</EmptyStateBody>
|
||||
<EmptyStateFooter>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => setToggleSourceRepos('toggle-other-repos')}
|
||||
>
|
||||
Search other repositories
|
||||
</Button>
|
||||
</EmptyStateFooter>
|
||||
</EmptyState>
|
||||
</Bullseye>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
const transformPackageData = () => {
|
||||
let transformedDistroData: IBPackageWithRepositoryInfo[] = [];
|
||||
let transformedCustomData: IBPackageWithRepositoryInfo[] = [];
|
||||
let transformedRecommendedData: IBPackageWithRepositoryInfo[] = [];
|
||||
|
||||
if (isSuccessDistroPackages) {
|
||||
transformedDistroData = dataDistroPackages.data.map((values) => ({
|
||||
|
|
@ -196,10 +279,28 @@ const Packages = () => {
|
|||
}));
|
||||
}
|
||||
|
||||
const combinedPackageData = transformedDistroData.concat(
|
||||
let combinedPackageData = transformedDistroData.concat(
|
||||
transformedCustomData
|
||||
);
|
||||
|
||||
if (
|
||||
searchTerm !== '' &&
|
||||
combinedPackageData.length === 0 &&
|
||||
isSuccessRecommendedPackages &&
|
||||
toggleSourceRepos === 'toggle-other-repos'
|
||||
) {
|
||||
transformedRecommendedData = dataRecommendedPackages!.map((values) => ({
|
||||
name: values.package_name!,
|
||||
summary: values.summary!,
|
||||
repository: 'recommended',
|
||||
isRequiredByOpenScap: false,
|
||||
}));
|
||||
|
||||
combinedPackageData = combinedPackageData.concat(
|
||||
transformedRecommendedData
|
||||
);
|
||||
}
|
||||
|
||||
if (toggleSelected === 'toggle-available') {
|
||||
return combinedPackageData;
|
||||
} else {
|
||||
|
|
@ -251,8 +352,22 @@ const Packages = () => {
|
|||
) => {
|
||||
if (isSelecting) {
|
||||
dispatch(addPackage(pkg));
|
||||
if (
|
||||
isSuccessEpelRepo &&
|
||||
epelRepo.data &&
|
||||
pkg.repository === 'recommended'
|
||||
) {
|
||||
dispatch(addRecommendedRepository(epelRepo.data[0]));
|
||||
}
|
||||
} else {
|
||||
dispatch(removePackage(pkg.name));
|
||||
if (
|
||||
isSuccessEpelRepo &&
|
||||
epelRepo.data &&
|
||||
packages.filter((pkg) => pkg.repository === 'recommended').length === 1
|
||||
) {
|
||||
dispatch(removeRecommendedRepository(epelRepo.data[0]));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -262,13 +377,11 @@ const Packages = () => {
|
|||
setToggleSelected(id);
|
||||
};
|
||||
|
||||
/*FOLLOW UP
|
||||
const handleRepoToggleClick = (event: React.MouseEvent) => {
|
||||
const id = event.currentTarget.id;
|
||||
setPage(1);
|
||||
setToggleSourceRepos(id);
|
||||
};
|
||||
*/
|
||||
|
||||
const handleSetPage = (_: React.MouseEvent, newPage: number) => {
|
||||
setPage(newPage);
|
||||
|
|
@ -365,25 +478,73 @@ const Packages = () => {
|
|||
/>
|
||||
</ToggleGroup>
|
||||
</ToolbarItem>
|
||||
{/*FOLLOW UP
|
||||
<ToolbarItem>
|
||||
{' '}
|
||||
<ToggleGroup>
|
||||
<ToggleGroupItem
|
||||
text="Included repos"
|
||||
text={
|
||||
<>
|
||||
Included repos{' '}
|
||||
<Popover
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text>
|
||||
View packages from the Red Hat repository and
|
||||
repositories you've selected.
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label="About included repositories"
|
||||
component="span"
|
||||
className="pf-u-p-0"
|
||||
size="sm"
|
||||
isInline
|
||||
>
|
||||
<HelpIcon />
|
||||
</Button>
|
||||
</Popover>
|
||||
</>
|
||||
}
|
||||
buttonId="toggle-included-repos"
|
||||
isSelected={toggleSourceRepos === 'toggle-included-repos'}
|
||||
onChange={handleRepoToggleClick}
|
||||
/>
|
||||
<ToggleGroupItem
|
||||
text="All repos"
|
||||
buttonId="toggle-all-repos"
|
||||
isSelected={toggleSourceRepos === 'toggle-all-repos'}
|
||||
text={
|
||||
<>
|
||||
Other repos{' '}
|
||||
<Popover
|
||||
bodyContent={
|
||||
<TextContent>
|
||||
<Text>
|
||||
View packages from popular repositories and your
|
||||
other repositories not included in the image.
|
||||
</Text>
|
||||
</TextContent>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
variant="plain"
|
||||
aria-label="About other repositories"
|
||||
component="span"
|
||||
className="pf-u-p-0"
|
||||
size="sm"
|
||||
isInline
|
||||
>
|
||||
<HelpIcon />
|
||||
</Button>
|
||||
</Popover>
|
||||
</>
|
||||
}
|
||||
buttonId="toggle-other-repos"
|
||||
isSelected={toggleSourceRepos === 'toggle-other-repos'}
|
||||
onChange={handleRepoToggleClick}
|
||||
/>
|
||||
</ToggleGroup>
|
||||
</ToolbarItem>
|
||||
*/}
|
||||
<ToolbarItem variant="pagination">
|
||||
<Pagination
|
||||
itemCount={transformedPackages.length}
|
||||
|
|
@ -410,7 +571,9 @@ const Packages = () => {
|
|||
{!searchTerm && toggleSelected === 'toggle-available' && (
|
||||
<EmptySearch />
|
||||
)}
|
||||
{searchTerm && transformedPackages.length === 0 && <NoResultsFound />}
|
||||
{searchTerm &&
|
||||
transformedPackages.length === 0 &&
|
||||
toggleSelected === 'toggle-available' && <NoResultsFound />}
|
||||
{searchTerm &&
|
||||
transformedPackages.length >= 100 &&
|
||||
handleExactMatch()}
|
||||
|
|
@ -444,11 +607,22 @@ const Packages = () => {
|
|||
</Td>
|
||||
<Td>Supported</Td>
|
||||
</>
|
||||
) : (
|
||||
) : pkg.repository === 'custom' ? (
|
||||
<>
|
||||
<Td>Third party repository</Td>
|
||||
<Td>Not supported</Td>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Td>
|
||||
<Icon status="warning">
|
||||
<OptimizeIcon />
|
||||
</Icon>{' '}
|
||||
EPEL {distribution === 'rhel-8' ? '8' : '9'} Everything
|
||||
x86_64
|
||||
</Td>
|
||||
<Td>Not supported</Td>
|
||||
</>
|
||||
)}
|
||||
</Tr>
|
||||
))}
|
||||
|
|
@ -462,6 +636,18 @@ const Packages = () => {
|
|||
onPerPageSelect={handlePerPageSelect}
|
||||
variant={PaginationVariant.bottom}
|
||||
/>
|
||||
{packages.some((pkg) => pkg.repository === 'recommended') && (
|
||||
<Alert
|
||||
variant="warning"
|
||||
title="Custom repositories will be added to your image"
|
||||
isInline
|
||||
>
|
||||
You have selected packages that belong to custom repositories. By
|
||||
continuing, you are acknowledging and consenting to adding the
|
||||
following custom repositories to your image: EPEL{' '}
|
||||
{distribution === 'rhel-8' ? '8' : '9'} Everything x86_64
|
||||
</Alert>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { ApiRepositoryResponseRead } from './contentSourcesApi';
|
||||
import {
|
||||
CustomRepository,
|
||||
DistributionProfileItem,
|
||||
|
|
@ -81,6 +82,7 @@ export type wizardState = {
|
|||
repositories: {
|
||||
customRepositories: CustomRepository[];
|
||||
payloadRepositories: Repository[];
|
||||
recommendedRepositories: ApiRepositoryResponseRead[];
|
||||
};
|
||||
packages: IBPackageWithRepositoryInfo[];
|
||||
details: {
|
||||
|
|
@ -136,6 +138,7 @@ const initialState: wizardState = {
|
|||
repositories: {
|
||||
customRepositories: [],
|
||||
payloadRepositories: [],
|
||||
recommendedRepositories: [],
|
||||
},
|
||||
packages: [],
|
||||
details: {
|
||||
|
|
@ -251,6 +254,10 @@ export const selectPayloadRepositories = (state: RootState) => {
|
|||
return state.wizard.repositories.payloadRepositories;
|
||||
};
|
||||
|
||||
export const selectRecommendedRepositories = (state: RootState) => {
|
||||
return state.wizard.repositories.recommendedRepositories;
|
||||
};
|
||||
|
||||
export const selectPackages = (state: RootState) => {
|
||||
return state.wizard.packages;
|
||||
};
|
||||
|
|
@ -450,6 +457,27 @@ export const wizardSlice = createSlice({
|
|||
changePayloadRepositories: (state, action: PayloadAction<Repository[]>) => {
|
||||
state.repositories.payloadRepositories = action.payload;
|
||||
},
|
||||
addRecommendedRepository: (
|
||||
state,
|
||||
action: PayloadAction<ApiRepositoryResponseRead>
|
||||
) => {
|
||||
if (
|
||||
!state.repositories.recommendedRepositories.some(
|
||||
(repo) => repo.url === action.payload.url
|
||||
)
|
||||
) {
|
||||
state.repositories.recommendedRepositories.push(action.payload);
|
||||
}
|
||||
},
|
||||
removeRecommendedRepository: (
|
||||
state,
|
||||
action: PayloadAction<ApiRepositoryResponseRead>
|
||||
) => {
|
||||
state.repositories.recommendedRepositories =
|
||||
state.repositories.recommendedRepositories.filter(
|
||||
(repo) => repo.url !== action.payload.url
|
||||
);
|
||||
},
|
||||
addPackage: (state, action: PayloadAction<IBPackageWithRepositoryInfo>) => {
|
||||
state.packages.push(action.payload);
|
||||
},
|
||||
|
|
@ -512,6 +540,8 @@ export const {
|
|||
changePartitionMinSize,
|
||||
changeCustomRepositories,
|
||||
changePayloadRepositories,
|
||||
addRecommendedRepository,
|
||||
removeRecommendedRepository,
|
||||
addPackage,
|
||||
removePackage,
|
||||
clearOscapPackages,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue