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

[HOLD] Handle errors on manual requests #22725

Closed
wants to merge 47 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
b3a44d3
Add RBR indicator on report preview
neil-marcellini Jul 12, 2023
96e14de
Fix disable pay button and when report has errors
neil-marcellini Jul 12, 2023
7fa160b
Display money request transaction error messages
neil-marcellini Jul 12, 2023
efd0783
Add transactionErrors prop type and default
neil-marcellini Jul 12, 2023
004ffed
WIP cleanUpFailedMoneyRequest
neil-marcellini Jul 12, 2023
e5a112e
Fix selector for transaction errors
neil-marcellini Jul 12, 2023
2dcd690
Get last message text from actions
neil-marcellini Jul 12, 2023
e0ed7da
WIP Clean up failed request with updated total
neil-marcellini Jul 12, 2023
00b5d8f
Merge branch 'main' into neil-request-errors
neil-marcellini Aug 28, 2023
51e5128
Remove unused prop after merge
neil-marcellini Aug 28, 2023
1568548
Remove duplicate error message
neil-marcellini Aug 28, 2023
ba72483
Clean up money request when clearing action errors
neil-marcellini Aug 28, 2023
e3338f9
Fix crash by getting preview from workspace chat
neil-marcellini Aug 30, 2023
a6b9ed0
Merge branch 'main' into neil-request-errors
neil-marcellini Sep 6, 2023
b554a58
Reset the report total after failed money request
neil-marcellini Sep 7, 2023
2cf82a2
Merge branch 'main' into neil-request-errors
neil-marcellini Sep 20, 2023
a535623
Fix settlement button after merge
neil-marcellini Sep 21, 2023
eaeccd2
WIP debug deleting action
neil-marcellini Sep 21, 2023
593d8e8
Fix deleting iou action, remove debugging
neil-marcellini Sep 22, 2023
e43a5c4
Show error message when iouReport creation fails
neil-marcellini Sep 25, 2023
c844962
Merge branch 'main' into neil-request-errors
thienlnam Sep 28, 2023
79a6a9a
Now that the onyx PR is merged, we can remove this workaround
thienlnam Sep 28, 2023
1eb4d6e
Merge branch 'main' into neil-request-errors
thienlnam Sep 29, 2023
7ccf623
Bump the onyx version
thienlnam Sep 29, 2023
51e2ad3
Update types / delete the reportAction
thienlnam Sep 29, 2023
82d0567
prettier
thienlnam Sep 29, 2023
cf6fd9e
Merge branch 'main' into neil-request-errors
neil-marcellini Oct 6, 2023
9f53c77
Style
neil-marcellini Oct 6, 2023
5faca22
Delete preview for failed expense report on open
neil-marcellini Oct 6, 2023
c681fcd
Use existing clearReportActionErrors function
neil-marcellini Oct 10, 2023
149d25e
Fix report preview error indicator
neil-marcellini Oct 10, 2023
d6a8619
Simplify money request clean up
neil-marcellini Oct 10, 2023
e7abeff
More clean up
neil-marcellini Oct 10, 2023
6eaacb7
Merge branch 'main' into neil-request-errors
neil-marcellini Oct 10, 2023
7097883
Set report preview error the same as in chat list
neil-marcellini Oct 10, 2023
dc8bc3b
Undo useless changes to settlement button
neil-marcellini Oct 10, 2023
16f5326
remove redundant boolean prop
neil-marcellini Oct 10, 2023
1a9ac08
Fix check for creation error with null values
neil-marcellini Oct 10, 2023
aba3c09
Fix console error
neil-marcellini Oct 11, 2023
cd2ee04
Ensure chat report's iouReport fields is reset
neil-marcellini Oct 11, 2023
b435855
Add JSDoc
neil-marcellini Oct 11, 2023
8476ff0
Fix lint again
neil-marcellini Oct 11, 2023
1c14141
Linked report must exist before deleting actions
neil-marcellini Oct 11, 2023
3e3937c
Merge branch 'main' into neil-request-errors
neil-marcellini Oct 11, 2023
525fe27
Fix missing code after merge
neil-marcellini Oct 11, 2023
64866e7
Implement review feedback
neil-marcellini Oct 13, 2023
8d099a4
More descriptive field comment
neil-marcellini Oct 13, 2023
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
161 changes: 85 additions & 76 deletions src/components/ReportActionItem/ReportPreview.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import * as ReceiptUtils from '../../libs/ReceiptUtils';
import * as ReportActionUtils from '../../libs/ReportActionsUtils';
import * as TransactionUtils from '../../libs/TransactionUtils';
import ReportActionItemImages from './ReportActionItemImages';
import OfflineWithFeedback from '../OfflineWithFeedback';
import * as OptionListUtils from '../../libs/OptionsListUtils';

