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

Add One Transaction Report View #36934

Merged
merged 51 commits into from
Mar 29, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
53ada16
report action utils functions
NikkiWines Feb 7, 2024
df1b56c
report utils functions for one transaction report
NikkiWines Feb 7, 2024
b841b6a
hide background for money requests on a single transaction report
NikkiWines Feb 7, 2024
7ec2e5c
update css style for money request
NikkiWines Feb 7, 2024
353b740
update report actions items to retrieve transaction thread report det…
NikkiWines Feb 9, 2024
42adcea
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Feb 9, 2024
66f498b
use undefined instead of {}
NikkiWines Feb 9, 2024
de66987
check prevProps
NikkiWines Feb 9, 2024
74a42f6
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Feb 20, 2024
1bf4c04
simplify transactionThreadReportActions display
NikkiWines Feb 20, 2024
6153bab
minor style and lint updates
NikkiWines Feb 21, 2024
d436ffe
use simplified report icons when applicable
NikkiWines Feb 21, 2024
b722725
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Feb 22, 2024
b13fc4b
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Feb 26, 2024
452d012
add transactionThreadReportID to report structure for onyx and props
NikkiWines Feb 27, 2024
cac6d81
simplify transactionThreadReportID logic to use value returned in the…
NikkiWines Feb 27, 2024
6a08ba5
minor style
NikkiWines Feb 27, 2024
1674735
don't show report if transaction thread and expense report have the s…
NikkiWines Feb 27, 2024
f3f5e52
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Feb 27, 2024
90bc361
update default value for transactionThreadReportActions key
NikkiWines Feb 28, 2024
0f21504
update utils files to no longer reference report.transactionThreadRep…
NikkiWines Mar 6, 2024
4bb0313
use general report and reportActions onyx keys instead of relying on …
NikkiWines Mar 6, 2024
35d0264
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 6, 2024
c941d23
update getOneTransactionReportID to take in only reportActions
NikkiWines Mar 7, 2024
2a02be8
fix some malformed logic
NikkiWines Mar 7, 2024
b5ae144
ensure we don't show outdated UI due to removed IOU requests
NikkiWines Mar 7, 2024
f6cfcb3
use transaction currency instead of checking between transactionThrea…
NikkiWines Mar 7, 2024
0140912
merge main and address conflicts from ts migrations for ReportActionItem
NikkiWines Mar 8, 2024
db05afc
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 8, 2024
c274090
style and ts updates
NikkiWines Mar 8, 2024
618b5ac
minor style
NikkiWines Mar 8, 2024
31cf018
adjust currency and filtering combined reportActions logic
NikkiWines Mar 9, 2024
6b2d28a
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 11, 2024
1b27a33
minor adjustments and stylistic changes
NikkiWines Mar 11, 2024
d36bc98
ts fixes
NikkiWines Mar 11, 2024
686a2dc
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 11, 2024
7994917
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 14, 2024
1003a1f
get transactionThreadReportActions in ReportActionsView instead of Re…
NikkiWines Mar 15, 2024
de600c8
pass transactionThreadReport and reportActions for report to reportAc…
NikkiWines Mar 15, 2024
034b93f
lint / style
NikkiWines Mar 15, 2024
716023c
supress incorrect linter error
NikkiWines Mar 20, 2024
3b88172
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 20, 2024
45c7fc1
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 21, 2024
f9f3607
update reportActionsView to account for getting newer and older repor…
NikkiWines Mar 22, 2024
e3be337
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 26, 2024
1090c5b
type resolution
NikkiWines Mar 26, 2024
a4926d3
updates to one-transaction utils functions
NikkiWines Mar 28, 2024
7dd2384
use reportActionsByReport, and some minor stylistic updates
NikkiWines Mar 28, 2024
fec3aa3
pass transactionThreadReportID from ReportScreen
NikkiWines Mar 28, 2024
5438f69
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 28, 2024
1aac6e0
Merge branch 'main' of github.com:Expensify/App into nikki-one-transa…
NikkiWines Mar 28, 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
10 changes: 7 additions & 3 deletions src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ type MoneyRequestViewPropsWithoutTransaction = MoneyRequestViewOnyxPropsWithoutT

