Skip to content

Commit 07a5ee4

Browse files
fix: [UIE-10308] - IAM Parent/Child - SwitchAccount Drawer: hide search if no child account (#13464)
* hide search if no child account * better condition * Added changeset: IAM Parent/Child - SwitchAccount Drawer: hide search if no child account * test fix * feedback
1 parent 7bf9ed9 commit 07a5ee4

6 files changed

Lines changed: 207 additions & 140 deletions

File tree

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+
IAM Parent/Child - SwitchAccount Drawer: hide search if no child account ([#13464](https://github.com/linode/manager/pull/13464))
Lines changed: 10 additions & 0 deletions
Loading

packages/manager/src/features/Account/SwitchAccountDrawer.test.tsx

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,31 @@ import { SwitchAccountDrawer } from './SwitchAccountDrawer';
99

1010
const queryMocks = vi.hoisted(() => ({
1111
useProfile: vi.fn().mockReturnValue({}),
12-
useGetListMyDelegatedChildAccountsQuery: vi.fn().mockReturnValue({}),
12+
useMyDelegatedChildAccountsQuery: vi.fn().mockReturnValue({}),
13+
useChildAccountsInfiniteQuery: vi.fn().mockReturnValue({}),
14+
useIsIAMDelegationEnabled: vi
15+
.fn()
16+
.mockReturnValue({ isIAMDelegationEnabled: true }),
1317
}));
1418

1519
vi.mock('@linode/queries', async () => {
1620
const actual = await vi.importActual('@linode/queries');
1721
return {
1822
...actual,
1923
useProfile: queryMocks.useProfile,
20-
useGetListMyDelegatedChildAccountsQuery:
21-
queryMocks.useGetListMyDelegatedChildAccountsQuery,
24+
useMyDelegatedChildAccountsQuery:
25+
queryMocks.useMyDelegatedChildAccountsQuery,
26+
useChildAccountsInfiniteQuery: queryMocks.useChildAccountsInfiniteQuery,
27+
};
28+
});
29+
30+
vi.mock('src/features/IAM/hooks/useIsIAMEnabled', async () => {
31+
const actual = await vi.importActual(
32+
'src/features/IAM/hooks/useIsIAMEnabled'
33+
);
34+
return {
35+
...actual,
36+
useIsIAMDelegationEnabled: queryMocks.useIsIAMDelegationEnabled,
2237
};
2338
});
2439

@@ -29,16 +44,34 @@ const props = {
2944
};
3045

3146
describe('SwitchAccountDrawer', () => {
47+
const accounts = accountFactory.buildList(5, {
48+
company: 'Test Account 1',
49+
});
50+
3251
beforeEach(() => {
3352
queryMocks.useProfile.mockReturnValue({});
34-
queryMocks.useGetListMyDelegatedChildAccountsQuery.mockReturnValue({
35-
data: accountFactory.buildList(5, {
36-
company: 'Test Account 1',
37-
euuid: '123',
38-
}),
53+
queryMocks.useIsIAMDelegationEnabled.mockReturnValue({
54+
isIAMDelegationEnabled: true,
55+
});
56+
queryMocks.useMyDelegatedChildAccountsQuery.mockReturnValue({
57+
data: { data: accounts, results: accounts.length, page: 1, pages: 1 },
3958
isLoading: false,
4059
isRefetching: false,
4160
});
61+
queryMocks.useChildAccountsInfiniteQuery.mockReturnValue({
62+
data: {
63+
pages: [
64+
{ data: accounts, results: accounts.length, page: 1, pages: 1 },
65+
],
66+
pageParams: [],
67+
},
68+
isInitialLoading: false,
69+
isRefetching: false,
70+
isFetchingNextPage: false,
71+
hasNextPage: false,
72+
fetchNextPage: vi.fn(),
73+
refetch: vi.fn(),
74+
});
4275
});
4376

4477
it('should have a title', () => {
@@ -94,4 +127,20 @@ describe('SwitchAccountDrawer', () => {
94127
expect(props.onClose).toHaveBeenCalledTimes(1);
95128
});
96129
});
130+
131+
it('should display an empty state when no child accounts are found', async () => {
132+
queryMocks.useMyDelegatedChildAccountsQuery.mockReturnValue({
133+
data: { data: [], results: 0, page: 1, pages: 1 },
134+
isLoading: false,
135+
isRefetching: false,
136+
});
137+
const { getByText } = renderWithTheme(<SwitchAccountDrawer {...props} />);
138+
139+
expect(getByText('You don’t have access to other accounts.')).toBeVisible();
140+
expect(
141+
getByText(
142+
'You must be added to a delegation by an account administrator to have access to other accounts.'
143+
)
144+
).toBeVisible();
145+
});
97146
});

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

Lines changed: 133 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
useMyDelegatedChildAccountsQuery,
44
} from '@linode/queries';
55
import {
6+
Box,
67
Button,
78
Drawer,
89
LinkButton,
@@ -14,6 +15,7 @@ import {
1415
import React, { useMemo, useState } from 'react';
1516

1617
import ErrorStateCloud from 'src/assets/icons/error-state-cloud.svg';
18+
import NoResultsState from 'src/assets/icons/no-results-state.svg';
1719
import { DebouncedSearchTextField } from 'src/components/DebouncedSearchTextField';
1820
import { useParentChildAuthentication } from 'src/features/Account/SwitchAccounts/useParentChildAuthentication';
1921
import { useSwitchToParentAccount } from 'src/features/Account/SwitchAccounts/useSwitchToParentAccount';
@@ -217,6 +219,7 @@ export const SwitchAccountDrawer = (props: Props) => {
217219
const hasError = isIAMDelegationEnabled
218220
? delegatedChildAccountsError
219221
: childAccountInfiniteError;
222+
220223
return (
221224
<Drawer onClose={handleClose} open={open} title="Switch Account">
222225
{createTokenErrorReason && (
@@ -225,116 +228,144 @@ export const SwitchAccountDrawer = (props: Props) => {
225228
{isParentTokenError.length > 0 && (
226229
<Notice text={isParentTokenError[0].reason} variant="error" />
227230
)}
228-
<Typography
229-
sx={(theme) => ({
230-
margin: `${theme.spacingFunction(24)} 0`,
231-
})}
232-
>
233-
Select an account to view and manage its settings and configurations
234-
{isProxyOrDelegateUserType && (
235-
<>
236-
{' or '}
237-
<LinkButton
238-
aria-label="parent-account-link"
239-
disabled={isSubmitting}
240-
onClick={() => {
241-
sendSwitchToParentAccountEvent();
242-
handleSwitchToParentAccount();
243-
}}
244-
>
245-
switch back to your account
246-
</LinkButton>
247-
</>
248-
)}
249-
.
250-
</Typography>
251-
252-
{hasError ? (
253-
<Stack alignItems="center" gap={1} justifyContent="center">
254-
<ErrorStateCloud />
255-
<Typography>Unable to load data.</Typography>
256-
<Typography>
257-
Try again or contact support if the issue persists.
231+
{childAccounts &&
232+
childAccounts.length === 0 &&
233+
!Object.prototype.hasOwnProperty.call(filter, 'company') ? (
234+
<Box alignItems="center" display="flex" flexDirection="column" mt={8}>
235+
<NoResultsState />
236+
<Typography sx={{ mt: 2, mb: 1 }} variant="h2">
237+
You don’t have access to other accounts.
238+
</Typography>
239+
<Typography sx={{ textAlign: 'center', maxWidth: 300 }}>
240+
You must be added to a delegation by an account administrator to
241+
have access to other accounts.
258242
</Typography>
259243
<Button
260-
buttonType="primary"
261-
onClick={() => refetchFn()}
262-
sx={(theme) => ({
263-
marginTop: theme.spacingFunction(16),
264-
})}
244+
buttonType="outlined"
245+
onClick={handleClose}
246+
sx={{ mt: 4, alignSelf: 'flex-end' }}
265247
>
266-
Try again
248+
Close
267249
</Button>
268-
</Stack>
250+
</Box>
269251
) : (
270252
<>
271-
<DebouncedSearchTextField
272-
clearable
273-
debounceTime={250}
274-
hideLabel
275-
key={`switch-search-${searchQuery}`}
276-
label="Search"
277-
onSearch={handleSearchQueryChange}
278-
placeholder="Search"
279-
sx={{ marginBottom: theme.spacingFunction(12) }}
280-
value={searchQuery}
281-
/>
282-
{searchQuery &&
283-
childAccounts &&
284-
childAccounts.length === 0 &&
285-
!isLoading && (
286-
<Typography
287-
sx={{
288-
fontStyle: 'italic',
289-
marginTop: theme.spacingFunction(6),
290-
}}
291-
>
292-
No search results
293-
</Typography>
253+
<Typography
254+
sx={(theme) => ({
255+
margin: `${theme.spacingFunction(24)} 0`,
256+
})}
257+
>
258+
Select an account to view and manage its settings and configurations
259+
{isProxyOrDelegateUserType && (
260+
<>
261+
{' or '}
262+
<LinkButton
263+
aria-label="parent-account-link"
264+
disabled={isSubmitting}
265+
onClick={() => {
266+
sendSwitchToParentAccountEvent();
267+
handleSwitchToParentAccount();
268+
}}
269+
>
270+
switch back to your account
271+
</LinkButton>
272+
</>
294273
)}
274+
.
275+
</Typography>
295276

296-
{isIAMDelegationEnabled && (
297-
<ChildAccountsTable
298-
childAccounts={childAccounts}
299-
currentTokenWithBearer={
300-
isProxyOrDelegateUserType
301-
? currentParentTokenWithBearer
302-
: currentTokenWithBearer
303-
}
304-
filter={filter}
305-
isLoading={isLoading}
306-
isSwitchingChildAccounts={isSwitchingChildAccounts}
307-
onClose={onClose}
308-
onPageChange={handlePageChange}
309-
onPageSizeChange={handlePageSizeChange}
310-
onSwitchAccount={handleSwitchToChildAccount}
311-
page={page}
312-
pageSize={pageSize}
313-
setIsSwitchingChildAccounts={setIsSwitchingChildAccounts}
314-
totalResults={delegatedChildAccounts?.results || 0}
315-
userType={userType}
316-
/>
317-
)}
318-
{!isIAMDelegationEnabled && (
319-
<ChildAccountList
320-
childAccounts={childAccounts}
321-
currentTokenWithBearer={
322-
isProxyOrDelegateUserType
323-
? currentParentTokenWithBearer
324-
: currentTokenWithBearer
325-
}
326-
fetchNextPage={fetchNextPage}
327-
filter={filter}
328-
hasNextPage={hasNextPage}
329-
isFetchingNextPage={isFetchingNextPage}
330-
isLoading={isLoading}
331-
isSwitchingChildAccounts={isSwitchingChildAccounts}
332-
onClose={onClose}
333-
onSwitchAccount={handleSwitchToChildAccount}
334-
refetchFn={refetchFn}
335-
setIsSwitchingChildAccounts={setIsSwitchingChildAccounts}
336-
userType={userType}
337-
/>
277+
{hasError ? (
278+
<Stack alignItems="center" gap={1} justifyContent="center">
279+
<ErrorStateCloud />
280+
<Typography>Unable to load data.</Typography>
281+
<Typography>
282+
Try again or contact support if the issue persists.
283+
</Typography>
284+
<Button
285+
buttonType="primary"
286+
onClick={() => refetchFn()}
287+
sx={(theme) => ({
288+
marginTop: theme.spacingFunction(16),
289+
})}
290+
>
291+
Try again
292+
</Button>
293+
</Stack>
294+
) : (
295+
<>
296+
{((childAccounts && childAccounts.length !== 0) ||
297+
searchQuery) && (
298+
<DebouncedSearchTextField
299+
clearable
300+
debounceTime={250}
301+
hideLabel
302+
key={`switch-search-${searchQuery}`}
303+
label="Search"
304+
loading={isLoading}
305+
onSearch={handleSearchQueryChange}
306+
placeholder="Search"
307+
sx={{ marginBottom: theme.spacingFunction(12) }}
308+
value={searchQuery}
309+
/>
310+
)}
311+
{isIAMDelegationEnabled &&
312+
searchQuery &&
313+
childAccounts &&
314+
childAccounts.length === 0 &&
315+
!isLoading && (
316+
<Typography
317+
sx={{
318+
fontStyle: 'italic',
319+
marginTop: theme.spacingFunction(6),
320+
}}
321+
>
322+
No search results
323+
</Typography>
324+
)}
325+
326+
{isIAMDelegationEnabled && (
327+
<ChildAccountsTable
328+
childAccounts={childAccounts}
329+
currentTokenWithBearer={
330+
isProxyOrDelegateUserType
331+
? currentParentTokenWithBearer
332+
: currentTokenWithBearer
333+
}
334+
isLoading={isLoading}
335+
isSwitchingChildAccounts={isSwitchingChildAccounts}
336+
onClose={onClose}
337+
onPageChange={handlePageChange}
338+
onPageSizeChange={handlePageSizeChange}
339+
onSwitchAccount={handleSwitchToChildAccount}
340+
page={page}
341+
pageSize={pageSize}
342+
setIsSwitchingChildAccounts={setIsSwitchingChildAccounts}
343+
totalResults={delegatedChildAccounts?.results || 0}
344+
userType={userType}
345+
/>
346+
)}
347+
{!isIAMDelegationEnabled && (
348+
<ChildAccountList
349+
childAccounts={childAccounts}
350+
currentTokenWithBearer={
351+
isProxyOrDelegateUserType
352+
? currentParentTokenWithBearer
353+
: currentTokenWithBearer
354+
}
355+
fetchNextPage={fetchNextPage}
356+
filter={filter}
357+
hasNextPage={hasNextPage}
358+
isFetchingNextPage={isFetchingNextPage}
359+
isLoading={isLoading}
360+
isSwitchingChildAccounts={isSwitchingChildAccounts}
361+
onClose={onClose}
362+
onSwitchAccount={handleSwitchToChildAccount}
363+
refetchFn={refetchFn}
364+
setIsSwitchingChildAccounts={setIsSwitchingChildAccounts}
365+
userType={userType}
366+
/>
367+
)}
368+
</>
338369
)}
339370
</>
340371
)}

0 commit comments

Comments
 (0)