Skip to content

Commit

Permalink
upcoming: [M3-7857] - Account Management Copy Updates & Improvements (#…
Browse files Browse the repository at this point in the history
…10270)

Co-authored-by: Jaalah Ramos <jaalah.ramos@gmail.com>
  • Loading branch information
jaalah-akamai and jaalah authored Mar 12, 2024
1 parent b2c985c commit 74695aa
Show file tree
Hide file tree
Showing 12 changed files with 94 additions and 58 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Parent/Child Account Management Copy Updates & Improvements ([#10270](https://github.com/linode/manager/pull/10270))
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { interceptGetProfile } from 'support/intercepts/profile';
import { mockUpdateUsername } from 'support/intercepts/account';
import { ui } from 'support/ui';
import { randomString } from 'support/util/random';
import { RESTRICTED_FIELD_TOOLTIP } from 'src/features/Account/constants';

const verifyUsernameAndEmail = (
mockRestrictedProxyProfile: Profile,
Expand Down Expand Up @@ -50,9 +51,7 @@ const verifyUsernameAndEmail = (
.should('be.disabled')
.trigger('mouseover');
// Click the button first, then confirm the tooltip is shown
ui.tooltip
.findByText('This account type cannot update this field.')
.should('be.visible');
ui.tooltip.findByText(RESTRICTED_FIELD_TOOLTIP).should('be.visible');
}
};

Expand Down Expand Up @@ -108,7 +107,7 @@ describe('Display Settings', () => {

verifyUsernameAndEmail(
mockRestrictedProxyProfile,
'This account type cannot update this field.',
RESTRICTED_FIELD_TOOLTIP,
true
);
});
Expand All @@ -121,7 +120,7 @@ describe('Display Settings', () => {

verifyUsernameAndEmail(
mockUnrestrictedProxyProfile,
'This account type cannot update this field.',
RESTRICTED_FIELD_TOOLTIP,
true
);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
mockGetFeatureFlagClientstream,
} from 'support/intercepts/feature-flags';
import { makeFeatureFlagData } from 'support/util/feature-flags';
import { PROXY_USER_RESTRICTED_TOOLTIP_TEXT } from 'src/features/Account/constants';

describe('Personal access tokens', () => {
/*
Expand Down Expand Up @@ -268,17 +269,6 @@ describe('Personal access tokens', () => {
'@getAppTokens',
]);

// Find 'Create a Personal Access Token' button, confirm it is disabled and tooltip displays.
ui.button
.findByTitle('Create a Personal Access Token')
.should('be.visible')
.should('be.disabled')
.click();

ui.tooltip
.findByText('You can only create tokens for your own company.')
.should('be.visible');

// Find token in list, confirm "Rename" is disabled and tooltip displays.
cy.findByText(proxyToken.label)
.should('be.visible')
Expand All @@ -292,7 +282,7 @@ describe('Personal access tokens', () => {
});

ui.tooltip
.findByText('Only company users can edit API tokens.')
.findByText(PROXY_USER_RESTRICTED_TOOLTIP_TEXT)
.should('be.visible');

// Confirm that token has not been renamed, initiate revocation.
Expand All @@ -319,6 +309,17 @@ describe('Personal access tokens', () => {
.click();
});

// Find 'Create a Personal Access Token' button, confirm it is disabled and tooltip displays.
ui.button
.findByTitle('Create a Personal Access Token')
.should('be.visible')
.should('be.disabled')
.click();

ui.tooltip
.findByText(PROXY_USER_RESTRICTED_TOOLTIP_TEXT)
.should('be.visible');

// Confirm that token is removed from list after revoking.
cy.wait(['@revokeToken', '@getTokens']);
ui.toast.assertMessage(`Successfully revoked ${proxyToken.label}`);
Expand Down
13 changes: 6 additions & 7 deletions packages/manager/cypress/e2e/core/account/user-profile.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ import {
import { randomString } from 'support/util/random';
import { ui } from 'support/ui';
import { mockUpdateProfile } from 'support/intercepts/profile';
import { PARENT_USER } from 'src/features/Account/constants';
import {
PARENT_USER,
RESTRICTED_FIELD_TOOLTIP,
} from 'src/features/Account/constants';

describe('User Profile', () => {
/*
Expand Down Expand Up @@ -224,9 +227,7 @@ describe('User Profile', () => {
.should('be.visible')
.trigger('mouseover');
// Click the button first, then confirm the tooltip is shown.
ui.tooltip
.findByText('This account type cannot update this field.')
.should('be.visible');
ui.tooltip.findByText(RESTRICTED_FIELD_TOOLTIP).should('be.visible');
});

cy.get('[data-qa-textfield-label="Username"]')
Expand All @@ -252,9 +253,7 @@ describe('User Profile', () => {
.should('be.visible')
.trigger('mouseover');
// Click the button first, then confirm the tooltip is shown.
ui.tooltip
.findByText('This account type cannot update this field.')
.should('be.visible');
ui.tooltip.findByText(RESTRICTED_FIELD_TOOLTIP).should('be.visible');
});

cy.get('[data-qa-textfield-label="Email"]')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { renderWithTheme } from 'src/utilities/testHelpers';
import CloseAccountSetting from './CloseAccountSetting';
import {
CHILD_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT,
PARENT_PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT,
PARENT_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT,
PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT,
} from './constants';

// Mock the useProfile hook to immediately return the expected data, circumventing the HTTP request and loading state.
Expand Down Expand Up @@ -60,9 +61,7 @@ describe('Close Account Settings', () => {
expect(getByRole('tooltip')).toBeInTheDocument();
});

expect(
getByText(PARENT_PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT)
).toBeVisible();
expect(getByText(PARENT_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT)).toBeVisible();
expect(button).toHaveAttribute('aria-describedby', 'button-tooltip');
expect(button).not.toHaveAttribute('disabled');
expect(button).toHaveAttribute('aria-disabled', 'true');
Expand Down Expand Up @@ -110,9 +109,7 @@ describe('Close Account Settings', () => {
expect(getByRole('tooltip')).toBeInTheDocument();
});

expect(
getByText(PARENT_PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT)
).toBeVisible();
expect(getByText(PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT)).toBeVisible();
expect(button).toHaveAttribute('aria-describedby', 'button-tooltip');
expect(button).not.toHaveAttribute('disabled');
expect(button).toHaveAttribute('aria-disabled', 'true');
Expand Down
22 changes: 17 additions & 5 deletions packages/manager/src/features/Account/CloseAccountSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { useProfile } from 'src/queries/profile';
import CloseAccountDialog from './CloseAccountDialog';
import {
CHILD_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT,
PARENT_PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT,
PARENT_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT,
PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT,
} from './constants';

const CloseAccountSetting = () => {
Expand All @@ -22,10 +23,21 @@ const CloseAccountSetting = () => {
const isCloseAccountDisabled = Boolean(
flags.parentChildAccountAccess && profile?.user_type !== 'default'
);
const closeAccountButtonTooltipText =
isCloseAccountDisabled && profile?.user_type === 'child'
? CHILD_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT
: PARENT_PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT;

let closeAccountButtonTooltipText;
const userType = profile?.user_type;
const caseKey = isCloseAccountDisabled ? userType : 'default';

switch (caseKey) {
case 'child':
closeAccountButtonTooltipText = CHILD_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT;
break;
case 'proxy':
closeAccountButtonTooltipText = PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT;
break;
default:
closeAccountButtonTooltipText = PARENT_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT;
}

return (
<>
Expand Down
10 changes: 7 additions & 3 deletions packages/manager/src/features/Account/SwitchAccountDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import { StyledLinkButton } from 'src/components/Button/StyledLinkButton';
import { Drawer } from 'src/components/Drawer';
import { Notice } from 'src/components/Notice/Notice';
import { Typography } from 'src/components/Typography';
import { PARENT_SESSION_EXPIRED } from 'src/features/Account/constants';
import { PARENT_USER_SESSION_EXPIRED } from 'src/features/Account/constants';
import {
isParentTokenValid,
setTokenInLocalStorage,
updateCurrentTokenBasedOnUserType,
} from 'src/features/Account/utils';
import { useCurrentToken } from 'src/hooks/useAuthentication';
import { sendSwitchToParentAccountEvent } from 'src/utilities/analytics';
import { getStorage } from 'src/utilities/storage';
import { getStorage, setStorage } from 'src/utilities/storage';

import { ChildAccountList } from './SwitchAccounts/ChildAccountList';

Expand Down Expand Up @@ -146,7 +146,7 @@ export const SwitchAccountDrawer = (props: Props) => {
if (!isParentTokenValid()) {
const expiredTokenError: APIError = {
field: 'token',
reason: PARENT_SESSION_EXPIRED,
reason: PARENT_USER_SESSION_EXPIRED,
};

setIsParentTokenError([expiredTokenError]);
Expand All @@ -155,6 +155,10 @@ export const SwitchAccountDrawer = (props: Props) => {
}

updateCurrentTokenBasedOnUserType({ userType: 'parent' });

// Reset flag for proxy user to display success toast once.
setStorage('proxy_user', 'false');

handleClose();
refreshPage();
}, [handleClose, refreshPage]);
Expand Down
19 changes: 13 additions & 6 deletions packages/manager/src/features/Account/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const PARENT_USER = 'parent user';
export const ADMINISTRATOR = 'account administrator';
export const CUSTOMER_SUPPORT = 'customer support';

export const grantTypeMap = {
account: 'Account',
Expand All @@ -15,11 +16,17 @@ export const grantTypeMap = {
vpc: 'VPCs',
} as const;

export const PARENT_PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT =
'Remove indirect customers before closing the account.';
export const CHILD_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT = `Contact your ${PARENT_USER} to close your account.`;
export const RESTRICTED_FIELD_TOOLTIP = 'This field can\u{2019}t be modified.';

// Parent User Messaging
export const PARENT_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT =
'Remove child accounts before closing the account.';
export const PARENT_USER_SESSION_EXPIRED = `Session expired. Please log in again to your ${PARENT_USER} account.`;

export const PARENT_SESSION_EXPIRED = `Session expired. Please log in again to your ${PARENT_USER} account.`;
// Proxy User Messaging
export const PROXY_USER_RESTRICTED_TOOLTIP_TEXT =
'You can\u{2019}t perform this action on child accounts.';
export const PROXY_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT = `Contact ${CUSTOMER_SUPPORT} to close this account.`;

export const RESTRICTED_FIELD_TOOLTIP =
'This account type cannot update this field.';
// Child User Messaging
export const CHILD_USER_CLOSE_ACCOUNT_TOOLTIP_TEXT = `Contact your ${PARENT_USER} to close your account.`;
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ const UpdateContactInformationForm = ({ focusEmail, onClose }: Props) => {
zip: account?.zip,
},
async onSubmit(values) {
await mutateAsync(values);
const clonedValues = { ...values };

if (isParentUser) {
// This is a disabled field that we want to omit from payload.
delete clonedValues.company;
}

await mutateAsync(clonedValues);

// If there's a "billing_email_bounce" notification on the account, and
// the user has just updated their email, re-request notifications to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as React from 'react';

import { Action, ActionMenu } from 'src/components/ActionMenu/ActionMenu';
import { InlineMenuAction } from 'src/components/InlineMenuAction/InlineMenuAction';
import { PROXY_USER_RESTRICTED_TOOLTIP_TEXT } from 'src/features/Account/constants';

interface Props {
isProxyUser: boolean;
Expand Down Expand Up @@ -44,7 +45,7 @@ export const APITokenMenu = (props: Props) => {
openEditDrawer(token);
},
title: 'Rename',
tooltip: 'Only company users can edit API tokens.',
tooltip: PROXY_USER_RESTRICTED_TOOLTIP_TEXT,
}
: null,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { Token } from '@linode/api-v4/lib/profile';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';

import AddNewLink from 'src/components/AddNewLink';
import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { DateTimeDisplay } from 'src/components/DateTimeDisplay';
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter';
import { Table } from 'src/components/Table';
Expand All @@ -17,6 +17,7 @@ import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading'
import { StyledTableSortCell } from 'src/components/TableSortCell/StyledTableSortCell';
import { TableSortCell } from 'src/components/TableSortCell/TableSortCell';
import { Typography } from 'src/components/Typography';
import { PROXY_USER_RESTRICTED_TOOLTIP_TEXT } from 'src/features/Account/constants';
import { SecretTokenDialog } from 'src/features/Profile/SecretTokenDialog/SecretTokenDialog';
import { useFlags } from 'src/hooks/useFlags';
import { useOrder } from 'src/hooks/useOrder';
Expand Down Expand Up @@ -205,16 +206,16 @@ export const APITokenTable = (props: Props) => {
</Grid>
<StyledAddNewWrapper>
{type === 'Personal Access Token' && (
<AddNewLink
disabledReason={
isProxyUser
? 'You can only create tokens for your own company.'
: undefined
<Button
tooltipText={
isProxyUser ? PROXY_USER_RESTRICTED_TOOLTIP_TEXT : undefined
}
buttonType="primary"
disabled={isProxyUser}
label="Create a Personal Access Token"
onClick={() => setIsCreateOpen(true)}
/>
>
Create a Personal Access Token
</Button>
)}
</StyledAddNewWrapper>
</StyledRootContainer>
Expand Down
9 changes: 6 additions & 3 deletions packages/manager/src/features/TopMenu/UserMenu/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGran
import { useAccount } from 'src/queries/account';
import { useGrants, useProfile } from 'src/queries/profile';
import { sendSwitchAccountEvent } from 'src/utilities/analytics';
import { getStorage } from 'src/utilities/storage';
import { getStorage, setStorage } from 'src/utilities/storage';

interface MenuLink {
display: string;
Expand Down Expand Up @@ -120,7 +120,10 @@ export const UserMenu = React.memo(() => {

React.useEffect(() => {
// Run after we've switched to a proxy user.
if (isProxyUser) {
if (isProxyUser && !getStorage('proxy_user')) {
// Flag for proxy user to display success toast once.
setStorage('proxy_user', 'true');

enqueueSnackbar(`Account switched to ${companyNameOrEmail}.`, {
variant: 'success',
});
Expand Down Expand Up @@ -270,7 +273,7 @@ export const UserMenu = React.memo(() => {
>
<Stack data-qa-user-menu minWidth={250} spacing={2}>
{canSwitchBetweenParentOrProxyAccount && (
<Typography>You are currently logged in as:</Typography>
<Typography>Current account:</Typography>
)}
<Typography
color={(theme) => theme.textColors.headlineStatic}
Expand Down

0 comments on commit 74695aa

Please sign in to comment.