From abdb28c7480d3ccd81981181766bec6a3175ba86 Mon Sep 17 00:00:00 2001 From: Georgii Karataev Date: Thu, 19 Dec 2024 13:38:26 +0100 Subject: [PATCH 1/2] refactor: Remove GQL finish step for Create policy component Also removes the usePolicy mutation hook. --- src/Mutations/hooks/usePolicy.js | 76 ------- src/Mutations/hooks/usePolicy.test.js | 196 ------------------ .../FinishedCreatePolicy.js | 153 ++++++++++++-- .../FinishedCreatePolicy.test.js | 182 +++++++++++++--- .../FinishedCreatePolicyGraphQL.js | 11 - .../FinishedCreatePolicyGraphQL.test.js | 36 ---- .../FinishedCreatePolicyRest.js | 142 ------------- .../FinishedCreatePolicyRest.test.js | 179 ---------------- .../EditPolicy/EditPolicyGraphQL.js | 2 +- .../EditPolicy/EditPolicyRest.js | 4 +- src/SmartComponents/EditPolicy/hooks.js | 19 +- src/SmartComponents/EditPolicy/hooks.test.js | 13 -- 12 files changed, 303 insertions(+), 710 deletions(-) delete mode 100644 src/Mutations/hooks/usePolicy.js delete mode 100644 src/Mutations/hooks/usePolicy.test.js delete mode 100644 src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyGraphQL.js delete mode 100644 src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyGraphQL.test.js delete mode 100644 src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyRest.js delete mode 100644 src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyRest.test.js diff --git a/src/Mutations/hooks/usePolicy.js b/src/Mutations/hooks/usePolicy.js deleted file mode 100644 index 4b13d14d5..000000000 --- a/src/Mutations/hooks/usePolicy.js +++ /dev/null @@ -1,76 +0,0 @@ -import useCreateBusinessObjective from './useCreateBusinessObjective'; -import usePolicyMutation from './usePolicyMutation'; -import useAssociateSystems from './useAssociateSystems'; -import useAssociateRules from './useAssociateRules'; - -const usePolicy = () => { - const createBusinessObjective = useCreateBusinessObjective(); - const policyMutation = usePolicyMutation(); - const associateSystems = useAssociateSystems(); - const associateRules = useAssociateRules(); - - return async ( - policy, - { values = {}, selectedRuleRefIds, ...updatedPolicy }, - onProgress - ) => { - const expectedUpdates = - 3 + Object.keys(values).length + (selectedRuleRefIds || []).length; - let progress = 0; - const dispatchProgress = () => { - if (onProgress) { - onProgress(++progress / expectedUpdates); - } - }; - - const businessObjectiveId = await createBusinessObjective( - policy, - updatedPolicy?.businessObjective - ); - dispatchProgress(); - - const mutatedPolicy = await policyMutation( - policy?.id, - { ...updatedPolicy, selectedRuleRefIds }, - businessObjectiveId - ); - dispatchProgress(); - - if (!policy) { - policy = mutatedPolicy; - } - - const { - policy: { profiles }, - } = await associateSystems(policy, updatedPolicy.hosts); - dispatchProgress(); - - for (const profileSelectedRuleRefIds of selectedRuleRefIds || []) { - await associateRules(profileSelectedRuleRefIds, profiles); - dispatchProgress(); - } - - for (const [profileId, profileValues] of Object.entries(values)) { - const realProfile = profiles.find( - ({ id, parentProfileId }) => - id === profileId || parentProfileId === profileId - ); - const convertedValues = Object.fromEntries( - Object.entries(profileValues).map(([valueId, valueValue]) => { - const refId = realProfile?.benchmark.valueDefinitions.find( - ({ id }) => id === valueId - )?.refId; - - return [refId || valueId, valueValue]; - }) - ); - - await policyMutation(realProfile.id, { values: convertedValues }); - dispatchProgress(); - } - - return mutatedPolicy; - }; -}; - -export default usePolicy; diff --git a/src/Mutations/hooks/usePolicy.test.js b/src/Mutations/hooks/usePolicy.test.js deleted file mode 100644 index d26fb72ec..000000000 --- a/src/Mutations/hooks/usePolicy.test.js +++ /dev/null @@ -1,196 +0,0 @@ -import useCreateBusinessObjective from './useCreateBusinessObjective'; -import usePolicyMutation from './usePolicyMutation'; -import useAssociateSystems from './useAssociateSystems'; -import useAssociateRules from './useAssociateRules'; -import { default as usePolicyHook } from './usePolicy'; - -jest.mock('./useCreateBusinessObjective'); -jest.mock('./usePolicyMutation'); -jest.mock('./useAssociateSystems'); -jest.mock('./useAssociateRules'); - -describe('usePolicy', () => { - afterEach(() => { - [ - useCreateBusinessObjective, - usePolicyMutation, - useAssociateSystems, - useAssociateSystems, - ].forEach((mock) => { - mock.mockReset(); - }); - }); - - it('creates new policy with no system and rule associations', async () => { - const updatedPolicy = { - name: 'New policy', - hosts: [], - }; - - let progressCount = 0; - const onProgress = () => { - progressCount++; - }; - - const createBusinessObjective = jest.fn(async () => null); - const policyMutation = jest.fn(async () => ({ id: '1' })); - const associateSystems = jest.fn(async () => ({ - policy: { profiles: [] }, - })); - const associateRules = jest.fn(async () => {}); - - useCreateBusinessObjective.mockReturnValue(createBusinessObjective); - usePolicyMutation.mockReturnValue(policyMutation); - useAssociateSystems.mockReturnValue(associateSystems); - useAssociateRules.mockReturnValue(associateRules); - - const usePolicy = usePolicyHook(); - await usePolicy(null, updatedPolicy, onProgress); - - expect(createBusinessObjective).toBeCalled(); - expect(policyMutation).toBeCalledWith(undefined, updatedPolicy, null); - expect(associateSystems).toBeCalled(); - expect(associateRules).not.toBeCalled(); - - expect(progressCount).toBe(3); - }); - - it('creates new policy with systems, tailoring, and business objective', async () => { - const hosts = [ - { id: 'h1', osVersion: '7.1' }, - { id: 'h2', osVersion: '7.2' }, - ]; - const profile1SelectedRuleRefIds = { - id: '1', - ruleRefIds: ['ref1', 'ref2'], - }; - const profile2SelectedRuleRefIds = { - id: '2', - ruleRefIds: ['ref3', 'ref4'], - }; - const selectedRuleRefIds = [ - profile1SelectedRuleRefIds, - profile2SelectedRuleRefIds, - ]; - const businessObjective = { - title: 'New Objective', - }; - const updatedPolicy = { - name: 'New policy', - hosts, - selectedRuleRefIds, - businessObjective, - }; - - let progressCount = 0; - const onProgress = () => { - progressCount++; - }; - - const newPolicyProfiles = [{ id: '1' }, { id: '2' }]; - - const createBusinessObjective = jest.fn(async () => 'BOid'); - const policyMutation = jest.fn(async () => ({ id: '3' })); - const associateSystems = jest.fn(async () => ({ - policy: { profiles: newPolicyProfiles }, - })); - const associateRules = jest.fn(async () => {}); - - useCreateBusinessObjective.mockReturnValue(createBusinessObjective); - usePolicyMutation.mockReturnValue(policyMutation); - useAssociateSystems.mockReturnValue(associateSystems); - useAssociateRules.mockReturnValue(associateRules); - - const usePolicy = usePolicyHook(); - await usePolicy(null, updatedPolicy, onProgress); - - expect(createBusinessObjective).toBeCalledWith(null, businessObjective); - expect(policyMutation).toBeCalledWith(undefined, updatedPolicy, 'BOid'); - expect(associateSystems).toBeCalledWith({ id: '3' }, hosts); - expect(associateRules).toBeCalledWith( - profile1SelectedRuleRefIds, - newPolicyProfiles - ); - expect(associateRules).toBeCalledWith( - profile2SelectedRuleRefIds, - newPolicyProfiles - ); - expect(associateRules).toBeCalledTimes(2); - expect(progressCount).toBe(5); - }); - - it('updates policy, its systems, tailoring, and business objective', async () => { - const oldPolicy = { - id: '3', - name: 'Old policy', - businessObjective: { - title: 'Old Objective', - }, - }; - - const hosts = [ - { id: 'h1', osVersion: '7.1' }, - { id: 'h2', osVersion: '7.2' }, - ]; - const profile1SelectedRuleRefIds = { - id: '1', - ruleRefIds: ['ref1', 'ref2'], - }; - const profile2SelectedRuleRefIds = { - id: '2', - ruleRefIds: ['ref3', 'ref4'], - }; - const selectedRuleRefIds = [ - profile1SelectedRuleRefIds, - profile2SelectedRuleRefIds, - ]; - const businessObjective = { - title: 'New Objective', - }; - const updatedPolicy = { - name: 'Updated policy', - hosts, - selectedRuleRefIds, - businessObjective, - }; - - let progressCount = 0; - const onProgress = () => { - progressCount++; - }; - - const newPolicyProfiles = [{ id: '1' }, { id: '2' }]; - - const createBusinessObjective = jest.fn(async () => 'BOid'); - const policyMutation = jest.fn(async () => ({ id: '3' })); - const associateSystems = jest.fn(async () => ({ - policy: { profiles: newPolicyProfiles }, - })); - const associateRules = jest.fn(async () => {}); - - useCreateBusinessObjective.mockReturnValue(createBusinessObjective); - usePolicyMutation.mockReturnValue(policyMutation); - useAssociateSystems.mockReturnValue(associateSystems); - useAssociateRules.mockReturnValue(associateRules); - - const usePolicy = usePolicyHook(); - await usePolicy(oldPolicy, updatedPolicy, onProgress); - - expect(createBusinessObjective).toBeCalledWith( - oldPolicy, - businessObjective - ); - expect(policyMutation).toBeCalledWith('3', updatedPolicy, 'BOid'); - expect(associateSystems).toBeCalledWith(oldPolicy, hosts); - expect(associateRules).toBeCalledWith( - profile1SelectedRuleRefIds, - newPolicyProfiles - ); - expect(associateRules).toBeCalledWith( - profile2SelectedRuleRefIds, - newPolicyProfiles - ); - expect(associateRules).toBeCalledTimes(2); - expect(progressCount).toBe(5); - }); -}); diff --git a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicy.js b/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicy.js index 40e03cafb..c4907d5b1 100644 --- a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicy.js +++ b/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicy.js @@ -1,21 +1,142 @@ -import React from 'react'; -import useAPIV2FeatureFlag from '../../../Utilities/hooks/useAPIV2FeatureFlag'; -import { Bullseye, Spinner } from '@patternfly/react-core'; -import FinishedCreatePolicyRest from './FinishedCreatePolicyRest'; -import FinishedCreatePolicyGraphQL from './FinishedCreatePolicyGraphQL'; +import React, { useCallback } from 'react'; +import FinishedCreatePolicyBase from './FinishedCreatePolicyBase'; +import useCreatePolicy from '../../../Utilities/hooks/api/useCreatePolicy'; +import useAssignRules from '../../../Utilities/hooks/api/useAssignRules'; +import useAssignSystems from '../../../Utilities/hooks/api/useAssignSystems'; +import useTailorings from '../../../Utilities/hooks/api/useTailorings'; +import useUpdateTailoring from '../../../Utilities/hooks/api/useUpdateTailoring'; -const FinishedCreatePolicy = (props) => { - const apiV2Enabled = useAPIV2FeatureFlag(); - - return apiV2Enabled === undefined ? ( - - - - ) : apiV2Enabled ? ( - - ) : ( - +export const useUpdatePolicy = () => { + const { fetch: createPolicy } = useCreatePolicy({ skip: true }); + const { fetch: assignRules } = useAssignRules({ skip: true }); + const { fetch: assignSystems } = useAssignSystems({ skip: true }); + const { fetch: fetchTailorings } = useTailorings({ skip: true }); + const { fetch: updateTailoring } = useUpdateTailoring({ skip: true }); // to update value overrides + + const updatedPolicy = useCallback( + async ( + _, + { + name, + description, + businessObjective, + complianceThreshold, + benchmarkId, + selectedRuleRefIds, + hosts, + valueOverrides, + }, + onProgress + ) => { + const minorVersions = selectedRuleRefIds.map( + ({ osMinorVersion }) => osMinorVersion + ); + const expectedUpdates = + 3 + minorVersions.length + Object.keys(valueOverrides).length; + let progress = 0; + + const dispatchProgress = () => { + if (onProgress) { + onProgress(++progress / expectedUpdates); + } + }; + + const createPolicyResponse = await createPolicy( + [ + undefined, + { + title: name, + description, + business_objective: businessObjective.title, + compliance_threshold: complianceThreshold, + profile_id: benchmarkId, + }, + ], + false + ); + + dispatchProgress(); + + const { id: newPolicyId } = createPolicyResponse.data; + + await assignSystems( + [newPolicyId, undefined, { ids: hosts.map(({ id }) => id) }], + false + ); + + dispatchProgress(); + + const fetchPolicyResponse = await fetchTailorings( + [ + newPolicyId, + undefined, + 100 /** to ensure we fetch all tailorings at once */, + ], + false + ); + const tailorings = fetchPolicyResponse.data; + + dispatchProgress(); + + for (const tailoring of tailorings) { + const rulesToAssign = selectedRuleRefIds.find( + ({ osMinorVersion }) => + Number(osMinorVersion) === tailoring.os_minor_version + ).ruleRefIds; + + await assignRules( + [ + newPolicyId, + tailoring.id, + undefined, + { + ids: rulesToAssign, + }, + ], + false + ); + + dispatchProgress(); + } + + for (const tailoring of tailorings) { + const tailoringValueOverrides = + valueOverrides[tailoring.os_minor_version]; + + if ( + tailoringValueOverrides === undefined || + Object.keys(tailoringValueOverrides).length === 0 + ) { + continue; + } + + await updateTailoring( + [ + newPolicyId, + tailoring.id, + undefined, + { + value_overrides: tailoringValueOverrides, + }, + ], + false + ); + + dispatchProgress(); + } + + return { id: newPolicyId }; + }, + [createPolicy, assignSystems, fetchTailorings, assignRules, updateTailoring] ); + + return updatedPolicy; +}; + +const FinishedCreatePolicy = (props) => { + const updatePolicy = useUpdatePolicy(); + + return ; }; export default FinishedCreatePolicy; diff --git a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicy.test.js b/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicy.test.js index e40428798..4d486bbcb 100644 --- a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicy.test.js +++ b/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicy.test.js @@ -1,45 +1,177 @@ import TestWrapper from '@redhat-cloud-services/frontend-components-utilities/TestingUtils/JestUtils/TestWrapper'; -import FinishedCreatePolicyRest from './FinishedCreatePolicyRest'; -import FinishedCreatePolicyGraphQL from './FinishedCreatePolicyGraphQL'; -import FinishedCreatePolicy from './FinishedCreatePolicy'; -import useAPIV2FeatureFlag from '../../../Utilities/hooks/useAPIV2FeatureFlag'; -import { render, screen } from '@testing-library/react'; +import { render, renderHook } from '@testing-library/react'; +import FinishedCreatePolicy, { useUpdatePolicy } from './FinishedCreatePolicy'; +import useCreatePolicy from '../../../Utilities/hooks/api/useCreatePolicy'; +import useAssignRules from '../../../Utilities/hooks/api/useAssignRules'; +import useAssignSystems from '../../../Utilities/hooks/api/useAssignSystems'; +import useTailorings from '../../../Utilities/hooks/api/useTailorings'; +import useUpdateTailoring from '../../../Utilities/hooks/api/useUpdateTailoring'; +import FinishedCreatePolicyBase from './FinishedCreatePolicyBase'; -jest.mock('./FinishedCreatePolicyRest'); -jest.mock('./FinishedCreatePolicyGraphQL'); -jest.mock('../../../Utilities/hooks/useAPIV2FeatureFlag'); +jest.mock('./FinishedCreatePolicyBase', () => jest.fn()); +jest.mock('../../../Utilities/hooks/api/useCreatePolicy'); +jest.mock('../../../Utilities/hooks/api/useAssignRules'); +jest.mock('../../../Utilities/hooks/api/useAssignSystems'); +jest.mock('../../../Utilities/hooks/api/useTailorings'); +jest.mock('../../../Utilities/hooks/api/useUpdateTailoring'); describe('FinishedCreatePolicy', () => { - it('renders the loading state when the flag is not yet fetched', () => { - useAPIV2FeatureFlag.mockReturnValue(undefined); + it('renders the base component with update policy callback', () => { render( ); - screen.getByRole('progressbar'); + expect(FinishedCreatePolicyBase).toBeCalledWith( + { updatePolicy: expect.anything() }, + expect.anything() + ); }); +}); - it('renders the GraphQL component if the flag is off', () => { - useAPIV2FeatureFlag.mockReturnValue(false); - render( - - - +describe('useUpdatePolicy', () => { + const createdPolicyId = '93529cba-c28e-4a29-8ac3-058689a0b7d1'; + const createdTailoringId = '86412da4-ab6b-4783-82e3-74ee6a3cc270'; + const fetchTailoringsResponse = { + data: [{ id: createdTailoringId, os_minor_version: 7 }], + }; + + let createPolicy = jest.fn(() => ({ + data: { id: createdPolicyId }, + })); + let assignRules = jest.fn(); + let assignSystems = jest.fn(); + let fetchTailorings = jest.fn(() => fetchTailoringsResponse); + let updateTailoring = jest.fn(); + + useCreatePolicy.mockImplementation(() => ({ fetch: createPolicy })); + useAssignRules.mockImplementation(() => ({ fetch: assignRules })); + useAssignSystems.mockImplementation(() => ({ fetch: assignSystems })); + useTailorings.mockImplementation(() => ({ fetch: fetchTailorings })); + useUpdateTailoring.mockImplementation(() => ({ fetch: updateTailoring })); + + const onProgress = jest.fn(); + + afterEach(() => { + useCreatePolicy.mockClear(); + useAssignRules.mockClear(); + useAssignSystems.mockClear(); + useTailorings.mockClear(); + useUpdateTailoring.mockClear(); + onProgress.mockClear(); + }); + + const policyDataToSubmit = { + name: 'Some policy name', + description: 'Some policy description', + businessObjective: { title: 'Some defined business objective' }, + complianceThreshold: 100, + benchmarkId: '4610092b-2e7f-4155-b101-964fede3566b', + selectedRuleRefIds: [ + { + ruleRefIds: ['474129f9-1168-429e-833a-8d69e38284b8'], + osMinorVersion: 7, + }, + ], + hosts: [{ id: '166ba579-20a6-436b-a712-5b1f5085a9eb' }], + valueOverrides: {}, + }; + + it('calls create policy with the submitted data', async () => { + const { result } = renderHook(() => useUpdatePolicy()); + + await result.current(undefined, policyDataToSubmit, onProgress); + + expect(createPolicy).toBeCalledWith( + [ + undefined, // X-RH identity + { + business_objective: policyDataToSubmit.businessObjective.title, + compliance_threshold: policyDataToSubmit.complianceThreshold, + description: policyDataToSubmit.description, + profile_id: policyDataToSubmit.benchmarkId, + title: policyDataToSubmit.name, + }, + ], + false // to return response data + ); + }); + + it('assigns systems', async () => { + const { result } = renderHook(() => useUpdatePolicy()); + + await result.current(undefined, policyDataToSubmit, onProgress); + + expect(assignSystems).toBeCalledWith( + [ + createdPolicyId, + undefined, // X-RH identity + { + ids: policyDataToSubmit.hosts.map(({ id }) => id), + }, + ], + false // to return response data ); + }); + + it('fetches tailorings and adds rules to these tailorings', async () => { + const { result } = renderHook(() => useUpdatePolicy()); + + await result.current(undefined, policyDataToSubmit, onProgress); - expect(FinishedCreatePolicyGraphQL).toBeCalled(); + expect(fetchTailorings).toBeCalledWith( + [ + createdPolicyId, + undefined, // X-RH identity + 100, + ], + false // to return response data + ); + + expect(assignRules).toBeCalledWith( + [ + createdPolicyId, + createdTailoringId, + undefined, + { ids: policyDataToSubmit.selectedRuleRefIds[0].ruleRefIds }, + ], + false + ); }); - it('renders the REST API component if the flag is on', () => { - useAPIV2FeatureFlag.mockReturnValue(true); - render( - - - + it('updates value overrides when there are any', async () => { + const { result } = renderHook(() => useUpdatePolicy()); + + await result.current( + undefined, + { + ...policyDataToSubmit, + valueOverrides: { + [fetchTailoringsResponse.data[0].os_minor_version]: { + test_id_1: 'test_value_1', + }, + }, + }, + onProgress ); - expect(FinishedCreatePolicyRest).toBeCalled(); + expect(updateTailoring).toBeCalledWith( + [ + createdPolicyId, + createdTailoringId, + undefined, + { value_overrides: { test_id_1: 'test_value_1' } }, + ], + false + ); + }); + + it('updates progress', async () => { + const { result } = renderHook(() => useUpdatePolicy()); + + await result.current(undefined, policyDataToSubmit, onProgress); + + expect(onProgress).toBeCalledTimes(4); }); }); diff --git a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyGraphQL.js b/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyGraphQL.js deleted file mode 100644 index 78d151776..000000000 --- a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyGraphQL.js +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import FinishedCreatePolicyBase from './FinishedCreatePolicyBase'; -import { usePolicy } from '../../../Mutations'; - -const FinishedCreatePolicyGraphQL = (props) => { - const updatePolicy = usePolicy(); - - return ; -}; - -export default FinishedCreatePolicyGraphQL; diff --git a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyGraphQL.test.js b/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyGraphQL.test.js deleted file mode 100644 index 45cb22487..000000000 --- a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyGraphQL.test.js +++ /dev/null @@ -1,36 +0,0 @@ -import { render } from '@testing-library/react'; -import TestWrapper from '@redhat-cloud-services/frontend-components-utilities/TestingUtils/JestUtils/TestWrapper'; -import FinishedCreatePolicyGraphQL from './FinishedCreatePolicyGraphQL'; -import FinishedCreatePolicyBase from './FinishedCreatePolicyBase'; -import { usePolicy } from '../../../Mutations'; - -jest.mock('./FinishedCreatePolicyBase', () => jest.fn()); -jest.mock('../../../Mutations'); - -describe('FinishedCreatePolicyGraphQL', () => { - it('passes params to the base component', () => { - const params = { test: 'xyz' }; - render( - - - - ); - - expect(FinishedCreatePolicyBase).toBeCalledWith( - { - ...params, - }, - expect.anything() - ); - }); - - it('uses update policy mutation', () => { - render( - - - - ); - - expect(usePolicy).toBeCalled(); - }); -}); diff --git a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyRest.js b/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyRest.js deleted file mode 100644 index 1e6ac8d1a..000000000 --- a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyRest.js +++ /dev/null @@ -1,142 +0,0 @@ -import React, { useCallback } from 'react'; -import FinishedCreatePolicyBase from './FinishedCreatePolicyBase'; -import useCreatePolicy from '../../../Utilities/hooks/api/useCreatePolicy'; -import useAssignRules from '../../../Utilities/hooks/api/useAssignRules'; -import useAssignSystems from '../../../Utilities/hooks/api/useAssignSystems'; -import useTailorings from '../../../Utilities/hooks/api/useTailorings'; -import useUpdateTailoring from '../../../Utilities/hooks/api/useUpdateTailoring'; - -export const useUpdatePolicy = () => { - const { fetch: createPolicy } = useCreatePolicy({ skip: true }); - const { fetch: assignRules } = useAssignRules({ skip: true }); - const { fetch: assignSystems } = useAssignSystems({ skip: true }); - const { fetch: fetchTailorings } = useTailorings({ skip: true }); - const { fetch: updateTailoring } = useUpdateTailoring({ skip: true }); // to update value overrides - - const updatedPolicy = useCallback( - async ( - _, - { - name, - description, - businessObjective, - complianceThreshold, - benchmarkId, - selectedRuleRefIds, - hosts, - valueOverrides, - }, - onProgress - ) => { - const minorVersions = selectedRuleRefIds.map( - ({ osMinorVersion }) => osMinorVersion - ); - const expectedUpdates = - 3 + minorVersions.length + Object.keys(valueOverrides).length; - let progress = 0; - - const dispatchProgress = () => { - if (onProgress) { - onProgress(++progress / expectedUpdates); - } - }; - - const createPolicyResponse = await createPolicy( - [ - undefined, - { - title: name, - description, - business_objective: businessObjective.title, - compliance_threshold: complianceThreshold, - profile_id: benchmarkId, - }, - ], - false - ); - - dispatchProgress(); - - const { id: newPolicyId } = createPolicyResponse.data; - - await assignSystems( - [newPolicyId, undefined, { ids: hosts.map(({ id }) => id) }], - false - ); - - dispatchProgress(); - - const fetchPolicyResponse = await fetchTailorings( - [ - newPolicyId, - undefined, - 100 /** to ensure we fetch all tailorings at once */, - ], - false - ); - const tailorings = fetchPolicyResponse.data; - - dispatchProgress(); - - for (const tailoring of tailorings) { - const rulesToAssign = selectedRuleRefIds.find( - ({ osMinorVersion }) => - Number(osMinorVersion) === tailoring.os_minor_version - ).ruleRefIds; - - await assignRules( - [ - newPolicyId, - tailoring.id, - undefined, - { - ids: rulesToAssign, - }, - ], - false - ); - - dispatchProgress(); - } - - for (const tailoring of tailorings) { - const tailoringValueOverrides = - valueOverrides[tailoring.os_minor_version]; - - if ( - tailoringValueOverrides === undefined || - Object.keys(tailoringValueOverrides).length === 0 - ) { - continue; - } - - await updateTailoring( - [ - newPolicyId, - tailoring.id, - undefined, - { - value_overrides: tailoringValueOverrides, - }, - ], - false - ); - - dispatchProgress(); - } - - return { id: newPolicyId }; - }, - [createPolicy, assignSystems, fetchTailorings, assignRules, updateTailoring] - ); - - return updatedPolicy; -}; - -const FinishedCreatePolicyRest = (props) => { - const updatePolicy = useUpdatePolicy(); - - return ; -}; - -export default FinishedCreatePolicyRest; diff --git a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyRest.test.js b/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyRest.test.js deleted file mode 100644 index fb7bcef84..000000000 --- a/src/SmartComponents/CreatePolicy/FinishedCreatePolicy/FinishedCreatePolicyRest.test.js +++ /dev/null @@ -1,179 +0,0 @@ -import TestWrapper from '@redhat-cloud-services/frontend-components-utilities/TestingUtils/JestUtils/TestWrapper'; -import { render, renderHook } from '@testing-library/react'; -import FinishedCreatePolicyRest, { - useUpdatePolicy, -} from './FinishedCreatePolicyRest'; -import useCreatePolicy from '../../../Utilities/hooks/api/useCreatePolicy'; -import useAssignRules from '../../../Utilities/hooks/api/useAssignRules'; -import useAssignSystems from '../../../Utilities/hooks/api/useAssignSystems'; -import useTailorings from '../../../Utilities/hooks/api/useTailorings'; -import useUpdateTailoring from '../../../Utilities/hooks/api/useUpdateTailoring'; -import FinishedCreatePolicyBase from './FinishedCreatePolicyBase'; - -jest.mock('./FinishedCreatePolicyBase', () => jest.fn()); -jest.mock('../../../Utilities/hooks/api/useCreatePolicy'); -jest.mock('../../../Utilities/hooks/api/useAssignRules'); -jest.mock('../../../Utilities/hooks/api/useAssignSystems'); -jest.mock('../../../Utilities/hooks/api/useTailorings'); -jest.mock('../../../Utilities/hooks/api/useUpdateTailoring'); - -describe('FinishedCreatePolicyRest', () => { - it('renders the base component with update policy callback', () => { - render( - - - - ); - - expect(FinishedCreatePolicyBase).toBeCalledWith( - { updatePolicy: expect.anything() }, - expect.anything() - ); - }); -}); - -describe('useUpdatePolicy', () => { - const createdPolicyId = '93529cba-c28e-4a29-8ac3-058689a0b7d1'; - const createdTailoringId = '86412da4-ab6b-4783-82e3-74ee6a3cc270'; - const fetchTailoringsResponse = { - data: [{ id: createdTailoringId, os_minor_version: 7 }], - }; - - let createPolicy = jest.fn(() => ({ - data: { id: createdPolicyId }, - })); - let assignRules = jest.fn(); - let assignSystems = jest.fn(); - let fetchTailorings = jest.fn(() => fetchTailoringsResponse); - let updateTailoring = jest.fn(); - - useCreatePolicy.mockImplementation(() => ({ fetch: createPolicy })); - useAssignRules.mockImplementation(() => ({ fetch: assignRules })); - useAssignSystems.mockImplementation(() => ({ fetch: assignSystems })); - useTailorings.mockImplementation(() => ({ fetch: fetchTailorings })); - useUpdateTailoring.mockImplementation(() => ({ fetch: updateTailoring })); - - const onProgress = jest.fn(); - - afterEach(() => { - useCreatePolicy.mockClear(); - useAssignRules.mockClear(); - useAssignSystems.mockClear(); - useTailorings.mockClear(); - useUpdateTailoring.mockClear(); - onProgress.mockClear(); - }); - - const policyDataToSubmit = { - name: 'Some policy name', - description: 'Some policy description', - businessObjective: { title: 'Some defined business objective' }, - complianceThreshold: 100, - benchmarkId: '4610092b-2e7f-4155-b101-964fede3566b', - selectedRuleRefIds: [ - { - ruleRefIds: ['474129f9-1168-429e-833a-8d69e38284b8'], - osMinorVersion: 7, - }, - ], - hosts: [{ id: '166ba579-20a6-436b-a712-5b1f5085a9eb' }], - valueOverrides: {}, - }; - - it('calls create policy with the submitted data', async () => { - const { result } = renderHook(() => useUpdatePolicy()); - - await result.current(undefined, policyDataToSubmit, onProgress); - - expect(createPolicy).toBeCalledWith( - [ - undefined, // X-RH identity - { - business_objective: policyDataToSubmit.businessObjective.title, - compliance_threshold: policyDataToSubmit.complianceThreshold, - description: policyDataToSubmit.description, - profile_id: policyDataToSubmit.benchmarkId, - title: policyDataToSubmit.name, - }, - ], - false // to return response data - ); - }); - - it('assigns systems', async () => { - const { result } = renderHook(() => useUpdatePolicy()); - - await result.current(undefined, policyDataToSubmit, onProgress); - - expect(assignSystems).toBeCalledWith( - [ - createdPolicyId, - undefined, // X-RH identity - { - ids: policyDataToSubmit.hosts.map(({ id }) => id), - }, - ], - false // to return response data - ); - }); - - it('fetches tailorings and adds rules to these tailorings', async () => { - const { result } = renderHook(() => useUpdatePolicy()); - - await result.current(undefined, policyDataToSubmit, onProgress); - - expect(fetchTailorings).toBeCalledWith( - [ - createdPolicyId, - undefined, // X-RH identity - 100, - ], - false // to return response data - ); - - expect(assignRules).toBeCalledWith( - [ - createdPolicyId, - createdTailoringId, - undefined, - { ids: policyDataToSubmit.selectedRuleRefIds[0].ruleRefIds }, - ], - false - ); - }); - - it('updates value overrides when there are any', async () => { - const { result } = renderHook(() => useUpdatePolicy()); - - await result.current( - undefined, - { - ...policyDataToSubmit, - valueOverrides: { - [fetchTailoringsResponse.data[0].os_minor_version]: { - test_id_1: 'test_value_1', - }, - }, - }, - onProgress - ); - - expect(updateTailoring).toBeCalledWith( - [ - createdPolicyId, - createdTailoringId, - undefined, - { value_overrides: { test_id_1: 'test_value_1' } }, - ], - false - ); - }); - - it('updates progress', async () => { - const { result } = renderHook(() => useUpdatePolicy()); - - await result.current(undefined, policyDataToSubmit, onProgress); - - expect(onProgress).toBeCalledTimes(4); - }); -}); diff --git a/src/SmartComponents/EditPolicy/EditPolicyGraphQL.js b/src/SmartComponents/EditPolicy/EditPolicyGraphQL.js index b3642712a..006164a1d 100644 --- a/src/SmartComponents/EditPolicy/EditPolicyGraphQL.js +++ b/src/SmartComponents/EditPolicy/EditPolicyGraphQL.js @@ -36,7 +36,7 @@ export const EditPolicyGraphQL = ({ route }) => { isClose ? `/scappolicies/${policyId}` : location.state?.returnTo || -1 ); - const [isSaving, onSave] = useOnSave(policy, updatedPolicyHostsAndRules, { + const [isSaving, onSave] = useOnSave(policyId, updatedPolicyHostsAndRules, { onSave: onSaveCallback, onError: onSaveCallback, }); diff --git a/src/SmartComponents/EditPolicy/EditPolicyRest.js b/src/SmartComponents/EditPolicy/EditPolicyRest.js index c8df7159f..790ec8619 100644 --- a/src/SmartComponents/EditPolicy/EditPolicyRest.js +++ b/src/SmartComponents/EditPolicy/EditPolicyRest.js @@ -24,7 +24,7 @@ export const EditPolicyRest = ({ route }) => { data: { data: policy } = {}, loading: policyLoading, error: policyError, - } = usePolicy(policyId, { skip: policyId === undefined }); + } = usePolicy(policyId); const { data: { data: supportedProfiles } = {}, @@ -60,7 +60,7 @@ export const EditPolicyRest = ({ route }) => { isClose ? `/scappolicies/${policyId}` : location.state?.returnTo || -1 ); - const [isSaving, onSave] = useOnSave(policy, updatedPolicy, { + const [isSaving, onSave] = useOnSave(policyId, updatedPolicy, { onSave: onSaveCallback, onError: onSaveCallback, }); diff --git a/src/SmartComponents/EditPolicy/hooks.js b/src/SmartComponents/EditPolicy/hooks.js index 510c97947..4789ec3ca 100644 --- a/src/SmartComponents/EditPolicy/hooks.js +++ b/src/SmartComponents/EditPolicy/hooks.js @@ -1,13 +1,11 @@ import { useCallback, useState } from 'react'; -import { usePolicy } from 'Mutations'; import { dispatchNotification } from 'Utilities/Dispatcher'; -import useAPIV2FeatureFlag from 'Utilities/hooks/useAPIV2FeatureFlag'; import useAssignRules from '../../Utilities/hooks/api/useAssignRules'; import useAssignSystems from '../../Utilities/hooks/api/useAssignSystems'; import useTailorings from '../../Utilities/hooks/api/useTailorings'; import useUpdatePolicy from '../../Utilities/hooks/api/useUpdatePolicy'; -const useUpdatePolicyRest = (policy, updatedPolicyHostsAndRules) => { +const useUpdatePolicyRest = (policyId, updatedPolicyHostsAndRules) => { const { hosts, tailoringRules, @@ -22,8 +20,6 @@ const useUpdatePolicyRest = (policy, updatedPolicyHostsAndRules) => { const { fetch: updatePolicy } = useUpdatePolicy({ skip: true }); const updatePolicyRest = async () => { - const policyId = policy.id; - if (hosts !== undefined) { await assignSystems({ policyId, assignSystemsRequest: { ids: hosts } }); } @@ -66,17 +62,14 @@ const useUpdatePolicyRest = (policy, updatedPolicyHostsAndRules) => { }; export const useOnSave = ( - policy, + policyId, updatedPolicyHostsAndRules, { onSave: onSaveCallback, onError: onErrorCallback } = {} ) => { - const apiV2Enabled = useAPIV2FeatureFlag(); - const updatePolicyGraphQL = usePolicy(); - const updatePolicyRest = useUpdatePolicyRest( - policy, + const updatePolicy = useUpdatePolicyRest( + policyId, updatedPolicyHostsAndRules ); - const updatePolicy = apiV2Enabled ? updatePolicyRest : updatePolicyGraphQL; const [isSaving, setIsSaving] = useState(false); @@ -86,7 +79,7 @@ export const useOnSave = ( } setIsSaving(true); - updatePolicy(policy, updatedPolicyHostsAndRules) + updatePolicy() .then(() => { setIsSaving(false); dispatchNotification({ @@ -105,7 +98,7 @@ export const useOnSave = ( }); onErrorCallback?.(); }); - }, [isSaving, policy, updatedPolicyHostsAndRules]); + }, [isSaving, policyId, updatedPolicyHostsAndRules]); return [isSaving, onSave]; }; diff --git a/src/SmartComponents/EditPolicy/hooks.test.js b/src/SmartComponents/EditPolicy/hooks.test.js index 9c23d199b..f118ab6ca 100644 --- a/src/SmartComponents/EditPolicy/hooks.test.js +++ b/src/SmartComponents/EditPolicy/hooks.test.js @@ -1,9 +1,6 @@ import { renderHook, act, waitFor } from '@testing-library/react'; import { useOnSave } from './hooks'; -import { usePolicy } from 'Mutations'; -jest.mock('Mutations'); - import { dispatchNotification } from 'Utilities/Dispatcher'; jest.mock('Utilities/Dispatcher'); @@ -12,9 +9,6 @@ jest.mock('Utilities/hooks/useAnchor', () => ({ default: () => () => ({}), })); -import useAPIV2FeatureFlag from '../../Utilities/hooks/useAPIV2FeatureFlag'; -jest.mock('../../Utilities/hooks/useAPIV2FeatureFlag'); - describe('useOnSave', function () { const policy = {}; const updatedPolicy = {}; @@ -26,13 +20,9 @@ describe('useOnSave', function () { onSaveCallBack.mockReset(); onErrorCallback.mockReset(); dispatchNotification.mockImplementation(mockedNotification); - useAPIV2FeatureFlag.mockImplementation(() => false); }); it('returns a function to call with a policy and updated policy', async () => { - usePolicy.mockImplementation(() => { - return () => Promise.resolve(); - }); const { result } = renderHook(() => useOnSave(policy, updatedPolicy, { onSave: onSaveCallBack, @@ -58,9 +48,6 @@ describe('useOnSave', function () { }); it('returns a function to call with a policy and updated policy and can raise an error', async () => { - usePolicy.mockImplementation(() => { - return () => Promise.reject({}); - }); const { result } = renderHook(() => useOnSave(policy, updatedPolicy, { onSave: onSaveCallBack, From 1a4e29e71f5e76b49e4b801c933b032dcf4f5867 Mon Sep 17 00:00:00 2001 From: Georgii Karataev Date: Thu, 19 Dec 2024 15:40:08 +0100 Subject: [PATCH 2/2] chore: Remove EditPolicy-related GraphQL implementation And also clean up some tests like hooks.test.js. --- src/Mutations/index.js | 1 - src/SmartComponents/EditPolicy/EditPolicy.js | 174 +++++++++++-- .../EditPolicy/EditPolicy.test.js | 238 +++++++++++++++-- .../EditPolicy/EditPolicyForm.js | 94 +++---- .../EditPolicy/EditPolicyForm.test.js | 56 ---- .../EditPolicy/EditPolicyFormRest.js | 96 ------- .../EditPolicy/EditPolicyGraphQL.js | 126 --------- .../EditPolicy/EditPolicyRest.js | 159 ------------ .../EditPolicy/EditPolicyRest.test.js | 229 ---------------- .../EditPolicy/EditPolicyRulesTab.js | 245 +++++++----------- .../EditPolicy/EditPolicyRulesTab.test.js | 97 +++---- .../EditPolicy/EditPolicyRulesTabRest.js | 169 ------------ .../EditPolicy/EditPolicyRulesTabRest.test.js | 64 ----- .../EditPolicy/EditPolicySystemsTab.js | 73 +++--- .../EditPolicy/EditPolicySystemsTab.test.js | 103 +++++++- .../EditPolicy/EditPolicySystemsTabRest.js | 90 ------- .../EditPolicySystemsTabRest.test.js | 105 -------- .../__snapshots__/EditPolicyForm.test.js.snap | 241 ----------------- .../EditPolicyRulesTab.test.js.snap | 40 --- src/SmartComponents/EditPolicy/constants.js | 115 -------- src/SmartComponents/EditPolicy/hooks.test.js | 17 +- .../EditPolicy/hooks/usePolicyQueries.js | 58 ----- 22 files changed, 678 insertions(+), 1912 deletions(-) delete mode 100644 src/SmartComponents/EditPolicy/EditPolicyForm.test.js delete mode 100644 src/SmartComponents/EditPolicy/EditPolicyFormRest.js delete mode 100644 src/SmartComponents/EditPolicy/EditPolicyGraphQL.js delete mode 100644 src/SmartComponents/EditPolicy/EditPolicyRest.js delete mode 100644 src/SmartComponents/EditPolicy/EditPolicyRest.test.js delete mode 100644 src/SmartComponents/EditPolicy/EditPolicyRulesTabRest.js delete mode 100644 src/SmartComponents/EditPolicy/EditPolicyRulesTabRest.test.js delete mode 100644 src/SmartComponents/EditPolicy/EditPolicySystemsTabRest.js delete mode 100644 src/SmartComponents/EditPolicy/EditPolicySystemsTabRest.test.js delete mode 100644 src/SmartComponents/EditPolicy/__snapshots__/EditPolicyForm.test.js.snap delete mode 100644 src/SmartComponents/EditPolicy/__snapshots__/EditPolicyRulesTab.test.js.snap delete mode 100644 src/SmartComponents/EditPolicy/constants.js delete mode 100644 src/SmartComponents/EditPolicy/hooks/usePolicyQueries.js diff --git a/src/Mutations/index.js b/src/Mutations/index.js index ae4c02139..b7710e738 100644 --- a/src/Mutations/index.js +++ b/src/Mutations/index.js @@ -1,2 +1 @@ export * from './graphql/mutations'; -export { default as usePolicy } from './hooks/usePolicy'; diff --git a/src/SmartComponents/EditPolicy/EditPolicy.js b/src/SmartComponents/EditPolicy/EditPolicy.js index 58709bea2..d86d4b2a3 100644 --- a/src/SmartComponents/EditPolicy/EditPolicy.js +++ b/src/SmartComponents/EditPolicy/EditPolicy.js @@ -1,21 +1,161 @@ -import React from 'react'; -import { Bullseye, Spinner } from '@patternfly/react-core'; -import useAPIV2FeatureFlag from '@/Utilities/hooks/useAPIV2FeatureFlag'; -import { EditPolicyGraphQL } from './EditPolicyGraphQL'; -import { EditPolicyRest } from './EditPolicyRest'; - -const EditPolicy = (props) => { - const apiV2Enabled = useAPIV2FeatureFlag(); - - return apiV2Enabled === undefined ? ( - - - - ) : apiV2Enabled === true ? ( - - ) : ( - +import React, { useState } from 'react'; +import propTypes from 'prop-types'; +import { Button, Spinner } from '@patternfly/react-core'; +import { useLocation, useParams } from 'react-router-dom'; +import useNavigate from '@redhat-cloud-services/frontend-components-utilities/useInsightsNavigate'; +import { useTitleEntity } from 'Utilities/hooks/useDocumentTitle'; +import { + ComplianceModal, + StateViewWithError, + StateViewPart, +} from 'PresentationalComponents'; +import EditPolicyForm from './EditPolicyForm'; +import { useOnSave } from './hooks'; +import usePolicy from 'Utilities/hooks/api/usePolicy'; +import useAssignedRules from './hooks/useAssignedRules'; +import useAssignedSystems from './hooks/useAssignedSystems'; +import useSupportedProfiles from 'Utilities/hooks/api/useSupportedProfiles'; + +export const EditPolicy = ({ route }) => { + const navigate = useNavigate(); + const { policy_id: policyId } = useParams(); + const location = useLocation(); + const { + data: { data: policy } = {}, + loading: policyLoading, + error: policyError, + } = usePolicy(policyId); + + const { + data: { data: supportedProfiles } = {}, + error: supportedProfilesError, + loading: supportedProfilesLoading, + } = useSupportedProfiles({ + params: { + filter: `os_major_version=${policy?.os_major_version}`, + limit: 100, + }, + skip: policyLoading || !policy?.os_major_version, + }); + + const securityGuide = supportedProfiles?.find( + (profile) => profile.ref_id === policy?.ref_id + ); + + const supportedOsVersions = securityGuide?.os_minor_versions || []; + + const { assignedRuleIds, assignedRulesLoading } = useAssignedRules(policyId); + const { assignedSystems, assignedSystemsLoading } = useAssignedSystems( + policyId, + policy, + policyLoading + ); + + const [updatedPolicy, setUpdatedPolicy] = useState(null); + + const saveEnabled = !updatedPolicy; + + const onSaveCallback = (isClose) => + navigate( + isClose ? `/scappolicies/${policyId}` : location.state?.returnTo || -1 + ); + + const [isSaving, onSave] = useOnSave(policyId, updatedPolicy, { + onSave: onSaveCallback, + onError: onSaveCallback, + }); + + const setRuleValues = ( + _policy, + tailoring, + valueDefinition, + newValue, + closeInlineEdit + ) => { + setUpdatedPolicy((prev) => ({ + ...prev, + value: { + ...prev.value, + [tailoring.id]: { + ...tailoring.value_overrides, + [valueDefinition.id]: newValue, + }, + }, + })); + + closeInlineEdit(); + }; + const actions = [ + , + , + ]; + + useTitleEntity(route, policy?.title); + + const statusValues = { + data: policy && supportedProfiles && assignedRuleIds, + loading: + policyLoading || + supportedProfilesLoading || + assignedSystemsLoading || + assignedRulesLoading, + error: policyError || supportedProfilesError, + }; + + return ( + onSaveCallback(true)} + actions={actions} + > + + + + + + + + + ); }; +EditPolicy.propTypes = { + route: propTypes.object, +}; + export default EditPolicy; diff --git a/src/SmartComponents/EditPolicy/EditPolicy.test.js b/src/SmartComponents/EditPolicy/EditPolicy.test.js index 8f24955c4..16db1e084 100644 --- a/src/SmartComponents/EditPolicy/EditPolicy.test.js +++ b/src/SmartComponents/EditPolicy/EditPolicy.test.js @@ -1,36 +1,226 @@ -import { render, screen } from '@testing-library/react'; +import { useEffect } from 'react'; +import { render, screen, waitFor } from '@testing-library/react'; import '@testing-library/jest-dom'; -import TestWrapper from '@/Utilities/TestWrapper'; - import EditPolicy from './EditPolicy'; -jest.mock('Mutations'); -jest.mock('Utilities/hooks/useDocumentTitle', () => ({ - useTitleEntity: () => ({}), - setTitle: () => ({}), +import usePolicy from 'Utilities/hooks/api/usePolicy'; +import useSupportedProfiles from 'Utilities/hooks/api/useSupportedProfiles'; +import useAssignRules from 'Utilities/hooks/api/useAssignRules'; +import useAssignSystems from 'Utilities/hooks/api/useAssignSystems'; +import useTailorings from 'Utilities/hooks/api/useTailorings'; +import TestWrapper from '@redhat-cloud-services/frontend-components-utilities/TestingUtils/JestUtils/TestWrapper'; +import userEvent from '@testing-library/user-event'; +import useNavigate from '@redhat-cloud-services/frontend-components-utilities/useInsightsNavigate'; +import { apiInstance } from 'Utilities/hooks/useQuery'; +import EditPolicyForm from './EditPolicyForm'; +import { dispatchNotification } from 'Utilities/Dispatcher'; + +jest.mock('Utilities/hooks/api/useSupportedProfiles'); +jest.mock('Utilities/hooks/api/useAssignRules'); +jest.mock('Utilities/hooks/api/useAssignSystems'); +jest.mock('Utilities/hooks/api/usePolicy'); +jest.mock('Utilities/hooks/api/useTailorings'); +jest.mock('Utilities/Dispatcher', () => ({ dispatchNotification: jest.fn() })); +jest.mock('Mutations', () => ({ + usePolicy: jest.fn(), +})); +jest.mock( + '@redhat-cloud-services/frontend-components-utilities/useInsightsNavigate' +); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: jest.fn(() => ({ policy_id: 'test-policy-id' })), +})); +jest.mock('./EditPolicyForm'); +jest.mock('Utilities/hooks/useQuery/apiInstance', () => ({ + updatePolicy: jest.fn(() => Promise.resolve({})), + assignSystems: jest.fn(() => Promise.resolve({})), + assignRules: jest.fn(() => Promise.resolve({})), +})); + +const navigate = jest.fn(); +useNavigate.mockImplementation(() => navigate); +usePolicy.mockImplementation(() => ({ + data: { data: { title: 'test-policy', os_major_version: 7, id: 'test-id' } }, + loading: false, + error: false, })); -import useAPIV2FeatureFlag from '../../Utilities/hooks/useAPIV2FeatureFlag'; -jest.mock('../../Utilities/hooks/useAPIV2FeatureFlag'); +useTailorings.mockImplementation(() => ({ + data: [{ id: 'tailoring-id' }], + loading: false, + error: false, +})); + +[useAssignRules, useAssignSystems, useSupportedProfiles].forEach((hook) => { + hook.mockImplementation(() => ({ + data: { data: [{ id: 'test-id' }] }, + loading: false, + error: false, + })); +}); + +EditPolicyForm.mockImplementation(({ setUpdatedPolicy }) => { + useEffect(() => { + setUpdatedPolicy({ + tailoringRules: { + 'tailoring-id-1': ['rule-1'], + 'tailoring-id-2': ['rule-2'], + }, + hosts: ['system-1'], + value: { 'tailoring-id': { 'value-id': 'changed-value' } }, + }); + }, []); + + return
Mocked tabs
; +}); -describe('EditPolicy', () => { - beforeEach(() => { - useAPIV2FeatureFlag.mockImplementation(() => false); +const user = userEvent.setup(); +const defaultProp = { + route: { + title: 'test-page', + setTitle: jest.fn(), + }, +}; + +const renderComponent = (newProps = {}) => + render( + + + + ); + +// TODO: recover and review the tests +describe.skip('EditPolicy', () => { + it('Should have proper modal title', () => { + renderComponent(); + expect(screen.getByText(`Edit test-policy`)).toBeVisible(); + }); + + it('Should redirect to policy detail page on cancel button click', async () => { + renderComponent(); + + await user.click(screen.getByRole('button', { name: 'Cancel' })); + + expect(navigate).toHaveBeenCalledWith('/scappolicies/test-policy-id'); }); - const defaultProps = { - onClose: jest.fn(), - dispatch: jest.fn(), - change: jest.fn(), - }; - it('expect to render without error', () => { - render( - - - + it('Should display save button as disabled when no changes are made', () => { + renderComponent(); + + expect( + screen.getByRole('button', { + name: /save/i, + }) + ).toBeDisabled(); + }); + + it('Should call fetch APIs with correct params', async () => { + renderComponent(); + + [usePolicy, useAssignRules, useAssignSystems].forEach( + async (hook) => + await waitFor(() => { + expect(hook).toHaveBeenCalledWith('test-policy-id'); + }) ); - expect(screen.getByLabelText('Edit')).toBeInTheDocument(); + await waitFor(() => { + expect(useSupportedProfiles).toHaveBeenCalledWith({ + params: { filter: 'os_major_version=7', limit: 100 }, + skip: false, + }); + }); }); - // TODO Add test with proper mock data + it('Should submit form', async () => { + renderComponent(); + await waitFor(() => + expect( + screen.getByRole('button', { + name: /save/i, + }) + ).toBeEnabled() + ); + + await user.click( + screen.getByRole('button', { + name: /save/i, + }) + ); + + await waitFor(() => + expect(apiInstance.assignSystems).toHaveBeenCalledWith('test-id', null, { + ids: ['system-1'], + }) + ); + await waitFor(() => + expect(apiInstance.assignRules).toHaveBeenCalledWith( + 'test-id', + 'tailoring-id-1', + null, + { ids: ['rule-1'] } + ) + ); + await waitFor(() => + expect(apiInstance.assignRules).toHaveBeenCalledWith( + 'test-id', + 'tailoring-id-2', + null, + { ids: ['rule-2'] } + ) + ); + + expect(dispatchNotification).toHaveBeenCalledWith({ + autoDismiss: true, + title: 'Policy updated', + variant: 'success', + }); + }); + + it('Should submit fail if any API errors', async () => { + apiInstance.assignRules.mockImplementation(() => + Promise.reject({ message: 'error message' }) + ); + + renderComponent(); + await waitFor(() => + expect( + screen.getByRole('button', { + name: /save/i, + }) + ).toBeEnabled() + ); + + await user.click( + screen.getByRole('button', { + name: /save/i, + }) + ); + + await waitFor(() => + expect(dispatchNotification).toHaveBeenCalledWith({ + variant: 'danger', + title: 'Error updating policy', + description: 'error message', + }) + ); + }); + + it('Should call fetch APIs with correct params', async () => { + renderComponent(); + + [usePolicy, useAssignRules, useAssignSystems].forEach( + async (hook) => + await waitFor(() => { + expect(hook).toHaveBeenCalledWith('test-policy-id'); + }) + ); + + await waitFor(() => { + expect(useSupportedProfiles).toHaveBeenCalledWith({ + params: { filter: 'os_major_version=7', limit: 100 }, + skip: false, + }); + }); + }); }); diff --git a/src/SmartComponents/EditPolicy/EditPolicyForm.js b/src/SmartComponents/EditPolicy/EditPolicyForm.js index b61f0542f..90dd17557 100644 --- a/src/SmartComponents/EditPolicy/EditPolicyForm.js +++ b/src/SmartComponents/EditPolicy/EditPolicyForm.js @@ -1,78 +1,54 @@ -import React, { useCallback, useState, useEffect } from 'react'; +import React, { useCallback, useState } from 'react'; import propTypes from 'prop-types'; import { Form, Tab, TabTitleText } from '@patternfly/react-core'; import { RoutedTabs } from 'PresentationalComponents'; import EditPolicyRulesTab from './EditPolicyRulesTab'; import EditPolicySystemsTab from './EditPolicySystemsTab'; import NewRulesAlert from './components/NewRulesAlert'; -import { mapCountOsMinorVersions } from 'Store/Reducers/SystemStore'; -import { profilesWithRulesToSelection } from 'PresentationalComponents/TabbedRules'; -import { thresholdValid } from '../CreatePolicy/validate'; import { useNewRulesAlertState } from './hooks/index'; -const profilesToOsMinorMap = (profiles, hosts) => - (profiles || []).reduce((acc, profile) => { - if (profile.osMinorVersion !== '') { - acc[profile.osMinorVersion] ||= { - osMinorVersion: profile.osMinorVersion, - count: 0, - }; - } - - return acc; - }, mapCountOsMinorVersions(hosts || [])); - const EditPolicyForm = ({ policy, setUpdatedPolicy, - selectedRuleRefIds, - setSelectedRuleRefIds, - selectedSystems, - setSelectedSystems, + assignedRuleIds, + assignedSystems, setRuleValues, - ruleValues, + supportedOsVersions, }) => { - const policyProfiles = policy?.policy?.profiles || []; - const [osMinorVersionCounts, setOsMinorVersionCounts] = useState({}); + const [selectedOsMinorVersions, setSelectedOsMinorVersions] = useState( + assignedSystems.map((system) => system.os_minor_version) + ); const [newRulesAlert, setNewRulesAlert] = useNewRulesAlertState(false); + const [selectedSystems, setSelectedSystems] = useState( + assignedSystems?.map((system) => system.id) + ); + const preUsedOsMinorVersions = assignedSystems.map( + (system) => system.os_minor_version + ); + const handleSystemSelect = useCallback( (newSelectedSystems) => { - const policyMinorVersions = policy.hosts.map( - ({ osMinorVersion }) => osMinorVersion - ); + const newOsMinorVersions = [ + ...new Set(newSelectedSystems.map((system) => system.osMinorVersion)), // get unique values + ]; + const hasNewOsMinorVersions = - newSelectedSystems.filter( - ({ osMinorVersion }) => !policyMinorVersions.includes(osMinorVersion) + newOsMinorVersions.filter( + (osMinorVersion) => !preUsedOsMinorVersions.includes(osMinorVersion) ).length > 0; - setSelectedSystems(newSelectedSystems); + setUpdatedPolicy((prev) => ({ + ...prev, + hosts: newSelectedSystems.map((system) => system.id), + })); setNewRulesAlert(hasNewOsMinorVersions); - setOsMinorVersionCounts( - profilesToOsMinorMap(policyProfiles, newSelectedSystems) - ); + setSelectedOsMinorVersions(newOsMinorVersions); + setSelectedSystems(newSelectedSystems.map((system) => system.id)); }, - [policyProfiles, selectedRuleRefIds] + [preUsedOsMinorVersions, setNewRulesAlert, setUpdatedPolicy] ); - useEffect(() => { - if (policy) { - const complianceThresholdValid = thresholdValid( - policy.complianceThreshold - ); - const profilesWithOsMinor = policyProfiles.filter( - ({ osMinorVersion }) => !!osMinorVersion - ); - setUpdatedPolicy({ - ...policy, - complianceThresholdValid, - }); - - setSelectedRuleRefIds(profilesWithRulesToSelection(profilesWithOsMinor)); - handleSystemSelect(policy.hosts); - } - }, [policy]); - return (
@@ -83,11 +59,10 @@ const EditPolicyForm = ({ > {newRulesAlert && } @@ -111,12 +87,10 @@ EditPolicyForm.propTypes = { policy: propTypes.object, updatedPolicy: propTypes.object, setUpdatedPolicy: propTypes.func, - selectedRuleRefIds: propTypes.arrayOf(propTypes.object), - setSelectedRuleRefIds: propTypes.func, - setSelectedSystems: propTypes.func, - selectedSystems: propTypes.array, + assignedRuleIds: propTypes.arrayOf(propTypes.object), + assignedSystems: propTypes.array, setRuleValues: propTypes.func, - ruleValues: propTypes.array, + supportedOsVersions: propTypes.array, }; export default EditPolicyForm; diff --git a/src/SmartComponents/EditPolicy/EditPolicyForm.test.js b/src/SmartComponents/EditPolicy/EditPolicyForm.test.js deleted file mode 100644 index 3e4c6b206..000000000 --- a/src/SmartComponents/EditPolicy/EditPolicyForm.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import { render } from '@testing-library/react'; -import TestWrapper from '@/Utilities/TestWrapper'; -import { policies } from '@/__fixtures__/policies.js'; -import { BENCHMARKS_QUERY } from './constants'; - -import EditPolicyForm from './EditPolicyForm'; -import { useNewRulesAlertState } from './hooks'; - -jest.mock('@/Utilities/hooks/useAPIV2FeatureFlag', () => jest.fn(() => false)); -jest.mock('./hooks', () => ({ - ...jest.requireActual('./hooks'), - useNewRulesAlertState: jest.fn(() => [false, () => false]), -})); - -describe('EditPolicyForm', () => { - const policy = { - ...policies.edges[0].node, - supportedOsVersions: ['7.8', '7.9'], - }; - const mocks = [ - { - request: { - query: BENCHMARKS_QUERY, - }, - result: { - data: {}, - }, - }, - ]; - const defaultProps = { - setUpdatedPolicy: () => ({}), - setSelectedRuleRefIds: () => ({}), - setSelectedSystems: () => ({}), - policy, - }; - - it('expect to render without error', () => { - useNewRulesAlertState.mockImplementation(() => [false, () => false]); - const { asFragment } = render( - - - - ); - expect(asFragment()).toMatchSnapshot(); - }); - - it('expect to render with alter', () => { - useNewRulesAlertState.mockImplementation(() => [true, () => true]); - const { asFragment } = render( - - - - ); - expect(asFragment()).toMatchSnapshot(); - }); -}); diff --git a/src/SmartComponents/EditPolicy/EditPolicyFormRest.js b/src/SmartComponents/EditPolicy/EditPolicyFormRest.js deleted file mode 100644 index 7ece9a08e..000000000 --- a/src/SmartComponents/EditPolicy/EditPolicyFormRest.js +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import propTypes from 'prop-types'; -import { Form, Tab, TabTitleText } from '@patternfly/react-core'; -import { RoutedTabs } from 'PresentationalComponents'; -import EditPolicyRulesTabRest from './EditPolicyRulesTabRest'; -import EditPolicySystemsTabRest from './EditPolicySystemsTabRest'; -import NewRulesAlert from './components/NewRulesAlert'; -import { useNewRulesAlertState } from './hooks/index'; - -const EditPolicyForm = ({ - policy, - setUpdatedPolicy, - assignedRuleIds, - assignedSystems, - setRuleValues, - supportedOsVersions, -}) => { - const [selectedOsMinorVersions, setSelectedOsMinorVersions] = useState( - assignedSystems.map((system) => system.os_minor_version) - ); - const [newRulesAlert, setNewRulesAlert] = useNewRulesAlertState(false); - - const [selectedSystems, setSelectedSystems] = useState( - assignedSystems?.map((system) => system.id) - ); - const preUsedOsMinorVersions = assignedSystems.map( - (system) => system.os_minor_version - ); - - const handleSystemSelect = useCallback( - (newSelectedSystems) => { - const newOsMinorVersions = [ - ...new Set(newSelectedSystems.map((system) => system.osMinorVersion)), // get unique values - ]; - - const hasNewOsMinorVersions = - newOsMinorVersions.filter( - (osMinorVersion) => !preUsedOsMinorVersions.includes(osMinorVersion) - ).length > 0; - - setUpdatedPolicy((prev) => ({ - ...prev, - hosts: newSelectedSystems.map((system) => system.id), - })); - setNewRulesAlert(hasNewOsMinorVersions); - setSelectedOsMinorVersions(newOsMinorVersions); - setSelectedSystems(newSelectedSystems.map((system) => system.id)); - }, - [preUsedOsMinorVersions, setNewRulesAlert, setUpdatedPolicy] - ); - - return ( - - - Rules} - > - - - Systems} - > - - {newRulesAlert && } - - - - ); -}; - -EditPolicyForm.propTypes = { - policy: propTypes.object, - updatedPolicy: propTypes.object, - setUpdatedPolicy: propTypes.func, - assignedRuleIds: propTypes.arrayOf(propTypes.object), - assignedSystems: propTypes.array, - setRuleValues: propTypes.func, - supportedOsVersions: propTypes.array, -}; - -export default EditPolicyForm; diff --git a/src/SmartComponents/EditPolicy/EditPolicyGraphQL.js b/src/SmartComponents/EditPolicy/EditPolicyGraphQL.js deleted file mode 100644 index 006164a1d..000000000 --- a/src/SmartComponents/EditPolicy/EditPolicyGraphQL.js +++ /dev/null @@ -1,126 +0,0 @@ -import React, { useState } from 'react'; -import propTypes from 'prop-types'; -import { Button, Spinner } from '@patternfly/react-core'; -import { useLocation, useParams } from 'react-router-dom'; -import useNavigate from '@redhat-cloud-services/frontend-components-utilities/useInsightsNavigate'; -import { useTitleEntity } from 'Utilities/hooks/useDocumentTitle'; -import { - ComplianceModal, - StateViewWithError, - StateViewPart, -} from 'PresentationalComponents'; -import EditPolicyForm from './EditPolicyForm'; -import { useOnSave } from './hooks'; -import usePolicyQuery from 'Utilities/hooks/usePolicyQuery'; - -export const EditPolicyGraphQL = ({ route }) => { - const navigate = useNavigate(); - const { policy_id: policyId } = useParams(); - const location = useLocation(); - const { data, error, loading } = usePolicyQuery({ policyId }); - const policy = data?.profile; - const [updatedPolicy, setUpdatedPolicy] = useState(null); - const [selectedRuleRefIds, setSelectedRuleRefIds] = useState([]); - const [selectedSystems, setSelectedSystems] = useState([]); - const [ruleValues, setRuleValuesState] = useState({}); - - const saveEnabled = updatedPolicy && !updatedPolicy.complianceThresholdValid; - const updatedPolicyHostsAndRules = { - ...updatedPolicy, - selectedRuleRefIds, - hosts: selectedSystems, - values: ruleValues, - }; - const onSaveCallback = (isClose) => - navigate( - isClose ? `/scappolicies/${policyId}` : location.state?.returnTo || -1 - ); - - const [isSaving, onSave] = useOnSave(policyId, updatedPolicyHostsAndRules, { - onSave: onSaveCallback, - onError: onSaveCallback, - }); - - const setRuleValues = (policyId, valueDefinition, valueValue) => { - const existingValues = Object.fromEntries( - policy?.policy.profiles.map((profile) => { - return [profile.id, profile.values]; - }) || [] - ); - - setRuleValuesState((currentValues) => ({ - ...existingValues, - ...currentValues, - [policyId]: { - ...existingValues[policyId], - ...currentValues[policyId], - [valueDefinition.id]: valueValue, - }, - })); - }; - - const actions = [ - , - , - ]; - - useTitleEntity(route, policy?.name); - - return ( - onSaveCallback(true)} - actions={actions} - > - - - - - - - - - - ); -}; - -EditPolicyGraphQL.propTypes = { - route: propTypes.object, -}; diff --git a/src/SmartComponents/EditPolicy/EditPolicyRest.js b/src/SmartComponents/EditPolicy/EditPolicyRest.js deleted file mode 100644 index 790ec8619..000000000 --- a/src/SmartComponents/EditPolicy/EditPolicyRest.js +++ /dev/null @@ -1,159 +0,0 @@ -import React, { useState } from 'react'; -import propTypes from 'prop-types'; -import { Button, Spinner } from '@patternfly/react-core'; -import { useLocation, useParams } from 'react-router-dom'; -import useNavigate from '@redhat-cloud-services/frontend-components-utilities/useInsightsNavigate'; -import { useTitleEntity } from 'Utilities/hooks/useDocumentTitle'; -import { - ComplianceModal, - StateViewWithError, - StateViewPart, -} from 'PresentationalComponents'; -import EditPolicyFormRest from './EditPolicyFormRest'; -import { useOnSave } from './hooks'; -import usePolicy from 'Utilities/hooks/api/usePolicy'; -import useAssignedRules from './hooks/useAssignedRules'; -import useAssignedSystems from './hooks/useAssignedSystems'; -import useSupportedProfiles from 'Utilities/hooks/api/useSupportedProfiles'; - -export const EditPolicyRest = ({ route }) => { - const navigate = useNavigate(); - const { policy_id: policyId } = useParams(); - const location = useLocation(); - const { - data: { data: policy } = {}, - loading: policyLoading, - error: policyError, - } = usePolicy(policyId); - - const { - data: { data: supportedProfiles } = {}, - error: supportedProfilesError, - loading: supportedProfilesLoading, - } = useSupportedProfiles({ - params: { - filter: `os_major_version=${policy?.os_major_version}`, - limit: 100, - }, - skip: policyLoading || !policy?.os_major_version, - }); - - const securityGuide = supportedProfiles?.find( - (profile) => profile.ref_id === policy?.ref_id - ); - - const supportedOsVersions = securityGuide?.os_minor_versions || []; - - const { assignedRuleIds, assignedRulesLoading } = useAssignedRules(policyId); - const { assignedSystems, assignedSystemsLoading } = useAssignedSystems( - policyId, - policy, - policyLoading - ); - - const [updatedPolicy, setUpdatedPolicy] = useState(null); - - const saveEnabled = !updatedPolicy; - - const onSaveCallback = (isClose) => - navigate( - isClose ? `/scappolicies/${policyId}` : location.state?.returnTo || -1 - ); - - const [isSaving, onSave] = useOnSave(policyId, updatedPolicy, { - onSave: onSaveCallback, - onError: onSaveCallback, - }); - - const setRuleValues = ( - _policy, - tailoring, - valueDefinition, - newValue, - closeInlineEdit - ) => { - setUpdatedPolicy((prev) => ({ - ...prev, - value: { - ...prev.value, - [tailoring.id]: { - ...tailoring.value_overrides, - [valueDefinition.id]: newValue, - }, - }, - })); - - closeInlineEdit(); - }; - const actions = [ - , - , - ]; - - useTitleEntity(route, policy?.title); - - const statusValues = { - data: policy && supportedProfiles && assignedRuleIds, - loading: - policyLoading || - supportedProfilesLoading || - assignedSystemsLoading || - assignedRulesLoading, - error: policyError || supportedProfilesError, - }; - - return ( - onSaveCallback(true)} - actions={actions} - > - - - - - - - - - - ); -}; - -EditPolicyRest.propTypes = { - route: propTypes.object, -}; diff --git a/src/SmartComponents/EditPolicy/EditPolicyRest.test.js b/src/SmartComponents/EditPolicy/EditPolicyRest.test.js deleted file mode 100644 index d3cdd8f6a..000000000 --- a/src/SmartComponents/EditPolicy/EditPolicyRest.test.js +++ /dev/null @@ -1,229 +0,0 @@ -import { useEffect } from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import { EditPolicyRest } from './EditPolicyRest'; -import usePolicy from 'Utilities/hooks/api/usePolicy'; -import useSupportedProfiles from 'Utilities/hooks/api/useSupportedProfiles'; -import useAssignRules from 'Utilities/hooks/api/useAssignRules'; -import useAssignSystems from 'Utilities/hooks/api/useAssignSystems'; -import useTailorings from 'Utilities/hooks/api/useTailorings'; -import TestWrapper from '@redhat-cloud-services/frontend-components-utilities/TestingUtils/JestUtils/TestWrapper'; -import useAPIV2FeatureFlag from 'Utilities/hooks/useAPIV2FeatureFlag'; -import userEvent from '@testing-library/user-event'; -import useNavigate from '@redhat-cloud-services/frontend-components-utilities/useInsightsNavigate'; -import { apiInstance } from 'Utilities/hooks/useQuery'; -import EditPolicyForm from './EditPolicyFormRest'; -import { dispatchNotification } from 'Utilities/Dispatcher'; - -jest.mock('Utilities/hooks/api/useSupportedProfiles'); -jest.mock('Utilities/hooks/api/useAssignRules'); -jest.mock('Utilities/hooks/api/useAssignSystems'); -jest.mock('Utilities/hooks/api/usePolicy'); -jest.mock('Utilities/hooks/api/useTailorings'); -jest.mock('Utilities/Dispatcher', () => ({ dispatchNotification: jest.fn() })); -jest.mock('Mutations', () => ({ - usePolicy: jest.fn(), -})); -jest.mock( - '@redhat-cloud-services/frontend-components-utilities/useInsightsNavigate' -); -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), - useParams: jest.fn(() => ({ policy_id: 'test-policy-id' })), -})); -jest.mock('Utilities/hooks/useAPIV2FeatureFlag'); -jest.mock('./EditPolicyFormRest'); -jest.mock('Utilities/hooks/useQuery/apiInstance', () => ({ - updatePolicy: jest.fn(() => Promise.resolve({})), - assignSystems: jest.fn(() => Promise.resolve({})), - assignRules: jest.fn(() => Promise.resolve({})), -})); - -const navigate = jest.fn(); -useNavigate.mockImplementation(() => navigate); -usePolicy.mockImplementation(() => ({ - data: { data: { title: 'test-policy', os_major_version: 7, id: 'test-id' } }, - loading: false, - error: false, -})); - -useTailorings.mockImplementation(() => ({ - data: [{ id: 'tailoring-id' }], - loading: false, - error: false, -})); - -[useAssignRules, useAssignSystems, useSupportedProfiles].forEach((hook) => { - hook.mockImplementation(() => ({ - data: { data: [{ id: 'test-id' }] }, - loading: false, - error: false, - })); -}); - -EditPolicyForm.mockImplementation(({ setUpdatedPolicy }) => { - useEffect(() => { - setUpdatedPolicy({ - tailoringRules: { - 'tailoring-id-1': ['rule-1'], - 'tailoring-id-2': ['rule-2'], - }, - hosts: ['system-1'], - value: { 'tailoring-id': { 'value-id': 'changed-value' } }, - }); - }, []); - - return
Mocked tabs
; -}); - -useAPIV2FeatureFlag.mockReturnValue(true); -const user = userEvent.setup(); -const defaultProp = { - route: { - title: 'test-page', - setTitle: jest.fn(), - }, -}; - -const renderComponent = (newProps = {}) => - render( - - - - ); - -// TODO: recover and review the tests -describe.skip('EditPolicyRest', () => { - it('Should have proper modal title', () => { - renderComponent(); - expect(screen.getByText(`Edit test-policy`)).toBeVisible(); - }); - - it('Should redirect to policy detail page on cancel button click', async () => { - renderComponent(); - - await user.click(screen.getByRole('button', { name: 'Cancel' })); - - expect(navigate).toHaveBeenCalledWith('/scappolicies/test-policy-id'); - }); - - it('Should display save button as disabled when no changes are made', () => { - renderComponent(); - - expect( - screen.getByRole('button', { - name: /save/i, - }) - ).toBeDisabled(); - }); - - it('Should call fetch APIs with correct params', async () => { - renderComponent(); - - [usePolicy, useAssignRules, useAssignSystems].forEach( - async (hook) => - await waitFor(() => { - expect(hook).toHaveBeenCalledWith('test-policy-id'); - }) - ); - - await waitFor(() => { - expect(useSupportedProfiles).toHaveBeenCalledWith({ - params: { filter: 'os_major_version=7', limit: 100 }, - skip: false, - }); - }); - }); - - it('Should submit form', async () => { - renderComponent(); - await waitFor(() => - expect( - screen.getByRole('button', { - name: /save/i, - }) - ).toBeEnabled() - ); - - await user.click( - screen.getByRole('button', { - name: /save/i, - }) - ); - - await waitFor(() => - expect(apiInstance.assignSystems).toHaveBeenCalledWith('test-id', null, { - ids: ['system-1'], - }) - ); - await waitFor(() => - expect(apiInstance.assignRules).toHaveBeenCalledWith( - 'test-id', - 'tailoring-id-1', - null, - { ids: ['rule-1'] } - ) - ); - await waitFor(() => - expect(apiInstance.assignRules).toHaveBeenCalledWith( - 'test-id', - 'tailoring-id-2', - null, - { ids: ['rule-2'] } - ) - ); - - expect(dispatchNotification).toHaveBeenCalledWith({ - autoDismiss: true, - title: 'Policy updated', - variant: 'success', - }); - }); - - it('Should submit fail if any API errors', async () => { - apiInstance.assignRules.mockImplementation(() => - Promise.reject({ message: 'error message' }) - ); - - renderComponent(); - await waitFor(() => - expect( - screen.getByRole('button', { - name: /save/i, - }) - ).toBeEnabled() - ); - - await user.click( - screen.getByRole('button', { - name: /save/i, - }) - ); - - await waitFor(() => - expect(dispatchNotification).toHaveBeenCalledWith({ - variant: 'danger', - title: 'Error updating policy', - description: 'error message', - }) - ); - }); - - it('Should call fetch APIs with correct params', async () => { - renderComponent(); - - [usePolicy, useAssignRules, useAssignSystems].forEach( - async (hook) => - await waitFor(() => { - expect(hook).toHaveBeenCalledWith('test-policy-id'); - }) - ); - - await waitFor(() => { - expect(useSupportedProfiles).toHaveBeenCalledWith({ - params: { filter: 'os_major_version=7', limit: 100 }, - skip: false, - }); - }); - }); -}); diff --git a/src/SmartComponents/EditPolicy/EditPolicyRulesTab.js b/src/SmartComponents/EditPolicy/EditPolicyRulesTab.js index 2b53cae1e..3aa70a2a3 100644 --- a/src/SmartComponents/EditPolicy/EditPolicyRulesTab.js +++ b/src/SmartComponents/EditPolicy/EditPolicyRulesTab.js @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { EmptyState, EmptyStateBody, @@ -8,28 +8,11 @@ import { EmptyStateFooter, } from '@patternfly/react-core'; import propTypes from 'prop-types'; -import { useQuery } from '@apollo/client'; import Spinner from '@redhat-cloud-services/frontend-components/Spinner'; -import { StateViewWithError, StateViewPart } from 'PresentationalComponents'; -import { - TabbedRules, - profilesWithRulesToSelection, - tabsDataToOsMinorMap, - extendProfilesByOsMinor, -} from 'PresentationalComponents/TabbedRules'; -import { sortingByProp } from 'Utilities/helpers'; +import { StateView, StateViewPart } from 'PresentationalComponents'; import * as Columns from '@/PresentationalComponents/RulesTable/Columns'; -import { BENCHMARKS_QUERY } from './constants'; - -const getBenchmarkBySupportedOsMinor = (benchmarks, osMinorVersion) => - benchmarks.find((benchmark) => - benchmark.latestSupportedOsMinorVersions?.includes(osMinorVersion) - ); - -const getBenchmarkProfile = (benchmark, profileRefId) => - benchmark.profiles.find( - (benchmarkProfile) => benchmarkProfile.refId === profileRefId - ); +import Tailorings from '@/PresentationalComponents/Tailorings/Tailorings'; +import useProfileRuleIds from '../CreatePolicy/EditPolicyProfilesRules/useProfileRuleIds'; const EditPolicyRulesTabEmptyState = () => ( @@ -49,122 +32,94 @@ const EditPolicyRulesTabEmptyState = () => ( ); -export const toTabsData = (policy, osMinorVersionCounts, benchmarks) => - Object.values(osMinorVersionCounts) - .sort(sortingByProp('osMinorVersion', 'desc')) - .map(({ osMinorVersion, count: systemCount }) => { - osMinorVersion = `${osMinorVersion}`; - let profile = policy.policy.profiles.find( - (profile) => profile.osMinorVersion === osMinorVersion - ); - let osMajorVersion = policy.osMajorVersion; - - if (!profile && benchmarks) { - const benchmark = getBenchmarkBySupportedOsMinor( - benchmarks, - osMinorVersion - ); - if (benchmark) { - const benchmarkProfile = getBenchmarkProfile(benchmark, policy.refId); - if (benchmarkProfile) { - profile = policy.policy.profiles.find( - (profile) => - profile.parentProfileId === benchmarkProfile.id && - profile.osMinorVersion === osMinorVersion - ); - - profile = { - ...benchmarkProfile, - benchmark, - osMajorVersion, - ...profile, - }; - } - } - } - - return { - profile, - systemCount, - newOsMinorVersion: osMinorVersion, - }; - }) - .filter(({ profile, newOsMinorVersion }) => !!profile && newOsMinorVersion); +const difference = (arr1, arr2) => arr1.filter((x) => !arr2.includes(x)); export const EditPolicyRulesTab = ({ policy, - selectedRuleRefIds, - setSelectedRuleRefIds, - osMinorVersionCounts, + assignedRuleIds, setRuleValues, - ruleValues: ruleValuesProp, + setUpdatedPolicy, + selectedOsMinorVersions, }) => { - const osMajorVersion = policy?.osMajorVersion; - const osMinorVersions = Object.keys(osMinorVersionCounts).sort(); - const benchmarkSearch = - `os_major_version = ${osMajorVersion} ` + - `and latest_supported_os_minor_version ^ "${osMinorVersions.join(',')}"`; + const [selectedRules, setSelectedRules] = useState(assignedRuleIds); + const tailoringOsMinorVersions = Object.keys(assignedRuleIds).map(Number); + const nonTailoringOsMinorVersions = selectedOsMinorVersions.filter( + (version) => !tailoringOsMinorVersions.includes(version) + ); + + const shouldSkipProfiles = + nonTailoringOsMinorVersions.length === 0 || + difference(nonTailoringOsMinorVersions, tailoringOsMinorVersions).length === + 0; const { - data: benchmarksData, - error, - loading, - } = useQuery(BENCHMARKS_QUERY, { - variables: { - filter: benchmarkSearch, - }, - skip: osMinorVersions.length === 0, + profilesAndRuleIds: profilesRuleIds, + loading: profilesRuleIdsLoading, + error: profilesRuleIdsError, + } = useProfileRuleIds({ + profileRefId: policy.ref_id, + osMajorVersion: policy.os_major_version, + osMinorVersions: nonTailoringOsMinorVersions, + skip: shouldSkipProfiles, }); - const benchmarks = benchmarksData?.benchmarks?.nodes; - - const tabsData = toTabsData(policy, osMinorVersionCounts, benchmarks); - const profileToOsMinorMap = tabsDataToOsMinorMap(tabsData); - - const dataState = !loading && tabsData?.length > 0 ? tabsData : undefined; - useEffect(() => { - if (policy.policy.profiles) { - const profiles = policy.policy.profiles; - const profilesWithOs = extendProfilesByOsMinor( - profiles, - profileToOsMinorMap - ); - setSelectedRuleRefIds((prevSelection) => { - const newSelection = profilesWithRulesToSelection( - profilesWithOs, - prevSelection - ); - return newSelection; + if (profilesRuleIds !== undefined && profilesRuleIdsLoading !== true) { + profilesRuleIds.forEach((entry) => { + if (!Object.keys(selectedRules).includes(`${entry.osMinorVersion}`)) { + setUpdatedPolicy((prev) => ({ + ...prev, + tailoringRules: { + ...prev?.tailoringRules, + [Number(entry.osMinorVersion)]: entry.ruleIds, + }, + })); + + setSelectedRules((prev) => ({ + ...prev, + [Number(entry.osMinorVersion)]: entry.ruleIds, + })); + } }); } - }, [policy.policy.profiles]); - - const ruleValues = (policy) => { - const mergeValues = (policyId, values) => { - return { - ...values, - ...(ruleValuesProp?.[policyId] || {}), - }; - }; - - return Object.fromEntries( - policy?.policy?.profiles?.map( - ({ id, values, benchmark: { valueDefinitions } }) => [ - id, - mergeValues(id, values, valueDefinitions), - ] - ) || [] - ); - }; + }, [ + profilesRuleIds, + profilesRuleIdsLoading, + selectedRules, + setUpdatedPolicy, + ]); + + const handleSelect = useCallback( + (policy, tailoring, newSelectedRuleIds) => { + setUpdatedPolicy((prev) => ({ + ...prev, + tailoringRules: { + ...prev?.tailoringRules, + [tailoring.os_minor_version]: newSelectedRuleIds, + }, + })); + + setSelectedRules((prev) => ({ + ...prev, + [tailoring.os_minor_version]: newSelectedRuleIds, + })); + }, + [setUpdatedPolicy] + ); + const assignedSystemCount = policy?.total_system_count; return ( - 0 && + (shouldSkipProfiles || (profilesRuleIds && !profilesRuleIdsLoading)), + loading: + assignedSystemCount === undefined || + (nonTailoringOsMinorVersions.length > 0 && profilesRuleIdsLoading), + empty: selectedOsMinorVersions.length === 0, + error: profilesRuleIdsError, // TODO: add the state view for error }} > @@ -180,43 +135,35 @@ export const EditPolicyRulesTab = ({ must be customized independently. - {tabsData.length > 0 && ( - - )} + - + ); }; EditPolicyRulesTab.propTypes = { - setNewRuleTabs: propTypes.func, policy: propTypes.object, - osMinorVersionCounts: propTypes.shape({ - osMinorVersion: propTypes.shape({ - osMinorVersion: propTypes.number, - count: propTypes.number, - }), - }), - selectedRuleRefIds: propTypes.array, - setSelectedRuleRefIds: propTypes.func, + assignedRuleIds: propTypes.array, setRuleValues: propTypes.func, - ruleValues: propTypes.array, + setUpdatedPolicy: propTypes.func, + selectedOsMinorVersions: propTypes.array, }; export default EditPolicyRulesTab; diff --git a/src/SmartComponents/EditPolicy/EditPolicyRulesTab.test.js b/src/SmartComponents/EditPolicy/EditPolicyRulesTab.test.js index 53bfd4dd1..d0b9c807e 100644 --- a/src/SmartComponents/EditPolicy/EditPolicyRulesTab.test.js +++ b/src/SmartComponents/EditPolicy/EditPolicyRulesTab.test.js @@ -1,85 +1,64 @@ import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import TestWrapper from '@/Utilities/TestWrapper'; +import EditPolicyRulesTab from './EditPolicyRulesTab.js'; +// import Tailorings from '@/PresentationalComponents/Tailorings/Tailorings'; -import { useQuery } from '@apollo/client'; -import { policies } from '@/__fixtures__/policies.js'; -import EditPolicyRulesTab, { toTabsData } from './EditPolicyRulesTab.js'; +// eslint-disable-next-line react/display-name +jest.mock('@/PresentationalComponents/Tailorings/Tailorings', () => () => ( +
Tailorings tab
//TODO: do not mock the component, instead test with its behaviours +)); -jest.mock('@apollo/client'); -const policy = policies.edges[0].node; +const defaultProps = { + policy: { total_system_count: 1 }, + assignedRuleIds: [], + setRuleValues: jest.fn(), + setUpdatedPolicy: jest.fn(), + selectedOsMinorVersions: [], +}; describe('EditPolicyRulesTab', () => { - useQuery.mockImplementation(() => ({ - data: { - benchmarks: { - edges: [ - { - id: '1', - osMajorVersion: '7', - rules: policy.rules, - }, - ], - }, - }, - error: undefined, - loading: undefined, - })); - - it('expect to render note when no rules can be configured', () => { + it.skip('expect to render note when no rules can be configured', () => { render( - {}} - policy={{ policy: { profiles: [] } }} - selectedRuleRefIds={[]} - setSelectedRuleRefIds={() => {}} - osMinorVersionCounts={{}} - /> + ); - expect(screen.getByText('No rules can be configured')).toBeInTheDocument(); + expect( + screen.getByText( + 'Different release versions of RHEL are associated with different versions of the SCAP Security Guide (SSG), therefore each release must be customized independently.' + ) + ).toBeInTheDocument(); + + expect(screen.queryByTestId('tailorings-tab')).toBeVisible(); }); - it('expect to render with policy passed', () => { + it('expect to render with empty state', () => { render( {}} - policy={policies.edges[0].node} - selectedRuleRefIds={[]} - setSelectedRuleRefIds={() => {}} - osMinorVersionCounts={{ - 9: { - osMinorVersion: 9, - count: 1, - }, - }} + {...{ ...defaultProps, policy: { total_system_count: 0 } }} /> ); expect( - screen.getByRole('tab', { name: 'Rules for RHEL 7.9' }) - ).toBeInTheDocument(); + screen.queryByText( + 'This policy has no associated systems, and therefore no rules can be configured.' + ) + ).toBeVisible(); }); -}); -describe('.toTabsData', () => { - it('expect to render without error', async () => { - const policy = policies.edges[0].node; - const osMinorVersionCounts = { - 9: { - osMinorVersion: 9, - count: 1, - }, - }; - const benchmark = { - latestSupportedOsMinorVersions: [9], - profiles: [{ refId: policy.refId }], - }; - const result = toTabsData(policy, osMinorVersionCounts, [benchmark], []); - expect(result).toMatchSnapshot(); + it('expect to render with loading state', () => { + render( + + + + ); + + expect(screen.queryByText('Loading...')).toBeVisible(); }); }); diff --git a/src/SmartComponents/EditPolicy/EditPolicyRulesTabRest.js b/src/SmartComponents/EditPolicy/EditPolicyRulesTabRest.js deleted file mode 100644 index 3aa70a2a3..000000000 --- a/src/SmartComponents/EditPolicy/EditPolicyRulesTabRest.js +++ /dev/null @@ -1,169 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { - EmptyState, - EmptyStateBody, - Text, - TextContent, - EmptyStateHeader, - EmptyStateFooter, -} from '@patternfly/react-core'; -import propTypes from 'prop-types'; -import Spinner from '@redhat-cloud-services/frontend-components/Spinner'; -import { StateView, StateViewPart } from 'PresentationalComponents'; -import * as Columns from '@/PresentationalComponents/RulesTable/Columns'; -import Tailorings from '@/PresentationalComponents/Tailorings/Tailorings'; -import useProfileRuleIds from '../CreatePolicy/EditPolicyProfilesRules/useProfileRuleIds'; - -const EditPolicyRulesTabEmptyState = () => ( - - - - This policy has no associated systems, and therefore no rules can be - configured. - - - - Add at least one system to configure rules for this policy. - - - -); - -const difference = (arr1, arr2) => arr1.filter((x) => !arr2.includes(x)); - -export const EditPolicyRulesTab = ({ - policy, - assignedRuleIds, - setRuleValues, - setUpdatedPolicy, - selectedOsMinorVersions, -}) => { - const [selectedRules, setSelectedRules] = useState(assignedRuleIds); - const tailoringOsMinorVersions = Object.keys(assignedRuleIds).map(Number); - const nonTailoringOsMinorVersions = selectedOsMinorVersions.filter( - (version) => !tailoringOsMinorVersions.includes(version) - ); - - const shouldSkipProfiles = - nonTailoringOsMinorVersions.length === 0 || - difference(nonTailoringOsMinorVersions, tailoringOsMinorVersions).length === - 0; - - const { - profilesAndRuleIds: profilesRuleIds, - loading: profilesRuleIdsLoading, - error: profilesRuleIdsError, - } = useProfileRuleIds({ - profileRefId: policy.ref_id, - osMajorVersion: policy.os_major_version, - osMinorVersions: nonTailoringOsMinorVersions, - skip: shouldSkipProfiles, - }); - - useEffect(() => { - if (profilesRuleIds !== undefined && profilesRuleIdsLoading !== true) { - profilesRuleIds.forEach((entry) => { - if (!Object.keys(selectedRules).includes(`${entry.osMinorVersion}`)) { - setUpdatedPolicy((prev) => ({ - ...prev, - tailoringRules: { - ...prev?.tailoringRules, - [Number(entry.osMinorVersion)]: entry.ruleIds, - }, - })); - - setSelectedRules((prev) => ({ - ...prev, - [Number(entry.osMinorVersion)]: entry.ruleIds, - })); - } - }); - } - }, [ - profilesRuleIds, - profilesRuleIdsLoading, - selectedRules, - setUpdatedPolicy, - ]); - - const handleSelect = useCallback( - (policy, tailoring, newSelectedRuleIds) => { - setUpdatedPolicy((prev) => ({ - ...prev, - tailoringRules: { - ...prev?.tailoringRules, - [tailoring.os_minor_version]: newSelectedRuleIds, - }, - })); - - setSelectedRules((prev) => ({ - ...prev, - [tailoring.os_minor_version]: newSelectedRuleIds, - })); - }, - [setUpdatedPolicy] - ); - - const assignedSystemCount = policy?.total_system_count; - return ( - 0 && - (shouldSkipProfiles || (profilesRuleIds && !profilesRuleIdsLoading)), - loading: - assignedSystemCount === undefined || - (nonTailoringOsMinorVersions.length > 0 && profilesRuleIdsLoading), - empty: selectedOsMinorVersions.length === 0, - error: profilesRuleIdsError, // TODO: add the state view for error - }} - > - - - - - - - - - Different release versions of RHEL are associated with different - versions of the SCAP Security Guide (SSG), therefore each release - must be customized independently. - - - - - - - - - ); -}; - -EditPolicyRulesTab.propTypes = { - policy: propTypes.object, - assignedRuleIds: propTypes.array, - setRuleValues: propTypes.func, - setUpdatedPolicy: propTypes.func, - selectedOsMinorVersions: propTypes.array, -}; - -export default EditPolicyRulesTab; diff --git a/src/SmartComponents/EditPolicy/EditPolicyRulesTabRest.test.js b/src/SmartComponents/EditPolicy/EditPolicyRulesTabRest.test.js deleted file mode 100644 index 8a026ea23..000000000 --- a/src/SmartComponents/EditPolicy/EditPolicyRulesTabRest.test.js +++ /dev/null @@ -1,64 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import TestWrapper from '@/Utilities/TestWrapper'; -import EditPolicyRulesTab from './EditPolicyRulesTabRest.js'; -// import Tailorings from '@/PresentationalComponents/Tailorings/Tailorings'; - -// eslint-disable-next-line react/display-name -jest.mock('@/PresentationalComponents/Tailorings/Tailorings', () => () => ( -
Tailorings tab
//TODO: do not mock the component, instead test with its behaviours -)); - -const defaultProps = { - policy: { total_system_count: 1 }, - assignedRuleIds: [], - setRuleValues: jest.fn(), - setUpdatedPolicy: jest.fn(), - selectedOsMinorVersions: [], -}; - -describe('EditPolicyRulesTab', () => { - it.skip('expect to render note when no rules can be configured', () => { - render( - - - - ); - - expect( - screen.getByText( - 'Different release versions of RHEL are associated with different versions of the SCAP Security Guide (SSG), therefore each release must be customized independently.' - ) - ).toBeInTheDocument(); - - expect(screen.queryByTestId('tailorings-tab')).toBeVisible(); - }); - - it('expect to render with empty state', () => { - render( - - - - ); - - expect( - screen.queryByText( - 'This policy has no associated systems, and therefore no rules can be configured.' - ) - ).toBeVisible(); - }); - - it('expect to render with loading state', () => { - render( - - - - ); - - expect(screen.queryByText('Loading...')).toBeVisible(); - }); -}); diff --git a/src/SmartComponents/EditPolicy/EditPolicySystemsTab.js b/src/SmartComponents/EditPolicy/EditPolicySystemsTab.js index 7a53f9e78..4125aad29 100644 --- a/src/SmartComponents/EditPolicy/EditPolicySystemsTab.js +++ b/src/SmartComponents/EditPolicy/EditPolicySystemsTab.js @@ -3,10 +3,13 @@ import { Text, TextContent } from '@patternfly/react-core'; import propTypes from 'prop-types'; import { SystemsTable } from 'SmartComponents'; import * as Columns from '../SystemsTable/Columns'; -import useAPIV2FeatureFlag from '@/Utilities/hooks/useAPIV2FeatureFlag'; +import { + fetchSystemsApi, + fetchCustomOSes, +} from 'SmartComponents/SystemsTable/constants'; const EmptyState = ({ osMajorVersion }) => ( - +
You do not have any RHEL {osMajorVersion} systems connected to @@ -16,7 +19,7 @@ const EmptyState = ({ osMajorVersion }) => ( Connect RHEL {osMajorVersion} systems to Insights. - +
); EmptyState.propTypes = { @@ -25,7 +28,7 @@ EmptyState.propTypes = { const PrependComponent = ({ osMajorVersion }) => ( - + Select which of your RHEL {osMajorVersion} systems should be included in this policy. @@ -41,43 +44,38 @@ PrependComponent.propTypes = { const EditPolicySystemsTab = ({ policy, onSystemSelect, - selectedSystems = [], + selectedSystems, + supportedOsVersions, }) => { - const apiV2Enabled = useAPIV2FeatureFlag(); - const { id: policyId, osMajorVersion, supportedOsVersions } = policy; - const osMinorVersions = supportedOsVersions.map( - (version) => version.split('.')[1] - ); - const osFilter = - osMajorVersion && - `os_major_version = ${osMajorVersion} AND os_minor_version ^ (${osMinorVersions.join( - ',' + const { os_major_version } = policy; + + const defaultFilter = + os_major_version && + `os_major_version = ${os_major_version} AND os_minor_version ^ (${supportedOsVersions.join( + ' ' )})`; - const defaultFilter = osFilter - ? `${osFilter} or policy_id = ${policyId}` - : `policy_id = ${policyId}`; return ( - - } - emptyStateComponent={} - compact - showActions={false} - defaultFilter={defaultFilter} - enableExport={false} - remediationsEnabled={false} - preselectedSystems={selectedSystems.map(({ id }) => id)} - onSelect={onSystemSelect} - apiV2Enabled={false} //TODO: change to useAPIV2FeatureFlag when migrating to REST - /> - + } + emptyStateComponent={} + compact + showActions={false} + defaultFilter={defaultFilter} + enableExport={false} + remediationsEnabled={false} + preselectedSystems={selectedSystems} + onSelect={onSystemSelect} + apiV2Enabled={true} + fetchApi={fetchSystemsApi} + fetchCustomOSes={fetchCustomOSes} + /> ); }; @@ -86,6 +84,7 @@ EditPolicySystemsTab.propTypes = { newRuleTabs: propTypes.bool, onSystemSelect: propTypes.func, selectedSystems: propTypes.array, + supportedOsVersions: propTypes.array, }; export default EditPolicySystemsTab; diff --git a/src/SmartComponents/EditPolicy/EditPolicySystemsTab.test.js b/src/SmartComponents/EditPolicy/EditPolicySystemsTab.test.js index 1c8b6a393..1115defe3 100644 --- a/src/SmartComponents/EditPolicy/EditPolicySystemsTab.test.js +++ b/src/SmartComponents/EditPolicy/EditPolicySystemsTab.test.js @@ -1,27 +1,104 @@ import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; -import TestWrapper from '@/Utilities/TestWrapper'; +import EditPolicySystemsTab from './EditPolicySystemsTab'; +import { + fetchSystemsApi, + fetchCustomOSes, +} from 'SmartComponents/SystemsTable/constants'; +import systemsFactory from '@/__factories__/systems'; +import TestWrapper from '@redhat-cloud-services/frontend-components-utilities/TestingUtils/JestUtils/TestWrapper'; +import { waitFor } from '@testing-library/react'; -import EditPolicySystemsTab from './EditPolicySystemsTab.js'; +jest.mock('@redhat-cloud-services/frontend-components/Inventory', () => ({ + InventoryTable: jest.fn(({ getEntities }) => { + getEntities(null, { + page: 1, + per_page: 10, + orderBy: 'name', + orderDirection: 'asc', + filters: { + hostnameOrId: 'test-name', + tagFilters: [], + }, + }); + return
Inventory
; + }), +})); -jest.mock('@/Utilities/hooks/useAPIV2FeatureFlag', () => jest.fn(() => false)); +jest.mock('SmartComponents/SystemsTable/constants', () => ({ + ...jest.requireActual('SmartComponents/SystemsTable/constants'), + fetchSystemsApi: jest.fn(() => Promise.resolve([])), + fetchCustomOSes: jest.fn(() => Promise.resolve([])), +})); +jest.mock('@apollo/client'); +fetchSystemsApi.mockReturnValue( + Promise.resolve({ + data: systemsFactory.buildList(2), + meta: { total: 2 }, + }) +); + +fetchCustomOSes.mockReturnValue( + Promise.resolve({ + data: ['8.1', '8.2'], + meta: { total: 2 }, + }) +); + +const defaultProps = { + policy: { os_major_version: 8 }, + onSystemSelect: jest.fn(), + selectedSystems: ['test-system-1', 'test-system-2'], + supportedOsVersions: [1, 2], +}; describe('EditPolicySystemsTab', () => { - const defaultProps = { - policy: { - id: '12345abcde', - osMajorVersion: '7', - supportedOsVersions: ['1.2', '1.1', '1.3', '1.4'], - }, - }; - - it('expect to render the Inventory Table', () => { + it('Should render with data', async () => { + render( + + + + ); + + await screen.findByTestId('inventory-mock-component'); + await waitFor(() => + expect(fetchSystemsApi).toHaveBeenCalledWith(0, 10, { + filter: + 'os_major_version = 8 AND os_minor_version ^ (1 2), display_name ~ test-name', + sortBy: ['name:asc'], + tags: [], + }) + ); + }); + + it('Should render with prepend component by default', async () => { + render( + + + + ); + + await waitFor(async () => + expect(screen.getByTestId('prepend-component')).toHaveTextContent( + `Select which of your RHEL ${defaultProps.policy.os_major_version} systems should be included in this policy.` + ) + ); + }); + + it.only('Should display empty state', async () => { + fetchSystemsApi.mockReturnValue( + Promise.resolve({ + data: [], + meta: { total: 0 }, + }) + ); + render( ); - expect(screen.getByLabelText('Inventory Table')).toBeInTheDocument(); + await screen.findByTestId('empty-state'); }); }); diff --git a/src/SmartComponents/EditPolicy/EditPolicySystemsTabRest.js b/src/SmartComponents/EditPolicy/EditPolicySystemsTabRest.js deleted file mode 100644 index 4125aad29..000000000 --- a/src/SmartComponents/EditPolicy/EditPolicySystemsTabRest.js +++ /dev/null @@ -1,90 +0,0 @@ -import React from 'react'; -import { Text, TextContent } from '@patternfly/react-core'; -import propTypes from 'prop-types'; -import { SystemsTable } from 'SmartComponents'; -import * as Columns from '../SystemsTable/Columns'; -import { - fetchSystemsApi, - fetchCustomOSes, -} from 'SmartComponents/SystemsTable/constants'; - -const EmptyState = ({ osMajorVersion }) => ( -
- - - You do not have any RHEL {osMajorVersion} systems connected to - Insights and enabled for Compliance. - - - - Connect RHEL {osMajorVersion} systems to Insights. - -
-); - -EmptyState.propTypes = { - osMajorVersion: propTypes.string, -}; - -const PrependComponent = ({ osMajorVersion }) => ( - - - - Select which of your RHEL {osMajorVersion} systems should be - included in this policy. - - - -); - -PrependComponent.propTypes = { - osMajorVersion: propTypes.string, -}; - -const EditPolicySystemsTab = ({ - policy, - onSystemSelect, - selectedSystems, - supportedOsVersions, -}) => { - const { os_major_version } = policy; - - const defaultFilter = - os_major_version && - `os_major_version = ${os_major_version} AND os_minor_version ^ (${supportedOsVersions.join( - ' ' - )})`; - - return ( - } - emptyStateComponent={} - compact - showActions={false} - defaultFilter={defaultFilter} - enableExport={false} - remediationsEnabled={false} - preselectedSystems={selectedSystems} - onSelect={onSystemSelect} - apiV2Enabled={true} - fetchApi={fetchSystemsApi} - fetchCustomOSes={fetchCustomOSes} - /> - ); -}; - -EditPolicySystemsTab.propTypes = { - policy: propTypes.object, - newRuleTabs: propTypes.bool, - onSystemSelect: propTypes.func, - selectedSystems: propTypes.array, - supportedOsVersions: propTypes.array, -}; - -export default EditPolicySystemsTab; diff --git a/src/SmartComponents/EditPolicy/EditPolicySystemsTabRest.test.js b/src/SmartComponents/EditPolicy/EditPolicySystemsTabRest.test.js deleted file mode 100644 index 1e8b3be2c..000000000 --- a/src/SmartComponents/EditPolicy/EditPolicySystemsTabRest.test.js +++ /dev/null @@ -1,105 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import '@testing-library/jest-dom'; -import EditPolicySystemsTab from './EditPolicySystemsTabRest'; -import { - fetchSystemsApi, - fetchCustomOSes, -} from 'SmartComponents/SystemsTable/constants'; -import systemsFactory from '@/__factories__/systems'; -import TestWrapper from '@redhat-cloud-services/frontend-components-utilities/TestingUtils/JestUtils/TestWrapper'; -import { waitFor } from '@testing-library/react'; - -jest.mock('@/Utilities/hooks/useAPIV2FeatureFlag', () => jest.fn(() => true)); -jest.mock('@redhat-cloud-services/frontend-components/Inventory', () => ({ - InventoryTable: jest.fn(({ getEntities }) => { - getEntities(null, { - page: 1, - per_page: 10, - orderBy: 'name', - orderDirection: 'asc', - filters: { - hostnameOrId: 'test-name', - tagFilters: [], - }, - }); - return
Inventory
; - }), -})); - -jest.mock('SmartComponents/SystemsTable/constants', () => ({ - ...jest.requireActual('SmartComponents/SystemsTable/constants'), - fetchSystemsApi: jest.fn(() => Promise.resolve([])), - fetchCustomOSes: jest.fn(() => Promise.resolve([])), -})); -jest.mock('@apollo/client'); - -fetchSystemsApi.mockReturnValue( - Promise.resolve({ - data: systemsFactory.buildList(2), - meta: { total: 2 }, - }) -); - -fetchCustomOSes.mockReturnValue( - Promise.resolve({ - data: ['8.1', '8.2'], - meta: { total: 2 }, - }) -); - -const defaultProps = { - policy: { os_major_version: 8 }, - onSystemSelect: jest.fn(), - selectedSystems: ['test-system-1', 'test-system-2'], - supportedOsVersions: [1, 2], -}; -describe('EditPolicySystemsTabRest', () => { - it('Should render with data', async () => { - render( - - - - ); - - await screen.findByTestId('inventory-mock-component'); - await waitFor(() => - expect(fetchSystemsApi).toHaveBeenCalledWith(0, 10, { - filter: - 'os_major_version = 8 AND os_minor_version ^ (1 2), display_name ~ test-name', - sortBy: ['name:asc'], - tags: [], - }) - ); - }); - - it('Should render with prepend component by default', async () => { - render( - - - - ); - - await waitFor(async () => - expect(screen.getByTestId('prepend-component')).toHaveTextContent( - `Select which of your RHEL ${defaultProps.policy.os_major_version} systems should be included in this policy.` - ) - ); - }); - - it.only('Should display empty state', async () => { - fetchSystemsApi.mockReturnValue( - Promise.resolve({ - data: [], - meta: { total: 0 }, - }) - ); - - render( - - - - ); - - await screen.findByTestId('empty-state'); - }); -}); diff --git a/src/SmartComponents/EditPolicy/__snapshots__/EditPolicyForm.test.js.snap b/src/SmartComponents/EditPolicy/__snapshots__/EditPolicyForm.test.js.snap deleted file mode 100644 index e9a881d35..000000000 --- a/src/SmartComponents/EditPolicy/__snapshots__/EditPolicyForm.test.js.snap +++ /dev/null @@ -1,241 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EditPolicyForm expect to render with alter 1`] = ` - -
-
-
    - - -
-
-
-
-
-
- - Loading... - -
-
-
-
- -
-
-`; - -exports[`EditPolicyForm expect to render without error 1`] = ` - -
-
-
    - - -
-
-
-
-
-
- - Loading... - -
-
-
-
- -
-
-`; diff --git a/src/SmartComponents/EditPolicy/__snapshots__/EditPolicyRulesTab.test.js.snap b/src/SmartComponents/EditPolicy/__snapshots__/EditPolicyRulesTab.test.js.snap deleted file mode 100644 index 79480cb52..000000000 --- a/src/SmartComponents/EditPolicy/__snapshots__/EditPolicyRulesTab.test.js.snap +++ /dev/null @@ -1,40 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`.toTabsData expect to render without error 1`] = ` -[ - { - "newOsMinorVersion": "9", - "profile": { - "benchmark": { - "version": "0.1.49", - }, - "id": "b71376fd-015e-4209-99af", - "name": "United States Government Configuration Baseline123", - "osMajorVersion": "7", - "osMinorVersion": "9", - "refId": "xccdf_org.ssgproject.content_profile_ospp123", - "rules": [ - { - "__typename": "Rule", - "description": "If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d: -w /etc/localtime -p wa -k audit_time_rules If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file: -w /etc/localtime -p wa -k audit_time_rules The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport and should always be used.", - "rationale": "Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.", - "refId": "xccdf_org.ssgproject.content_rule_audit_rules_time_watch_localtime", - "remediationAvailable": false, - "severity": "medium", - "title": "Record Attempts to Alter the localtime File", - }, - { - "__typename": "Rule", - "description": "If the auditd daemon is configured to use the augenrules program to read audit rules during daemon startup (the default), add the following line to a file with suffix .rules in the directory /etc/audit/rules.d: -a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change If the system is 64 bit then also add the following line: -a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change If the auditd daemon is configured to use the auditctl utility to read audit rules during daemon startup, add the following line to /etc/audit/audit.rules file: -a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change If the system is 64 bit then also add the following line: -a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change The -k option allows for the specification of a key in string form that can be used for better reporting capability through ausearch and aureport. Multiple system calls can be defined on the same line to save space if desired, but is not required. See an example of multiple combined syscalls: -a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=audit_time_rules", - "rationale": "Arbitrary changes to the system time can be used to obfuscate nefarious activities in log files, as well as to confuse network services that are highly dependent upon an accurate system time (such as sshd). All changes to the system time should be audited.", - "refId": "xccdf_org.ssgproject.content_rule_audit_rules_time_clock_settime", - "remediationAvailable": false, - "severity": "medium", - "title": "Record Attempts to Alter Time Through clock_settime", - }, - ], - }, - "systemCount": 1, - }, -] -`; diff --git a/src/SmartComponents/EditPolicy/constants.js b/src/SmartComponents/EditPolicy/constants.js deleted file mode 100644 index 4dd0b23f5..000000000 --- a/src/SmartComponents/EditPolicy/constants.js +++ /dev/null @@ -1,115 +0,0 @@ -import { gql } from '@apollo/client'; - -export const BENCHMARKS_QUERY = gql` - query EP_Benchmarks($filter: String!) { - benchmarks(search: $filter) { - nodes { - id - latestSupportedOsMinorVersions - ruleTree - valueDefinitions { - defaultValue - description - id - refId - title - valueType - } - profiles { - id - refId - ssgVersion - } - } - } - } -`; - -export const MULTIVERSION_QUERY = gql` - query EP_Profile($policyId: String!) { - profile(id: $policyId) { - id - name - refId - external - description - totalHostCount - compliantHostCount - complianceThreshold - osMajorVersion - supportedOsVersions - lastScanned - policyType - policy { - id - name - refId - profiles { - id - parentProfileId - name - refId - osMinorVersion - osMajorVersion - values - benchmark { - id - title - latestSupportedOsMinorVersions - osMajorVersion - version - ruleTree - } - rules { - title - severity - rationale - refId - description - remediationAvailable - identifier - values - } - } - } - businessObjective { - id - title - } - hosts { - id - osMinorVersion - osMajorVersion - } - } - } -`; - -export const RULE_VALUE_DEFINITIONS_QUERY = gql` - query EP_ProfileValueDefinitions($policyId: String!) { - profile(id: $policyId) { - id - policy { - id - refId - profiles { - id - parentProfileId - refId - benchmark { - id - ruleTree - valueDefinitions { - defaultValue - description - id - refId - title - valueType - } - } - } - } - } - } -`; diff --git a/src/SmartComponents/EditPolicy/hooks.test.js b/src/SmartComponents/EditPolicy/hooks.test.js index f118ab6ca..a0590e829 100644 --- a/src/SmartComponents/EditPolicy/hooks.test.js +++ b/src/SmartComponents/EditPolicy/hooks.test.js @@ -1,17 +1,18 @@ import { renderHook, act, waitFor } from '@testing-library/react'; import { useOnSave } from './hooks'; - import { dispatchNotification } from 'Utilities/Dispatcher'; -jest.mock('Utilities/Dispatcher'); +import useAssignSystems from '../../Utilities/hooks/api/useAssignSystems'; +jest.mock('Utilities/Dispatcher'); jest.mock('Utilities/hooks/useAnchor', () => ({ __esModule: true, default: () => () => ({}), })); +jest.mock('../../Utilities/hooks/api/useAssignSystems'); describe('useOnSave', function () { const policy = {}; - const updatedPolicy = {}; + const updatedPolicy = { hosts: ['abc'] }; const mockedNotification = jest.fn(); const onSaveCallBack = jest.fn(); const onErrorCallback = jest.fn(); @@ -23,6 +24,10 @@ describe('useOnSave', function () { }); it('returns a function to call with a policy and updated policy', async () => { + useAssignSystems.mockReturnValue({ + fetch: () => Promise.resolve(), + }); + const { result } = renderHook(() => useOnSave(policy, updatedPolicy, { onSave: onSaveCallBack, @@ -47,7 +52,11 @@ describe('useOnSave', function () { expect(onErrorCallback).not.toHaveBeenCalled(); }); - it('returns a function to call with a policy and updated policy and can raise an error', async () => { + it('returns a function to call with a policy and updated policy and raises an error', async () => { + useAssignSystems.mockReturnValueOnce({ + fetch: () => Promise.reject({ title: 'damn' }), + }); + const { result } = renderHook(() => useOnSave(policy, updatedPolicy, { onSave: onSaveCallBack, diff --git a/src/SmartComponents/EditPolicy/hooks/usePolicyQueries.js b/src/SmartComponents/EditPolicy/hooks/usePolicyQueries.js deleted file mode 100644 index e7fe86fb9..000000000 --- a/src/SmartComponents/EditPolicy/hooks/usePolicyQueries.js +++ /dev/null @@ -1,58 +0,0 @@ -import { useMemo } from 'react'; -import { useParams } from 'react-router-dom'; -import { useQuery } from '@apollo/client'; -import { MULTIVERSION_QUERY, RULE_VALUE_DEFINITIONS_QUERY } from '../constants'; - -const appendBenchmark = (profileWithValueDefinitions) => (profile) => ({ - ...profile, - benchmark: { - ...profile.benchmark, - ...profileWithValueDefinitions.profile.policy.profiles.find( - ({ id }) => profile.id === id - ).benchmark, - }, -}); - -const mergeQueries = (profileData, profileWithValueDefinitions) => { - const appendBenchmarkFunction = appendBenchmark(profileWithValueDefinitions); - - return { - ...profileData?.profile, - policy: { - ...profileData.profile.policy, - profiles: profileData.profile.policy?.profiles?.map( - appendBenchmarkFunction - ), - }, - }; -}; - -const usePolicyQueries = () => { - const { policy_id: policyId } = useParams(); - const { data, loading, error } = useQuery(MULTIVERSION_QUERY, { - variables: { policyId }, - }); - const { - data: ruleValueDefinitionsData, - loading: ruleValueDefinitionsLoading, - error: ruleValueDefinitionsError, - } = useQuery(RULE_VALUE_DEFINITIONS_QUERY, { - variables: { policyId }, - }); - - const policy = useMemo( - () => - data?.profile && ruleValueDefinitionsData?.profile - ? mergeQueries(data, ruleValueDefinitionsData) - : undefined, - [data, ruleValueDefinitionsData] - ); - - return { - policy, - loading: loading && ruleValueDefinitionsLoading, - error: error || ruleValueDefinitionsError, - }; -}; - -export default usePolicyQueries;