const propTypes = {
/** All the data of the action */
Expand Down Expand Up @@ -124,7 +126,7 @@ function ReportPreview(props) {
const numberOfScanningReceipts = _.filter(transactionsWithReceipts, (transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length;
const hasReceipts = transactionsWithReceipts.length > 0;
const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action);
const hasErrors = hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID);
const hasErrors = !_.isEmpty(OptionListUtils.getAllReportErrors(props.iouReport, {[props.action.reportActionID]: props.action}));
const lastThreeTransactionsWithReceipts = ReportUtils.getReportPreviewDisplayTransactions(props.action);
const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, ({receipt, filename}) => ReceiptUtils.getThumbnailAndImageURIs(receipt.source, filename || ''));

Expand Down Expand Up @@ -175,88 +177,95 @@ function ReportPreview(props) {
: !_.isEmpty(props.iouReport) && isCurrentUserManager && !isReportDraft && !iouSettled && !iouCanceled && !props.iouReport.isWaitingOnBankAccount && reportTotal !== 0;

return (
<View style={[styles.chatItemMessage, ...props.containerStyles]}>
<PressableWithoutFeedback
onPress={() => {
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(props.iouReportID));
}}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)}
style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]}
accessibilityRole="button"
accessibilityLabel={props.translate('iou.viewDetails')}
>
<View style={[styles.reportPreviewBox, props.isHovered || isScanning || props.isWhisper ? styles.reportPreviewBoxHoverBorder : undefined]}>
{hasReceipts && (
<ReportActionItemImages
images={lastThreeReceipts}
size={3}
total={transactionsWithReceipts.length}
isHovered={props.isHovered || isScanning}
/>
)}
<View style={styles.reportPreviewBoxBody}>
<View style={styles.flexRow}>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]}>
<Text style={[styles.textLabelSupporting, styles.mb1, styles.lh20]}>{getPreviewMessage()}</Text>
</View>
{hasErrors && (
<Icon
src={Expensicons.DotIndicator}
fill={themeColors.danger}
/>
)}
</View>
<View style={styles.flexRow}>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]}>
<Text style={styles.textHeadline}>{getDisplayAmount()}</Text>
{ReportUtils.isSettled(props.iouReportID) && (
<View style={styles.defaultCheckmarkWrapper}>
<Icon
src={Expensicons.Checkmark}
fill={themeColors.iconSuccessFill}
/>
</View>
<OfflineWithFeedback
onClose={() => IOU.cleanUpFailedMoneyRequest(props.iouReport.reportID, props.action)}
pendingAction={props.action.pendingAction}
errors={lodashGet(props.iouReport, 'errorFields.createChat')}
shouldDisableStrikeThrough
>
<View style={[styles.chatItemMessage, ...props.containerStyles]}>
<PressableWithoutFeedback
onPress={() => {
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(props.iouReportID));
}}
onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()}
onPressOut={() => ControlSelection.unblock()}
onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)}
style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]}
accessibilityRole="button"
accessibilityLabel={props.translate('iou.viewDetails')}
>
<View style={[styles.reportPreviewBox, props.isHovered || isScanning || props.isWhisper ? styles.reportPreviewBoxHoverBorder : undefined]}>
{hasReceipts && (
<ReportActionItemImages
images={lastThreeReceipts}
size={3}
total={transactionsWithReceipts.length}
isHovered={props.isHovered || isScanning}
/>
)}
<View style={styles.reportPreviewBoxBody}>
<View style={styles.flexRow}>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]}>
<Text style={[styles.textLabelSupporting, styles.mb1, styles.lh20]}>{getPreviewMessage()}</Text>
</View>
{hasErrors && (
<Icon
src={Expensicons.DotIndicator}
fill={themeColors.danger}
/>
)}
</View>
</View>
{!isScanning && (numberOfRequests > 1 || hasReceipts) && (
<View style={styles.flexRow}>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]}>
<Text style={[styles.textLabelSupporting, styles.mb1, styles.lh20]}>{previewSubtitle || moneyRequestComment}</Text>
<Text style={styles.textHeadline}>{getDisplayAmount()}</Text>
{ReportUtils.isSettled(props.iouReportID) && (
<View style={styles.defaultCheckmarkWrapper}>
<Icon
src={Expensicons.Checkmark}
fill={themeColors.iconSuccessFill}
/>
</View>
)}
</View>
</View>
)}
{shouldShowSettlementButton && (
<SettlementButton
currency={props.iouReport.currency}
policyID={props.policyID}
chatReportID={props.chatReportID}
iouReport={props.iouReport}
onPress={(paymentType) => IOU.payMoneyRequest(paymentType, props.chatReport, props.iouReport)}
enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS}
addBankAccountRoute={bankAccountRoute}
style={[styles.requestPreviewBox]}
anchorAlignment={{
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
/>
)}
{shouldShowSubmitButton && (
<Button
medium
success={props.chatReport.isOwnPolicyExpenseChat}
text={translate('common.submit')}
style={styles.requestPreviewBox}
onPress={() => IOU.submitReport(props.iouReport)}
/>
)}
{!isScanning && (numberOfRequests > 1 || hasReceipts) && (
<View style={styles.flexRow}>
<View style={[styles.flex1, styles.flexRow, styles.alignItemsCenter]}>
<Text style={[styles.textLabelSupporting, styles.mb1, styles.lh20]}>{previewSubtitle || moneyRequestComment}</Text>
</View>
</View>
)}
{shouldShowSettlementButton && (
<SettlementButton
currency={props.iouReport.currency}
policyID={props.policyID}
chatReportID={props.chatReportID}
iouReport={props.iouReport}
onPress={(paymentType) => IOU.payMoneyRequest(paymentType, props.chatReport, props.iouReport)}
enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS}
addBankAccountRoute={bankAccountRoute}
style={[styles.requestPreviewBox]}
anchorAlignment={{
horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT,
vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM,
}}
/>
)}
{shouldShowSubmitButton && (
<Button
medium
success={props.chatReport.isOwnPolicyExpenseChat}
text={translate('common.submit')}
style={styles.requestPreviewBox}
onPress={() => IOU.submitReport(props.iouReport)}
/>
)}
</View>
</View>
</View>
</PressableWithoutFeedback>
</View>
</PressableWithoutFeedback>
</View>
</OfflineWithFeedback>
);
}

