Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Expand Up @@ -11,7 +11,7 @@ import { ChangeRoleForEntityDrawer } from '../../Shared/AssignedEntitiesTable/Ch
import type { EntitiesRole } from '../../Shared/types';

const queryMocks = vi.hoisted(() => ({
useParams: vi.fn().mockReturnValue({}),
useParams: vi.fn().mockReturnValue({ username: 'test_user' }),
useAccountRoles: vi.fn().mockReturnValue({}),
useUserRoles: vi.fn().mockReturnValue({}),
}));
Expand Down Expand Up @@ -47,6 +47,7 @@ const props = {
onClose: vi.fn(),
open: true,
role: mockRole,
username: 'test_user',
};

const mockUpdateUserRole = vi.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
import { Button, CircleProgress, Select, Typography } from '@linode/ui';
import { useTheme } from '@mui/material';
import Grid from '@mui/material/Grid';
import { useNavigate } from '@tanstack/react-router';
import { useNavigate, useParams } from '@tanstack/react-router';
import React from 'react';

import { CollapsibleTable } from 'src/components/CollapsibleTable/CollapsibleTable';
Expand Down Expand Up @@ -70,11 +70,8 @@ const ALL_ROLES_OPTION: SelectOption = {
label: 'All Assigned Roles',
value: 'all',
};
interface Props {
username?: string;
}
export const AssignedRolesTable = (props: Props) => {
const { username } = props;
export const AssignedRolesTable = () => {
const { username } = useParams({ strict: false });
const navigate = useNavigate();
const theme = useTheme();

Expand Down Expand Up @@ -431,26 +428,22 @@ export const AssignedRolesTable = (props: Props) => {
assignedRoles={assignedRoles}
onClose={() => setIsAssignNewRoleDrawerOpen(false)}
open={isAssignNewRoleDrawerOpen}
username={username}
/>
<ChangeRoleDrawer
mode={drawerMode}
onClose={() => setIsChangeRoleDrawerOpen(false)}
open={isChangeRoleDrawerOpen}
role={selectedRole}
username={username}
/>
<UnassignRoleConfirmationDialog
onClose={() => setIsUnassignRoleDialogOpen(false)}
open={isUnassignRoleDialogOpen}
role={selectedRole}
username={username}
/>
<UpdateEntitiesDrawer
onClose={() => setIsUpdateEntitiesDrawerOpen(false)}
open={isUpdateEntitiesDrawerOpen}
role={selectedRole}
username={username}
/>
<RemoveAssignmentConfirmationDialog
onClose={() => setIsRemoveAssignmentDialogOpen(false)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
Typography,
} from '@linode/ui';
import { useTheme } from '@mui/material/styles';
import { useParams } from '@tanstack/react-router';
import React from 'react';
import { Controller, useForm } from 'react-hook-form';

Expand All @@ -38,18 +39,11 @@ interface Props {
onClose: () => void;
open: boolean;
role: ExtendedRoleView | undefined;
username?: string;
}

export const ChangeRoleDrawer = ({
mode,
onClose,
open,
role,
username,
}: Props) => {
export const ChangeRoleDrawer = ({ mode, onClose, open, role }: Props) => {
const theme = useTheme();

const { username } = useParams({ strict: false });
const { data: accountRoles, isLoading: accountPermissionsLoading } =
useAccountRoles();

Expand All @@ -67,7 +61,10 @@ export const ChangeRoleDrawer = ({
const assignedRoles = isDefaultDelegationRolesForChildAccount
? defaultRolesData
: userRolesData;
const { mutateAsync: updateUserRoles } = useUserRolesMutation(username || '');
const { mutateAsync: updateUserRoles } = useUserRolesMutation(
username,
Boolean(username)
);

const { mutateAsync: updateDefaultRoles } =
useUpdateDefaultDelegationAccessQuery();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@ const props = {
onSuccess: vi.fn(),
open: true,
role: mockRole,
username: 'test_user',
};

const queryMocks = vi.hoisted(() => ({
useParams: vi.fn().mockReturnValue({}),
useParams: vi.fn().mockReturnValue({ username: 'test_user' }),
useAccountRoles: vi.fn().mockReturnValue({}),
useUserRoles: vi.fn().mockReturnValue({}),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
useUserRolesMutation,
} from '@linode/queries';
import { ActionsPanel, Notice, Typography } from '@linode/ui';
import { useParams } from '@tanstack/react-router';
import { useSnackbar } from 'notistack';
import React from 'react';

Expand All @@ -20,12 +21,12 @@ interface Props {
onSuccess?: () => void;
open: boolean;
role: ExtendedRoleView | undefined;
username?: string;
}

export const UnassignRoleConfirmationDialog = (props: Props) => {
const { onClose: _onClose, onSuccess, open, role, username } = props;
const { onClose: _onClose, onSuccess, open, role } = props;
const { enqueueSnackbar } = useSnackbar();
const { username } = useParams({ strict: false });
const { isDefaultDelegationRolesForChildAccount } =
useIsDefaultDelegationRolesForChildAccount();
const { data: defaultRolesData } = useGetDefaultDelegationAccessQuery({
Expand All @@ -45,11 +46,15 @@ export const UnassignRoleConfirmationDialog = (props: Props) => {
isPending,
mutateAsync: updateUserRoles,
reset,
} = useUserRolesMutation(username || '');
} = useUserRolesMutation(username, Boolean(username));

const { mutateAsync: updateDefaultRoles, isPending: isDefaultRolesPending } =
useUpdateDefaultDelegationAccessQuery();

const mutationFn = isDefaultDelegationRolesForChildAccount
? updateDefaultRoles
: updateUserRoles;

const onClose = () => {
reset(); // resets the error state of the useMutation
_onClose();
Expand All @@ -65,11 +70,7 @@ export const UnassignRoleConfirmationDialog = (props: Props) => {
initialRole,
});

if (isDefaultDelegationRolesForChildAccount) {
await updateDefaultRoles(updatedUserRoles);
} else {
await updateUserRoles(updatedUserRoles);
}
await mutationFn(updatedUserRoles);

enqueueSnackbar(`Role ${role?.name} has been deleted successfully.`, {
variant: 'success',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { useUserRoles, useUserRolesMutation } from '@linode/queries';
import {
useGetDefaultDelegationAccessQuery,
useUpdateDefaultDelegationAccessQuery,
useUserRoles,
useUserRolesMutation,
} from '@linode/queries';
import { ActionsPanel, Drawer, Notice, Typography } from '@linode/ui';
import { useTheme } from '@mui/material';
import { useParams } from '@tanstack/react-router';
import React from 'react';
import { Controller, FormProvider, useForm } from 'react-hook-form';

import { useIsDefaultDelegationRolesForChildAccount } from '../../hooks/useDelegationRole';
import { AssignedPermissionsPanel } from '../AssignedPermissionsPanel/AssignedPermissionsPanel';
import { INTERNAL_ERROR_NO_CHANGES_SAVED } from '../constants';
import { toEntityAccess } from '../utilities';
Expand All @@ -16,20 +23,36 @@ interface Props {
onClose: () => void;
open: boolean;
role: ExtendedRoleView | undefined;
username?: string;
}

export const UpdateEntitiesDrawer = ({
onClose,
open,
role,
username,
}: Props) => {
export const UpdateEntitiesDrawer = ({ onClose, open, role }: Props) => {
const theme = useTheme();
const { username } = useParams({ strict: false });
const { isDefaultDelegationRolesForChildAccount } =
useIsDefaultDelegationRolesForChildAccount();
const { data: defaultRolesData } = useGetDefaultDelegationAccessQuery({
enabled: isDefaultDelegationRolesForChildAccount,
});

const { data: userRolesData } = useUserRoles(
username ?? '',
!isDefaultDelegationRolesForChildAccount
);

const assignedRoles = isDefaultDelegationRolesForChildAccount
? defaultRolesData
: userRolesData;
const { mutateAsync: updateUserRoles } = useUserRolesMutation(
username,
Boolean(username)
);

const { data: assignedRoles } = useUserRoles(username ?? '');
const { mutateAsync: updateDefaultRoles } =
useUpdateDefaultDelegationAccessQuery();

const { mutateAsync: updateUserRoles } = useUserRolesMutation(username || '');
const mutationFn = isDefaultDelegationRolesForChildAccount
? updateDefaultRoles
: updateUserRoles;

const formattedAssignedEntities: EntitiesOption[] = React.useMemo(() => {
if (!role || !role.entity_names || !role.entity_ids) {
Expand Down Expand Up @@ -88,7 +111,7 @@ export const UpdateEntitiesDrawer = ({
role!.entity_type
);

await updateUserRoles({
await mutationFn({
...assignedRoles!,
entity_access: entityAccess,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const props = {
onSuccess: vi.fn(),
open: true,
role: mockRole,
username: 'test_user',
};

const queryMocks = vi.hoisted(() => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ const props = {
account_access: [mockAccountAccessRole.id],
entity_access: [],
} as IamUserRoles,
username: 'test_user',
};

const mockUpdateUserRole = vi.fn();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from '@linode/ui';
import { useTheme } from '@mui/material';
import Grid from '@mui/material/Grid';
import { useParams } from '@tanstack/react-router';
import { enqueueSnackbar } from 'notistack';
import React, { useEffect, useState } from 'react';
import { FormProvider, useFieldArray, useForm } from 'react-hook-form';
Expand Down Expand Up @@ -42,18 +43,16 @@ interface Props {
assignedRoles?: IamUserRoles;
onClose: () => void;
open: boolean;
username?: string;
}

export const AssignNewRoleDrawer = ({
assignedRoles,
username,
onClose,
open,
}: Props) => {
const theme = useTheme();
const queryClient = useQueryClient();

const { username } = useParams({ strict: false });
const { data: accountRoles } = useAccountRoles();
Comment thread
mpolotsk-akamai marked this conversation as resolved.
const { isDefaultDelegationRolesForChildAccount } =
useIsDefaultDelegationRolesForChildAccount();
Expand Down Expand Up @@ -100,23 +99,17 @@ export const AssignNewRoleDrawer = ({
}, [accountRoles, assignedRoles]);

const { mutateAsync: updateUserRoles, isPending: isUserRolesPending } =
useUserRolesMutation(username || '');
useUserRolesMutation(username, Boolean(username));

const { mutateAsync: updateDefaultRoles, isPending: isDefaultRolesPending } =
useUpdateDefaultDelegationAccessQuery();

const onSubmit = async (values: AssignNewRoleFormValues) => {
try {
const queryKey = iamQueries.user(username ?? '')._ctx.roles.queryKey;
const currentRoles = queryClient.getQueryData<IamUserRoles>(queryKey);
const currentDefaultRoles = queryClient.getQueryData<IamUserRoles>(
delegationQueries.defaultAccess.queryKey
);
const mergedRoles = mergeAssignedRolesIntoExistingRoles(
values,
structuredClone(currentRoles)
);
if (isDefaultDelegationRolesForChildAccount) {
const currentDefaultRoles = queryClient.getQueryData<IamUserRoles>(
delegationQueries.defaultAccess.queryKey
);
const mergedDefaultRoles = mergeAssignedRolesIntoExistingRoles(
values,
structuredClone(currentDefaultRoles)
Expand All @@ -126,6 +119,13 @@ export const AssignNewRoleDrawer = ({
if (!username) {
return;
}
const queryKey = iamQueries.user(username ?? '')._ctx.roles.queryKey;
const currentRoles = queryClient.getQueryData<IamUserRoles>(queryKey);

const mergedRoles = mergeAssignedRolesIntoExistingRoles(
values,
structuredClone(currentRoles)
);
await updateUserRoles(mergedRoles);
}
enqueueSnackbar(`Roles added.`, { variant: 'success' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ export const UserRoles = () => {
>
View and manage roles assigned to the user.
</Typography>
<AssignedRolesTable username={username} />
<AssignedRolesTable />
</Paper>
) : (
<NoAssignedRoles
Expand Down
23 changes: 17 additions & 6 deletions packages/queries/src/iam/iam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,26 @@ export const useAccountRoles = (enabled = true) => {
});
};

export const useUserRolesMutation = (username: string) => {
export const useUserRolesMutation = (
username?: string,
enabled: boolean = true,
) => {
const queryClient = useQueryClient();

return useMutation<IamUserRoles, APIError[], IamUserRoles>({
mutationFn: (data) => updateUserRoles(username, data),
mutationFn: (data) => {
if (!username) {
throw new Error('Username is required');
}
return updateUserRoles(username, data);
},
onSuccess: (role) => {
queryClient.setQueryData<IamUserRoles>(
iamQueries.user(username)._ctx.roles.queryKey,
role,
);
if (username && enabled) {
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.

@abailly-akamai , could you please check if that's what you mean? I haven't found anything similar to this in CM (might've missed something)

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.

This works but I would simplify: (sorry the enabled suggestion was not appropriate/needed)

conditional queries/mutations are tricky to handle. usually we tend to render different components but I think this should suffice.

Maybe i am overthinking this and username ?? '' is fine but either way the challenge is to surface a proper error if that happens. Usually i try to prevent sending a mutation if we already know the payload isn't right. sending '' is just getting around the types which isn't ideal

export const useUserRolesMutation = (username: string | undefined) => {
  const queryClient = useQueryClient();

  return useMutation<IamUserRoles, APIError[], IamUserRoles>({
    mutationFn: (data) => {
      if (!username) {
        throw new Error('Username is required');
      }
      return updateUserRoles(username, data);
    },
    onSuccess: (role) => {
      if (username) {
        queryClient.setQueryData<IamUserRoles>(
          iamQueries.user(username)._ctx.roles.queryKey,
          role,
        );
      }
    },
  });
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Updated, thanks!

queryClient.setQueryData<IamUserRoles>(
iamQueries.user(username)._ctx.roles.queryKey,
role,
);
}
},
});
};
Expand Down