Skip to content

Commit 44d29e8

Browse files
authored
upcoming: [M3-10204] - Add VPC IPv4 and IPv6 columns to Node Pool tables on LKE-E Cluster Details page (linode#12600)
* Add stack_type to api-v4 package * Add new table columns for VPC IPs * Pass clusterStackType prop, update logic to show VPC columns * Update LinodeIPsResponse type for IPv6 VPCs and factories * Move util to new file, add new param * Update columns and rows to show correct VPC IP data * Update mocks * Add changesets * Remove unused clusterStackType prop * Add optional chaining to fix error surfaced in unit test * Improve variable names * Fix typecheck error created from merging * Address feedback: combine conditional; fix error text color * Improve mocks to handle auto vs BYO VPC on LKE-E creation * Fix double-click required for submitting issue by awaiting trigger call
1 parent 473315f commit 44d29e8

File tree

13 files changed

+310
-47
lines changed

13 files changed

+310
-47
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": Changed
3+
---
4+
5+
Update `LinodeIPsResponseIPV6` to include `vpc` array ([#12600](https://github.com/linode/manager/pull/12600))

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ export interface LinodeIPsResponseIPV6 {
162162
global: IPRange[];
163163
link_local: IPAddress;
164164
slaac: IPAddress;
165+
vpc: VPCIP[];
165166
}
166167

167168
export type LinodeStatus =
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 IPv4 and IPv6 columns to node pools table on LKE-E cluster details page ([#12600](https://github.com/linode/manager/pull/12600))

packages/manager/cypress/e2e/core/linodes/linode-network.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ describe('IP Addresses', () => {
7474
global: [_ipv6Range],
7575
link_local: ipv6Address,
7676
slaac: ipv6Address,
77+
vpc: [],
7778
},
7879
}).as('getLinodeIPAddresses');
7980
mockUpdateIPAddress(linodeIPv4, mockRDNS).as('updateIPAddress');

packages/manager/src/features/Kubernetes/CreateCluster/CreateCluster.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,9 @@ export const CreateCluster = () => {
349349
// TODO: Improve error handling in M3-10429, at which point we shouldn't need this.
350350
if (isLkeEnterprisePhase2FeatureEnabled && selectedTier === 'enterprise') {
351351
// Trigger the React Hook Form validation for BYO VPC selection.
352-
trigger();
352+
const isValid = await trigger();
353353
// Don't submit the form while RHF errors persist.
354-
if (!formState.isValid) {
354+
if (!isValid) {
355355
setSubmitting(false);
356356
scrollErrorIntoViewV2(formContainerRef);
357357
return;

packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeRow.tsx

Lines changed: 75 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import styled from '@emotion/styled';
2-
import { usePreferences } from '@linode/queries';
2+
import { useLinodeIPsQuery, usePreferences } from '@linode/queries';
33
import { Box, Typography } from '@linode/ui';
44
import * as React from 'react';
55

@@ -13,7 +13,7 @@ import { useInProgressEvents } from 'src/queries/events/events';
1313

1414
import { NodeActionMenu } from './NodeActionMenu';
1515

16-
import type { APIError } from '@linode/api-v4';
16+
import type { APIError, VPCIP } from '@linode/api-v4';
1717

1818
export interface NodeRow {
1919
instanceId?: number;
@@ -22,6 +22,7 @@ export interface NodeRow {
2222
label?: string;
2323
nodeId: string;
2424
nodeStatus: string;
25+
shouldShowVpcIPAddressColumns: boolean;
2526
}
2627

2728
interface NodeRowProps extends NodeRow {
@@ -43,13 +44,25 @@ export const NodeRow = React.memo((props: NodeRowProps) => {
4344
nodeStatus,
4445
openRecycleNodeDialog,
4546
typeLabel,
47+
shouldShowVpcIPAddressColumns,
4648
} = props;
4749

50+
const { data: ips, error: ipsError } = useLinodeIPsQuery(
51+
instanceId ?? -1,
52+
Boolean(instanceId)
53+
);
4854
const { data: events } = useInProgressEvents();
4955
const { data: maskSensitiveDataPreference } = usePreferences(
5056
(preferences) => preferences?.maskSensitiveData
5157
);
5258

59+
const vpcIpv4: VPCIP = ips?.ipv4?.vpc.find(
60+
(ip: VPCIP) => ip.address !== null
61+
);
62+
const vpcIpv6: VPCIP = ips?.ipv6?.vpc?.find(
63+
(ip: VPCIP) => ip.ipv6_addresses[0].slaac_address !== null
64+
);
65+
5366
const recentEvent = events?.find(
5467
(event) =>
5568
event.entity?.id === instanceId && event.entity?.type === 'linode'
@@ -67,61 +80,107 @@ export const NodeRow = React.memo((props: NodeRowProps) => {
6780
? 'active'
6881
: 'inactive';
6982

70-
const displayLabel = label ?? typeLabel;
83+
const labelText = label ?? typeLabel;
7184

72-
const displayStatus =
85+
const statusText =
7386
nodeStatus === 'not_ready'
7487
? 'Provisioning'
7588
: transitionText(instanceStatus ?? '', instanceId ?? -1, recentEvent);
7689

77-
const displayIP = ip ?? '';
90+
const publicIPv4Text = ip ?? '';
91+
const vpcIpv4Text = vpcIpv4?.address ?? '';
92+
const vpcIpv6Text = vpcIpv6?.ipv6_addresses[0].slaac_address ?? '';
7893

7994
return (
8095
<TableRow data-qa-node-row={nodeId}>
8196
<TableCell noWrap>
82-
{linodeLink ? (
83-
<Link to={linodeLink}>{displayLabel}</Link>
84-
) : (
85-
displayLabel
86-
)}
97+
{linodeLink ? <Link to={linodeLink}>{labelText}</Link> : labelText}
8798
</TableCell>
8899
<TableCell statusCell={!linodeError}>
89100
{linodeError ? (
90101
<Typography
91102
sx={(theme) => ({
92-
color: theme.color.red,
103+
color: theme.tokens.alias.Content.Text.Negative,
93104
})}
94105
>
95106
Error retrieving status
96107
</Typography>
97108
) : (
98109
<>
99110
<StatusIcon status={iconStatus} />
100-
{displayStatus}
111+
{statusText}
101112
</>
102113
)}
103114
</TableCell>
104115
<TableCell noWrap>
105116
{linodeError ? (
106117
<Typography
107118
sx={(theme) => ({
108-
color: theme.color.red,
119+
color: theme.tokens.alias.Content.Text.Negative,
109120
})}
110121
>
111122
Error retrieving IP
112123
</Typography>
113-
) : displayIP.length > 0 ? (
124+
) : publicIPv4Text.length > 0 ? (
114125
<Box alignItems="center" display="flex" gap={0.5}>
115126
<CopyTooltip
116127
copyableText
117128
masked={Boolean(maskSensitiveDataPreference)}
118129
maskedTextLength="ipv4"
119-
text={displayIP}
130+
text={publicIPv4Text}
120131
/>
121-
<StyledCopyTooltip text={displayIP} />
132+
<StyledCopyTooltip text={publicIPv4Text} />
122133
</Box>
123134
) : null}
124135
</TableCell>
136+
{shouldShowVpcIPAddressColumns && (
137+
<TableCell noWrap>
138+
{linodeError || ipsError ? (
139+
<Typography
140+
sx={(theme) => ({
141+
color: theme.tokens.alias.Content.Text.Negative,
142+
})}
143+
>
144+
Error retrieving IP
145+
</Typography>
146+
) : vpcIpv4Text.length > 0 ? (
147+
<Box alignItems="center" display="flex" gap={0.5}>
148+
<CopyTooltip
149+
copyableText
150+
masked={Boolean(maskSensitiveDataPreference)}
151+
maskedTextLength="ipv4"
152+
text={vpcIpv4Text}
153+
/>
154+
<StyledCopyTooltip text={vpcIpv4Text} />
155+
</Box>
156+
) : null}
157+
</TableCell>
158+
)}
159+
{shouldShowVpcIPAddressColumns && (
160+
<TableCell noWrap>
161+
{linodeError || ipsError ? (
162+
<Typography
163+
sx={(theme) => ({
164+
color: theme.tokens.alias.Content.Text.Negative,
165+
})}
166+
>
167+
Error retrieving IP
168+
</Typography>
169+
) : vpcIpv6Text.length > 0 ? (
170+
<Box alignItems="center" display="flex" gap={0.5}>
171+
<CopyTooltip
172+
copyableText
173+
masked={Boolean(maskSensitiveDataPreference)}
174+
maskedTextLength="ipv6"
175+
text={vpcIpv6Text}
176+
/>
177+
<StyledCopyTooltip text={vpcIpv6Text} />
178+
</Box>
179+
) : (
180+
'—'
181+
)}
182+
</TableCell>
183+
)}
125184
<TableCell actionCell>
126185
<NodeActionMenu
127186
instanceLabel={label}

packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/NodeTable.tsx

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@ import { useUpdateNodePoolMutation } from 'src/queries/kubernetes';
3030
import { parseAPIDate } from 'src/utilities/date';
3131
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
3232

33+
import { useIsLkeEnterpriseEnabled } from '../../kubeUtils';
3334
import { NodeRow as _NodeRow } from './NodeRow';
3435
import { NodePoolTableFooter } from './NodeTable.styles';
36+
import { nodeToRow } from './utils';
3537

3638
import type { StatusFilter } from './NodePoolsDisplay';
37-
import type { NodeRow } from './NodeRow';
3839
import type {
3940
KubeNodePoolResponse,
4041
KubernetesTier,
4142
PoolNodeResponse,
4243
} from '@linode/api-v4/lib/kubernetes';
4344
import type { EncryptionStatus } from '@linode/api-v4/lib/linodes/types';
44-
import type { LinodeWithMaintenance } from 'src/utilities/linodes';
4545

4646
export interface Props {
4747
clusterCreated: string;
@@ -83,6 +83,7 @@ export const NodeTable = React.memo((props: Props) => {
8383
const { data: linodes, error, isLoading } = useAllLinodesQuery();
8484
const { isDiskEncryptionFeatureEnabled } =
8585
useIsDiskEncryptionFeatureEnabled();
86+
const { isLkeEnterprisePhase2FeatureEnabled } = useIsLkeEnterpriseEnabled();
8687

8788
const { mutateAsync: updateNodePool } = useUpdateNodePoolMutation(
8889
clusterId,
@@ -103,7 +104,13 @@ export const NodeTable = React.memo((props: Props) => {
103104
[updateNodePool]
104105
);
105106

106-
const rowData = nodes.map((thisNode) => nodeToRow(thisNode, linodes ?? []));
107+
const shouldShowVpcIPAddressColumns =
108+
isLkeEnterprisePhase2FeatureEnabled && clusterTier === 'enterprise';
109+
const numColumns = shouldShowVpcIPAddressColumns ? 6 : 4;
110+
111+
const rowData = nodes.map((thisNode) =>
112+
nodeToRow(thisNode, linodes ?? [], shouldShowVpcIPAddressColumns)
113+
);
107114

108115
const filteredRowData = ['offline', 'provisioning', 'running'].includes(
109116
statusFilter
@@ -201,16 +208,23 @@ export const NodeTable = React.memo((props: Props) => {
201208
width: '35%',
202209
})}
203210
>
204-
IP Address
211+
Public IPv4
205212
</TableSortCell>
213+
{shouldShowVpcIPAddressColumns && (
214+
<>
215+
<TableCell>VPC IPv4</TableCell>
216+
<TableCell>VPC IPv6</TableCell>
217+
</>
218+
)}
219+
206220
<TableCell />
207221
</TableRow>
208222
</TableHead>
209223
<TableBody>
210224
{rowData.length === 0 &&
211225
isEnterpriseClusterWithin20MinsOfCreation() && (
212226
<TableRow>
213-
<TableCell colSpan={4}>
227+
<TableCell colSpan={numColumns}>
214228
<ErrorState
215229
compact
216230
CustomIcon={EmptyStateCloud}
@@ -238,7 +252,7 @@ export const NodeTable = React.memo((props: Props) => {
238252
<TableContentWrapper
239253
length={paginatedAndOrderedData.length}
240254
loading={isLoading}
241-
loadingProps={{ columns: 4 }}
255+
loadingProps={{ columns: numColumns }}
242256
>
243257
{paginatedAndOrderedData.map((eachRow) => {
244258
return (
@@ -253,6 +267,9 @@ export const NodeTable = React.memo((props: Props) => {
253267
nodeId={eachRow.nodeId}
254268
nodeStatus={eachRow.nodeStatus}
255269
openRecycleNodeDialog={openRecycleNodeDialog}
270+
shouldShowVpcIPAddressColumns={
271+
shouldShowVpcIPAddressColumns
272+
}
256273
typeLabel={typeLabel}
257274
/>
258275
);
@@ -320,27 +337,6 @@ export const NodeTable = React.memo((props: Props) => {
320337
);
321338
});
322339

323-
/**
324-
* Transforms an LKE Pool Node to a NodeRow.
325-
*/
326-
export const nodeToRow = (
327-
node: PoolNodeResponse,
328-
linodes: LinodeWithMaintenance[]
329-
): NodeRow => {
330-
const foundLinode = linodes.find(
331-
(thisLinode) => thisLinode.id === node.instance_id
332-
);
333-
334-
return {
335-
instanceId: node.instance_id || undefined,
336-
instanceStatus: foundLinode?.status,
337-
ip: foundLinode?.ipv4[0],
338-
label: foundLinode?.label,
339-
nodeId: node.id,
340-
nodeStatus: node.status,
341-
};
342-
};
343-
344340
export const EncryptedStatus = ({
345341
encryptionStatus,
346342
regionSupportsDiskEncryption,

packages/manager/src/features/Kubernetes/KubernetesClusterDetail/NodePoolsDisplay/utils.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import type { NodeRow } from './NodeRow';
2+
import type { PoolNodeResponse } from '@linode/api-v4';
3+
import type { LinodeWithMaintenance } from 'src/utilities/linodes';
4+
15
/**
26
* Checks whether prices are valid - 0 is valid, but undefined and null prices are invalid.
37
* @returns true if either value is null or undefined
@@ -11,3 +15,26 @@ export const hasInvalidNodePoolPrice = (
1115

1216
return isInvalidPricePerNode || isInvalidTotalPrice;
1317
};
18+
19+
/**
20+
* Transforms an LKE Pool Node to a NodeRow.
21+
*/
22+
export const nodeToRow = (
23+
node: PoolNodeResponse,
24+
linodes: LinodeWithMaintenance[],
25+
shouldShowVpcIPAddressColumns: boolean
26+
): NodeRow => {
27+
const foundLinode = linodes.find(
28+
(thisLinode) => thisLinode.id === node.instance_id
29+
);
30+
31+
return {
32+
instanceId: node.instance_id || undefined,
33+
instanceStatus: foundLinode?.status,
34+
ip: foundLinode?.ipv4[0],
35+
label: foundLinode?.label,
36+
nodeId: node.id,
37+
nodeStatus: node.status,
38+
shouldShowVpcIPAddressColumns,
39+
};
40+
};

packages/manager/src/features/Linodes/LinodesDetail/LinodeNetworking/LinodeIPAddresses.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ describe('ipResponseToDisplayRows utility function', () => {
5959
],
6060
link_local: ipAddressFactory.build({ type: 'ipv6' }),
6161
slaac: ipAddressFactory.build({ type: 'ipv6' }),
62+
vpc: [],
6263
},
6364
};
6465

packages/manager/src/mocks/mockState.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ export const emptyStore: MockState = {
3535
kubernetesNodePools: [],
3636
linodeConfigs: [],
3737
linodeInterfaces: [],
38+
linodeIps: [],
3839
linodes: [],
3940
nodeBalancerConfigNodes: [],
4041
nodeBalancerConfigs: [],

0 commit comments

Comments
 (0)