Expand Down
13 changes: 13 additions & 0 deletions src/libs/actions/IOU.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import * as Report from './Report';
import * as NumberUtils from '../NumberUtils';
import ReceiptGeneric from '../../../assets/images/receipt-generic.png';
import * as LocalePhoneNumber from '../LocalePhoneNumber';
import * as ReportActions from './ReportActions';
import * as Policy from './Policy';

let allPersonalDetails;
Expand Down Expand Up @@ -364,6 +365,17 @@ function buildOnyxDataForMoneyRequest(
return [optimisticData, successData, failureData];
}

/**
* @param {String} iouReportID
* @param {Object} iouAction
*/
function cleanUpFailedMoneyRequest(iouReportID, iouAction) {
const iouReport = allReports[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`];
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReport.reportID}`, null);
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, null);
ReportActions.clearReportActionErrors(iouReportID, iouAction);
}

/**
* Gathers all the data needed to make a money request. It attempts to find existing reports, iouReports, and receipts. If it doesn't find them, then
* it creates optimistic versions of them and uses those instead
Expand Down Expand Up @@ -2658,6 +2670,7 @@ export {
resetMoneyRequestTag,
setMoneyRequestBillable,
setMoneyRequestParticipants,
cleanUpFailedMoneyRequest,
setMoneyRequestReceipt,
createEmptyTransaction,
navigateToNextPage,
Expand Down
22 changes: 22 additions & 0 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,28 @@ function openReport(reportID, participantLoginList = [], newReportObject = {}, p
},
];

// If an expense report is being opened that has a failed money request, remove the report preview action in the failure data
// We need to do this because if the failed request was the first one on the workspace then the expense report will have also failed to create. Opening the report is going to fail and
// delete the report, so to complete the clean up we also need to get rid of the report preview action
const currentReport = lodashGet(allReports, `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {});
const reportCreationErrors = _.compact(lodashGet(currentReport, 'errorFields.createChat'));
if (ReportUtils.isExpenseReport(currentReport) && currentReport.parentReportID && currentReport.parentReportActionID && !_.isEmpty(reportCreationErrors)) {
reportFailureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReport.parentReportID}`,
value: {
[currentReport.parentReportActionID]: null,
},
});
reportFailureData.push({
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${currentReport.parentReportID}`,
value: {
iouReportID: null,
},
});
}

const onyxData = {
optimisticData: optimisticReportData,
successData: reportSuccessData,
Expand Down
13 changes: 11 additions & 2 deletions src/libs/actions/ReportActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,26 @@ function clearReportActionErrors(reportID: string, reportAction: ReportAction) {
}

if (reportAction.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) {
// Delete the optimistic action
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${originalReportID}`, {
[reportAction.reportActionID]: null,
});

// If the reportAction is linked to a different report, delete it from that report too
if (reportAction?.reportID && reportAction?.reportID !== originalReportID) {
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportAction.reportID}`, {
[reportAction.reportActionID]: null,
});
}

Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${originalReportID}`, {
errorFields: null,
});

// If there's a linked transaction, delete that too
const linkedTransactionID = ReportActionUtils.getLinkedTransactionID(originalReportID, reportAction.reportActionID);
if (linkedTransactionID) {
Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID}`, null);
}

return;
}

Expand Down
2 changes: 1 addition & 1 deletion src/pages/home/report/ReportActionItemSingle.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const showWorkspaceDetails = (reportID) => {
};

function ReportActionItemSingle(props) {
const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID : props.action.actorAccountID;
const actorAccountID = props.action.actionName === CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && props.iouReport ? props.iouReport.managerID || 0 : props.action.actorAccountID;
let {displayName} = props.personalDetailsList[actorAccountID] || {};
const {avatar, login, pendingFields, status, fallbackIcon} = props.personalDetailsList[actorAccountID] || {};
let actorHint = (login || displayName || '').replace(CONST.REGEX.MERGED_ACCOUNT_PREFIX, '');
Expand Down
2 changes: 2 additions & 0 deletions src/types/onyx/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ type Report = {
participantAccountIDs?: number[];
total?: number;
currency?: string;
errorFields?: string[];
preexistingReportID?: string;

/** If the report contains nonreimbursable expenses, send the nonreimbursable total */
nonReimbursableTotal?: number;
};
Expand Down
3 changes: 3 additions & 0 deletions src/types/onyx/ReportAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ type ReportActionBase = {
/** The ID of the previous reportAction on the report. It is a string represenation of a 64-bit integer (or null for CREATED actions). */
previousReportActionID?: string;

/** The ID of the report which the action belongs to */
reportID?: string;

actorAccountID?: number;

/** Person who created the action */
Expand Down