Skip to content

Commit

Permalink
feat: [M3-6149] - Add Cloud Init checkbox to Image Capture/Create flow (
Browse files Browse the repository at this point in the history
#8807)

* Add checkbox to Capture Image form

* Update request to support cloud_init

* Fix controlled component error where checkbox is undefined

* Update image validation and types interface for cloud_init

* (Debugging  an undefined value in createImage request)

* Add cloud_init to redux create fn params to include in request

* Move checkbox state to ImageCreate so value persists across tabs; code cleanup

* Code clean up

* Fix comment referencing cloud-init for naming consistency

* Update CheckBox tooltip: allow interactivity, minor styling fix

* Add final tooltip copy for now
  • Loading branch information
mjac0bs authored Feb 28, 2023
1 parent 1d1f832 commit a5e1b25
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 8 deletions.
5 changes: 4 additions & 1 deletion packages/api-v4/src/images/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,19 @@ export const getImages = (params: any = {}, filters: any = {}) =>
* @param diskId { number } The ID of the Linode Disk that this Image will be created from.
* @param label { string } A short description of the Image. Labels cannot contain special characters.
* @param description { string } A detailed description of this Image.
* @param cloud_init { boolean } An indicator of whether Image supports cloud-init.
*/
export const createImage = (
diskId: number,
label?: string,
description?: string
description?: string,
cloud_init?: boolean
) => {
const data = {
disk_id: diskId,
...(label && { label }),
...(description && { description }),
...(cloud_init && { cloud_init }),
};

return Request<Image>(
Expand Down
1 change: 1 addition & 0 deletions packages/api-v4/src/images/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export interface UploadImageResponse {
export interface BaseImagePayload {
label?: string;
description?: string;
cloud_init?: boolean;
}

export interface CreateImagePayload extends BaseImagePayload {
Expand Down
17 changes: 12 additions & 5 deletions packages/manager/src/components/CheckBox/CheckBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,19 @@ const useStyles = makeStyles((theme: Theme) => ({
fill: `${theme.bg.main}`,
},
},
formControlStyles: {
marginRight: 0,
},
}));

interface Props extends CheckboxProps {
text?: string | JSX.Element;
toolTipText?: string;
toolTipText?: string | JSX.Element;
toolTipInteractive?: boolean;
}

const LinodeCheckBox: React.FC<Props> = (props) => {
const { toolTipText, text, ...rest } = props;
const LinodeCheckBox = (props: Props) => {
const { toolTipInteractive, toolTipText, text, ...rest } = props;
const classes = useStyles();

const classnames = classNames({
Expand All @@ -51,6 +55,7 @@ const LinodeCheckBox: React.FC<Props> = (props) => {
return (
<React.Fragment>
<FormControlLabel
className={classes.formControlStyles}
control={
<Checkbox
color="primary"
Expand All @@ -61,9 +66,11 @@ const LinodeCheckBox: React.FC<Props> = (props) => {
{...rest}
/>
}
label={props.text}
label={text}
/>
{toolTipText && <HelpIcon text={toolTipText} />}
{toolTipText ? (
<HelpIcon interactive={toolTipInteractive} text={toolTipText} />
) : null}
</React.Fragment>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useGrants, useProfile } from 'src/queries/profile';
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
import getAPIErrorFor from 'src/utilities/getAPIErrorFor';
import Link from 'src/components/Link';
import CheckBox from 'src/components/CheckBox';

const useStyles = makeStyles((theme: Theme) => ({
container: {
Expand Down Expand Up @@ -57,21 +58,37 @@ const useStyles = makeStyles((theme: Theme) => ({
width: '100%',
},
},
cloudInitCheckboxWrapper: {
marginTop: theme.spacing(2),
marginLeft: 3,
},
}));

const cloudInitTooltipMessage = (
<Typography>
Many Linode supported distributions are compatible with cloud-init by
default, or you may have installed cloud-init.{' '}
<Link to="/">Learn more.</Link>
</Typography>
);

export interface Props {
label?: string;
description?: string;
isCloudInit?: boolean;
changeLabel: (e: React.ChangeEvent<HTMLInputElement>) => void;
changeDescription: (e: React.ChangeEvent<HTMLInputElement>) => void;
changeIsCloudInit: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

export const CreateImageTab: React.FC<Props & ImagesDispatch> = (props) => {
const {
label,
description,
isCloudInit,
changeLabel,
changeDescription,
changeIsCloudInit,
createImage,
} = props;

Expand Down Expand Up @@ -150,6 +167,7 @@ export const CreateImageTab: React.FC<Props & ImagesDispatch> = (props) => {
diskID: Number(selectedDisk),
label,
description: safeDescription,
cloud_init: isCloudInit ? isCloudInit : undefined,
})
.then((_) => {
resetEventsPolling();
Expand Down Expand Up @@ -265,6 +283,15 @@ export const CreateImageTab: React.FC<Props & ImagesDispatch> = (props) => {
/>
</Box>
{isRawDisk ? rawDiskWarning : null}
<Box className={classes.cloudInitCheckboxWrapper}>
<CheckBox
checked={isCloudInit}
onChange={changeIsCloudInit}
text="This image is Cloud-init compatible"
toolTipText={cloudInitTooltipMessage}
toolTipInteractive
/>
</Box>
<>
<TextField
label="Label"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const ImageCreate: React.FC<CombinedProps> = (props) => {
const [description, setDescription] = React.useState<string>(
location?.state ? location.state.imageDescription : ''
);
const [isCloudInit, setIsCloudInit] = React.useState<boolean>(false);

const handleSetLabel = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
Expand All @@ -37,8 +38,10 @@ export const ImageCreate: React.FC<CombinedProps> = (props) => {
<CreateImageTab
label={label}
description={description}
isCloudInit={isCloudInit}
changeLabel={handleSetLabel}
changeDescription={handleSetDescription}
changeIsCloudInit={() => setIsCloudInit(!isCloudInit)}
/>
),
},
Expand Down
3 changes: 2 additions & 1 deletion packages/manager/src/store/image/image.requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export const requestImages = createRequestThunk(requestImagesActions, () =>

export const createImage = createRequestThunk(
createImageActions,
({ diskID, label, description }) => _create(diskID, label, description)
({ diskID, label, description, cloud_init }) =>
_create(diskID, label, description, cloud_init)
);

export const uploadImage = createRequestThunk(uploadImageActions, (payload) =>
Expand Down
3 changes: 2 additions & 1 deletion packages/validation/src/images.schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { number, object, string } from 'yup';
import { boolean, number, object, string } from 'yup';

const labelSchema = string()
.max(50, 'Length must be 50 characters or less.')
Expand All @@ -10,6 +10,7 @@ const labelSchema = string()
export const baseImageSchema = object().shape({
label: labelSchema.notRequired(),
description: string().notRequired().min(1).max(65000),
cloud_init: boolean().notRequired(),
});

export const createImageSchema = baseImageSchema.shape({
Expand Down

0 comments on commit a5e1b25

Please sign in to comment.