debian-image-builder-frontend/src/Components/CreateImageWizard/steps/Packages/PackageRecommendations.tsx
2024-08-16 10:26:40 +02:00

256 lines
8.6 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import {
Alert,
Button,
ExpandableSection,
Flex,
FlexItem,
Icon,
Panel,
PanelMain,
PanelMainBody,
Popover,
Spinner,
Text,
TextContent,
TextVariants,
} from '@patternfly/react-core';
import { HelpIcon, OptimizeIcon } from '@patternfly/react-icons';
import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { useDispatch } from 'react-redux';
import PackageRecommendationDescription from './components/PackageRecommendationDescription';
import { RedHatRepository } from './Packages';
import { ContentOrigin } from '../../../../constants';
import { useListRepositoriesQuery } from '../../../../store/contentSourcesApi';
import { useAppSelector } from '../../../../store/hooks';
import { useRecommendPackageMutation } from '../../../../store/imageBuilderApi';
import {
addPackage,
selectArchitecture,
selectDistribution,
selectPackages,
} from '../../../../store/wizardSlice';
import { releaseToVersion } from '../../../../Utilities/releaseToVersion';
import useDebounce from '../../../../Utilities/useDebounce';
const PackageRecommendations = () => {
const { analytics } = useChrome();
const dispatch = useDispatch();
const arch = useAppSelector(selectArchitecture);
const distribution = useAppSelector(selectDistribution);
const version = releaseToVersion(distribution);
const undebouncedPackages = useAppSelector(selectPackages);
const packages = useDebounce(undebouncedPackages);
let distroRepoUrls: string[] = [];
const [isExpanded, setIsExpanded] = useState(true);
const { data: distroRepositories, isSuccess: isSuccessDistroRepositories } =
useListRepositoriesQuery({
availableForArch: arch,
availableForVersion: version,
contentType: 'rpm',
origin: ContentOrigin.REDHAT,
limit: 100,
offset: 0,
});
if (isSuccessDistroRepositories && distroRepositories.data) {
distroRepoUrls = distroRepositories.data.map((repo) => repo.url || '');
}
const [fetchRecommendedPackages, { data, isSuccess, isLoading, isError }] =
useRecommendPackageMutation();
useEffect(() => {
if (isExpanded && packages.length > 0) {
(async () => {
const response = await fetchRecommendedPackages({
recommendPackageRequest: {
packages: packages.map((pkg) => pkg.name),
recommendedPackages: 5,
},
});
if (response && response.data && response.data.packages.length > 0) {
analytics.track('recommendationsShown', {
shownRecommendations: response.data.packages,
selectedPackages: packages.map((pkg) => pkg.name),
});
}
})();
}
}, [fetchRecommendedPackages, packages, isExpanded]);
const addAllPackages = () => {
if (data?.packages?.length) {
data.packages.forEach((pkg) =>
dispatch(
addPackage({
name: pkg,
summary: 'Added from recommended packages',
repository: 'distro',
})
)
);
}
};
const addRecommendedPackage = (pkg: string) => {
dispatch(
addPackage({
name: pkg,
summary: 'Added from recommended packages',
repository: 'distro',
})
);
};
const isRecommendedPackageSelected = (recPkg: string) => {
const foundInPackages = packages?.some((pkg) => recPkg === pkg.name);
return foundInPackages;
};
return (
<Panel variant="bordered" className="panel-border">
<PanelMain>
<PanelMainBody>
<ExpandableSection
toggleContent={
<Flex>
<FlexItem>
<Icon>
<OptimizeIcon />
</Icon>{' '}
Recommended Red Hat packages{' '}
</FlexItem>
<FlexItem>
<TextContent>
<Text component={TextVariants.small}>
<em>Powered by RHEL Lightspeed</em>{' '}
<Popover
maxWidth="30rem"
// overrides the expandable behaviour to allow placing
// a popover there
onShow={() => setIsExpanded(false)}
onHide={() => setIsExpanded(false)}
bodyContent={
<TextContent>
<Text>
RHEL Lightspeed provides intelligent tools to
improve the productivity and efficiency of teams
using RHEL.
</Text>
</TextContent>
}
>
<HelpIcon />
</Popover>
</Text>
</TextContent>
</FlexItem>
</Flex>
}
onToggle={(_, bool) => setIsExpanded(bool)}
isExpanded={isExpanded}
>
{packages.length === 0 && (
<>Select packages to generate recommendations.</>
)}
{isLoading && <Spinner size="lg" />}
{isError && (
<Alert
title="Recommendations couldn't be fetched"
variant="danger"
isPlain
isInline
>
There was an error when fetching package recommendations. Try
again by changing your selected packages.
</Alert>
)}
{isSuccess && !data?.packages?.length && packages.length > 0 && (
<>No recommendations found for the set of selected packages</>
)}
{isSuccess && data && data?.packages && (
<>
<TextContent>
<Text>
Other users commonly add these packages with the ones you
selected.
</Text>
</TextContent>
<Table variant="compact">
<Thead>
<Tr>
<Th width={20}>Package name</Th>
<Th width={35}>Description</Th>
<Th width={25}>Package repository</Th>
<Th width={20}>
<Button
variant="link"
component="a"
onClick={() => addAllPackages()}
isInline
data-testid="add-all-recommendations-button"
>
Add all packages
</Button>
</Th>
</Tr>
</Thead>
<Tbody>
{data.packages.map((pkg) => (
<Tr key={pkg}>
<Td>{pkg}</Td>
<Td>
{distroRepoUrls && (
<PackageRecommendationDescription
pkg={pkg}
urls={distroRepoUrls}
/>
)}
</Td>
<Td>
<RedHatRepository />
</Td>
<Td>
<Button
variant="link"
component="a"
onClick={() => {
analytics.track('recommendedPackageAdded', {
packageName: pkg,
selectedPackages: packages.map(
(pkg) => pkg.name
),
shownRecommendations: data.packages,
});
addRecommendedPackage(pkg);
}}
isInline
isDisabled={isRecommendedPackageSelected(pkg)}
data-testid="add-recommendation-button"
>
Add package
</Button>
</Td>
</Tr>
))}
</Tbody>
</Table>
</>
)}
</ExpandableSection>
</PanelMainBody>
</PanelMain>
</Panel>
);
};
export default PackageRecommendations;