Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions playwright/Customizations/FipsMode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,7 @@ test('FIPS switch toggles and persists through save', async ({
.getByRole('radio', { name: 'Use a default OpenSCAP profile' })
.check();

const typeToFilter = frame.getByRole('textbox', { name: 'Type to filter' });

await typeToFilter.fill('stig');
await frame.getByTestId('profileSelect').click();
await frame
.getByRole('option', {
name: /Red Hat STIG for Red Hat Enterprise Linux 10/i,
Expand Down
21 changes: 8 additions & 13 deletions playwright/Customizations/OpenSCAP.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,14 @@ test('Create a blueprint with OpenSCAP customization', async ({
await expect(
frame.getByText('WSL: customization is not supported'),
).toBeVisible();
await expect(
frame.getByRole('textbox', { name: 'Type to filter' }),
).toBeEnabled();
});

await test.step('Select a CIS profile then switch to None', async () => {
await frame
.getByRole('radio', { name: 'Use a default OpenSCAP profile' })
.click();
await frame.getByRole('textbox', { name: 'Type to filter' }).fill('cis');
await expect(frame.getByTestId('profileSelect')).toBeEnabled();
await frame.getByTestId('profileSelect').click();
await frame
.getByRole('option', {
name: 'CIS Red Hat Enterprise Linux 9 Benchmark for Level 1 - Server',
Expand All @@ -91,7 +89,7 @@ test('Create a blueprint with OpenSCAP customization', async ({
.getByRole('radio', { name: 'Use a default OpenSCAP profile' })
.click();

await frame.getByRole('textbox', { name: 'Type to filter' }).fill('cis');
await frame.getByTestId('profileSelect').click();
await frame
.getByRole('option', {
name: 'CIS Red Hat Enterprise Linux 9 Benchmark for Level 1 - Server',
Expand Down Expand Up @@ -143,14 +141,11 @@ test('Create a blueprint with OpenSCAP customization', async ({
await test.step('Edit BP', async () => {
await frame.getByRole('button', { name: 'Edit blueprint' }).click();
await frame.getByRole('button', { name: 'Base settings' }).click();
await frame.getByRole('textbox', { name: 'Type to filter' }).click();
await expect(
frame.getByText(
'CIS Red Hat Enterprise Linux 9 Benchmark for Level 1 - Server',
),
).toBeVisible();
await frame.getByRole('textbox', { name: 'Type to filter' }).clear();
await frame.getByRole('textbox', { name: 'Type to filter' }).fill('cis');
await expect(frame.getByTestId('profileSelect')).toContainText(
'CIS Red Hat Enterprise Linux 9 Benchmark for Level 1 - Server',
{ timeout: 60000 },
);
await frame.getByTestId('profileSelect').click();
await frame
.getByRole('option', {
name: 'CIS Red Hat Enterprise Linux 9 Benchmark for Level 2 - Server',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
}
}
}
}, [isSuccessPolicies]);

Check warning on line 127 in src/Components/CreateImageWizard/steps/Oscap/components/PolicySelector.tsx

View workflow job for this annotation

GitHub Actions / ESLint

React Hook useEffect has missing dependencies: 'dispatch', 'policies', 'policyID', and 'policyTitle'. Either include them or remove the dependency array

const handleToggle = () => {
setIsOpen(!isOpen);
Expand Down Expand Up @@ -229,6 +229,7 @@
<MenuToggle
ouiaId='compliancePolicySelect'
ref={toggleRef}
isPlaceholder={!policyTitle && !isApplying}
onClick={() => setIsOpen(!isOpen)}
isExpanded={isOpen}
isDisabled={isDisabled || isFetchingPolicies || isApplying}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import React, { useEffect, useState } from 'react';

import {
Button,
FormGroup,
MenuToggle,
MenuToggleElement,
Select,
SelectList,
SelectOption,
Spinner,
TextInputGroup,
TextInputGroupMain,
TextInputGroupUtilities,
} from '@patternfly/react-core';
import { TimesIcon } from '@patternfly/react-icons';

import {
DistributionProfileItem,
Expand All @@ -31,7 +26,6 @@ import {
changeFscMode,
clearKernelAppend,
selectComplianceProfileID,
selectComplianceType,
selectDistribution,
setOscapProfile,
} from '@/store/slices/wizard';
Expand Down Expand Up @@ -65,21 +59,13 @@ const ProfileSelector = ({
const release = removeBetaFromRelease(useAppSelector(selectDistribution));
const dispatch = useAppDispatch();
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState<string>('');
const [filterValue, setFilterValue] = useState<string>('');
const [selectOptions, setSelectOptions] = useState<
{
id: DistributionProfileItem;
name: string | undefined;
}[]
>([]);
const [isApplying, setIsApplying] = useState(false);
const [profileDetails, setProfileDetails] = useState<
{
id: DistributionProfileItem;
name: string | undefined;
}[]
>([]);
const complianceType = useAppSelector(selectComplianceType);
const prefetchProfile = useBackendPrefetch('getOscapCustomizations');
const {
clearCompliancePackages,
Expand All @@ -100,14 +86,6 @@ const ProfileSelector = ({

const [trigger] = useLazyGetOscapCustomizationsQuery();

useEffect(() => {
if (!profileID) {
setInputValue('');
setFilterValue('');
setIsOpen(false);
}
}, [profileID]);

// prefetch the profiles customizations for on-prem
// and save the results to the cache, since the request
// is quite slow
Expand Down Expand Up @@ -145,33 +123,16 @@ const ProfileSelector = ({

const resolvedProfiles = await Promise.all(promises);
setProfileDetails(resolvedProfiles);
setSelectOptions(resolvedProfiles);
};

fetchProfileDetails();
}, [profiles, release, trigger]);

useEffect(() => {
if (!filterValue) {
setSelectOptions(profileDetails);
return;
}
const trimmedFilter = filterValue.toLowerCase().trim();
const filtered = profileDetails.filter(({ name }) =>
name?.toLowerCase().includes(trimmedFilter),
);

setSelectOptions(filtered);
if (!isOpen && filtered.length > 0) {
setIsOpen(true);
}
}, [filterValue, profileDetails, isOpen]);

const handleToggle = () => {
if (!isOpen && complianceType === 'openscap') {
const handleToggle = (nextIsOpen: boolean) => {
if (nextIsOpen) {
refetch();
}
setIsOpen(!isOpen);
setIsOpen(nextIsOpen);
};

const handleClear = () => {
Expand All @@ -181,53 +142,13 @@ const ProfileSelector = ({
handleServices(undefined);
dispatch(clearKernelAppend());
dispatch(changeFips(false));
setInputValue('');
setFilterValue('');
};

const onInputClick = () => {
if (!isOpen) {
setIsOpen(true);
} else if (!inputValue) {
setIsOpen(false);
}
};

const onTextInputChange = (_event: React.FormEvent, value: string) => {
setInputValue(value);
setFilterValue(value);

if (value !== profileID) {
dispatch(setOscapProfile(undefined));
}
};

const onKeyDown = (event: React.KeyboardEvent) => {
if (event.key === 'Enter') {
event.preventDefault();

if (!isOpen) {
setIsOpen(true);
} else if (selectOptions.length === 1) {
const singleProfile = selectOptions[0];
const selection: OScapSelectOptionValueType = {
profileID: singleProfile.id,
toString: () => singleProfile.name || '',
};

setInputValue(singleProfile.name || '');
setFilterValue('');
applyChanges(selection);
setIsOpen(false);
}
}
};

const applyChanges = (selection: OScapSelectOptionValueType) => {
if (selection.profileID === undefined) {
// handle user has selected 'None' case
handleClear();
} else {
setIsApplying(true);
const oldOscapPackages = currentProfileData?.packages || [];
trigger(
{
Expand All @@ -250,7 +171,8 @@ const ProfileSelector = ({
handleKernelAppend(response.kernel?.append);
dispatch(setOscapProfile(selection.profileID));
dispatch(changeFips(response.fips?.enabled || false));
});
})
.finally(() => setIsApplying(false));
}
};

Expand All @@ -260,49 +182,56 @@ const ProfileSelector = ({
) => {
if (selection === undefined) return;

setInputValue((selection as OScapSelectOptionValueType['profileID']) || '');
setFilterValue('');
applyChanges(selection as unknown as OScapSelectOptionValueType);
setIsOpen(false);
};

const selectedProfileName = profileID
? profileDetails.find(({ id }) => id === profileID)?.name || profileID
: undefined;

const profileOptions = () => {
if (isFetching) {
return [
<SelectOption key='oscap-loader' value='loader'>
<Spinner size='lg' />
</SelectOption>,
];
}

const res = profileDetails.map(({ id, name }) => (
<SelectOption
key={id}
value={{
profileID: id,
toString: () => name,
}}
isSelected={profileID === id}
>
{name}
</SelectOption>
));
return res;
};

const toggleOpenSCAP = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
data-testid='profileSelect'
ref={toggleRef}
variant='typeahead'
onClick={() => setIsOpen(!isOpen)}
isPlaceholder={!selectedProfileName && !isApplying}
onClick={() => handleToggle(!isOpen)}
isExpanded={isOpen}
isDisabled={isDisabled || !isSuccess}
isDisabled={isDisabled || !isSuccess || isApplying}
isFullWidth
style={{ maxWidth: 'none' }}
>
<TextInputGroup isPlain>
<TextInputGroupMain
value={
profileID
? profileDetails.find(({ id }) => id === profileID)?.name ||
profileID
: inputValue
}
onClick={onInputClick}
onChange={onTextInputChange}
onKeyDown={onKeyDown}
autoComplete='off'
placeholder='Select a profile'
isExpanded={isOpen}
/>

{profileID && (
<TextInputGroupUtilities>
<Button
icon={<TimesIcon />}
variant='plain'
onClick={handleClear}
aria-label='Clear input'
/>
</TextInputGroupUtilities>
)}
</TextInputGroup>
{isApplying ? (
<>
<Spinner size='sm' /> Applying profile...
</>
) : (
selectedProfileName || 'Select a profile'
)}
</MenuToggle>
);

Expand All @@ -316,35 +245,8 @@ const ProfileSelector = ({
onOpenChange={handleToggle}
toggle={toggleOpenSCAP}
shouldFocusFirstItemOnOpen={false}
popperProps={{
maxWidth: '50vw',
}}
>
<SelectList>
{isFetching && (
<SelectOption value='loader'>
<Spinner size='lg' />
</SelectOption>
)}
{selectOptions.length > 0 &&
selectOptions.map(({ id, name }) => (
<SelectOption
key={id}
value={{
profileID: id,
toString: () => name,
}}
isSelected={profileID === id}
>
{name}
</SelectOption>
))}
{isSuccess && selectOptions.length === 0 && (
<SelectOption isDisabled>
{`No results found for "${filterValue}"`}
</SelectOption>
)}
</SelectList>
<SelectList>{profileOptions()}</SelectList>
</Select>
</FormGroup>
);
Expand Down
Loading
Loading