Wizard: Display customized policy rules in review summary
Previously, when a user selected a compliance policy with tailored rules,
the review page always showed the default profile customizations instead
of the policy-specific customizations.
Root cause: OscapProfileInformation component was only using the profile
endpoint (/oscap/{distribution}/{profile}/customizations) which returns
base profile rules, not the policy endpoint
(/oscap/{policy}/{distribution}/policy_customizations) which returns
customized rules.
Changes:
- Add useGetOscapCustomizationsForPolicyQuery export to backendApi
- Implement dual data fetching in OscapProfileInformation:
* Profile endpoint: for description and reference ID
* Policy endpoint: for customized packages, services, kernel args
Fixes the compliance policy customization display bug where edited
policy rules were not reflected in the image build summary.
Add unit tests for compliance policy customizations
Fix profile description title
This commit is contained in:
parent
42b16bafd8
commit
bf77501eea
6 changed files with 115 additions and 23 deletions
|
|
@ -8,7 +8,10 @@ import {
|
|||
Spinner,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
import { useGetOscapCustomizationsQuery } from '../../../../../store/backendApi';
|
||||
import {
|
||||
useGetComplianceCustomizationsQuery,
|
||||
useGetOscapCustomizationsQuery,
|
||||
} from '../../../../../store/backendApi';
|
||||
import { PolicyRead, usePolicyQuery } from '../../../../../store/complianceApi';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import { OpenScapProfile } from '../../../../../store/imageBuilderApi';
|
||||
|
|
@ -16,6 +19,7 @@ import {
|
|||
changeCompliance,
|
||||
selectCompliancePolicyID,
|
||||
selectComplianceProfileID,
|
||||
selectComplianceType,
|
||||
selectDistribution,
|
||||
selectFips,
|
||||
} from '../../../../../store/wizardSlice';
|
||||
|
|
@ -31,12 +35,29 @@ export const OscapProfileInformation = ({
|
|||
const release = useAppSelector(selectDistribution);
|
||||
const compliancePolicyID = useAppSelector(selectCompliancePolicyID);
|
||||
const complianceProfileID = useAppSelector(selectComplianceProfileID);
|
||||
const complianceType = useAppSelector(selectComplianceType);
|
||||
const fips = useAppSelector(selectFips);
|
||||
|
||||
const {
|
||||
data: oscapPolicyInfo,
|
||||
isFetching: isFetchingOscapPolicyInfo,
|
||||
isSuccess: isSuccessOscapPolicyInfo,
|
||||
error: policyError,
|
||||
} = useGetComplianceCustomizationsQuery(
|
||||
{
|
||||
distribution: release,
|
||||
policy: compliancePolicyID!,
|
||||
},
|
||||
{
|
||||
skip: !compliancePolicyID || !!process.env.IS_ON_PREMISE,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
data: oscapProfileInfo,
|
||||
isFetching: isFetchingOscapProfileInfo,
|
||||
isSuccess: isSuccessOscapProfileInfo,
|
||||
error: profileError,
|
||||
} = useGetOscapCustomizationsQuery(
|
||||
{
|
||||
distribution: release,
|
||||
|
|
@ -48,6 +69,20 @@ export const OscapProfileInformation = ({
|
|||
},
|
||||
);
|
||||
|
||||
const customizationData =
|
||||
compliancePolicyID && oscapPolicyInfo ? oscapPolicyInfo : oscapProfileInfo;
|
||||
const profileMetadata = oscapProfileInfo;
|
||||
const isPolicyDataLoading = compliancePolicyID
|
||||
? isFetchingOscapPolicyInfo
|
||||
: false;
|
||||
const isFetchingOscapData = isPolicyDataLoading || isFetchingOscapProfileInfo;
|
||||
const isPolicyDataSuccess = compliancePolicyID
|
||||
? isSuccessOscapPolicyInfo
|
||||
: true;
|
||||
const isSuccessOscapData = isPolicyDataSuccess && isSuccessOscapProfileInfo;
|
||||
const hasCriticalError = profileError || (compliancePolicyID && policyError);
|
||||
const shouldShowData = isSuccessOscapData && !hasCriticalError;
|
||||
|
||||
const {
|
||||
data: policyInfo,
|
||||
isFetching: isFetchingPolicyInfo,
|
||||
|
|
@ -74,23 +109,28 @@ export const OscapProfileInformation = ({
|
|||
policyTitle: pol.title,
|
||||
}),
|
||||
);
|
||||
}, [isSuccessPolicyInfo]);
|
||||
}, [isSuccessPolicyInfo, dispatch, policyInfo]);
|
||||
|
||||
const oscapProfile = oscapProfileInfo?.openscap as OpenScapProfile;
|
||||
const oscapProfile = profileMetadata?.openscap as OpenScapProfile | undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
{(isFetchingOscapProfileInfo || isFetchingPolicyInfo) && (
|
||||
<Spinner size='lg' />
|
||||
{(isFetchingOscapData || isFetchingPolicyInfo) && <Spinner size='lg' />}
|
||||
{hasCriticalError && (
|
||||
<Content component={ContentVariants.p} className='pf-v6-u-color-200'>
|
||||
Unable to load compliance information. Please try again.
|
||||
</Content>
|
||||
)}
|
||||
{isSuccessOscapProfileInfo && (
|
||||
{shouldShowData && (
|
||||
<>
|
||||
<Content component={ContentVariants.dl} className='review-step-dl'>
|
||||
<Content
|
||||
component={ContentVariants.dt}
|
||||
className='pf-v6-u-min-width'
|
||||
>
|
||||
Profile description
|
||||
{complianceType === 'compliance'
|
||||
? 'Policy description'
|
||||
: 'Profile description'}
|
||||
</Content>
|
||||
<Content component={ContentVariants.dd}>
|
||||
{oscapProfile?.profile_description}
|
||||
|
|
@ -116,7 +156,7 @@ export const OscapProfileInformation = ({
|
|||
<Content component={ContentVariants.dd}>
|
||||
<CodeBlock>
|
||||
<CodeBlockCode>
|
||||
{(oscapProfileInfo?.packages ?? []).join(', ')}
|
||||
{(customizationData?.packages ?? []).join(', ')}
|
||||
</CodeBlockCode>
|
||||
</CodeBlock>
|
||||
</Content>
|
||||
|
|
@ -129,7 +169,7 @@ export const OscapProfileInformation = ({
|
|||
<Content component={ContentVariants.dd}>
|
||||
<CodeBlock>
|
||||
<CodeBlockCode>
|
||||
{oscapProfileInfo?.kernel?.append}
|
||||
{customizationData?.kernel?.append}
|
||||
</CodeBlockCode>
|
||||
</CodeBlock>
|
||||
</Content>
|
||||
|
|
@ -142,7 +182,7 @@ export const OscapProfileInformation = ({
|
|||
<Content component={ContentVariants.dd}>
|
||||
<CodeBlock>
|
||||
<CodeBlockCode>
|
||||
{(oscapProfileInfo?.services?.enabled ?? []).join(' ')}
|
||||
{(customizationData?.services?.enabled ?? []).join(' ')}
|
||||
</CodeBlockCode>
|
||||
</CodeBlock>
|
||||
</Content>
|
||||
|
|
@ -155,8 +195,8 @@ export const OscapProfileInformation = ({
|
|||
<Content component={ContentVariants.dd}>
|
||||
<CodeBlock>
|
||||
<CodeBlockCode>
|
||||
{(oscapProfileInfo?.services?.disabled ?? [])
|
||||
.concat(oscapProfileInfo?.services?.masked ?? [])
|
||||
{(customizationData?.services?.disabled ?? [])
|
||||
.concat(customizationData?.services?.masked ?? [])
|
||||
.join(' ')}
|
||||
</CodeBlockCode>
|
||||
</CodeBlock>
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ import {
|
|||
|
||||
import { useSelectorHandlers } from './useSelectorHandlers';
|
||||
|
||||
import {
|
||||
useGetComplianceCustomizationsQuery,
|
||||
useLazyGetComplianceCustomizationsQuery,
|
||||
} from '../../../../../store/backendApi';
|
||||
import {
|
||||
PolicyRead,
|
||||
usePoliciesQuery,
|
||||
} from '../../../../../store/complianceApi';
|
||||
import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
|
||||
import {
|
||||
useGetOscapCustomizationsForPolicyQuery,
|
||||
useLazyGetOscapCustomizationsForPolicyQuery,
|
||||
} from '../../../../../store/imageBuilderApi';
|
||||
import {
|
||||
changeCompliance,
|
||||
changeFileSystemConfigurationType,
|
||||
|
|
@ -97,7 +97,7 @@ const PolicySelector = () => {
|
|||
filter: `os_major_version=${majorVersion}`,
|
||||
});
|
||||
|
||||
const { data: currentProfileData } = useGetOscapCustomizationsForPolicyQuery(
|
||||
const { data: currentProfileData } = useGetComplianceCustomizationsQuery(
|
||||
{
|
||||
distribution: release,
|
||||
policy: policyID!,
|
||||
|
|
@ -105,7 +105,7 @@ const PolicySelector = () => {
|
|||
{ skip: !policyID },
|
||||
);
|
||||
|
||||
const [trigger] = useLazyGetOscapCustomizationsForPolicyQuery();
|
||||
const [trigger] = useLazyGetComplianceCustomizationsQuery();
|
||||
|
||||
useEffect(() => {
|
||||
if (!policies || policies.data === undefined) {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ export const useLazyGetOscapCustomizationsQuery = process.env.IS_ON_PREMISE
|
|||
? cockpitQueries.useLazyGetOscapCustomizationsQuery
|
||||
: serviceQueries.useLazyGetOscapCustomizationsQuery;
|
||||
|
||||
export const useGetComplianceCustomizationsQuery =
|
||||
serviceQueries.useGetOscapCustomizationsForPolicyQuery;
|
||||
|
||||
export const useLazyGetComplianceCustomizationsQuery =
|
||||
serviceQueries.useLazyGetOscapCustomizationsForPolicyQuery;
|
||||
|
||||
export const useComposeBlueprintMutation = process.env.IS_ON_PREMISE
|
||||
? cockpitQueries.useComposeBlueprintMutation
|
||||
: serviceQueries.useComposeBlueprintMutation;
|
||||
|
|
|
|||
|
|
@ -332,4 +332,30 @@ describe('OpenSCAP edit mode', () => {
|
|||
user.click(selectedBtn);
|
||||
await screen.findByText('neovim');
|
||||
});
|
||||
|
||||
test('customized policy shows only non-removed rules', async () => {
|
||||
const { oscapCustomizations, oscapCustomizationsPolicy } = await import(
|
||||
'../../../../fixtures/oscap'
|
||||
);
|
||||
|
||||
const profileId = 'xccdf_org.ssgproject.content_profile_cis_workstation_l1';
|
||||
const normalProfile = oscapCustomizations(profileId);
|
||||
expect(normalProfile.packages).toEqual(['aide', 'neovim']);
|
||||
const customPolicy = oscapCustomizationsPolicy('custom-policy-123');
|
||||
expect(customPolicy.packages).toEqual(['neovim']);
|
||||
await renderCreateMode();
|
||||
await selectRhel9();
|
||||
await selectGuestImageTarget();
|
||||
await goToOscapStep();
|
||||
await selectProfile();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/aide, neovim/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(customPolicy.packages).not.toContain('aide');
|
||||
expect(customPolicy.packages).toContain('neovim');
|
||||
expect(normalProfile.packages).toContain('aide');
|
||||
expect(normalProfile.packages).toContain('neovim');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
14
src/test/fixtures/compliance.ts
vendored
14
src/test/fixtures/compliance.ts
vendored
|
|
@ -36,8 +36,20 @@ export const mockPolicies = {
|
|||
profile_title: 'DISA STIG with GUI for Red Hat Enterprise Linux 8',
|
||||
ref_id: 'xccdf_org.ssgproject.content_profile_stig_gui',
|
||||
},
|
||||
{
|
||||
id: 'custom-policy-123',
|
||||
title: 'Custom CIS Policy (Partial Rules)',
|
||||
description: 'A customized policy where user removed some rules',
|
||||
compliance_threshold: 100,
|
||||
total_system_count: 5,
|
||||
type: 'policy',
|
||||
os_major_version: 8,
|
||||
profile_title:
|
||||
'Custom CIS Red Hat Enterprise Linux 8 Benchmark for Level 1 - Workstation',
|
||||
ref_id: 'xccdf_org.ssgproject.content_profile_cis_workstation_l1',
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
total: 3,
|
||||
total: 4,
|
||||
},
|
||||
};
|
||||
|
|
|
|||
16
src/test/fixtures/oscap.ts
vendored
16
src/test/fixtures/oscap.ts
vendored
|
|
@ -124,9 +124,17 @@ export const oscapCustomizationsPolicy = (
|
|||
): GetOscapCustomizationsApiResponse => {
|
||||
const policyData = mockPolicies.data.find((p) => p.id === policy);
|
||||
const customizations = oscapCustomizations(policyData!.ref_id);
|
||||
// filter out a single package to simulate the customizations being tailored
|
||||
customizations.packages = customizations.packages!.filter(
|
||||
(p) => p !== 'aide',
|
||||
);
|
||||
|
||||
// Simulate different levels of customization based on policy
|
||||
if (policy === 'custom-policy-123') {
|
||||
// This policy has user-customized rules - only neovim remains
|
||||
customizations.packages = ['neovim']; // User removed aide package
|
||||
} else {
|
||||
// Other policies: filter out a single package to simulate basic customizations
|
||||
customizations.packages = customizations.packages!.filter(
|
||||
(p) => p !== 'aide',
|
||||
);
|
||||
}
|
||||
|
||||
return customizations;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue