Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wizard: Add Kernel name input (HMS-5204) #2690

Merged
merged 6 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/Components/CreateImageWizard/CreateImageWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
useDetailsValidation,
useRegistrationValidation,
useHostnameValidation,
useKernelValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
Expand Down Expand Up @@ -223,6 +224,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const fileSystemValidation = useFilesystemValidation();
// Hostname
const hostnameValidation = useHostnameValidation();
// Kernel
const kernelValidation = useKernelValidation();
// Firstboot
const firstBootValidation = useFirstBootValidation();
// Details
Expand Down Expand Up @@ -510,8 +513,12 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
key="wizard-kernel"
navItem={customStatusNavItem}
isHidden={!isKernelEnabled}
status={kernelValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
<CustomWizardFooter
disableNext={kernelValidation.disabledNext}
optional={true}
/>
}
>
<KernelStep />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,186 @@
import React from 'react';
import React, { useEffect, useState } from 'react';

import { FormGroup } from '@patternfly/react-core';
import {
Alert,
Button,
HelperText,
HelperTextItem,
MenuToggle,
MenuToggleElement,
Select,
SelectList,
SelectOption,
TextInputGroup,
TextInputGroupMain,
TextInputGroupUtilities,
} from '@patternfly/react-core/dist/esm';
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';

import { useAppDispatch, useAppSelector } from '../../../../../store/hooks';
import {
changeKernelName,
selectKernel,
} from '../../../../../store/wizardSlice';
import { useKernelValidation } from '../../../utilities/useValidation';

const initialOptions = ['kernel', 'kernel-debug'];
let kernelOptions = initialOptions;

