Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion src/libs/OptionsListUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,8 @@ Onyx.connect({

if (transactionThreadReportID) {
const transactionThreadReportActionsArray = Object.values(actions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`] ?? {});
sortedReportActions = getCombinedReportActions(sortedReportActions, transactionThreadReportID, transactionThreadReportActionsArray, reportID);
const isSelfDM = report?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM;
sortedReportActions = getCombinedReportActions(sortedReportActions, transactionThreadReportID, transactionThreadReportActionsArray, isSelfDM);
}

const firstReportAction = sortedReportActions.at(0);
Expand Down
9 changes: 1 addition & 8 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,12 +785,7 @@ function getSortedReportActions(reportActions: ReportAction[] | null, shouldSort
* Returns a sorted and filtered list of report actions from a report and it's associated child
* transaction thread report in order to correctly display reportActions from both reports in the one-transaction report view.
*/
function getCombinedReportActions(
reportActions: ReportAction[],
transactionThreadReportID: string | null,
transactionThreadReportActions: ReportAction[],
reportID?: string,
): ReportAction[] {
function getCombinedReportActions(reportActions: ReportAction[], transactionThreadReportID: string | null, transactionThreadReportActions: ReportAction[], isSelfDM = false): ReportAction[] {
const isSentMoneyReport = reportActions.some((action) => isSentMoneyReportAction(action));

// We don't want to combine report actions of transaction thread in iou report of send money request because we display the transaction report of send money request as a normal thread
Expand All @@ -813,8 +808,6 @@ function getCombinedReportActions(
filteredParentReportActions = reportActions?.filter((action) => action.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED);
}

const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
const isSelfDM = report?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM;
// Filter out request and send money request actions because we don't want to show any preview actions for one transaction reports
const filteredReportActions = [...filteredParentReportActions, ...filteredTransactionThreadReportActions].filter((action) => {
if (!isMoneyRequestAction(action)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {OnyxCollection} from 'react-native-onyx';
import {getCombinedReportActions, getOneTransactionThreadReportID, getSortedReportActions, withDEWRoutedActionsArray} from '@libs/ReportActionsUtils';
import createOnyxDerivedValueConfig from '@userActions/OnyxDerived/createOnyxDerivedValueConfig';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Report, ReportAction, ReportActions} from '@src/types/onyx';
import type {SortedReportActionsDerivedValue} from '@src/types/onyx/DerivedValues';
Expand All @@ -23,7 +24,8 @@ function computeForReport(

if (transactionThreadReportID && allReportActions) {
const transactionThreadReportActionsArray = Object.values(allReportActions[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`] ?? {});
sortedReportActions = getCombinedReportActions(sortedReportActions, transactionThreadReportID, transactionThreadReportActionsArray, reportID);
const isSelfDM = report?.chatType === CONST.REPORT.CHAT_TYPE.SELF_DM;
sortedReportActions = getCombinedReportActions(sortedReportActions, transactionThreadReportID, transactionThreadReportActionsArray, isSelfDM);
}

return {
Expand Down
113 changes: 113 additions & 0 deletions tests/unit/ReportActionsUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
getAutoPayApprovedReportsEnabledMessage,
getAutoReimbursementMessage,
getCardIssuedMessage,
getCombinedReportActions,
getCompanyAddressUpdateMessage,
getCreatedReportForUnapprovedTransactionsMessage,
getCurrencyDefaultTaxUpdateMessage,
Expand Down Expand Up @@ -5111,6 +5112,118 @@ describe('ReportActionsUtils', () => {
});
});

