Skip to content

Commit ff14235

Browse files
upcoming: [M3-9981] – Add support for VPC IPv6 in the Assign/Unassign Linodes drawers (linode#12778)
1 parent c8e8337 commit ff14235

File tree

20 files changed

+702
-128
lines changed

20 files changed

+702
-128
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/api-v4": Upcoming Features
3+
---
4+
5+
Make `address` an optional property on the IPv6SLAAC object ([#12778](https://github.com/linode/manager/pull/12778))

packages/api-v4/src/linodes/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export interface ConfigInterfaceIPv4 {
191191
}
192192

193193
export interface IPv6SLAAC {
194-
address: string;
194+
address?: string;
195195
range: string;
196196
}
197197

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Upcoming Features
3+
---
4+
5+
Add VPC IPv6 support in Assign Linodes and Unassign Linodes drawers ([#12778](https://github.com/linode/manager/pull/12778))

packages/manager/cypress/e2e/core/vpc/vpc-linodes-update.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ describe('VPC assign/unassign flows', () => {
5050
});
5151

5252
beforeEach(() => {
53-
// TODO - Remove mock once `nodebalancerVpc` feature flag is removed.
53+
// TODO - Remove mock once `nodebalancerVpc` and `vpcIpv6 feature flags are removed.
5454
mockAppendFeatureFlags({
5555
nodebalancerVpc: false,
56+
vpcIpv6: false,
5657
}).as('getFeatureFlags');
5758
});
5859

@@ -166,9 +167,7 @@ describe('VPC assign/unassign flows', () => {
166167
.click();
167168

168169
// Auto-assign IPv4 checkbox checked by default
169-
cy.findByLabelText(
170-
'Auto-assign a VPC IPv4 address for this Linode'
171-
).should('be.checked');
170+
cy.findByLabelText('Auto-assign VPC IPv4 address').should('be.checked');
172171

173172
cy.wait('@getLinodeConfigs');
174173

@@ -305,7 +304,7 @@ describe('VPC assign/unassign flows', () => {
305304
.click();
306305

307306
// Uncheck auto-assign checkbox and type in VPC IPv4
308-
cy.findByLabelText('Auto-assign a VPC IPv4 address for this Linode')
307+
cy.findByLabelText('Auto-assign VPC IPv4 address')
309308
.should('be.checked')
310309
.click();
311310
cy.findByLabelText('VPC IPv4').should('be.visible').click();
@@ -335,6 +334,7 @@ describe('VPC assign/unassign flows', () => {
335334

336335
ui.button
337336
.findByTitle('Done')
337+
.scrollIntoView()
338338
.should('be.visible')
339339
.should('be.enabled')
340340
.click();

packages/manager/src/components/MaintenanceBanner/MaintenanceBannerV2.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ export const MaintenanceBannerV2 = () => {
3131
);
3232

3333
return (
34-
maintenanceLinodes.size > 0 && (
35-
<Notice data-qa-maintenance-banner-v2="true" data-testid="maintenance-banner" variant="warning">
34+
maintenanceLinodes.size > 0 && (
35+
<Notice
36+
data-qa-maintenance-banner-v2="true"
37+
data-testid="maintenance-banner"
38+
variant="warning"
39+
>
3640
<Typography>
3741
<strong>
3842
{pluralize('Linode', 'Linodes', maintenanceLinodes.size)}

packages/manager/src/components/MultipleIPInput/MultipleIPInput.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ const useStyles = makeStyles()((theme: Theme) => ({
5959
}));
6060

6161
export interface MultipeIPInputProps {
62+
/**
63+
* Tightens spacing when used in VPC Dual Stack contexts.
64+
* @default false
65+
*/
66+
adjustSpacingForVPCDualStack?: boolean;
67+
6268
/**
6369
* Text displayed on the button.
6470
*/
@@ -90,7 +96,7 @@ export interface MultipeIPInputProps {
9096
* Indicates if the input is for VPC IPv4 ranges.
9197
* @default false
9298
*/
93-
forVPCIPv4Ranges?: boolean;
99+
forVPCIPRanges?: boolean;
94100

95101
/**
96102
* Helper text for additional guidance.
@@ -147,12 +153,13 @@ export interface MultipeIPInputProps {
147153

148154
export const MultipleIPInput = React.memo((props: MultipeIPInputProps) => {
149155
const {
156+
adjustSpacingForVPCDualStack,
150157
buttonText,
151158
className,
152159
disabled,
153160
error,
154161
forDatabaseAccessControls,
155-
forVPCIPv4Ranges,
162+
forVPCIPRanges,
156163
helperText,
157164
ips,
158165
isLinkStyled,
@@ -202,8 +209,17 @@ export const MultipleIPInput = React.memo((props: MultipeIPInputProps) => {
202209
}
203210

204211
const addIPButton =
205-
forVPCIPv4Ranges || isLinkStyled ? (
206-
<StyledLinkButtonBox sx={{ marginTop: isLinkStyled ? '8px' : '12px' }}>
212+
forVPCIPRanges || isLinkStyled ? (
213+
<StyledLinkButtonBox
214+
sx={{
215+
marginTop:
216+
adjustSpacingForVPCDualStack && ips.length === 0
217+
? '0px'
218+
: isLinkStyled
219+
? '8px'
220+
: '12px',
221+
}}
222+
>
207223
<LinkButton isDisabled={disabled} onClick={addNewInput}>
208224
{buttonText}
209225
</LinkButton>
@@ -222,7 +238,7 @@ export const MultipleIPInput = React.memo((props: MultipeIPInputProps) => {
222238

223239
return (
224240
<div className={cx(classes.root, className)}>
225-
{tooltip ? (
241+
{tooltip && title ? (
226242
<div className={classes.ipNetmaskTooltipSection}>
227243
<InputLabel>{title}</InputLabel>
228244
<TooltipIcon
@@ -236,12 +252,16 @@ export const MultipleIPInput = React.memo((props: MultipeIPInputProps) => {
236252
/>
237253
</div>
238254
) : (
239-
<InputLabel>
240-
{title}
241-
{required ? (
242-
<span className={classes.required}> (required)</span>
243-
) : null}
244-
</InputLabel>
255+
// There are a couple of instances in the codebase where an empty string is passed as the title so a title isn't displayed.
256+
// Having this check ensures we don't render an empty label element (which can still impact spacing) in those cases.
257+
title && (
258+
<InputLabel>
259+
{title}
260+
{required ? (
261+
<span className={classes.required}> (required)</span>
262+
) : null}
263+
</InputLabel>
264+
)
245265
)}
246266
{helperText && (
247267
<Typography className={classes.helperText}>{helperText}</Typography>
@@ -257,7 +277,7 @@ export const MultipleIPInput = React.memo((props: MultipeIPInputProps) => {
257277
spacing={2}
258278
sx={{
259279
justifyContent: 'center',
260-
maxWidth: forVPCIPv4Ranges ? '415px' : undefined,
280+
maxWidth: forVPCIPRanges ? '415px' : undefined,
261281
}}
262282
>
263283
<Grid size={11}>
@@ -284,7 +304,7 @@ export const MultipleIPInput = React.memo((props: MultipeIPInputProps) => {
284304
* used in DBaaS or for Linode VPC interfaces
285305
*/}
286306
<Grid size={1}>
287-
{(idx > 0 || forDatabaseAccessControls || forVPCIPv4Ranges) && (
307+
{(idx > 0 || forDatabaseAccessControls || forVPCIPRanges) && (
288308
<IconButton
289309
aria-disabled={disabled}
290310
className={classes.button}

packages/manager/src/components/RemovableSelectionsList/RemovableSelectionsListTable.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ export type RemovableItem = {
2121
};
2222

2323
export interface RemovableSelectionsListTableProps {
24+
/**
25+
* If false, do not include VPC IPv6 and VPC IPv6 Ranges cells
26+
*/
27+
displayVPCIPv6Data?: boolean;
2428
/**
2529
* The descriptive text to display above the list
2630
*/
@@ -58,6 +62,7 @@ export const RemovableSelectionsListTable = (
5862
) => {
5963
const {
6064
headerText,
65+
displayVPCIPv6Data = false,
6166
isRemovable = true,
6267
noDataText,
6368
onRemove,
@@ -85,8 +90,20 @@ export const RemovableSelectionsListTable = (
8590
</TableCell>
8691
<TableCell>{selection.vpcIPv4 ?? null}</TableCell>
8792
<TableCell>
88-
{determineNoneSingleOrMultipleWithChip(selection.vpcRanges ?? [])}
93+
{determineNoneSingleOrMultipleWithChip(
94+
selection.vpcIPv4Ranges ?? []
95+
)}
8996
</TableCell>
97+
{displayVPCIPv6Data && (
98+
<>
99+
<TableCell>{selection.vpcIPv6 ?? null}</TableCell>
100+
<TableCell>
101+
{determineNoneSingleOrMultipleWithChip(
102+
selection.vpcIPv6Ranges ?? []
103+
)}
104+
</TableCell>
105+
</>
106+
)}
90107
<TableCell>
91108
{isRemovable && (
92109
<IconButton

packages/manager/src/features/Linodes/LinodesDetail/LinodeSettings/VPCPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,8 +255,8 @@ export const VPCPanel = (props: VPCPanelProps) => {
255255
<AssignIPRanges
256256
handleIPRangeChange={handleIPv4RangeChange}
257257
includeDescriptionInTooltip
258-
ipRanges={additionalIPv4RangesForVPC}
259258
ipRangesError={vpcIPRangesError}
259+
ipv4Ranges={additionalIPv4RangesForVPC}
260260
/>
261261
</>
262262
)}

packages/manager/src/features/VPCs/VPCDetail/AssignIPRanges.test.tsx

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ describe('AssignIPRanges', () => {
2020
const { getByText } = renderWithTheme(
2121
<AssignIPRanges
2222
handleIPRangeChange={handleIPRangeChangeMock}
23-
ipRanges={ipRanges}
23+
handleIPv6RangeChange={vi.fn()}
24+
ipv4Ranges={ipRanges}
2425
/>
2526
);
2627
expect(getByText(ASSIGN_IPV4_RANGES_TITLE)).toBeInTheDocument();
@@ -36,8 +37,9 @@ describe('AssignIPRanges', () => {
3637
const { getByText } = renderWithTheme(
3738
<AssignIPRanges
3839
handleIPRangeChange={handleIPRangeChangeMock}
39-
ipRanges={ipRanges}
40+
handleIPv6RangeChange={vi.fn()}
4041
ipRangesError={ipRangesError}
42+
ipv4Ranges={ipRanges}
4143
/>
4244
);
4345
expect(getByText('Error message')).toBeInTheDocument();
@@ -47,11 +49,52 @@ describe('AssignIPRanges', () => {
4749
const { getByText } = renderWithTheme(
4850
<AssignIPRanges
4951
handleIPRangeChange={handleIPRangeChangeMock}
50-
ipRanges={ipRanges}
52+
handleIPv6RangeChange={vi.fn()}
53+
ipv4Ranges={ipRanges}
5154
/>
5255
);
5356

5457
const button = getByText('Add IPv4 Range');
5558
expect(button).toBeInTheDocument();
5659
});
60+
61+
it('has section titles reflective of dual stack when showIPv6Fields is true', () => {
62+
const { getByText } = renderWithTheme(
63+
<AssignIPRanges
64+
handleIPRangeChange={vi.fn()}
65+
handleIPv6RangeChange={vi.fn()}
66+
ipv4Ranges={[]}
67+
ipv6Ranges={[]}
68+
showIPv6Fields={true}
69+
/>
70+
);
71+
72+
const additionalIPRangesSectionTitle = getByText(
73+
/Assign additional IP ranges/i
74+
);
75+
expect(additionalIPRangesSectionTitle).toBeInTheDocument();
76+
77+
const button = getByText('Add IPv6 Range');
78+
expect(button).toBeInTheDocument();
79+
});
80+
81+
it('does not have section titles reflective of dual stack when showIPv6Fields is false', () => {
82+
const { queryByText } = renderWithTheme(
83+
<AssignIPRanges
84+
handleIPRangeChange={vi.fn()}
85+
handleIPv6RangeChange={vi.fn()}
86+
ipv4Ranges={[]}
87+
ipv6Ranges={[]}
88+
showIPv6Fields={false}
89+
/>
90+
);
91+
92+
const additionalIPRangesSectionTitle = queryByText(
93+
/Assign additional IP ranges/i
94+
);
95+
expect(additionalIPRangesSectionTitle).not.toBeInTheDocument();
96+
97+
const button = queryByText('Add IPv6 Range');
98+
expect(button).not.toBeInTheDocument();
99+
});
57100
});

0 commit comments

Comments
 (0)