Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a13d9e2
fix: refactor getReportOrDraftReport usage
truph01 May 11, 2026
19694db
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 May 18, 2026
2da8f5f
fix: prettier
truph01 May 18, 2026
f0a00b2
fix: use targetTransactionReport directly
truph01 May 18, 2026
5fcb07c
fix: add comment
truph01 May 19, 2026
985b593
fix: add comment
truph01 May 19, 2026
b0a4856
fix: conflicts
truph01 May 19, 2026
7b1057c
fix: conflicts
truph01 May 19, 2026
4539dbb
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 May 22, 2026
9c1d54d
merge main
truph01 May 26, 2026
c734a15
fix: merge main
truph01 May 26, 2026
7de4a90
fix: conflicts
truph01 May 26, 2026
f39797c
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 Jun 1, 2026
7e9c970
fix: revert the refactor related to getReportName
truph01 Jun 1, 2026
c89640c
fix: revert the refactor related to getReportName
truph01 Jun 1, 2026
a20fc25
fix: remove transaction?: OnyxEntry<Transaction>
truph01 Jun 1, 2026
e09f1ae
fix: test
truph01 Jun 1, 2026
34818a8
fix: remove redundant param
truph01 Jun 1, 2026
9940ea1
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 Jun 2, 2026
4347c27
fix: conflicts
truph01 Jun 4, 2026
b1d4a59
fix: conflicts
truph01 Jun 4, 2026
475e230
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 Jun 4, 2026
90e0734
fix: conflicts
truph01 Jun 8, 2026
594bf74
fix: conflicts
truph01 Jun 8, 2026
16d4406
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 Jun 8, 2026
d399118
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 Jun 9, 2026
4cf9f1d
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 Jun 9, 2026
b578cd4
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 Jun 9, 2026
e739444
fix: conflicts
truph01 Jun 10, 2026
93181c0
fix: conflicts
truph01 Jun 10, 2026
afaea68
fix: lint
truph01 Jun 10, 2026
84cffa3
Merge branch 'Expensify:main' into fix/66416-part-4
truph01 Jun 12, 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
10 changes: 6 additions & 4 deletions src/hooks/useSearchBulkActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ function shouldShowBulkDuplicateOption({
}

const reportID = selectedTransactions[id]?.reportID;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CONSISTENCY-3 (docs)

The expression allReports?.[\${ONYXKEYS.COLLECTION.REPORT}${reportID}`]is repeated across multiple call sites in this file and inuseSelectedTransactionsActions.ts. This boilerplate for looking up a report from the allReportscollection and passing it as the 5th positional argument (with threeundefined` placeholders) is duplicated 6+ times.

Consider extracting a helper function to encapsulate this pattern:

function getReportFromAllReports(reportID: string | undefined) {
    return reportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] : undefined;
}

Then each call site becomes:

getReportOrDraftReport(reportID, searchReports, undefined, undefined, getReportFromAllReports(reportID))

Or better yet, consider whether getReportOrDraftReport itself should accept a named options object instead of positional parameters to avoid the undefined, undefined, undefined pattern entirely.


Reviewed at: a13d9e2 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a common pattern used throughout the app for looking up Onyx collection entries. Introducing a helper like:

function getReportFromAllReports(reportID: string | undefined) {
    return reportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] : undefined;
}

would just replace allReports?.[${ONYXKEYS.COLLECTION.REPORT}${reportID}] with getReportFromAllReports(reportID) — it doesn't reduce complexity or improve readability in a meaningful way. The lookup is a single expression and is already self-explanatory. I'll leave this as-is.