const KernelName = () => {
return <FormGroup isRequired={false} label="Name"></FormGroup>;
const dispatch = useAppDispatch();
const kernel = useAppSelector(selectKernel).name;

const stepValidation = useKernelValidation();

const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState<string>('');
const [filterValue, setFilterValue] = useState<string>('');
const [selectOptions, setSelectOptions] = useState<string[]>(kernelOptions);

useEffect(() => {
let filteredKernelPkgs = kernelOptions;

if (filterValue) {
filteredKernelPkgs = kernelOptions.filter((kernel: string) =>
String(kernel).toLowerCase().includes(filterValue.toLowerCase())
);
if (!filteredKernelPkgs.some((kernel) => kernel === filterValue)) {
filteredKernelPkgs = [
...filteredKernelPkgs,
`Custom kernel package "${filterValue}"`,
];
}
if (!isOpen) {
setIsOpen(true);
}
}
setSelectOptions(filteredKernelPkgs);

// This useEffect hook should run *only* on when the filter value changes.
// eslint's exhaustive-deps rule does not support this use.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filterValue]);

const onToggle = (isOpen: boolean) => {
setIsOpen(!isOpen);
};

const onInputClick = () => {
if (!isOpen) {
setIsOpen(true);
} else if (!inputValue) {
setIsOpen(false);
}
};

const onSelect = (_event: React.MouseEvent, value: string) => {
if (value) {
if (/custom kernel package/i.test(value)) {
if (!kernelOptions.some((kernel) => kernel === filterValue)) {
kernelOptions = [...kernelOptions, filterValue];
}
dispatch(changeKernelName(filterValue));
setFilterValue('');
setIsOpen(false);
} else {
setInputValue(value);
setFilterValue('');
dispatch(changeKernelName(value));
setIsOpen(false);
}
}
};

const onTextInputChange = (_event: React.FormEvent, value: string) => {
setInputValue(value);
setFilterValue(value);

if (value !== kernel) {
dispatch(changeKernelName(''));
}
};

const onToggleClick = () => {
setIsOpen(!isOpen);
};

const onClearButtonClick = () => {
setInputValue('');
setFilterValue('');
dispatch(changeKernelName(''));
};

const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
variant="typeahead"
onClick={onToggleClick}
isExpanded={isOpen}
isFullWidth
>
<TextInputGroup isPlain>
<TextInputGroupMain
value={kernel ? kernel : inputValue}
onClick={onInputClick}
onChange={onTextInputChange}
autoComplete="off"
placeholder="Select kernel package"
isExpanded={isOpen}
/>

{kernel && (
<TextInputGroupUtilities>
<Button
variant="plain"
onClick={onClearButtonClick}
aria-label="Clear input"
>
<TimesIcon />
</Button>
</TextInputGroupUtilities>
)}
</TextInputGroup>
</MenuToggle>
);

return (
<>
{kernel && !initialOptions.includes(kernel) && (
<Alert
title="Custom kernel packages cannot be validated and can cause build issues."
isInline
variant="warning"
/>
)}
<FormGroup isRequired={false} label="Name">
<Select
isScrollable
isOpen={isOpen}
selected={kernel}
onSelect={onSelect}
onOpenChange={onToggle}
toggle={toggle}
shouldFocusFirstItemOnOpen={false}
>
<SelectList>
{selectOptions.map((option) => (
<SelectOption key={option} value={option}>
{option}
</SelectOption>
))}
</SelectList>
</Select>
{stepValidation.errors.kernel && (
<HelperText>
<HelperTextItem variant={'error'}>
{stepValidation.errors.kernel}
</HelperTextItem>
</HelperText>
)}
</FormGroup>
</>
);
};

export default KernelName;
18 changes: 15 additions & 3 deletions src/Components/CreateImageWizard/utilities/requestMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ function commonRequestToState(
disabled: request.customizations?.services?.disabled || [],
},
kernel: {
name: request.customizations.kernel?.name || '',
append: request.customizations?.kernel?.append || '',
},
timezone: {
Expand Down Expand Up @@ -539,9 +540,7 @@ const getCustomizations = (state: RootState, orgID: string): Customizations => {
users: getUsers(state),
services: getServices(state),
hostname: selectHostname(state) || undefined,
kernel: selectKernel(state).append
? { append: selectKernel(state).append }
: undefined,
kernel: getKernel(state),
groups: undefined,
timezone: getTimezone(state),
locale: getLocale(state),
Expand Down Expand Up @@ -758,3 +757,16 @@ const getPayloadRepositories = (state: RootState) => {
}
return payloadAndRecommendedRepositories;
};

const getKernel = (state: RootState) => {
const kernel = selectKernel(state);

if (!kernel.name && !kernel.append) {
return undefined;
}

return {
name: selectKernel(state).name || undefined,
append: selectKernel(state).append || undefined,
};
};
18 changes: 18 additions & 0 deletions src/Components/CreateImageWizard/utilities/useValidation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
selectActivationKey,
selectRegistrationType,
selectHostname,
selectKernel,
} from '../../../store/wizardSlice';
import {
getDuplicateMountPoints,
Expand All @@ -27,6 +28,7 @@ import {
isMountpointMinSizeValid,
isSnapshotValid,
isHostnameValid,
isKernelNameValid,
} from '../validators';

export type StepValidation = {
Expand All @@ -41,13 +43,15 @@ export function useIsBlueprintValid(): boolean {
const filesystem = useFilesystemValidation();
const snapshot = useSnapshotValidation();
const hostname = useHostnameValidation();
const kernel = useKernelValidation();
const firstBoot = useFirstBootValidation();
const details = useDetailsValidation();
return (
!registration.disabledNext &&
!filesystem.disabledNext &&
!snapshot.disabledNext &&
!hostname.disabledNext &&
!kernel.disabledNext &&
!firstBoot.disabledNext &&
!details.disabledNext
);
Expand Down Expand Up @@ -155,6 +159,20 @@ export function useHostnameValidation(): StepValidation {
return { errors: {}, disabledNext: false };
}

export function useKernelValidation(): StepValidation {
const kernel = useAppSelector(selectKernel);

if (!isKernelNameValid(kernel.name)) {
return {
errors: {
kernel: 'Invalid format.',
},
disabledNext: true,
};
}
return { errors: {}, disabledNext: false };
}

export function useDetailsValidation(): StepValidation {
const name = useAppSelector(selectBlueprintName);
const description = useAppSelector(selectBlueprintDescription);
Expand Down
11 changes: 11 additions & 0 deletions src/Components/CreateImageWizard/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,17 @@ export const isHostnameValid = (hostname: string) => {
);
};

export const isKernelNameValid = (kernelName: string) => {
if (!kernelName) {
return true;
}

return (
kernelName.length < 65 &&
/^[a-z0-9]|[a-z0-9][a-z0-9-_.+]*[a-z0-9]$/.test(kernelName)
);
};

export const isPortValid = (port: string) => {
return /^(\d{1,5}|[a-z]{1,6})(-\d{1,5})?:[a-z]{1,6}$/.test(port);
};
6 changes: 6 additions & 0 deletions src/store/wizardSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ export type wizardState = {
disabled: string[];
};
kernel: {
name: string;
append: string;
};
locale: Locale;
Expand Down Expand Up @@ -204,6 +205,7 @@ export const initialState: wizardState = {
disabled: [],
},
kernel: {
name: '',
append: '',
},
locale: {
Expand Down Expand Up @@ -800,6 +802,9 @@ export const wizardSlice = createSlice({
changeDisabledServices: (state, action: PayloadAction<string[]>) => {
state.services.disabled = action.payload;
},
changeKernelName: (state, action: PayloadAction<string>) => {
state.kernel.name = action.payload;
},
changeKernelAppend: (state, action: PayloadAction<string>) => {
state.kernel.append = action.payload;
},
Expand Down Expand Up @@ -922,6 +927,7 @@ export const {
changeEnabledServices,
changeMaskedServices,
changeDisabledServices,
changeKernelName,
changeKernelAppend,
changeTimezone,
addNtpServer,
Expand Down
Loading
Loading