Skip to content

Commit

Permalink
feat: show warning about unreachable users [FS-1573] (#14836)
Browse files Browse the repository at this point in the history
* feat: show warning about unreachable users [FS-1573]

* implement warning when some federated users will never receive a message

* create unit tests for unreachable users and mix of named and unreachable users

Co-authored-by: Thomas Belin <thomasbelin4@gmail.com>

* address cr

Co-authored-by: Przemysław Jóźwik <przemyslaw.jozwik@wire.com>

* replace message logic by if statement

* rename reduceWithCommas to joinWith and add a separator arg

---------

Co-authored-by: Thomas Belin <thomasbelin4@gmail.com>
Co-authored-by: Przemysław Jóźwik <przemyslaw.jozwik@wire.com>
  • Loading branch information
3 people committed Mar 20, 2023
1 parent ea1df95 commit c39d613
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 34 deletions.
13 changes: 8 additions & 5 deletions src/i18n/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -673,12 +673,15 @@
"messageDetailsTitleLikes": "Liked{{count}}",
"messageDetailsTitleReceipts": "Read{{count}}",
"messageFailedToSendHideDetails": "Hide details",
"messageFailedToSendParticipants": "{{count}} Participants",
"messageFailedToSendParticipants": "{{count}} participants",
"messageFailedToSendParticipantsFromDomain": "{{count}} participants from {{domain}}",
"messageFailedToSendParticipantsFromDomainSingular": "1 participant from {{domain}}",
"messageFailedToSendShowDetails": "Show details",
"messageFailedToSendToOne": "will receive your message later.",
"messageFailedToSendToSome": "had issues receiving this message.",
"messageFailedToSendWillNotReceive": "will not receive the message.",
"messageFailedToSendWillReceive": "will receive the message later.",
"messageFailedToSendToSome": "didn't get your message.",
"messageFailedToSendWillNotReceive": "won't get your message.",
"messageFailedToSendWillNotReceiveSingular": "won't get your message.",
"messageFailedToSendWillReceive": "will get your message later.",
"messageFailedToSendWillReceiveSingular": "will get your message later.",
"messageWillNotBeSent": "File could not be sent due to connectivity issues.",
"messageWillNotBeSentDiscard": "Discard",
"mlsToggleInfo": "When this is on, conversation will use the new messaging layer security (MLS) protocol.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import {act, render} from '@testing-library/react';
import type {QualifiedUserClients} from '@wireapp/api-client/lib/conversation';
import {QualifiedId} from '@wireapp/api-client/lib/user';

import en from 'I18n/en-US.json';
import {withTheme} from 'src/script/auth/util/test/TestUtil';
Expand All @@ -45,19 +46,26 @@ function generateUserClients(users: User[]): QualifiedUserClients {
});
return userClients;
}
function generateQualifiedIds(nbUsers: number, domain: string) {
const users: QualifiedId[] = [];
for (let i = 0; i < nbUsers; i++) {
users.push({id: createRandomUuid(), domain});
}
return users;
}

