Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
73b5acc
Thread hasGuidesEmails and currentUserLogin through ReportUtils acces…
Krishna2323 Jul 5, 2026
4c0d082
Fix PR3 checklist issues: tests, export, and TODO comments
Krishna2323 Jul 5, 2026
e81a05f
add missing dependency.
Krishna2323 Jul 5, 2026
fa33392
Fix LHN cache invalidation when guide details hydrate and resolve ESL…
Krishna2323 Jul 5, 2026
32a6c0b
Optimize guidesEmailsByReport selector and add resolveHasGuidesEmails…
Krishna2323 Jul 5, 2026
691ef6b
Address MelvinBot review: gate guide-email checks and memoize selectors
Krishna2323 Jul 5, 2026
ca15171
Remove createGuidesEmailsByReportSelector tests to fix ESLint failures
Krishna2323 Jul 5, 2026
8d35bd5
Merge branch 'Expensify:main' into krishna2323/issue/66413/part-3-v2
Krishna2323 Jul 5, 2026
6c5756a
Fix ESLint no-inline-useOnyx-selector in withReportOrNotFound
Krishna2323 Jul 5, 2026
7158ae1
Merge branch 'Expensify:main' into krishna2323/issue/66413/part-3-v2
Krishna2323 Jul 6, 2026
dccddfc
Merge branch 'Expensify:main' into krishna2323/issue/66413/part-3-v2
Krishna2323 Jul 6, 2026
44503f4
Merge branch 'main' into krishna2323/issue/66413/part-3-v2
Krishna2323 Jul 6, 2026
a32cf53
fix multiple import.
Krishna2323 Jul 6, 2026
c3e165a
Merge branch 'main' into krishna2323/issue/66413/part-3-v2
Krishna2323 Jul 9, 2026
ec4ed1e
fix typescript check.
Krishna2323 Jul 11, 2026
dcf4c29
Merge upstream/main and resolve PR conflicts
Krishna2323 Jul 11, 2026
16ce767
Fix no-useOnyx-dependencies-arg lint in PR 3 callers
Krishna2323 Jul 11, 2026
d268df7
Merge upstream/main and resolve Link.ts conflict
Krishna2323 Jul 14, 2026
60265a4
Merge upstream/main and resolve withReportAndReportActionOrNotFound c…
Krishna2323 Jul 16, 2026
e90f701
Merge upstream/main and resolve Link.ts and PersonalDetails conflicts
Krishna2323 Jul 22, 2026
11b3e16
fix ESLint.
Krishna2323 Jul 22, 2026
baca28d
Merge branch 'Expensify:main' into krishna2323/issue/66413/part-3-v2
Krishna2323 Jul 23, 2026
b09cb54
merge main.
Krishna2323 Jul 27, 2026
205372c
add missing import.
Krishna2323 Jul 27, 2026
b6a817a
Merge branch 'Expensify:main' into krishna2323/issue/66413/part-3-v2
Krishna2323 Jul 28, 2026
5c8c218
Simplify guidesEmailsByReport selector wiring in LHN hook
Krishna2323 Jul 31, 2026
d35adeb
Merge branch 'Expensify:main' into krishna2323/issue/66413/part-3-v2
Krishna2323 Aug 3, 2026
a93c918
merge main and fix conflicts.
Krishna2323 Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions src/hooks/useSidebarOrderedReports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type * as OnyxTypes from '@src/types/onyx';

import type {ValueOf} from 'type-fest';

import {createGuidesEmailsByReportSelector} from '@selectors/PersonalDetails';
import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';

import useCollectionDelta from './useCollectionDelta';
Expand Down Expand Up @@ -44,7 +45,14 @@ type SidebarOrderedReportsActionsContextValue = {
setStickyReportID: (reportID: string) => void;
};

type ReportsToDisplayInLHN = Record<string, OnyxTypes.Report & {hasErrorsOtherThanFailedReceipt?: boolean; requiresAttention?: boolean; isUnreadReport?: boolean}>;
type ReportsToDisplayInLHN = Record<
string,
OnyxTypes.Report & {
hasErrorsOtherThanFailedReceipt?: boolean;
requiresAttention?: boolean;
isUnreadReport?: boolean;
}
>;

