From f3a321da3f5025dfcec55279fc90d7d085f66b79 Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Wed, 24 May 2023 16:33:29 +0200 Subject: [PATCH] [NO JIRA] Improve account and token accessiblity --- .../current-user/CurrentUserContext.ts | 12 ++- .../src/main/js/apps/account/Account.tsx | 74 +++++++------------ .../js/apps/account/__tests__/Account-it.tsx | 6 +- .../notifications/GlobalNotifications.tsx | 2 +- .../js/apps/account/security/Security.tsx | 12 +-- .../js/apps/users/__tests__/UsersApp-it.tsx | 4 +- .../js/apps/users/components/TokensForm.tsx | 6 +- .../apps/users/components/TokensFormItem.tsx | 32 ++++++-- .../resources/org/sonar/l10n/core.properties | 4 +- 9 files changed, 78 insertions(+), 74 deletions(-) diff --git a/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContext.ts b/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContext.ts index 3bc8792dc04..56de6d94c66 100644 --- a/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContext.ts +++ b/server/sonar-web/src/main/js/app/components/current-user/CurrentUserContext.ts @@ -19,7 +19,9 @@ */ import { noop } from 'lodash'; import * as React from 'react'; -import { CurrentUser, HomePage, NoticeType } from '../../../types/users'; +import { useContext } from 'react'; +import handleRequiredAuthentication from '../../../helpers/handleRequiredAuthentication'; +import { CurrentUser, HomePage, LoggedInUser, NoticeType } from '../../../types/users'; export interface CurrentUserContextInterface { currentUser: CurrentUser; @@ -35,3 +37,11 @@ export const CurrentUserContext = React.createContext { - componentDidMount() { - if (!this.props.currentUser.isLoggedIn) { - handleRequiredAuthentication(); - } - } - - render() { - const { currentUser } = this.props; +export default function Account() { + const currentUser = useCurrentLoginUser(); - if (!currentUser.isLoggedIn) { - return null; - } + const title = translate('my_account.page'); + return ( +
+ + + +
+
+ +
+
- const title = translate('my_account.page'); - return ( -
- - - -
-
- -
-
- -
- -
-
- ); - } +
+ +
+
+ ); } - -export default withCurrentUserContext(Account); diff --git a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx index a740d347879..afdf7ead08e 100644 --- a/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx +++ b/server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx @@ -347,15 +347,15 @@ describe('security page', () => { // Revoke the token const revokeButtons = within(row).getByRole('button', { - name: 'users.tokens.revoke_token', + name: 'users.tokens.revoke_label.importantToken', }); await user.click(revokeButtons); expect( - screen.getByRole('heading', { name: 'users.tokens.revoke_token' }) + screen.getByRole('heading', { name: 'users.tokens.revoke_label.importantToken' }) ).toBeInTheDocument(); - await user.click(screen.getByText('users.tokens.revoke_token', { selector: 'button' })); + await user.click(screen.getByRole('button', { name: 'yes' })); expect(screen.getAllByRole('row')).toHaveLength(3); // 2 tokens + header } diff --git a/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx b/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx index 6c08994e89b..06addf71926 100644 --- a/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx +++ b/server/sonar-web/src/main/js/apps/account/notifications/GlobalNotifications.tsx @@ -39,7 +39,7 @@ export default function GlobalNotifications(props: Props) { - {props.channels.map((channel) => ( - diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx index 6991a61d50f..5e211934640 100644 --- a/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/TokensFormItem.tsx @@ -21,13 +21,13 @@ import classNames from 'classnames'; import * as React from 'react'; import { FormattedMessage } from 'react-intl'; import { revokeToken } from '../../../api/user-tokens'; -import { Button } from '../../../components/controls/buttons'; import ConfirmButton from '../../../components/controls/ConfirmButton'; +import { Button } from '../../../components/controls/buttons'; import WarningIcon from '../../../components/icons/WarningIcon'; import DateFormatter from '../../../components/intl/DateFormatter'; import DateFromNow from '../../../components/intl/DateFromNow'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; -import { translate } from '../../../helpers/l10n'; +import { translate, translateWithParameters } from '../../../helpers/l10n'; import { UserToken } from '../../../types/token'; export type TokenDeleteConfirmation = 'inline' | 'modal'; @@ -110,9 +110,21 @@ export default class TokensFormItem extends React.PureComponent { {token.expirationDate ? : '–'}
+ {translate('notification.notification')}

{translate('notification.channel', channel)}

diff --git a/server/sonar-web/src/main/js/apps/account/security/Security.tsx b/server/sonar-web/src/main/js/apps/account/security/Security.tsx index 31c2a2189e7..305c83c2108 100644 --- a/server/sonar-web/src/main/js/apps/account/security/Security.tsx +++ b/server/sonar-web/src/main/js/apps/account/security/Security.tsx @@ -19,17 +19,13 @@ */ import * as React from 'react'; import { Helmet } from 'react-helmet-async'; -import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; +import { useCurrentLoginUser } from '../../../app/components/current-user/CurrentUserContext'; import ResetPasswordForm from '../../../components/common/ResetPasswordForm'; import { translate } from '../../../helpers/l10n'; -import { LoggedInUser } from '../../../types/users'; import Tokens from './Tokens'; -export interface SecurityProps { - currentUser: LoggedInUser; -} - -export function Security({ currentUser }: SecurityProps) { +export default function Security() { + const currentUser = useCurrentLoginUser(); return (
@@ -43,5 +39,3 @@ export function Security({ currentUser }: SecurityProps) {
); } - -export default withCurrentUserContext(Security); diff --git a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx index e15c5c56af4..566ab5435da 100644 --- a/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx @@ -63,7 +63,7 @@ const ui = { reloadButton: byRole('button', { name: 'reload' }), doneButton: byRole('button', { name: 'done' }), changeButton: byRole('button', { name: 'change_verb' }), - revokeButton: byRole('button', { name: 'users.tokens.revoke' }), + revokeButton: (name: string) => byRole('button', { name: `users.tokens.revoke_label.${name}` }), generateButton: byRole('button', { name: 'users.generate' }), sureButton: byRole('button', { name: 'users.tokens.sure' }), updateButton: byRole('button', { name: 'update_verb' }), @@ -541,7 +541,7 @@ describe('in manage mode', () => { expect(getTokensList()).toHaveLength(3); expect(ui.sureButton.query()).not.toBeInTheDocument(); - await user.click(ui.revokeButton.getAll()[1]); + await user.click(ui.revokeButton('test').get()); expect(await ui.sureButton.find()).toBeInTheDocument(); await act(() => user.click(ui.sureButton.get())); diff --git a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx index 0f0c017aea4..330de683c1a 100644 --- a/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx +++ b/server/sonar-web/src/main/js/apps/users/components/TokensForm.tsx @@ -22,13 +22,13 @@ import * as React from 'react'; import { getScannableProjects } from '../../../api/components'; import { generateToken, getTokens } from '../../../api/user-tokens'; import withCurrentUserContext from '../../../app/components/current-user/withCurrentUserContext'; -import { SubmitButton } from '../../../components/controls/buttons'; import Select, { LabelValueSelectOption } from '../../../components/controls/Select'; +import { SubmitButton } from '../../../components/controls/buttons'; import DeferredSpinner from '../../../components/ui/DeferredSpinner'; import { translate } from '../../../helpers/l10n'; import { - computeTokenExpirationDate, EXPIRATION_OPTIONS, + computeTokenExpirationDate, getAvailableExpirationOptions, } from '../../../helpers/tokens'; import { hasGlobalPermission } from '../../../helpers/users'; @@ -401,7 +401,7 @@ export class TokensForm extends React.PureComponent {
{translate('my_account.tokens_last_usage')} {translate('created')} {translate('my_account.tokens.expiration')} + {translate('actions')}
- {deleteConfirmation === 'modal' ? ( + {token.isExpired && ( + + )} + {!token.isExpired && deleteConfirmation === 'modal' && ( { values={{ token: {token.name} }} /> } - modalHeader={translate('users.tokens.revoke_token')} + modalHeader={translateWithParameters('users.tokens.revoke_label', token.name)} onConfirm={this.handleRevoke} > {({ onClick }) => ( @@ -129,16 +141,22 @@ export default class TokensFormItem extends React.PureComponent { className="button-red input-small" disabled={loading} onClick={onClick} - title={translate('users.tokens.revoke_token')} + aria-label={translateWithParameters('users.tokens.revoke_label', token.name)} > {translate('users.tokens.revoke')} )} - ) : ( + )} + {!token.isExpired && deleteConfirmation === 'inline' && (