Skip to content
Closed
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e65871b
initial commit
hasyed-akamai Jan 29, 2025
5f3f86f
populating IpV4 and ipV6
hasyed-akamai Jan 30, 2025
9b81d82
ACL Enable and IPv4 and IPv6 are added correctly
hasyed-akamai Feb 3, 2025
7388c0c
fix unit test and add IPv4 and IPv6 input for default case
hasyed-akamai Feb 3, 2025
c5b792d
cleanup
hasyed-akamai Feb 3, 2025
d96d617
Fix Cypress Test `Part-1`
hasyed-akamai Feb 4, 2025
e863e1e
Fix e2e Test `Part-2`
hasyed-akamai Feb 5, 2025
83be6c2
Remove Loading Bug
hasyed-akamai Feb 5, 2025
dfa1223
Fix k8_version initial value
hasyed-akamai Feb 6, 2025
f6e7795
Add Validation to the IPv4 and IPv6
hasyed-akamai Feb 6, 2025
f902c94
Set Validation errors and k8_version
hasyed-akamai Feb 6, 2025
5cdebc0
Add Error Scrollable Behaviour
hasyed-akamai Feb 6, 2025
e841372
Added changeset: Optimize CreateCluster component: Use React Hook Form
hasyed-akamai Feb 6, 2025
8768c89
Merge branch 'develop' into M3-8777-refactor-CreateCluster-component-…
hasyed-akamai Feb 6, 2025
795d6ea
removed unused props and added updateFor prop
hasyed-akamai Feb 12, 2025
c1f15bb
Fixed issue of Multiple call of `ControlPlaneACLPane` component
hasyed-akamai Feb 12, 2025
7fb587b
disabled remove button for the first input
hasyed-akamai Feb 13, 2025
31a5987
Merge branch 'develop' into M3-8777-refactor-CreateCluster-component-…
hasyed-akamai Feb 19, 2025
608bea6
Replaced useForm with useFormContext and add initialvalues for ipv4 a…
hasyed-akamai Feb 21, 2025
b2f5908
Merge branch 'develop' into M3-8777-refactor-CreateCluster-component-…
hasyed-akamai Feb 21, 2025
935379d
Remove useState and useEffect
hasyed-akamai Feb 24, 2025
4ae1357
Merge branch 'develop' into M3-8777-refactor-CreateCluster-component-…
hasyed-akamai Feb 24, 2025
a76ff57
fix unit test
hasyed-akamai Feb 24, 2025
d22b54f
Merge branch 'develop' into M3-8777-refactor-CreateCluster-component-…
hasyed-akamai Feb 25, 2025
09fd694
remove isSubmitting useState
hasyed-akamai Feb 26, 2025
932bcf1
refactor `ControlPlaneACLPane`
hasyed-akamai Feb 28, 2025
1baaaa5
refactor `ControlPlaneACLPane` Part-2
hasyed-akamai Mar 4, 2025
a0676b5
Merge branch 'develop' into M3-8777-refactor-CreateCluster-component-…
hasyed-akamai Mar 4, 2025
1e35fba
fix unit test
hasyed-akamai Mar 4, 2025
b6f7398
cleanup
hasyed-akamai Mar 4, 2025
847325a
Added default values in `ControlPlaneACLPane` test file
hasyed-akamai Mar 5, 2025
c9c9457
Merge branch 'develop' into M3-8777-refactor-CreateCluster-component-…
hasyed-akamai Mar 7, 2025
f599847
populate k8_versions for enterprise tier
hasyed-akamai Mar 7, 2025
84676ad
cleanup
hasyed-akamai Mar 10, 2025
955ed0a
Merge branch 'develop' into M3-8777-refactor-CreateCluster-component-…
Apr 9, 2025
2916d98
Revert "Merge branch 'develop' into M3-8777-refactor-CreateCluster-co…
Apr 9, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tech Stories
---

