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": Upcoming Features
---

Update the usePermissions hook to return consistent with the other queries ([#12617](https://github.com/linode/manager/pull/12617))
133 changes: 71 additions & 62 deletions packages/manager/src/components/ActionMenu/ActionMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { convertToKebabCase, TooltipIcon } from '@linode/ui';
import { CircleProgress, convertToKebabCase, TooltipIcon } from '@linode/ui';
import { IconButton, ListItemText } from '@mui/material';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
Expand All @@ -23,6 +23,10 @@ export interface ActionMenuProps {
* Gives the Menu Button an accessible name
*/
ariaLabel: string;
/**
* If true, show a loading indicator
*/
loading?: boolean;
/**
* A function that is called when the Menu is opened. Useful for analytics.
*/
Expand All @@ -40,7 +44,8 @@ export interface ActionMenuProps {
* No more than 8 items should be displayed within an action menu.
*/
export const ActionMenu = React.memo((props: ActionMenuProps) => {
const { actionsList, ariaLabel, onOpen, stopClickPropagation } = props;
const { actionsList, ariaLabel, loading, onOpen, stopClickPropagation } =
props;

const menuId = convertToKebabCase(ariaLabel);
const buttonId = `${convertToKebabCase(ariaLabel)}-button`;
Expand Down Expand Up @@ -95,6 +100,8 @@ export const ActionMenu = React.memo((props: ActionMenuProps) => {
aria-label={ariaLabel}
color="inherit"
id={buttonId}
loading={loading}
loadingIndicator={<CircleProgress noPadding size="xs" />}
onClick={handleClick}
onKeyDown={handleKeyPress}
sx={(theme) => ({
Expand All @@ -112,66 +119,68 @@ export const ActionMenu = React.memo((props: ActionMenuProps) => {
>
<KebabIcon />
</IconButton>
<Menu
anchorEl={anchorEl}
anchorOrigin={{
horizontal: 'right',
vertical: 'bottom',
}}
data-qa-action-menu
disableScrollLock
id={menuId}
marginThreshold={0}
MenuListProps={{
'aria-labelledby': buttonId,
}}
onClose={handleClose}
open={open}
slotProps={{
paper: {
sx: (theme) => ({
backgroundColor: theme.palette.primary.main,
}),
},
}}
transformOrigin={{
horizontal: 'right',
vertical: 'top',
}}
transitionDuration={225}
>
{actionsList.map((a, idx) => (
<MenuItem
data-qa-action-menu-item={a.title}
data-testid={a.title}
disabled={a.disabled}
key={idx}
onClick={(e) => {
if (!a.disabled) {
handleClose(e);
a.onClick();
}
if (stopClickPropagation) {
e.stopPropagation();
}
}}
onMouseEnter={handleMouseEnter}
>
<ListItemText primaryTypographyProps={{ color: 'inherit' }}>
{a.title}
</ListItemText>
{a.tooltip && (
<TooltipIcon
data-qa-tooltip-icon
status="info"
sxTooltipIcon={sxTooltipIcon}
text={a.tooltip}
tooltipPosition="right"
/>
)}
</MenuItem>
))}
</Menu>
{!loading && (
<Menu
anchorEl={anchorEl}
anchorOrigin={{
horizontal: 'right',
vertical: 'bottom',
}}
data-qa-action-menu
disableScrollLock
id={menuId}
marginThreshold={0}
MenuListProps={{
'aria-labelledby': buttonId,
}}
onClose={handleClose}
open={open}
slotProps={{
paper: {
sx: (theme) => ({
backgroundColor: theme.palette.primary.main,
}),
},
}}
transformOrigin={{
horizontal: 'right',
vertical: 'top',
}}
transitionDuration={225}
>
{actionsList.map((a, idx) => (
<MenuItem
data-qa-action-menu-item={a.title}
data-testid={a.title}
disabled={a.disabled}
key={idx}
onClick={(e) => {
if (!a.disabled) {
handleClose(e);
a.onClick();
}
if (stopClickPropagation) {
e.stopPropagation();
}
}}
onMouseEnter={handleMouseEnter}
>
<ListItemText primaryTypographyProps={{ color: 'inherit' }}>
{a.title}
</ListItemText>
{a.tooltip && (
<TooltipIcon
data-qa-tooltip-icon
status="info"
sxTooltipIcon={sxTooltipIcon}
text={a.tooltip}
tooltipPosition="right"
/>
)}
</MenuItem>
))}
</Menu>
)}
</>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { PaymentMethodRow } from './PaymentMethodRow';

const queryMocks = vi.hoisted(() => ({
userPermissions: vi.fn(() => ({
permissions: {
data: {
make_billing_payment: false,
update_account: false,
},
Expand Down Expand Up @@ -146,7 +146,7 @@ describe('Payment Method Row', () => {
it('Calls `onDelete` callback when "Delete" action is clicked', async () => {
const mockFunction = vi.fn();
queryMocks.userPermissions.mockReturnValue({
permissions: {
data: {
make_billing_payment: false,
update_account: true,
},
Expand All @@ -172,7 +172,7 @@ describe('Payment Method Row', () => {

it('Makes payment method default when "Make Default" action is clicked', async () => {
queryMocks.userPermissions.mockReturnValue({
permissions: {
data: {
make_billing_payment: true,
update_account: true,
},
Expand Down Expand Up @@ -203,7 +203,7 @@ describe('Payment Method Row', () => {

it('should disable "Make a Payment" button if the user does not have make_billing_payment permissions', async () => {
queryMocks.userPermissions.mockReturnValue({
permissions: {
data: {
make_billing_payment: false,
update_account: false,
},
Expand All @@ -229,7 +229,7 @@ describe('Payment Method Row', () => {

it('should enable "Make a Payment" button if the user has make_billing_payment permissions', async () => {
queryMocks.userPermissions.mockReturnValue({
permissions: {
data: {
make_billing_payment: true,
update_account: false,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const PaymentMethodRow = (props: Props) => {
const { mutateAsync: makePaymentMethodDefault } =
useMakeDefaultPaymentMethodMutation(props.paymentMethod.id);

const { permissions } = usePermissions('account', [
const { data: permissions } = usePermissions('account', [
'make_billing_payment',
'update_account',
]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const testId = 'select-firewall-panel';

const queryMocks = vi.hoisted(() => ({
usePermissions: vi.fn(() => ({
permissions: { delete_firewall: true, update_firewall: true },
data: { delete_firewall: true, update_firewall: true },
})),
useQueryWithPermissions: vi.fn().mockReturnValue({
data: [],
Expand Down
4 changes: 2 additions & 2 deletions packages/manager/src/features/Account/AccountLanding.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { AccountLanding } from './AccountLanding';

const queryMocks = vi.hoisted(() => ({
userPermissions: vi.fn(() => ({
permissions: { make_billing_payment: false },
data: { make_billing_payment: false },
})),
}));

Expand All @@ -26,7 +26,7 @@ describe('AccountLanding', () => {

it('should enable "Make a Payment" button if the user has make_billing_payment permission', async () => {
queryMocks.userPermissions.mockReturnValue({
permissions: { make_billing_payment: true },
data: { make_billing_payment: true },
});

const { getByRole } = renderWithTheme(<AccountLanding />);
Expand Down
4 changes: 3 additions & 1 deletion packages/manager/src/features/Account/AccountLanding.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ export const AccountLanding = () => {
const { data: profile } = useProfile();
const { limitsEvolution } = useFlags();

const { permissions } = usePermissions('account', ['make_billing_payment']);
const { data: permissions } = usePermissions('account', [
'make_billing_payment',
]);

const [isDrawerOpen, setIsDrawerOpen] = React.useState<boolean>(false);
const sessionContext = React.useContext(switchAccountSessionContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const accountBalanceValue = 'account-balance-value';

const queryMocks = vi.hoisted(() => ({
userPermissions: vi.fn(() => ({
permissions: {
data: {
create_promo_code: false,
},
})),
Expand Down Expand Up @@ -192,7 +192,7 @@ describe('BillingSummary', () => {

it('displays the "Add a promo code" button if user has create_promo_code permission', async () => {
queryMocks.userPermissions.mockReturnValue({
permissions: {
data: {
create_promo_code: true,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export const BillingSummary = (props: BillingSummaryProps) => {
const { data: notifications } = useNotificationsQuery();
const { data: account } = useAccount();

const { permissions } = usePermissions('account', ['create_promo_code']);
const { data: permissions } = usePermissions('account', [
'create_promo_code',
]);

const [isPromoDialogOpen, setIsPromoDialogOpen] =
React.useState<boolean>(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const EDIT_BUTTON_ID = 'edit-contact-info';
const queryMocks = vi.hoisted(() => ({
useProfile: vi.fn().mockReturnValue({}),
userPermissions: vi.fn(() => ({
permissions: { update_account: false },
data: { update_account: false },
})),
}));

Expand Down Expand Up @@ -85,7 +85,7 @@ describe('Edit Contact Information', () => {
});

queryMocks.userPermissions.mockReturnValue({
permissions: { update_account: true },
data: { update_account: true },
});

const { getByTestId } = renderWithTheme(<ContactInformation {...props} />);
Expand All @@ -104,7 +104,7 @@ describe('Edit Contact Information', () => {
});

queryMocks.userPermissions.mockReturnValue({
permissions: { update_account: true },
data: { update_account: true },
});

const { getByTestId } = renderWithTheme(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const ContactInformation = React.memo((props: Props) => {
return notification.type === 'tax_id_verifying';
});

const { permissions } = usePermissions('account', ['update_account']);
const { data: permissions } = usePermissions('account', ['update_account']);

const isReadOnly = !permissions.update_account || isChildUser;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ vi.mock('@linode/api-v4/lib/account', async () => {
const queryMocks = vi.hoisted(() => ({
useProfile: vi.fn().mockReturnValue({}),
userPermissions: vi.fn(() => ({
permissions: { update_account: false, make_billing_payment: false },
data: { update_account: false, make_billing_payment: false },
})),
}));

Expand Down Expand Up @@ -98,7 +98,7 @@ describe('Payment Info Panel', () => {

it('Opens "Add Payment Method" drawer when "Add Payment Method" is clicked', async () => {
queryMocks.userPermissions.mockReturnValue({
permissions: { update_account: true, make_billing_payment: true },
data: { update_account: true, make_billing_payment: true },
});
const { getByTestId, findByTestId } = renderWithTheme(
<PayPalScriptProvider options={{ clientId: PAYPAL_CLIENT_ID }}>
Expand Down Expand Up @@ -187,7 +187,7 @@ describe('Payment Info Panel', () => {
});

queryMocks.userPermissions.mockReturnValue({
permissions: { update_account: false, make_billing_payment: false },
data: { update_account: false, make_billing_payment: false },
});

const { getByTestId } = renderWithTheme(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const PaymentInformation = (props: Props) => {

const isChildUser = profile?.user_type === 'child';

const { permissions } = usePermissions('account', ['update_account']);
const { data: permissions } = usePermissions('account', ['update_account']);

const isReadOnly = !permissions.update_account || isChildUser;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const props = {
const queryMocks = vi.hoisted(() => ({
useParams: vi.fn().mockReturnValue({}),
userPermissions: vi.fn(() => ({
permissions: {
data: {
create_firewall_device: false,
},
})),
Expand Down Expand Up @@ -63,7 +63,7 @@ describe('AddLinodeDrawer', () => {

it('should disable "Add" button if the user does not have create_firewall_device permission', async () => {
queryMocks.userPermissions.mockReturnValue({
permissions: {
data: {
create_firewall_device: false,
},
});
Expand All @@ -86,7 +86,7 @@ describe('AddLinodeDrawer', () => {

it('should enable "Add" button if the user has create_firewall_device permission', async () => {
queryMocks.userPermissions.mockReturnValue({
permissions: {
data: {
create_firewall_device: true,
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const AddLinodeDrawer = (props: Props) => {

const firewall = data?.find((firewall) => firewall.id === Number(id));

const { permissions } = usePermissions(
const { data: permissions } = usePermissions(
'firewall',
['create_firewall_device'],
firewall?.id
Expand Down
Loading