const SidebarOrderedReportsStateContext = createContext<SidebarOrderedReportsStateContextValue>({
filteredReports: [],
Expand Down Expand Up @@ -95,6 +103,12 @@ function SidebarOrderedReportsContextProvider({
const [reportsDrafts] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT);
const reportsDraftsUpdates = useCollectionDelta(reportsDrafts);
const [betas] = useOnyx(ONYXKEYS.BETAS);
const guidesEmailsByReportSelector = useMemo(() => createGuidesEmailsByReportSelector(chatReports), [chatReports]);
Comment thread
Krishna2323 marked this conversation as resolved.
const [guidesEmailsByReport] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {
selector: guidesEmailsByReportSelector,
});
const guidesEmailsByReportKey = useMemo(() => JSON.stringify(guidesEmailsByReport ?? {}), [guidesEmailsByReport]);
const prevGuidesEmailsByReportKey = usePrevious(guidesEmailsByReportKey);
const [conciergeReportID] = useOnyx(ONYXKEYS.CONCIERGE_REPORT_ID);
const reportAttributes = useReportAttributes();
const [currentReportsToDisplay, setCurrentReportsToDisplay] = useState<ReportsToDisplayInLHN>({});
Expand Down Expand Up @@ -202,7 +216,14 @@ function SidebarOrderedReportsContextProvider({
// When reportAttributes changes (e.g. on startup hydration) but no report-specific keys were
// updated, getUpdatedReports() returns []. Rather than falling through to a full scan of all
// reports, recheck only the already-displayed reports with the new reportAttributes.
const effectiveUpdatedReports = updatedReports.length === 0 && hasCachedReports ? Object.keys(currentReportsToDisplay) : updatedReports;
let effectiveUpdatedReports = updatedReports.length === 0 && hasCachedReports ? Object.keys(currentReportsToDisplay) : updatedReports;

// When guide personal details hydrate after the reports collection, guidesEmailsByReport changes but
// getUpdatedReports() returns no report keys. Re-evaluate all reports so domain rooms previously
// filtered out can appear in the LHN.
if (hasCachedReports && prevGuidesEmailsByReportKey !== undefined && guidesEmailsByReportKey !== prevGuidesEmailsByReportKey) {
effectiveUpdatedReports = Object.keys(chatReports ?? {});
}
const shouldDoIncrementalUpdate = effectiveUpdatedReports.length > 0 && hasCachedReports;
let reportsToDisplay = {};
if (shouldDoIncrementalUpdate) {
Expand All @@ -222,6 +243,7 @@ function SidebarOrderedReportsContextProvider({
currentUserLogin: currentUserLogin ?? '',
currentUserAccountID: accountID,
conciergeReportID,
guidesEmailsByReport,
});
} else {
Log.info('[useSidebarOrderedReports] building reportsToDisplay from scratch');
Expand All @@ -239,6 +261,7 @@ function SidebarOrderedReportsContextProvider({
reportNameValuePairs,
reportAttributes,
conciergeReportID,
guidesEmailsByReport,
});
}

Expand All @@ -260,6 +283,9 @@ function SidebarOrderedReportsContextProvider({
currentUserLogin,
accountID,
conciergeReportID,
guidesEmailsByReport,
guidesEmailsByReportKey,
prevGuidesEmailsByReportKey,
]);

// Derive a stable boolean map indicating which reports have drafts.
Expand Down
3 changes: 3 additions & 0 deletions src/libs/DebugUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,7 @@ function getReasonForShowingRowInLHN({
currentUserLogin,
currentUserAccountID,
conciergeReportID,
hasGuidesEmails,
}: {
report: OnyxEntry<Report>;
chatReport: OnyxEntry<Report>;
Expand All @@ -1476,6 +1477,7 @@ function getReasonForShowingRowInLHN({
draftComment: string | undefined;
currentUserLogin?: string;
currentUserAccountID?: number;
hasGuidesEmails: boolean;
conciergeReportID: string | undefined;
}): TranslationPaths | null {
if (!report) {
Expand All @@ -1497,6 +1499,7 @@ function getReasonForShowingRowInLHN({
currentUserLogin,
currentUserAccountID,
conciergeReportID,
hasGuidesEmails,
});

if (!([CONST.REPORT_IN_LHN_REASONS.HAS_ADD_WORKSPACE_ROOM_ERRORS, CONST.REPORT_IN_LHN_REASONS.HAS_IOU_VIOLATIONS] as Array<typeof reason>).includes(reason) && hasRBR) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ function ReportsSplitNavigator({navigation, route}: PlatformStackScreenProps<Tab
return '';
}

const initialReport = ReportUtils.findLastAccessedReport(!isBetaEnabled(CONST.BETAS.DEFAULT_ROOMS), isOpenOnAdminRoom, undefined, reportNameValuePairs);
// TODO: Pass guidesEmailsByReport map once callers are fully migrated — PR 33 (https://github.com/Expensify/App/issues/66413); findLastAccessedReport falls back to hasExpensifyGuidesEmails → allPersonalDetails
Comment thread
Krishna2323 marked this conversation as resolved.
const initialReport = ReportUtils.findLastAccessedReport(!isBetaEnabled(CONST.BETAS.DEFAULT_ROOMS), undefined, isOpenOnAdminRoom, undefined, reportNameValuePairs);
// eslint-disable-next-line rulesdir/no-default-id-values
return initialReport?.reportID ?? '';
});
Expand Down
14 changes: 13 additions & 1 deletion src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,11 @@ import {
getReportTransactions,
getUnreportedTransactionMessage,
getViolatingReportIDForRBRInLHN,
hasExpensifyGuidesEmails,
hasIOUWaitingOnCurrentUserBankAccount,
isArchivedNonExpenseReport,
isChatThread,
isDefaultRoom,
isDM,
isExpenseReport,
isHiddenForCurrentUser,
Expand Down Expand Up @@ -2317,7 +2319,14 @@ function getUserToInviteOption({
return userToInvite;
}

function isValidReport(option: SearchOption<Report>, policy: OnyxEntry<Policy>, config: IsValidReportsConfig, draftComment: string | undefined, chatReport: OnyxEntry<Report>): boolean {
function isValidReport(
option: SearchOption<Report>,
policy: OnyxEntry<Policy>,
config: IsValidReportsConfig,
draftComment: string | undefined,
chatReport: OnyxEntry<Report>,
hasGuidesEmails: boolean,
): boolean {
const {
betas = [],
includeMultipleParticipantReports = false,
Expand Down Expand Up @@ -2361,6 +2370,7 @@ function isValidReport(option: SearchOption<Report>, policy: OnyxEntry<Policy>,
currentUserLogin,
currentUserAccountID,
conciergeReportID,
hasGuidesEmails,
});

if (!shouldBeInOptionList) {
Expand Down Expand Up @@ -2748,6 +2758,8 @@ function getValidOptions(
},
draftComment,
chatReport,
// TODO: Pass personalDetailsList once callers are fully migrated — PR 33 (https://github.com/Expensify/App/issues/66413); hasExpensifyGuidesEmails falls back to allPersonalDetails
Comment thread
Krishna2323 marked this conversation as resolved.
isDefaultRoom(report.item) ? hasExpensifyGuidesEmails(Object.keys(report.item?.participants ?? {}).map(Number), undefined) : false,
);
};

Expand Down
63 changes: 50 additions & 13 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2408,8 +2408,31 @@ function isHiddenForCurrentUser(reportOrPreference: OnyxEntry<Report> | string |
* by cross-referencing the accountIDs with personalDetails since guides that are participants
* of the user's chats should have their personal details in Onyx.
*/
function hasExpensifyGuidesEmails(accountIDs: number[]): boolean {
return accountIDs.some((accountID) => Str.extractEmailDomain(allPersonalDetails?.[accountID]?.login ?? '') === CONST.EMAIL.GUIDES_DOMAIN);
function hasExpensifyGuidesEmails(accountIDs: number[], personalDetailsList: OnyxEntry<PersonalDetailsList>): boolean {
Comment thread
Krishna2323 marked this conversation as resolved.
// TODO: Remove fallback once all callers pass personalDetailsList (https://github.com/Expensify/App/issues/66413)
const resolvedPersonalDetails = personalDetailsList ?? allPersonalDetails;
return accountIDs.some((accountID) => Str.extractEmailDomain(resolvedPersonalDetails?.[accountID]?.login ?? '') === CONST.EMAIL.GUIDES_DOMAIN);
}

/**
* Resolves whether a report has guide participants, preferring pre-computed values when available
* and falling back to module-level personal details while selectors/maps are still loading.
*/
function resolveHasGuidesEmails({
participantAccountIDs,
hasGuidesEmails,
guidesEmailsByReport,
reportID,
}: {
participantAccountIDs: number[];
hasGuidesEmails?: boolean;
guidesEmailsByReport?: Record<string, boolean>;
reportID?: string;
}): boolean {
if (reportID && guidesEmailsByReport && reportID in guidesEmailsByReport) {
return guidesEmailsByReport[reportID];
}
return hasGuidesEmails ?? hasExpensifyGuidesEmails(participantAccountIDs, undefined);
}

function getMostRecentlyVisitedReport(reports: Array<OnyxEntry<Report>>, lastVisitTimes: Record<string, string>): OnyxEntry<Report> {
Expand All @@ -2428,6 +2451,7 @@ function getMostRecentlyVisitedReport(reports: Array<OnyxEntry<Report>>, lastVis
*/
function findLastAccessedReport(
ignoreDomainRooms: boolean,
guidesEmailsByReport: Record<string, boolean> | undefined,
openOnAdminRoom = false,
excludeReportID?: string,
reportNameValuePairs?: OnyxCollection<ReportNameValuePairs>,
Expand Down Expand Up @@ -2456,7 +2480,16 @@ function findLastAccessedReport(
// We allow public announce rooms, admins, and announce rooms through since we bypass the default rooms beta for them.
// Check where findLastAccessedReport is called in MainDrawerNavigator.js for more context.
// Domain rooms are now the only type of default room that are on the defaultRooms beta.
if (ignoreDomainRooms && isDomainRoom(report) && !hasExpensifyGuidesEmails(Object.keys(report?.participants ?? {}).map(Number))) {
// When guidesEmailsByReport is undefined or missing this reportID, fall back to hasExpensifyGuidesEmails → allPersonalDetails (https://github.com/Expensify/App/issues/66413)
if (
ignoreDomainRooms &&
isDomainRoom(report) &&
!resolveHasGuidesEmails({
participantAccountIDs: Object.keys(report?.participants ?? {}).map(Number),
guidesEmailsByReport,
reportID: report?.reportID,
})
) {
return false;
}

Expand Down Expand Up @@ -2563,7 +2596,7 @@ function isClosedReport(report: OnyxInputOrEntry<Report>): boolean {
/**
* Whether the provided report is the admin's room
*/
function isJoinRequestInAdminRoom(report: OnyxEntry<Report>): boolean {
function isJoinRequestInAdminRoom(report: OnyxEntry<Report>, currentUserLogin: string | undefined): boolean {
if (!report) {
return false;
}
Expand All @@ -2573,7 +2606,7 @@ function isJoinRequestInAdminRoom(report: OnyxEntry<Report>): boolean {
if (report.policyID) {
// This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850
const policy = getPolicy(report.policyID);
if (!isExpensifyTeam(policy?.owner) && isExpensifyTeam(currentUserPersonalDetails?.login)) {
if (!isExpensifyTeam(policy?.owner) && isExpensifyTeam(currentUserLogin)) {
return false;
}
}
Expand Down Expand Up @@ -4458,7 +4491,7 @@ function getReasonAndReportActionThatRequiresAttention(
return null;
}

if (isJoinRequestInAdminRoom(optionOrReport)) {
if (isJoinRequestInAdminRoom(optionOrReport, currentUserLogin)) {
return {
reason: CONST.REQUIRES_ATTENTION_REASONS.HAS_JOIN_REQUEST,
reportAction: getActionableJoinRequestPendingReportAction(optionOrReport.reportID),
Expand Down Expand Up @@ -9395,14 +9428,14 @@ function isIOUOwnedByCurrentUser(report: OnyxEntry<Report>, allReportsDict?: Ony
* Assuming the passed in report is a default room, lets us know whether we can see it or not, based on permissions and
* the various subsets of users we've allowed to use default rooms.
*/
function canSeeDefaultRoom(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>, isReportArchived = false): boolean {
function canSeeDefaultRoom(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>, hasGuidesEmails: boolean, isReportArchived = false): boolean {
// Include archived rooms
if (isArchivedNonExpenseReport(report, isReportArchived)) {
return true;
}

// If the room has an assigned guide, it can be seen.
if (hasExpensifyGuidesEmails(Object.keys(report?.participants ?? {}).map(Number))) {
if (hasGuidesEmails) {
return true;
}

Expand All @@ -9415,9 +9448,9 @@ function canSeeDefaultRoom(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>,
return Permissions.isBetaEnabled(CONST.BETAS.DEFAULT_ROOMS, betas ?? []);
}

function canAccessReport(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>, isReportArchived = false): boolean {
function canAccessReport(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>, hasGuidesEmails: boolean, isReportArchived = false): boolean {
// We hide default rooms (it's basically just domain rooms now) from people who aren't on the defaultRooms beta.
if (isDefaultRoom(report) && !canSeeDefaultRoom(report, betas, isReportArchived)) {
if (isDefaultRoom(report) && !canSeeDefaultRoom(report, betas, hasGuidesEmails, isReportArchived)) {
return false;
}

Expand Down Expand Up @@ -9887,6 +9920,7 @@ type ShouldReportBeInOptionListParams = {
conciergeReportID: string | undefined;
/** Pre-computed value from reportAttributes derived value. When provided, skips the expensive requiresAttentionFromCurrentUser recomputation. */
requiresAttention?: boolean;
hasGuidesEmails: boolean;
};

function reasonForReportToBeInOptionList({
Expand All @@ -9906,6 +9940,7 @@ function reasonForReportToBeInOptionList({
isReportArchived,
conciergeReportID,
requiresAttention,
hasGuidesEmails,
}: ShouldReportBeInOptionListParams): ValueOf<typeof CONST.REPORT_IN_LHN_REASONS> | null {
const isInDefaultMode = !isInFocusMode;

Expand Down Expand Up @@ -9957,7 +9992,7 @@ function reasonForReportToBeInOptionList({
return null;
}

if (!canAccessReport(report, betas, isReportArchived)) {
if (!canAccessReport(report, betas, hasGuidesEmails, isReportArchived)) {
return null;
}

Expand Down Expand Up @@ -11145,8 +11180,8 @@ function isReportParticipant(accountID: number | undefined, report: OnyxEntry<Re
/**
* Check to see if the current user has access to view the report.
*/
function canCurrentUserOpenReport(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>, isReportArchived = false): boolean {
return (isReportParticipant(deprecatedCurrentUserAccountID, report) || isPublicRoom(report)) && canAccessReport(report, betas, isReportArchived);
function canCurrentUserOpenReport(report: OnyxEntry<Report>, betas: OnyxEntry<Beta[]>, hasGuidesEmails: boolean, isReportArchived = false): boolean {
return (isReportParticipant(deprecatedCurrentUserAccountID, report) || isPublicRoom(report)) && canAccessReport(report, betas, hasGuidesEmails, isReportArchived);
}

function shouldUseFullTitleToDisplay(report: OnyxEntry<Report>): boolean {
Expand Down Expand Up @@ -14204,6 +14239,8 @@ export {
getIntegrationIcon,
canBeExported,
isExported,
hasExpensifyGuidesEmails,
resolveHasGuidesEmails,
hasExportError,
hasOnlyNonReimbursableTransactions,
getReportLastMessage,
Expand Down
Loading
Loading