mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #17683 from overleaf/jel-unlink
[web] Unlink group SSO member GitOrigin-RevId: ac3498fc72538fd0eef639c783dc7675ac593b94
This commit is contained in:
parent
9d78fd0945
commit
d8f870555c
12 changed files with 257 additions and 2 deletions
|
@ -1229,6 +1229,7 @@
|
||||||
"sso_link_invite_has_been_sent_to_email": "",
|
"sso_link_invite_has_been_sent_to_email": "",
|
||||||
"sso_logs": "",
|
"sso_logs": "",
|
||||||
"sso_not_active": "",
|
"sso_not_active": "",
|
||||||
|
"sso_reauth_request": "",
|
||||||
"sso_test_interstitial_info_1": "",
|
"sso_test_interstitial_info_1": "",
|
||||||
"sso_test_interstitial_info_2": "",
|
"sso_test_interstitial_info_2": "",
|
||||||
"sso_test_interstitial_title": "",
|
"sso_test_interstitial_title": "",
|
||||||
|
@ -1446,6 +1447,8 @@
|
||||||
"unlink_provider_account_warning": "",
|
"unlink_provider_account_warning": "",
|
||||||
"unlink_reference": "",
|
"unlink_reference": "",
|
||||||
"unlink_the_project_from_the_current_github_repo": "",
|
"unlink_the_project_from_the_current_github_repo": "",
|
||||||
|
"unlink_user": "",
|
||||||
|
"unlink_user_explanation": "",
|
||||||
"unlink_users": "",
|
"unlink_users": "",
|
||||||
"unlink_warning_reference": "",
|
"unlink_warning_reference": "",
|
||||||
"unlinking": "",
|
"unlinking": "",
|
||||||
|
|
|
@ -22,6 +22,7 @@ type resendInviteResponse = {
|
||||||
type ManagedUserDropdownButtonProps = {
|
type ManagedUserDropdownButtonProps = {
|
||||||
user: User
|
user: User
|
||||||
openOffboardingModalForUser: (user: User) => void
|
openOffboardingModalForUser: (user: User) => void
|
||||||
|
openUnlinkUserModal: (user: User) => void
|
||||||
groupId: string
|
groupId: string
|
||||||
setGroupUserAlert: Dispatch<SetStateAction<GroupUserAlert>>
|
setGroupUserAlert: Dispatch<SetStateAction<GroupUserAlert>>
|
||||||
}
|
}
|
||||||
|
@ -29,6 +30,7 @@ type ManagedUserDropdownButtonProps = {
|
||||||
export default function DropdownButton({
|
export default function DropdownButton({
|
||||||
user,
|
user,
|
||||||
openOffboardingModalForUser,
|
openOffboardingModalForUser,
|
||||||
|
openUnlinkUserModal,
|
||||||
groupId,
|
groupId,
|
||||||
setGroupUserAlert,
|
setGroupUserAlert,
|
||||||
}: ManagedUserDropdownButtonProps) {
|
}: ManagedUserDropdownButtonProps) {
|
||||||
|
@ -178,6 +180,10 @@ export default function DropdownButton({
|
||||||
removeMember(user)
|
removeMember(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onUnlinkUserClick = () => {
|
||||||
|
openUnlinkUserModal(user)
|
||||||
|
}
|
||||||
|
|
||||||
const buttons = []
|
const buttons = []
|
||||||
|
|
||||||
if (userPending) {
|
if (userPending) {
|
||||||
|
@ -208,6 +214,17 @@ export default function DropdownButton({
|
||||||
</MenuItemButton>
|
</MenuItemButton>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (groupSSOActive && isGroupSSOLinked) {
|
||||||
|
buttons.push(
|
||||||
|
<MenuItemButton
|
||||||
|
onClick={onUnlinkUserClick}
|
||||||
|
key="unlink-user-action"
|
||||||
|
data-testid="unlink-user-action"
|
||||||
|
>
|
||||||
|
{t('unlink_user')}
|
||||||
|
</MenuItemButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
if (groupSSOActive && !isGroupSSOLinked && !userPending) {
|
if (groupSSOActive && !isGroupSSOLinked && !userPending) {
|
||||||
buttons.push(
|
buttons.push(
|
||||||
<MenuItemButton
|
<MenuItemButton
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { type PropsWithChildren, useState } from 'react'
|
||||||
import { Alert, type AlertProps } from 'react-bootstrap'
|
import { Alert, type AlertProps } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import type { GroupUserAlertVariant } from '../../utils/types'
|
import type { GroupUserAlertVariant } from '../../utils/types'
|
||||||
|
import NotificationScrolledTo from '@/shared/components/notification-scrolled-to'
|
||||||
|
|
||||||
type GroupUsersListAlertProps = {
|
type GroupUsersListAlertProps = {
|
||||||
variant: GroupUserAlertVariant
|
variant: GroupUserAlertVariant
|
||||||
|
@ -53,6 +54,25 @@ export default function ListAlert({
|
||||||
)
|
)
|
||||||
case 'resendInviteTooManyRequests':
|
case 'resendInviteTooManyRequests':
|
||||||
return <TooManyRequests onDismiss={onDismiss} userEmail={userEmail} />
|
return <TooManyRequests onDismiss={onDismiss} userEmail={userEmail} />
|
||||||
|
case 'unlinkedSSO':
|
||||||
|
return (
|
||||||
|
<NotificationScrolledTo
|
||||||
|
type="success"
|
||||||
|
content={
|
||||||
|
<Trans
|
||||||
|
i18nKey="sso_reauth_request"
|
||||||
|
values={{ email: userEmail }}
|
||||||
|
components={[<strong />]} // eslint-disable-line react/jsx-key
|
||||||
|
shouldUnescape
|
||||||
|
tOptions={{ interpolation: { escapeValue: true } }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
id="sso-user-unlinked"
|
||||||
|
ariaLive="polite"
|
||||||
|
isDismissible
|
||||||
|
onDismiss={onDismiss}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ import getMeta from '@/utils/meta'
|
||||||
type ManagedUserRowProps = {
|
type ManagedUserRowProps = {
|
||||||
user: User
|
user: User
|
||||||
openOffboardingModalForUser: (user: User) => void
|
openOffboardingModalForUser: (user: User) => void
|
||||||
|
openUnlinkUserModal: (user: User) => void
|
||||||
groupId: string
|
groupId: string
|
||||||
setGroupUserAlert: Dispatch<SetStateAction<GroupUserAlert>>
|
setGroupUserAlert: Dispatch<SetStateAction<GroupUserAlert>>
|
||||||
}
|
}
|
||||||
|
@ -21,6 +22,7 @@ type ManagedUserRowProps = {
|
||||||
export default function MemberRow({
|
export default function MemberRow({
|
||||||
user,
|
user,
|
||||||
openOffboardingModalForUser,
|
openOffboardingModalForUser,
|
||||||
|
openUnlinkUserModal,
|
||||||
setGroupUserAlert,
|
setGroupUserAlert,
|
||||||
groupId,
|
groupId,
|
||||||
}: ManagedUserRowProps) {
|
}: ManagedUserRowProps) {
|
||||||
|
@ -95,6 +97,7 @@ export default function MemberRow({
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={openOffboardingModalForUser}
|
openOffboardingModalForUser={openOffboardingModalForUser}
|
||||||
|
openUnlinkUserModal={openUnlinkUserModal}
|
||||||
setGroupUserAlert={setGroupUserAlert}
|
setGroupUserAlert={setGroupUserAlert}
|
||||||
groupId={groupId}
|
groupId={groupId}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,6 +11,7 @@ import ListAlert from './list-alert'
|
||||||
import SelectAllCheckbox from './select-all-checkbox'
|
import SelectAllCheckbox from './select-all-checkbox'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import getMeta from '@/utils/meta'
|
import getMeta from '@/utils/meta'
|
||||||
|
import UnlinkUserModal from './unlink-user-modal'
|
||||||
|
|
||||||
type ManagedUsersListProps = {
|
type ManagedUsersListProps = {
|
||||||
groupId: string
|
groupId: string
|
||||||
|
@ -23,6 +24,7 @@ export default function MembersList({ groupId }: ManagedUsersListProps) {
|
||||||
)
|
)
|
||||||
const [groupUserAlert, setGroupUserAlert] =
|
const [groupUserAlert, setGroupUserAlert] =
|
||||||
useState<GroupUserAlert>(undefined)
|
useState<GroupUserAlert>(undefined)
|
||||||
|
const [userToUnlink, setUserToUnlink] = useState<User | undefined>(undefined)
|
||||||
const { users } = useGroupMembersContext()
|
const { users } = useGroupMembersContext()
|
||||||
const managedUsersActive: any = getMeta('ol-managedUsersActive')
|
const managedUsersActive: any = getMeta('ol-managedUsersActive')
|
||||||
const groupSSOActive = getMeta('ol-groupSSOActive')
|
const groupSSOActive = getMeta('ol-groupSSOActive')
|
||||||
|
@ -100,6 +102,7 @@ export default function MembersList({ groupId }: ManagedUsersListProps) {
|
||||||
key={user.email}
|
key={user.email}
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={setUserToOffboard}
|
openOffboardingModalForUser={setUserToOffboard}
|
||||||
|
openUnlinkUserModal={setUserToUnlink}
|
||||||
setGroupUserAlert={setGroupUserAlert}
|
setGroupUserAlert={setGroupUserAlert}
|
||||||
groupId={groupId}
|
groupId={groupId}
|
||||||
/>
|
/>
|
||||||
|
@ -118,6 +121,13 @@ export default function MembersList({ groupId }: ManagedUsersListProps) {
|
||||||
onClose={() => setUserToOffboard(undefined)}
|
onClose={() => setUserToOffboard(undefined)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{userToUnlink && (
|
||||||
|
<UnlinkUserModal
|
||||||
|
user={userToUnlink}
|
||||||
|
onClose={() => setUserToUnlink(undefined)}
|
||||||
|
setGroupUserAlert={setGroupUserAlert}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
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'
|
||||||
|
import { SetStateAction, useCallback, useState, type Dispatch } from 'react'
|
||||||
|
import useAsync from '@/shared/hooks/use-async'
|
||||||
|
import { postJSON } from '@/infrastructure/fetch-json'
|
||||||
|
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'
|
||||||
|
|
||||||
|
export type UnlinkUserModalProps = {
|
||||||
|
onClose: () => void
|
||||||
|
user: User
|
||||||
|
setGroupUserAlert: Dispatch<SetStateAction<GroupUserAlert>>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UnlinkUserModal({
|
||||||
|
onClose,
|
||||||
|
user,
|
||||||
|
setGroupUserAlert,
|
||||||
|
}: UnlinkUserModalProps) {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const groupId = getMeta('ol-groupId')
|
||||||
|
const [hasError, setHasError] = useState<string | undefined>()
|
||||||
|
const { isLoading: unlinkInFlight, runAsync, reset } = useAsync()
|
||||||
|
const { updateMemberView } = useGroupMembersContext()
|
||||||
|
|
||||||
|
const setUserAsUnlinked = useCallback(() => {
|
||||||
|
if (!user.enrollment?.sso) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const updatedUser = Object.assign({}, user, {
|
||||||
|
enrollment: {
|
||||||
|
sso: user.enrollment.sso.filter(sso => sso.groupId !== groupId),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
updateMemberView(user._id, updatedUser)
|
||||||
|
}, [groupId, updateMemberView, user])
|
||||||
|
|
||||||
|
const handleUnlink = useCallback(
|
||||||
|
event => {
|
||||||
|
event.preventDefault()
|
||||||
|
setHasError(undefined)
|
||||||
|
if (!user) {
|
||||||
|
setHasError(t('generic_something_went_wrong'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
runAsync(postJSON(`/manage/groups/${groupId}/unlink-user/${user._id}`))
|
||||||
|
.then(() => {
|
||||||
|
setUserAsUnlinked()
|
||||||
|
setGroupUserAlert({
|
||||||
|
variant: 'unlinkedSSO',
|
||||||
|
email: user.email,
|
||||||
|
})
|
||||||
|
onClose()
|
||||||
|
reset()
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
debugConsole.error(e)
|
||||||
|
setHasError(t('generic_something_went_wrong'))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[
|
||||||
|
groupId,
|
||||||
|
onClose,
|
||||||
|
reset,
|
||||||
|
runAsync,
|
||||||
|
setGroupUserAlert,
|
||||||
|
setUserAsUnlinked,
|
||||||
|
t,
|
||||||
|
user,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AccessibleModal show onHide={onClose}>
|
||||||
|
<Modal.Header>
|
||||||
|
<Modal.Title>{t('unlink_user')}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
{hasError && (
|
||||||
|
<div className="mb-3">
|
||||||
|
<NotificationScrolledTo
|
||||||
|
type="error"
|
||||||
|
content={hasError}
|
||||||
|
id="alert-unlink-user-error"
|
||||||
|
ariaLive="polite"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<p>
|
||||||
|
<Trans
|
||||||
|
i18nKey="unlink_user_explanation"
|
||||||
|
components={[<strong />]} // eslint-disable-line react/jsx-key
|
||||||
|
values={{ email: user.email }}
|
||||||
|
shouldUnescape
|
||||||
|
tOptions={{ interpolation: { escapeValue: true } }}
|
||||||
|
/>
|
||||||
|
</p>
|
||||||
|
</Modal.Body>
|
||||||
|
<Modal.Footer>
|
||||||
|
<button className="btn btn-secondary" disabled={unlinkInFlight}>
|
||||||
|
{t('cancel')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn btn-danger"
|
||||||
|
onClick={e => handleUnlink(e)}
|
||||||
|
disabled={unlinkInFlight}
|
||||||
|
>
|
||||||
|
{t('unlink_user')}
|
||||||
|
</button>
|
||||||
|
</Modal.Footer>
|
||||||
|
</AccessibleModal>
|
||||||
|
)
|
||||||
|
}
|
|
@ -28,6 +28,7 @@ export type GroupMembersContextValue = {
|
||||||
removeMember: (user: User) => Promise<void>
|
removeMember: (user: User) => Promise<void>
|
||||||
removeMemberLoading: boolean
|
removeMemberLoading: boolean
|
||||||
removeMemberError?: APIError
|
removeMemberError?: APIError
|
||||||
|
updateMemberView: (userId: string, updatedUser: User) => void
|
||||||
inviteMemberLoading: boolean
|
inviteMemberLoading: boolean
|
||||||
inviteError?: APIError
|
inviteError?: APIError
|
||||||
paths: { [key: string]: string }
|
paths: { [key: string]: string }
|
||||||
|
@ -140,6 +141,21 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
|
||||||
[selectedUsers, removeMember]
|
[selectedUsers, removeMember]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const updateMemberView = useCallback(
|
||||||
|
(userId, updatedUser) => {
|
||||||
|
setUsers(
|
||||||
|
users.map(u => {
|
||||||
|
if (u._id === userId) {
|
||||||
|
return updatedUser
|
||||||
|
} else {
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[setUsers, users]
|
||||||
|
)
|
||||||
|
|
||||||
const value = useMemo<GroupMembersContextValue>(
|
const value = useMemo<GroupMembersContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
users,
|
users,
|
||||||
|
@ -149,6 +165,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
|
||||||
selectAllNonManagedUsers,
|
selectAllNonManagedUsers,
|
||||||
selectUser,
|
selectUser,
|
||||||
unselectUser,
|
unselectUser,
|
||||||
|
updateMemberView,
|
||||||
addMembers,
|
addMembers,
|
||||||
removeMembers,
|
removeMembers,
|
||||||
removeMember,
|
removeMember,
|
||||||
|
@ -166,6 +183,7 @@ export function GroupMembersProvider({ children }: GroupMembersProviderProps) {
|
||||||
selectAllNonManagedUsers,
|
selectAllNonManagedUsers,
|
||||||
selectUser,
|
selectUser,
|
||||||
unselectUser,
|
unselectUser,
|
||||||
|
updateMemberView,
|
||||||
addMembers,
|
addMembers,
|
||||||
removeMembers,
|
removeMembers,
|
||||||
removeMember,
|
removeMember,
|
||||||
|
|
|
@ -6,6 +6,7 @@ export type GroupUserAlertVariant =
|
||||||
| 'resendInviteTooManyRequests'
|
| 'resendInviteTooManyRequests'
|
||||||
| 'resendSSOLinkInviteSuccess'
|
| 'resendSSOLinkInviteSuccess'
|
||||||
| 'resendSSOLinkInviteFailed'
|
| 'resendSSOLinkInviteFailed'
|
||||||
|
| 'unlinkedSSO'
|
||||||
|
|
||||||
export type GroupUserAlert =
|
export type GroupUserAlert =
|
||||||
| {
|
| {
|
||||||
|
|
|
@ -1780,6 +1780,7 @@
|
||||||
"sso_logs": "SSO Logs",
|
"sso_logs": "SSO Logs",
|
||||||
"sso_not_active": "SSO not active",
|
"sso_not_active": "SSO not active",
|
||||||
"sso_not_linked": "You have not linked your account to __provider__. Please log in to your account another way and link your __provider__ account via your account settings.",
|
"sso_not_linked": "You have not linked your account to __provider__. Please log in to your account another way and link your __provider__ account via your account settings.",
|
||||||
|
"sso_reauth_request": "SSO reauthentication request has been sent to <0>__email__</0>",
|
||||||
"sso_test_interstitial_info_1": "<0>Before starting this test</0>, please ensure you’ve <1>configured Overleaf as a Service Provider in your IdP</1>, and authorized access to the Overleaf service.",
|
"sso_test_interstitial_info_1": "<0>Before starting this test</0>, please ensure you’ve <1>configured Overleaf as a Service Provider in your IdP</1>, and authorized access to the Overleaf service.",
|
||||||
"sso_test_interstitial_info_2": "Clicking <0>Test configuration</0> will redirect you to your IdP’s login screen. <1>Read our documentation</1> for full details of what happens during the test. And check our <2>SSO troubleshooting advice</2> if you get stuck.",
|
"sso_test_interstitial_info_2": "Clicking <0>Test configuration</0> will redirect you to your IdP’s login screen. <1>Read our documentation</1> for full details of what happens during the test. And check our <2>SSO troubleshooting advice</2> if you get stuck.",
|
||||||
"sso_test_interstitial_title": "Let’s test your SSO configuration",
|
"sso_test_interstitial_title": "Let’s test your SSO configuration",
|
||||||
|
@ -2060,6 +2061,8 @@
|
||||||
"unlink_provider_account_warning": "Warning: When you unlink your account from __provider__ you will not be able to sign in using __provider__ anymore.",
|
"unlink_provider_account_warning": "Warning: When you unlink your account from __provider__ you will not be able to sign in using __provider__ anymore.",
|
||||||
"unlink_reference": "Unlink References Provider",
|
"unlink_reference": "Unlink References Provider",
|
||||||
"unlink_the_project_from_the_current_github_repo": "Unlink the project from the current GitHub repository and create a connection to a repository you own. (You need an active __appName__ subscription to set up a GitHub Sync).",
|
"unlink_the_project_from_the_current_github_repo": "Unlink the project from the current GitHub repository and create a connection to a repository you own. (You need an active __appName__ subscription to set up a GitHub Sync).",
|
||||||
|
"unlink_user": "Unlink user",
|
||||||
|
"unlink_user_explanation": "You’re about to remove the SSO login option for <0>__email__</0>. This will force them to reauthenticate their Overleaf account with your IdP. They’ll receive an email asking them to do this.",
|
||||||
"unlink_users": "Unlink users",
|
"unlink_users": "Unlink users",
|
||||||
"unlink_warning_reference": "Warning: When you unlink your account from this provider you will not be able to import references into your projects.",
|
"unlink_warning_reference": "Warning: When you unlink your account from this provider you will not be able to import references into your projects.",
|
||||||
"unlinking": "Unlinking",
|
"unlinking": "Unlinking",
|
||||||
|
|
|
@ -23,6 +23,7 @@ function mountDropDownComponent(user: User, subscriptionId: string) {
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -314,6 +315,7 @@ describe('DropdownButton', function () {
|
||||||
|
|
||||||
cy.findByTestId('resend-managed-user-invite-action').should('not.exist')
|
cy.findByTestId('resend-managed-user-invite-action').should('not.exist')
|
||||||
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
||||||
|
cy.findByTestId('unlink-user-action').should('not.exist')
|
||||||
cy.findByTestId('no-actions-available').should('not.exist')
|
cy.findByTestId('no-actions-available').should('not.exist')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -422,6 +424,7 @@ describe('DropdownButton', function () {
|
||||||
|
|
||||||
cy.findByTestId('resend-managed-user-invite-action').should('not.exist')
|
cy.findByTestId('resend-managed-user-invite-action').should('not.exist')
|
||||||
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
||||||
|
cy.findByTestId('unlink-user-action').should('not.exist')
|
||||||
cy.findByTestId('no-actions-available').should('not.exist')
|
cy.findByTestId('no-actions-available').should('not.exist')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -468,6 +471,7 @@ describe('DropdownButton', function () {
|
||||||
'be.visible'
|
'be.visible'
|
||||||
)
|
)
|
||||||
cy.findByTestId('remove-user-action').should('be.visible')
|
cy.findByTestId('remove-user-action').should('be.visible')
|
||||||
|
cy.findByTestId('unlink-user-action').should('be.visible')
|
||||||
|
|
||||||
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
||||||
})
|
})
|
||||||
|
@ -512,6 +516,7 @@ describe('DropdownButton', function () {
|
||||||
cy.findByTestId('resend-sso-link-invite-action').should('be.visible')
|
cy.findByTestId('resend-sso-link-invite-action').should('be.visible')
|
||||||
|
|
||||||
cy.findByTestId('no-actions-available').should('not.exist')
|
cy.findByTestId('no-actions-available').should('not.exist')
|
||||||
|
cy.findByTestId('unlink-user-action').should('not.exist')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -557,6 +562,7 @@ describe('DropdownButton', function () {
|
||||||
'be.visible'
|
'be.visible'
|
||||||
)
|
)
|
||||||
cy.findByTestId('remove-user-action').should('be.visible')
|
cy.findByTestId('remove-user-action').should('be.visible')
|
||||||
|
cy.findByTestId('unlink-user-action').should('be.visible')
|
||||||
|
|
||||||
cy.findByTestId('delete-user-action').should('not.exist')
|
cy.findByTestId('delete-user-action').should('not.exist')
|
||||||
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
|
||||||
|
@ -779,11 +785,12 @@ describe('DropdownButton', function () {
|
||||||
cy.get(`.action-btn`).should('exist')
|
cy.get(`.action-btn`).should('exist')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should show no actions when dropdown button is clicked', function () {
|
it('should show no actions except to unlink when dropdown button is clicked', function () {
|
||||||
cy.get('.action-btn').click()
|
cy.get('.action-btn').click()
|
||||||
|
|
||||||
cy.findByTestId('no-actions-available').should('exist')
|
cy.findByTestId('unlink-user-action').should('exist')
|
||||||
|
|
||||||
|
cy.findByTestId('no-actions-available').should('not.exist')
|
||||||
cy.findByTestId('delete-user-action').should('not.exist')
|
cy.findByTestId('delete-user-action').should('not.exist')
|
||||||
cy.findByTestId('remove-user-action').should('not.exist')
|
cy.findByTestId('remove-user-action').should('not.exist')
|
||||||
cy.findByTestId('resend-managed-user-invite-action').should('not.exist')
|
cy.findByTestId('resend-managed-user-invite-action').should('not.exist')
|
||||||
|
|
|
@ -36,6 +36,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -87,6 +88,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -122,6 +124,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -157,6 +160,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -205,6 +209,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -255,6 +260,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -290,6 +296,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -325,6 +332,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -373,6 +381,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -425,6 +434,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -460,6 +470,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -495,6 +506,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -544,6 +556,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -596,6 +609,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -631,6 +645,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
@ -666,6 +681,7 @@ describe('MemberRow', function () {
|
||||||
<MemberRow
|
<MemberRow
|
||||||
user={user}
|
user={user}
|
||||||
openOffboardingModalForUser={sinon.stub()}
|
openOffboardingModalForUser={sinon.stub()}
|
||||||
|
openUnlinkUserModal={sinon.stub()}
|
||||||
groupId={subscriptionId}
|
groupId={subscriptionId}
|
||||||
setGroupUserAlert={sinon.stub()}
|
setGroupUserAlert={sinon.stub()}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { render, screen } from '@testing-library/react'
|
||||||
|
import { ReactElement } from 'react'
|
||||||
|
import sinon from 'sinon'
|
||||||
|
import fetchMock from 'fetch-mock'
|
||||||
|
import UnlinkUserModal from '@/features/group-management/components/members-table/unlink-user-modal'
|
||||||
|
import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
|
||||||
|
|
||||||
|
export function renderWithContext(component: ReactElement, props = {}) {
|
||||||
|
const GroupMembersProviderWrapper = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: ReactElement
|
||||||
|
}) => <GroupMembersProvider {...props}>{children}</GroupMembersProvider>
|
||||||
|
|
||||||
|
return render(component, { wrapper: GroupMembersProviderWrapper })
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('<UnlinkUserModal />', function () {
|
||||||
|
let defaultProps: any
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
defaultProps = {
|
||||||
|
onClose: sinon.stub(),
|
||||||
|
user: {},
|
||||||
|
setGroupUserAlert: sinon.stub(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
fetchMock.reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('displays the modal', async function () {
|
||||||
|
renderWithContext(<UnlinkUserModal {...defaultProps} />)
|
||||||
|
await screen.findByRole('heading', {
|
||||||
|
name: 'Unlink user',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue