Skip to content

Commit 3c9df95

Browse files
authored
Merge pull request #91863 from lorretheboy/fix-03/73656
(Part 3) Remove Onyx.connect() for the key: ONYXKEYS.PERSONAL_DETAILS_LIST in src/libs/actions/Report.ts
2 parents aebab39 + cba308e commit 3c9df95

14 files changed

Lines changed: 130 additions & 47 deletions

src/components/ReportActionItem/MoneyRequestReceiptView.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {useRoute} from '@react-navigation/native';
22
import {hasSeenTourSelector} from '@selectors/Onboarding';
3-
import {conciergePersonalDetailSelector, personalDetailByAccountIDSelector} from '@selectors/PersonalDetails';
3+
import {conciergePersonalDetailSelector, personalDetailsSelector} from '@selectors/PersonalDetails';
44
import mapValues from 'lodash/mapValues';
55
import React, {useEffect, useMemo, useRef, useState} from 'react';
66
import {View} from 'react-native';
@@ -143,9 +143,9 @@ function MoneyRequestReceiptView({
143143
const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector});
144144
const [betas] = useOnyx(ONYXKEYS.BETAS);
145145
const [conciergePersonalDetail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: conciergePersonalDetailSelector});
146-
const reportOwnerSelector = useMemo(() => personalDetailByAccountIDSelector(report?.ownerAccountID), [report?.ownerAccountID]);
146+
const reportOwnerSelector = useMemo(() => personalDetailsSelector(report?.ownerAccountID), [report?.ownerAccountID]);
147147
const [reportOwnerPersonalDetail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: reportOwnerSelector}, [reportOwnerSelector]);
148-
const chatReportOwnerSelector = useMemo(() => personalDetailByAccountIDSelector(chatReport?.ownerAccountID), [chatReport?.ownerAccountID]);
148+
const chatReportOwnerSelector = useMemo(() => personalDetailsSelector(chatReport?.ownerAccountID), [chatReport?.ownerAccountID]);
149149
const [chatReportOwnerPersonalDetail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: chatReportOwnerSelector}, [chatReportOwnerSelector]);
150150
const delegateAccountID = useDelegateAccountID();
151151

src/libs/PersonalDetailsUtils.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@ function getPersonalDetailsListByIDs(accountIDs: Array<number | undefined>, pers
168168
}, {} as PersonalDetailsList);
169169
}
170170

171+
/**
172+
* Build a personal details list scoped to the given participant accountIDs. A participant that is missing from the
173+
* source list stays missing (mapped to `null`) so optimistic-account detection keeps the same semantics as the full list.
174+
*/
175+
function getParticipantsPersonalDetails(accountIDs: number[], personalDetails: OnyxEntry<PersonalDetailsList>): PersonalDetailsList {
176+
const result: PersonalDetailsList = {};
177+
for (const accountID of accountIDs) {
178+
result[accountID] = personalDetails?.[accountID] ?? null;
179+
}
180+
return result;
181+
}
182+
171183
function getDisplayNameOrYou(displayName: string, accountID: number, currentUserAccountID: number, translate: LocalizedTranslate) {
172184
if (accountID === currentUserAccountID) {
173185
return translate('common.you');
@@ -514,6 +526,7 @@ export {
514526
getPersonalDetailsByID,
515527
getPersonalDetailsByIDs,
516528
newGetPersonalDetailsByIDs,
529+
getParticipantsPersonalDetails,
517530
getPersonalDetailsListByIDs,
518531
getDisplayNameOrYou,
519532
getPersonalDetailByEmail,

src/libs/actions/Report/index.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ import {
160160
isIOUReportUsingReport,
161161
isMoneyRequestReport,
162162
isOpenExpenseReport,
163+
isOptimisticPersonalDetail,
163164
isProcessingReport,
164165
isReportManuallyReimbursed,
165166
isReportNotFound,
@@ -1879,22 +1880,26 @@ function pruneReportActionPagesToNewestWindow(reportID: string | undefined, sort
18791880
* Create a group chat report. Simplified version specifically for group chats without unnecessary logic.
18801881
*
18811882
* @param reportID The ID of the group chat report to open
1882-
* @param participantLoginList The list of user logins included in the group chat
1883+
* @param participantsPersonalDetails The personal details of the participants included in the group chat. Logins and accountIDs are derived from this, and entries flagged with `isOptimisticPersonalDetail` are the new participants we still need to create optimistic personal details for.
18831884
* @param newReportObject The optimistic report object for the new group chat
18841885
* @param currentUserLogin The login of the current user
18851886
* @param introSelected The intro selected data for guided setup
18861887
* @param avatar The avatar file to upload for the group chat (optional)
18871888
*/
18881889
function createGroupChat(
18891890
reportID: string,
1890-
participantLoginList: string[],
1891+
participantsPersonalDetails: OnyxEntry<PersonalDetailsList>,
18911892
newReportObject: OptimisticChatReport,
18921893
currentUserLogin: string,
18931894
introSelected: OnyxEntry<IntroSelected>,
18941895
isSelfTourViewed: boolean | undefined,
18951896
betas: OnyxEntry<Beta[]>,
18961897
avatar?: File | CustomRNImageManipulatorResult,
18971898
) {
1899+
const participantLoginList = Object.values(participantsPersonalDetails ?? {})
1900+
.map((participant) => participant?.login)
1901+
.filter((login): login is string => !!login);
1902+
18981903
const optimisticReport = {
18991904
reportName: CONST.REPORT.DEFAULT_REPORT_NAME,
19001905
};
@@ -2015,7 +2020,7 @@ function createGroupChat(
20152020

20162021
for (const [index, login] of participantLoginList.entries()) {
20172022
const accountID = participantAccountIDs.at(index) ?? -1;
2018-
const isOptimisticAccount = !allPersonalDetails?.[accountID];
2023+
const isOptimisticAccount = isOptimisticPersonalDetail(accountID);
20192024

20202025
if (!isOptimisticAccount) {
20212026
continue;
@@ -2338,8 +2343,9 @@ function navigateToAndOpenReport(
23382343
navigateToReport(chat.reportID, {shouldDismissModal, ...linkToOptions});
23392344
}
23402345

2346+
// TODO: update to object structure https://github.com/Expensify/App/issues/73656
23412347
function navigateToAndCreateGroupChat(
2342-
userLogins: string[],
2348+
participantsPersonalDetails: OnyxEntry<PersonalDetailsList>,
23432349
reportName: string,
23442350
currentUserLogin: string,
23452351
optimisticReportID: string,
@@ -2350,11 +2356,14 @@ function navigateToAndCreateGroupChat(
23502356
avatarUri?: string,
23512357
avatarFile?: File | CustomRNImageManipulatorResult | undefined,
23522358
) {
2359+
const userLogins = Object.values(participantsPersonalDetails ?? {})
2360+
.map((participant) => participant?.login)
2361+
.filter((login): login is string => !!login);
23532362
const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(userLogins);
23542363

23552364
// If we are creating a group chat then participantAccountIDs is expected to contain currentUserAccountID
23562365
const newChat = buildOptimisticGroupChatReport(participantAccountIDs, reportName, avatarUri ?? '', currentUserAccountID, optimisticReportID, CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);
2357-
createGroupChat(newChat.reportID, userLogins, newChat, currentUserLogin, introSelected, isSelfTourViewed, betas, avatarFile);
2366+
createGroupChat(newChat.reportID, participantsPersonalDetails, newChat, currentUserLogin, introSelected, isSelfTourViewed, betas, avatarFile);
23582367

23592368
navigateToReport(newChat.reportID, {afterTransition: clearGroupChat});
23602369
}
@@ -2455,10 +2464,11 @@ function navigateToAndOpenChildReport(
24552464
currentUserAccountID: number,
24562465
introSelected: OnyxEntry<IntroSelected>,
24572466
betas: OnyxEntry<Beta[]>,
2458-
personalDetails: OnyxEntry<PersonalDetailsList>,
2467+
// The personal details of the child report participants (the current user and the parent action's actor).
2468+
participantsPersonalDetails: OnyxEntry<PersonalDetailsList>,
24592469
isSelfTourViewed: boolean | undefined,
24602470
) {
2461-
const report = childReport ?? createChildReport(childReport, parentReportAction, parentReport, currentUserAccountID, introSelected, betas, isSelfTourViewed, personalDetails);
2471+
const report = childReport ?? createChildReport(childReport, parentReportAction, parentReport, currentUserAccountID, introSelected, betas, isSelfTourViewed, participantsPersonalDetails);
24622472

24632473
if (isSearchTopmostFullScreenRoute()) {
24642474
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: report.reportID, backTo: Navigation.getActiveRoute()}));
@@ -2480,8 +2490,8 @@ function createChildReport(
24802490
introSelected: OnyxEntry<IntroSelected>,
24812491
betas: OnyxEntry<Beta[]>,
24822492
isSelfTourViewed: boolean | undefined,
2483-
// TODO: personalDetails should be a required field in follow-up PRs https://github.com/Expensify/App/issues/73656
2484-
personalDetails?: OnyxEntry<PersonalDetailsList>,
2493+
// The personal details of the child report participants (the current user and the parent action's actor).
2494+
participantsPersonalDetails: OnyxEntry<PersonalDetailsList>,
24852495
): Report {
24862496
const participantAccountIDs = [...new Set([currentUserAccountID, Number(parentReportAction.actorAccountID)])];
24872497
// Threads from DMs and selfDMs don't have a chatType. All other threads inherit the chatType from their parent
@@ -2519,8 +2529,7 @@ function createChildReport(
25192529
reportID: newChat.reportID,
25202530
introSelected,
25212531
participants,
2522-
// TODO: allPersonalDetails fallback should be removed in follow-up PRs https://github.com/Expensify/App/issues/73656
2523-
personalDetails: personalDetails ?? allPersonalDetails,
2532+
personalDetails: participantsPersonalDetails,
25242533
newReportObject: newChat,
25252534
parentReportActionID: parentReportAction.reportActionID,
25262535
isNewThread: true,
@@ -2538,6 +2547,8 @@ function createChildReport(
25382547
* Creates an explanation thread for a report action with reasoning
25392548
* Adds a "Please explain this to me." comment from the user
25402549
*/
2550+
// TODO: update to object structure https://github.com/Expensify/App/issues/73656
2551+
// eslint-disable-next-line @typescript-eslint/max-params
25412552
function explain(
25422553
childReport: OnyxEntry<Report>,
25432554
originalReport: OnyxEntry<Report>,
@@ -2548,14 +2559,16 @@ function explain(
25482559
betas: OnyxEntry<Beta[]>,
25492560
isSelfTourViewed: boolean | undefined,
25502561
delegateAccountID: number | undefined,
2562+
// The personal details of the explanation thread participants (the current user and the report action's actor).
2563+
participantsPersonalDetails: OnyxEntry<PersonalDetailsList>,
25512564
timezone: Timezone = CONST.DEFAULT_TIME_ZONE,
25522565
) {
25532566
if (!originalReport?.reportID || !reportAction) {
25542567
return;
25552568
}
25562569

25572570
// Check if explanation thread report already exists
2558-
const report = childReport ?? createChildReport(childReport, reportAction, originalReport, currentUserAccountID, introSelected, betas, isSelfTourViewed);
2571+
const report = childReport ?? createChildReport(childReport, reportAction, originalReport, currentUserAccountID, introSelected, betas, isSelfTourViewed, participantsPersonalDetails);
25592572

25602573
if (isSearchTopmostFullScreenRoute()) {
25612574
Navigation.navigate(ROUTES.SEARCH_REPORT.getRoute({reportID: report.reportID, backTo: Navigation.getActiveRoute()}));

src/pages/Debug/Report/DebugReportPage.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {hasSeenTourSelector} from '@selectors/Onboarding';
2-
import {conciergePersonalDetailSelector, personalDetailByAccountIDSelector} from '@selectors/PersonalDetails';
2+
import {conciergePersonalDetailSelector, personalDetailsSelector} from '@selectors/PersonalDetails';
33
import React, {useCallback, useMemo} from 'react';
44
import {View} from 'react-native';
55
import type {OnyxEntry} from 'react-native-onyx';
@@ -80,7 +80,7 @@ function DebugReportPage({
8080
const currentUserPersonalDetail = useCurrentUserPersonalDetails();
8181
const {accountID: currentUserAccountID, login: currentUserLogin} = currentUserPersonalDetail;
8282
const [conciergePersonalDetail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: conciergePersonalDetailSelector});
83-
const reportOwnerSelector = useMemo(() => personalDetailByAccountIDSelector(report?.ownerAccountID), [report?.ownerAccountID]);
83+
const reportOwnerSelector = useMemo(() => personalDetailsSelector(report?.ownerAccountID), [report?.ownerAccountID]);
8484
const [reportOwnerPersonalDetail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: reportOwnerSelector}, [reportOwnerSelector]);
8585
const transactionID = DebugUtils.getTransactionID(report, reportActions);
8686
const isReportArchived = useReportIsArchived(reportID);

src/pages/NewChatConfirmPage.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
2828
import ROUTES from '@src/ROUTES';
2929
import {hasSeenTourSelector} from '@src/selectors/Onboarding';
3030
import type {Participant} from '@src/types/onyx/IOU';
31+
import type {PersonalDetailsList} from '@src/types/onyx/PersonalDetails';
3132
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
3233

3334
function navigateBack() {
@@ -162,9 +163,23 @@ function NewChatConfirmPage() {
162163
return;
163164
}
164165

165-
const logins: string[] = (newGroupDraft.participants ?? []).map((participant) => participant.login).filter((login): login is string => !!login);
166+
// Build the participants' personal details so the action can derive logins/accountIDs and tell which participants are new
167+
// (entries flagged with isOptimisticPersonalDetail) without reading the global personal details list.
168+
const participantsPersonalDetails: PersonalDetailsList = {};
169+
for (const participant of newGroupDraft.participants ?? []) {
170+
const {accountID, login} = participant;
171+
if (!accountID || !login) {
172+
continue;
173+
}
174+
participantsPersonalDetails[accountID] = allPersonalDetails?.[accountID] ?? {
175+
accountID,
176+
login,
177+
displayName: login,
178+
isOptimisticPersonalDetail: true,
179+
};
180+
}
166181
navigateToAndCreateGroupChat(
167-
logins,
182+
participantsPersonalDetails,
168183
newGroupDraft.reportName ?? '',
169184
personalData.login ?? '',
170185
optimisticReportID.current,

src/pages/inbox/report/AncestorReportActionItem.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {personalDetailByAccountIDSelector} from '@selectors/PersonalDetails';
1+
import {personalDetailsSelector} from '@selectors/PersonalDetails';
22
import React from 'react';
33
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
44
import OfflineWithFeedback from '@components/OfflineWithFeedback';
@@ -88,7 +88,7 @@ function AncestorReportActionItem({
8888
}: AncestorReportActionItemProps) {
8989
const styles = useThemeStyles();
9090
const currentUserPersonalDetail = useCurrentUserPersonalDetails();
91-
const [reportOwnerPersonalDetail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailByAccountIDSelector(report?.ownerAccountID)});
91+
const [reportOwnerPersonalDetail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsSelector(report?.ownerAccountID)});
9292
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.chatReportID)}`, {selector: getStableReportSelector});
9393

9494
const shouldDisplayThreadDivider = !isTripPreview(reportAction);

src/pages/inbox/report/ContextMenu/ContextMenuActions.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {getForReportAction} from '@libs/ModifiedExpenseMessage';
2222
import createDynamicRoute from '@libs/Navigation/helpers/dynamicRoutesUtils/createDynamicRoute';
2323
import Navigation from '@libs/Navigation/Navigation';
2424
import Parser from '@libs/Parser';
25+
import {getParticipantsPersonalDetails} from '@libs/PersonalDetailsUtils';
2526
import {getCleanedTagName, isPolicyAdmin} from '@libs/PolicyUtils';
2627
import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager';
2728
import stripFollowupListFromHtml from '@libs/ReportActionFollowupUtils/stripFollowupListFromHtml';
@@ -462,15 +463,16 @@ const ContextMenuActions: ContextMenuAction[] = [
462463
return !shouldDisableThread(reportAction, isThreadReportParentAction, isArchivedRoom);
463464
},
464465
onPress: (closePopover, {reportAction, childReport, originalReport, currentUserAccountID, introSelected, betas, isSelfTourViewed, personalDetails}) => {
466+
const participantsPersonalDetails = getParticipantsPersonalDetails([currentUserAccountID, Number(reportAction.actorAccountID)], personalDetails);
465467
if (closePopover) {
466468
hideContextMenu(false, () => {
467469
KeyboardUtils.dismiss().then(() => {
468-
navigateToAndOpenChildReport(childReport, reportAction, originalReport, currentUserAccountID, introSelected, betas, personalDetails, isSelfTourViewed);
470+
navigateToAndOpenChildReport(childReport, reportAction, originalReport, currentUserAccountID, introSelected, betas, participantsPersonalDetails, isSelfTourViewed);
469471
});
470472
});
471473
return;
472474
}
473-
navigateToAndOpenChildReport(childReport, reportAction, originalReport, currentUserAccountID, introSelected, betas, personalDetails, isSelfTourViewed);
475+
navigateToAndOpenChildReport(childReport, reportAction, originalReport, currentUserAccountID, introSelected, betas, participantsPersonalDetails, isSelfTourViewed);
474476
},
475477
getDescription: () => {},
476478
sentryLabel: CONST.SENTRY_LABEL.CONTEXT_MENU.REPLY_IN_THREAD,
@@ -504,11 +506,16 @@ const ContextMenuActions: ContextMenuAction[] = [
504506

505507
return hasReasoning(reportAction);
506508
},
507-
onPress: (closePopover, {reportAction, childReport, originalReport, translate, currentUserPersonalDetails, introSelected, betas, isSelfTourViewed, delegateAccountID}) => {
509+
onPress: (
510+
closePopover,
511+
{reportAction, childReport, originalReport, translate, currentUserPersonalDetails, introSelected, betas, isSelfTourViewed, delegateAccountID, personalDetails},
512+
) => {
508513
if (!originalReport?.reportID) {
509514
return;
510515
}
511516

517+
const participantsPersonalDetails = getParticipantsPersonalDetails([currentUserPersonalDetails.accountID, Number(reportAction?.actorAccountID)], personalDetails);
518+
512519
if (closePopover) {
513520
hideContextMenu(false, () => {
514521
KeyboardUtils.dismiss().then(() => {
@@ -522,6 +529,7 @@ const ContextMenuActions: ContextMenuAction[] = [
522529
betas,
523530
isSelfTourViewed,
524531
delegateAccountID,
532+
participantsPersonalDetails,
525533
currentUserPersonalDetails?.timezone,
526534
);
527535
});
@@ -539,6 +547,7 @@ const ContextMenuActions: ContextMenuAction[] = [
539547
betas,
540548
isSelfTourViewed,
541549
delegateAccountID,
550+
participantsPersonalDetails,
542551
currentUserPersonalDetails?.timezone,
543552
);
544553
},

src/pages/inbox/report/ReportActionCompose/ComposerLocalTime.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {personalDetailByAccountIDSelector} from '@selectors/PersonalDetails';
1+
import {personalDetailsSelector} from '@selectors/PersonalDetails';
22
import React from 'react';
33
import OfflineWithFeedback from '@components/OfflineWithFeedback';
44
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
@@ -19,7 +19,7 @@ function ComposerLocalTime() {
1919
const canShowRecipientLocalTime = useReportRecipientLocalTime({report});
2020
const shouldShow = canShowRecipientLocalTime && !isComposerFullSize;
2121
const reportRecipientAccountID = getReportRecipientAccountIDs(report, currentUserAccountID).at(0);
22-
const [reportRecipient] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailByAccountIDSelector(shouldShow ? reportRecipientAccountID : undefined)});
22+
const [reportRecipient] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsSelector(shouldShow ? reportRecipientAccountID : undefined)});
2323

2424
if (!shouldShow || isEmptyObject(reportRecipient) || isAgentEmail(reportRecipient?.login)) {
2525
return null;

0 commit comments

Comments
 (0)