mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-12 07:30:46 +02:00
Merge pull request #23743 from overleaf/ii-bs5-manage-group-members
[web] BS5 Group members management GitOrigin-RevId: fab24ee6f6de07aa64887e123df930593fcec6a2
This commit is contained in:
@@ -38,6 +38,8 @@ async function manageGroupMembers(req, res, next) {
|
||||
'flexible-group-licensing'
|
||||
)
|
||||
|
||||
await SplitTestHandler.promises.getAssignment(req, res, 'bootstrap-5-groups')
|
||||
|
||||
const plan = PlansLocator.findLocalPlanInSettings(subscription.planCode)
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
const isAdmin = subscription.admin_id.toString() === userId
|
||||
|
||||
@@ -2,6 +2,10 @@ extends ../layout-marketing
|
||||
|
||||
block entrypointVar
|
||||
- entrypoint = 'pages/user/subscription/group-management/group-members'
|
||||
|
||||
block vars
|
||||
- bootstrap5PageStatus = 'enabled' // One of 'disabled', 'enabled', and 'queryStringOnly'
|
||||
- bootstrap5PageSplitTest = 'bootstrap-5-groups'
|
||||
|
||||
block append meta
|
||||
meta(name="ol-users", data-type="json", content=users)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import IconButton from '@/features/ui/components/bootstrap-5/icon-button'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
type BackButtonProps = {
|
||||
href: string
|
||||
accessibilityLabel: string
|
||||
}
|
||||
|
||||
function BackButton({ href, accessibilityLabel }: BackButtonProps) {
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<a href={href} className="back-btn">
|
||||
<MaterialIcon
|
||||
type="arrow_back"
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
/>
|
||||
</a>
|
||||
}
|
||||
bs5={
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
href={href}
|
||||
size="lg"
|
||||
icon="arrow_back"
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default BackButton
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
export type APIError = {
|
||||
message?: string
|
||||
@@ -17,15 +18,14 @@ export default function ErrorAlert({ error }: ErrorAlertProps) {
|
||||
|
||||
if (error.message) {
|
||||
return (
|
||||
<div className="alert alert-danger">
|
||||
{t('error')}: {error.message}
|
||||
</div>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={`${t('error')}: ${error.message}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="alert alert-danger">
|
||||
{t('generic_something_went_wrong')}
|
||||
</div>
|
||||
<OLNotification type="error" content={t('generic_something_went_wrong')} />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Button, Col, Form, FormControl, Row } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import MaterialIcon from '../../../shared/components/material-icon'
|
||||
import useWaitForI18n from '../../../shared/hooks/use-wait-for-i18n'
|
||||
import getMeta from '../../../utils/meta'
|
||||
import { useGroupMembersContext } from '../context/group-members-context'
|
||||
@@ -9,6 +7,13 @@ import ErrorAlert from './error-alert'
|
||||
import MembersList from './members-table/members-list'
|
||||
import { useFeatureFlag } from '@/shared/context/split-test-context'
|
||||
import { sendMB } from '../../../infrastructure/event-tracking'
|
||||
import BackButton from '@/features/group-management/components/back-button'
|
||||
import OLRow from '@/features/ui/components/ol/ol-row'
|
||||
import OLCol from '@/features/ui/components/ol/ol-col'
|
||||
import OLCard from '@/features/ui/components/ol/ol-card'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
|
||||
import OLFormText from '@/features/ui/components/ol/ol-form-text'
|
||||
|
||||
export default function GroupMembers() {
|
||||
const { isReady } = useWaitForI18n()
|
||||
@@ -48,7 +53,7 @@ export default function GroupMembers() {
|
||||
return null
|
||||
}
|
||||
|
||||
const onAddMembersSubmit = (e: React.FormEvent<Form>) => {
|
||||
const onAddMembersSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
addMembers(emailString)
|
||||
}
|
||||
@@ -98,36 +103,37 @@ export default function GroupMembers() {
|
||||
|
||||
return (
|
||||
<div className="container">
|
||||
<Row>
|
||||
<Col md={10} mdOffset={1}>
|
||||
<h1>
|
||||
<a href="/user/subscription" className="back-btn">
|
||||
<MaterialIcon
|
||||
type="arrow_back"
|
||||
accessibilityLabel={t('back_to_subscription')}
|
||||
/>
|
||||
</a>{' '}
|
||||
{groupName || t('group_subscription')}
|
||||
</h1>
|
||||
<div className="card">
|
||||
<div className="page-header">
|
||||
<OLRow>
|
||||
<OLCol lg={{ span: 10, offset: 1 }}>
|
||||
<div className="group-heading" data-testid="group-heading">
|
||||
<BackButton
|
||||
href="/user/subscription"
|
||||
accessibilityLabel={t('back_to_subscription')}
|
||||
/>
|
||||
<h1 className="heading">{groupName || t('group_subscription')}</h1>
|
||||
</div>
|
||||
<OLCard>
|
||||
<div
|
||||
className="page-header mb-4"
|
||||
data-testid="page-header-members-details"
|
||||
>
|
||||
<div className="pull-right">
|
||||
{selectedUsers.length === 0 && groupSizeDetails()}
|
||||
{removeMemberLoading ? (
|
||||
<Button bsStyle="danger" disabled>
|
||||
<OLButton variant="danger" disabled>
|
||||
{t('removing')}…
|
||||
</Button>
|
||||
</OLButton>
|
||||
) : (
|
||||
<>
|
||||
{selectedUsers.length > 0 && (
|
||||
<Button bsStyle="danger" onClick={removeMembers}>
|
||||
<OLButton variant="danger" onClick={removeMembers}>
|
||||
{t('remove_from_group')}
|
||||
</Button>
|
||||
</OLButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<h3>{t('members_management')}</h3>
|
||||
<h2 className="h3 mt-0">{t('members_management')}</h2>
|
||||
</div>
|
||||
<div className="row-spaced-small">
|
||||
<ErrorAlert error={removeMemberError} />
|
||||
@@ -145,58 +151,67 @@ export default function GroupMembers() {
|
||||
: t('add_more_members')}
|
||||
</p>
|
||||
<ErrorAlert error={inviteError} />
|
||||
<Form horizontal onSubmit={onAddMembersSubmit} className="form">
|
||||
<Row>
|
||||
<Col xs={6}>
|
||||
<FormControl
|
||||
<form onSubmit={onAddMembersSubmit}>
|
||||
<OLRow>
|
||||
<OLCol xs={6}>
|
||||
<OLFormControl
|
||||
type="input"
|
||||
placeholder="jane@example.com, joe@example.com"
|
||||
aria-describedby="add-members-description"
|
||||
value={emailString}
|
||||
onChange={handleEmailsChange}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={4}>
|
||||
{inviteMemberLoading ? (
|
||||
<Button bsStyle="primary" disabled>
|
||||
{isFlexibleGroupLicensing
|
||||
? t('inviting')
|
||||
: t('adding')}
|
||||
…
|
||||
</Button>
|
||||
) : (
|
||||
<Button bsStyle="primary" onClick={onAddMembersSubmit}>
|
||||
{isFlexibleGroupLicensing ? t('invite') : t('add')}
|
||||
</Button>
|
||||
)}
|
||||
</Col>
|
||||
<Col xs={2}>
|
||||
</OLCol>
|
||||
<OLCol xs={4}>
|
||||
<OLButton
|
||||
variant="primary"
|
||||
onClick={onAddMembersSubmit}
|
||||
isLoading={inviteMemberLoading}
|
||||
bs3Props={{
|
||||
loading: inviteMemberLoading ? (
|
||||
<>
|
||||
{isFlexibleGroupLicensing
|
||||
? t('inviting')
|
||||
: t('adding')}
|
||||
…
|
||||
</>
|
||||
) : isFlexibleGroupLicensing ? (
|
||||
t('invite')
|
||||
) : (
|
||||
t('add')
|
||||
),
|
||||
}}
|
||||
>
|
||||
{isFlexibleGroupLicensing ? t('invite') : t('add')}
|
||||
</OLButton>
|
||||
</OLCol>
|
||||
<OLCol xs={2}>
|
||||
<a href={paths.exportMembers}>{t('export_csv')}</a>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col xs={8}>
|
||||
<span className="help-block">
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
<OLRow>
|
||||
<OLCol xs={8}>
|
||||
<OLFormText bs3Props={{ className: 'help-block' }}>
|
||||
{t('add_comma_separated_emails_help')}
|
||||
</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</OLFormText>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
{users.length >= groupSize && users.length > 0 && (
|
||||
<>
|
||||
<ErrorAlert error={inviteError} />
|
||||
<Row>
|
||||
<Col xs={2} xsOffset={10}>
|
||||
<OLRow>
|
||||
<OLCol xs={{ span: 2, offset: 10 }}>
|
||||
<a href={paths.exportMembers}>{t('export_csv')}</a>
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</OLCard>
|
||||
</OLCol>
|
||||
</OLRow>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+117
-47
@@ -6,7 +6,13 @@ import {
|
||||
type SetStateAction,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Dropdown, MenuItem } from 'react-bootstrap'
|
||||
import { Dropdown as BS3Dropdown, MenuItem } from 'react-bootstrap'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenu,
|
||||
DropdownToggle,
|
||||
} from '@/features/ui/components/bootstrap-5/dropdown-menu'
|
||||
import { User } from '../../../../../../types/group-management/user'
|
||||
import useAsync from '@/shared/hooks/use-async'
|
||||
import { type FetchError, postJSON } from '@/infrastructure/fetch-json'
|
||||
@@ -14,6 +20,10 @@ import Icon from '@/shared/components/icon'
|
||||
import { GroupUserAlert } from '../../utils/types'
|
||||
import { useGroupMembersContext } from '../../context/group-members-context'
|
||||
import getMeta from '@/utils/meta'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item'
|
||||
import { Spinner } from 'react-bootstrap-5'
|
||||
|
||||
type resendInviteResponse = {
|
||||
success: boolean
|
||||
@@ -191,12 +201,10 @@ export default function DropdownButton({
|
||||
<MenuItemButton
|
||||
onClick={onResendGroupInviteClick}
|
||||
key="resend-group-invite-action"
|
||||
isLoading={isResendingGroupInvite}
|
||||
data-testid="resend-group-invite-action"
|
||||
>
|
||||
{t('resend_group_invite')}
|
||||
{isResendingGroupInvite ? (
|
||||
<Icon type="spinner" spin style={{ marginLeft: '5px' }} />
|
||||
) : null}
|
||||
</MenuItemButton>
|
||||
)
|
||||
}
|
||||
@@ -205,12 +213,10 @@ export default function DropdownButton({
|
||||
<MenuItemButton
|
||||
onClick={onResendManagedUserInviteClick}
|
||||
key="resend-managed-user-invite-action"
|
||||
isLoading={isResendingManagedUserInvite}
|
||||
data-testid="resend-managed-user-invite-action"
|
||||
>
|
||||
{t('resend_managed_user_invite')}
|
||||
{isResendingManagedUserInvite ? (
|
||||
<Icon type="spinner" spin style={{ marginLeft: '5px' }} />
|
||||
) : null}
|
||||
</MenuItemButton>
|
||||
)
|
||||
}
|
||||
@@ -230,12 +236,10 @@ export default function DropdownButton({
|
||||
<MenuItemButton
|
||||
onClick={onResendSSOLinkInviteClick}
|
||||
key="resend-sso-link-invite-action"
|
||||
isLoading={isResendingSSOLinkInvite}
|
||||
data-testid="resend-sso-link-invite-action"
|
||||
>
|
||||
{t('resend_link_sso')}
|
||||
{isResendingSSOLinkInvite ? (
|
||||
<Icon type="spinner" spin style={{ marginLeft: '5px' }} />
|
||||
) : null}
|
||||
</MenuItemButton>
|
||||
)
|
||||
}
|
||||
@@ -257,6 +261,7 @@ export default function DropdownButton({
|
||||
data-testid="remove-user-action"
|
||||
onClick={onRemoveFromGroup}
|
||||
className="delete-user-action"
|
||||
variant="danger"
|
||||
>
|
||||
{t('remove_from_group')}
|
||||
</MenuItemButton>
|
||||
@@ -265,54 +270,119 @@ export default function DropdownButton({
|
||||
|
||||
if (buttons.length === 0) {
|
||||
buttons.push(
|
||||
<MenuItem key="no-actions-available" data-testid="no-actions-available">
|
||||
<span className="text-muted">{t('no_actions')}</span>
|
||||
</MenuItem>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<MenuItem
|
||||
key="no-actions-available"
|
||||
data-testid="no-actions-available"
|
||||
>
|
||||
<span className="text-muted">{t('no_actions')}</span>
|
||||
</MenuItem>
|
||||
}
|
||||
bs5={
|
||||
<DropdownListItem>
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
data-testid="no-actions-available"
|
||||
disabled
|
||||
>
|
||||
{t('no_actions')}
|
||||
</DropdownItem>
|
||||
</DropdownListItem>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="managed-user-actions">
|
||||
<Dropdown
|
||||
id={`managed-user-dropdown-${user.email}`}
|
||||
open={isOpened}
|
||||
onToggle={open => setIsOpened(open)}
|
||||
>
|
||||
<Dropdown.Toggle
|
||||
bsStyle={null}
|
||||
className="btn btn-link action-btn"
|
||||
noCaret
|
||||
>
|
||||
<i
|
||||
className="fa fa-ellipsis-v"
|
||||
aria-hidden="true"
|
||||
aria-label={t('actions')}
|
||||
/>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right managed-user-dropdown-menu">
|
||||
{buttons}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
</span>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<div className="managed-user-actions">
|
||||
<BS3Dropdown
|
||||
id={`managed-user-dropdown-${user.email}`}
|
||||
open={isOpened}
|
||||
onToggle={open => setIsOpened(open)}
|
||||
>
|
||||
<BS3Dropdown.Toggle
|
||||
bsStyle={null}
|
||||
className="btn btn-link action-btn"
|
||||
noCaret
|
||||
>
|
||||
<Icon type="ellipsis-v" accessibilityLabel={t('actions')} />
|
||||
</BS3Dropdown.Toggle>
|
||||
<BS3Dropdown.Menu className="dropdown-menu-right managed-user-dropdown-menu">
|
||||
{buttons}
|
||||
</BS3Dropdown.Menu>
|
||||
</BS3Dropdown>
|
||||
</div>
|
||||
}
|
||||
bs5={
|
||||
<Dropdown align="end">
|
||||
<DropdownToggle
|
||||
id={`managed-user-dropdown-${user.email}`}
|
||||
bsPrefix="dropdown-table-button-toggle"
|
||||
>
|
||||
<MaterialIcon type="more_vert" accessibilityLabel={t('actions')} />
|
||||
</DropdownToggle>
|
||||
<DropdownMenu flip={false}>{buttons}</DropdownMenu>
|
||||
</Dropdown>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type MenuItemButtonProps = {
|
||||
isLoading?: boolean
|
||||
'data-testid'?: string
|
||||
} & Pick<ComponentProps<'button'>, 'children' | 'onClick' | 'className'> &
|
||||
Pick<ComponentProps<typeof DropdownItem>, 'variant'>
|
||||
|
||||
function MenuItemButton({
|
||||
children,
|
||||
onClick,
|
||||
className,
|
||||
...buttonProps
|
||||
}: ComponentProps<'button'>) {
|
||||
isLoading,
|
||||
variant,
|
||||
'data-testid': dataTestId,
|
||||
}: MenuItemButtonProps) {
|
||||
return (
|
||||
<li role="presentation" className={className}>
|
||||
<button
|
||||
className="managed-user-menu-item-button"
|
||||
role="menuitem"
|
||||
onClick={onClick}
|
||||
{...buttonProps}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</li>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<li role="presentation" className={className}>
|
||||
<button
|
||||
className="managed-user-menu-item-button"
|
||||
role="menuitem"
|
||||
onClick={onClick}
|
||||
data-testid={dataTestId}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</li>
|
||||
}
|
||||
bs5={
|
||||
<DropdownListItem>
|
||||
<DropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
onClick={onClick}
|
||||
leadingIcon={
|
||||
isLoading ? (
|
||||
<Spinner
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
size="sm"
|
||||
role="status"
|
||||
/>
|
||||
) : null
|
||||
}
|
||||
data-testid={dataTestId}
|
||||
variant={variant}
|
||||
>
|
||||
{children}
|
||||
</DropdownItem>
|
||||
</DropdownListItem>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+135
-135
@@ -1,8 +1,7 @@
|
||||
import { type PropsWithChildren, useState } from 'react'
|
||||
import { Alert, type AlertProps } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { Trans } from 'react-i18next'
|
||||
import type { GroupUserAlertVariant } from '../../utils/types'
|
||||
import NotificationScrolledTo from '@/shared/components/notification-scrolled-to'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
|
||||
type GroupUsersListAlertProps = {
|
||||
variant: GroupUserAlertVariant
|
||||
@@ -86,20 +85,25 @@ function ResendManagedUserInviteSuccess({
|
||||
userEmail,
|
||||
}: GroupUsersListAlertComponentProps) {
|
||||
return (
|
||||
<AlertComponent bsStyle="success" onDismiss={onDismiss}>
|
||||
<Trans
|
||||
i18nKey="managed_user_invite_has_been_sent_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</AlertComponent>
|
||||
<OLNotification
|
||||
type="success"
|
||||
content={
|
||||
<Trans
|
||||
i18nKey="managed_user_invite_has_been_sent_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
isDismissible
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -108,20 +112,25 @@ function ResendSSOLinkInviteSuccess({
|
||||
userEmail,
|
||||
}: GroupUsersListAlertComponentProps) {
|
||||
return (
|
||||
<AlertComponent bsStyle="success" onDismiss={onDismiss}>
|
||||
<Trans
|
||||
i18nKey="sso_link_invite_has_been_sent_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</AlertComponent>
|
||||
<OLNotification
|
||||
type="success"
|
||||
content={
|
||||
<Trans
|
||||
i18nKey="sso_link_invite_has_been_sent_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
isDismissible
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -130,20 +139,25 @@ function FailedToResendManagedInvite({
|
||||
userEmail,
|
||||
}: GroupUsersListAlertComponentProps) {
|
||||
return (
|
||||
<AlertComponent bsStyle="danger" onDismiss={onDismiss}>
|
||||
<Trans
|
||||
i18nKey="failed_to_send_managed_user_invite_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</AlertComponent>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={
|
||||
<Trans
|
||||
i18nKey="failed_to_send_managed_user_invite_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
isDismissible
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
)
|
||||
}
|
||||
function FailedToResendSSOLink({
|
||||
@@ -151,20 +165,25 @@ function FailedToResendSSOLink({
|
||||
userEmail,
|
||||
}: GroupUsersListAlertComponentProps) {
|
||||
return (
|
||||
<AlertComponent bsStyle="danger" onDismiss={onDismiss}>
|
||||
<Trans
|
||||
i18nKey="failed_to_send_sso_link_invite_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</AlertComponent>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={
|
||||
<Trans
|
||||
i18nKey="failed_to_send_sso_link_invite_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
isDismissible
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -173,20 +192,25 @@ function ResendGroupInviteSuccess({
|
||||
userEmail,
|
||||
}: GroupUsersListAlertComponentProps) {
|
||||
return (
|
||||
<AlertComponent bsStyle="success" onDismiss={onDismiss}>
|
||||
<Trans
|
||||
i18nKey="group_invite_has_been_sent_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</AlertComponent>
|
||||
<OLNotification
|
||||
type="success"
|
||||
content={
|
||||
<Trans
|
||||
i18nKey="group_invite_has_been_sent_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
isDismissible
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -195,20 +219,25 @@ function FailedToResendGroupInvite({
|
||||
userEmail,
|
||||
}: GroupUsersListAlertComponentProps) {
|
||||
return (
|
||||
<AlertComponent bsStyle="danger" onDismiss={onDismiss}>
|
||||
<Trans
|
||||
i18nKey="failed_to_send_group_invite_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</AlertComponent>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={
|
||||
<Trans
|
||||
i18nKey="failed_to_send_group_invite_to_email"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
isDismissible
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -217,53 +246,24 @@ function TooManyRequests({
|
||||
userEmail,
|
||||
}: GroupUsersListAlertComponentProps) {
|
||||
return (
|
||||
<AlertComponent bsStyle="danger" onDismiss={onDismiss}>
|
||||
<Trans
|
||||
i18nKey="an_email_has_already_been_sent_to"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
</AlertComponent>
|
||||
)
|
||||
}
|
||||
|
||||
type AlertComponentProps = PropsWithChildren<{
|
||||
bsStyle: AlertProps['bsStyle']
|
||||
onDismiss: AlertProps['onDismiss']
|
||||
}>
|
||||
|
||||
function AlertComponent({ bsStyle, onDismiss, children }: AlertComponentProps) {
|
||||
const [show, setShow] = useState(true)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleDismiss = () => {
|
||||
if (onDismiss) {
|
||||
onDismiss()
|
||||
}
|
||||
|
||||
setShow(false)
|
||||
}
|
||||
|
||||
if (!show) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert bsStyle={bsStyle} className="managed-users-list-alert">
|
||||
<span>{children}</span>
|
||||
<div className="managed-users-list-alert-close">
|
||||
<button type="button" className="close" onClick={handleDismiss}>
|
||||
<span aria-hidden="true">×</span>
|
||||
<span className="sr-only">{t('close')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</Alert>
|
||||
<OLNotification
|
||||
type="error"
|
||||
content={
|
||||
<Trans
|
||||
i18nKey="an_email_has_already_been_sent_to"
|
||||
values={{
|
||||
email: userEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
}
|
||||
isDismissible
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+58
-35
@@ -2,14 +2,18 @@ import moment from 'moment'
|
||||
import { type Dispatch, type SetStateAction } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { User } from '../../../../../../types/group-management/user'
|
||||
import Badge from '../../../../shared/components/badge'
|
||||
import Tooltip from '../../../../shared/components/tooltip'
|
||||
import type { GroupUserAlert } from '../../utils/types'
|
||||
import ManagedUserStatus from './managed-user-status'
|
||||
import SSOStatus from './sso-status'
|
||||
import DropdownButton from './dropdown-button'
|
||||
import SelectUserCheckbox from './select-user-checkbox'
|
||||
import getMeta from '@/utils/meta'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
import OLTag from '@/features/ui/components/ol/ol-tag'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import classnames from 'classnames'
|
||||
|
||||
type ManagedUserRowProps = {
|
||||
user: User
|
||||
@@ -31,61 +35,80 @@ export default function MemberRow({
|
||||
const groupSSOActive = getMeta('ol-groupSSOActive')
|
||||
|
||||
return (
|
||||
<tr
|
||||
key={`user-${user.email}`}
|
||||
className={`managed-user-row ${user.invite ? 'text-muted' : ''}`}
|
||||
>
|
||||
<tr className="managed-user-row">
|
||||
<SelectUserCheckbox user={user} />
|
||||
<td className="cell-email">
|
||||
<td
|
||||
className={classnames('cell-email', {
|
||||
'text-muted': user.invite,
|
||||
})}
|
||||
>
|
||||
<span>
|
||||
{user.email}
|
||||
{user.invite ? (
|
||||
<span>
|
||||
{user.invite && (
|
||||
<>
|
||||
|
||||
<Tooltip
|
||||
id={`pending-invite-symbol-${user._id}`}
|
||||
<OLTooltip
|
||||
id={`pending-invite-symbol-${user.email}`}
|
||||
description={t('pending_invite')}
|
||||
>
|
||||
<Badge
|
||||
bsStyle={null}
|
||||
className="badge-tag-bs3"
|
||||
aria-label={t('pending_invite')}
|
||||
data-testid="badge-pending-invite"
|
||||
>
|
||||
<OLTag data-testid="badge-pending-invite">
|
||||
{t('pending_invite')}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
</span>
|
||||
) : (
|
||||
''
|
||||
</OLTag>
|
||||
</OLTooltip>
|
||||
</>
|
||||
)}
|
||||
{user.isEntityAdmin && (
|
||||
<span>
|
||||
<>
|
||||
|
||||
<Tooltip
|
||||
id={`group-admin-symbol-${user._id}`}
|
||||
<OLTooltip
|
||||
id={`group-admin-symbol-${user.email}`}
|
||||
description={t('group_admin')}
|
||||
>
|
||||
<i
|
||||
className="fa fa-user-circle-o"
|
||||
aria-hidden="true"
|
||||
aria-label={t('group_admin')}
|
||||
/>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span data-testid="group-admin-symbol">
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<Icon
|
||||
type="user-circle-o"
|
||||
fw
|
||||
accessibilityLabel={t('group_admin')}
|
||||
/>
|
||||
}
|
||||
bs5={
|
||||
<MaterialIcon
|
||||
type="account_circle"
|
||||
accessibilityLabel={t('group_admin')}
|
||||
className="align-middle"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
</td>
|
||||
<td className="cell-name">
|
||||
<td
|
||||
className={classnames('cell-name', {
|
||||
'text-muted': user.invite,
|
||||
})}
|
||||
>
|
||||
{user.first_name} {user.last_name}
|
||||
</td>
|
||||
<td className="cell-last-active">
|
||||
<td
|
||||
className={classnames('cell-last-active', {
|
||||
'text-muted': user.invite,
|
||||
})}
|
||||
>
|
||||
{user.last_active_at
|
||||
? moment(user.last_active_at).format('Do MMM YYYY')
|
||||
: 'N/A'}
|
||||
</td>
|
||||
{groupSSOActive && (
|
||||
<td className="cell-security">
|
||||
<td
|
||||
className={classnames('cell-security', {
|
||||
'text-muted': user.invite,
|
||||
})}
|
||||
>
|
||||
<div className="managed-user-security">
|
||||
<SSOStatus user={user} />
|
||||
</div>
|
||||
|
||||
+57
-68
@@ -1,8 +1,6 @@
|
||||
import { useState } from 'react'
|
||||
import { Col, Row } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { User } from '../../../../../../types/group-management/user'
|
||||
import Tooltip from '@/shared/components/tooltip'
|
||||
import { useGroupMembersContext } from '../../context/group-members-context'
|
||||
import type { GroupUserAlert } from '../../utils/types'
|
||||
import MemberRow from './member-row'
|
||||
@@ -12,6 +10,8 @@ import SelectAllCheckbox from './select-all-checkbox'
|
||||
import classNames from 'classnames'
|
||||
import getMeta from '@/utils/meta'
|
||||
import UnlinkUserModal from './unlink-user-modal'
|
||||
import OLTable from '@/features/ui/components/ol/ol-table'
|
||||
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
|
||||
|
||||
type ManagedUsersListProps = {
|
||||
groupId: string
|
||||
@@ -38,9 +38,9 @@ export default function MembersList({ groupId }: ManagedUsersListProps) {
|
||||
onDismiss={() => setGroupUserAlert(undefined)}
|
||||
/>
|
||||
)}
|
||||
<ul
|
||||
<OLTable
|
||||
className={classNames(
|
||||
'list-unstyled',
|
||||
'managed-users-table',
|
||||
'structured-list',
|
||||
'managed-users-list',
|
||||
{
|
||||
@@ -48,71 +48,60 @@ export default function MembersList({ groupId }: ManagedUsersListProps) {
|
||||
'group-sso-active': groupSSOActive,
|
||||
}
|
||||
)}
|
||||
container={false}
|
||||
hover
|
||||
data-testid="managed-users-table"
|
||||
>
|
||||
<li className="container-fluid">
|
||||
<Row id="managed-users-list-headers">
|
||||
<Col xs={12}>
|
||||
<table className="managed-users-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<SelectAllCheckbox />
|
||||
<td className="cell-email">
|
||||
<span className="header">{t('email')}</span>
|
||||
</td>
|
||||
<td className="cell-name">
|
||||
<span className="header">{t('name')}</span>
|
||||
</td>
|
||||
<td className="cell-last-active">
|
||||
<Tooltip
|
||||
id="last-active-tooltip"
|
||||
description={t('last_active_description')}
|
||||
overlayProps={{
|
||||
placement: 'left',
|
||||
}}
|
||||
>
|
||||
<span className="header">
|
||||
{t('last_active')}
|
||||
<sup>(?)</sup>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</td>
|
||||
{groupSSOActive && (
|
||||
<td className="cell-security">
|
||||
<span className="header">{t('security')}</span>
|
||||
</td>
|
||||
)}
|
||||
{managedUsersActive && (
|
||||
<td className="cell-managed">
|
||||
<span className="header">{t('managed')}</span>
|
||||
</td>
|
||||
)}
|
||||
<td />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.length === 0 && (
|
||||
<tr>
|
||||
<td className="text-center" colSpan={5}>
|
||||
<small>{t('no_members')}</small>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{users.map((user: any) => (
|
||||
<MemberRow
|
||||
key={user.email}
|
||||
user={user}
|
||||
openOffboardingModalForUser={setUserToOffboard}
|
||||
openUnlinkUserModal={setUserToUnlink}
|
||||
setGroupUserAlert={setGroupUserAlert}
|
||||
groupId={groupId}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Col>
|
||||
</Row>
|
||||
</li>
|
||||
</ul>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="cell-checkbox">
|
||||
<SelectAllCheckbox />
|
||||
</th>
|
||||
<th className="cell-email">{t('email')}</th>
|
||||
<th className="cell-name">{t('name')}</th>
|
||||
<th className="cell-last-active">
|
||||
<OLTooltip
|
||||
id="last-active-tooltip"
|
||||
description={t('last_active_description')}
|
||||
overlayProps={{
|
||||
placement: 'left',
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
{t('last_active')}
|
||||
<sup>(?)</sup>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
</th>
|
||||
{groupSSOActive && (
|
||||
<th className="cell-security">{t('security')}</th>
|
||||
)}
|
||||
{managedUsersActive && (
|
||||
<th className="cell-managed">{t('managed')}</th>
|
||||
)}
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.length === 0 && (
|
||||
<tr>
|
||||
<td className="text-center" colSpan={5}>
|
||||
<small>{t('no_members')}</small>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{users.map(user => (
|
||||
<MemberRow
|
||||
key={user.email}
|
||||
user={user}
|
||||
openOffboardingModalForUser={setUserToOffboard}
|
||||
openUnlinkUserModal={setUserToUnlink}
|
||||
setGroupUserAlert={setGroupUserAlert}
|
||||
groupId={groupId}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</OLTable>
|
||||
{userToOffboard && (
|
||||
<OffboardManagedUserModal
|
||||
user={userToOffboard}
|
||||
|
||||
+53
-55
@@ -1,14 +1,4 @@
|
||||
import { User } from '../../../../../../types/group-management/user'
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
ControlLabel,
|
||||
Form,
|
||||
FormControl,
|
||||
FormGroup,
|
||||
Modal,
|
||||
} from 'react-bootstrap'
|
||||
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||
import Icon from '@/shared/components/icon'
|
||||
import { useState } from 'react'
|
||||
import useAsync from '@/shared/hooks/use-async'
|
||||
@@ -16,6 +6,18 @@ import { useTranslation } from 'react-i18next'
|
||||
import { useLocation } from '@/shared/hooks/use-location'
|
||||
import { FetchError, postJSON } from '@/infrastructure/fetch-json'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
import OLNotification from '@/features/ui/components/ol/ol-notification'
|
||||
import OLFormControl from '@/features/ui/components/ol/ol-form-control'
|
||||
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
|
||||
import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
|
||||
|
||||
type OffboardManagedUserModalProps = {
|
||||
user: User
|
||||
@@ -69,12 +71,12 @@ export default function OffboardManagedUserModal({
|
||||
}
|
||||
|
||||
return (
|
||||
<AccessibleModal id={`delete-user-modal-${user._id}`} show onHide={onClose}>
|
||||
<Form id="delete-user-form" onSubmit={handleDeleteUserSubmit}>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{t('delete_user')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<OLModal id={`delete-user-modal-${user._id}`} show onHide={onClose}>
|
||||
<form id="delete-user-form" onSubmit={handleDeleteUserSubmit}>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('delete_user')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>
|
||||
<p>
|
||||
{t('about_to_delete_user_preamble', {
|
||||
userName: userFullName,
|
||||
@@ -95,21 +97,14 @@ export default function OffboardManagedUserModal({
|
||||
</p>
|
||||
<strong>{t('transfer_this_users_projects')}</strong>
|
||||
<p>{t('transfer_this_users_projects_description')}</p>
|
||||
<FormGroup>
|
||||
<ControlLabel htmlFor="recipient-select-input">
|
||||
{t('select_a_new_owner_for_projects')}
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
id="recipient-select-input"
|
||||
className="form-control"
|
||||
componentClass="select"
|
||||
<OLFormGroup controlId="recipient-select-input">
|
||||
<OLFormLabel>{t('select_a_new_owner_for_projects')}</OLFormLabel>
|
||||
<OLFormSelect
|
||||
aria-label={t('select_user')}
|
||||
required
|
||||
placeholder={t('choose_from_group_members')}
|
||||
value={selectedRecipientId || ''}
|
||||
onChange={(e: React.ChangeEvent<HTMLFormElement & FormControl>) =>
|
||||
setSelectedRecipientId(e.target.value)
|
||||
}
|
||||
onChange={e => setSelectedRecipientId(e.target.value)}
|
||||
>
|
||||
<option hidden disabled value="">
|
||||
{t('choose_from_group_members')}
|
||||
@@ -119,47 +114,50 @@ export default function OffboardManagedUserModal({
|
||||
{member.email}
|
||||
</option>
|
||||
))}
|
||||
</FormControl>
|
||||
</FormGroup>
|
||||
</OLFormSelect>
|
||||
</OLFormGroup>
|
||||
<p>
|
||||
<span>{t('all_projects_will_be_transferred_immediately')}</span>
|
||||
</p>
|
||||
<FormGroup>
|
||||
<ControlLabel htmlFor="supplied-email-input">
|
||||
<OLFormGroup controlId="supplied-email-input">
|
||||
<OLFormLabel>
|
||||
{t('confirm_delete_user_type_email_address', {
|
||||
userName: userFullName,
|
||||
})}
|
||||
</ControlLabel>
|
||||
<FormControl
|
||||
id="supplied-email-input"
|
||||
</OLFormLabel>
|
||||
<OLFormControl
|
||||
type="email"
|
||||
aria-label={t('email')}
|
||||
onChange={(e: React.ChangeEvent<HTMLFormElement & FormControl>) =>
|
||||
setSuppliedEmail(e.target.value)
|
||||
}
|
||||
onChange={e => setSuppliedEmail(e.target.value)}
|
||||
/>
|
||||
</FormGroup>
|
||||
{error && <Alert bsStyle="danger">{error}</Alert>}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<FormGroup>
|
||||
<Button onClick={onClose}>{t('cancel')}</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
bsStyle="danger"
|
||||
disabled={isLoading || isSuccess || !shouldEnableDeleteUserButton}
|
||||
>
|
||||
{isLoading ? (
|
||||
</OLFormGroup>
|
||||
{error && (
|
||||
<OLNotification type="error" content={error} className="mb-0" />
|
||||
)}
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton variant="secondary" onClick={onClose}>
|
||||
{t('cancel')}
|
||||
</OLButton>
|
||||
<OLButton
|
||||
type="submit"
|
||||
variant="danger"
|
||||
disabled={isLoading || isSuccess || !shouldEnableDeleteUserButton}
|
||||
isLoading={isLoading}
|
||||
bs3Props={{
|
||||
loading: isLoading ? (
|
||||
<>
|
||||
<Icon type="refresh" fw spin /> {t('deleting')}…
|
||||
</>
|
||||
) : (
|
||||
t('delete_user')
|
||||
)}
|
||||
</Button>
|
||||
</FormGroup>
|
||||
</Modal.Footer>
|
||||
</Form>
|
||||
</AccessibleModal>
|
||||
),
|
||||
}}
|
||||
>
|
||||
{t('delete_user')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</form>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
+24
-13
@@ -1,6 +1,8 @@
|
||||
import React, { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGroupMembersContext } from '../../context/group-members-context'
|
||||
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
export default function SelectAllCheckbox() {
|
||||
const { t } = useTranslation()
|
||||
@@ -28,18 +30,27 @@ export default function SelectAllCheckbox() {
|
||||
}
|
||||
|
||||
return (
|
||||
<td className="cell-checkbox">
|
||||
<label htmlFor="select-all" className="sr-only">
|
||||
{t('select_all')}
|
||||
</label>
|
||||
<input
|
||||
className="select-all"
|
||||
id="select-all"
|
||||
type="checkbox"
|
||||
autoComplete="off"
|
||||
onChange={handleSelectAllNonManagedClick}
|
||||
checked={selectedUsers.length === nonManagedUsers.length}
|
||||
/>
|
||||
</td>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<input
|
||||
className="select-all"
|
||||
type="checkbox"
|
||||
autoComplete="off"
|
||||
onChange={handleSelectAllNonManagedClick}
|
||||
checked={selectedUsers.length === nonManagedUsers.length}
|
||||
aria-label={t('select_all')}
|
||||
data-testid="select-all-checkbox"
|
||||
/>
|
||||
}
|
||||
bs5={
|
||||
<OLFormCheckbox
|
||||
autoComplete="off"
|
||||
onChange={handleSelectAllNonManagedClick}
|
||||
checked={selectedUsers.length === nonManagedUsers.length}
|
||||
aria-label={t('select_all')}
|
||||
data-testid="select-all-checkbox"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
+25
-14
@@ -2,6 +2,8 @@ import { useTranslation } from 'react-i18next'
|
||||
import type { User } from '../../../../../../types/group-management/user'
|
||||
import { useGroupMembersContext } from '../../context/group-members-context'
|
||||
import { useCallback } from 'react'
|
||||
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
|
||||
type ManagedUsersSelectUserCheckboxProps = {
|
||||
user: User
|
||||
@@ -39,21 +41,30 @@ export default function SelectUserCheckbox({
|
||||
|
||||
return (
|
||||
<td className="cell-checkbox">
|
||||
{/* the next check will hide the `checkbox` but still show the `td` */}
|
||||
{/* the next check will hide the `checkbox` but still show the `th` */}
|
||||
{user.enrollment?.managedBy ? null : (
|
||||
<>
|
||||
<label htmlFor={`select-user-${user.email}`} className="sr-only">
|
||||
{t('select_user')}
|
||||
</label>
|
||||
<input
|
||||
className="select-item"
|
||||
id={`select-user-${user.email}`}
|
||||
type="checkbox"
|
||||
autoComplete="off"
|
||||
checked={selected}
|
||||
onChange={e => handleSelectUser(e, user)}
|
||||
/>
|
||||
</>
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={
|
||||
<input
|
||||
className="select-item"
|
||||
type="checkbox"
|
||||
autoComplete="off"
|
||||
checked={selected}
|
||||
onChange={e => handleSelectUser(e, user)}
|
||||
aria-label={t('select_user')}
|
||||
data-testid="select-single-checkbox"
|
||||
/>
|
||||
}
|
||||
bs5={
|
||||
<OLFormCheckbox
|
||||
autoComplete="off"
|
||||
checked={selected}
|
||||
onChange={e => handleSelectUser(e, user)}
|
||||
aria-label={t('select_user')}
|
||||
data-testid="select-single-checkbox"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
)
|
||||
|
||||
+21
-16
@@ -1,5 +1,3 @@
|
||||
import { Modal } from 'react-bootstrap'
|
||||
import AccessibleModal from '@/shared/components/accessible-modal'
|
||||
import { useTranslation, Trans } from 'react-i18next'
|
||||
import { User } from '../../../../../../types/group-management/user'
|
||||
import getMeta from '@/utils/meta'
|
||||
@@ -10,6 +8,13 @@ import NotificationScrolledTo from '@/shared/components/notification-scrolled-to
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { GroupUserAlert } from '../../utils/types'
|
||||
import { useGroupMembersContext } from '../../context/group-members-context'
|
||||
import OLModal, {
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/features/ui/components/ol/ol-modal'
|
||||
import OLButton from '@/features/ui/components/ol/ol-button'
|
||||
|
||||
export type UnlinkUserModalProps = {
|
||||
onClose: () => void
|
||||
@@ -77,11 +82,11 @@ export default function UnlinkUserModal({
|
||||
)
|
||||
|
||||
return (
|
||||
<AccessibleModal show onHide={onClose}>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{t('unlink_user')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<OLModal show onHide={onClose}>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('unlink_user')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>
|
||||
{hasError && (
|
||||
<div className="mb-3">
|
||||
<NotificationScrolledTo
|
||||
@@ -101,19 +106,19 @@ export default function UnlinkUserModal({
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<button className="btn btn-secondary" disabled={unlinkInFlight}>
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton variant="secondary" disabled={unlinkInFlight}>
|
||||
{t('cancel')}
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-danger"
|
||||
</OLButton>
|
||||
<OLButton
|
||||
variant="danger"
|
||||
onClick={e => handleUnlink(e)}
|
||||
disabled={unlinkInFlight}
|
||||
>
|
||||
{t('unlink_user')}
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</AccessibleModal>
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Card } from 'react-bootstrap-5'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { FC } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
const OLCard: FC<{ className?: string }> = ({ children, className }) => {
|
||||
return (
|
||||
<BootstrapVersionSwitcher
|
||||
bs3={<div className={classNames('card', className)}>{children}</div>}
|
||||
bs5={
|
||||
<Card className={className}>
|
||||
<Card.Body>{children}</Card.Body>
|
||||
</Card>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default OLCard
|
||||
@@ -1,6 +1,7 @@
|
||||
import Table from '@/features/ui/components/bootstrap-5/table'
|
||||
import { Table as BS3Table } from 'react-bootstrap'
|
||||
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
|
||||
import { getAriaAndDataProps } from '@/features/utils/bootstrap-5'
|
||||
|
||||
type OLFormProps = React.ComponentProps<typeof Table> & {
|
||||
bs3Props?: React.ComponentProps<typeof BS3Table>
|
||||
@@ -11,10 +12,12 @@ function OLTable(props: OLFormProps) {
|
||||
|
||||
const bs3FormProps: React.ComponentProps<typeof BS3Table> = {
|
||||
bsClass: rest.className,
|
||||
id: rest.id,
|
||||
condensed: rest.size === 'sm',
|
||||
children: rest.children,
|
||||
responsive:
|
||||
typeof rest.responsive !== 'string' ? rest.responsive : undefined,
|
||||
...getAriaAndDataProps(rest),
|
||||
...bs3Props,
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,10 @@ function NotificationScrolledTo({ ...props }: NotificationProps) {
|
||||
|
||||
notificationProps.className = `${notificationProps.className} notification-with-scroll-margin`
|
||||
|
||||
return <Notification {...notificationProps} />
|
||||
return (
|
||||
<div className="notification-list">
|
||||
<Notification {...notificationProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default NotificationScrolledTo
|
||||
|
||||
@@ -361,3 +361,22 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-04);
|
||||
margin-bottom: var(--spacing-06);
|
||||
|
||||
.heading {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,3 +43,4 @@
|
||||
@import 'invite';
|
||||
@import 'upgrade-prompt';
|
||||
@import 'integrations-panel';
|
||||
@import 'group-members';
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/* Styles for group-subscription members view */
|
||||
|
||||
.structured-list.managed-users-list {
|
||||
/* Override scrolling behaviour on structured-list */
|
||||
overflow: initial;
|
||||
overflow-y: initial;
|
||||
overflow-x: initial;
|
||||
}
|
||||
|
||||
.managed-users-list {
|
||||
vertical-align: middle;
|
||||
|
||||
.security-state-invite-pending {
|
||||
color: var(--content-disabled);
|
||||
}
|
||||
|
||||
.security-state-managed {
|
||||
color: var(--content-positive);
|
||||
}
|
||||
|
||||
.security-state-not-managed {
|
||||
color: var(--content-danger);
|
||||
}
|
||||
|
||||
.managed-user-row {
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.managed-user-security {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.managed-users-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
.cell-checkbox {
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
.cell-email {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&.group-sso-active .cell-email {
|
||||
width: 29%;
|
||||
}
|
||||
|
||||
.cell-name {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.cell-last-active {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.cell-security {
|
||||
width: 12%;
|
||||
}
|
||||
|
||||
.cell-managed {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.cell-dropdown {
|
||||
width: 26%;
|
||||
min-width: 36px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.managed-users-active {
|
||||
.cell-email {
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
&.group-sso-active {
|
||||
.cell-checkbox {
|
||||
width: 3%;
|
||||
}
|
||||
|
||||
.cell-email {
|
||||
width: 37%;
|
||||
}
|
||||
|
||||
.cell-last-active {
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
.cell-name {
|
||||
width: 18%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(xl) {
|
||||
.cell-checkbox {
|
||||
width: 5%;
|
||||
}
|
||||
|
||||
.cell-email {
|
||||
width: 41%;
|
||||
}
|
||||
|
||||
&.group-sso-active .cell-email {
|
||||
width: 41%;
|
||||
}
|
||||
|
||||
.cell-name {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.cell-last-active {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.cell-security {
|
||||
width: 10%;
|
||||
}
|
||||
|
||||
.cell-managed {
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
.cell-dropdown {
|
||||
width: 19%;
|
||||
min-width: 36px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.managed-users-active {
|
||||
.cell-email {
|
||||
width: 41%;
|
||||
}
|
||||
|
||||
&.group-sso-active {
|
||||
.cell-checkbox {
|
||||
width: 3%;
|
||||
}
|
||||
|
||||
.cell-email {
|
||||
width: 36%;
|
||||
}
|
||||
|
||||
.cell-name {
|
||||
width: 18%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.managed-user-security {
|
||||
.material-symbols {
|
||||
position: relative;
|
||||
top: 4px;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,14 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
.dropdown-table-button-toggle {
|
||||
@include action-button;
|
||||
|
||||
padding: var(--spacing-04);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-container-bordered {
|
||||
|
||||
@@ -570,12 +570,6 @@
|
||||
right: var(--spacing-04);
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.dropdown-table-button-toggle {
|
||||
@include action-button;
|
||||
|
||||
padding: var(--spacing-04);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,8 @@
|
||||
gap: var(--spacing-04);
|
||||
margin-bottom: var(--spacing-06);
|
||||
|
||||
h2 {
|
||||
h2,
|
||||
.heading {
|
||||
@include heading-lg;
|
||||
|
||||
margin: 0;
|
||||
|
||||
@@ -61,19 +61,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.managed-users-list-alert {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.managed-users-list-alert-close {
|
||||
padding-left: @padding-sm;
|
||||
text-align: right;
|
||||
width: 10%;
|
||||
@media (min-width: @screen-sm-min) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.managed-users-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
@@ -106,36 +93,24 @@
|
||||
@media (min-width: @screen-xs) {
|
||||
.cell-checkbox {
|
||||
width: 5%;
|
||||
.managed-users-active.group-sso-active & {
|
||||
width: 3%;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-email {
|
||||
width: 50%;
|
||||
.managed-users-active & {
|
||||
width: 35%;
|
||||
}
|
||||
.group-sso-active & {
|
||||
}
|
||||
|
||||
&.group-sso-active {
|
||||
.cell-email {
|
||||
width: 37%;
|
||||
}
|
||||
.managed-users-active.group-sso-active & {
|
||||
width: 29%;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-name {
|
||||
width: 20%;
|
||||
.managed-users-active.group-sso-active & {
|
||||
width: 18%;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-last-active {
|
||||
width: 20%;
|
||||
.managed-users-active.group-sso-active & {
|
||||
width: 16%;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-security {
|
||||
@@ -150,33 +125,47 @@
|
||||
width: 6%;
|
||||
min-width: 25px;
|
||||
}
|
||||
|
||||
&.managed-users-active {
|
||||
.cell-email {
|
||||
width: 35%;
|
||||
}
|
||||
|
||||
&.group-sso-active {
|
||||
.cell-checkbox {
|
||||
width: 3%;
|
||||
}
|
||||
|
||||
.cell-email {
|
||||
width: 29%;
|
||||
}
|
||||
|
||||
.cell-last-active {
|
||||
width: 16%;
|
||||
}
|
||||
|
||||
.cell-name {
|
||||
width: 18%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: @screen-lg) {
|
||||
.cell-checkbox {
|
||||
width: 5%;
|
||||
.managed-users-active.group-sso-active & {
|
||||
width: 3%;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-email {
|
||||
width: 55%;
|
||||
.managed-users-active & {
|
||||
width: 42%;
|
||||
}
|
||||
.group-sso-active & {
|
||||
width: 45%;
|
||||
}
|
||||
.managed-users-active.group-sso-active & {
|
||||
width: 36%;
|
||||
}
|
||||
}
|
||||
|
||||
&.group-sso-active .cell-email {
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.cell-name {
|
||||
width: 20%;
|
||||
.managed-users-active.group-sso-active & {
|
||||
width: 18%;
|
||||
}
|
||||
}
|
||||
|
||||
.cell-last-active {
|
||||
@@ -195,6 +184,26 @@
|
||||
width: 5%;
|
||||
min-width: 25px;
|
||||
}
|
||||
|
||||
&.managed-users-active {
|
||||
.cell-email {
|
||||
width: 42%;
|
||||
}
|
||||
|
||||
&.group-sso-active {
|
||||
.cell-checkbox {
|
||||
width: 3%;
|
||||
}
|
||||
|
||||
.cell-email {
|
||||
width: 36%;
|
||||
}
|
||||
|
||||
.cell-name {
|
||||
width: 18%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+169
-135
@@ -59,27 +59,31 @@ describe('GroupMembers', function () {
|
||||
})
|
||||
|
||||
it('renders the group members page', function () {
|
||||
cy.get('h1').contains('My Awesome Team')
|
||||
cy.get('small').contains('You have added 2 of 10 available members')
|
||||
cy.findByRole('heading', { name: /my awesome team/i, level: 1 })
|
||||
cy.findByTestId('page-header-members-details').contains(
|
||||
'You have added 2 of 10 available members'
|
||||
)
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('john.doe@test.com')
|
||||
cy.contains('John Doe')
|
||||
cy.contains('15th Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
})
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('john.doe@test.com')
|
||||
cy.contains('John Doe')
|
||||
cy.contains('15th Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
})
|
||||
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('sends an invite', function () {
|
||||
@@ -96,16 +100,18 @@ describe('GroupMembers', function () {
|
||||
cy.get('.form-control').type('someone.else@test.com')
|
||||
cy.get('.add-more-members-form button').click()
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('someone.else@test.com')
|
||||
cy.contains('N/A')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('someone.else@test.com')
|
||||
cy.contains('N/A')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('tries to send an invite and displays the error', function () {
|
||||
@@ -124,25 +130,29 @@ describe('GroupMembers', function () {
|
||||
})
|
||||
|
||||
it('checks the select all checkbox', function () {
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('.select-all').click()
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
})
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('remove a member', function () {
|
||||
@@ -150,23 +160,27 @@ describe('GroupMembers', function () {
|
||||
statusCode: 200,
|
||||
})
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('button').contains('Remove from group').click()
|
||||
|
||||
cy.get('small').contains('You have added 1 of 10 available members')
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.contains('Pending invite').should('not.exist')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.contains('Pending invite').should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('tries to remove a user and displays the error', function () {
|
||||
@@ -174,11 +188,13 @@ describe('GroupMembers', function () {
|
||||
statusCode: 500,
|
||||
})
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
})
|
||||
})
|
||||
})
|
||||
cy.get('button').contains('Remove from group').click()
|
||||
|
||||
cy.get('.alert').contains('Sorry, something went wrong')
|
||||
@@ -241,35 +257,37 @@ describe('GroupMembers', function () {
|
||||
cy.get('h1').contains('My Awesome Team')
|
||||
cy.get('small').contains('You have added 3 of 10 available members')
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('john.doe@test.com')
|
||||
cy.contains('John Doe')
|
||||
cy.contains('15th Jan 2023')
|
||||
cy.get('.sr-only').contains('Pending invite')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
cy.get(`.security-state-invite-pending`).should('exist')
|
||||
})
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('john.doe@test.com')
|
||||
cy.contains('John Doe')
|
||||
cy.contains('15th Jan 2023')
|
||||
cy.get('.sr-only').contains('Pending invite')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
cy.get(`.security-state-invite-pending`).should('exist')
|
||||
})
|
||||
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
cy.get('.sr-only').contains('Not managed')
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
cy.get('.sr-only').contains('Not managed')
|
||||
})
|
||||
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.contains('Claire Jennings')
|
||||
cy.contains('3rd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
cy.get('.sr-only').contains('Managed')
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.contains('Claire Jennings')
|
||||
cy.contains('3rd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
cy.get('.sr-only').contains('Managed')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('sends an invite', function () {
|
||||
@@ -286,18 +304,20 @@ describe('GroupMembers', function () {
|
||||
cy.get('.form-control').type('someone.else@test.com')
|
||||
cy.get('.add-more-members-form button').click()
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(4)').within(() => {
|
||||
cy.contains('someone.else@test.com')
|
||||
cy.contains('N/A')
|
||||
cy.get('.sr-only').contains('Pending invite')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
cy.get(`.security-state-invite-pending`).should('exist')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(4)').within(() => {
|
||||
cy.contains('someone.else@test.com')
|
||||
cy.contains('N/A')
|
||||
cy.get('.sr-only').contains('Pending invite')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
cy.get(`.security-state-invite-pending`).should('exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('tries to send an invite and displays the error', function () {
|
||||
@@ -316,25 +336,29 @@ describe('GroupMembers', function () {
|
||||
})
|
||||
|
||||
it('checks the select all checkbox', function () {
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('.select-all').click()
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
})
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('button').contains('Remove from group').click()
|
||||
})
|
||||
@@ -344,22 +368,26 @@ describe('GroupMembers', function () {
|
||||
statusCode: 200,
|
||||
})
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('button').contains('Remove from group').click()
|
||||
|
||||
cy.get('small').contains('You have added 2 of 10 available members')
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('cannot remove a managed member', function () {
|
||||
@@ -367,12 +395,14 @@ describe('GroupMembers', function () {
|
||||
statusCode: 200,
|
||||
})
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
// no checkbox should be shown for 'Claire Jennings', a managed user
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.get('.select-item').should('not.exist')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
// no checkbox should be shown for 'Claire Jennings', a managed user
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.get('.select-item').should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('tries to remove a user and displays the error', function () {
|
||||
@@ -380,11 +410,13 @@ describe('GroupMembers', function () {
|
||||
statusCode: 500,
|
||||
})
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
})
|
||||
})
|
||||
})
|
||||
cy.get('.page-header').within(() => {
|
||||
cy.get('button').contains('Remove from group').click()
|
||||
})
|
||||
@@ -448,17 +480,19 @@ describe('GroupMembers', function () {
|
||||
})
|
||||
|
||||
it('should display the Security column', function () {
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.get('.sr-only').contains('SSO not active')
|
||||
})
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.get('.sr-only').contains('SSO not active')
|
||||
})
|
||||
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.get('.sr-only').contains('SSO active')
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.get('.sr-only').contains('SSO active')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
+108
-88
@@ -86,36 +86,38 @@ describe('group members, with managed users', function () {
|
||||
cy.get('h1').contains('My Awesome Team')
|
||||
cy.get('small').contains('You have added 3 of 10 available members')
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('john.doe@test.com')
|
||||
cy.contains('John Doe')
|
||||
cy.contains('15th Jan 2023')
|
||||
cy.get('.sr-only').contains('Pending invite')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('john.doe@test.com')
|
||||
cy.contains('John Doe')
|
||||
cy.contains('15th Jan 2023')
|
||||
cy.get('.sr-only').contains('Pending invite')
|
||||
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
cy.get(`.security-state-invite-pending`).should('exist')
|
||||
})
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
cy.get(`.security-state-invite-pending`).should('exist')
|
||||
})
|
||||
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
cy.get('.sr-only').contains('Not managed')
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
cy.get('.sr-only').contains('Not managed')
|
||||
})
|
||||
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.contains('Claire Jennings')
|
||||
cy.contains('3rd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
cy.get('.sr-only').contains('Managed')
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.contains('Claire Jennings')
|
||||
cy.contains('3rd Jan 2023')
|
||||
cy.findByTestId('badge-pending-invite').should('not.exist')
|
||||
cy.get('.sr-only').contains('Managed')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('sends an invite', function () {
|
||||
@@ -132,18 +134,20 @@ describe('group members, with managed users', function () {
|
||||
cy.get('.form-control').type('someone.else@test.com')
|
||||
cy.get('.add-more-members-form button').click()
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(4)').within(() => {
|
||||
cy.contains('someone.else@test.com')
|
||||
cy.contains('N/A')
|
||||
cy.get('.sr-only').contains('Pending invite')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
cy.get(`.security-state-invite-pending`).should('exist')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(4)').within(() => {
|
||||
cy.contains('someone.else@test.com')
|
||||
cy.contains('N/A')
|
||||
cy.get('.sr-only').contains('Pending invite')
|
||||
cy.findByTestId('badge-pending-invite').should(
|
||||
'have.text',
|
||||
'Pending invite'
|
||||
)
|
||||
cy.get(`.security-state-invite-pending`).should('exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('tries to send an invite and displays the error', function () {
|
||||
@@ -162,25 +166,29 @@ describe('group members, with managed users', function () {
|
||||
})
|
||||
|
||||
it('checks the select all checkbox', function () {
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('.select-all').click()
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
})
|
||||
})
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.get('.select-item').should('be.checked')
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('button').contains('Remove from group').click()
|
||||
})
|
||||
@@ -190,22 +198,26 @@ describe('group members, with managed users', function () {
|
||||
statusCode: 200,
|
||||
})
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
cy.get('button').contains('Remove from group').click()
|
||||
|
||||
cy.get('small').contains('You have added 2 of 10 available members')
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.contains('Bobby Lapointe')
|
||||
cy.contains('2nd Jan 2023')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('cannot remove a managed member', function () {
|
||||
@@ -213,12 +225,14 @@ describe('group members, with managed users', function () {
|
||||
statusCode: 200,
|
||||
})
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
// no checkbox should be shown for 'Claire Jennings', a managed user
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.get('.select-item').should('not.exist')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
// no checkbox should be shown for 'Claire Jennings', a managed user
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.get('.select-item').should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('tries to remove a user and displays the error', function () {
|
||||
@@ -226,11 +240,13 @@ describe('group members, with managed users', function () {
|
||||
statusCode: 500,
|
||||
})
|
||||
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(1)').within(() => {
|
||||
cy.get('.select-item').check()
|
||||
})
|
||||
})
|
||||
})
|
||||
cy.get('.page-header').within(() => {
|
||||
cy.get('button').contains('Remove from group').click()
|
||||
})
|
||||
@@ -259,17 +275,19 @@ describe('Group members when group SSO is enabled', function () {
|
||||
win.metaAttributesCache.set('ol-groupSSOActive', false)
|
||||
})
|
||||
mountGroupMembersProvider()
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.get('.sr-only').contains('SSO not active').should('not.exist')
|
||||
})
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.get('.sr-only').contains('SSO not active').should('not.exist')
|
||||
})
|
||||
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.get('.sr-only').contains('SSO active').should('not.exist')
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.get('.sr-only').contains('SSO active').should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should display SSO Column when Group SSO is enabled', function () {
|
||||
@@ -277,16 +295,18 @@ describe('Group members when group SSO is enabled', function () {
|
||||
win.metaAttributesCache.set('ol-groupSSOActive', true)
|
||||
})
|
||||
mountGroupMembersProvider()
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.get('.sr-only').contains('SSO not active')
|
||||
})
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(2)').within(() => {
|
||||
cy.contains('bobby.lapointe@test.com')
|
||||
cy.get('.sr-only').contains('SSO not active')
|
||||
})
|
||||
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.get('.sr-only').contains('SSO active')
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.contains('claire.jennings@test.com')
|
||||
cy.get('.sr-only').contains('SSO active')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+37
-31
@@ -40,9 +40,9 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('renders the row', function () {
|
||||
cy.get('tr').should('exist')
|
||||
cy.get('tr')
|
||||
// Checkbox
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
// Email
|
||||
cy.get('tr').contains(user.email)
|
||||
// Name
|
||||
@@ -131,7 +131,9 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('should render a "Group admin" symbol', function () {
|
||||
cy.get('[aria-label="Group admin"].fa-user-circle-o').should('exist')
|
||||
cy.findByTestId('group-admin-symbol').within(() => {
|
||||
cy.findByText(/group admin/i)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -167,11 +169,11 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('should select and unselect the user', function () {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.get('.select-item').click()
|
||||
cy.get('.select-item').should('be.checked')
|
||||
cy.get('.select-item').click()
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').click()
|
||||
cy.findByTestId('select-single-checkbox').should('be.checked')
|
||||
cy.findByTestId('select-single-checkbox').click()
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -217,7 +219,7 @@ describe('MemberRow', function () {
|
||||
it('renders the row', function () {
|
||||
cy.get('tr').should('exist')
|
||||
// Checkbox
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
// Email
|
||||
cy.get('tr').contains(user.email)
|
||||
// Name
|
||||
@@ -305,7 +307,9 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('should render a "Group admin" symbol', function () {
|
||||
cy.get('[aria-label="Group admin"].fa-user-circle-o').should('exist')
|
||||
cy.findByTestId('group-admin-symbol').within(() => {
|
||||
cy.findByText(/group admin/i)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -341,11 +345,11 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('should select and unselect the user', function () {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.get('.select-item').click()
|
||||
cy.get('.select-item').should('be.checked')
|
||||
cy.get('.select-item').click()
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').click()
|
||||
cy.findByTestId('select-single-checkbox').should('be.checked')
|
||||
cy.findByTestId('select-single-checkbox').click()
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -389,9 +393,8 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('renders the row', function () {
|
||||
cy.get('tr').should('exist')
|
||||
// Checkbox
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
// Email
|
||||
cy.get('tr').contains(user.email)
|
||||
// Name
|
||||
@@ -481,7 +484,9 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('should render a "Group admin" symbol', function () {
|
||||
cy.get('[aria-label="Group admin"].fa-user-circle-o').should('exist')
|
||||
cy.findByTestId('group-admin-symbol').within(() => {
|
||||
cy.findByText(/group admin/i)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -517,11 +522,11 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('should select and unselect the user', function () {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.get('.select-item').click()
|
||||
cy.get('.select-item').should('be.checked')
|
||||
cy.get('.select-item').click()
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').click()
|
||||
cy.findByTestId('select-single-checkbox').should('be.checked')
|
||||
cy.findByTestId('select-single-checkbox').click()
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -566,9 +571,8 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('renders the row', function () {
|
||||
cy.get('tr').should('exist')
|
||||
// Checkbox
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
// Email
|
||||
cy.get('tr').contains(user.email)
|
||||
// Name
|
||||
@@ -658,7 +662,9 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('should render a "Group admin" symbol', function () {
|
||||
cy.get('[aria-label="Group admin"].fa-user-circle-o').should('exist')
|
||||
cy.findByTestId('group-admin-symbol').within(() => {
|
||||
cy.findByText(/group admin/i)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -694,11 +700,11 @@ describe('MemberRow', function () {
|
||||
})
|
||||
|
||||
it('should select and unselect the user', function () {
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.get('.select-item').click()
|
||||
cy.get('.select-item').should('be.checked')
|
||||
cy.get('.select-item').click()
|
||||
cy.get('.select-item').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
cy.findByTestId('select-single-checkbox').click()
|
||||
cy.findByTestId('select-single-checkbox').should('be.checked')
|
||||
cy.findByTestId('select-single-checkbox').click()
|
||||
cy.findByTestId('select-single-checkbox').should('not.be.checked')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+121
-71
@@ -50,46 +50,74 @@ describe('MembersList', function () {
|
||||
win.metaAttributesCache.set('ol-groupSSOActive', false)
|
||||
})
|
||||
mountManagedUsersList()
|
||||
cy.get('#managed-users-list-headers').should('exist')
|
||||
|
||||
// Select-all checkbox
|
||||
cy.get('#managed-users-list-headers .select-all').should('exist')
|
||||
|
||||
cy.get('#managed-users-list-headers').contains('Email')
|
||||
cy.get('#managed-users-list-headers').contains('Name')
|
||||
cy.get('#managed-users-list-headers').contains('Last Active')
|
||||
cy.get('#managed-users-list-headers')
|
||||
.contains('Security')
|
||||
.should('not.exist')
|
||||
cy.findByTestId('managed-users-table').within(() => {
|
||||
cy.findByTestId('select-all-checkbox')
|
||||
})
|
||||
cy.findByTestId('managed-users-table').should('contain.text', 'Email')
|
||||
cy.findByTestId('managed-users-table').should('contain.text', 'Name')
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'contain.text',
|
||||
'Last Active'
|
||||
)
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'not.contain.text',
|
||||
'Security'
|
||||
)
|
||||
})
|
||||
it('should render the table headers with SSO Column', function () {
|
||||
cy.window().then(win => {
|
||||
win.metaAttributesCache.set('ol-groupSSOActive', true)
|
||||
})
|
||||
mountManagedUsersList()
|
||||
cy.get('#managed-users-list-headers').should('exist')
|
||||
|
||||
// Select-all checkbox
|
||||
cy.get('#managed-users-list-headers .select-all').should('exist')
|
||||
cy.findByTestId('managed-users-table').within(() => {
|
||||
cy.findByTestId('select-all-checkbox')
|
||||
})
|
||||
|
||||
cy.get('#managed-users-list-headers').contains('Email')
|
||||
cy.get('#managed-users-list-headers').contains('Name')
|
||||
cy.get('#managed-users-list-headers').contains('Last Active')
|
||||
cy.get('#managed-users-list-headers').contains('Security')
|
||||
cy.findByTestId('managed-users-table').should('contain.text', 'Email')
|
||||
cy.findByTestId('managed-users-table').should('contain.text', 'Name')
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'contain.text',
|
||||
'Last Active'
|
||||
)
|
||||
cy.findByTestId('managed-users-table').should('contain.text', 'Security')
|
||||
})
|
||||
|
||||
it('should render the list of users', function () {
|
||||
cy.get('.managed-users-list')
|
||||
.find('.managed-user-row')
|
||||
.should('have.length', 2)
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.findAllByRole('row').should('have.length', 2)
|
||||
})
|
||||
// First user
|
||||
cy.get('.managed-users-list').contains(users[0].email)
|
||||
cy.get('.managed-users-list').contains(users[0].first_name)
|
||||
cy.get('.managed-users-list').contains(users[0].last_name)
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'contain.text',
|
||||
users[0].email
|
||||
)
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'contain.text',
|
||||
users[0].first_name
|
||||
)
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'contain.text',
|
||||
users[0].last_name
|
||||
)
|
||||
// Second user
|
||||
cy.get('.managed-users-list').contains(users[1].email)
|
||||
cy.get('.managed-users-list').contains(users[1].first_name)
|
||||
cy.get('.managed-users-list').contains(users[1].last_name)
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'contain.text',
|
||||
users[1].email
|
||||
)
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'contain.text',
|
||||
users[1].first_name
|
||||
)
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'contain.text',
|
||||
users[1].last_name
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -106,10 +134,17 @@ describe('MembersList', function () {
|
||||
})
|
||||
|
||||
it('should render the list, with a "no members" message', function () {
|
||||
cy.get('.managed-users-list').contains('No members')
|
||||
cy.get('.managed-users-list')
|
||||
.find('.managed-user-row')
|
||||
.should('have.length', 0)
|
||||
cy.findByTestId('managed-users-table').should(
|
||||
'contain.text',
|
||||
'No members'
|
||||
)
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.findAllByRole('row')
|
||||
.should('have.length', 1)
|
||||
.and('contain.text', 'No members')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -188,27 +223,32 @@ describe('MembersList', function () {
|
||||
describe('unlinking user', function () {
|
||||
beforeEach(function () {
|
||||
mountManagedUsersList()
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.get('.sr-only').contains('SSO active')
|
||||
cy.get('.action-btn').click()
|
||||
cy.findByTestId('unlink-user-action').click()
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.findByText('SSO active')
|
||||
cy.findByRole('button', { name: /actions/i }).click()
|
||||
cy.findByTestId('unlink-user-action').click()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should show successs notification and update the user row after unlinking', function () {
|
||||
cy.get('.modal').within(() => {
|
||||
cy.get('.btn-danger').click()
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('button', { name: /unlink user/i }).click()
|
||||
})
|
||||
cy.get('.notification').contains(
|
||||
cy.findByRole('alert').should(
|
||||
'contain.text',
|
||||
`SSO reauthentication request has been sent to ${USER_LINKED.email}`
|
||||
)
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.get('.sr-only').contains('SSO not active')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.findByText('SSO not active')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -222,57 +262,67 @@ describe('MembersList', function () {
|
||||
|
||||
describe('when user is not managed', function () {
|
||||
beforeEach(function () {
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.get('.sr-only').contains('SSO active')
|
||||
cy.get('.sr-only').contains('Not managed')
|
||||
cy.get('.action-btn').click()
|
||||
cy.findByTestId('unlink-user-action').click()
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.findByText('SSO active')
|
||||
cy.findByText('Not managed')
|
||||
cy.findByRole('button', { name: /actions/i }).click()
|
||||
cy.findByTestId('unlink-user-action').click()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should show successs notification and update the user row after unlinking', function () {
|
||||
cy.get('.modal').within(() => {
|
||||
cy.get('.btn-danger').click()
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('button', { name: /unlink user/i }).click()
|
||||
})
|
||||
cy.get('.notification').contains(
|
||||
cy.findByRole('alert').should(
|
||||
'contain.text',
|
||||
`SSO reauthentication request has been sent to ${USER_LINKED.email}`
|
||||
)
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.get('.sr-only').contains('SSO not active')
|
||||
cy.get('.sr-only').contains('Not managed')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(3)').within(() => {
|
||||
cy.findByText('SSO not active')
|
||||
cy.findByText('Not managed')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when user is managed', function () {
|
||||
beforeEach(function () {
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(4)').within(() => {
|
||||
cy.get('.sr-only').contains('SSO active')
|
||||
cy.get('.sr-only').contains('Managed')
|
||||
cy.get('.action-btn').click()
|
||||
cy.findByTestId('unlink-user-action').click()
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(4)').within(() => {
|
||||
cy.findByText('SSO active')
|
||||
cy.findAllByText('Managed')
|
||||
cy.findByRole('button', { name: /actions/i }).click()
|
||||
cy.findByTestId('unlink-user-action').click()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should show successs notification and update the user row after unlinking', function () {
|
||||
cy.get('.modal').within(() => {
|
||||
cy.get('.btn-danger').click()
|
||||
cy.findByRole('dialog').within(() => {
|
||||
cy.findByRole('button', { name: /unlink user/i }).click()
|
||||
})
|
||||
cy.get('.notification').contains(
|
||||
cy.findByRole('alert').should(
|
||||
'contain.text',
|
||||
`SSO reauthentication request has been sent to ${USER_LINKED_AND_MANAGED.email}`
|
||||
)
|
||||
cy.get('ul.managed-users-list table > tbody').within(() => {
|
||||
cy.get('tr:nth-child(4)').within(() => {
|
||||
cy.get('.sr-only').contains('SSO not active')
|
||||
cy.get('.sr-only').contains('Managed')
|
||||
cy.findByTestId('managed-users-table')
|
||||
.find('tbody')
|
||||
.within(() => {
|
||||
cy.get('tr:nth-child(4)').within(() => {
|
||||
cy.findByText('SSO not active')
|
||||
cy.findAllByText('Managed')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user