Skip to content

Commit

Permalink
fix: Show all the members from a group even unreachable ones
Browse files Browse the repository at this point in the history
When the flag `sharing.show-recipient-groups` is set, all the members of a group are counted to maintain consistency with the display of detail modal when clicking on a group recipient.
  • Loading branch information
cballevre committed Apr 8, 2024
1 parent 774543b commit e62ec59
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 52 deletions.
107 changes: 67 additions & 40 deletions packages/cozy-sharing/src/components/ShareRecipientsInput.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import get from 'lodash/get'
import PropTypes from 'prop-types'
import React, { useMemo } from 'react'
import React from 'react'

import { useQueryAll, isQueryLoading } from 'cozy-client'
import flag from 'cozy-flags'

import ShareAutosuggest from './ShareAutosuggest'
import { getContactsFromGroupId } from '../helpers/contacts'
import {
buildReachableContactsQuery,
buildContactGroupsQuery
buildContactGroupsByIdsQuery,
buildUnreachableContactsWithGroupsQuery
} from '../queries/queries'

/**
* ShareRecipientsInput is responsible for fetching contacts and groups.
* We retrieve all the contacts that are reachable and the groups they belong to.
* In order to display the total number of members in each group, we also retrieve the contacts that are not reachable.
*/
const ShareRecipientsInput = ({
recipients,
placeholder,
Expand All @@ -22,55 +29,75 @@ const ShareRecipientsInput = ({
reachableContactsQuery.options
)

const contactGroupsQuery = buildContactGroupsQuery()
const contactGroupsResult = useQueryAll(
contactGroupsQuery.definition,
contactGroupsQuery.options
const unreachableContactsWithGroupsQuery =
buildUnreachableContactsWithGroupsQuery()
const unreachableContactsWithGroupsResult = useQueryAll(
unreachableContactsWithGroupsQuery.definition,
{
...unreachableContactsWithGroupsQuery.options,
enabled: !!flag('sharing.show-recipient-groups')
}
)

const contactGroupsByIdsQuery = buildContactGroupsByIdsQuery([
...new Set(
(reachableContactsResult.data || []).flatMap(contact => {
return (contact.relationships?.groups?.data || []).map(
group => group._id
)
})
)
])
const contactGroupsByIdsResult = useQueryAll(
contactGroupsByIdsQuery.definition,
contactGroupsByIdsQuery.options
)

const isLoading =
isQueryLoading(reachableContactsResult) ||
reachableContactsResult.hasMore ||
isQueryLoading(contactGroupsResult) ||
contactGroupsResult.hasMore
isQueryLoading(contactGroupsByIdsResult) ||
contactGroupsByIdsResult.hasMore ||
(flag('sharing.show-recipient-groups') &&
(isQueryLoading(unreachableContactsWithGroupsResult) ||
unreachableContactsWithGroupsResult.hasMore))

const contactsAndGroups = useMemo(() => {
// we need contacts to be loaded to be able to add all group members to recipients
if (
reachableContactsResult.hasMore ||
reachableContactsResult.fetchStatus === 'loading'
) {
return reachableContactsResult.data
}
const contactGroups = (contactGroupsByIdsResult.data || []).map(
contactGroup => {
const reachableMembers = getContactsFromGroupId(
reachableContactsResult.data,
contactGroup.id
).map(contact => ({
...contact,
isReachable: true
}))

if (
!reachableContactsResult.hasMore &&
reachableContactsResult.fetchStatus === 'loaded' &&
!contactGroupsResult.hasMore &&
contactGroupsResult.fetchStatus === 'loaded'
) {
const contactGroupsWithMembers = contactGroupsResult.data
.map(contactGroup => ({
if (flag('sharing.show-recipient-groups') !== true) {
return {
...contactGroup,
members: reachableContactsResult.data.reduce((members, contact) => {
if (
get(contact, 'relationships.groups.data', [])
.map(group => group._id)
.includes(contactGroup._id)
) {
return [...members, contact]
}
members: reachableMembers
}
}

return members
}, [])
}))
.filter(group => group.members.length > 0)
const unreachableMembers = getContactsFromGroupId(
unreachableContactsWithGroupsResult.data,
contactGroup.id
).map(contact => ({
...contact,
isReachable: false
}))

return [...reachableContactsResult.data, ...contactGroupsWithMembers]
return {
...contactGroup,
members: [...reachableMembers, ...unreachableMembers]
}
}
)

return []
}, [reachableContactsResult, contactGroupsResult])
const contactsAndGroups = [
...(reachableContactsResult.data || []),
...contactGroups
]

return (
<ShareAutosuggest
Expand Down
72 changes: 63 additions & 9 deletions packages/cozy-sharing/src/components/ShareRecipientsInput.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ import { render, screen } from '@testing-library/react'
import React from 'react'

import { createMockClient } from 'cozy-client'
import flag from 'cozy-flags'

import ShareRecipientsInput from './ShareRecipientsInput'
import AppLike from './SharingBanner/test/AppLike'

jest.mock('cozy-flags', () => ({
__esModule: true,
default: jest.fn()
}))

jest.mock('./ShareAutosuggest', () => ({
__esModule: true,
default: ({ contactsAndGroups }) => {
Expand All @@ -19,7 +25,11 @@ jest.mock('./ShareAutosuggest', () => ({
</li>
)
} else {
return <li key={contactOrGroup.id}>{contactOrGroup.name}</li>
return (
<li key={contactOrGroup.id}>
{contactOrGroup.name} - {contactOrGroup.members.length} members
</li>
)
}
})}
</ul>
Expand All @@ -28,17 +38,20 @@ jest.mock('./ShareAutosuggest', () => ({
}))

describe('ShareRecipientsInput component', () => {
const setup = ({ contacts, contactGroups }) => {
const setup = ({ contacts, contactGroups, unreachableContact }) => {
const mockClient = createMockClient({
queries: {
'io.cozy.contacts/reachable': {
doctype: 'io.cozy.contacts',
data: contacts
},
'io.cozy.contacts.groups': {
'io.cozy.contacts.groups/by-ids/fe86af20-c6c5': {
doctype: 'io.cozy.contacts.groups',
data: contactGroups,
hasMore: false
data: contactGroups
},
'io.cozy.contacts/unreachable-with-groups': {
doctype: 'io.cozy.contacts',
data: unreachableContact
}
}
})
Expand Down Expand Up @@ -98,10 +111,14 @@ describe('ShareRecipientsInput component', () => {

expect(screen.getByText('Michale Russel')).toBeInTheDocument()
expect(screen.getByText('Teagan Wolf')).toBeInTheDocument()
expect(screen.getByText("The Night's Watch")).toBeInTheDocument()
expect(
screen.getByText("The Night's Watch - 1 members")
).toBeInTheDocument()
})

it('should include groups only if they have more than one member', async () => {
it('should include unreachable members when flag sharing.show-recipient-groups is activated', async () => {
flag.mockReturnValue(true)

const contacts = [
{
id: 'df563cc4-6440',
Expand All @@ -119,6 +136,16 @@ describe('ShareRecipientsInput component', () => {
name: {
givenName: 'Teagan',
familyName: 'Wolf'
},
relationships: {
groups: {
data: [
{
_id: 'fe86af20-c6c5',
_type: 'io.cozy.contacts.groups'
}
]
}
}
}
]
Expand All @@ -132,10 +159,37 @@ describe('ShareRecipientsInput component', () => {
}
]

setup({ contacts, contactGroups })
const unreachableContact = [
{
id: 'df532cc4-6560',
_id: 'df532cc4-6560',
_type: 'io.cozy.contacts',
name: {
givenName: 'Tony',
familyName: 'Stark'
},
relationships: {
groups: {
data: [
{
_id: 'fe86af20-c6c5',
_type: 'io.cozy.contacts.groups'
}
]
}
}
}
]

setup({ contacts, contactGroups, unreachableContact })

expect(screen.getByText('Michale Russel')).toBeInTheDocument()
expect(screen.getByText('Teagan Wolf')).toBeInTheDocument()
expect(screen.queryByText("The Night's Watch")).toBeNull()
// 3 members because cozy-client replay the query with the new data
// so the unreachableContact is added to the reachable contact query
// TODO: improve createMockClient to handle this case
expect(
screen.getByText("The Night's Watch - 3 members")
).toBeInTheDocument()
})
})
8 changes: 8 additions & 0 deletions packages/cozy-sharing/src/helpers/contacts.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,11 @@ export const getOrCreateFromArray = async (client, contacts, createContact) => {
})
)
}

export const getContactsFromGroupId = (contacts = [], groupId) => {
return contacts.filter(contact => {
return contact.relationships?.groups?.data?.some(
group => group._id === groupId
)
})
}
1 change: 1 addition & 0 deletions packages/cozy-sharing/src/helpers/recipients.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export const hasReachRecipientsLimit = (current, next) => {

/**
* Merges the recipients list with a new recipient and their associated contacts.
* Only used when flag sharing.show-recipient-groups is not enabled.
* @param {object[]} recipients - The list of recipients.
* @param {object} recipient - The new recipient.
* @param {object[]} contacts - The list of contacts.
Expand Down
37 changes: 34 additions & 3 deletions packages/cozy-sharing/src/queries/queries.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,41 @@ export const buildReachableContactsQuery = () => ({
}
})

export const buildContactGroupsQuery = () => ({
definition: Q(Group.doctype),
export const buildContactGroupsByIdsQuery = ids => ({
definition: Q(Group.doctype).getByIds(ids),
options: {
as: 'io.cozy.contacts.groups'
as: `io.cozy.contacts.groups/by-ids/${ids.join('/')}`,
fetchPolicy: defaultFetchPolicy,
enabled: ids.length > 0
}
})

export const buildUnreachableContactsWithGroupsQuery = () => ({
definition: Q(Contact.doctype)
.where({
_id: {
$gt: null
}
})
.partialIndex({
trashed: {
$or: [{ $eq: false }, { $exists: false }]
},
cozy: {
$size: 0
},
email: {
$size: 0
},
'relationships.groups.data': {
$exists: true
}
})
.indexFields(['_id'])
.limitBy(1000),
options: {
as: 'io.cozy.contacts/unreachable-with-groups',
fetchPolicy: defaultFetchPolicy
}
})

Expand Down

0 comments on commit e62ec59

Please sign in to comment.