Skip to content

Commit bb8ad10

Browse files
fix: [M3-9883] - Fix Upgrade Interface issues: URL navigation and Unable to find this Interfaces Firewall device in Edit Interface Drawer (#12146)
* fix diff issue when navigating to network tab * initial invalidation - maybe can tighten up a bit? * vpc invalidations so far * remove stray console logs * update changelog * Update packages/queries/src/linodes/interfaces.ts Co-authored-by: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> * fix invalidation and fix firewall error subnet linode row * remove comment * update test --------- Co-authored-by: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com>
1 parent f5a7640 commit bb8ad10

File tree

8 files changed

+188
-151
lines changed

8 files changed

+188
-151
lines changed

packages/manager/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
101101
- Add non-dismissible option support to Dismissible Banner ([#12115](https://github.com/linode/manager/pull/12115))
102102
- Add mocks and update `PlansPanel` to support `mtc-tt-2025` plans in selected regions (#12050)
103103
- IAM RBAC: Implement method to merge user-selected roles into existing roles ([#12125](https://github.com/linode/manager/pull/12125))
104+
- Add navigation to Linode Network tab after successfully upgrading interfaces ([#12146](https://github.com/linode/manager/pull/12146))
105+
- Fix "Error retrieving Firewalls" message in Subnet Linode Row after upgrading interfaces ([#12146](https://github.com/linode/manager/pull/12146))
104106

105107
## [2025-04-22] - v1.140.0
106108

packages/manager/src/features/Linodes/LinodesDetail/LinodeConfigs/UpgradeInterfaces/DialogContents/SuccessDialogContent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ export const SuccessDialogContent = (
9797
onClick={() => {
9898
const newPath = location.pathname
9999
.split('/')
100-
// remove 'upgrade-interfaces' from URL
101-
.slice(0, -1)
100+
// keep only xxx/linodes/:linodeId
101+
.slice(0, 3)
102102
// join everything back together
103103
.join('/')
104104
.concat('/networking');

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

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
} from 'src/factories';
1616
import { linodeConfigFactory } from 'src/factories/linodeConfigs';
1717
import { makeResourcePage } from 'src/mocks/serverHandlers';
18-
import { HttpResponse, http, server } from 'src/mocks/testServer';
18+
import { http, HttpResponse, server } from 'src/mocks/testServer';
1919
import {
2020
mockMatchMedia,
2121
renderWithTheme,
@@ -82,26 +82,22 @@ describe('SubnetLinodeRow', () => {
8282
const handlePowerActionsLinode = vi.fn();
8383
const handleUnassignLinode = vi.fn();
8484

85-
const {
86-
getAllByRole,
87-
getAllByText,
88-
getByTestId,
89-
getByText,
90-
} = renderWithTheme(
91-
wrapWithTableBody(
92-
<SubnetLinodeRow
93-
handlePowerActionsLinode={handlePowerActionsLinode}
94-
handleUnassignLinode={handleUnassignLinode}
95-
isVPCLKEEnterpriseCluster={false}
96-
linodeId={linodeFactory1.id}
97-
subnet={subnetFactory.build()}
98-
subnetId={1}
99-
subnetInterfaces={[{ active: true, config_id: config.id, id: 1 }]}
100-
/>
101-
)
102-
);
85+
const { getAllByRole, getAllByText, getByTestId, findByText } =
86+
renderWithTheme(
87+
wrapWithTableBody(
88+
<SubnetLinodeRow
89+
handlePowerActionsLinode={handlePowerActionsLinode}
90+
handleUnassignLinode={handleUnassignLinode}
91+
isVPCLKEEnterpriseCluster={false}
92+
linodeId={linodeFactory1.id}
93+
subnet={subnetFactory.build()}
94+
subnetId={1}
95+
subnetInterfaces={[{ active: true, config_id: config.id, id: 1 }]}
96+
/>
97+
)
98+
);
10399

104-
// Loading state should render
100+
// Loading states should render
105101
expect(getByTestId(loadingTestId)).toBeInTheDocument();
106102

107103
await waitForElementToBeRemoved(getByTestId(loadingTestId));
@@ -113,7 +109,6 @@ describe('SubnetLinodeRow', () => {
113109
);
114110

115111
getAllByText('10.0.0.0');
116-
getByText(mockFirewall0);
117112

118113
const plusChipButton = getAllByRole('button')[1];
119114
expect(plusChipButton).toHaveTextContent('+1');
@@ -127,6 +122,8 @@ describe('SubnetLinodeRow', () => {
127122
expect(unassignLinodeButton).toHaveTextContent('Unassign Linode');
128123
await userEvent.click(unassignLinodeButton);
129124
expect(handleUnassignLinode).toHaveBeenCalled();
125+
const firewall = await findByText(mockFirewall0);
126+
expect(firewall).toBeVisible();
130127
});
131128

132129
it('should display the ip, range, and firewall for a Linode using Linode Interfaces', async () => {
@@ -148,24 +145,20 @@ describe('SubnetLinodeRow', () => {
148145
const handlePowerActionsLinode = vi.fn();
149146
const handleUnassignLinode = vi.fn();
150147

151-
const {
152-
getAllByRole,
153-
getAllByText,
154-
getByTestId,
155-
getByText,
156-
} = renderWithTheme(
157-
wrapWithTableBody(
158-
<SubnetLinodeRow
159-
handlePowerActionsLinode={handlePowerActionsLinode}
160-
handleUnassignLinode={handleUnassignLinode}
161-
isVPCLKEEnterpriseCluster={false}
162-
linodeId={linodeFactory1.id}
163-
subnet={subnetFactory.build()}
164-
subnetId={1}
165-
subnetInterfaces={[{ active: true, config_id: null, id: 1 }]}
166-
/>
167-
)
168-
);
148+
const { getAllByRole, getAllByText, getByTestId, findByText } =
149+
renderWithTheme(
150+
wrapWithTableBody(
151+
<SubnetLinodeRow
152+
handlePowerActionsLinode={handlePowerActionsLinode}
153+
handleUnassignLinode={handleUnassignLinode}
154+
isVPCLKEEnterpriseCluster={false}
155+
linodeId={linodeFactory1.id}
156+
subnet={subnetFactory.build()}
157+
subnetId={1}
158+
subnetInterfaces={[{ active: true, config_id: null, id: 1 }]}
159+
/>
160+
)
161+
);
169162

170163
// Loading state should render
171164
expect(getByTestId(loadingTestId)).toBeInTheDocument();
@@ -180,7 +173,8 @@ describe('SubnetLinodeRow', () => {
180173

181174
getAllByText('10.0.0.0');
182175
getAllByText('10.0.0.1');
183-
getByText(mockFirewall0);
176+
const firewall = await findByText(mockFirewall0);
177+
expect(firewall).toBeVisible();
184178
});
185179

186180
it('should not display reboot linode button if the linode has all active interfaces', async () => {
@@ -215,15 +209,15 @@ describe('SubnetLinodeRow', () => {
215209
const { getAllByRole, getByTestId } = renderWithTheme(
216210
wrapWithTableBody(
217211
<SubnetLinodeRow
218-
subnetInterfaces={[
219-
{ active: true, config_id: config.id, id: vpcInterface.id },
220-
]}
221212
handlePowerActionsLinode={handlePowerActionsLinode}
222213
handleUnassignLinode={handleUnassignLinode}
223214
isVPCLKEEnterpriseCluster={false}
224215
linodeId={linodeFactory1.id}
225216
subnet={subnetFactory.build()}
226217
subnetId={0}
218+
subnetInterfaces={[
219+
{ active: true, config_id: config.id, id: vpcInterface.id },
220+
]}
227221
/>
228222
)
229223
);

packages/manager/src/features/VPCs/VPCDetail/SubnetLinodeRow.tsx

Lines changed: 20 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { TableRow } from 'src/components/TableRow';
1313
import { getLinodeIconStatus } from 'src/features/Linodes/LinodesLanding/utils';
1414
import { determineNoneSingleOrMultipleWithChip } from 'src/utilities/noneSingleOrMultipleWithChip';
1515

16-
import { useInterfaceAndFirewallDataForLinode } from '../../../hooks/useInterfaceAndFirewallDataForLinode';
16+
import { useInterfaceDataForLinode } from '../../../hooks/useInterfaceDataForLinode';
1717
import {
1818
VPC_REBOOT_MESSAGE,
1919
WARNING_ICON_UNRECOMMENDED_CONFIG,
@@ -25,10 +25,13 @@ import {
2525
hasUnrecommendedConfigurationLinodeInterface,
2626
} from '../utils';
2727
import { StyledWarningIcon } from './SubnetLinodeRow.styles';
28+
import {
29+
ConfigInterfaceFirewallCell,
30+
LinodeInterfaceFirewallCell,
31+
} from './SubnetLinodeRowFirewallsCell';
2832

2933
import type {
3034
APIError,
31-
Firewall,
3235
Interface,
3336
Linode,
3437
LinodeInterface,
@@ -82,18 +85,16 @@ export const SubnetLinodeRow = (props: Props) => {
8285

8386
/**
8487
* We need to handle support for both legacy interfaces and Linode interfaces.
85-
* The below hook gives us the relevant firewall and interface data depending on
88+
* The below hook gives us the relevant interface data depending on
8689
* interface type.
8790
*/
88-
const { firewallsInfo, interfacesInfo } =
89-
useInterfaceAndFirewallDataForLinode({
90-
configId, // subnet.linodes.interfaces data now includes config_id, so we no longer have to fetch all configs
91-
interfaceId,
92-
isLinodeInterface,
93-
linodeId,
94-
});
91+
const { interfacesInfo } = useInterfaceDataForLinode({
92+
configId, // subnet.linodes.interfaces data now includes config_id, so we no longer have to fetch all configs
93+
interfaceId,
94+
isLinodeInterface,
95+
linodeId,
96+
});
9597

96-
const { attachedFirewalls, firewallsError, firewallsLoading } = firewallsInfo;
9798
const {
9899
config, // undefined if this Linode is using Linode Interfaces. Used to determine an unrecommended configuration
99100
configInterface, // undefined if this Linode is using Linode Interfaces
@@ -225,13 +226,14 @@ export const SubnetLinodeRow = (props: Props) => {
225226
</TableCell>
226227
</Hidden>
227228
<Hidden smDown>
228-
<TableCell>
229-
{getFirewallsCellString(
230-
attachedFirewalls?.data ?? [],
231-
firewallsLoading,
232-
firewallsError ?? undefined
233-
)}
234-
</TableCell>
229+
{isLinodeInterface ? (
230+
<LinodeInterfaceFirewallCell
231+
interfaceId={interfaceId}
232+
linodeId={linodeId}
233+
/>
234+
) : (
235+
<ConfigInterfaceFirewallCell linodeId={linodeId} />
236+
)}
235237
</Hidden>
236238
<TableCell actionCell>
237239
{!isVPCLKEEnterpriseCluster && (
@@ -270,26 +272,6 @@ export const SubnetLinodeRow = (props: Props) => {
270272
);
271273
};
272274

273-
const getFirewallsCellString = (
274-
data: Firewall[],
275-
loading: boolean,
276-
error?: APIError[]
277-
): JSX.Element | string => {
278-
if (loading) {
279-
return 'Loading...';
280-
}
281-
282-
if (error) {
283-
return 'Error retrieving Firewalls';
284-
}
285-
286-
if (data.length === 0) {
287-
return 'None';
288-
}
289-
290-
return getFirewallLinks(data);
291-
};
292-
293275
const getSubnetLinodeIPv4CellString = (
294276
interfaceData: Interface | LinodeInterface | undefined,
295277
loading: boolean,
@@ -357,30 +339,6 @@ const getIPRangesCellContents = (
357339
}
358340
};
359341

360-
const getFirewallLinks = (data: Firewall[]): JSX.Element => {
361-
const firstThreeFirewalls = data.slice(0, 3);
362-
return (
363-
<>
364-
{firstThreeFirewalls.map((firewall, idx) => (
365-
<Link
366-
className="link secondaryLink"
367-
data-testid="firewall-row-link"
368-
key={firewall.id}
369-
to={`/firewalls/${firewall.id}`}
370-
>
371-
{idx > 0 && `, `}
372-
{firewall.label}
373-
</Link>
374-
))}
375-
{data.length > 3 && (
376-
<span>
377-
{`, `}plus {data.length - 3} more.
378-
</span>
379-
)}
380-
</>
381-
);
382-
};
383-
384342
export const SubnetLinodeTableRowHead = (
385343
<TableRow>
386344
<TableCell>Linode Label</TableCell>
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import {
2+
useLinodeFirewallsQuery,
3+
useLinodeInterfaceFirewallsQuery,
4+
} from '@linode/queries';
5+
import * as React from 'react';
6+
7+
import { Link } from 'src/components/Link';
8+
import { TableCell } from 'src/components/TableCell';
9+
10+
import type { APIError, Firewall } from '@linode/api-v4';
11+
12+
export const ConfigInterfaceFirewallCell = (props: { linodeId: number }) => {
13+
const { linodeId } = props;
14+
const {
15+
data: attachedFirewalls,
16+
error,
17+
isLoading,
18+
} = useLinodeFirewallsQuery(linodeId);
19+
20+
return (
21+
<TableCell>
22+
{getFirewallsCellString(
23+
attachedFirewalls?.data ?? [],
24+
isLoading,
25+
error ?? undefined
26+
)}
27+
</TableCell>
28+
);
29+
};
30+
31+
export const LinodeInterfaceFirewallCell = (props: {
32+
interfaceId: number;
33+
linodeId: number;
34+
}) => {
35+
const { linodeId, interfaceId } = props;
36+
const {
37+
data: attachedFirewalls,
38+
error,
39+
isLoading,
40+
} = useLinodeInterfaceFirewallsQuery(linodeId, interfaceId);
41+
42+
return (
43+
<TableCell>
44+
{getFirewallsCellString(
45+
attachedFirewalls?.data ?? [],
46+
isLoading,
47+
error ?? undefined
48+
)}
49+
</TableCell>
50+
);
51+
};
52+
53+
const getFirewallsCellString = (
54+
data: Firewall[],
55+
loading: boolean,
56+
error?: APIError[]
57+
): JSX.Element | string => {
58+
if (loading) {
59+
return 'Loading...';
60+
}
61+
62+
if (error) {
63+
return 'Error retrieving Firewalls';
64+
}
65+
66+
if (data.length === 0) {
67+
return 'None';
68+
}
69+
70+
return getFirewallLinks(data);
71+
};
72+
73+
const getFirewallLinks = (data: Firewall[]): JSX.Element => {
74+
const firstThreeFirewalls = data.slice(0, 3);
75+
return (
76+
<>
77+
{firstThreeFirewalls.map((firewall, idx) => (
78+
<Link
79+
className="link secondaryLink"
80+
data-testid="firewall-row-link"
81+
key={firewall.id}
82+
to={`/firewalls/${firewall.id}`}
83+
>
84+
{idx > 0 && `, `}
85+
{firewall.label}
86+
</Link>
87+
))}
88+
{data.length > 3 && (
89+
<span>
90+
{`, `}plus {data.length - 3} more.
91+
</span>
92+
)}
93+
</>
94+
);
95+
};

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,11 @@ describe('VPC Subnets table', () => {
156156
});
157157

158158
it('should disable Create Subnet button if the VPC is associated with a LKE-E cluster', async () => {
159+
server.use(
160+
http.get('*/networking/firewalls/settings', () => {
161+
return HttpResponse.json(firewallSettingsFactory.build());
162+
})
163+
);
159164
const { getByRole, queryByTestId } = await renderWithThemeAndRouter(
160165
<VPCSubnetsTable
161166
isVPCLKEEnterpriseCluster={true}

0 commit comments

Comments
 (0)