describe('getCombinedReportActions', () => {
const makeAction = (id: string, actionName: string, created: string, overrides: Partial<ReportAction> = {}): ReportAction =>
({
reportActionID: id,
actionName,
created,
message: [{type: 'TEXT', html: '', text: '', isEdited: false, isDeletedParentAction: false}],
...overrides,
}) as unknown as ReportAction;

const makeIOUAction = (id: string, created: string, type: string, hasIOUDetails = false): ReportAction =>
makeAction(id, CONST.REPORT.ACTIONS.TYPE.IOU, created, {
originalMessage: {
type,
...(hasIOUDetails ? {IOUDetails: {amount: 100, currency: 'USD'}} : {}),
},
} as Partial<ReportAction>);

it('returns original reportActions when transactionThreadReportID is null', () => {
const actions = [makeAction('1', CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, '2024-01-01 10:00:00')];
const result = getCombinedReportActions(actions, null, []);
expect(result).toEqual(actions);
});

it('returns original reportActions for sent money reports', () => {
const sentMoneyAction = makeIOUAction('1', '2024-01-01 10:00:00', CONST.IOU.REPORT_ACTION_TYPE.PAY, true);
const actions = [sentMoneyAction];
const result = getCombinedReportActions(actions, 'txnThread1', [makeAction('2', CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, '2024-01-01 11:00:00')]);
expect(result).toEqual(actions);
});

it('combines and sorts actions from parent and transaction thread', () => {
const parentCreated = makeAction('1', CONST.REPORT.ACTIONS.TYPE.CREATED, '2024-01-01 08:00:00');
const parentComment = makeAction('2', CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, '2024-01-01 10:00:00');
const txnComment = makeAction('3', CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, '2024-01-01 09:00:00');
const txnCreated = makeAction('4', CONST.REPORT.ACTIONS.TYPE.CREATED, '2024-01-01 09:30:00');

const result = getCombinedReportActions([parentCreated, parentComment], 'txnThread1', [txnCreated, txnComment]);

// parentCreated should be kept since it's older; txnCreated should be filtered out
// Result should be sorted descending by created, with CREATED last
const resultIDs = result.map((a) => a.reportActionID);
expect(resultIDs).toContain('1');
expect(resultIDs).toContain('2');
expect(resultIDs).toContain('3');
});

it('keeps transaction thread CREATED when it is older than parent CREATED', () => {
const parentCreated = makeAction('1', CONST.REPORT.ACTIONS.TYPE.CREATED, '2024-01-01 10:00:00');
const txnCreated = makeAction('2', CONST.REPORT.ACTIONS.TYPE.CREATED, '2024-01-01 08:00:00');

const result = getCombinedReportActions([parentCreated], 'txnThread1', [txnCreated]);
const actionNames = result.map((a) => a.actionName);
// Only one CREATED should be present
expect(actionNames.filter((name) => name === CONST.REPORT.ACTIONS.TYPE.CREATED).length).toBe(1);
});

it('filters out IOU CREATE actions in non-selfDM context', () => {
const parentCreated = makeAction('1', CONST.REPORT.ACTIONS.TYPE.CREATED, '2024-01-01 08:00:00');
const iouCreateAction = makeIOUAction('2', '2024-01-01 09:00:00', CONST.IOU.REPORT_ACTION_TYPE.CREATE);
const txnComment = makeAction('3', CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, '2024-01-01 10:00:00');

const result = getCombinedReportActions([parentCreated, iouCreateAction], 'txnThread1', [txnComment]);
const resultIDs = result.map((a) => a.reportActionID);
expect(resultIDs).not.toContain('2');
});

it('filters out IOU TRACK actions in non-selfDM context', () => {
const parentCreated = makeAction('1', CONST.REPORT.ACTIONS.TYPE.CREATED, '2024-01-01 08:00:00');
const iouTrackAction = makeIOUAction('2', '2024-01-01 09:00:00', CONST.IOU.REPORT_ACTION_TYPE.TRACK);
const txnComment = makeAction('3', CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, '2024-01-01 10:00:00');

const result = getCombinedReportActions([parentCreated, iouTrackAction], 'txnThread1', [txnComment]);
const resultIDs = result.map((a) => a.reportActionID);
expect(resultIDs).not.toContain('2');
});

it('keeps IOU TRACK actions in selfDM context', () => {
const parentCreated = makeAction('1', CONST.REPORT.ACTIONS.TYPE.CREATED, '2024-01-01 08:00:00');
const iouTrackAction = makeIOUAction('2', '2024-01-01 09:00:00', CONST.IOU.REPORT_ACTION_TYPE.TRACK);
const txnComment = makeAction('3', CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, '2024-01-01 10:00:00');

const result = getCombinedReportActions([parentCreated, iouTrackAction], 'txnThread1', [txnComment], true);
const resultIDs = result.map((a) => a.reportActionID);
expect(resultIDs).toContain('2');
});

it('filters out IOU CREATE actions even in selfDM context', () => {
const parentCreated = makeAction('1', CONST.REPORT.ACTIONS.TYPE.CREATED, '2024-01-01 08:00:00');
const iouCreateAction = makeIOUAction('2', '2024-01-01 09:00:00', CONST.IOU.REPORT_ACTION_TYPE.CREATE);
const txnComment = makeAction('3', CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, '2024-01-01 10:00:00');

const result = getCombinedReportActions([parentCreated, iouCreateAction], 'txnThread1', [txnComment], true);
const resultIDs = result.map((a) => a.reportActionID);
expect(resultIDs).not.toContain('2');
});

it('passes report parameter correctly to determine selfDM behavior', () => {
const parentCreated = makeAction('1', CONST.REPORT.ACTIONS.TYPE.CREATED, '2024-01-01 08:00:00');
const iouTrackAction = makeIOUAction('2', '2024-01-01 09:00:00', CONST.IOU.REPORT_ACTION_TYPE.TRACK);
const txnComment = makeAction('3', CONST.REPORT.ACTIONS.TYPE.ADD_COMMENT, '2024-01-01 10:00:00');

// Without report - TRACK should be filtered
const resultWithoutReport = getCombinedReportActions([parentCreated, iouTrackAction], 'txnThread1', [txnComment], false);
expect(resultWithoutReport.map((a) => a.reportActionID)).not.toContain('2');

// With selfDM report - TRACK should be kept
const resultWithReport = getCombinedReportActions([parentCreated, iouTrackAction], 'txnThread1', [txnComment], true);
expect(resultWithReport.map((a) => a.reportActionID)).toContain('2');
});
});

describe('getModerationFlagState', () => {
function makeActionWithDecision(decision: DecisionName | undefined): ReportAction {
return {
Expand Down
Loading