From 0897257125084d41c4e5f4d3a25a10041e30c820 Mon Sep 17 00:00:00 2001 From: Sanne Raymaekers Date: Fri, 13 Sep 2024 12:18:54 +0200 Subject: [PATCH] src/store: Remove server store hooks Removing the server store makes the way we handle data going in and out of the wizard state more consistent. Each customisation is mapped into the wizard state and pulled out when generating the blueprint payload. When the services and kernel customisations are implemented, this information will need to be stored inside of the wizard state anyway. Lastly this will make implementing a compliance step easier for edit mode, removing the need to write to the wizard state from within the server store when only a compliance policy id is available (on the review page), which would be used to fetch the profile ref id, which would in turn be used to fetch the customisations not stored in the wizard state. --- .../CreateImageWizard/steps/Oscap/Oscap.tsx | 37 ++++++--- .../steps/Review/Footer/Footer.tsx | 4 +- .../utilities/requestMapper.ts | 79 +++++++++---------- src/store/hooks.ts | 34 -------- src/store/wizardSlice.ts | 40 ++++++++++ 5 files changed, 104 insertions(+), 90 deletions(-) diff --git a/src/Components/CreateImageWizard/steps/Oscap/Oscap.tsx b/src/Components/CreateImageWizard/steps/Oscap/Oscap.tsx index 4417589d..0903ebb8 100644 --- a/src/Components/CreateImageWizard/steps/Oscap/Oscap.tsx +++ b/src/Components/CreateImageWizard/steps/Oscap/Oscap.tsx @@ -18,17 +18,14 @@ import { v4 as uuidv4 } from 'uuid'; import OscapProfileInformation from './OscapProfileInformation'; -import { - useAppDispatch, - useAppSelector, - useServerStore, -} from '../../../../store/hooks'; +import { useAppDispatch, useAppSelector } from '../../../../store/hooks'; import { DistributionProfileItem, Filesystem, useGetOscapCustomizationsQuery, useGetOscapProfilesQuery, useLazyGetOscapCustomizationsQuery, + Services, } from '../../../../store/imageBuilderApi'; import { changeOscapProfile, @@ -40,6 +37,10 @@ import { removePackage, clearPartitions, selectImageTypes, + changeEnabledServices, + changeMaskedServices, + changeDisabledServices, + changeKernelAppend, } from '../../../../store/wizardSlice'; import { useHasSpecificTargetOnly } from '../../utilities/hasSpecificTargetOnly'; import { parseSizeUnit } from '../../utilities/parseSizeUnit'; @@ -47,7 +48,6 @@ import { Partition, Units } from '../FileSystem/FileSystemConfiguration'; const ProfileSelector = () => { const oscapProfile = useAppSelector(selectProfile); - const oscapData = useServerStore(); const release = useAppSelector(selectDistribution); const hasWslTargetOnly = useHasSpecificTargetOnly('wsl'); const dispatch = useAppDispatch(); @@ -62,7 +62,14 @@ const ProfileSelector = () => { distribution: release, }); - const profileName = oscapProfile ? oscapData.profileName : 'None'; + const { data: currentProfileData } = useGetOscapCustomizationsQuery( + { + distribution: release, + // @ts-ignore if openScapProfile is undefined the query is going to get skipped + profile: oscapProfile, + }, + { skip: !oscapProfile } + ); const [trigger] = useLazyGetOscapCustomizationsQuery(); @@ -75,8 +82,10 @@ const ProfileSelector = () => { const handleClear = () => { dispatch(changeOscapProfile(undefined)); - clearOscapPackages(oscapData.packages || []); + clearOscapPackages(currentProfileData?.packages || []); dispatch(changeFileSystemConfigurationType('automatic')); + handleServices(undefined); + dispatch(changeKernelAppend('')); }; const handlePackages = ( @@ -124,6 +133,12 @@ const ProfileSelector = () => { } }; + const handleServices = (services: Services | undefined) => { + dispatch(changeEnabledServices(services?.enabled || [])); + dispatch(changeMaskedServices(services?.masked || [])); + dispatch(changeDisabledServices(services?.disabled || [])); + }; + const handleSelect = ( _event: React.MouseEvent, selection: OScapSelectOptionValueType @@ -132,7 +147,7 @@ const ProfileSelector = () => { // handle user has selected 'None' case handleClear(); } else { - const oldOscapPackages = oscapData.packages || []; + const oldOscapPackages = currentProfileData?.packages || []; trigger( { distribution: release, @@ -146,6 +161,8 @@ const ProfileSelector = () => { const newOscapPackages = response.packages || []; handlePartitions(oscapPartitions); handlePackages(oldOscapPackages, newOscapPackages); + handleServices(response.services); + dispatch(changeKernelAppend(response.kernel?.append || '')); dispatch(changeOscapProfile(selection.id)); }); } @@ -200,7 +217,7 @@ const ProfileSelector = () => { onSelect={handleSelect} onClear={handleClear} maxHeight="300px" - selections={profileName} + selections={oscapProfile} isOpen={isOpen} placeholderText="Select a profile" typeAheadAriaLabel="Select a profile" diff --git a/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx b/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx index ff6792b0..d3677ccd 100644 --- a/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx +++ b/src/Components/CreateImageWizard/steps/Review/Footer/Footer.tsx @@ -15,7 +15,6 @@ import { useNavigate, useParams } from 'react-router-dom'; import { CreateSaveAndBuildBtn, CreateSaveButton } from './CreateDropdown'; import { EditSaveAndBuildBtn, EditSaveButton } from './EditDropdown'; -import { useServerStore } from '../../../../../store/hooks'; import { useCreateBlueprintMutation, useUpdateBlueprintMutation, @@ -30,7 +29,6 @@ const ReviewWizardFooter = () => { useCreateBlueprintMutation({ fixedCacheKey: 'createBlueprintKey' }); // initialize the server store with the data from RTK query - const serverStore = useServerStore(); const [, { isSuccess: isUpdateSuccess, reset: resetUpdate }] = useUpdateBlueprintMutation({ fixedCacheKey: 'updateBlueprintKey' }); const { auth } = useChrome(); @@ -54,7 +52,7 @@ const ReviewWizardFooter = () => { const getBlueprintPayload = async () => { const userData = await auth?.getUser(); const orgId = userData?.identity?.internal?.org_id; - const requestBody = orgId && mapRequestFromState(store, orgId, serverStore); + const requestBody = orgId && mapRequestFromState(store, orgId); return requestBody; }; diff --git a/src/Components/CreateImageWizard/utilities/requestMapper.ts b/src/Components/CreateImageWizard/utilities/requestMapper.ts index 9c169a17..deac97d0 100644 --- a/src/Components/CreateImageWizard/utilities/requestMapper.ts +++ b/src/Components/CreateImageWizard/utilities/requestMapper.ts @@ -51,12 +51,14 @@ import { selectGcpShareMethod, selectGroups, selectImageTypes, + selectKernel, selectPackages, selectPayloadRepositories, selectRecommendedRepositories, selectProfile, selectRegistrationType, selectServerUrl, + selectServices, wizardState, selectFileSystemConfigurationType, selectPartitions, @@ -85,11 +87,6 @@ import { AwsShareMethod } from '../steps/TargetEnvironment/Aws'; import { AzureShareMethod } from '../steps/TargetEnvironment/Azure'; import { GcpAccountType, GcpShareMethod } from '../steps/TargetEnvironment/Gcp'; -type ServerStore = { - kernel?: { append?: string }; // TODO use API types - services?: { enabled?: string[]; disabled?: string[]; masked?: string[] }; // TODO use API types -}; - /** * This function maps the wizard state to a valid CreateBlueprint request object * @param {Store} store redux store @@ -99,12 +96,11 @@ type ServerStore = { */ export const mapRequestFromState = ( store: Store, - orgID: string, - serverStore: ServerStore + orgID: string ): CreateBlueprintRequest => { const state = store.getState(); const imageRequests = getImageRequests(state); - const customizations = getCustomizations(state, orgID, serverStore); + const customizations = getCustomizations(state, orgID); return { name: selectBlueprintName(state), @@ -258,6 +254,14 @@ function commonRequestToState( repository: '' as PackageRepository, package_list: [], })) || [], + services: { + enabled: request.customizations?.services?.enabled || [], + masked: request.customizations?.services?.masked || [], + disabled: request.customizations?.services?.disabled || [], + }, + kernel: { + append: request.customizations?.kernel?.append || '', + }, }; } @@ -418,11 +422,7 @@ const getImageOptions = ( return {}; }; -const getCustomizations = ( - state: RootState, - orgID: string, - serverStore: ServerStore -): Customizations => { +const getCustomizations = (state: RootState, orgID: string): Customizations => { return { containers: undefined, directories: undefined, @@ -450,10 +450,10 @@ const getCustomizations = ( openscap: getOpenscapProfile(state), filesystem: getFileSystem(state), users: undefined, - services: getServices(serverStore, state), + services: getServices(state), hostname: undefined, - kernel: serverStore.kernel?.append - ? { append: serverStore.kernel?.append } + kernel: selectKernel(state).append + ? { append: selectKernel(state).append } : undefined, groups: undefined, timezone: undefined, @@ -467,36 +467,29 @@ const getCustomizations = ( }; }; -const getServices = ( - serverStore: ServerStore, - state: RootState -): Services | undefined => { - const serverEnabledServices: string[] | undefined = - serverStore.services?.enabled; - const serverDisabledServicesFromServer: string[] | undefined = - serverStore.services?.disabled; - const serverMaskedServices = serverStore.services?.masked; - const firstbootFlag: boolean = - !!selectFirstBootScript(state) && - !serverEnabledServices?.includes(FIRST_BOOT_SERVICE); - - const enabledServices = [ - ...(serverEnabledServices ? serverEnabledServices : []), - ...(firstbootFlag ? [FIRST_BOOT_SERVICE] : []), - ]; - +const getServices = (state: RootState): Services | undefined => { + const services = selectServices(state); if ( - enabledServices.length || - serverDisabledServicesFromServer || - serverMaskedServices + services.enabled.length === 0 && + services.masked.length === 0 && + services.disabled.length === 0 ) { - return { - enabled: enabledServices, - disabled: serverDisabledServicesFromServer, - masked: serverMaskedServices, - }; + return undefined; } - return undefined; + + const enabledSvcs = services.enabled || []; + const includeFBSvc: boolean = + !!selectFirstBootScript(state) && + !services.enabled?.includes(FIRST_BOOT_SERVICE); + if (includeFBSvc) { + enabledSvcs.push(FIRST_BOOT_SERVICE); + } + + return { + enabled: enabledSvcs.length ? enabledSvcs : undefined, + masked: services.masked.length ? services.masked : undefined, + disabled: services.disabled.length ? services.disabled : undefined, + }; }; const getOpenscapProfile = (state: RootState): OpenScap | undefined => { diff --git a/src/store/hooks.ts b/src/store/hooks.ts index 30dcc88d..2117db80 100644 --- a/src/store/hooks.ts +++ b/src/store/hooks.ts @@ -1,41 +1,7 @@ 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, - masked: data?.services?.masked, - }, - packages: data?.packages, - filesystem: data?.filesystem, - profileName: data?.openscap?.profile_name, - }; -}; - -export const useServerStore = () => { - const oscap = useOscapData(); - return { ...oscap }; -}; diff --git a/src/store/wizardSlice.ts b/src/store/wizardSlice.ts index 8db8321e..4110698e 100644 --- a/src/store/wizardSlice.ts +++ b/src/store/wizardSlice.ts @@ -93,6 +93,14 @@ export type wizardState = { }; packages: IBPackageWithRepositoryInfo[]; groups: GroupWithRepositoryInfo[]; + services: { + enabled: string[]; + masked: string[]; + disabled: string[]; + }; + kernel: { + append: string; + }; details: { blueprintName: string; blueprintDescription: string; @@ -151,6 +159,14 @@ export const initialState: wizardState = { }, packages: [], groups: [], + services: { + enabled: [], + masked: [], + disabled: [], + }, + kernel: { + append: '', + }, details: { blueprintName: '', blueprintDescription: '', @@ -277,6 +293,14 @@ export const selectGroups = (state: RootState) => { return state.wizard.groups; }; +export const selectServices = (state: RootState) => { + return state.wizard.services; +}; + +export const selectKernel = (state: RootState) => { + return state.wizard.kernel; +}; + export const selectBlueprintName = (state: RootState) => { return state.wizard.details.blueprintName; }; @@ -604,6 +628,18 @@ export const wizardSlice = createSlice({ setFirstBootScript: (state, action: PayloadAction) => { state.firstBoot.script = action.payload; }, + changeEnabledServices: (state, action: PayloadAction) => { + state.services.enabled = action.payload; + }, + changeMaskedServices: (state, action: PayloadAction) => { + state.services.masked = action.payload; + }, + changeDisabledServices: (state, action: PayloadAction) => { + state.services.disabled = action.payload; + }, + changeKernelAppend: (state, action: PayloadAction) => { + state.kernel.append = action.payload; + }, }, }); @@ -657,5 +693,9 @@ export const { changeBlueprintDescription, loadWizardState, setFirstBootScript, + changeEnabledServices, + changeMaskedServices, + changeDisabledServices, + changeKernelAppend, } = wizardSlice.actions; export default wizardSlice.reducer;