Skip to content

Commit

Permalink
Merge pull request #39472 from Expensify/nikki-load-oldest-actions
Browse files Browse the repository at this point in the history
Polish for one-transaction view
  • Loading branch information
amyevans authored May 3, 2024
2 parents d38fb99 + 007011d commit 1fc0339
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 61 deletions.
9 changes: 8 additions & 1 deletion src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,8 +266,14 @@ Onyx.connect({
}
const reportID = CollectionUtils.extractCollectionItemID(key);
allReportActions[reportID] = actions;
const sortedReportActions = ReportActionUtils.getSortedReportActions(Object.values(actions), true);
let sortedReportActions = ReportActionUtils.getSortedReportActions(Object.values(actions), true);
allSortedReportActions[reportID] = sortedReportActions;

const transactionThreadReportID = ReportActionUtils.getOneTransactionThreadReportID(reportID, allReportActions[reportID], true);
if (transactionThreadReportID) {
sortedReportActions = ReportActionUtils.getCombinedReportActions(allSortedReportActions[reportID], allSortedReportActions[transactionThreadReportID]);
}

lastReportActions[reportID] = sortedReportActions[0];

// The report is only visible if it is the last action not deleted that
Expand Down Expand Up @@ -554,6 +560,7 @@ function getAlternateText(
*/
function getLastMessageTextForReport(report: OnyxEntry<Report>, lastActorDetails: Partial<PersonalDetails> | null, policy?: OnyxEntry<Policy>): string {
const lastReportAction = visibleReportActionItems[report?.reportID ?? ''] ?? null;

// some types of actions are filtered out for lastReportAction, in some cases we need to check the actual last action
const lastOriginalReportAction = lastReportActions[report?.reportID ?? ''] ?? null;
let lastMessageTextFromReport = '';
Expand Down
123 changes: 83 additions & 40 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,46 +225,6 @@ function isTransactionThread(parentReportAction: OnyxEntry<ReportAction> | Empty
);
}

/**
* Returns the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions with a childReportID. Returns a reportID if there is exactly one transaction thread for the report, and null otherwise.
*/
function getOneTransactionThreadReportID(reportID: string, reportActions: OnyxEntry<ReportActions> | ReportAction[], isOffline: boolean | undefined = undefined): string | null {
// If the report is not an IOU, Expense report or an Invoice, it shouldn't be treated as one-transaction report.
const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE && report?.type !== CONST.REPORT.TYPE.INVOICE) {
return null;
}

const reportActionsArray = Object.values(reportActions ?? {});

if (!reportActionsArray.length) {
return null;
}

// Get all IOU report actions for the report.
const iouRequestTypes: Array<ValueOf<typeof CONST.IOU.REPORT_ACTION_TYPE>> = [
CONST.IOU.REPORT_ACTION_TYPE.CREATE,
CONST.IOU.REPORT_ACTION_TYPE.SPLIT,
CONST.IOU.REPORT_ACTION_TYPE.PAY,
CONST.IOU.REPORT_ACTION_TYPE.TRACK,
];
const iouRequestActions = reportActionsArray.filter(
(action) =>
action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU &&
(iouRequestTypes.includes(action.originalMessage.type) ?? []) &&
action.childReportID &&
(Boolean(action.originalMessage.IOUTransactionID) || (action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && (isOffline ?? isNetworkOffline))),
);

// If we don't have any IOU request actions, or we have more than one IOU request actions, this isn't a oneTransaction report
if (!iouRequestActions.length || iouRequestActions.length > 1) {
return null;
}

// Ensure we have a childReportID associated with the IOU report action
return iouRequestActions[0].childReportID ?? null;
}

/**
* Sort an array of reportActions by their created timestamp first, and reportActionID second
* This gives us a stable order even in the case of multiple reportActions created on the same millisecond
Expand Down Expand Up @@ -301,6 +261,27 @@ function getSortedReportActions(reportActions: ReportAction[] | null, shouldSort
return sortedActions;
}

/**
* 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[], transactionThreadReportActions: ReportAction[]): ReportAction[] {
if (isEmptyObject(transactionThreadReportActions)) {
return reportActions;
}

// Filter out the created action from the transaction thread report actions, since we already have the parent report's created action in `reportActions`
const filteredTransactionThreadReportActions = transactionThreadReportActions?.filter((action) => action.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED);

// Filter out request and send money request actions because we don't want to show any preview actions for one transaction reports
const filteredReportActions = [...reportActions, ...filteredTransactionThreadReportActions].filter((action) => {
const actionType = (action as OriginalMessageIOU).originalMessage?.type ?? '';
return actionType !== CONST.IOU.REPORT_ACTION_TYPE.CREATE && actionType !== CONST.IOU.REPORT_ACTION_TYPE.TRACK && !isSentMoneyReportAction(action);
});

return getSortedReportActions(filteredReportActions, true);
}

/**
* Returns the largest gapless range of reportActions including a the provided reportActionID, where a "gap" is defined as a reportAction's `previousReportActionID` not matching the previous reportAction in the sortedReportActions array.
* See unit tests for example of inputs and expected outputs.
Expand Down Expand Up @@ -821,6 +802,67 @@ function isTaskAction(reportAction: OnyxEntry<ReportAction>): boolean {
);
}

/**
* Gets the reportID for the transaction thread associated with a report by iterating over the reportActions and identifying the IOU report actions.
* Returns a reportID if there is exactly one transaction thread for the report, and null otherwise.
*/
function getOneTransactionThreadReportID(
reportID: string,
reportActions: OnyxEntry<ReportActions> | ReportAction[],
skipReportTypeCheck: boolean | undefined = undefined,
isOffline: boolean | undefined = undefined,
): string | null {
if (!skipReportTypeCheck) {
// If the report is not an IOU, Expense report, or Invoice, it shouldn't be treated as one-transaction report.
const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`];
if (report?.type !== CONST.REPORT.TYPE.IOU && report?.type !== CONST.REPORT.TYPE.EXPENSE && report?.type !== CONST.REPORT.TYPE.INVOICE) {
return null;
}
}

const reportActionsArray = Object.values(reportActions ?? {});
if (!reportActionsArray.length) {
return null;
}

// Get all IOU report actions for the report.
const iouRequestTypes: Array<ValueOf<typeof CONST.IOU.REPORT_ACTION_TYPE>> = [
CONST.IOU.REPORT_ACTION_TYPE.CREATE,
CONST.IOU.REPORT_ACTION_TYPE.SPLIT,
CONST.IOU.REPORT_ACTION_TYPE.PAY,
CONST.IOU.REPORT_ACTION_TYPE.TRACK,
];

const iouRequestActions = reportActionsArray.filter(
(action) =>
action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU &&
(iouRequestTypes.includes(action.originalMessage.type) ?? []) &&
action.childReportID &&
// Include deleted IOU reportActions if:
// - they have an assocaited IOU transaction ID or
// - they have visibile childActions (like comments) that we'd want to display
// - the action is pending deletion and the user is offline
(Boolean(action.originalMessage.IOUTransactionID) ||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
(isMessageDeleted(action) && action.childVisibleActionCount) ||
(action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE && (isOffline ?? isNetworkOffline))),
);

// If we don't have any IOU request actions, or we have more than one IOU request actions, this isn't a oneTransaction report
if (!iouRequestActions.length || iouRequestActions.length > 1) {
return null;
}

// If there's only one IOU request action associated with the report but it's been deleted, then we don't consider this a oneTransaction report
// and want to display it using the standard view
if (((iouRequestActions[0] as OriginalMessageIOU).originalMessage?.deleted ?? '') !== '') {
return null;
}

// Ensure we have a childReportID associated with the IOU report action
return iouRequestActions[0].childReportID ?? null;
}

/**
* When we delete certain reports, we want to check whether there are any visible actions left to display.
* If there are no visible actions left (including system messages), we can hide the report from view entirely
Expand Down Expand Up @@ -1142,6 +1184,7 @@ export {
isApprovedOrSubmittedReportAction,
getReportPreviewAction,
getSortedReportActions,
getCombinedReportActions,
getSortedReportActionsForDisplay,
isConsecutiveActionMadeByPreviousActor,
isCreatedAction,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/home/ReportScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ function ReportScreen({
}

const transactionThreadReportID = useMemo(
() => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], isOffline),
() => ReportActionsUtils.getOneTransactionThreadReportID(report.reportID, reportActions ?? [], false, isOffline),
[report.reportID, reportActions, isOffline],
);

Expand Down
34 changes: 15 additions & 19 deletions src/pages/home/report/ReportActionsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -138,22 +138,18 @@ function ReportActionsView({

// Get a sorted array of reportActions for both the current report and the transaction thread report associated with this report (if there is one)
// so that we display transaction-level and report-level report actions in order in the one-transaction view
const [combinedReportActions, parentReportActionForTransactionThread] = useMemo(() => {
if (isEmptyObject(transactionThreadReportActions)) {
return [allReportActions, null];
}

// Filter out the created action from the transaction thread report actions, since we already have the parent report's created action in `reportActions`
const filteredTransactionThreadReportActions = transactionThreadReportActions?.filter((action) => action.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED);
const moneyRequestAction = allReportActions.find((action) => action.reportActionID === transactionThreadReport?.parentReportActionID);
const combinedReportActions = useMemo(
() => ReportActionsUtils.getCombinedReportActions(allReportActions, transactionThreadReportActions),
[allReportActions, transactionThreadReportActions],
);

// Filter out the expense actions because we don't want to show any preview actions for one-transaction reports
const filteredReportActions = [...allReportActions, ...filteredTransactionThreadReportActions].filter((action) => {
const actionType = (action as OnyxTypes.OriginalMessageIOU).originalMessage?.type ?? '';
return actionType !== CONST.IOU.REPORT_ACTION_TYPE.CREATE && actionType !== CONST.IOU.REPORT_ACTION_TYPE.TRACK && !ReportActionsUtils.isSentMoneyReportAction(action);
});
return [ReportActionsUtils.getSortedReportActions(filteredReportActions, true), moneyRequestAction ?? null];
}, [allReportActions, transactionThreadReportActions, transactionThreadReport?.parentReportActionID]);
const parentReportActionForTransactionThread = useMemo(
() =>
isEmptyObject(transactionThreadReportActions)
? null
: (allReportActions.find((action) => action.reportActionID === transactionThreadReport?.parentReportActionID) as OnyxEntry<OnyxTypes.ReportAction>),
[allReportActions, transactionThreadReportActions, transactionThreadReport?.parentReportActionID],
);

const indexOfLinkedAction = useMemo(() => {
if (!reportActionID) {
Expand Down Expand Up @@ -322,13 +318,13 @@ function ReportActionsView({
}

if (!isEmptyObject(transactionThreadReport)) {
// Get newer actions based on the newest reportAction for the current report
// Get older actions based on the oldest reportAction for the current report
const oldestActionCurrentReport = reportActionIDMap.findLast((item) => item.reportID === reportID);
Report.getNewerActions(oldestActionCurrentReport?.reportID ?? '0', oldestActionCurrentReport?.reportActionID ?? '0');
Report.getOlderActions(oldestActionCurrentReport?.reportID ?? '0', oldestActionCurrentReport?.reportActionID ?? '0');

// Get newer actions based on the newest reportAction for the transaction thread report
// Get older actions based on the oldest reportAction for the transaction thread report
const oldestActionTransactionThreadReport = reportActionIDMap.findLast((item) => item.reportID === transactionThreadReport.reportID);
Report.getNewerActions(oldestActionTransactionThreadReport?.reportID ?? '0', oldestActionTransactionThreadReport?.reportActionID ?? '0');
Report.getOlderActions(oldestActionTransactionThreadReport?.reportID ?? '0', oldestActionTransactionThreadReport?.reportActionID ?? '0');
} else {
// Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments
Report.getOlderActions(reportID, oldestReportAction.reportActionID);
Expand Down
1 change: 1 addition & 0 deletions src/types/onyx/OriginalMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ type IOUMessage = {
type: ValueOf<typeof CONST.IOU.REPORT_ACTION_TYPE>;
cancellationReason?: string;
paymentType?: PaymentMethodType;
deleted?: string;
/** Only exists when we are sending money */
IOUDetails?: IOUDetails;
};
Expand Down

0 comments on commit 1fc0339

Please sign in to comment.