/** Whether we should display the horizontal rule below the component */
shouldShowHorizontalRule: boolean;

/** Whether we should display the animated banner above the component */
shouldShowAnimatedBackground: boolean;
};

type MoneyRequestViewProps = MoneyRequestViewTransactionOnyxProps & MoneyRequestViewPropsWithoutTransaction;
Expand All @@ -85,6 +88,7 @@ function MoneyRequestView({
policyTagList,
policy,
transactionViolations,
shouldShowAnimatedBackground,
}: MoneyRequestViewProps) {
const theme = useTheme();
const styles = useThemeStyles();
Expand Down Expand Up @@ -239,9 +243,9 @@ function MoneyRequestView({
);

return (
<View style={[StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth)]}>
<AnimatedEmptyStateBackground />
<View style={[StyleUtils.getReportWelcomeTopMarginStyle(isSmallScreenWidth)]}>
<View style={[StyleUtils.getReportWelcomeContainerStyle(isSmallScreenWidth, true, shouldShowAnimatedBackground)]}>
{shouldShowAnimatedBackground && <AnimatedEmptyStateBackground />}
<View style={shouldShowAnimatedBackground && [StyleUtils.getReportWelcomeTopMarginStyle(isSmallScreenWidth, true)]}>
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
{(showMapAsImage || hasReceipt) && (
<OfflineWithFeedback
Expand Down
36 changes: 36 additions & 0 deletions src/libs/ReportActionsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,22 @@ function getSortedReportActionsForDisplay(reportActions: ReportActions | null, s
return getSortedReportActions(baseURLAdjustedReportActions, true, shouldMarkTheFirstItemAsNewest);
}

/**
* This method returns a combined array of report actions from a parent report and child transaction thread report that
* are ready for display in the ReportActionView.
NikkiWines marked this conversation as resolved.
Show resolved Hide resolved
*/
function getCombinedReportActionsForDisplay(reportActions: ReportAction[], transactionThreadReportActions: ReportAction[]): ReportAction[] {

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

// Sort the combined list of parent report actions and transaction thread report actions
const sortedReportActions = getSortedReportActions([...reportActions, ...filteredTransactionThreadReportActions], true);

// Filter out IOU report actions because we don't want to show any preview actions for one transaction reports
return sortedReportActions.filter(action => action.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU);
}

/**
* In some cases, there can be multiple closed report actions in a chat report.
* This method returns the last closed report action so we can always show the correct archived report reason.
Expand Down Expand Up @@ -693,6 +709,24 @@ function getAllReportActions(reportID: string): ReportActions {
return allReportActions?.[reportID] ?? {};
}

/**
* Gets an array of IOU report actions
*/
function getIOUReportActions(reportID: string): ReportAction[] | null {
const reportActions = Object.values(getAllReportActions(reportID));
if (!reportActions.length) {
return null;
}

const iouRequestTypes: Array<ValueOf<typeof CONST.IOU.REPORT_ACTION_TYPE>> = [CONST.IOU.REPORT_ACTION_TYPE.CREATE, CONST.IOU.REPORT_ACTION_TYPE.SPLIT];
const iouRequestActions = reportActions?.filter((action) => action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && iouRequestTypes.includes(action.originalMessage.type)) ?? [];

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

/**
* Check whether a report action is an attachment (a file, such as an image or a zip).
*
Expand Down Expand Up @@ -892,6 +926,7 @@ function isCurrentActionUnread(report: Report | EmptyObject, reportAction: Repor
export {
extractLinksFromMessageHtml,
getAllReportActions,
getIOUReportActions,
getIOUReportIDFromReportActionPreview,
getLastClosedReportAction,
getLastVisibleAction,
Expand All @@ -906,6 +941,7 @@ export {
getReportPreviewAction,
getSortedReportActions,
getSortedReportActionsForDisplay,
getCombinedReportActionsForDisplay,
isConsecutiveActionMadeByPreviousActor,
isCreatedAction,
isCreatedTaskReportAction,
Expand Down
49 changes: 49 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,38 @@ function isMoneyRequestReport(reportOrID: OnyxEntry<Report> | string): boolean {
return isIOUReport(report) || isExpenseReport(report);
}

/**
* Checks if a report has only one transaction associated with it
*/
function isOneTransactionReport(reportOrID: OnyxEntry<Report> | string): boolean {
const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null;

// Check the parent report (which would be the IOU or expense report if the passed report is an IOU or expense request)
// to see how many IOU report actions it contains
const iouReportActions = ReportActionsUtils.getIOUReportActions(report?.reportID ?? '');
return (iouReportActions?.length ?? 0) === 1;
}

/**
* Returns the reportID of the first transaction thread associated with a report
*/
function getOneTransactionThreadReportID(reportOrID: OnyxEntry<Report> | string): string | undefined {
const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null;

// Get all IOU report actions for the report.
const iouReportAction = ReportActionsUtils.getIOUReportActions(report?.reportID ?? '')?.find(reportAction => reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.childReportID);
return iouReportAction ? String(iouReportAction.childReportID) : '0'
}

/**
* Checks if a report is a transaction thread associated with a report that has only one transaction
*/
function isOneTransactionThread(reportOrID: OnyxEntry<Report> | string): boolean {
const report = typeof reportOrID === 'object' ? reportOrID : allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportOrID}`] ?? null;
const parentReport = getParentReport(report);
return isOneTransactionReport(parentReport?.reportID ?? '');
}

Copy link
Contributor

Choose a reason for hiding this comment

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

These methods are anti-patterns, like the ones being removed in #27262.

The problem with them is in how they are used. If a view component uses these methods, then the view is accessing data that is not connected via withOnyx(). Meaning that if the onyx data changes, the view won't re-render and it will have stale data.

What you'll have to do instead is make sure the component is connected to the REPORT collection, and then using they key function and maybe a selector, you'll have to look up all this stuff from the connected data.

I hope that makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was not awaer of that tracking/ clean up issue, thanks for raising this. It makes sense, annoyingly this is easy to miss, these methods are convenient so that you might not realize the consequence

/**
* Should return true only for personal 1:1 report
*
Expand Down Expand Up @@ -1604,6 +1636,11 @@ function getIcons(
};
const isPayer = currentUserAccountID === report?.managerID;

// For one transaction IOUs, display a simplified report icon
if (isOneTransactionReport(report)) {
return [ownerIcon];
}

return isPayer ? [managerIcon, ownerIcon] : [ownerIcon, managerIcon];
}

Expand Down Expand Up @@ -3944,6 +3981,11 @@ function shouldReportBeInOptionList({
return false;
}

// If this is a transaction thread associated with a report that only has one transaction, omit it
if (isOneTransactionThread(report)) {
return false;
}

// Include the currently viewed report. If we excluded the currently viewed report, then there
// would be no way to highlight it in the options list and it would be confusing to users because they lose
// a sense of context.
Expand Down Expand Up @@ -4388,6 +4430,10 @@ function shouldReportShowSubscript(report: OnyxEntry<Report>): boolean {
return true;
}

if (isExpenseReport(report) && isOneTransactionReport(report)) {
return true;
}

if (isWorkspaceTaskReport(report)) {
return true;
}
Expand Down Expand Up @@ -5173,6 +5219,9 @@ export {
hasSingleParticipant,
getReportRecipientAccountIDs,
isOneOnOneChat,
isOneTransactionReport,
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this method being exported? It doesn't look like it is used anywhere outside of this file (and this is one of those anti-patterns we discussed). In fact, would you mind adding this method to https://github.com/Expensify/App/blob/main/tests/actions/EnforceActionExportRestrictions.ts so that it doesn't get exported and used somewhere else?

Copy link
Contributor

Choose a reason for hiding this comment

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

In fact, now I'm also worried that any of the methods in this file that use isOneTransactionReport() are being exported and used improperly.

getOneTransactionThreadReportID,
isOneTransactionThread,
goBackToDetailsPage,
getTransactionReportName,
getTransactionDetails,
Expand Down
20 changes: 17 additions & 3 deletions src/pages/home/ReportScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ const propTypes = {
/** All the report actions for this report */
reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),

/** The report actions for the first transaction thread associated with the report */
transactionThreadReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)),

/** The report's parentReportAction */
parentReportAction: PropTypes.shape(reportActionPropTypes),

Expand Down Expand Up @@ -103,7 +106,8 @@ const propTypes = {

const defaultProps = {
isSidebarLoaded: false,
reportActions: {},
reportActions: [],
transactionThreadReportActions: [],
parentReportAction: {},
report: {},
reportMetadata: {
Expand Down Expand Up @@ -143,6 +147,7 @@ function ReportScreen({
report: reportProp,
reportMetadata,
reportActions,
transactionThreadReportActions,
parentReportAction,
accountManagerReportID,
markReadyForHydration,
Expand Down Expand Up @@ -263,7 +268,6 @@ function ReportScreen({
const {reportPendingAction, reportErrors} = ReportUtils.getReportOfflinePendingActionAndErrors(report);
const screenWrapperStyle = [styles.appContent, styles.flex1, {marginTop: viewportOffsetTop}];
const isEmptyChat = useMemo(() => _.isEmpty(reportActions), [reportActions]);
// There are no reportActions at all to display and we are still in the process of loading the next set of actions.
const isLoadingInitialReportActions = _.isEmpty(reportActions) && reportMetadata.isLoadingInitialReportActions;
const isOptimisticDelete = lodashGet(report, 'statusNum') === CONST.REPORT.STATUS_NUM.CLOSED;
const shouldHideReport = !ReportUtils.canAccessReport(report, policies, betas);
Expand Down Expand Up @@ -559,7 +563,7 @@ function ReportScreen({
>
{isReportReadyForDisplay && !isLoadingInitialReportActions && !isLoading && (
<ReportActionsView
reportActions={reportActions}
reportActions={_.isEmpty(transactionThreadReportActions) ? reportActions : ReportActionsUtils.getCombinedReportActionsForDisplay(reportActions, transactionThreadReportActions)}
report={report}
isLoadingInitialReportActions={reportMetadata.isLoadingInitialReportActions}
isLoadingNewerReportActions={reportMetadata.isLoadingNewerReportActions}
Expand Down Expand Up @@ -612,6 +616,15 @@ export default compose(
canEvict: false,
selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true),
},
transactionThreadReportActions: {
key: ({route}) => {
const reportID = getReportID(route);
const transactionThreadReportID = reportID && ReportUtils.isOneTransactionReport(reportID) ? ReportUtils.getOneTransactionThreadReportID(reportID) : '0';
return `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadReportID}`
},
canEvict: false,
selector: (reportActions) => ReportActionsUtils.getSortedReportActionsForDisplay(reportActions, true),
},
report: {
key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${getReportID(route)}`,
allowStaleData: true,
Expand Down Expand Up @@ -664,6 +677,7 @@ export default compose(
(prevProps, nextProps) =>
prevProps.isSidebarLoaded === nextProps.isSidebarLoaded &&
_.isEqual(prevProps.reportActions, nextProps.reportActions) &&
_.isEqual(prevProps.transactionThreadReportActions, nextProps.transactionThreadReportActions) &&
_.isEqual(prevProps.reportMetadata, nextProps.reportMetadata) &&
prevProps.isComposerFullSize === nextProps.isComposerFullSize &&
_.isEqual(prevProps.betas, nextProps.betas) &&
Expand Down
23 changes: 23 additions & 0 deletions src/pages/home/report/ReportActionItem.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import * as User from '@userActions/User';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import AnimatedEmptyStateBackground from './AnimatedEmptyStateBackground';
import MiniReportActionContextMenu from './ContextMenu/MiniReportActionContextMenu';
import * as ReportActionContextMenu from './ContextMenu/ReportActionContextMenu';
Expand Down Expand Up @@ -113,6 +114,9 @@ const propTypes = {
/** IOU report for this action, if any */
iouReport: reportPropTypes,

/** Single transaction thread associated with the report, if any */
transactionThreadReport: reportPropTypes,

/** Flag to show, hide the thread divider line */
shouldHideThreadDividerLine: PropTypes.bool,

Expand All @@ -132,6 +136,7 @@ const defaultProps = {
emojiReactions: {},
shouldShowSubscriptAvatar: false,
iouReport: undefined,
transactionThreadReport: undefined,
shouldHideThreadDividerLine: false,
userWallet: {},
parentReportActions: {},
Expand Down Expand Up @@ -658,6 +663,7 @@ function ReportActionItem(props) {
<MoneyRequestView
report={props.report}
shouldShowHorizontalRule={!props.shouldHideThreadDividerLine}
shouldShowAnimatedBackground
/>
</ShowContextMenuContext.Provider>
);
Expand Down Expand Up @@ -700,6 +706,15 @@ function ReportActionItem(props) {
policyReportFields={_.values(props.policyReportFields)}
shouldShowHorizontalRule={!props.shouldHideThreadDividerLine}
/>
{props.transactionThreadReport && !isEmptyObject(props.transactionThreadReport) && (
<ShowContextMenuContext.Provider value={contextValue}>
<MoneyRequestView
report={props.transactionThreadReport}
shouldShowHorizontalRule={!props.shouldHideThreadDividerLine}
shouldShowAnimatedBackground={false}
/>
</ShowContextMenuContext.Provider>
)}
</OfflineWithFeedback>
);
}
Expand Down Expand Up @@ -861,6 +876,13 @@ export default compose(
},
initialValue: {},
},
transactionThreadReport: {
key: ({report}) => {
const transactionThreadReportID = ReportUtils.isOneTransactionReport(report) ? ReportUtils.getOneTransactionThreadReportID(report) : '';
return transactionThreadReportID ? `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}` : undefined;
},
initialValue: {},
},
policyReportFields: {
key: ({report}) => (report && 'policyID' in report ? `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${report.policyID}` : undefined),
initialValue: [],
Expand Down Expand Up @@ -896,6 +918,7 @@ export default compose(
_.isEqual(prevProps.report.pendingFields, nextProps.report.pendingFields) &&
_.isEqual(prevProps.report.isDeletedParentAction, nextProps.report.isDeletedParentAction) &&
_.isEqual(prevProps.report.errorFields, nextProps.report.errorFields) &&
_.isEqual(prevProps.transactionThreadReport, nextProps.transactionThreadReport) &&
lodashGet(prevProps.report, 'statusNum') === lodashGet(nextProps.report, 'statusNum') &&
lodashGet(prevProps.report, 'stateNum') === lodashGet(nextProps.report, 'stateNum') &&
lodashGet(prevProps.report, 'parentReportID') === lodashGet(nextProps.report, 'parentReportID') &&
Expand Down
19 changes: 8 additions & 11 deletions src/styles/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,21 +756,18 @@ function getLineHeightStyle(lineHeight: number): TextStyle {
/**
* Gets the correct size for the empty state container based on screen dimensions
*/
function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false): ViewStyle {
function getReportWelcomeContainerStyle(isSmallScreenWidth: boolean, isMoneyOrTaskReport = false, shouldShowAnimatedBackground = true): ViewStyle {
const emptyStateBackground = isMoneyOrTaskReport ? CONST.EMPTY_STATE_BACKGROUND.MONEY_OR_TASK_REPORT : CONST.EMPTY_STATE_BACKGROUND;
if (isSmallScreenWidth) {
return {
minHeight: emptyStateBackground.SMALL_SCREEN.CONTAINER_MINHEIGHT,
display: 'flex',
justifyContent: 'space-between',
};
}

return {
minHeight: emptyStateBackground.WIDE_SCREEN.CONTAINER_MINHEIGHT,
const baseStyles: ViewStyle = {
display: 'flex',
justifyContent: 'space-between',
};

if (shouldShowAnimatedBackground) {
baseStyles.minHeight = isSmallScreenWidth ? emptyStateBackground.SMALL_SCREEN.CONTAINER_MINHEIGHT : emptyStateBackground.WIDE_SCREEN.CONTAINER_MINHEIGHT;
}

return baseStyles;
}

type GetBaseAutoCompleteSuggestionContainerStyleParams = {
Expand Down
Loading