diff --git a/src/Components/CreateImageWizardV2/steps/Oscap/Oscap.tsx b/src/Components/CreateImageWizardV2/steps/Oscap/Oscap.tsx index 3146f0f3..2efa5c37 100644 --- a/src/Components/CreateImageWizardV2/steps/Oscap/Oscap.tsx +++ b/src/Components/CreateImageWizardV2/steps/Oscap/Oscap.tsx @@ -1,9 +1,8 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Alert, FormGroup, - Spinner, Popover, TextContent, Text, @@ -26,14 +25,8 @@ import { } from '../../../../store/imageBuilderApi'; import { changeOscapProfile, - changeKernel, selectDistribution, selectProfile, - selectKernel, - selectDisabledServices, - selectEnabledServices, - changeDisabledServices, - changeEnabledServices, clearOscapPackages, addPackage, selectPackages, @@ -42,13 +35,10 @@ import { const ProfileSelector = () => { const oscapProfile = useAppSelector(selectProfile); - let kernel = useAppSelector(selectKernel); - let disabledServices = useAppSelector(selectDisabledServices); - let enabledServices = useAppSelector(selectEnabledServices); + const release = useAppSelector(selectDistribution); const packages = useAppSelector(selectPackages); const dispatch = useAppDispatch(); - const [profileName, setProfileName] = useState('None'); const [isOpen, setIsOpen] = useState(false); const { data: profiles, @@ -60,7 +50,7 @@ const ProfileSelector = () => { distribution: release, }); - const { data } = useGetOscapCustomizationsQuery( + const { data: oscapData } = useGetOscapCustomizationsQuery( { distribution: release, // @ts-ignore if oscapProfile is undefined the query is going to get skipped, so it's safe here to ignore the linter here @@ -70,55 +60,28 @@ const ProfileSelector = () => { skip: !oscapProfile, } ); - kernel = data?.kernel?.append; - disabledServices = data?.services?.disabled; - enabledServices = data?.services?.enabled; - - useEffect(() => { - if (isFetching || !isSuccess) return; - dispatch(changeKernel(kernel)); - dispatch(changeDisabledServices(disabledServices)); - dispatch(changeEnabledServices(enabledServices)); - }, [ - isFetching, - isSuccess, - dispatch, - data?.kernel?.append, - data?.services?.disabled, - data?.services?.enabled, - disabledServices, - enabledServices, - kernel, - ]); - - useEffect(() => { - if ( - data && - data.openscap && - typeof data.openscap.profile_name === 'string' - ) { - setProfileName(data.openscap.profile_name); - } - }, [data]); + const profileName = oscapProfile ? oscapData?.openscap?.profile_name : 'None'; useEffect(() => { dispatch(clearOscapPackages()); - for (const pkg in data?.packages) { + for (const pkg in oscapData?.packages) { if ( - packages.map((pkg) => pkg.name).includes(data?.packages[Number(pkg)]) + packages + .map((pkg) => pkg.name) + .includes(oscapData?.packages[Number(pkg)]) ) { - dispatch(removePackage(data?.packages[Number(pkg)])); + dispatch(removePackage(oscapData?.packages[Number(pkg)])); } dispatch( addPackage({ - name: data?.packages[Number(pkg)], + name: oscapData?.packages[Number(pkg)], summary: 'Required by chosen OpenSCAP profile', repository: 'distro', isRequiredByOpenScap: true, }) ); } - }, [data?.packages, dispatch]); + }, [oscapData?.packages, dispatch]); const handleToggle = () => { if (!isOpen) { @@ -129,52 +92,26 @@ const ProfileSelector = () => { const handleClear = () => { dispatch(changeOscapProfile(undefined)); - dispatch(changeKernel(undefined)); - dispatch(changeDisabledServices(undefined)); - dispatch(changeEnabledServices(undefined)); dispatch(clearOscapPackages()); - setProfileName(undefined); }; const handleSelect = ( _event: React.MouseEvent, - selection: DistributionProfileItem + selection: OScapSelectOptionValueType ) => { - dispatch(changeOscapProfile(selection)); - dispatch(changeKernel(kernel)); - dispatch(changeDisabledServices(disabledServices)); - dispatch(changeEnabledServices(enabledServices)); + dispatch(changeOscapProfile(selection.id)); setIsOpen(false); }; - const options = [ - , - ]; - if (isSuccess) { - options.concat( - profiles.map((profile_id) => { - return ( - - ); - }) - ); - } - - if (isFetching) { - options.push( - - - - ); - } + const options = () => { + if (profiles) { + return [].concat( + profiles.map((profile_id, index) => { + return ; + }) + ); + } + }; return ( { } > {isError && ( { ); }; -type OScapNoneOptionPropType = { - setProfileName: (name: string) => void; -}; - -const OScapNoneOption = ({ setProfileName }: OScapNoneOptionPropType) => { +const OScapNoneOption = () => { return ( - { - setProfileName('None'); - }} - > -

{'None'}

-
+ 'None', compareTo: () => false }} /> ); }; type OScapSelectOptionPropType = { profile_id: DistributionProfileItem; - setProfileName: (name: string) => void; - input?: string; + filter?: string; +}; + +type OScapSelectOptionValueType = { + id: DistributionProfileItem; + toString: () => string; }; const OScapSelectOption = ({ profile_id, - setProfileName, - input, + filter, }: OScapSelectOptionPropType) => { const release = useAppSelector(selectDistribution); const { data } = useGetOscapCustomizationsQuery({ distribution: release, profile: profile_id, }); - if ( - input && - !data?.openscap?.profile_name?.toLowerCase().includes(input.toLowerCase()) + filter && + !data?.openscap?.profile_name?.toLowerCase().includes(filter.toLowerCase()) ) { return null; } + const selectObject = ( + id: DistributionProfileItem, + name?: string + ): OScapSelectOptionValueType => ({ + id, + toString: () => name || '', + }); return ( { - if (data?.openscap?.profile_name) { - setProfileName(data?.openscap?.profile_name); - } - }} - > -

{data?.openscap?.profile_name}

-
+ value={selectObject(profile_id, data?.openscap?.profile_name)} + description={data?.openscap?.profile_description} + /> ); }; diff --git a/src/Components/CreateImageWizardV2/steps/Oscap/index.tsx b/src/Components/CreateImageWizardV2/steps/Oscap/index.tsx index dbde7464..98d913b1 100644 --- a/src/Components/CreateImageWizardV2/steps/Oscap/index.tsx +++ b/src/Components/CreateImageWizardV2/steps/Oscap/index.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Button, Form, Text, Title } from '@patternfly/react-core'; import { ExternalLinkAltIcon } from '@patternfly/react-icons'; @@ -10,9 +10,14 @@ import { useAppSelector } from '../../../../store/hooks'; import { selectDistribution } from '../../../../store/wizardSlice'; const OscapStep = () => { - const prefetchOscapProfile = imageBuilderApi.usePrefetch('getOscapProfiles'); + const prefetchOscapProfile = imageBuilderApi.usePrefetch( + 'getOscapProfiles', + {} + ); const release = useAppSelector(selectDistribution); - prefetchOscapProfile({ distribution: release }); + useEffect(() => { + prefetchOscapProfile({ distribution: release }); + }, []); return (
diff --git a/src/Components/CreateImageWizardV2/steps/Review/Footer/Footer.tsx b/src/Components/CreateImageWizardV2/steps/Review/Footer/Footer.tsx index da22736e..97eb9cac 100644 --- a/src/Components/CreateImageWizardV2/steps/Review/Footer/Footer.tsx +++ b/src/Components/CreateImageWizardV2/steps/Review/Footer/Footer.tsx @@ -16,6 +16,7 @@ import { useNavigate, useParams } from 'react-router-dom'; import CreateDropdown from './CreateDropdown'; import EditDropdown from './EditDropdown'; +import { useServerStore } from '../../../../../store/hooks'; import { useCreateBlueprintMutation, useUpdateBlueprintMutation, @@ -33,6 +34,9 @@ const ReviewWizardFooter = () => { reset: resetCreate, }, ] = useCreateBlueprintMutation({ fixedCacheKey: 'createBlueprintKey' }); + + // initialize the server store with the data from RTK query + const serverStore = useServerStore(); const [ , { @@ -61,7 +65,7 @@ const ReviewWizardFooter = () => { const getBlueprintPayload = async () => { const userData = await auth?.getUser(); const orgId = userData?.identity?.internal?.org_id; - const requestBody = orgId && mapRequestFromState(store, orgId); + const requestBody = orgId && mapRequestFromState(store, orgId, serverStore); return requestBody; }; diff --git a/src/Components/CreateImageWizardV2/utilities/requestMapper.tsx b/src/Components/CreateImageWizardV2/utilities/requestMapper.tsx index 32fb6847..88667194 100644 --- a/src/Components/CreateImageWizardV2/utilities/requestMapper.tsx +++ b/src/Components/CreateImageWizardV2/utilities/requestMapper.tsx @@ -7,9 +7,12 @@ import { BlueprintResponse, CreateBlueprintRequest, Customizations, + DistributionProfileItem, GcpUploadRequestOptions, ImageRequest, ImageTypes, + OpenScap, + Services, Subscription, UploadTypes, } from '../../../store/imageBuilderApi'; @@ -36,6 +39,7 @@ import { selectPackages, selectPayloadRepositories, selectRecommendedRepositories, + selectProfile, selectRegistrationType, selectServerUrl, wizardState, @@ -46,18 +50,26 @@ import { } from '../steps/Repositories/Repositories'; import { GcpAccountType } from '../steps/TargetEnvironment/Gcp'; +type ServerStore = { + kernel?: { append?: string }; + services?: { enabled?: string[]; disabled?: string[] }; +}; + /** * This function maps the wizard state to a valid CreateBlueprint request object + * @param {Store} store redux store * @param {string} orgID organization ID + * * @returns {CreateBlueprintRequest} blueprint creation request payload */ export const mapRequestFromState = ( store: Store, - orgID: string + orgID: string, + serverStore: ServerStore ): CreateBlueprintRequest => { const state = store.getState(); const imageRequests = getImageRequests(state); - const customizations = getCustomizations(state, orgID); + const customizations = getCustomizations(state, orgID, serverStore); return { name: selectBlueprintName(state), @@ -102,16 +114,9 @@ export const mapRequestToState = (request: BlueprintResponse): wizardState => { serverUrl: request.customizations.subscription?.['server-url'] || '', baseUrl: request.customizations.subscription?.['base-url'] || '', }, - // TODO: add openscap support openScap: { - profile: undefined, - kernel: { - kernelAppend: '', - }, - services: { - disabled: [], - enabled: [], - }, + profile: request.customizations.openscap + ?.profile_id as DistributionProfileItem, }, fileSystem: { mode: 'automatic', @@ -258,7 +263,11 @@ const getImageOptions = ( return {}; }; -const getCustomizations = (state: RootState, orgID: string): Customizations => { +const getCustomizations = ( + state: RootState, + orgID: string, + serverStore: ServerStore +): Customizations => { return { containers: undefined, directories: undefined, @@ -267,12 +276,14 @@ const getCustomizations = (state: RootState, orgID: string): Customizations => { packages: getPackages(state), payload_repositories: getPayloadRepositories(state), custom_repositories: getCustomRepositories(state), - openscap: undefined, + openscap: getOpenscapProfile(state), filesystem: undefined, users: undefined, - services: undefined, + services: getServices(serverStore), hostname: undefined, - kernel: undefined, + kernel: serverStore.kernel?.append + ? { append: serverStore.kernel?.append } + : undefined, groups: undefined, timezone: undefined, locale: undefined, @@ -285,6 +296,27 @@ const getCustomizations = (state: RootState, orgID: string): Customizations => { }; }; +const getServices = (serverStore: ServerStore): Services | undefined => { + const enabledServices = serverStore.services?.enabled; + const disabledServices = serverStore.services?.disabled; + + if (enabledServices || disabledServices) { + return { + enabled: enabledServices, + disabled: disabledServices, + }; + } + return undefined; +}; + +const getOpenscapProfile = (state: RootState): OpenScap | undefined => { + const profile = selectProfile(state); + if (profile) { + return { profile_id: profile }; + } + return undefined; +}; + const getPackages = (state: RootState) => { const packages = selectPackages(state); diff --git a/src/store/hooks.ts b/src/store/hooks.ts index 2117db80..cabdd719 100644 --- a/src/store/hooks.ts +++ b/src/store/hooks.ts @@ -1,7 +1,37 @@ import { useDispatch, useSelector } from 'react-redux'; +import { useGetOscapCustomizationsQuery } from './imageBuilderApi'; +import { selectDistribution, selectProfile } from './wizardSlice'; + import type { RootState, AppDispatch } from './index'; // Use throughout your app instead of plain `useDispatch` and `useSelector` export const useAppDispatch = useDispatch.withTypes(); export const useAppSelector = useSelector.withTypes(); + +// common hooks +export const useOscapData = () => { + const release = useAppSelector(selectDistribution); + const openScapProfile = useAppSelector(selectProfile); + const { data } = useGetOscapCustomizationsQuery( + { + distribution: release, + // @ts-ignore if openScapProfile is undefined the query is going to get skipped + profile: openScapProfile, + }, + { skip: !openScapProfile } + ); + if (!openScapProfile) return undefined; + return { + kernel: { append: data?.kernel?.append }, + services: { + enabled: data?.services?.enabled, + disabled: data?.services?.disabled, + }, + }; +}; + +export const useServerStore = () => { + const oscap = useOscapData(); + return { ...oscap }; +}; diff --git a/src/store/wizardSlice.ts b/src/store/wizardSlice.ts index 206976b1..2d83aae6 100644 --- a/src/store/wizardSlice.ts +++ b/src/store/wizardSlice.ts @@ -66,13 +66,6 @@ export type wizardState = { }; openScap: { profile: DistributionProfileItem | undefined; - kernel: { - kernelAppend: string | undefined; - }; - services: { - disabled: string[] | undefined; - enabled: string[] | undefined; - }; }; fileSystem: { mode: FileSystemPartitionMode; @@ -122,13 +115,6 @@ const initialState: wizardState = { }, openScap: { profile: undefined, - kernel: { - kernelAppend: '', - }, - services: { - disabled: [], - enabled: [], - }, }, fileSystem: { mode: 'automatic', @@ -223,18 +209,6 @@ export const selectProfile = (state: RootState) => { return state.wizard.openScap.profile; }; -export const selectKernel = (state: RootState) => { - return state.wizard.openScap.kernel.kernelAppend; -}; - -export const selectDisabledServices = (state: RootState) => { - return state.wizard.openScap.services.disabled; -}; - -export const selectEnabledServices = (state: RootState) => { - return state.wizard.openScap.services.enabled; -}; - export const selectFileSystemPartitionMode = (state: RootState) => { return state.wizard.fileSystem.mode; }; @@ -370,21 +344,6 @@ export const wizardSlice = createSlice({ state.openScap.profile = action.payload; }, - changeKernel: (state, action: PayloadAction) => { - state.openScap.kernel.kernelAppend = action.payload; - }, - changeDisabledServices: ( - state, - action: PayloadAction - ) => { - state.openScap.services.disabled = action.payload; - }, - changeEnabledServices: ( - state, - action: PayloadAction - ) => { - state.openScap.services.enabled = action.payload; - }, changeFileSystemConfiguration: ( state, action: PayloadAction @@ -527,9 +486,6 @@ export const { changeRegistrationType, changeActivationKey, changeOscapProfile, - changeKernel, - changeDisabledServices, - changeEnabledServices, changeFileSystemConfiguration, setIsNextButtonTouched, changeFileSystemPartitionMode,