Optimize CreateCluster component: Use React Hook Form ([#11581](https://github.com/linode/manager/pull/11581))
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,9 @@ describe('LKE Cluster Creation with ACL', () => {
.should('be.visible')
.should('be.enabled')
.click();
cy.get('[id="domain-transfer-ip-1"]').should('be.visible').click();
cy.get('[id="ipv4-addresses-or-cidrs-ip-address-1"]')
.should('be.visible')
.click();
cy.focused().type('10.0.1.0/24');
cy.findByLabelText('IPv6 Addresses or CIDRs ip-address-0')
.should('be.visible')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { Theme } from '@mui/material/styles';
interface Props {
handleClusterTierSelection: (tier: KubernetesTier) => void;
isUserRestricted: boolean;
selectedTier: KubernetesTier;
selectedTier: KubernetesTier | undefined;
Comment thread
hasyed-akamai marked this conversation as resolved.
Outdated
}

export const ClusterTierPanel = (props: Props) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import userEvent from '@testing-library/user-event';
import * as React from 'react';

import { renderWithTheme } from 'src/utilities/testHelpers';
import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers';

import { ControlPlaneACLPane } from './ControlPlaneACLPane';

Expand All @@ -10,16 +10,26 @@ import type { ControlPlaneACLProps } from './ControlPlaneACLPane';
const props: ControlPlaneACLProps = {
enableControlPlaneACL: true,
errorText: undefined,
handleIPv4Change: vi.fn(),
handleIPv6Change: vi.fn(),
ipV4Addr: [{ address: '' }],
ipV6Addr: [{ address: '' }],
setControlPlaneACL: vi.fn(),
};

describe('ControlPlaneACLPane', () => {
it('renders all fields when enableControlPlaneACL is true', () => {
const { getByText } = renderWithTheme(<ControlPlaneACLPane {...props} />);
const { getByText } = renderWithThemeAndHookFormContext({
component: <ControlPlaneACLPane {...props} />,
useFormOptions: {
defaultValues: {
control_plane: {
acl: {
addresses: {
ipv4: [''],
ipv6: [''],
},
},
},
},
},
});

expect(getByText('Control Plane ACL')).toBeVisible();
expect(
Expand All @@ -35,9 +45,11 @@ describe('ControlPlaneACLPane', () => {
});

it('hides IP fields when enableControlPlaneACL is false', () => {
const { getByText, queryByText } = renderWithTheme(
<ControlPlaneACLPane {...props} enableControlPlaneACL={false} />
);
const { getByText, queryByText } = renderWithThemeAndHookFormContext({
component: (
<ControlPlaneACLPane {...props} enableControlPlaneACL={false} />
),
});

expect(getByText('Control Plane ACL')).toBeVisible();
expect(
Expand All @@ -53,7 +65,9 @@ describe('ControlPlaneACLPane', () => {
});

it('calls setControlPlaneACL when clicking the toggle', async () => {
const { getByText } = renderWithTheme(<ControlPlaneACLPane {...props} />);
const { getByText } = renderWithThemeAndHookFormContext({
component: <ControlPlaneACLPane {...props} />,
});

const toggle = getByText('Enable Control Plane ACL');
await userEvent.click(toggle);
Expand All @@ -62,18 +76,26 @@ describe('ControlPlaneACLPane', () => {
});

it('handles IP changes', async () => {
const { getByLabelText } = renderWithTheme(
<ControlPlaneACLPane {...props} />
);

const ipv4 = getByLabelText('IPv4 Addresses or CIDRs ip-address-0');
const { getByTestId } = renderWithThemeAndHookFormContext({
component: <ControlPlaneACLPane {...props} />,
useFormOptions: {
defaultValues: {
control_plane: {
acl: {
addresses: {
ipv4: [''],
ipv6: [''],
},
},
},
},
},
});

const ipv4 = getByTestId('ipv4-addresses-or-cidrs-ip-address-0');
await userEvent.type(ipv4, 'test');

expect(props.handleIPv4Change).toHaveBeenCalled();

const ipv6 = getByLabelText('IPv6 Addresses or CIDRs ip-address-0');
const ipv6 = getByTestId('ipv6-addresses-or-cidrs-ip-address-0');
await userEvent.type(ipv6, 'test');

expect(props.handleIPv6Change).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,71 @@ import {
Box,
FormControl,
FormControlLabel,
IconButton,
Notice,
Stack,
TextField,
Toggle,
Typography,
} from '@linode/ui';
import CloseIcon from '@mui/icons-material/Close';
import { FormLabel } from '@mui/material';
import * as React from 'react';
import { Controller, useFormContext } from 'react-hook-form';

import { ErrorMessage } from 'src/components/ErrorMessage';
import { MultipleIPInput } from 'src/components/MultipleIPInput/MultipleIPInput';
import { validateIPs } from 'src/utilities/ipUtils';

import type { ExtendedIP } from 'src/utilities/ipUtils';
import { LinkButton } from 'src/components/LinkButton';
import {
extendedIPToString,
stringToExtendedIP,
validateIPs,
} from 'src/utilities/ipUtils';

export interface ControlPlaneACLProps {
import type { CreateKubeClusterPayload } from '@linode/api-v4';
import type { RenderGuardProps } from 'src/components/RenderGuard';
export interface ControlPlaneACLProps extends RenderGuardProps {
Comment thread
hasyed-akamai marked this conversation as resolved.
enableControlPlaneACL: boolean;
errorText: string | undefined;
handleIPv4Change: (ips: ExtendedIP[]) => void;
handleIPv6Change: (ips: ExtendedIP[]) => void;
ipV4Addr: ExtendedIP[];
ipV6Addr: ExtendedIP[];
initialIpv4Addresses?: string[];
initialIpv6Addresses?: string[];
setControlPlaneACL: (enabled: boolean) => void;
}

export const ControlPlaneACLPane = (props: ControlPlaneACLProps) => {
const { enableControlPlaneACL, errorText, setControlPlaneACL } = props;

const {
enableControlPlaneACL,
errorText,
handleIPv4Change,
handleIPv6Change,
ipV4Addr,
ipV6Addr,
setControlPlaneACL,
} = props;
clearErrors,
control,
getValues,
setError,
setValue,
watch,
} = useFormContext<CreateKubeClusterPayload>();

const ipv4Addresses = watch('control_plane.acl.addresses.ipv4');
const ipv6Addresses = watch('control_plane.acl.addresses.ipv6');

const handleBlur = (value: string, index: number, type: 'ipv4' | 'ipv6') => {
const _ips = value ? stringToExtendedIP(value) : stringToExtendedIP('');
const validatedIPs = validateIPs([_ips], {
allowEmptyAddress: true,
errorMessage:
type === 'ipv4'
? 'Must be a valid IPv4 address.'
: 'Must be a valid IPv6 address.',
});
if (validatedIPs[0].error) {
setError(`control_plane.acl.addresses.${type}.${index}`, {
message: validatedIPs[0].error,
type: 'manual',
});
} else {
clearErrors(`control_plane.acl.addresses.${type}.${index}`);
}
const newIP = extendedIPToString(_ips);
setValue(`control_plane.acl.addresses.${type}.${index}`, newIP);
};

return (
<>
Expand Down Expand Up @@ -63,37 +95,133 @@ export const ControlPlaneACLPane = (props: ControlPlaneACLProps) => {
label="Enable Control Plane ACL"
/>
</FormControl>

{enableControlPlaneACL && (
<Box sx={{ marginBottom: 3, maxWidth: 450 }}>
<MultipleIPInput
onBlur={(_ips: ExtendedIP[]) => {
const validatedIPs = validateIPs(_ips, {
allowEmptyAddress: true,
errorMessage: 'Must be a valid IPv4 address.',
});
handleIPv4Change(validatedIPs);
}}
buttonText="Add IPv4 Address"
ips={ipV4Addr}
isLinkStyled
onChange={handleIPv4Change}
title="IPv4 Addresses or CIDRs"
/>
{/* IPv4 Addresses */}
Comment thread
hasyed-akamai marked this conversation as resolved.
Outdated
<Box marginTop={2}>
<MultipleIPInput
onBlur={(_ips: ExtendedIP[]) => {
const validatedIPs = validateIPs(_ips, {
allowEmptyAddress: true,
errorMessage: 'Must be a valid IPv6 address.',
});
handleIPv6Change(validatedIPs);
<Typography mb={1} variant="inherit">
IPv4 Addresses or CIDRs
</Typography>
{(getValues('control_plane.acl.addresses.ipv4') || []).map(
(field: string, index: number) => (
<Stack
alignItems="flex-start"
direction="row"
key={`ipv4-${index}`}
spacing={0.5}
sx={{ marginBottom: 1 }}
>
<Controller
render={({ field: controllerField, fieldState }) => (
<TextField
{...controllerField}
onBlur={() =>
handleBlur(controllerField.value, index, 'ipv4')
}
data-testid={`ipv4-addresses-or-cidrs-ip-address-${index}`}
error={!!fieldState.error}
errorText={fieldState.error?.message}
hideLabel
label={`IPv4 Addresses or CIDRs ip-address-${index}`}
ref={null}
Comment thread
mjac0bs marked this conversation as resolved.
sx={{ minWidth: 350 }}
value={controllerField.value}
/>
)}
control={control}
name={`control_plane.acl.addresses.ipv4.${index}`}
/>
{index > 0 && (
<IconButton
onClick={() => {
const currentIPv4Addresses =
ipv4Addresses?.filter((_, i) => i !== index) || [];
setValue(
'control_plane.acl.addresses.ipv4',
currentIPv4Addresses
);
}}
aria-label={`Remove IPv4 Address ${index}`}
sx={{ padding: 0.75 }}
>
<CloseIcon />
</IconButton>
)}
</Stack>
)
)}
<LinkButton
onClick={() => {
const newIpv4Addresses = [...(ipv4Addresses || []), ''];
setValue('control_plane.acl.addresses.ipv4', newIpv4Addresses);
}}
buttonText="Add IPv6 Address"
ips={ipV6Addr}
isLinkStyled
onChange={handleIPv6Change}
title="IPv6 Addresses or CIDRs"
/>
>
Add IPv4 Address
</LinkButton>
</Box>

{/* IPv6 Addresses */}
<Box marginTop={2}>
<Typography mb={1} variant="inherit">
IPv6 Addresses or CIDRs
</Typography>
{(getValues('control_plane.acl.addresses.ipv6') || []).map(
(field: string, index: number) => (
<Stack
alignItems="flex-start"
direction="row"
key={`ipv6-${index}`}
spacing={0.5}
sx={{ marginBottom: 1 }}
>
<Controller
render={({ field: controllerField, fieldState }) => (
<TextField
{...controllerField}
onBlur={() =>
handleBlur(controllerField.value, index, 'ipv6')
}
data-testid={`ipv6-addresses-or-cidrs-ip-address-${index}`}
error={!!fieldState.error}
errorText={fieldState.error?.message}
hideLabel
label={`IPv6 Addresses or CIDRs ip-address-${index}`}
ref={null}
Comment thread
mjac0bs marked this conversation as resolved.
sx={{ minWidth: 350 }}
value={controllerField.value}
/>
)}
control={control}
name={`control_plane.acl.addresses.ipv6.${index}`}
/>
{index > 0 && (
<IconButton
onClick={() => {
const currentIPv6Addresses =
ipv6Addresses?.filter((_, i) => i !== index) || [];
setValue(
'control_plane.acl.addresses.ipv6',
currentIPv6Addresses
);
}}
aria-label={`Remove IPv6 Address ${index}`}
sx={{ padding: 0.75 }}
>
<CloseIcon />
</IconButton>
)}
</Stack>
)
)}
<LinkButton
onClick={() => {
const newIpv6Addresses = [...(ipv6Addresses || []), ''];
setValue('control_plane.acl.addresses.ipv6', newIpv6Addresses);
}}
>
Add IPv6 Address
</LinkButton>
</Box>
</Box>
)}
Expand Down
Loading