Wizard: Replace deprecated select in OpenSCAP step
This replaces v4 deprecated select for a non-deprecated one and moves some functions to separate files.
This commit is contained in:
parent
248bc1d67a
commit
698037a0ae
1 changed files with 262 additions and 156 deletions
|
|
@ -1,11 +1,20 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Alert, FormGroup, Spinner } from '@patternfly/react-core';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
FormGroup,
|
||||
MenuToggle,
|
||||
MenuToggleElement,
|
||||
Select,
|
||||
SelectList,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from '@patternfly/react-core/deprecated';
|
||||
Spinner,
|
||||
TextInputGroup,
|
||||
TextInputGroupMain,
|
||||
TextInputGroupUtilities,
|
||||
} from '@patternfly/react-core';
|
||||
import { TimesIcon } from '@patternfly/react-icons';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import OpenSCAPFGLabel from './OpenSCAPFGLabel';
|
||||
|
|
@ -50,6 +59,91 @@ import { parseSizeUnit } from '../../../utilities/parseSizeUnit';
|
|||
import { Partition, Units } from '../../FileSystem/FileSystemTable';
|
||||
import { removeBetaFromRelease } from '../removeBetaFromRelease';
|
||||
|
||||
type OScapSelectOptionPropType = {
|
||||
profile_id: DistributionProfileItem;
|
||||
filter?: string;
|
||||
};
|
||||
|
||||
type OScapSelectOptionValueType = {
|
||||
profileID: DistributionProfileItem;
|
||||
toString: () => string;
|
||||
};
|
||||
|
||||
const OScapSelectOption = ({
|
||||
profile_id,
|
||||
filter,
|
||||
}: OScapSelectOptionPropType) => {
|
||||
const release = useAppSelector(selectDistribution);
|
||||
const { data } = useGetOscapCustomizationsQuery({
|
||||
distribution: release,
|
||||
profile: profile_id,
|
||||
});
|
||||
const oscapProfile = data?.openscap as OpenScapProfile;
|
||||
if (
|
||||
filter &&
|
||||
!oscapProfile?.profile_name?.toLowerCase().includes(filter.toLowerCase())
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const selectObject = (
|
||||
id: DistributionProfileItem,
|
||||
name?: string
|
||||
): OScapSelectOptionValueType => ({
|
||||
profileID: id,
|
||||
toString: () => name || '',
|
||||
});
|
||||
|
||||
return (
|
||||
<SelectOption
|
||||
key={profile_id}
|
||||
value={selectObject(profile_id, oscapProfile?.profile_name)}
|
||||
description={oscapProfile?.profile_description}
|
||||
>
|
||||
{oscapProfile?.profile_name}
|
||||
</SelectOption>
|
||||
);
|
||||
};
|
||||
|
||||
type ComplianceSelectOptionPropType = {
|
||||
policy: PolicyRead;
|
||||
};
|
||||
|
||||
type ComplianceSelectOptionValueType = {
|
||||
policyID: string;
|
||||
profileID: string;
|
||||
toString: () => string;
|
||||
};
|
||||
|
||||
const ComplianceSelectOption = ({ policy }: ComplianceSelectOptionPropType) => {
|
||||
const selectObj = (
|
||||
policyID: string,
|
||||
profileID: string,
|
||||
title: string
|
||||
): ComplianceSelectOptionValueType => ({
|
||||
policyID,
|
||||
profileID,
|
||||
toString: () => title,
|
||||
});
|
||||
|
||||
const descr = (
|
||||
<>
|
||||
Threshold: {policy.compliance_threshold}
|
||||
<br />
|
||||
Active systems: {policy.total_system_count}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectOption
|
||||
key={policy.id}
|
||||
value={selectObj(policy.id!, policy.ref_id!, policy.title!)}
|
||||
description={descr}
|
||||
>
|
||||
{policy.title}
|
||||
</SelectOption>
|
||||
);
|
||||
};
|
||||
|
||||
const ProfileSelector = () => {
|
||||
const policyID = useAppSelector(selectCompliancePolicyID);
|
||||
const policyTitle = useAppSelector(selectCompliancePolicyTitle);
|
||||
|
|
@ -59,6 +153,10 @@ const ProfileSelector = () => {
|
|||
const hasWslTargetOnly = useHasSpecificTargetOnly('wsl');
|
||||
const dispatch = useAppDispatch();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [inputValue, setInputValue] = useState<string>('');
|
||||
const [filterValue, setFilterValue] = useState<string>('');
|
||||
const [selectOptions, setSelectOptions] = useState<string[]>([]);
|
||||
const [selected, setSelected] = useState<string>('None');
|
||||
const complianceType = useAppSelector(selectComplianceType);
|
||||
const prefetchProfile = useBackendPrefetch('getOscapCustomizations');
|
||||
|
||||
|
|
@ -134,6 +232,27 @@ const ProfileSelector = () => {
|
|||
}
|
||||
}, [isSuccessPolicies]);
|
||||
|
||||
useEffect(() => {
|
||||
let filteredProfiles = profiles;
|
||||
|
||||
if (filterValue) {
|
||||
filteredProfiles = profiles?.filter((profile: string) =>
|
||||
String(profile).toLowerCase().includes(filterValue.toLowerCase())
|
||||
);
|
||||
if (!isOpen) {
|
||||
setIsOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredProfiles) {
|
||||
setSelectOptions(filteredProfiles);
|
||||
}
|
||||
|
||||
// This useEffect hook should run *only* on when the filter value changes.
|
||||
// eslint's exhaustive-deps rule does not support this use.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [filterValue, profiles]);
|
||||
|
||||
const handleToggle = () => {
|
||||
if (!isOpen && complianceType === 'openscap') {
|
||||
refetch();
|
||||
|
|
@ -153,6 +272,8 @@ const ProfileSelector = () => {
|
|||
dispatch(changeFileSystemConfigurationType('automatic'));
|
||||
handleServices(undefined);
|
||||
dispatch(clearKernelAppend());
|
||||
setInputValue('');
|
||||
setFilterValue('');
|
||||
};
|
||||
|
||||
const handlePackages = (
|
||||
|
|
@ -217,8 +338,30 @@ const ProfileSelector = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const handleSelect = (
|
||||
_event: React.MouseEvent<Element, MouseEvent>,
|
||||
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(
|
||||
changeCompliance({
|
||||
profileID: undefined,
|
||||
policyID: undefined,
|
||||
policyTitle: undefined,
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const applyChanges = (
|
||||
selection: OScapSelectOptionValueType | ComplianceSelectOptionValueType
|
||||
) => {
|
||||
if (selection.profileID === undefined) {
|
||||
|
|
@ -251,6 +394,7 @@ const ProfileSelector = () => {
|
|||
);
|
||||
} else {
|
||||
const compl = selection as ComplianceSelectOptionValueType;
|
||||
setSelected(compl.toString());
|
||||
dispatch(
|
||||
changeCompliance({
|
||||
profileID: compl.profileID,
|
||||
|
|
@ -261,20 +405,21 @@ const ProfileSelector = () => {
|
|||
}
|
||||
});
|
||||
}
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const options = () => {
|
||||
if (isFetching) {
|
||||
return [<OScapLoadingOption key="oscap-loading-option" />];
|
||||
}
|
||||
|
||||
if (profiles) {
|
||||
return [<OScapNoneOption key="oscap-none-option" />].concat(
|
||||
profiles.map((profile_id, index) => {
|
||||
return <OScapSelectOption key={index} profile_id={profile_id} />;
|
||||
})
|
||||
const handleSelect = (
|
||||
_event: React.MouseEvent<Element, MouseEvent>,
|
||||
selection: string
|
||||
) => {
|
||||
if (selection) {
|
||||
setInputValue(selection);
|
||||
setFilterValue('');
|
||||
applyChanges(
|
||||
selection as unknown as
|
||||
| OScapSelectOptionValueType
|
||||
| ComplianceSelectOptionValueType
|
||||
);
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -283,7 +428,14 @@ const ProfileSelector = () => {
|
|||
return [];
|
||||
}
|
||||
|
||||
const res = [<ComplianceNoneOption key="compliance-non-option" />];
|
||||
const res = [
|
||||
<SelectOption
|
||||
key="compliance-none-option"
|
||||
value={{ toString: () => 'None', compareTo: () => false }}
|
||||
>
|
||||
None
|
||||
</SelectOption>,
|
||||
];
|
||||
for (const p of policies.data) {
|
||||
if (p === undefined) {
|
||||
continue;
|
||||
|
|
@ -294,6 +446,58 @@ const ProfileSelector = () => {
|
|||
return res;
|
||||
};
|
||||
|
||||
const toggleOpenSCAP = (toggleRef: React.Ref<MenuToggleElement>) => (
|
||||
<MenuToggle
|
||||
ouiaId="profileSelect"
|
||||
data-testid="profileSelect"
|
||||
ref={toggleRef}
|
||||
variant="typeahead"
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
isExpanded={isOpen}
|
||||
isDisabled={!isSuccess || hasWslTargetOnly}
|
||||
>
|
||||
<TextInputGroup isPlain>
|
||||
<TextInputGroupMain
|
||||
value={profileID ? profileID : inputValue}
|
||||
onClick={onInputClick}
|
||||
onChange={onTextInputChange}
|
||||
autoComplete="off"
|
||||
placeholder="Select a profile"
|
||||
isExpanded={isOpen}
|
||||
/>
|
||||
|
||||
{profileID && (
|
||||
<TextInputGroupUtilities>
|
||||
<Button
|
||||
variant="plain"
|
||||
onClick={handleClear}
|
||||
aria-label="Clear input"
|
||||
>
|
||||
<TimesIcon />
|
||||
</Button>
|
||||
</TextInputGroupUtilities>
|
||||
)}
|
||||
</TextInputGroup>
|
||||
</MenuToggle>
|
||||
);
|
||||
|
||||
const toggleCompliance = (toggleRef: React.Ref<MenuToggleElement>) => (
|
||||
<MenuToggle
|
||||
ouiaId="compliancePolicySelect"
|
||||
ref={toggleRef}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
isExpanded={isOpen}
|
||||
isDisabled={isFetchingPolicies}
|
||||
style={
|
||||
{
|
||||
width: '200px',
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{selected}
|
||||
</MenuToggle>
|
||||
);
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
data-testid="profiles-form-group"
|
||||
|
|
@ -301,51 +505,55 @@ const ProfileSelector = () => {
|
|||
>
|
||||
{complianceType === 'openscap' && (
|
||||
<Select
|
||||
ouiaId="profileSelect"
|
||||
variant={SelectVariant.typeahead}
|
||||
onToggle={handleToggle}
|
||||
onSelect={handleSelect}
|
||||
onClear={handleClear}
|
||||
maxHeight="300px"
|
||||
selections={profileID}
|
||||
isScrollable
|
||||
isOpen={isOpen}
|
||||
placeholderText="Select a profile"
|
||||
typeAheadAriaLabel="Select a profile"
|
||||
isDisabled={!isSuccess || hasWslTargetOnly}
|
||||
onFilter={(_event, value) => {
|
||||
if (isFetching) {
|
||||
return [<OScapLoadingOption key="oscap-loading-option" />];
|
||||
}
|
||||
if (profiles) {
|
||||
return [<OScapNoneOption key="oscap-none-option" />].concat(
|
||||
profiles.map((profile_id, index) => {
|
||||
return (
|
||||
<OScapSelectOption
|
||||
key={index}
|
||||
profile_id={profile_id}
|
||||
filter={value}
|
||||
/>
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
selected={profileID}
|
||||
onSelect={handleSelect}
|
||||
onOpenChange={handleToggle}
|
||||
toggle={toggleOpenSCAP}
|
||||
shouldFocusFirstItemOnOpen={false}
|
||||
>
|
||||
{options()}
|
||||
<SelectList>
|
||||
{isFetching && (
|
||||
<SelectOption
|
||||
value="loader"
|
||||
data-testid="openscap-profiles-fetching"
|
||||
>
|
||||
<Spinner size="lg" />
|
||||
</SelectOption>
|
||||
)}
|
||||
{selectOptions.length > 0 &&
|
||||
[
|
||||
<SelectOption
|
||||
key="oscap-none-option"
|
||||
value={{ toString: () => 'None', compareTo: () => false }}
|
||||
>
|
||||
None
|
||||
</SelectOption>,
|
||||
].concat(
|
||||
selectOptions.map(
|
||||
(name: DistributionProfileItem, index: number) => (
|
||||
<OScapSelectOption key={index} profile_id={name} />
|
||||
)
|
||||
)
|
||||
)}
|
||||
{isSuccess && selectOptions.length === 0 && (
|
||||
<SelectOption isDisabled>
|
||||
{`No results found for "${filterValue}"`}
|
||||
</SelectOption>
|
||||
)}
|
||||
</SelectList>
|
||||
</Select>
|
||||
)}
|
||||
{complianceType === 'compliance' && (
|
||||
<Select
|
||||
isDisabled={isFetchingPolicies}
|
||||
isScrollable
|
||||
isOpen={isOpen}
|
||||
onSelect={handleSelect}
|
||||
onToggle={handleToggle}
|
||||
selections={
|
||||
isFetchingPolicies
|
||||
? 'Loading policies'
|
||||
: policyTitle || policyID || 'None'
|
||||
}
|
||||
ouiaId="compliancePolicySelect"
|
||||
onOpenChange={handleToggle}
|
||||
selected={selected}
|
||||
toggle={toggleCompliance}
|
||||
shouldFocusFirstItemOnOpen={false}
|
||||
>
|
||||
{complianceOptions()}
|
||||
</Select>
|
||||
|
|
@ -364,106 +572,4 @@ const ProfileSelector = () => {
|
|||
);
|
||||
};
|
||||
|
||||
const OScapNoneOption = () => {
|
||||
return (
|
||||
<SelectOption value={{ toString: () => 'None', compareTo: () => false }} />
|
||||
);
|
||||
};
|
||||
|
||||
const OScapLoadingOption = () => {
|
||||
return (
|
||||
<SelectOption
|
||||
value={{ toString: () => 'Loading...', compareTo: () => false }}
|
||||
>
|
||||
<Spinner size="lg" />
|
||||
</SelectOption>
|
||||
);
|
||||
};
|
||||
|
||||
type OScapSelectOptionPropType = {
|
||||
profile_id: DistributionProfileItem;
|
||||
filter?: string;
|
||||
};
|
||||
|
||||
type OScapSelectOptionValueType = {
|
||||
profileID: DistributionProfileItem;
|
||||
toString: () => string;
|
||||
};
|
||||
|
||||
const OScapSelectOption = ({
|
||||
profile_id,
|
||||
filter,
|
||||
}: OScapSelectOptionPropType) => {
|
||||
const release = useAppSelector(selectDistribution);
|
||||
const { data } = useGetOscapCustomizationsQuery({
|
||||
distribution: release,
|
||||
profile: profile_id,
|
||||
});
|
||||
const oscapProfile = data?.openscap as OpenScapProfile;
|
||||
if (
|
||||
filter &&
|
||||
!oscapProfile?.profile_name?.toLowerCase().includes(filter.toLowerCase())
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const selectObject = (
|
||||
id: DistributionProfileItem,
|
||||
name?: string
|
||||
): OScapSelectOptionValueType => ({
|
||||
profileID: id,
|
||||
toString: () => name || '',
|
||||
});
|
||||
|
||||
return (
|
||||
<SelectOption
|
||||
key={profile_id}
|
||||
value={selectObject(profile_id, oscapProfile?.profile_name)}
|
||||
description={oscapProfile?.profile_description}
|
||||
/>
|
||||
);
|
||||
};
|
||||
type ComplianceSelectOptionPropType = {
|
||||
policy: PolicyRead;
|
||||
};
|
||||
|
||||
type ComplianceSelectOptionValueType = {
|
||||
policyID: string;
|
||||
profileID: string;
|
||||
toString: () => string;
|
||||
};
|
||||
|
||||
const ComplianceNoneOption = () => {
|
||||
return (
|
||||
<SelectOption value={{ toString: () => 'None', compareTo: () => false }} />
|
||||
);
|
||||
};
|
||||
|
||||
const ComplianceSelectOption = ({ policy }: ComplianceSelectOptionPropType) => {
|
||||
const selectObj = (
|
||||
policyID: string,
|
||||
profileID: string,
|
||||
title: string
|
||||
): ComplianceSelectOptionValueType => ({
|
||||
policyID,
|
||||
profileID,
|
||||
toString: () => title,
|
||||
});
|
||||
|
||||
const descr = (
|
||||
<>
|
||||
Threshold: {policy.compliance_threshold}
|
||||
<br />
|
||||
Active systems: {policy.total_system_count}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<SelectOption
|
||||
key={policy.id}
|
||||
value={selectObj(policy.id!, policy.ref_id!, policy.title!)}
|
||||
description={descr}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileSelector;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue