loadMore={fetchNextPage}
ready={!isLoading}
total={data?.pages[0].page.total}
+ useMIUIButtons
/>
</main>
);
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { ActionCell, ContentCell, HelperHintIcon, Table, TableRow } from 'design-system';
import * as React from 'react';
import HelpTooltip from '../../components/controls/HelpTooltip';
import { translate } from '../../helpers/l10n';
}
export default function UsersList({ identityProviders, users, manageProvider }: Props) {
+ const header = (
+ <TableRow>
+ <ContentCell>{translate('users.user_name')}</ContentCell>
+ <ContentCell>{translate('my_profile.scm_accounts')}</ContentCell>
+ <ContentCell>{translate('users.last_connection')}</ContentCell>
+ <ContentCell>
+ {translate('users.last_sonarlint_connection')}
+ <HelpTooltip overlay={translate('users.last_sonarlint_connection.help_text')}>
+ <HelperHintIcon />
+ </HelpTooltip>
+ </ContentCell>
+ <ContentCell>{translate('my_profile.groups')}</ContentCell>
+ <ContentCell>{translate('users.tokens')}</ContentCell>
+ {(manageProvider === undefined || users.some((u) => !u.managed)) && (
+ <ActionCell>{translate('actions')}</ActionCell>
+ )}
+ </TableRow>
+ );
+
return (
- <div className="boxed-group boxed-group-inner">
- <table className="data zebra" id="users-list">
- <thead>
- <tr>
- <th className="nowrap">{translate('users.user_name')}</th>
- <th className="nowrap">{translate('my_profile.scm_accounts')}</th>
- <th className="nowrap">{translate('users.last_connection')}</th>
- <th className="nowrap">
- {translate('users.last_sonarlint_connection')}
- <HelpTooltip
- className="sw-ml-1"
- overlay={translate('users.last_sonarlint_connection.help_text')}
- />
- </th>
- <th className="nowrap">{translate('my_profile.groups')}</th>
- <th className="nowrap">{translate('users.tokens')}</th>
- {(manageProvider === undefined || users.some((u) => !u.managed)) && (
- <th className="nowrap">{translate('actions')}</th>
- )}
- </tr>
- </thead>
- <tbody>
- {users.map((user) => (
- <UserListItem
- identityProvider={identityProviders.find(
- (provider) => user.externalProvider === provider.key,
- )}
- key={user.login}
- user={user}
- manageProvider={manageProvider}
- />
- ))}
- </tbody>
- </table>
- </div>
+ <Table columnCount={7} header={header} id="users-list">
+ {users.map((user) => (
+ <UserListItem
+ identityProvider={identityProviders.find(
+ (provider) => user.externalProvider === provider.key,
+ )}
+ key={user.login}
+ user={user}
+ manageProvider={manageProvider}
+ />
+ ))}
+ </Table>
);
}
aliceUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.alice.merveille' }),
aliceUpdateButton: byRole('button', { name: 'users.manage_user.alice.merveille' }),
denisUpdateButton: byRole('button', { name: 'users.manage_user.denis.villeneuve' }),
- alicedDeactivateButton: byRole('button', { name: 'users.deactivate' }),
+ alicedDeactivateButton: byRole('menuitem', { name: 'users.deactivate' }),
bobUpdateGroupButton: byRole('button', { name: 'users.update_users_groups.bob.marley' }),
bobUpdateButton: byRole('button', { name: 'users.manage_user.bob.marley' }),
scmAddButton: byRole('button', { name: 'add_verb' }),
name: `remove_x.users.create_user.scm_account_${value ? `x.${value}` : 'new'}`,
}),
userRows: byRole('row', {
- name: (accessibleName) => /^[A-Z]+ /.test(accessibleName),
+ name: (accessibleName) => /^[A-Z]+[a-z]*/.test(accessibleName),
}),
aliceRow: byRole('row', {
- name: (accessibleName) => accessibleName.startsWith('AM Alice Merveille alice.merveille '),
+ name: (accessibleName) =>
+ accessibleName.startsWith('Alice Merveille Alice Merveille alice.merveille '),
}),
aliceRowWithLocalBadge: byRole('row', {
name: (accessibleName) =>
accessibleName.startsWith(
- 'AM Alice Merveille alice.merveille alice.merveille@wonderland.com local ',
+ 'Alice Merveille Alice Merveille alice.merveille alice.merveille@wonderland.com local ',
),
}),
bobRow: byRole('row', {
- name: (accessibleName) => accessibleName.startsWith('BM Bob Marley bob.marley '),
+ name: (accessibleName) => accessibleName.startsWith('Bob Marley Bob Marley bob.marley '),
}),
charlieRow: byRole('row', {
- name: (accessibleName) => accessibleName.startsWith('CC Charlie Cox charlie.cox'),
+ name: (accessibleName) => accessibleName.startsWith('Charlie Cox Charlie Cox charlie.cox'),
}),
denisRow: byRole('row', {
- name: (accessibleName) => accessibleName.startsWith('DV Denis Villeneuve denis.villeneuve '),
+ name: (accessibleName) =>
+ accessibleName.startsWith('Denis Villeneuve Denis Villeneuve denis.villeneuve '),
}),
evaRow: byRole('row', {
- name: (accessibleName) => accessibleName.startsWith('EG Eva Green eva.green '),
+ name: (accessibleName) => accessibleName.startsWith('Eva Green Eva Green eva.green '),
}),
franckRow: byRole('row', {
- name: (accessibleName) => accessibleName.startsWith('FG Franck Grillo franck.grillo '),
+ name: (accessibleName) =>
+ accessibleName.startsWith('Franck Grillo Franck Grillo franck.grillo '),
}),
jackRow: byRole('row', { name: /Jack/ }),
renderUsersApp();
await user.click(await ui.aliceUpdateButton.find());
- await user.click(await ui.aliceRow.byRole('button', { name: 'update_details' }).find());
+ await user.click(await ui.aliceRow.byRole('menuitem', { name: 'update_details' }).find());
expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
expect(ui.userNameInput.get()).toHaveValue('Alice Merveille');
renderUsersApp();
await user.click(await ui.aliceUpdateButton.find());
- await user.click(await ui.aliceRow.byRole('button', { name: 'users.deactivate' }).find());
+ await user.click(await ui.aliceRow.byRole('menuitem', { name: 'users.deactivate' }).find());
expect(await ui.dialogDeactivateUser.find()).toBeInTheDocument();
expect(ui.deleteUserAlert.query()).not.toBeInTheDocument();
await user.click(ui.deleteUserCheckbox.get());
await user.click(await ui.aliceUpdateButton.find());
await user.click(
- await ui.aliceRow.byRole('button', { name: 'my_profile.password.title' }).find(),
+ await ui.aliceRow.byRole('menuitem', { name: 'my_profile.password.title' }).find(),
);
expect(await ui.dialogPasswords.find()).toBeInTheDocument();
renderUsersApp([], currentUser);
await user.click(await ui.denisUpdateButton.find());
- await user.click(await ui.denisRow.byRole('button', { name: 'update_details' }).find());
+ await user.click(await ui.denisRow.byRole('menuitem', { name: 'update_details' }).find());
expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
expect(ui.userNameInput.get()).toHaveValue('Denis Villeneuve');
expect(await ui.aliceRow.byText('alice.merveille@wonderland.com').find()).toBeInTheDocument();
await user.click(await ui.aliceUpdateButton.find());
- await user.click(await ui.aliceRow.byRole('button', { name: 'update_details' }).find());
+ await user.click(await ui.aliceRow.byRole('menuitem', { name: 'update_details' }).find());
expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
expect(ui.emailInput.get()).toHaveValue('alice.merveille@wonderland.com');
ui.bobRow.byRole('button', { name: 'my_profile.password.title' }).query(),
).not.toBeInTheDocument();
- await user.click(ui.bobRow.byRole('button', { name: 'update_scm' }).get());
+ await user.click(ui.bobRow.byRole('menuitem', { name: 'update_scm' }).get());
expect(ui.userNameInput.get()).toBeDisabled();
expect(ui.emailInput.get()).toBeDisabled();
// user update dialog should be accessible
await user.click(await ui.aliceUpdateButton.find());
- await user.click(await ui.aliceRow.byRole('button', { name: 'update_details' }).find());
+ await user.click(await ui.aliceRow.byRole('menuitem', { name: 'update_details' }).find());
expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
await expect(await ui.dialogUpdateUser.find()).toHaveNoA11yViolations();
await user.click(ui.cancelButton.get());
// user password dialog should be accessible
await user.click(await ui.aliceUpdateButton.find());
await user.click(
- await ui.aliceRow.byRole('button', { name: 'my_profile.password.title' }).find(),
+ await ui.aliceRow.byRole('menuitem', { name: 'my_profile.password.title' }).find(),
);
expect(await ui.dialogPasswords.find()).toBeInTheDocument();
await expect(await ui.dialogPasswords.find()).toHaveNoA11yViolations();
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+
+import {
+ ActionsDropdown,
+ ItemButton,
+ ItemDangerButton,
+ ItemDivider,
+ PopupZLevel,
+} from 'design-system';
import * as React from 'react';
-import ActionsDropdown, {
- ActionsDropdownDivider,
- ActionsDropdownItem,
-} from '../../../components/controls/ActionsDropdown';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Provider } from '../../../types/types';
import { RestUserDetailed, isUserActive } from '../../../types/users';
return (
<>
- <ActionsDropdown label={translateWithParameters('users.manage_user', user.login)}>
- <ActionsDropdownItem className="js-user-update" onClick={() => setOpenForm('update')}>
+ <ActionsDropdown
+ id={`user-settings-action-dropdown-${user.login}`}
+ toggleClassName="it__user-actions-toggle"
+ allowResizing
+ ariaLabel={translateWithParameters('users.manage_user', user.login)}
+ zLevel={PopupZLevel.Global}
+ >
+ <ItemButton className="it__user-update" onClick={() => setOpenForm('update')}>
{isInstanceManaged ? translate('update_scm') : translate('update_details')}
- </ActionsDropdownItem>
+ </ItemButton>
{!isInstanceManaged && user.local && (
- <ActionsDropdownItem
- className="js-user-change-password"
- onClick={() => setOpenForm('password')}
- >
+ <ItemButton className="it__user-change-password" onClick={() => setOpenForm('password')}>
{translate('my_profile.password.title')}
- </ActionsDropdownItem>
+ </ItemButton>
)}
-
- {isUserActive(user) && !isInstanceManaged && <ActionsDropdownDivider />}
+ {isUserActive(user) && !isInstanceManaged && <ItemDivider />}
{isUserActive(user) && (!isInstanceManaged || isUserLocal) && (
- <ActionsDropdownItem
- className="js-user-deactivate"
- destructive
+ <ItemDangerButton
+ className="it__user-deactivate"
onClick={() => setOpenForm('deactivate')}
>
{translate('users.deactivate')}
- </ActionsDropdownItem>
+ </ItemDangerButton>
)}
</ActionsDropdown>
{openForm === 'deactivate' && isUserActive(user) && (
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import {
+ Avatar,
+ ContentCell,
+ InteractiveIcon,
+ MenuIcon,
+ Spinner,
+ TableRow,
+ Tooltip,
+} from 'design-system';
import * as React from 'react';
-import { ButtonIcon } from '../../../components/controls/buttons';
-import BulletListIcon from '../../../components/icons/BulletListIcon';
import DateFromNow from '../../../components/intl/DateFromNow';
-import LegacyAvatar from '../../../components/ui/LegacyAvatar';
-import Spinner from '../../../components/ui/Spinner';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { useUserGroupsCountQuery, useUserTokensQuery } from '../../../queries/users';
import { IdentityProvider, Provider } from '../../../types/types';
const { data: groupsCount, isLoading: groupsAreLoading } = useUserGroupsCountQuery(login);
return (
- <tr>
- <td className="thin text-middle">
+ <TableRow>
+ <ContentCell>
<div className="sw-flex sw-items-center">
- <LegacyAvatar className="sw-shrink-0 sw-mr-4" hash={avatar} name={name} size={36} />
+ <Avatar className="sw-shrink-0 sw-mr-4" hash={avatar} name={name} size="md" />
<UserListItemIdentity
identityProvider={identityProvider}
user={user}
manageProvider={manageProvider}
/>
</div>
- </td>
- <td className="thin text-middle">
+ </ContentCell>
+ <ContentCell>
<UserScmAccounts scmAccounts={scmAccounts || []} />
- </td>
- <td className="thin nowrap text-middle">
+ </ContentCell>
+ <ContentCell>
<DateFromNow date={sonarQubeLastConnectionDate ?? ''} hourPrecision />
- </td>
- <td className="thin nowrap text-middle">
+ </ContentCell>
+ <ContentCell>
<DateFromNow date={sonarLintLastConnectionDate ?? ''} hourPrecision />
- </td>
- <td className="thin nowrap text-middle">
+ </ContentCell>
+ <ContentCell>
<Spinner loading={groupsAreLoading}>
{groupsCount}
{manageProvider === undefined && (
- <ButtonIcon
- aria-label={translateWithParameters('users.update_users_groups', user.login)}
- className="js-user-groups spacer-left button-small"
- onClick={() => setOpenGroupForm(true)}
- tooltip={translate('users.update_groups')}
- >
- <BulletListIcon />
- </ButtonIcon>
+ <Tooltip overlay={translate('users.update_groups')}>
+ <InteractiveIcon
+ Icon={MenuIcon}
+ className="it__user-groups sw-ml-2"
+ aria-label={translateWithParameters('users.update_users_groups', user.login)}
+ onClick={() => setOpenGroupForm(true)}
+ size="small"
+ />
+ </Tooltip>
)}
</Spinner>
- </td>
- <td className="thin nowrap text-middle">
+ </ContentCell>
+ <ContentCell>
<Spinner loading={tokensAreLoading}>
{tokens?.length}
- <ButtonIcon
- className="js-user-tokens spacer-left button-small"
- onClick={() => setOpenTokenForm(true)}
- tooltip={translateWithParameters('users.update_tokens')}
- aria-label={translateWithParameters('users.update_tokens_for_x', name ?? login)}
- >
- <BulletListIcon />
- </ButtonIcon>
+ <Tooltip overlay={translateWithParameters('users.update_tokens')}>
+ <InteractiveIcon
+ Icon={MenuIcon}
+ className="it__user-tokens sw-ml-2"
+ aria-label={translateWithParameters('users.update_tokens_for_x', name ?? login)}
+ onClick={() => setOpenTokenForm(true)}
+ size="small"
+ />
+ </Tooltip>
</Spinner>
- </td>
+ </ContentCell>
- <td className="thin nowrap text-right text-middle">
+ <ContentCell>
<UserActions user={user} manageProvider={manageProvider} />
- </td>
+ </ContentCell>
{openTokenForm && <TokensFormModal onClose={() => setOpenTokenForm(false)} user={user} />}
{openGroupForm && <GroupsForm onClose={() => setOpenGroupForm(false)} user={user} />}
- </tr>
+ </TableRow>
);
}
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { getTextColor } from 'design-system';
+
+import { Badge, Note, getTextColor } from 'design-system';
import * as React from 'react';
import { colors } from '../../../app/theme';
import { translate } from '../../../helpers/l10n';
export default function UserListItemIdentity({ identityProvider, user, manageProvider }: Props) {
return (
<div>
- <div>
- <strong className="js-user-name">{user.name}</strong>
- <span className="js-user-login note little-spacer-left">{user.login}</span>
+ <div className="sw-flex sw-flex-col">
+ <strong className="it__user-name sw-body-sm-highlight">{user.name}</strong>
+ <Note className="it__user-login">{user.login}</Note>
</div>
- {user.email && <div className="js-user-email little-spacer-top">{user.email}</div>}
+ {user.email && <div className="it__user-email sw-mt-1">{user.email}</div>}
{!user.local && user.externalProvider !== 'sonarqube' && (
<ExternalProvider identityProvider={identityProvider} user={user} />
)}
- {!user.managed && manageProvider !== undefined && (
- <span className="badge">{translate('local')}</span>
- )}
+ {!user.managed && manageProvider !== undefined && <Badge>{translate('local')}</Badge>}
</div>
);
}
export function ExternalProvider({ identityProvider, user }: Omit<Props, 'manageProvider'>) {
if (!identityProvider) {
return (
- <div className="js-user-identity-provider little-spacer-top">
+ <div className="it__user-identity-provider sw-mt-1">
<span>
{user.externalProvider}: {user.externalLogin}
</span>
}
return (
- <div className="js-user-identity-provider little-spacer-top">
+ <div className="it__user-identity-provider sw-mt-1">
<div
- className="identity-provider"
style={{
backgroundColor: identityProvider.backgroundColor,
color: getTextColor(identityProvider.backgroundColor, colors.secondFontColor),
>
<img
alt={identityProvider.name}
- className="little-spacer-right"
+ className="sw-mr-1"
height="14"
src={getBaseUrl() + identityProvider.iconPath}
width="14"
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import { Link } from 'design-system';
import * as React from 'react';
import { translateWithParameters } from '../../../helpers/l10n';
const { scmAccounts } = this.props;
const limit = scmAccounts.length > SCM_LIMIT ? SCM_LIMIT - 1 : SCM_LIMIT;
return (
- <ul className="js-scm-accounts">
- {scmAccounts.slice(0, limit).map((scmAccount, idx) => (
- <li className="little-spacer-bottom" key={idx}>
+ <ul className="it__scm-accounts">
+ {scmAccounts.slice(0, limit).map((scmAccount) => (
+ <li className="sw-mb-1" key={scmAccount}>
{scmAccount}
</li>
))}
{scmAccounts.length > SCM_LIMIT &&
(this.state.showMore ? (
- scmAccounts.slice(limit).map((scmAccount, idx) => (
- <li className="little-spacer-bottom" key={idx + limit}>
+ scmAccounts.slice(limit).map((scmAccount) => (
+ <li className="sw-mb-1" key={scmAccount}>
{scmAccount}
</li>
))
) : (
- <li className="little-spacer-bottom">
- <a className="js-user-more-scm" href="#" onClick={this.toggleShowMore}>
+ <li className="sw-mb-1">
+ <Link to="#" onClick={this.toggleShowMore}>
{translateWithParameters('more_x', scmAccounts.length - limit)}
- </a>
+ </Link>
</li>
))}
</ul>