Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polish for one-transaction view #39472

Merged
merged 26 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
255255b
fix reversed actions logic
NikkiWines Apr 3, 2024
fba77cf
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines Apr 4, 2024
1d256b9
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines Apr 4, 2024
853f71b
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines Apr 4, 2024
f9d5eaa
don't display report as one-transaction report if it has previously d…
NikkiWines Apr 4, 2024
a0a3ff0
only include deleted IOUs if they have visible child actions we need …
NikkiWines Apr 4, 2024
2e030d9
move combined reportAction logic into ReportActionUtils
NikkiWines Apr 5, 2024
1ca6f0d
ensure lastMessageText is correct for oneTransaction reports
NikkiWines Apr 5, 2024
7936ff1
clarifying comment
NikkiWines Apr 5, 2024
50a275c
minor style change and also use isMessageDeleted instead of originalM…
NikkiWines Apr 5, 2024
0e73e03
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines Apr 8, 2024
38adc1b
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines Apr 11, 2024
48976d7
update stray function to pass correct params
NikkiWines Apr 11, 2024
6423f2b
use const
NikkiWines Apr 11, 2024
f4b4ecc
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines Apr 16, 2024
8b0e251
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines Apr 17, 2024
ddcd727
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines Apr 22, 2024
f9149cc
fix typing
NikkiWines Apr 22, 2024
6d31950
lint
NikkiWines Apr 22, 2024
b1e1957
lint 2
NikkiWines Apr 22, 2024
d241ff4
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines Apr 29, 2024
3714c38
prettier
NikkiWines Apr 30, 2024
9fc9010
Comment update
NikkiWines May 3, 2024
125c725
Merge branch 'main' of github.com:Expensify/App into nikki-load-oldes…
NikkiWines May 3, 2024
f7cdd3f
add exception for checking report type
NikkiWines May 3, 2024
007011d
lint/style
NikkiWines May 3, 2024
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
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
Loading