describe('PartialFailureToSendWarning', () => {
it('displays the number of users that did not get the message', () => {
const nbUsers = Math.floor(Math.random() * 100);
const nbUsers = Math.floor(Math.random() * 100) + 2;
const users = generateUsers(nbUsers, 'domain');

const queued = generateUserClients(users);
const {container} = render(withTheme(<PartialFailureToSendWarning knownUsers={[]} failedToSend={{queued}} />));
expect(container.textContent).toContain(`${nbUsers} Participants had issues receiving this message`);
expect(container.textContent).toContain(`${nbUsers} participants didn't get your message`);
});

it('displays the number of users that did not get the message across multiple domains', () => {
const nbUsersDomain1 = Math.floor(Math.random() * 100);
it('displays the number of named users that did not get the message across multiple domains', () => {
const nbUsersDomain1 = Math.floor(Math.random() * 100) + 2;
const nbUsersDomain2 = Math.floor(Math.random() * 100);
const users1 = generateUsers(nbUsersDomain1, 'domain1');
const users2 = generateUsers(nbUsersDomain2, 'domain2');
Expand All @@ -67,20 +75,67 @@ describe('PartialFailureToSendWarning', () => {
...generateUserClients(users2),
};
const {container} = render(withTheme(<PartialFailureToSendWarning knownUsers={[]} failedToSend={{queued}} />));
expect(container.textContent).toContain(`${nbUsersDomain1 + nbUsersDomain2} participants didn't get your message`);
});

it('displays the number of unreachable users that did not get the message across multiple domains', () => {
const nbUsersDomain1 = Math.floor(Math.random() * 100) + 2;
const nbUsersDomain2 = Math.floor(Math.random() * 100);
const users1 = generateQualifiedIds(nbUsersDomain1, 'domain1');
const users2 = generateQualifiedIds(nbUsersDomain2, 'domain2');

const failed = [...users1, ...users2];
const {container} = render(withTheme(<PartialFailureToSendWarning knownUsers={[]} failedToSend={{failed}} />));
expect(container.textContent).toContain(`${nbUsersDomain1 + nbUsersDomain2} participants didn't get your message`);
});

it('displays the number of users, named or unreachable that did not get the message across multiple domains', () => {
const nbUsersDomain1 = Math.floor(Math.random() * 100) + 2;
const nbUsersDomain2 = Math.floor(Math.random() * 100);
const users1 = generateUsers(nbUsersDomain1, 'domain1');
const users2 = generateUsers(nbUsersDomain2, 'domain2');

const queued = {
...generateUserClients(users1),
...generateUserClients(users2),
};

const nbUnreachableUsersDomain1 = Math.floor(Math.random() * 100);
const nbUnreachableUsersDomain2 = Math.floor(Math.random() * 100);
const unreachableUsers1 = generateQualifiedIds(nbUnreachableUsersDomain1, 'domain1');
const unreachableUsers2 = generateQualifiedIds(nbUnreachableUsersDomain2, 'domain2');

const failed = [...unreachableUsers1, ...unreachableUsers2];
const {container} = render(
withTheme(<PartialFailureToSendWarning knownUsers={[]} failedToSend={{queued, failed}} />),
);
expect(container.textContent).toContain(
`${nbUsersDomain1 + nbUsersDomain2} Participants had issues receiving this message`,
`${
nbUsersDomain1 + nbUsersDomain2 + nbUnreachableUsersDomain1 + nbUnreachableUsersDomain2
} participants didn't get your message`,
);
});

it('does not show the extra info toggle if there is only a single user', () => {
it('does not show the extra info toggle if there is only a single named user', () => {
const users = generateUsers(1, 'domain');
const queued = generateUserClients(users);
const {queryByText, container} = render(
withTheme(<PartialFailureToSendWarning knownUsers={users} failedToSend={{queued}} />),
);

expect(queryByText('Show details')).toBeNull();
expect(container.textContent).toContain(`${users[0].username()} will receive your message later`);
expect(container.textContent).toContain(`${users[0].username()} will get your message later`);
});

it('does not show the extra info toggle if there is only a single unreachable user', () => {
const users = generateQualifiedIds(1, 'domain');
const failed = users;
const {queryByText, container} = render(
withTheme(<PartialFailureToSendWarning knownUsers={[]} failedToSend={{failed}} />),
);

expect(queryByText('Show details')).toBeNull();
expect(container.textContent).toContain(`1 participant from domain won't get your message`);
});

it('toggles the extra info', () => {
Expand Down Expand Up @@ -113,7 +168,55 @@ describe('PartialFailureToSendWarning', () => {
getByText('Show details').click();
});

expect(getAllByTestId('recipient')).toHaveLength(nbUsers);
expect(getAllByTestId('named-user')).toHaveLength(nbUsers);
expect(getByText('Hide details')).not.toBeNull();
});

it('displays both the username of named participants and the correct domain of unreachable users when applicable', () => {
const nbNamedUsers = Math.floor(Math.random() * 10) + 2;
const namedUsers = generateUsers(nbNamedUsers, 'domain');
const queued = generateUserClients(namedUsers);

const nbUsersDomain1 = Math.floor(Math.random() * 10) + 2;
const nbUsersDomain2 = Math.floor(Math.random() * 10) + 2;
const failed = [
...generateQualifiedIds(nbUsersDomain1, 'domain1'),
...generateQualifiedIds(nbUsersDomain2, 'domain2'),
];

const {getByText, getAllByTestId, container} = render(
withTheme(<PartialFailureToSendWarning knownUsers={namedUsers} failedToSend={{queued, failed}} />),
);

act(() => {
getByText('Show details').click();
});

expect(getAllByTestId('named-user')).toHaveLength(nbNamedUsers);
expect(container.textContent).toContain(
`${nbUsersDomain1} participants from domain1, ${nbUsersDomain2} participants from domain2 won't get your message`,
);
});

it('displays the info toggle when there is a single named user and a single unreachable user', () => {
const namedUsers = generateUsers(1, 'domain1');
const queued = generateUserClients(namedUsers);

const failed = [...generateQualifiedIds(1, 'domain2')];

const {getByText} = render(
withTheme(<PartialFailureToSendWarning knownUsers={namedUsers} failedToSend={{queued, failed}} />),
);
act(() => {
getByText('Show details').click();
});

expect(getByText('Hide details')).not.toBeNull();

act(() => {
getByText('Hide details').click();
});

expect(getByText('Show details')).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {useState} from 'react';

import type {QualifiedUserClients} from '@wireapp/api-client/lib/conversation';
import {QualifiedId} from '@wireapp/api-client/lib/user';
import {countBy, map} from 'underscore';

import {Bold, Button, ButtonVariant} from '@wireapp/react-ui-kit';

Expand Down Expand Up @@ -60,23 +61,41 @@ function generateNamedUsers(users: User[], userClients: QualifiedUserClients): P
);
}

function generateUnreachableUsers(users: QualifiedId[]) {
const userCountByDomain = countBy(users, 'domain');
return map(userCountByDomain, (count, domain) => ({count, domain}));
}

function joinWith(elements: React.ReactNode[], separator: string) {
return elements.reduce<React.ReactNode[]>((prev, element) => {
return prev.length === 0 ? [element] : [...prev, separator, element];
}, []);
}

export const PartialFailureToSendWarning = ({failedToSend, knownUsers}: Props) => {
const [isOpen, setIsOpen] = useState(false);
const {queued = {}} = failedToSend;
const {queued = {}, failed = []} = failedToSend;

const userCount = Object.entries(queued).reduce((count, [_domain, users]) => count + Object.keys(users).length, 0);
const userCount =
Object.entries(queued).reduce((count, [_domain, users]) => count + Object.keys(users).length, 0) + failed.length;

const showToggle = userCount > 1;

const {namedUsers} = generateNamedUsers(knownUsers, queued);

const message =
namedUsers.length === 1
? {head: namedUsers[0].username(), rest: t('messageFailedToSendToOne')}
: {
head: t('messageFailedToSendParticipants', {count: userCount.toString()}),
rest: t('messageFailedToSendToSome'),
};
const unreachableUsers = generateUnreachableUsers(failed);

const message = {head: '', rest: ''};
if (showToggle) {
message.head = t('messageFailedToSendParticipants', {count: userCount.toString()});
message.rest = t('messageFailedToSendToSome');
} else if (namedUsers.length === 1) {
message.head = namedUsers[0].username();
message.rest = t('messageFailedToSendWillReceiveSingular');
} else if (unreachableUsers.length === 1) {
message.head = t('messageFailedToSendParticipantsFromDomainSingular', {domain: unreachableUsers[0].domain});
message.rest = t('messageFailedToSendWillNotReceiveSingular');
}

return (
<div>
Expand All @@ -86,18 +105,53 @@ export const PartialFailureToSendWarning = ({failedToSend, knownUsers}: Props) =
{showToggle && (
<>
{isOpen && (
<p css={warning}>
{namedUsers
.map(user => (
<span data-uie-name="recipient" data-uie-value={user.qualifiedId.id} key={user.qualifiedId.id}>
{user.username()}
</span>
))
.reduce<React.ReactNode[]>((prev, element) => {
return prev.length === 0 ? [element] : [...prev, ', ', element];
}, [])}
{` ${t('messageFailedToSendWillReceive')}`}
</p>
<>
{/* maps through the known users that will receive the message later:
"Alice, Bob will get your message later" */}
{namedUsers.length !== 0 && (
<p css={warning}>
{joinWith(
namedUsers.map(user => (
<Bold
css={warning}
data-uie-name="named-user"
data-uie-value={user.qualifiedId.id}
key={user.qualifiedId.id}
>
{user.username()}
</Bold>
)),
', ',
)}
{` ${t('messageFailedToSendWillReceive')}`}
</p>
)}

{/* maps through the unreachable users that will never receive the message:
"3 participants from alpha.domain, 1 participant from beta.domain won't get your message" */}
{failed && (
<p css={warning}>
{joinWith(
unreachableUsers.map(user => (
<Bold css={warning} data-uie-name="unreachable-domain" key={user.domain + user.count.toString()}>
{user.count > 1
? t('messageFailedToSendParticipantsFromDomain', {
count: user.count.toString(),
domain: user.domain,
})
: t('messageFailedToSendParticipantsFromDomainSingular', {
domain: user.domain,
})}
</Bold>
)),
', ',
)}
{unreachableUsers.length === 1
? ` ${t('messageFailedToSendWillNotReceiveSingular')}`
: ` ${t('messageFailedToSendWillNotReceive')}`}
</p>
)}
</>
)}
<Button type="button" variant={ButtonVariant.TERTIARY} onClick={() => setIsOpen(state => !state)}>
{isOpen ? t('messageFailedToSendHideDetails') : t('messageFailedToSendShowDetails')}
Expand Down

0 comments on commit c39d613

Please sign in to comment.