Skip to content

Commit de83a80

Browse files
fix: [M3-10498], [M3-10499] - Fix routing issues in /account and /account/users when iam nav is enabled (linode#12735)
* fix routing issuse for /users and /account/users when iam nav flag enabled * Added changeset: Navigating to /account redirects to /billing when IAM navigation is enabled * Added changeset: Navigating to account/users shows tabs for administration pages when IAM nav is enabled * fix type errors * fix - lint errors * Update Cypress tests following account routing changes * Make Billing consistent in both menu sections * Delete deep link smoke test that was made obsolete by account/administration routing changes --------- Co-authored-by: Joe D'Amore <[email protected]>
1 parent 1bde6f4 commit de83a80

26 files changed

+323
-95
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Fixed
3+
---
4+
5+
Navigating to "/account" redirects to "/billing" when IAM navigation is enabled ([#12735](https://github.com/linode/manager/pull/12735))
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Fixed
3+
---
4+
5+
Navigating to "account/users" shows tabs for administration pages when IAM nav is enabled ([#12735](https://github.com/linode/manager/pull/12735))

packages/manager/cypress/e2e/core/account/user-permissions.spec.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
mockUpdateUser,
1010
mockUpdateUserGrants,
1111
} from 'support/intercepts/account';
12+
import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags';
1213
import { mockGetProfile } from 'support/intercepts/profile';
1314
import { ui } from 'support/ui';
1415
import { shuffleArray } from 'support/util/arrays';
@@ -168,6 +169,13 @@ const assertBillingAccessSelected = (
168169
};
169170

170171
describe('User permission management', () => {
172+
beforeEach(() => {
173+
// TODO M3-10003 - Remove mock once `limitsEvolution` feature flag is removed.
174+
mockAppendFeatureFlags({
175+
iamRbacPrimaryNavChanges: true,
176+
}).as('getFeatureFlags');
177+
});
178+
171179
/*
172180
* - Confirms that full account access can be toggled for account users using mock API data.
173181
* - Confirms that users can navigate to User Permissions pages via Users & Grants page.
@@ -193,7 +201,7 @@ describe('User permission management', () => {
193201
mockGetUserGrantsUnrestrictedAccess(mockUser.username).as('getUserGrants');
194202

195203
// Navigate to Users & Grants page, find mock user, click its "User Permissions" button.
196-
cy.visitWithLogin('/account/users');
204+
cy.visitWithLogin('/users');
197205
cy.wait('@getUsers');
198206
cy.findByText(mockUser.username)
199207
.should('be.visible')
@@ -208,10 +216,7 @@ describe('User permission management', () => {
208216

209217
// Confirm that Cloud navigates to the user's permissions page and that user has
210218
// unrestricted account access.
211-
cy.url().should(
212-
'endWith',
213-
`/account/users/${mockUser.username}/permissions`
214-
);
219+
cy.url().should('endWith', `/users/${mockUser.username}/permissions`);
215220
cy.findByText(unrestrictedAccessMessage).should('be.visible');
216221

217222
// Restrict account access, confirm page updates to reflect change.
@@ -305,7 +310,7 @@ describe('User permission management', () => {
305310

306311
mockGetUser(mockUser).as('getUser');
307312
mockGetUserGrants(mockUser.username, mockUserGrants).as('getUserGrants');
308-
cy.visitWithLogin(`/account/users/${mockUser.username}/permissions`);
313+
cy.visitWithLogin(`/users/${mockUser.username}/permissions`);
309314
cy.wait(['@getUser', '@getUserGrants']);
310315

311316
mockUpdateUserGrants(mockUser.username, mockUserGrantsUpdatedGlobal).as(
@@ -397,7 +402,7 @@ describe('User permission management', () => {
397402

398403
mockGetUser(mockUser);
399404
mockGetUserGrants(mockUser.username, mockUserGrants);
400-
cy.visitWithLogin(`/account/users/${mockUser.username}/permissions`);
405+
cy.visitWithLogin(`/users/${mockUser.username}/permissions`);
401406

402407
// Test reset in Global Permissions section.
403408
cy.get('[data-qa-global-section]')
@@ -508,9 +513,7 @@ describe('User permission management', () => {
508513
mockGetUserGrants(mockActiveUser.username, mockUserGrants);
509514
mockGetProfile(mockProfile);
510515

511-
cy.visitWithLogin(
512-
`/account/users/${mockRestrictedUser.username}/permissions`
513-
);
516+
cy.visitWithLogin(`/users/${mockRestrictedUser.username}/permissions`);
514517
mockGetUser(mockRestrictedUser);
515518
mockGetUserGrants(mockRestrictedUser.username, mockUserGrants);
516519

@@ -572,9 +575,7 @@ describe('User permission management', () => {
572575
mockGetUser(mockRestrictedProxyUser);
573576
mockGetUserGrants(mockRestrictedProxyUser.username, mockUserGrants);
574577

575-
cy.visitWithLogin(
576-
`/account/users/${mockRestrictedProxyUser.username}/permissions`
577-
);
578+
cy.visitWithLogin(`/users/${mockRestrictedProxyUser.username}/permissions`);
578579

579580
cy.findByText('Parent User Permissions', { exact: false }).should(
580581
'be.visible'

packages/manager/cypress/e2e/core/account/users-landing-page.spec.ts

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
mockGetUserGrantsUnrestrictedAccess,
99
mockGetUsers,
1010
} from 'support/intercepts/account';
11+
import { mockAppendFeatureFlags } from 'support/intercepts/feature-flags';
1112
import {
1213
mockGetProfile,
1314
mockGetProfileGrants,
@@ -79,6 +80,13 @@ const initTestUsers = (profile: Profile, enableChildAccountAccess: boolean) => {
7980
};
8081

8182
describe('Users landing page', () => {
83+
beforeEach(() => {
84+
// TODO M3-10003 - Remove mock once `limitsEvolution` feature flag is removed.
85+
mockAppendFeatureFlags({
86+
iamRbacPrimaryNavChanges: true,
87+
}).as('getFeatureFlags');
88+
});
89+
8290
/*
8391
* Confirm the visibility and status of the "Child account access" column for the following users:
8492
* - Unrestricted parent user (Enabled)
@@ -97,7 +105,7 @@ describe('Users landing page', () => {
97105
const mockUsers = initTestUsers(mockProfile, true);
98106

99107
// Navigate to Users & Grants page.
100-
cy.visitWithLogin('/account/users');
108+
cy.visitWithLogin('/users');
101109
cy.wait('@getUsers');
102110

103111
// Confirm that "Child account access" column is present
@@ -130,7 +138,7 @@ describe('Users landing page', () => {
130138
initTestUsers(mockProfile, true);
131139

132140
// Navigate to Users & Grants page.
133-
cy.visitWithLogin('/account/users');
141+
cy.visitWithLogin('/users');
134142

135143
// Confirm that "Child account access" column is present
136144
cy.findByText('Child Account Access').should('be.visible');
@@ -146,7 +154,7 @@ describe('Users landing page', () => {
146154
initTestUsers(mockProfile, false);
147155

148156
// Navigate to Users & Grants page.
149-
cy.visitWithLogin('/account/users');
157+
cy.visitWithLogin('/users');
150158

151159
// Confirm that "Child account access" column is not present
152160
cy.findByText('Child Account Access').should('not.exist');
@@ -161,7 +169,7 @@ describe('Users landing page', () => {
161169
initTestUsers(mockProfile, false);
162170

163171
// Navigate to Users & Grants page.
164-
cy.visitWithLogin('/account/users');
172+
cy.visitWithLogin('/users');
165173
cy.wait('@getUsers');
166174

167175
// Confirm that "Child account access" column is not present
@@ -178,7 +186,7 @@ describe('Users landing page', () => {
178186
initTestUsers(mockProfile, false);
179187

180188
// Navigate to Users & Grants page.
181-
cy.visitWithLogin('/account/users');
189+
cy.visitWithLogin('/users');
182190
cy.wait('@getUsers');
183191

184192
// Confirm that "Child account access" column is not present
@@ -195,7 +203,7 @@ describe('Users landing page', () => {
195203
initTestUsers(mockProfile, false);
196204

197205
// Navigate to Users & Grants page.
198-
cy.visitWithLogin('/account/users');
206+
cy.visitWithLogin('/users');
199207
cy.wait('@getUsers');
200208

201209
// Confirm that "Child account access" column is not present
@@ -224,7 +232,7 @@ describe('Users landing page', () => {
224232
mockGetProfile(mockProfile);
225233

226234
// Navigate to Users & Grants page.
227-
cy.visitWithLogin('/account/users');
235+
cy.visitWithLogin('/users');
228236
cy.wait('@getUsers');
229237

230238
// Confirm the "Parent User Settings" and "User Settings" sections are not present.
@@ -236,7 +244,7 @@ describe('Users landing page', () => {
236244
* Confirm the Users & Grants and User Permissions pages flow for a child account viewing a proxy user.
237245
* Confirm that "Parent User Settings" and "User Settings" sections are present on the Users & Grants page.
238246
* Confirm that proxy accounts are listed under "Parent User Settings".
239-
* Confirm that clicking the "Manage Access" button navigates to the proxy user's User Permissions page at /account/users/:user/permissions.
247+
* Confirm that clicking the "Manage Access" button navigates to the proxy user's User Permissions page at /users/:user/permissions.
240248
*/
241249
it('tests the users landing flow for a child account viewing a proxy user', () => {
242250
const mockChildProfile = profileFactory.build({
@@ -267,7 +275,7 @@ describe('Users landing page', () => {
267275
mockGetUserGrants(mockRestrictedProxyUser.username, mockUserGrants);
268276

269277
// Navigate to Users & Grants page and confirm "Parent User Settings" and "User Settings" sections are visible.
270-
cy.visitWithLogin('/account/users');
278+
cy.visitWithLogin('/users');
271279
cy.wait('@getUsers');
272280
cy.findByText(`${PARENT_USER} Settings`).should('be.visible');
273281
cy.findByText('User Settings').should('be.visible');
@@ -288,10 +296,10 @@ describe('Users landing page', () => {
288296
});
289297
});
290298

291-
// Confirm button navigates to the proxy user's User Permissions page at /account/users/:user/permissions.
299+
// Confirm button navigates to the proxy user's User Permissions page at /users/:user/permissions.
292300
cy.url().should(
293301
'endWith',
294-
`/account/users/${mockRestrictedProxyUser.username}/permissions`
302+
`/users/${mockRestrictedProxyUser.username}/permissions`
295303
);
296304
});
297305

@@ -314,7 +322,7 @@ describe('Users landing page', () => {
314322
mockAddUser(newUser).as('addUser');
315323

316324
// Navigate to Users & Grants page, find mock user, click its "User Permissions" button.
317-
cy.visitWithLogin('/account/users');
325+
cy.visitWithLogin('/users');
318326
cy.wait('@getUsers');
319327

320328
// Confirm that the "Users & Grants" page initially lists the main user
@@ -449,7 +457,7 @@ describe('Users landing page', () => {
449457
mockAddUser(newUser).as('addUser');
450458

451459
// Navigate to Users & Grants page, find mock user, click its "User Permissions" button.
452-
cy.visitWithLogin('/account/users');
460+
cy.visitWithLogin('/users');
453461
cy.wait('@getUsers');
454462

455463
// Confirm that the "Users & Grants" page initially lists the main user
@@ -565,7 +573,7 @@ describe('Users landing page', () => {
565573
mockDeleteUser(additionalUser.username).as('deleteUser');
566574

567575
// Navigate to Users & Grants page, find mock user, click its "User Permissions" button.
568-
cy.visitWithLogin('/account/users');
576+
cy.visitWithLogin('/users');
569577
cy.wait('@getUsers');
570578

571579
mockGetUsers([mockUser]).as('getUsers');

packages/manager/cypress/e2e/core/billing/smoke-billing-activity.spec.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,7 @@ const navigateToBilling = () => {
4848
.find()
4949
.should('be.visible')
5050
.within(() => {
51-
cy.findByText('Billing & Contact Information')
52-
.should('be.visible')
53-
.click();
51+
cy.findByText('Billing').should('be.visible').click();
5452
});
5553

5654
cy.url().should('endWith', '/billing');

packages/manager/cypress/support/ui/constants.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -243,23 +243,6 @@ export const pages: Page[] = [
243243
name: 'Support/Tickets/Closed',
244244
url: `${routes.supportTicketsClosed}`,
245245
},
246-
{
247-
assertIsLoaded: () =>
248-
cy.findByText('Update Contact Information').should('be.visible'),
249-
goWithUI: [
250-
{
251-
go: () => {
252-
loadAppNoLogin(`/account/users`);
253-
cy.findByText('Username');
254-
waitDoubleRerender();
255-
cy.findByText('Billing Info').should('be.visible').click();
256-
},
257-
name: 'Tab',
258-
},
259-
],
260-
name: 'Account/Billing',
261-
url: `/billing`,
262-
},
263246
{
264247
assertIsLoaded: () =>
265248
cy.findByText('Backup Auto Enrollment').should('be.visible'),

packages/manager/src/components/PrimaryNav/PrimaryNav.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
281281
{
282282
display: 'Users & Grants',
283283
hide: isIAMEnabled,
284-
to: '/account/users',
284+
to: '/users',
285285
},
286286
{
287287
display: 'Identity & Access',

packages/manager/src/features/Account/AccountLanding.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { useAccount, useProfile } from '@linode/queries';
2+
import { NotFound } from '@linode/ui';
23
import {
34
Outlet,
45
useLocation,
@@ -46,13 +47,6 @@ export const AccountLanding = () => {
4647
const [isDrawerOpen, setIsDrawerOpen] = React.useState<boolean>(false);
4748
const sessionContext = React.useContext(switchAccountSessionContext);
4849

49-
// This is the default route for the account route, so we need to redirect to the billing tab but keep /account as legacy
50-
if (location.pathname === '/account') {
51-
navigate({
52-
to: '/account/billing',
53-
});
54-
}
55-
5650
const isAkamaiAccount = account?.billing_source === 'akamai';
5751
const isProxyUser = profile?.user_type === 'proxy';
5852
const isChildUser = profile?.user_type === 'child';
@@ -109,6 +103,16 @@ export const AccountLanding = () => {
109103
}
110104
}, [match.routeId, showQuotasTab, navigate, iamRbacPrimaryNavChanges]);
111105

106+
// This is the default route for the account route, so we need to redirect to the billing tab but keep /account as legacy
107+
if (location.pathname === '/account') {
108+
if (iamRbacPrimaryNavChanges) {
109+
return <NotFound />;
110+
}
111+
navigate({
112+
to: '/account/billing',
113+
});
114+
}
115+
112116
const handleAccountSwitch = () => {
113117
if (isParentTokenExpired) {
114118
return sessionContext.updateState({
@@ -145,7 +149,7 @@ export const AccountLanding = () => {
145149
if (!isAkamaiAccount) {
146150
landingHeaderProps.onButtonClick = () =>
147151
navigate({
148-
to: '/account/billing',
152+
to: iamRbacPrimaryNavChanges ? '/billing' : '/account/billing',
149153
search: { action: 'make-payment' },
150154
});
151155
}

packages/manager/src/features/TopMenu/UserMenu/UserMenuPopover.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const UserMenuPopover = (props: UserMenuPopoverProps) => {
9292
const accountLinks: MenuLink[] = React.useMemo(
9393
() => [
9494
{
95-
display: 'Billing & Contact Information',
95+
display: 'Billing',
9696
to: flags?.iamRbacPrimaryNavChanges ? '/billing' : '/account/billing',
9797
},
9898
{
@@ -103,7 +103,9 @@ export const UserMenuPopover = (props: UserMenuPopoverProps) => {
103103
to:
104104
flags?.iamRbacPrimaryNavChanges && isIAMEnabled
105105
? '/iam'
106-
: '/account/users',
106+
: flags?.iamRbacPrimaryNavChanges && !isIAMEnabled
107+
? '/users'
108+
: '/account/users',
107109
isBeta: flags?.iamRbacPrimaryNavChanges && isIAMEnabled,
108110
},
109111
{

packages/manager/src/features/Users/CreateUserDrawer.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import { useNavigate } from '@tanstack/react-router';
1111
import * as React from 'react';
1212

13+
import { useFlags } from 'src/hooks/useFlags';
1314
import { getAPIErrorOrDefault } from 'src/utilities/errorUtils';
1415
import { getAPIErrorFor } from 'src/utilities/getAPIErrorFor';
1516

@@ -18,6 +19,7 @@ import type { APIError } from '@linode/api-v4/lib/types';
1819
import type { UseNavigateResult } from '@tanstack/react-router';
1920

2021
interface CreateUserDrawerProps {
22+
iamRbacPrimaryNavChanges?: boolean;
2123
navigate: UseNavigateResult<'/account/users'>;
2224
onClose: () => void;
2325
open: boolean;
@@ -37,7 +39,14 @@ const withNavigation = (
3739
) => {
3840
return (props: CreateUserDrawerProps) => {
3941
const navigate = useNavigate();
40-
return <WrappedComponent {...props} navigate={navigate} />;
42+
const { iamRbacPrimaryNavChanges } = useFlags();
43+
return (
44+
<WrappedComponent
45+
{...props}
46+
iamRbacPrimaryNavChanges={iamRbacPrimaryNavChanges}
47+
navigate={navigate}
48+
/>
49+
);
4150
};
4251
};
4352

@@ -88,7 +97,7 @@ class CreateUserDrawerComponent extends React.Component<
8897
};
8998

9099
onSubmit = () => {
91-
const { onClose, refetch, navigate } = this.props;
100+
const { onClose, refetch, navigate, iamRbacPrimaryNavChanges } = this.props;
92101
const { email, restricted, username } = this.state;
93102
this.setState({ errors: [], submitting: true });
94103
createUser({ email, restricted, username })
@@ -97,7 +106,9 @@ class CreateUserDrawerComponent extends React.Component<
97106
onClose();
98107
if (user.restricted) {
99108
navigate({
100-
to: '/account/users/$username/permissions',
109+
to: iamRbacPrimaryNavChanges
110+
? '/users/$username/permissions'
111+
: '/account/users/$username/permissions',
101112
params: { username: user.username },
102113
});
103114
}

0 commit comments

Comments
 (0)