From fc7e5d79dd5076b524a4df848d973496ead63247 Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Fri, 16 Feb 2024 15:47:26 -0500 Subject: [PATCH 1/3] refactor: [M3-7786] - Convert isRestrictedGlobalGrantType to hook --- .../manager/src/features/Account/utils.ts | 40 +---------- .../NodeBalancers/NodeBalancerCreate.tsx | 13 ++-- .../NodeBalancersLanding.tsx | 14 +--- .../NodeBalancersLandingEmptyState.tsx | 13 +--- .../useRestrictedGlobalGrantCheck.test.ts | 69 +++++++++++++++++++ .../hooks/useRestrictedGlobalGrantCheck.ts | 31 +++++++++ 6 files changed, 112 insertions(+), 68 deletions(-) create mode 100644 packages/manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts create mode 100644 packages/manager/src/hooks/useRestrictedGlobalGrantCheck.ts diff --git a/packages/manager/src/features/Account/utils.ts b/packages/manager/src/features/Account/utils.ts index de9b38a4699..0d44ba94697 100644 --- a/packages/manager/src/features/Account/utils.ts +++ b/packages/manager/src/features/Account/utils.ts @@ -3,8 +3,6 @@ import { getStorage, setStorage } from 'src/utilities/storage'; import type { GlobalGrantTypes, GrantLevel, - Grants, - Profile, Token, UserType, } from '@linode/api-v4'; @@ -18,17 +16,12 @@ interface GetRestrictedResourceText { resourceType: GrantTypeMap; } -interface GrantsProfileSchema { - grants: Grants | undefined; - profile: Profile | undefined; -} - -interface AccountAccessGrant extends GrantsProfileSchema { +interface AccountAccessGrant { globalGrantType: 'account_access'; permittedGrantLevel: GrantLevel; } -interface NonAccountAccessGrant extends GrantsProfileSchema { +interface NonAccountAccessGrant { globalGrantType: Exclude; permittedGrantLevel?: GrantLevel; } @@ -67,35 +60,6 @@ export const getAccessRestrictedText = ( } to request the necessary permission.`; }; -/** - * Determine whether the user has restricted access to a specific resource. - * - * @example - * // If account access does not equal 'read_write', the user has restricted access. - * isRestrictedGlobalGrantType({ - * globalGrantType: 'account_access', - * permittedGrantLevel: 'read_write', - * grants, - * profile - * }); - * // Returns: true - */ -export const isRestrictedGlobalGrantType = ({ - globalGrantType, - grants, - permittedGrantLevel, - profile, -}: RestrictedGlobalGrantType): boolean => { - if (globalGrantType !== 'account_access') { - return Boolean(profile?.restricted) && !grants?.global[globalGrantType]; - } - - return ( - Boolean(profile?.restricted) && - grants?.global[globalGrantType] !== permittedGrantLevel - ); -}; - // TODO: Parent/Child: FOR MSW ONLY, REMOVE WHEN API IS READY // ================================================================ // const mockExpiredTime = diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancerCreate.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancerCreate.tsx index 5cf8dadbeea..07ca7deb82a 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancerCreate.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancerCreate.tsx @@ -30,18 +30,16 @@ import { Tag, TagsInput } from 'src/components/TagsInput/TagsInput'; import { TextField } from 'src/components/TextField'; import { Typography } from 'src/components/Typography'; import { FIREWALL_GET_STARTED_LINK } from 'src/constants'; -import { - getRestrictedResourceText, - isRestrictedGlobalGrantType, -} from 'src/features/Account/utils'; +import { getRestrictedResourceText } from 'src/features/Account/utils'; import { useFlags } from 'src/hooks/useFlags'; +import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; import { reportAgreementSigningError, useAccountAgreements, useMutateAccountAgreements, } from 'src/queries/accountAgreements'; import { useNodebalancerCreateMutation } from 'src/queries/nodebalancers'; -import { useGrants, useProfile } from 'src/queries/profile'; +import { useProfile } from 'src/queries/profile'; import { useRegionsQuery } from 'src/queries/regions'; import { sendCreateNodeBalancerEvent } from 'src/utilities/analytics'; import { getAPIErrorOrDefault } from 'src/utilities/errorUtils'; @@ -94,7 +92,6 @@ const defaultFieldsStates = { const NodeBalancerCreate = () => { const flags = useFlags(); const { data: agreements } = useAccountAgreements(); - const { data: grants } = useGrants(); const { data: profile } = useProfile(); const { data: regions } = useRegionsQuery(); @@ -130,10 +127,8 @@ const NodeBalancerCreate = () => { const theme = useTheme(); const matchesSmDown = useMediaQuery(theme.breakpoints.down('md')); - const isRestricted = isRestrictedGlobalGrantType({ + const isRestricted = useRestrictedGlobalGrantCheck({ globalGrantType: 'add_nodebalancers', - grants, - profile, }); const addNodeBalancer = () => { diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.tsx index 4ae72c1f46a..0626eca8bab 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLanding.tsx @@ -14,14 +14,11 @@ import { TableHead } from 'src/components/TableHead'; import { TableRow } from 'src/components/TableRow'; import { TableSortCell } from 'src/components/TableSortCell/TableSortCell'; import { TransferDisplay } from 'src/components/TransferDisplay/TransferDisplay'; -import { - getRestrictedResourceText, - isRestrictedGlobalGrantType, -} from 'src/features/Account/utils'; +import { getRestrictedResourceText } from 'src/features/Account/utils'; import { useOrder } from 'src/hooks/useOrder'; import { usePagination } from 'src/hooks/usePagination'; +import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; import { useNodeBalancersQuery } from 'src/queries/nodebalancers'; -import { useGrants, useProfile } from 'src/queries/profile'; import { NodeBalancerDeleteDialog } from '../NodeBalancerDeleteDialog'; import { NodeBalancerTableRow } from './NodeBalancerTableRow'; @@ -39,13 +36,8 @@ export const NodeBalancersLanding = () => { const history = useHistory(); const pagination = usePagination(1, preferenceKey); - const { data: grants } = useGrants(); - const { data: profile } = useProfile(); - - const isRestricted = isRestrictedGlobalGrantType({ + const isRestricted = useRestrictedGlobalGrantCheck({ globalGrantType: 'add_nodebalancers', - grants, - profile, }); const { handleOrderChange, order, orderBy } = useOrder( diff --git a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLandingEmptyState.tsx b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLandingEmptyState.tsx index e839141075d..5b941b51098 100644 --- a/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLandingEmptyState.tsx +++ b/packages/manager/src/features/NodeBalancers/NodeBalancersLanding/NodeBalancersLandingEmptyState.tsx @@ -7,21 +7,14 @@ import { DocumentTitleSegment } from 'src/components/DocumentTitle'; import { Link } from 'src/components/Link'; import { Placeholder } from 'src/components/Placeholder/Placeholder'; import { Typography } from 'src/components/Typography'; -import { - getRestrictedResourceText, - isRestrictedGlobalGrantType, -} from 'src/features/Account/utils'; -import { useGrants, useProfile } from 'src/queries/profile'; +import { getRestrictedResourceText } from 'src/features/Account/utils'; +import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; export const NodeBalancerLandingEmptyState = () => { const history = useHistory(); - const { data: grants } = useGrants(); - const { data: profile } = useProfile(); - const isRestricted = isRestrictedGlobalGrantType({ + const isRestricted = useRestrictedGlobalGrantCheck({ globalGrantType: 'add_nodebalancers', - grants, - profile, }); return ( diff --git a/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts b/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts new file mode 100644 index 00000000000..36e7767ab23 --- /dev/null +++ b/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts @@ -0,0 +1,69 @@ +import { renderHook } from '@testing-library/react-hooks'; + +import { useRestrictedGlobalGrantCheck } from './useRestrictedGlobalGrantCheck'; + +const queryMocks = vi.hoisted(() => ({ + useGrants: vi.fn().mockReturnValue({}), + useProfile: vi.fn().mockReturnValue({}), +})); + +vi.mock('src/queries/profile', async () => { + const actual = await vi.importActual('src/queries/profile'); + return { + ...actual, + useGrants: queryMocks.useGrants, + useProfile: queryMocks.useProfile, + }; +}); + +describe('useRestrictedGlobalGrantCheck', () => { + it('returns true for restricted access with non-permitted grant level', () => { + queryMocks.useGrants.mockReturnValue({ + data: { global: { account_access: 'read_only' } }, + }); + queryMocks.useProfile.mockReturnValue({ data: { restricted: true } }); + + const { result } = renderHook(() => + useRestrictedGlobalGrantCheck({ + globalGrantType: 'account_access', + permittedGrantLevel: 'read_write', + }) + ); + + expect(result.current).toBe(true); + }); + + it('returns false for unrestricted access with permitted grant level', () => { + queryMocks.useGrants.mockReturnValue({ + data: { global: { account_access: 'read_write' } }, + }); + queryMocks.useProfile.mockReturnValue({ data: { restricted: false } }); + + const { result } = renderHook(() => + useRestrictedGlobalGrantCheck({ + globalGrantType: 'account_access', + permittedGrantLevel: 'read_write', + }) + ); + + expect(result.current).toBe(false); + }); + + it('returns true for restricted access to non-account_access resource', () => { + queryMocks.useGrants.mockReturnValue({ + data: { global: { add_linodes: false } }, + }); + queryMocks.useProfile.mockReturnValue({ data: { restricted: true } }); + + const { result } = renderHook(() => + useRestrictedGlobalGrantCheck({ + globalGrantType: 'add_linodes', + permittedGrantLevel: 'read_write', // This param is ignored for non-account_access types + }) + ); + + expect(result.current).toBe(true); + }); + + // Add more tests as needed to cover different scenarios and edge cases +}); diff --git a/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.ts b/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.ts new file mode 100644 index 00000000000..56c6c6ee34d --- /dev/null +++ b/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.ts @@ -0,0 +1,31 @@ +import { useGrants, useProfile } from 'src/queries/profile'; + +import type { RestrictedGlobalGrantType } from 'src/features/Account/utils'; + +/** + * Determine whether the user has restricted access to a specific resource. + * + * @example + * // If account access does not equal 'read_write', the user has restricted access. + * useRestrictedGlobalGrantCheck({ + * globalGrantType: 'account_access', + * permittedGrantLevel: 'read_write', + * }); + * // Returns: true + */ +export const useRestrictedGlobalGrantCheck = ({ + globalGrantType, + permittedGrantLevel, +}: RestrictedGlobalGrantType): boolean => { + const { data: grants } = useGrants(); + const { data: profile } = useProfile(); + + if (globalGrantType !== 'account_access') { + return Boolean(profile?.restricted) && !grants?.global[globalGrantType]; + } + + return ( + Boolean(profile?.restricted) && + grants?.global[globalGrantType] !== permittedGrantLevel + ); +}; From 82e968ad960cf4da3dd7864a294ad064b2c6ab6d Mon Sep 17 00:00:00 2001 From: Jaalah Ramos Date: Fri, 16 Feb 2024 16:42:22 -0500 Subject: [PATCH 2/3] Added changeset: Convert isRestrictedGlobalGrantType to Hook --- .../.changeset/pr-10203-tech-stories-1708119742778.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-10203-tech-stories-1708119742778.md diff --git a/packages/manager/.changeset/pr-10203-tech-stories-1708119742778.md b/packages/manager/.changeset/pr-10203-tech-stories-1708119742778.md new file mode 100644 index 00000000000..9bde62beef8 --- /dev/null +++ b/packages/manager/.changeset/pr-10203-tech-stories-1708119742778.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +Convert isRestrictedGlobalGrantType to Hook ([#10203](https://github.com/linode/manager/pull/10203)) From a499f5c194d1f0a05bfc004b01d645b6930d2bca Mon Sep 17 00:00:00 2001 From: Jaalah Ramos <125309814+jaalah-akamai@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:52:01 -0500 Subject: [PATCH 3/3] Update packages/manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts Co-authored-by: Banks Nussman <115251059+bnussman-akamai@users.noreply.github.com> --- .../manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts b/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts index 36e7767ab23..b3ffd123797 100644 --- a/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts +++ b/packages/manager/src/hooks/useRestrictedGlobalGrantCheck.test.ts @@ -1,4 +1,4 @@ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useRestrictedGlobalGrantCheck } from './useRestrictedGlobalGrantCheck';