Skip to content

Commit 0d953f9

Browse files
committed
Merge branch 'main' of github.com:callstack-internal/Expensify-App into perf-send-message-phase-3
2 parents db12251 + 29aca3b commit 0d953f9

3 files changed

Lines changed: 238 additions & 56 deletions

File tree

src/pages/inbox/report/PureReportActionItem.tsx

Lines changed: 12 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ import type {ReportsSplitNavigatorParamList} from '@libs/Navigation/types';
7676
import {getBankAccountLastFourDigits} from '@libs/PaymentUtils';
7777
import Permissions from '@libs/Permissions';
7878
import {getDisplayNameOrDefault} from '@libs/PersonalDetailsUtils';
79-
import {getCleanedTagName, hasDynamicExternalWorkflow, isPolicyAdmin} from '@libs/PolicyUtils';
79+
import {getCleanedTagName, isPolicyAdmin} from '@libs/PolicyUtils';
8080
import {containsActionableFollowUps, parseFollowupsFromHtml} from '@libs/ReportActionFollowupUtils';
8181
import {
8282
extractLinksFromMessageHtml,
@@ -167,8 +167,6 @@ import {
167167
getWorkspaceTagUpdateMessage,
168168
getWorkspaceTaxUpdateMessage,
169169
getWorkspaceUpdateFieldMessage,
170-
hasPendingDEWApprove,
171-
hasPendingDEWSubmit,
172170
isActionableAddPaymentCard,
173171
isActionableCardFraudAlert,
174172
isActionableJoinRequest,
@@ -185,7 +183,6 @@ import {
185183
isDeletedAction,
186184
isDeletedParentAction as isDeletedParentActionUtils,
187185
isIOURequestReportAction,
188-
isMarkAsClosedAction,
189186
isMessageDeleted,
190187
isMoneyRequestAction,
191188
isPendingRemove,
@@ -237,6 +234,7 @@ import type SCREENS from '@src/SCREENS';
237234
import type * as OnyxTypes from '@src/types/onyx';
238235
import type {Errors} from '@src/types/onyx/OnyxCommon';
239236
import {isEmptyObject, isEmptyValueObject} from '@src/types/utils/EmptyObject';
237+
import ApprovalFlowContent, {isApprovalFlowAction} from './actionContents/ApprovalFlowContent';
240238
import ConfirmWhisperContent from './actionContents/ConfirmWhisperContent';
241239
import FraudAlertContent from './actionContents/FraudAlertContent';
242240
import JoinRequestContent from './actionContents/JoinRequestContent';
@@ -1238,47 +1236,16 @@ function PureReportActionItem({
12381236
originalReport={originalReport}
12391237
/>
12401238
);
1241-
} else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) || isMarkAsClosedAction(action)) {
1242-
const wasSubmittedViaHarvesting = !isMarkAsClosedAction(action) ? (getOriginalMessage(action)?.harvesting ?? false) : false;
1243-
const isDEWPolicy = hasDynamicExternalWorkflow(policy);
1244-
1245-
const isPendingAdd = action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD;
1246-
if (wasSubmittedViaHarvesting) {
1247-
children = (
1248-
<ReportActionItemMessageWithExplain
1249-
message={translate('iou.automaticallySubmitted')}
1250-
action={action}
1251-
childReport={childReport}
1252-
originalReport={originalReport}
1253-
/>
1254-
);
1255-
} else if (hasPendingDEWSubmit(reportMetadata, isDEWPolicy) && isPendingAdd) {
1256-
children = <ReportActionItemBasicMessage message={translate('iou.queuedToSubmitViaDEW')} />;
1257-
} else if (isDEWPolicy) {
1258-
// Don't show a memo for DEW actions, it's shown in the Concierge action below
1259-
children = <ReportActionItemBasicMessage message={translate('iou.submitted')} />;
1260-
} else {
1261-
children = <ReportActionItemBasicMessage message={translate('iou.submitted', getOriginalMessage(action)?.message)} />;
1262-
}
1263-
} else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.APPROVED)) {
1264-
const wasAutoApproved = getOriginalMessage(action)?.automaticAction ?? false;
1265-
const isDEWPolicy = hasDynamicExternalWorkflow(policy);
1266-
const isPendingAdd = action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD;
1267-
1268-
if (wasAutoApproved) {
1269-
children = (
1270-
<ReportActionItemMessageWithExplain
1271-
message={translate('iou.automaticallyApproved')}
1272-
action={action}
1273-
childReport={childReport}
1274-
originalReport={originalReport}
1275-
/>
1276-
);
1277-
} else if (hasPendingDEWApprove(reportMetadata, isDEWPolicy) && isPendingAdd) {
1278-
children = <ReportActionItemBasicMessage message={translate('iou.queuedToApproveViaDEW')} />;
1279-
} else {
1280-
children = <ReportActionItemBasicMessage message={translate('iou.approvedMessage')} />;
1281-
}
1239+
} else if (isApprovalFlowAction(action)) {
1240+
children = (
1241+
<ApprovalFlowContent
1242+
action={action}
1243+
policy={policy}
1244+
reportMetadata={reportMetadata}
1245+
childReport={childReport}
1246+
originalReport={originalReport}
1247+
/>
1248+
);
12821249
} else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.IOU) && getOriginalMessage(action)?.type === CONST.IOU.REPORT_ACTION_TYPE.PAY) {
12831250
const wasAutoPaid = getOriginalMessage(action)?.automaticAction ?? false;
12841251
const paymentType = getOriginalMessage(action)?.paymentType;
@@ -1327,17 +1294,6 @@ function PureReportActionItem({
13271294
children = <ReportActionItemBasicMessage message={getReimbursedMessage(translate, action, report, currentUserAccountID)} />;
13281295
} else if (isSimpleMessageAction(action)) {
13291296
children = <SimpleMessageContent action={action} />;
1330-
} else if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.FORWARDED)) {
1331-
const wasAutoForwarded = getOriginalMessage(action)?.automaticAction ?? false;
1332-
if (wasAutoForwarded) {
1333-
children = (
1334-
<ReportActionItemBasicMessage>
1335-
<RenderHTML html={`<comment><muted-text>${translate('iou.automaticallyForwarded')}</muted-text></comment>`} />
1336-
</ReportActionItemBasicMessage>
1337-
);
1338-
} else {
1339-
children = <ReportActionItemBasicMessage message={translate('iou.forwarded')} />;
1340-
}
13411297
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.CORPORATE_UPGRADE) {
13421298
children = <ReportActionItemBasicMessage message={translate('workspaceActions.upgradedWorkspace')} />;
13431299
} else if (action.actionName === CONST.REPORT.ACTIONS.TYPE.POLICY_CHANGE_LOG.CORPORATE_FORCE_UPGRADE) {
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React from 'react';
2+
import type {OnyxEntry} from 'react-native-onyx';
3+
import RenderHTML from '@components/RenderHTML';
4+
import useLocalize from '@hooks/useLocalize';
5+
import {hasDynamicExternalWorkflow} from '@libs/PolicyUtils';
6+
import {getOriginalMessage, hasPendingDEWApprove, hasPendingDEWSubmit, isActionOfType, isMarkAsClosedAction} from '@libs/ReportActionsUtils';
7+
import ReportActionItemBasicMessage from '@pages/inbox/report/ReportActionItemBasicMessage';
8+
import ReportActionItemMessageWithExplain from '@pages/inbox/report/ReportActionItemMessageWithExplain';
9+
import CONST from '@src/CONST';
10+
import type * as OnyxTypes from '@src/types/onyx';
11+
12+
type ApprovalFlowContentProps = {
13+
action: OnyxTypes.ReportAction;
14+
policy: OnyxEntry<OnyxTypes.Policy>;
15+
reportMetadata: OnyxEntry<OnyxTypes.ReportMetadata>;
16+
childReport: OnyxEntry<OnyxTypes.Report>;
17+
originalReport: OnyxEntry<OnyxTypes.Report>;
18+
};
19+
20+
function isApprovalFlowAction(action: OnyxTypes.ReportAction): boolean {
21+
return (
22+
isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) ||
23+
isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) ||
24+
isMarkAsClosedAction(action) ||
25+
isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.APPROVED) ||
26+
isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.FORWARDED)
27+
);
28+
}
29+
30+
function ApprovalFlowContent({action, policy, reportMetadata, childReport, originalReport}: ApprovalFlowContentProps) {
31+
const {translate} = useLocalize();
32+
const isDEWPolicy = hasDynamicExternalWorkflow(policy);
33+
const isPendingAdd = action?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD;
34+
35+
if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED) || isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.SUBMITTED_AND_CLOSED) || isMarkAsClosedAction(action)) {
36+
const wasSubmittedViaHarvesting = !isMarkAsClosedAction(action) ? (getOriginalMessage(action)?.harvesting ?? false) : false;
37+
38+
if (wasSubmittedViaHarvesting) {
39+
return (
40+
<ReportActionItemMessageWithExplain
41+
message={translate('iou.automaticallySubmitted')}
42+
action={action}
43+
childReport={childReport}
44+
originalReport={originalReport}
45+
/>
46+
);
47+
}
48+
49+
if (hasPendingDEWSubmit(reportMetadata, isDEWPolicy) && isPendingAdd) {
50+
return <ReportActionItemBasicMessage message={translate('iou.queuedToSubmitViaDEW')} />;
51+
}
52+
53+
if (isDEWPolicy) {
54+
// Don't show a memo for DEW actions, it's shown in the Concierge action below
55+
return <ReportActionItemBasicMessage message={translate('iou.submitted')} />;
56+
}
57+
58+
return <ReportActionItemBasicMessage message={translate('iou.submitted', getOriginalMessage(action)?.message)} />;
59+
}
60+
61+
if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.APPROVED)) {
62+
const wasAutoApproved = getOriginalMessage(action)?.automaticAction ?? false;
63+
64+
if (wasAutoApproved) {
65+
return (
66+
<ReportActionItemMessageWithExplain
67+
message={translate('iou.automaticallyApproved')}
68+
action={action}
69+
childReport={childReport}
70+
originalReport={originalReport}
71+
/>
72+
);
73+
}
74+
75+
if (hasPendingDEWApprove(reportMetadata, isDEWPolicy) && isPendingAdd) {
76+
return <ReportActionItemBasicMessage message={translate('iou.queuedToApproveViaDEW')} />;
77+
}
78+
79+
return <ReportActionItemBasicMessage message={translate('iou.approvedMessage')} />;
80+
}
81+
82+
if (isActionOfType(action, CONST.REPORT.ACTIONS.TYPE.FORWARDED)) {
83+
const wasAutoForwarded = getOriginalMessage(action)?.automaticAction ?? false;
84+
85+
if (wasAutoForwarded) {
86+
return (
87+
<ReportActionItemBasicMessage>
88+
<RenderHTML html={`<comment><muted-text>${translate('iou.automaticallyForwarded')}</muted-text></comment>`} />
89+
</ReportActionItemBasicMessage>
90+
);
91+
}
92+
93+
return <ReportActionItemBasicMessage message={translate('iou.forwarded')} />;
94+
}
95+
96+
return null;
97+
}
98+
99+
ApprovalFlowContent.displayName = 'ApprovalFlowContent';
100+
101+
export default ApprovalFlowContent;
102+
export {isApprovalFlowAction};

