Wizard: Add "refine search" warning to Packages step
Fixes #913. This adds status bars to the `DualListSelectorPane` on the Packages step. The status bar indicates how many packages were found and how many of those have been selected. Warning for too many returned results was also added. When an exact match is found during a search with over 100 results, it is shown together with the warning.
This commit is contained in:
parent
c3c8a687a0
commit
e5a9f0eaf9
4 changed files with 138 additions and 25 deletions
|
|
@ -9,8 +9,8 @@
|
|||
}
|
||||
|
||||
.pf-c-dual-list-selector {
|
||||
--pf-c-dual-list-selector__menu--MinHeight: 18.5rem;
|
||||
--pf-c-dual-list-selector__menu--MaxHeight: 18.5rem;
|
||||
--pf-c-dual-list-selector__menu--MinHeight: 15.5rem;
|
||||
--pf-c-dual-list-selector__menu--MaxHeight: 15.5rem;
|
||||
}
|
||||
|
||||
.pf-c-form {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import React, {
|
|||
import useFormApi from '@data-driven-forms/react-form-renderer/use-form-api';
|
||||
import WizardContext from '@data-driven-forms/react-form-renderer/wizard-context';
|
||||
import {
|
||||
Alert,
|
||||
Divider,
|
||||
DualListSelector,
|
||||
DualListSelectorControl,
|
||||
DualListSelectorControlsWrapper,
|
||||
|
|
@ -31,6 +33,31 @@ import api from '../../../api';
|
|||
import { useGetArchitecturesByDistributionQuery } from '../../../store/apiSlice';
|
||||
import isBeta from '../../../Utilities/isBeta';
|
||||
|
||||
const ExactMatch = ({
|
||||
pkgList,
|
||||
search,
|
||||
chosenPackages,
|
||||
selectedAvailablePackages,
|
||||
handleSelectAvailableFunc,
|
||||
}) => {
|
||||
const match = pkgList.find((pkg) => pkg.name === search);
|
||||
return (
|
||||
<DualListSelectorListItem
|
||||
data-testid={`exact-match-${match.name}`}
|
||||
isDisabled={chosenPackages[match.name] ? true : false}
|
||||
isSelected={selectedAvailablePackages.has(match.name)}
|
||||
onOptionSelect={(e) => handleSelectAvailableFunc(e, match.name)}
|
||||
>
|
||||
<TextContent key={`${match.name}`}>
|
||||
<small className="pf-u-mb-sm">Exact match</small>
|
||||
<span className="pf-c-dual-list-selector__item-text">{match.name}</span>
|
||||
<small>{match.summary}</small>
|
||||
<Divider />
|
||||
</TextContent>
|
||||
</DualListSelectorListItem>
|
||||
);
|
||||
};
|
||||
|
||||
export const RedHatPackages = ({ defaultArch }) => {
|
||||
const { getState } = useFormApi();
|
||||
const distribution = getState()?.values?.release;
|
||||
|
|
@ -269,20 +296,36 @@ const Packages = ({ getAllPackages, isSuccess }) => {
|
|||
<DualListSelectorPane
|
||||
title="Available packages"
|
||||
searchInput={
|
||||
<SearchInput
|
||||
placeholder="Search for a package"
|
||||
data-testid="search-available-pkgs-input"
|
||||
value={packagesSearchName}
|
||||
ref={firstInputElement}
|
||||
onFocus={() => setFocus('available')}
|
||||
onBlur={() => setFocus('')}
|
||||
onChange={(_, val) => setPackagesSearchName(val)}
|
||||
submitSearchButtonLabel="Search button for available packages"
|
||||
onSearch={handleAvailablePackagesSearch}
|
||||
resetButtonLabel="Clear available packages search"
|
||||
onClear={handleClearAvailableSearch}
|
||||
isDisabled={currentStep.name === 'packages' ? !isSuccess : false}
|
||||
/>
|
||||
<>
|
||||
<SearchInput
|
||||
placeholder="Search for a package"
|
||||
data-testid="search-available-pkgs-input"
|
||||
value={packagesSearchName}
|
||||
ref={firstInputElement}
|
||||
onFocus={() => setFocus('available')}
|
||||
onBlur={() => setFocus('')}
|
||||
onChange={(_, val) => setPackagesSearchName(val)}
|
||||
submitSearchButtonLabel="Search button for available packages"
|
||||
onSearch={handleAvailablePackagesSearch}
|
||||
resetButtonLabel="Clear available packages search"
|
||||
onClear={handleClearAvailableSearch}
|
||||
isDisabled={currentStep.name === 'packages' ? !isSuccess : false}
|
||||
/>
|
||||
{availablePackagesDisplayList.length >= 100 && (
|
||||
<Alert
|
||||
title="Over 100 results found. Refine your search."
|
||||
variant="warning"
|
||||
isPlain
|
||||
isInline
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
status={
|
||||
selectedAvailablePackages.size > 0
|
||||
? `${selectedAvailablePackages.size}
|
||||
of ${availablePackagesDisplayList.length} items`
|
||||
: `${availablePackagesDisplayList.length} items`
|
||||
}
|
||||
>
|
||||
<DualListSelectorList data-testid="available-pkgs-list">
|
||||
|
|
@ -293,9 +336,38 @@ const Packages = ({ getAllPackages, isSuccess }) => {
|
|||
packages to your image
|
||||
</p>
|
||||
) : availablePackagesDisplayList.length === 0 ? (
|
||||
<p className="pf-u-text-align-center pf-u-mt-md">
|
||||
No packages found
|
||||
</p>
|
||||
<>
|
||||
<p className="pf-u-text-align-center pf-u-mt-md pf-u-font-size-lg pf-u-font-weight-bold">
|
||||
No results found
|
||||
</p>
|
||||
<br />
|
||||
<p className="pf-u-text-align-center pf-u-mt-md">
|
||||
Adjust your search and try again
|
||||
</p>
|
||||
</>
|
||||
) : availablePackagesDisplayList.length >= 100 ? (
|
||||
<>
|
||||
{availablePackagesDisplayList.some(
|
||||
(pkg) => pkg.name === packagesSearchName
|
||||
) && (
|
||||
<ExactMatch
|
||||
pkgList={availablePackagesDisplayList}
|
||||
search={packagesSearchName}
|
||||
chosenPackages={chosenPackages}
|
||||
selectedAvailablePackages={selectedAvailablePackages}
|
||||
handleSelectAvailableFunc={handleSelectAvailable}
|
||||
/>
|
||||
)}
|
||||
<p className="pf-u-text-align-center pf-u-mt-md pf-u-font-size-lg pf-u-font-weight-bold">
|
||||
Too many results to display
|
||||
</p>
|
||||
<br />
|
||||
<p className="pf-u-text-align-center pf-u-mt-md">
|
||||
Please make the search more specific
|
||||
<br />
|
||||
and try again
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
availablePackagesDisplayList.map((pkg) => {
|
||||
return (
|
||||
|
|
@ -366,6 +438,12 @@ const Packages = ({ getAllPackages, isSuccess }) => {
|
|||
onClear={handleClearChosenSearch}
|
||||
/>
|
||||
}
|
||||
status={
|
||||
selectedChosenPackages.size > 0
|
||||
? `${selectedChosenPackages.size}
|
||||
of ${chosenPackagesDisplayList.length} items`
|
||||
: `${chosenPackagesDisplayList.length} items`
|
||||
}
|
||||
isChosen
|
||||
>
|
||||
<DualListSelectorList data-testid="chosen-pkgs-list">
|
||||
|
|
@ -402,6 +480,14 @@ const Packages = ({ getAllPackages, isSuccess }) => {
|
|||
);
|
||||
};
|
||||
|
||||
ExactMatch.propTypes = {
|
||||
pkgList: PropTypes.arrayOf(PropTypes.object),
|
||||
search: PropTypes.string,
|
||||
chosenPackages: PropTypes.object,
|
||||
selectedAvailablePackages: PropTypes.arrayOf(PropTypes.string),
|
||||
handleSelectAvailableFunc: PropTypes.func,
|
||||
};
|
||||
|
||||
RedHatPackages.propTypes = {
|
||||
defaultArch: PropTypes.string,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -486,7 +486,7 @@ describe('Step Packages', () => {
|
|||
|
||||
await searchForAvailablePackages(searchbox, 'asdf');
|
||||
|
||||
await screen.findByText('No packages found');
|
||||
await screen.findByText('No results found');
|
||||
});
|
||||
|
||||
test('should display empty chosen state on failed search', async () => {
|
||||
|
|
|
|||
|
|
@ -935,7 +935,7 @@ describe('Step Packages', () => {
|
|||
searchbox.click();
|
||||
|
||||
await searchForAvailablePackages(searchbox, 'asdf');
|
||||
screen.getByText('No packages found');
|
||||
screen.getByText('No results found');
|
||||
});
|
||||
|
||||
test('should display empty available state on failed search after a successful search', async () => {
|
||||
|
|
@ -955,7 +955,7 @@ describe('Step Packages', () => {
|
|||
|
||||
await searchForAvailablePackages(searchbox, 'asdf');
|
||||
|
||||
screen.getByText('No packages found');
|
||||
screen.getByText('No results found');
|
||||
});
|
||||
|
||||
test('should display empty chosen state on failed search', async () => {
|
||||
|
|
@ -979,7 +979,7 @@ describe('Step Packages', () => {
|
|||
await searchForChosenPackages(searchboxChosen, '');
|
||||
});
|
||||
|
||||
test('should get all packages, regardless of api default limit', async () => {
|
||||
test('should display warning when over hundred results were found', async () => {
|
||||
await setUp();
|
||||
|
||||
const searchbox = screen.getAllByRole('textbox')[0]; // searching by id doesn't update the input ref
|
||||
|
|
@ -999,11 +999,38 @@ describe('Step Packages', () => {
|
|||
await searchForAvailablePackages(searchbox, 'testPkg');
|
||||
expect(getPackages).toHaveBeenCalledTimes(2);
|
||||
|
||||
screen.getByText('Over 100 results found. Refine your search.');
|
||||
screen.getByText('Too many results to display');
|
||||
});
|
||||
|
||||
test('should display an exact match if found regardless of too many results', async () => {
|
||||
await setUp();
|
||||
|
||||
const searchbox = screen.getAllByRole('textbox')[0]; // searching by id doesn't update the input ref
|
||||
|
||||
expect(searchbox).toBeDisabled();
|
||||
await waitFor(() => expect(searchbox).toBeEnabled());
|
||||
searchbox.click();
|
||||
|
||||
const getPackages = jest
|
||||
.spyOn(api, 'getPackages')
|
||||
.mockImplementation((distribution, architecture, search, limit) => {
|
||||
return limit
|
||||
? Promise.resolve(mockPkgResultAll)
|
||||
: Promise.resolve(mockPkgResultPartial);
|
||||
});
|
||||
|
||||
await searchForAvailablePackages(searchbox, 'testPkg-128');
|
||||
expect(getPackages).toHaveBeenCalledTimes(2);
|
||||
|
||||
const availablePackagesList = screen.getByTestId('available-pkgs-list');
|
||||
const availablePackagesItems = within(availablePackagesList).getAllByRole(
|
||||
const availablePackagesItems = within(availablePackagesList).getByRole(
|
||||
'option'
|
||||
);
|
||||
expect(availablePackagesItems).toHaveLength(132);
|
||||
expect(availablePackagesItems).toBeInTheDocument();
|
||||
screen.getByText('Exact match');
|
||||
screen.getByText('testPkg-128');
|
||||
screen.getByText('Too many results to display');
|
||||
});
|
||||
|
||||
test('search results should be sorted alphabetically', async () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue