Merge pull request #15669 from overleaf/ab-fix-sso-linking-status

[web] Fix SSO status in group members table

GitOrigin-RevId: e54e7b0c9640f0b96d9692c0208357e3bac2de91
This commit is contained in:
Alexandre Bourdin 2023-11-09 17:26:46 +01:00 committed by Copybot
parent 1819f3e4e7
commit c4bea21ee2
7 changed files with 92 additions and 35 deletions

View file

@ -66,6 +66,7 @@ function buildUserViewModel(user, isInvite) {
? { ? {
managedBy: user.enrollment.managedBy, managedBy: user.enrollment.managedBy,
enrolledAt: user.enrollment.enrolledAt, enrolledAt: user.enrollment.enrolledAt,
sso: user.enrollment.sso,
} }
: undefined, : undefined,
} }

View file

@ -1,13 +1,27 @@
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import getMeta from '@/utils/meta'
import { User } from '../../../../../../types/group-management/user' import { User } from '../../../../../../types/group-management/user'
import MaterialIcon from '@/shared/components/material-icon' import MaterialIcon from '@/shared/components/material-icon'
type SSOStatusProps = { type SSOStatusProps = {
user: User user: User
} }
export default function SSOStatus({ user }: SSOStatusProps) { export default function SSOStatus({ user }: SSOStatusProps) {
const groupId = getMeta('ol-groupId')
if (user.invite) {
return <PendingInvite />
}
const linkedSSO = user.enrollment?.sso?.some(sso => sso.groupId === groupId)
return linkedSSO ? <SSOLinked /> : <SSOUnlinked />
}
function PendingInvite() {
const { t } = useTranslation() const { t } = useTranslation()
const invitedSSO = ( return (
<span className="security-state-invite-pending"> <span className="security-state-invite-pending">
<MaterialIcon <MaterialIcon
type="schedule" type="schedule"
@ -17,22 +31,24 @@ export default function SSOStatus({ user }: SSOStatusProps) {
&nbsp; {t('sso')} &nbsp; {t('sso')}
</span> </span>
) )
const acceptedSSO = ( }
function SSOLinked() {
const { t } = useTranslation()
return (
<span className="security-state-managed"> <span className="security-state-managed">
<MaterialIcon type="check" accessibilityLabel={t('sso_active')} /> <MaterialIcon type="check" accessibilityLabel={t('sso_active')} />
&nbsp; {t('sso')} &nbsp; {t('sso')}
</span> </span>
) )
const notAcceptedSSO = ( }
function SSOUnlinked() {
const { t } = useTranslation()
return (
<span className="security-state-not-managed"> <span className="security-state-not-managed">
<MaterialIcon type="close" accessibilityLabel={t('sso_not_active')} /> <MaterialIcon type="close" accessibilityLabel={t('sso_not_active')} />
&nbsp; {t('sso')} &nbsp; {t('sso')}
</span> </span>
) )
if (user.invite) {
return invitedSSO
}
return user.enrollment?.sso ? acceptedSSO : notAcceptedSSO
} }

View file

@ -1,5 +1,6 @@
import GroupMembers from '@/features/group-management/components/group-members' import GroupMembers from '@/features/group-management/components/group-members'
import { GroupMembersProvider } from '@/features/group-management/context/group-members-context' import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
import { User } from '../../../../../types/group-management/user'
const GROUP_ID = '777fff777fff' const GROUP_ID = '777fff777fff'
const PATHS = { const PATHS = {
@ -173,7 +174,7 @@ describe('GroupMembers', function () {
}) })
describe('with Managed Users enabled', function () { describe('with Managed Users enabled', function () {
const JOHN_DOE = { const JOHN_DOE: User = {
_id: 'abc123def456', _id: 'abc123def456',
first_name: 'John', first_name: 'John',
last_name: 'Doe', last_name: 'Doe',
@ -181,7 +182,7 @@ describe('GroupMembers', function () {
last_active_at: new Date('2023-01-15'), last_active_at: new Date('2023-01-15'),
invite: true, invite: true,
} }
const BOBBY_LAPOINTE = { const BOBBY_LAPOINTE: User = {
_id: 'bcd234efa567', _id: 'bcd234efa567',
first_name: 'Bobby', first_name: 'Bobby',
last_name: 'Lapointe', last_name: 'Lapointe',
@ -189,7 +190,7 @@ describe('GroupMembers', function () {
last_active_at: new Date('2023-01-02'), last_active_at: new Date('2023-01-02'),
invite: false, invite: false,
} }
const CLAIRE_JENNINGS = { const CLAIRE_JENNINGS: User = {
_id: 'defabc231453', _id: 'defabc231453',
first_name: 'Claire', first_name: 'Claire',
last_name: 'Jennings', last_name: 'Jennings',
@ -199,10 +200,13 @@ describe('GroupMembers', function () {
enrollment: { enrollment: {
managedBy: GROUP_ID, managedBy: GROUP_ID,
enrolledAt: new Date('2023-01-03'), enrolledAt: new Date('2023-01-03'),
sso: { sso: [
providerId: '123', {
externalId: '123', groupId: GROUP_ID,
linkedAt: new Date(),
primary: true,
}, },
],
}, },
} }
@ -376,7 +380,7 @@ describe('GroupMembers', function () {
}) })
describe('with Group SSO enabled', function () { describe('with Group SSO enabled', function () {
const JOHN_DOE = { const JOHN_DOE: User = {
_id: 'abc123def456', _id: 'abc123def456',
first_name: 'John', first_name: 'John',
last_name: 'Doe', last_name: 'Doe',
@ -384,7 +388,7 @@ describe('GroupMembers', function () {
last_active_at: new Date('2023-01-15'), last_active_at: new Date('2023-01-15'),
invite: true, invite: true,
} }
const BOBBY_LAPOINTE = { const BOBBY_LAPOINTE: User = {
_id: 'bcd234efa567', _id: 'bcd234efa567',
first_name: 'Bobby', first_name: 'Bobby',
last_name: 'Lapointe', last_name: 'Lapointe',
@ -392,7 +396,7 @@ describe('GroupMembers', function () {
last_active_at: new Date('2023-01-02'), last_active_at: new Date('2023-01-02'),
invite: false, invite: false,
} }
const CLAIRE_JENNINGS = { const CLAIRE_JENNINGS: User = {
_id: 'defabc231453', _id: 'defabc231453',
first_name: 'Claire', first_name: 'Claire',
last_name: 'Jennings', last_name: 'Jennings',
@ -402,10 +406,13 @@ describe('GroupMembers', function () {
enrollment: { enrollment: {
managedBy: GROUP_ID, managedBy: GROUP_ID,
enrolledAt: new Date('2023-01-03'), enrolledAt: new Date('2023-01-03'),
sso: { sso: [
providerId: '123', {
externalId: '123', groupId: GROUP_ID,
linkedAt: new Date(),
primary: true,
}, },
],
}, },
} }

View file

@ -1,8 +1,9 @@
import GroupMembers from '@/features/group-management/components/group-members' import GroupMembers from '@/features/group-management/components/group-members'
import { GroupMembersProvider } from '@/features/group-management/context/group-members-context' import { GroupMembersProvider } from '@/features/group-management/context/group-members-context'
import { User } from '../../../../../types/group-management/user'
const GROUP_ID = '777fff777fff' const GROUP_ID = '777fff777fff'
const JOHN_DOE = { const JOHN_DOE: User = {
_id: 'abc123def456', _id: 'abc123def456',
first_name: 'John', first_name: 'John',
last_name: 'Doe', last_name: 'Doe',
@ -10,15 +11,24 @@ const JOHN_DOE = {
last_active_at: new Date('2023-01-15'), last_active_at: new Date('2023-01-15'),
invite: true, invite: true,
} }
const BOBBY_LAPOINTE = { const BOBBY_LAPOINTE: User = {
_id: 'bcd234efa567', _id: 'bcd234efa567',
first_name: 'Bobby', first_name: 'Bobby',
last_name: 'Lapointe', last_name: 'Lapointe',
email: 'bobby.lapointe@test.com', email: 'bobby.lapointe@test.com',
last_active_at: new Date('2023-01-02'), last_active_at: new Date('2023-01-02'),
invite: false, invite: false,
enrollment: {
sso: [
{
groupId: 'another',
linkedAt: new Date(),
primary: true,
},
],
},
} }
const CLAIRE_JENNINGS = { const CLAIRE_JENNINGS: User = {
_id: 'defabc231453', _id: 'defabc231453',
first_name: 'Claire', first_name: 'Claire',
last_name: 'Jennings', last_name: 'Jennings',
@ -28,10 +38,13 @@ const CLAIRE_JENNINGS = {
enrollment: { enrollment: {
managedBy: GROUP_ID, managedBy: GROUP_ID,
enrolledAt: new Date('2023-01-03'), enrolledAt: new Date('2023-01-03'),
sso: { sso: [
providerId: '123', {
externalId: '123', groupId: GROUP_ID,
linkedAt: new Date(),
primary: true,
}, },
],
}, },
} }
const PATHS = { const PATHS = {

View file

@ -233,6 +233,7 @@ describe('ManagedUserDropdownButton', function () {
}, },
isEntityAdmin: undefined, isEntityAdmin: undefined,
} }
beforeEach(function () { beforeEach(function () {
cy.window().then(win => { cy.window().then(win => {
win.metaAttributesCache.set('ol-users', [user]) win.metaAttributesCache.set('ol-users', [user])
@ -241,11 +242,13 @@ describe('ManagedUserDropdownButton', function () {
win.metaAttributesCache.set('ol-groupSSOActive', true) win.metaAttributesCache.set('ol-groupSSOActive', true)
}) })
}) })
it('should show resend invite when user is admin', function () { it('should show resend invite when user is admin', function () {
mountDropDownComponent({ ...user, isEntityAdmin: true }, '123abc') mountDropDownComponent({ ...user, isEntityAdmin: true }, '123abc')
cy.get('.action-btn').click() cy.get('.action-btn').click()
cy.findByTestId('resend-sso-link-invite-action').should('exist') cy.findByTestId('resend-sso-link-invite-action').should('exist')
}) })
it('should not show resend invite when SSO is disabled', function () { it('should not show resend invite when SSO is disabled', function () {
cy.window().then(win => { cy.window().then(win => {
win.metaAttributesCache.set('ol-groupSSOActive', false) win.metaAttributesCache.set('ol-groupSSOActive', false)
@ -254,6 +257,7 @@ describe('ManagedUserDropdownButton', function () {
cy.get('.action-btn').click() cy.get('.action-btn').click()
cy.findByTestId('resend-sso-link-invite-action').should('not.exist') cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
}) })
it('should not show resend invite when user has accepted SSO already', function () { it('should not show resend invite when user has accepted SSO already', function () {
cy.window().then(win => { cy.window().then(win => {
win.metaAttributesCache.set('ol-groupSSOActive', false) win.metaAttributesCache.set('ol-groupSSOActive', false)
@ -264,10 +268,13 @@ describe('ManagedUserDropdownButton', function () {
enrollment: { enrollment: {
managedBy: 'some-group', managedBy: 'some-group',
enrolledAt: new Date(), enrolledAt: new Date(),
sso: { sso: [
providerId: '123', {
externalId: '123', groupId: 'abc123abc123',
linkedAt: new Date(),
primary: true,
}, },
],
}, },
}, },
'123abc' '123abc'
@ -275,6 +282,7 @@ describe('ManagedUserDropdownButton', function () {
cy.get('.action-btn').click() cy.get('.action-btn').click()
cy.findByTestId('resend-sso-link-invite-action').should('not.exist') cy.findByTestId('resend-sso-link-invite-action').should('not.exist')
}) })
it('should show the resend SSO invite option when dropdown button is clicked', function () { it('should show the resend SSO invite option when dropdown button is clicked', function () {
cy.window().then(win => { cy.window().then(win => {
win.metaAttributesCache.set('ol-groupSSOActive', true) win.metaAttributesCache.set('ol-groupSSOActive', true)
@ -286,6 +294,7 @@ describe('ManagedUserDropdownButton', function () {
Cypress.dom.isVisible($el) Cypress.dom.isVisible($el)
}) })
}) })
it('should make the correct post request when resend SSO invite is clicked ', function () { it('should make the correct post request when resend SSO invite is clicked ', function () {
cy.window().then(win => { cy.window().then(win => {
win.metaAttributesCache.set('ol-groupSSOActive', true) win.metaAttributesCache.set('ol-groupSSOActive', true)

View file

@ -42,6 +42,11 @@ describe('UserMembershipViewModel', function () {
enrollment: { enrollment: {
managedBy: 'mock-group-id', managedBy: 'mock-group-id',
enrolledAt: new Date(), enrolledAt: new Date(),
sso: {
groupId: 'abc123abc123',
linkedAt: new Date(),
primary: true,
},
}, },
} }
}) })

View file

@ -1,7 +1,13 @@
export type SSOEnrollment = {
groupId: string
linkedAt: Date
primary: boolean
}
export type UserEnrollment = { export type UserEnrollment = {
managedBy?: string managedBy?: string
enrolledAt?: Date enrolledAt?: Date
sso?: object sso?: SSOEnrollment[]
} }
export type User = { export type User = {