tests/ui/PureReportActionItemTest.tsx

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,130 @@ describe('PureReportActionItem', () => {
482482
expect(screen.getByText(translateLocal('iou.submitted'))).toBeOnTheScreen();
483483
expect(screen.queryByText(translateLocal('iou.queuedToSubmitViaDEW'))).not.toBeOnTheScreen();
484484
});
485+
486+
it('should display DEW queued message for pending APPROVED action when policy has DEW enabled', async () => {
487+
const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.APPROVED, {automaticAction: false});
488+
action.pendingAction = CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD;
489+
490+
const dewPolicy = {
491+
id: 'testPolicy',
492+
name: 'Test DEW Policy',
493+
type: CONST.POLICY.TYPE.TEAM,
494+
role: CONST.POLICY.ROLE.ADMIN,
495+
owner: 'owner@test.com',
496+
outputCurrency: CONST.CURRENCY.USD,
497+
isPolicyExpenseChatEnabled: true,
498+
approvalMode: CONST.POLICY.APPROVAL_MODE.DYNAMICEXTERNAL,
499+
} as const;
500+
501+
const reportMetadata = {
502+
pendingExpenseAction: CONST.EXPENSE_PENDING_ACTION.APPROVE,
503+
};
504+
505+
await act(async () => {
506+
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}testPolicy`, dewPolicy);
507+
});
508+
await waitForBatchedUpdatesWithAct();
509+
510+
render(
511+
<ComposeProviders components={[OnyxListItemProvider, LocaleContextProvider, HTMLEngineProvider]}>
512+
<OptionsListContextProvider>
513+
<ScreenWrapper testID="test">
514+
<PortalProvider>
515+
<PureReportActionItem
516+
personalPolicyID={undefined}
517+
currentUserEmail={undefined}
518+
policy={dewPolicy as Policy}
519+
report={{reportID: 'testReport', policyID: 'testPolicy'}}
520+
parentReportAction={undefined}
521+
action={action}
522+
displayAsGroup={false}
523+
shouldDisplayNewMarker={false}
524+
index={0}
525+
isFirstVisibleReportAction={false}
526+
taskReport={undefined}
527+
linkedReport={undefined}
528+
iouReportOfLinkedReport={undefined}
529+
reportMetadata={reportMetadata}
530+
currentUserAccountID={ACTOR_ACCOUNT_ID}
531+
betas={undefined}
532+
draftTransactionIDs={[]}
533+
userBillingGracePeriodEnds={undefined}
534+
/>
535+
</PortalProvider>
536+
</ScreenWrapper>
537+
</OptionsListContextProvider>
538+
</ComposeProviders>,
539+
);
540+
await waitForBatchedUpdatesWithAct();
541+
542+
expect(screen.getByText(actorEmail)).toBeOnTheScreen();
543+
expect(screen.getByText(translateLocal('iou.queuedToApproveViaDEW'))).toBeOnTheScreen();
544+
});
545+
546+
it('should display submitted without memo for SUBMITTED action on DEW policy that is not pending', async () => {
547+
const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.SUBMITTED, {harvesting: false, message: 'my memo'});
548+
549+
const dewPolicy = {
550+
id: 'testPolicy',
551+
name: 'Test DEW Policy',
552+
type: CONST.POLICY.TYPE.TEAM,
553+
role: CONST.POLICY.ROLE.ADMIN,
554+
owner: 'owner@test.com',
555+
outputCurrency: CONST.CURRENCY.USD,
556+
isPolicyExpenseChatEnabled: true,
557+
approvalMode: CONST.POLICY.APPROVAL_MODE.DYNAMICEXTERNAL,
558+
} as const;
559+
560+
await act(async () => {
561+
await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}testPolicy`, dewPolicy);
562+
});
563+
await waitForBatchedUpdatesWithAct();
564+
565+
render(
566+
<ComposeProviders components={[OnyxListItemProvider, LocaleContextProvider, HTMLEngineProvider]}>
567+
<OptionsListContextProvider>
568+
<ScreenWrapper testID="test">
569+
<PortalProvider>
570+
<PureReportActionItem
571+
personalPolicyID={undefined}
572+
currentUserEmail={undefined}
573+
policy={dewPolicy as Policy}
574+
report={{reportID: 'testReport', policyID: 'testPolicy'}}
575+
parentReportAction={undefined}
576+
action={action}
577+
displayAsGroup={false}
578+
shouldDisplayNewMarker={false}
579+
index={0}
580+
isFirstVisibleReportAction={false}
581+
taskReport={undefined}
582+
linkedReport={undefined}
583+
iouReportOfLinkedReport={undefined}
584+
currentUserAccountID={ACTOR_ACCOUNT_ID}
585+
betas={undefined}
586+
draftTransactionIDs={[]}
587+
userBillingGracePeriodEnds={undefined}
588+
/>
589+
</PortalProvider>
590+
</ScreenWrapper>
591+
</OptionsListContextProvider>
592+
</ComposeProviders>,
593+
);
594+
await waitForBatchedUpdatesWithAct();
595+
596+
expect(screen.getByText(actorEmail)).toBeOnTheScreen();
597+
// DEW policy should show submitted without the memo (memo is shown in the Concierge action)
598+
expect(screen.getByText(translateLocal('iou.submitted'))).toBeOnTheScreen();
599+
expect(screen.queryByText('my memo')).not.toBeOnTheScreen();
600+
});
601+
602+
it('CLOSED action with amount (MARK_AS_CLOSED) renders submitted message', async () => {
603+
const action = createReportAction(CONST.REPORT.ACTIONS.TYPE.CLOSED, {amount: 5000});
604+
renderItemWithAction(action);
605+
await waitForBatchedUpdatesWithAct();
606+
607+
expect(screen.getByText(translateLocal('iou.submitted'))).toBeOnTheScreen();
608+
});
485609
});
486610

487611
describe('Followup list buttons', () => {

0 commit comments

Comments
 (0)