const submitterReport = reportID ? getReportOrDraftReport(reportID, searchReports) : undefined;
const submitterReport = reportID ? getReportOrDraftReport(reportID, searchReports, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]) : undefined;
if (submitterReport && !isCurrentUserSubmitter(submitterReport)) {
return false;
}
Expand Down Expand Up @@ -1169,10 +1169,10 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
return (
selectedTransactionReportIDs.length > 0 &&
selectedTransactionReportIDs.every((id) => {
return isCurrentUserSubmitter(getReportOrDraftReport(id, reports));
return isCurrentUserSubmitter(getReportOrDraftReport(id, reports, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`]));
})
);
}, [selectedTransactionReportIDs, currentUserPersonalDetails?.accountID, currentSearchResults?.data]);
}, [selectedTransactionReportIDs, currentUserPersonalDetails?.accountID, currentSearchResults?.data, allReports]);

const duplicateHandlerRef = useRef<() => void>(() => {});
const setDuplicateHandler = useCallback((handler: () => void) => {
Expand Down Expand Up @@ -1820,7 +1820,9 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) {
if (!transactionEntry) {
continue;
}
const ownerAccountID = transactionEntry.ownerAccountID ?? getReportOrDraftReport(transactionEntry.reportID)?.ownerAccountID;
const ownerAccountID =
transactionEntry.ownerAccountID ??
getReportOrDraftReport(transactionEntry.reportID, undefined, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionEntry.reportID}`])?.ownerAccountID;
if (typeof ownerAccountID === 'number') {
ownerAccountIDs.add(ownerAccountID);
if (ownerAccountIDs.size > 1) {
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useSelectedTransactionsActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function useSelectedTransactionsActions({
continue;
}

const parentReport = getReportOrDraftReport(reportID);
const parentReport = getReportOrDraftReport(reportID, undefined, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]);
const ownerAccountID = parentReport?.ownerAccountID;

if (typeof ownerAccountID === 'number') {
Expand Down
2 changes: 2 additions & 0 deletions src/libs/ReportNameUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -860,10 +860,12 @@ function computeChatThreadReportName(
if (transactions) {
linkedTransactions = linkedTransaction ? [linkedTransaction] : [];
}
const linkedTransactionReport = linkedTransaction?.reportID ? reports?.[`${ONYXKEYS.COLLECTION.REPORT}${linkedTransaction.reportID}`] : undefined;
let formattedName = getTransactionReportName({
translate,
reportAction: parentReportAction,
transactions: linkedTransactions,
report: linkedTransactionReport,
});

if (isArchivedNonExpense) {
Expand Down
5 changes: 2 additions & 3 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5298,12 +5298,12 @@ function getTransactionReportName({
translate,
reportAction,
transactions,
reports,
report,
}: {
translate: LocalizedTranslate;
reportAction: OnyxEntry<ReportAction | OptimisticIOUReportAction>;
transactions?: Transaction[];
reports?: Report[];
report: OnyxEntry<Report>;
}): string {
if (reportAction && isReversedTransaction(reportAction)) {
return translate('parentReportAction.reversedTransaction');
Expand All @@ -5324,7 +5324,6 @@ function getTransactionReportName({
return translate('iou.receiptScanning', {count: 1});
}

const report = getReportOrDraftReport(transaction?.reportID, reports);
if (hasMissingSmartscanFieldsTransactionUtils(transaction, report)) {
return translate('iou.receiptMissingDetails');
}
Expand Down
4 changes: 2 additions & 2 deletions src/pages/TransactionMerge/MergeTransactionsListContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
import {getTransactionsForMerging, setupMergeTransactionData, setupMergeTransactionDataAndNavigate} from '@libs/actions/MergeTransaction';
import {fillMissingReceiptSource} from '@libs/MergeTransactionUtils';
import {getTransactionReportName, isIOUReport} from '@libs/ReportUtils';
import {getReportOrDraftReport, getTransactionReportName, isIOUReport} from '@libs/ReportUtils';
import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan';
import tokenizedSearch from '@libs/tokenizedSearch';
import {getAmount, getCreated, getCurrency, getDescription, getMerchant, isExpenseUnreported} from '@libs/TransactionUtils';
Expand Down Expand Up @@ -131,7 +131,7 @@ function MergeTransactionsListContent({transactionID, mergeTransaction}: MergeTr
translate,
reportAction: undefined,
transactions: [targetTransaction],
reports: targetTransactionReport ? [targetTransactionReport] : [],
report: getReportOrDraftReport(targetTransaction?.reportID, targetTransactionReport ? [targetTransactionReport] : [], undefined, undefined, targetTransactionReport),
})
: '';

Expand Down
3 changes: 2 additions & 1 deletion src/pages/iou/request/step/IOURequestStepConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@ function IOURequestStepConfirmation({
const isMRReport = isMoneyRequestReport(report);
const destinationReportID =
backToReport ?? (isPerDiemRequest && isMRReport && Navigation.getTopmostReportId() !== report?.reportID ? report?.chatReportID : report?.reportID) ?? selfDMReportID;
const [destinationReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${destinationReportID}`);

useEffect(() => {
if (hasPreInsertFired.current || !isTransactionReady || !getIsNarrowLayout()) {
Expand Down Expand Up @@ -470,7 +471,7 @@ function IOURequestStepConfirmation({
const hasValidDestination = !!destinationReportID && Navigation.getTopmostReportId() !== destinationReportID;

// The report must be in Onyx so the pre-inserted screen can render immediately.
const isDestinationReportLoaded = !!destinationReportID && !!getReportOrDraftReport(destinationReportID)?.reportID;
const isDestinationReportLoaded = !!destinationReportID && !!getReportOrDraftReport(destinationReportID, undefined, undefined, undefined, destinationReport)?.reportID;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ CONSISTENCY-3 (docs)

The pattern getReportOrDraftReport(reportID, undefined, undefined, undefined, report) with three positional undefined placeholders is duplicated across this file and SubmitExpenseOrchestrator.tsx (which has the same pattern twice). This is the same code smell present in the hooks files.

Since getReportOrDraftReport already has a positional API with 5 parameters, and most callers only need the 1st and 5th, consider refactoring it to accept a named options object:

function getReportOrDraftReport({ reportID, searchReports, fallbackReport, reportDrafts, report }: GetReportOrDraftReportParams)

This would eliminate all the undefined placeholders and make call sites self-documenting.


Reviewed at: a13d9e2 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ince getReportOrDraftReport already has a positional API with 5 parameters, and most callers only need the 1st and 5th, consider refactoring it to accept a named options object:

function getReportOrDraftReport({ reportID, searchReports, fallbackReport, reportDrafts, report }: GetReportOrDraftReportParams)

That plan sounds good. However, I think we can do it in another PR.
cc @DylanDylann


const shouldPreInsertReport = canUseReportPreInsert && isOutsideRHP && hasValidDestination && isDestinationReportLoaded;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {useEffect, useRef, useState} from 'react';
import LocationPermissionModal from '@components/LocationPermissionModal';
import useOnyx from '@hooks/useOnyx';
import DateUtils from '@libs/DateUtils';
import {cancelDeferredWrite, flushDeferredWrite, reserveDeferredWriteChannel} from '@libs/deferredLayoutWrite';
import getIsNarrowLayout from '@libs/getIsNarrowLayout';
Expand All @@ -18,6 +19,7 @@ import {updateLastLocationPermissionPrompt} from '@userActions/IOU/MoneyRequest'
import type {IOUType} from '@src/CONST';
import CONST from '@src/CONST';
import NAVIGATORS from '@src/NAVIGATORS';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Receipt} from '@src/types/onyx/Transaction';
import {getSubmitHandler, SUBMIT_HANDLER} from './getSubmitHandler';
Expand Down Expand Up @@ -121,6 +123,7 @@ function SubmitExpenseOrchestrator({
isFromFloatingActionButtonOnTransaction,
children,
}: SubmitExpenseOrchestratorProps) {
const [destinationReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${destinationReportID}`);
const [isConfirming, setIsConfirming] = useState(false);
const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false);
const confirmingSafetyTimeout = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
Expand Down Expand Up @@ -178,7 +181,7 @@ function SubmitExpenseOrchestrator({
isReportInRHP: isReportOpenInRHP(rootState),
isReportTopmostSplit: isReportTopmostSplitNavigator(),
isSearchTopmostFullScreen: isSearchTopmostFullScreenRoute(),
isDestinationReportLoaded: !!destinationReportID && !!getReportOrDraftReport(destinationReportID)?.reportID,
isDestinationReportLoaded: !!destinationReportID && !!getReportOrDraftReport(destinationReportID, undefined, undefined, undefined, destinationReport)?.reportID,
};
};

Expand Down Expand Up @@ -324,7 +327,7 @@ function SubmitExpenseOrchestrator({
setFastPath(CONST.TELEMETRY.FAST_PATH_HANDLER.REPORT_IN_RHP_DISMISS, CONST.TELEMETRY.SUBMIT_OPTIMIZATION.DISMISS_FIRST);
const rootState = navigationRef.getRootState();

const report = destinationReportID ? getReportOrDraftReport(destinationReportID) : undefined;
const report = destinationReportID ? getReportOrDraftReport(destinationReportID, undefined, undefined, undefined, destinationReport) : undefined;
const isDestinationEmpty = !!report && isMoneyRequestReport(report) && !report.transactionCount;
if (isDestinationEmpty) {
reserveDeferredWriteChannel(CONST.DEFERRED_LAYOUT_WRITE_KEYS.DISMISS_MODAL, {destinationReportID});
Expand Down
102 changes: 102 additions & 0 deletions tests/unit/ReportUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ import {
getTaskAssigneeChatOnyxData,
getTitleFieldWithFallback,
getTransactionDetails,
getTransactionReportName,
getTransactionSortValue,
getUnreportedTransactionMessage,
getViolatingReportIDForRBRInLHN,
Expand Down Expand Up @@ -12692,6 +12693,107 @@ describe('ReportUtils', () => {
});
});

describe('getTransactionReportName', () => {
const mockReportID = '100';
const mockTransactionReport: Report = {
...createRandomReport(100, undefined),
reportName: 'Transaction Report',
type: CONST.REPORT.TYPE.EXPENSE,
};

beforeEach(async () => {
await Onyx.clear();
});

test('uses report param when provided', async () => {
const transaction: Transaction = {
...createRandomTransaction(1),
reportID: mockReportID,
merchant: 'Coffee Shop',
amount: -1000,
currency: CONST.CURRENCY.USD,
};

const result = getTransactionReportName({
translate: translateLocal,
reportAction: undefined,
transactions: [transaction],
report: mockTransactionReport,
});

expect(result).toBeDefined();
expect(typeof result).toBe('string');
});

test('returns report name with report param', () => {
const transaction: Transaction = {
...createRandomTransaction(1),
reportID: mockReportID,
merchant: 'Coffee Shop',
amount: -1000,
currency: CONST.CURRENCY.USD,
};

const result = getTransactionReportName({
translate: translateLocal,
reportAction: undefined,
transactions: [transaction],
report: mockTransactionReport,
});

expect(result).toBeDefined();
expect(typeof result).toBe('string');
});

test('returns reversed transaction message when action is reversed', () => {
const reportAction = createRandomReportAction(1);

jest.spyOn(require('@libs/ReportActionsUtils'), 'isReversedTransaction').mockReturnValueOnce(true);

const result = getTransactionReportName({
translate: translateLocal,
reportAction,
report: mockTransactionReport,
});

expect(result).toBe(translateLocal('parentReportAction.reversedTransaction'));
});

test('returns deleted expense message when action is deleted', () => {
const reportAction = createRandomReportAction(1);

jest.spyOn(require('@libs/ReportActionsUtils'), 'isDeletedAction').mockReturnValueOnce(true);

const result = getTransactionReportName({
translate: translateLocal,
reportAction,
report: mockTransactionReport,
});

expect(result).toBe(translateLocal('parentReportAction.deletedExpense'));
});

test('uses report param from caller', () => {
const transaction: Transaction = {
...createRandomTransaction(1),
reportID: mockReportID,
merchant: 'Coffee Shop',
amount: -1000,
currency: CONST.CURRENCY.USD,
};

const result = getTransactionReportName({
translate: translateLocal,
reportAction: undefined,
transactions: [transaction],
report: mockTransactionReport,
});

expect(result).toBeDefined();
expect(typeof result).toBe('string');
});
});

describe('buildOptimisticExpenseReport', () => {
beforeEach(Onyx.clear);
// Other describe blocks leave isPaidGroupPolicy stuck on mockReturnValue(true); restore the type-based default so the report-name fallback is driven by the real policy type
Expand Down
Loading