Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tech Stories
---

IAM - Clean up beta flag + BETA/LA logic ([#13266](https://github.com/linode/manager/pull/13266))
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const queryString = 'menu-item-Managed';

const queryMocks = vi.hoisted(() => ({
useIsIAMEnabled: vi.fn(() => ({
isIAMBeta: false,
isIAMEnabled: false,
})),
usePreferences: vi.fn().mockReturnValue({}),
Expand Down Expand Up @@ -497,7 +496,6 @@ describe('PrimaryNav', () => {
it('should show Administration links', async () => {
const flags: Partial<Flags> = {
iam: {
beta: true,
enabled: true,
},
limitsEvolution: {
Expand All @@ -508,7 +506,6 @@ describe('PrimaryNav', () => {
};

queryMocks.useIsIAMEnabled.mockReturnValue({
isIAMBeta: true,
isIAMEnabled: true,
});

Expand Down Expand Up @@ -544,13 +541,11 @@ describe('PrimaryNav', () => {
it('should hide Identity & Access link for non beta users', async () => {
const flags: Partial<Flags> = {
iam: {
beta: true,
enabled: false,
},
};

queryMocks.useIsIAMEnabled.mockReturnValue({
isIAMBeta: true,
isIAMEnabled: false,
});

Expand Down
6 changes: 2 additions & 4 deletions packages/manager/src/components/PrimaryNav/PrimaryNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
const { isPlacementGroupsEnabled } = useIsPlacementGroupsEnabled();
const { isDatabasesEnabled, isDatabasesV2Beta } = useIsDatabasesEnabled();

const { isIAMBeta, isIAMEnabled } = useIsIAMEnabled();
const { isIAMEnabled } = useIsIAMEnabled();
const showLimitedAvailabilityBadges = flags.iamLimitedAvailabilityBadges;

const { isNetworkLoadBalancerEnabled } = useIsNetworkLoadBalancerEnabled();
Expand Down Expand Up @@ -297,8 +297,7 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
display: 'Identity & Access',
hide: !isIAMEnabled,
to: '/iam',
isBeta: isIAMBeta,
isNew: !isIAMBeta && showLimitedAvailabilityBadges,
isNew: isIAMEnabled && showLimitedAvailabilityBadges,
},
{
display: 'Quotas',
Expand Down Expand Up @@ -352,7 +351,6 @@ export const PrimaryNav = (props: PrimaryNavProps) => {
isACLPEnabled,
isACLPLogsBeta,
isACLPLogsEnabled,
isIAMBeta,
isIAMEnabled,
isMarketplaceV2FeatureEnabled,
isNetworkLoadBalancerEnabled,
Expand Down
2 changes: 1 addition & 1 deletion packages/manager/src/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ export interface Flags {
gecko2: GeckoFeatureFlag;
generationalPlansv2: GenerationalPlansFlag;
gpuv2: GpuV2;
iam: BetaFeatureFlag;
iam: BaseFeatureFlag;
iamDelegation: BaseFeatureFlag;
iamLimitedAvailabilityBadges: boolean;
ipv6Sharing: boolean;
Expand Down
4 changes: 2 additions & 2 deletions packages/manager/src/features/IAM/IAMLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { IAM_DOCS_LINK, ROLES_LEARN_MORE_LINK } from './Shared/constants';

export const IdentityAccessLanding = React.memo(() => {
const flags = useFlags();
const { isIAMBeta, isIAMEnabled } = useIsIAMEnabled();
const { isIAMEnabled } = useIsIAMEnabled();
const showLimitedAvailabilityBadges =
flags.iamLimitedAvailabilityBadges && isIAMEnabled && !isIAMBeta;
flags.iamLimitedAvailabilityBadges && isIAMEnabled;
const location = useLocation();
const navigate = useNavigate();
const { isParentAccount } = useDelegationRole();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ export const DefaultsLanding = () => {
const location = useLocation();
const navigate = useNavigate();
const flags = useFlags();
const { isIAMBeta, isIAMEnabled } = useIsIAMEnabled();
const { isIAMEnabled } = useIsIAMEnabled();
const showLimitedAvailabilityBadges =
flags.iamLimitedAvailabilityBadges && isIAMEnabled && !isIAMBeta;
flags.iamLimitedAvailabilityBadges && isIAMEnabled;

const { tabs, tabIndex, handleTabChange } = useTabs([
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import {

export const UserDetailsLanding = () => {
const flags = useFlags();
const { isIAMBeta, isIAMEnabled } = useIsIAMEnabled();
const { isIAMEnabled } = useIsIAMEnabled();
const showLimitedAvailabilityBadges =
flags.iamLimitedAvailabilityBadges && isIAMEnabled && !isIAMBeta;
flags.iamLimitedAvailabilityBadges && isIAMEnabled;
const { username } = useParams({ from: '/iam/users/$username' });
const { isIAMDelegationEnabled } = useIsIAMDelegationEnabled();
const { isParentAccount } = useDelegationRole();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@ import {

import { entityPermissionMapFrom } from './adapters/permissionAdapters';
import { useIsIAMEnabled } from './useIsIAMEnabled';
import {
BETA_ACCESS_TYPE_SCOPE,
LA_ACCOUNT_ADMIN_PERMISSIONS_TO_EXCLUDE,
} from './usePermissions';

import type {
APIError,
Expand Down Expand Up @@ -115,7 +111,7 @@ export const useGetAllUserEntitiesByPermission = <T extends FullEntityType>({
filter = {},
params = {},
}: UseGetEntitiesByPermissionProps) => {
const { isIAMBeta, isIAMEnabled, profile } = useIsIAMEnabled();
const { isIAMEnabled, profile } = useIsIAMEnabled();

// Get entities by permission (Restricted IAM users only)
const {
Expand All @@ -129,24 +125,11 @@ export const useGetAllUserEntitiesByPermission = <T extends FullEntityType>({
enabled: enabled && isIAMEnabled,
});

/**
* Determine if we should use IAM permissions or legacy permissions
*/
const useBetaPermissions =
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yay! x2

isIAMEnabled &&
isIAMBeta &&
BETA_ACCESS_TYPE_SCOPE.includes(entityType) &&
LA_ACCOUNT_ADMIN_PERMISSIONS_TO_EXCLUDE.some((blacklistedPermission) =>
permission.includes(blacklistedPermission)
) === false;
const useLAPermissions = isIAMEnabled && !isIAMBeta;
const shouldUseIAMPermissions = useBetaPermissions || useLAPermissions;

/**
* Extract entity IDs from the entities by permission data
*/
const entityIds =
shouldUseIAMPermissions && profile?.restricted
isIAMEnabled && profile?.restricted
? entitiesByPermission?.map((e) => e.id)
: undefined;

Expand All @@ -159,7 +142,7 @@ export const useGetAllUserEntitiesByPermission = <T extends FullEntityType>({
* Legacy grants for non-IAM users
*/
const { data: grants, isLoading: grantsLoading } = useGrants(
!shouldUseIAMPermissions && profile?.restricted && enabled
!isIAMEnabled && profile?.restricted && enabled
);

/**
Expand All @@ -168,7 +151,7 @@ export const useGetAllUserEntitiesByPermission = <T extends FullEntityType>({
* In case a filter was used, we also return it to be used for client-side filtering
* ex: we also pass this filter to the LinodeSelect to avoid caching two different queries (with and without filter)
*/
if (shouldUseIAMPermissions) {
if (isIAMEnabled) {
return {
...entityQuery,
filter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ describe('useIsIAMEnabled', () => {
});

await waitFor(() => {
expect(result.current.isIAMBeta).toBe(true);
expect(result.current.isIAMEnabled).toBe(true);
});
});
Expand All @@ -67,8 +66,6 @@ describe('useIsIAMEnabled', () => {
});

await waitFor(() => {
expect(result.current.isIAMBeta).toBe(false);
// eslint-disable-next-line testing-library/no-wait-for-multiple-assertions
expect(result.current.isIAMEnabled).toBe(true);
// eslint-disable-next-line testing-library/no-wait-for-multiple-assertions
expect(queryMocks.useUserAccountPermissions).toHaveBeenCalledWith(true);
Expand All @@ -94,8 +91,6 @@ describe('useIsIAMEnabled', () => {
});

await waitFor(() => {
expect(result.current.isIAMBeta).toBe(false);
// eslint-disable-next-line testing-library/no-wait-for-multiple-assertions
expect(result.current.isIAMEnabled).toBe(false);
// eslint-disable-next-line testing-library/no-wait-for-multiple-assertions
expect(queryMocks.useUserAccountPermissions).toHaveBeenCalledWith(false);
Expand All @@ -120,8 +115,6 @@ describe('useIsIAMEnabled', () => {
});

await waitFor(() => {
expect(result.current.isIAMBeta).toBe(true);
// eslint-disable-next-line testing-library/no-wait-for-multiple-assertions
expect(result.current.isIAMEnabled).toBe(false);
// eslint-disable-next-line testing-library/no-wait-for-multiple-assertions
expect(queryMocks.useUserAccountPermissions).toHaveBeenCalledWith(true);
Expand Down
1 change: 0 additions & 1 deletion packages/manager/src/features/IAM/hooks/useIsIAMEnabled.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export const useIsIAMEnabled = () => {
useUserAccountPermissions(flags?.iam?.enabled === true);

return {
isIAMBeta: flags.iam?.beta,
isIAMEnabled: flags?.iam?.enabled && Boolean(roles || permissions),
isLoading: isLoadingRoles || isLoadingPermissions,
accountRoles: roles,
Expand Down
89 changes: 4 additions & 85 deletions packages/manager/src/features/IAM/hooks/usePermissions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import { usePermissions } from './usePermissions';
import type { AccessType, PermissionType } from '@linode/api-v4';

const queryMocks = vi.hoisted(() => ({
useIsIAMEnabled: vi
.fn()
.mockReturnValue({ isIAMEnabled: true, isIAMBeta: true }),
useIsIAMEnabled: vi.fn().mockReturnValue({ isIAMEnabled: true }),
useUserAccountPermissions: vi.fn().mockReturnValue({
data: ['cancel_account', 'create_linode'],
}),
Expand Down Expand Up @@ -75,7 +73,7 @@ describe('usePermissions', () => {
});

it('returns correct map when IAM is enabled (account)', () => {
const flags = { iam: { beta: true, enabled: true } };
const flags = { iam: { enabled: true } };

renderHook(
() => usePermissions('account', ['cancel_account', 'create_linode']),
Expand All @@ -94,7 +92,7 @@ describe('usePermissions', () => {
});

it('returns correct map when IAM is enabled (entity)', () => {
const flags = { iam: { beta: true, enabled: true } };
const flags = { iam: { enabled: true } };

renderHook(
() => usePermissions('linode', ['reboot_linode', 'view_linode'], 123),
Expand All @@ -120,7 +118,7 @@ describe('usePermissions', () => {
data: { global: { add_linode: true } },
});

const flags = { iam: { beta: false, enabled: false } };
const flags = { iam: { enabled: false } };
renderHook(
() => usePermissions('account', ['cancel_account', 'create_linode']),
{
Expand All @@ -136,83 +134,4 @@ describe('usePermissions', () => {
false
);
});

it('returns correct map when IAM beta is false', () => {
queryMocks.useIsIAMEnabled.mockReturnValue({
isIAMEnabled: true,
isIAMBeta: false,
});
const flags = { iam: { beta: false, enabled: true } };

renderHook(() => usePermissions('account', ['create_linode']), {
wrapper: (ui) => wrapWithTheme(ui, { flags }),
});

expect(queryMocks.useGrants).toHaveBeenCalledWith(false);
expect(queryMocks.useUserEntityPermissions).toHaveBeenCalledWith(
'account',
undefined,
true
);
});

it('returns correct map when beta is true and neither the access type nor the permissions are in the limited availability scope', () => {
const flags = { iam: { beta: true, enabled: true } };
queryMocks.useIsIAMEnabled.mockReturnValue({
isIAMEnabled: true,
isIAMBeta: true,
});

renderHook(() => usePermissions('linode', ['update_linode'], 123), {
wrapper: (ui) => wrapWithTheme(ui, { flags }),
});

expect(queryMocks.useGrants).toHaveBeenCalledWith(false);
expect(queryMocks.useUserAccountPermissions).toHaveBeenCalledWith(false);
expect(queryMocks.useUserEntityPermissions).toHaveBeenCalledWith(
'linode',
123,
true
);
});

it('returns correct map when beta is true and the access type is in the limited availability scope', () => {
const flags = { iam: { beta: true, enabled: true } };
queryMocks.useIsIAMEnabled.mockReturnValue({
isIAMEnabled: true,
isIAMBeta: true,
});

renderHook(() => usePermissions('volume', ['resize_volume'], 123), {
wrapper: (ui) => wrapWithTheme(ui, { flags }),
});

expect(queryMocks.useGrants).toHaveBeenCalledWith(true);
expect(queryMocks.useUserAccountPermissions).toHaveBeenCalledWith(false);
expect(queryMocks.useUserEntityPermissions).toHaveBeenCalledWith(
'volume',
123,
false
);
});

it('returns correct map when beta is true and one of the permissions is in the limited availability scope', () => {
const flags = { iam: { beta: true, enabled: true } };
queryMocks.useIsIAMEnabled.mockReturnValue({
isIAMEnabled: true,
isIAMBeta: true,
});

renderHook(() => usePermissions('account', ['create_volume']), {
wrapper: (ui) => wrapWithTheme(ui, { flags }),
});

expect(queryMocks.useGrants).toHaveBeenCalledWith(true);
expect(queryMocks.useUserAccountPermissions).toHaveBeenCalledWith(false);
expect(queryMocks.useUserEntityPermissions).toHaveBeenCalledWith(
'account',
undefined,
false
);
});
});
Loading