diff --git a/src/libs/PersonalDetailsUtils.ts b/src/libs/PersonalDetailsUtils.ts index f50c5cc1c2e6..bdf7b774552b 100644 --- a/src/libs/PersonalDetailsUtils.ts +++ b/src/libs/PersonalDetailsUtils.ts @@ -28,11 +28,13 @@ type FirstAndLastName = { let allPersonalDetails: OnyxEntry = {}; let emailToPersonalDetailsCache: Record = {}; +let allPersonalDetailLogins: string[] = []; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, callback: (val) => { const personalDetails = Object.values(val ?? {}); allPersonalDetails = val; + allPersonalDetailLogins = personalDetails.map((detail) => detail?.login ?? ''); emailToPersonalDetailsCache = personalDetails.reduce((acc: Record, detail) => { if (detail?.login) { acc[detail.login.toLowerCase()] = detail; @@ -264,6 +266,10 @@ function getPersonalDetailByEmail(email: string | undefined): PersonalDetails | return emailToPersonalDetailsCache[email.toLowerCase()]; } +function getAllPersonalDetailLogins(): string[] { + return allPersonalDetailLogins; +} + /** * Returns the accountID for a login only when it exists in personal details. * Unlike getAccountIDsByLogins, does not fabricate optimistic account IDs for unknown logins. @@ -625,6 +631,7 @@ export { getParticipantsPersonalDetails, getPersonalDetailsListByIDs, getDisplayNameOrYou, + getAllPersonalDetailLogins, getPersonalDetailByEmail, getKnownAccountIDByLogin, getAccountIDsByLogins, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f12653caf56f..a08f351254f2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -144,7 +144,14 @@ import Parser from './Parser'; import {getParsedMessageWithShortMentions} from './ParsingUtils'; import {getBankAccountLastFourDigits} from './PaymentUtils'; import Permissions from './Permissions'; -import {getAccountIDsByLogins, getDisplayNameOrDefault, getLoginByAccountID, getPersonalDetailByEmail, temporaryGetDisplayNameOrDefault} from './PersonalDetailsUtils'; +import { + getAccountIDsByLogins, + getAllPersonalDetailLogins, + getDisplayNameOrDefault, + getLoginByAccountID, + getPersonalDetailByEmail, + temporaryGetDisplayNameOrDefault, +} from './PersonalDetailsUtils'; import { canSendInvoiceFromWorkspace, getActivePolicies, @@ -1041,7 +1048,6 @@ Onyx.connect({ }); let allPersonalDetails: OnyxEntry; -let allPersonalDetailLogins: string[]; let currentUserPersonalDetails: OnyxEntry; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, @@ -1050,7 +1056,6 @@ Onyx.connect({ currentUserPersonalDetails = value?.[deprecatedCurrentUserAccountID] ?? undefined; } allPersonalDetails = value ?? {}; - allPersonalDetailLogins = Object.values(allPersonalDetails).map((personalDetail) => personalDetail?.login ?? ''); }, }); @@ -6537,7 +6542,7 @@ function getParsedComment( return getParsedMessageWithShortMentions({ text, - availableMentionLogins: allPersonalDetailLogins, + availableMentionLogins: getAllPersonalDetailLogins(), userEmailDomain, parserOptions: { disabledRules: isGroupPolicyReport ? [...rules] : ['reportMentions', ...rules], diff --git a/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts b/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts index 0934a83d578b..460df2194709 100644 --- a/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts +++ b/src/pages/inbox/report/ReportActionCompose/useComposerSubmit.ts @@ -1,17 +1,15 @@ -import {usePersonalDetails} from '@components/OnyxListItemProvider'; - import useAncestors from '@hooks/useAncestors'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDelegateAccountID from '@hooks/useDelegateAccountID'; import useIsInSidePanel from '@hooks/useIsInSidePanel'; import useOnyx from '@hooks/useOnyx'; -import useShortMentionsList from '@hooks/useShortMentionsList'; import {addAttachmentWithComment, addComment, clearAgentZeroProcessingIndicator} from '@libs/actions/Report'; import {createTaskAndNavigate, setNewOptimisticAssignee} from '@libs/actions/Task'; import {isEmailPublicDomain} from '@libs/LoginUtils'; import {rand64} from '@libs/NumberUtils'; import {addDomainToShortMention} from '@libs/ParsingUtils'; +import {getAllPersonalDetailLogins, getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import {isConciergeChatReport} from '@libs/ReportUtils'; import {startSpan} from '@libs/telemetry/activeSpans'; import getSendMessageSource from '@libs/telemetry/getSendMessageSource'; @@ -36,8 +34,6 @@ import useSidePanelContext from './useSidePanelContext'; function useComposerSubmit(reportID: string) { const currentUserPersonalDetails = useCurrentUserPersonalDetails(); - const personalDetails = usePersonalDetails(); - const {availableLoginsList} = useShortMentionsList(); const isInSidePanel = useIsInSidePanel(); const sidePanelContext = useSidePanelContext(reportID); const route = useRoute(); @@ -110,15 +106,15 @@ function useComposerSubmit(reportID: string) { if (taskTitle) { const mention = taskMatch[1] ? taskMatch[1].trim() : ''; const currentUserPrivateDomain = isEmailPublicDomain(currentUserEmail) ? '' : Str.extractEmailDomain(currentUserEmail); - const mentionWithDomain = addDomainToShortMention(mention, availableLoginsList, currentUserPrivateDomain) ?? mention; + const mentionWithDomain = addDomainToShortMention(mention, getAllPersonalDetailLogins(), currentUserPrivateDomain) ?? mention; const isValidMention = Str.isValidEmail(mentionWithDomain); let assignee: OnyxEntry; let assigneeChatReport; if (mentionWithDomain) { if (isValidMention) { - assignee = Object.values(personalDetails ?? {}).find((value) => value?.login === mentionWithDomain) ?? undefined; - if (!Object.keys(assignee ?? {}).length) { + assignee = getPersonalDetailByEmail(mentionWithDomain); + if (!assignee) { const optimisticDataForNewAssignee = setNewOptimisticAssignee(currentUserPersonalDetails.accountID, { accountID: generateAccountID(mentionWithDomain), login: mentionWithDomain, diff --git a/tests/ui/ReportActionComposeTest.tsx b/tests/ui/ReportActionComposeTest.tsx index 60874e9c92c6..239acf3cdcdc 100644 --- a/tests/ui/ReportActionComposeTest.tsx +++ b/tests/ui/ReportActionComposeTest.tsx @@ -1,10 +1,14 @@ import {act, fireEvent, render, screen, waitFor} from '@testing-library/react-native'; import ComposeProviders from '@components/ComposeProviders'; +import {CurrentUserPersonalDetailsProvider} from '@components/CurrentUserPersonalDetailsProvider'; import {LocaleContextProvider} from '@components/LocaleContextProvider'; import OnyxListItemProvider from '@components/OnyxListItemProvider'; import {KeyboardStateProvider} from '@components/withKeyboardState'; +import type * as TaskActions from '@libs/actions/Task'; +import {createTaskAndNavigate} from '@libs/actions/Task'; + import type {ReportActionComposeProps} from '@pages/inbox/report/ReportActionCompose/ReportActionCompose'; import ReportActionCompose from '@pages/inbox/report/ReportActionCompose/ReportActionCompose'; import {ReportActionEditMessageContextProvider} from '@pages/inbox/report/ReportActionEditMessageContext'; @@ -26,6 +30,11 @@ jest.mock('@libs/ComponentUtils', () => ({ forceClearInput: jest.fn(), })); +jest.mock('@libs/actions/Task', () => ({ + ...jest.requireActual('@libs/actions/Task'), + createTaskAndNavigate: jest.fn(), +})); + jest.mock('@hooks/useLocalize', () => jest.fn(() => ({ translate: jest.fn((key: string) => key), @@ -70,7 +79,13 @@ function ReportActionEditMessageContextProviderForReport({children}: PropsWithCh } function ReportScreenProviders({children}: PropsWithChildren) { - return {children}; + return ( + + {children} + + ); } const renderReportActionCompose = (props?: Partial) => { @@ -437,4 +452,40 @@ describe('ReportActionCompose Integration Tests', () => { unmount(); }); }); + + describe('Task creation with a short mention', () => { + it('assigns the task to the user resolved from a same-private-domain short mention', async () => { + // Given a current user on a private domain and a coworker on the same domain + const coworkerAccountID = 2; + await TestHelper.signInWithTestUser(1, 'user@domain.com'); + await act(async () => { + await Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, { + [coworkerAccountID]: TestHelper.buildPersonalDetails('mat@domain.com', coworkerAccountID, 'Mat'), + }); + }); + + const {unmount} = renderReportActionCompose(); + await waitForBatchedUpdatesWithAct(); + + // When a task with a short mention of the coworker is typed and submitted + // (the composer submits by clearing the input, which hands the draft to validateAndSubmitDraft) + const composer = screen.getByTestId('composer'); + fireEvent.changeText(composer, '[] @mat Buy milk'); + fireEvent(composer, 'clear', {nativeEvent: {text: '[] @mat Buy milk'}}); + + // Then the task is created with the mention resolved to the coworker's full login + await waitFor(() => { + expect(createTaskAndNavigate).toHaveBeenCalledWith( + expect.objectContaining({ + title: 'Buy milk', + assigneeEmail: 'mat@domain.com', + assigneeAccountID: coworkerAccountID, + }), + ); + }); + + unmount(); + await waitForBatchedUpdatesWithAct(); + }); + }); });